1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 * Defines the PN80T spidev device and platform wrappers consumed in
17 * the common code.
18 */
19
20#include <fcntl.h>
21#include <limits.h>
22#include <linux/spi/spidev.h>
23#include <stdint.h>
24#include <stdio.h>
25#include <stdlib.h>
26#include <string.h>
27#include <sys/ioctl.h>
28#include <unistd.h>
29
30#include "../include/ese/hw/nxp/pn80t/common.h"
31#include "../include/ese/hw/nxp/spi_board.h"
32
33struct Handle {
34  int spi_fd;
35  struct NxpSpiBoard *board;
36};
37
38int gpio_set(int num, int val) {
39  char val_path[256];
40  char val_chr = (val ? '1' : '0');
41  int fd;
42  if (num < 0) {
43    return 0;
44  }
45  if (snprintf(val_path, sizeof(val_path), "/sys/class/gpio/gpio%d/value",
46               num) >= (int)sizeof(val_path)) {
47    return -1;
48  }
49  printf("Gpio @ %s\n", val_path);
50  fd = open(val_path, O_WRONLY);
51  if (fd < 0) {
52    return -1;
53  }
54  if (write(fd, &val_chr, 1) < 0) {
55    close(fd);
56    return -1;
57  }
58  close(fd);
59  return 0;
60}
61
62int platform_toggle_ven(void *blob, int val) {
63  struct Handle *handle = blob;
64  printf("Toggling VEN: %d\n", val);
65  return gpio_set(handle->board->gpios[kBoardGpioNfcVen], val);
66}
67
68int platform_toggle_reset(void *blob, int val) {
69  struct Handle *handle = blob;
70  printf("Toggling RST: %d\n", val);
71  return gpio_set(handle->board->gpios[kBoardGpioEseRst], val);
72}
73
74int platform_toggle_power_req(void *blob, int val) {
75  struct Handle *handle = blob;
76  printf("Toggling SVDD_PWR_REQ: %d\n", val);
77  return gpio_set(handle->board->gpios[kBoardGpioEseSvddPwrReq], val);
78}
79
80int gpio_configure(int num, int out, int val) {
81  char dir_path[256];
82  char numstr[8];
83  char dir[5];
84  int fd;
85  /* <0 is unmapped. No work to do! */
86  if (num < 0) {
87    return 0;
88  }
89  if (snprintf(dir, sizeof(dir), "%s", (out ? "out" : "in")) >=
90      (int)sizeof(dir)) {
91    return -1;
92  }
93  if (snprintf(dir_path, sizeof(dir_path), "/sys/class/gpio/gpio%d/direction",
94               num) >= (int)sizeof(dir_path)) {
95    return -1;
96  }
97  if (snprintf(numstr, sizeof(numstr), "%d", num) >= (int)sizeof(numstr)) {
98    return -1;
99  }
100  fd = open("/sys/class/gpio/export", O_WRONLY);
101  if (fd < 0) {
102    return -1;
103  }
104  /* Exporting can only happen once, so instead of stat()ing, just ignore
105   * errors. */
106  (void)write(fd, numstr, strlen(numstr));
107  close(fd);
108
109  fd = open(dir_path, O_WRONLY);
110  if (fd < 0) {
111    return -1;
112  }
113  if (write(fd, dir, strlen(dir)) < 0) {
114    close(fd);
115    return -1;
116  }
117  close(fd);
118  return gpio_set(num, val);
119}
120
121void *platform_init(void *hwopts) {
122  struct NxpSpiBoard *board = hwopts;
123  struct Handle *handle;
124  int gpio = 0;
125
126  handle = malloc(sizeof(*handle));
127  if (!handle) {
128    return NULL;
129  }
130  handle->board = board;
131
132  /* Initialize the mapped GPIOs */
133  for (; gpio < kBoardGpioMax; ++gpio) {
134    if (gpio_configure(board->gpios[gpio], 1, 1) < 0) {
135      free(handle);
136      return NULL;
137    }
138  }
139
140  handle->spi_fd = open(board->dev_path, O_RDWR);
141  if (handle->spi_fd < 0) {
142    free(handle);
143    return NULL;
144  }
145  /* If we need anything fancier, we'll need MODE32 in the headers. */
146  if (ioctl(handle->spi_fd, SPI_IOC_WR_MODE, &board->mode) < 0) {
147    close(handle->spi_fd);
148    free(handle);
149    return NULL;
150  }
151  if (ioctl(handle->spi_fd, SPI_IOC_WR_BITS_PER_WORD, &board->bits) < 0) {
152    close(handle->spi_fd);
153    free(handle);
154    return NULL;
155  }
156  if (ioctl(handle->spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &board->speed) < 0) {
157    close(handle->spi_fd);
158    free(handle);
159    return NULL;
160  }
161  printf("Linux SPIDev initialized\n");
162  return (void *)handle;
163}
164
165int platform_release(void *blob) {
166  struct Handle *handle = blob;
167  close(handle->spi_fd);
168  free(handle);
169  /* Note, we don't unconfigure the GPIOs. */
170  return 0;
171}
172
173int platform_wait(void *blob __attribute__((unused)), long usec) {
174  return usleep((useconds_t)usec);
175}
176
177uint32_t spidev_transmit(struct EseInterface *ese, const uint8_t *buf,
178                         uint32_t len, int complete) {
179  struct NxpState *ns = NXP_PN80T_STATE(ese);
180  struct Handle *handle = ns->handle;
181  struct spi_ioc_transfer tr = {
182      .tx_buf = (unsigned long)buf,
183      .rx_buf = 0,
184      .len = (uint32_t)len,
185      .delay_usecs = 0,
186      .speed_hz = 0,
187      .bits_per_word = 0,
188      .cs_change = !!complete,
189  };
190  ssize_t ret = -1;
191  ALOGV("spidev:%s: called [%d]", __func__, len);
192  if (len > INT_MAX) {
193    ese_set_error(ese, kNxpPn80tErrorTransmitSize);
194    ALOGE("Unexpectedly large transfer attempted: %u", len);
195    return 0;
196  }
197  ret = ioctl(handle->spi_fd, SPI_IOC_MESSAGE(1), &tr);
198  if (ret < 1) {
199    ese_set_error(ese, kNxpPn80tErrorTransmit);
200    ALOGE("%s: failed to write to hw (ret=%zd)", __func__, ret);
201    return 0;
202  }
203  return len;
204}
205
206uint32_t spidev_receive(struct EseInterface *ese, uint8_t *buf, uint32_t len,
207                        int complete) {
208  struct NxpState *ns = NXP_PN80T_STATE(ese);
209  struct Handle *handle = ns->handle;
210  ssize_t ret = -1;
211  struct spi_ioc_transfer tr = {
212      .tx_buf = 0,
213      .rx_buf = (unsigned long)buf,
214      .len = (uint32_t)len,
215      .delay_usecs = 0,
216      .speed_hz = 0,
217      .bits_per_word = 0,
218      .cs_change = !!complete,
219  };
220  ALOGV("spidev:%s: called [%d]", __func__, len);
221  if (len > INT_MAX) {
222    ese_set_error(ese, kNxpPn80tErrorReceiveSize);
223    ALOGE("Unexpectedly large receive attempted: %u", len);
224    return 0;
225  }
226  ret = ioctl(handle->spi_fd, SPI_IOC_MESSAGE(1), &tr);
227  if (ret < 1) {
228    ALOGE("%s: failed to read from hw (ret=%zd)", __func__, ret);
229    ese_set_error(ese, kNxpPn80tErrorReceive);
230    return 0;
231  }
232  ALOGV("%s: read bytes: %zd", __func__, len);
233  return len;
234}
235
236static const struct Pn80tPlatform kPn80tLinuxSpidevPlatform = {
237    .initialize = &platform_init,
238    .release = &platform_release,
239    .toggle_reset = &platform_toggle_reset,
240    .toggle_ven = &platform_toggle_ven,
241    .toggle_power_req = &platform_toggle_power_req,
242    .toggle_bootloader = NULL,
243    .wait = &platform_wait,
244};
245
246static const struct EseOperations ops = {
247    .name = "NXP PN80T/PN81A (PN553)",
248    .open = &nxp_pn80t_open,
249    .hw_receive = &spidev_receive,
250    .hw_transmit = &spidev_transmit,
251    .hw_reset = &nxp_pn80t_reset,
252    .transceive = &nxp_pn80t_transceive,
253    .poll = &nxp_pn80t_poll,
254    .close = &nxp_pn80t_close,
255    .opts = &kPn80tLinuxSpidevPlatform,
256    .errors = kNxpPn80tErrorMessages,
257    .errors_count = kNxpPn80tErrorMax,
258};
259__attribute__((visibility("default")))
260ESE_DEFINE_HW_OPS(ESE_HW_NXP_PN80T_SPIDEV, ops);
261