| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| * Platform implementation for a nq-nci extension driver. |
| * |
| * The driver presents the following interface on a miscdev: |
| * - ioctl(): |
| * - for setting and getting power. |
| * This handles SVDD_PWR_REQ and NFC_VEN muxing. |
| * (ESE_RST is not connected in this case.) |
| * - read(): |
| * - For reading arbitrary amounts of data. |
| * CS is asserted and deasserted on each call, but the clock |
| * also appears to do the same which keeps the ese data available |
| * as far as I can tell. |
| * - write(): |
| * - For writing arbitrary amounts of data. |
| * CS is asserted as with read() calls, so the less fragmented |
| * the better. |
| * |
| * All GPIO toggling and chip select requirements are handled behind this |
| * interface. |
| * |
| */ |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <limits.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/ioctl.h> |
| #include <unistd.h> |
| |
| #include "../include/ese/hw/nxp/pn80t/common.h" |
| |
| #ifndef UNUSED |
| #define UNUSED(x) x __attribute__((unused)) |
| #endif |
| |
| /* From kernel/drivers/nfc/nq-nci.h */ |
| #define ESE_SET_PWR _IOW(0xE9, 0x02, unsigned int) |
| #define ESE_GET_PWR _IOR(0xE9, 0x03, unsigned int) |
| #define ESE_CLEAR_GPIO _IOW(0xE9, 0x11, unsigned int) |
| |
| static const char kDevicePath[] = "/dev/pn81a"; |
| |
| struct PlatformHandle { |
| int fd; |
| }; |
| |
| int platform_toggle_bootloader(void *blob, int val) { |
| const struct PlatformHandle *handle = blob; |
| if (!handle) { |
| return -1; |
| } |
| return ioctl(handle->fd, ESE_CLEAR_GPIO, val); |
| } |
| |
| int platform_toggle_reset(void *blob, int val) { |
| const struct PlatformHandle *handle = blob; |
| if (!handle) { |
| return -1; |
| } |
| /* 0=power and 1=no power in the kernel. */ |
| return ioctl(handle->fd, ESE_SET_PWR, !val); |
| } |
| |
| void *platform_init(void *hwopts) { |
| /* TODO(wad): It may make sense to pass in the dev path here. */ |
| if (hwopts != NULL) { |
| return NULL; |
| } |
| |
| struct PlatformHandle *handle = calloc(1, sizeof(*handle)); |
| if (!handle) { |
| ALOGE("%s: unable to allocate memory for handle", __func__); |
| return NULL; |
| } |
| handle->fd = open(kDevicePath, O_RDWR); |
| if (handle->fd < 0) { |
| ALOGE("%s: opening '%s' failed: %s", __func__, kDevicePath, |
| strerror(errno)); |
| free(handle); |
| return NULL; |
| } |
| return handle; |
| } |
| |
| int platform_release(void *blob) { |
| struct PlatformHandle *handle = blob; |
| if (!handle) { |
| return -1; |
| } |
| /* Power off and cooldown should've happened via common code. */ |
| close(handle->fd); |
| free(handle); |
| return 0; |
| } |
| |
| int platform_wait(void *UNUSED(blob), long usec) { |
| return usleep((useconds_t)usec); |
| } |
| |
| uint32_t nq_transmit(struct EseInterface *ese, const uint8_t *buf, uint32_t len, |
| int UNUSED(complete)) { |
| struct NxpState *ns = NXP_PN80T_STATE(ese); |
| const struct Pn80tPlatform *platform = ese->ops->opts; |
| uint32_t bytes = 0; |
| ALOGV("nq_nci:%s: called [%d]", __func__, len); |
| if (len > INT_MAX) { |
| ese_set_error(ese, kNxpPn80tErrorTransmitSize); |
| ALOGE("Unexpectedly large transfer attempted: %u", len); |
| return 0; |
| } |
| if (len == 0) |
| return len; |
| const struct PlatformHandle *handle = ns->handle; |
| while (bytes < len) { |
| ssize_t ret = write(handle->fd, (void *)(buf + bytes), len - bytes); |
| if (ret < 0) { |
| if (errno == EAGAIN || errno == EINTR) { |
| continue; |
| } |
| ese_set_error(ese, kNxpPn80tErrorTransmit); |
| ALOGE("%s: failed to write to hw (ret=%zd, errno=%d)", __func__, ret, |
| errno); |
| return 0; |
| } |
| bytes += ret; |
| } |
| return len; |
| } |
| |
| uint32_t nq_receive(struct EseInterface *ese, uint8_t *buf, uint32_t len, |
| int UNUSED(complete)) { |
| const struct Pn80tPlatform *platform = ese->ops->opts; |
| struct NxpState *ns = NXP_PN80T_STATE(ese); |
| ALOGV("nq_nci:%s: called [%d]", __func__, len); |
| if (!ns) { |
| ALOGE("NxpState was NULL"); |
| return 0; |
| } |
| if (len > INT_MAX) { |
| ese_set_error(ese, kNxpPn80tErrorReceiveSize); |
| ALOGE("Unexpectedly large receive attempted: %u", len); |
| return 0; |
| } |
| const struct PlatformHandle *handle = ns->handle; |
| if (len == 0) { |
| return 0; |
| } |
| uint32_t bytes = 0; |
| while (bytes < len) { |
| ssize_t ret = read(handle->fd, (void *)(buf + bytes), len - bytes); |
| if (ret < 0) { |
| if (errno == EAGAIN || errno == EINTR) { |
| continue; |
| } |
| ALOGE("%s: failed to read from hw (ret=%zd, errno=%d)", __func__, ret, |
| errno); |
| ese_set_error(ese, kNxpPn80tErrorReceive); |
| return 0; |
| } |
| bytes += ret; |
| } |
| ALOGV("%s: read bytes: %u", __func__, bytes); |
| return len; |
| } |
| |
| static const struct Pn80tPlatform kPn80tNqNciPlatform = { |
| .initialize = &platform_init, |
| .release = &platform_release, |
| .toggle_reset = &platform_toggle_reset, |
| .toggle_ven = NULL, |
| .toggle_power_req = NULL, |
| .toggle_bootloader = &platform_toggle_bootloader, |
| .wait = &platform_wait, |
| }; |
| |
| static const struct EseOperations ops = { |
| .name = "NXP PN80T/PN81A (NQ-NCI:PN553)", |
| .open = &nxp_pn80t_open, |
| .hw_receive = &nq_receive, |
| .hw_transmit = &nq_transmit, |
| .hw_reset = &nxp_pn80t_reset, |
| .transceive = &nxp_pn80t_transceive, |
| .poll = &nxp_pn80t_poll, |
| .close = &nxp_pn80t_close, |
| .opts = &kPn80tNqNciPlatform, |
| .errors = kNxpPn80tErrorMessages, |
| .errors_count = kNxpPn80tErrorMax, |
| }; |
| __attribute__((visibility("default"))) |
| ESE_DEFINE_HW_OPS(ESE_HW_NXP_PN80T_NQ_NCI, ops); |