| /* Copyright (c) 2013, The Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/fs.h> |
| #include <linux/reboot.h> |
| #include <linux/slab.h> |
| #include <linux/i2c.h> |
| #include <linux/irq.h> |
| #include <linux/delay.h> |
| #include <linux/interrupt.h> |
| #include <linux/gpio.h> |
| #include <linux/spinlock.h> |
| #include <linux/poll.h> |
| #include <linux/of_gpio.h> |
| #include <linux/clk.h> |
| #include <linux/of_device.h> |
| #include <linux/regulator/consumer.h> |
| #include "nfc-nci.h" |
| #include <mach/gpiomux.h> |
| #include <linux/pm_runtime.h> |
| |
| struct qca199x_platform_data { |
| unsigned int irq_gpio; |
| unsigned int irq_gpio_clk_req; |
| unsigned int clk_req_irq_num; |
| unsigned int dis_gpio; |
| unsigned int clkreq_gpio; |
| unsigned int reg; |
| const char *clk_src_name; |
| unsigned int clk_src_gpio; |
| }; |
| |
| static struct of_device_id msm_match_table[] = { |
| {.compatible = "qcom,nfc-nci"}, |
| {} |
| }; |
| |
| MODULE_DEVICE_TABLE(of, msm_match_table); |
| |
| #define MAX_BUFFER_SIZE (780) |
| #define PACKET_MAX_LENGTH (258) |
| /* Read data */ |
| #define PACKET_HEADER_SIZE_NCI (4) |
| #define PACKET_TYPE_NCI (16) |
| #define MAX_PACKET_SIZE (PACKET_HEADER_SIZE_NCI + 255) |
| #define MAX_QCA_REG (116) |
| /* will timeout in approx. 100ms as 10us steps */ |
| #define NFC_RF_CLK_FREQ (19200000) |
| #define NTF_TIMEOUT (25) |
| #define CORE_RESET_RSP_GID (0x60) |
| #define CORE_RESET_OID (0x00) |
| #define CORE_RST_NTF_LENGTH (0x02) |
| #define WAKE_TIMEOUT (1000) |
| #define WAKE_REG (0x10) |
| #define EFUSE_REG (0xA0) |
| #define WAKEUP_SRC_TIMEOUT (2000) |
| |
| static void clk_req_update(struct work_struct *work); |
| |
| struct qca199x_dev { |
| wait_queue_head_t read_wq; |
| struct mutex read_mutex; |
| struct i2c_client *client; |
| struct miscdevice qca199x_device; |
| /* NFC_IRQ new NCI data available */ |
| unsigned int irq_gpio; |
| /* CLK_REQ IRQ to signal the state has changed */ |
| unsigned int irq_gpio_clk_req; |
| /* Actual IRQ no. assigned to CLK_REQ */ |
| unsigned int clk_req_irq_num; |
| unsigned int dis_gpio; |
| unsigned int clkreq_gpio; |
| /* NFC_IRQ state */ |
| bool irq_enabled; |
| bool sent_first_nci_write; |
| spinlock_t irq_enabled_lock; |
| unsigned int count_irq; |
| /* CLK_REQ IRQ state */ |
| bool irq_enabled_clk_req; |
| spinlock_t irq_enabled_lock_clk_req; |
| unsigned int count_irq_clk_req; |
| enum nfcc_state state; |
| /* CLK control */ |
| unsigned int clk_src_gpio; |
| const char *clk_src_name; |
| struct clk *s_clk; |
| unsigned int core_reset_ntf; |
| bool clk_run; |
| struct work_struct msm_clock_controll_work; |
| struct workqueue_struct *my_wq; |
| }; |
| |
| static int nfcc_reboot(struct notifier_block *notifier, unsigned long val, |
| void *v); |
| static int nfc_i2c_write(struct i2c_client *client, u8 *buf, int len); |
| static int nfcc_hw_check(struct i2c_client *client, unsigned short curr_addr); |
| static int nfcc_initialise(struct i2c_client *client, unsigned short curr_addr, |
| struct qca199x_dev *qca199x_dev); |
| static int qca199x_clock_select(struct qca199x_dev *qca199x_dev); |
| static int qca199x_clock_deselect(struct qca199x_dev *qca199x_dev); |
| |
| |
| |
| /* |
| * To allow filtering of nfc logging from user. This is set via |
| * IOCTL NFC_KERNEL_LOGGING_MODE. |
| */ |
| static int logging_level; |
| /* |
| * FTM-RAW-I2C RD/WR MODE |
| */ |
| static struct devicemode device_mode; |
| static int ftm_raw_write_mode; |
| static int ftm_werr_code; |
| |
| |
| unsigned int disable_ctrl; |
| bool region2_sent; |
| |
| static void qca199x_init_stat(struct qca199x_dev *qca199x_dev) |
| { |
| qca199x_dev->count_irq = 0; |
| } |
| |
| static void qca199x_disable_irq(struct qca199x_dev *qca199x_dev) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&qca199x_dev->irq_enabled_lock, flags); |
| if (qca199x_dev->irq_enabled) { |
| disable_irq_nosync(qca199x_dev->client->irq); |
| qca199x_dev->irq_enabled = false; |
| } |
| spin_unlock_irqrestore(&qca199x_dev->irq_enabled_lock, flags); |
| } |
| |
| static void qca199x_enable_irq(struct qca199x_dev *qca199x_dev) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&qca199x_dev->irq_enabled_lock, flags); |
| if (!qca199x_dev->irq_enabled) { |
| qca199x_dev->irq_enabled = true; |
| enable_irq(qca199x_dev->client->irq); |
| } |
| spin_unlock_irqrestore(&qca199x_dev->irq_enabled_lock, flags); |
| } |
| |
| static irqreturn_t qca199x_dev_irq_handler(int irq, void *dev_id) |
| { |
| struct qca199x_dev *qca199x_dev = dev_id; |
| unsigned long flags; |
| |
| if (device_may_wakeup(&qca199x_dev->client->dev) && |
| (qca199x_dev->client->dev.power.is_suspended == true)) { |
| dev_dbg(&qca199x_dev->client->dev, |
| "%s: NFC:Processor in suspend state device_may_wakeup\n", |
| __func__); |
| /* |
| * Keep system awake long enough to allow userspace |
| * to process the packet. |
| */ |
| pm_wakeup_event(&qca199x_dev->client->dev, WAKEUP_SRC_TIMEOUT); |
| } else { |
| dev_dbg(&qca199x_dev->client->dev, |
| "%s: NFC:Processor not in suspend state\n", __func__); |
| } |
| |
| spin_lock_irqsave(&qca199x_dev->irq_enabled_lock, flags); |
| qca199x_dev->count_irq++; |
| spin_unlock_irqrestore(&qca199x_dev->irq_enabled_lock, flags); |
| wake_up(&qca199x_dev->read_wq); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static unsigned int nfc_poll(struct file *filp, poll_table *wait) |
| { |
| struct qca199x_dev *qca199x_dev = filp->private_data; |
| unsigned int mask = 0; |
| unsigned long flags; |
| |
| poll_wait(filp, &qca199x_dev->read_wq, wait); |
| |
| spin_lock_irqsave(&qca199x_dev->irq_enabled_lock, flags); |
| if (qca199x_dev->count_irq > 0) { |
| qca199x_dev->count_irq--; |
| mask |= POLLIN | POLLRDNORM; |
| } |
| spin_unlock_irqrestore(&qca199x_dev->irq_enabled_lock, flags); |
| |
| return mask; |
| } |
| |
| /* Handlers for CLK_REQ */ |
| static void qca199x_disable_irq_clk_req(struct qca199x_dev *qca199x_dev) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&qca199x_dev->irq_enabled_lock_clk_req, flags); |
| if (qca199x_dev->irq_enabled_clk_req) { |
| disable_irq_nosync(qca199x_dev->clk_req_irq_num); |
| qca199x_dev->irq_enabled_clk_req = false; |
| } |
| spin_unlock_irqrestore(&qca199x_dev->irq_enabled_lock_clk_req, flags); |
| } |
| |
| |
| static void qca199x_enable_irq_clk_req(struct qca199x_dev *qca199x_dev) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&qca199x_dev->irq_enabled_lock_clk_req, flags); |
| if (!qca199x_dev->irq_enabled_clk_req) { |
| qca199x_dev->irq_enabled_clk_req = true; |
| enable_irq(qca199x_dev->clk_req_irq_num); |
| } |
| spin_unlock_irqrestore(&qca199x_dev->irq_enabled_lock_clk_req, flags); |
| } |
| |
| |
| static irqreturn_t qca199x_dev_irq_handler_clk_req(int irq, void *dev_id) |
| { |
| struct qca199x_dev *qca199x_dev = dev_id; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&qca199x_dev->irq_enabled_lock_clk_req, flags); |
| qca199x_dev->count_irq_clk_req++; |
| spin_unlock_irqrestore(&qca199x_dev->irq_enabled_lock_clk_req, flags); |
| |
| queue_work(qca199x_dev->my_wq, &qca199x_dev->msm_clock_controll_work); |
| |
| return IRQ_HANDLED; |
| } |
| |
| |
| static struct gpiomux_setting nfc_clk_on = { |
| .func = GPIOMUX_FUNC_2, |
| .drv = GPIOMUX_DRV_2MA, |
| .pull = GPIOMUX_PULL_NONE, |
| }; |
| static struct gpiomux_setting nfc_clk_on_suspend = { |
| .func = GPIOMUX_FUNC_2, |
| .drv = GPIOMUX_DRV_2MA, |
| .pull = GPIOMUX_PULL_DOWN, |
| }; |
| static struct gpiomux_setting nfc_clk_off = { |
| .func = GPIOMUX_FUNC_GPIO, |
| .drv = GPIOMUX_DRV_2MA, |
| .pull = GPIOMUX_PULL_DOWN, |
| }; |
| |
| static void clk_req_update(struct work_struct *work) |
| { |
| struct i2c_client *client; |
| struct qca199x_dev *qca199x_dev; |
| int gpio_clk_req_level = 0; |
| |
| qca199x_dev = container_of(work, struct qca199x_dev, |
| msm_clock_controll_work); |
| client = qca199x_dev->client; |
| |
| /* Read status level of CLK_REQ from NFC Controller, QCA199_x */ |
| gpio_clk_req_level = gpio_get_value(qca199x_dev->irq_gpio_clk_req); |
| if (gpio_clk_req_level == 1) { |
| if (qca199x_dev->clk_run == false) { |
| msm_gpiomux_write(qca199x_dev->clk_src_gpio, |
| GPIOMUX_ACTIVE, &nfc_clk_on, NULL); |
| msm_gpiomux_write(qca199x_dev->clk_src_gpio, |
| GPIOMUX_SUSPENDED, &nfc_clk_on_suspend, NULL); |
| qca199x_dev->clk_run = true; |
| } |
| } else{ |
| if (qca199x_dev->clk_run == true) { |
| msm_gpiomux_write(qca199x_dev->clk_src_gpio, |
| GPIOMUX_ACTIVE, &nfc_clk_off, NULL); |
| msm_gpiomux_write(qca199x_dev->clk_src_gpio, |
| GPIOMUX_SUSPENDED, &nfc_clk_off, NULL); |
| qca199x_dev->clk_run = false; |
| } |
| } |
| } |
| |
| /* |
| * ONLY for FTM-RAW-I2C Mode |
| * Required to instigate a read, which comes from DT layer. This means we need |
| * to spoof an interrupt and send a wake up event. |
| */ |
| void ftm_raw_trigger_read(struct qca199x_dev *qca199x_dev) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&qca199x_dev->irq_enabled_lock, flags); |
| qca199x_dev->count_irq++; |
| spin_unlock_irqrestore(&qca199x_dev->irq_enabled_lock, flags); |
| wake_up(&qca199x_dev->read_wq); |
| } |
| |
| static ssize_t nfc_read(struct file *filp, char __user *buf, |
| size_t count, loff_t *offset) |
| { |
| struct qca199x_dev *qca199x_dev = filp->private_data; |
| unsigned char tmp[MAX_BUFFER_SIZE], rd_byte; |
| unsigned char len[PAYLOAD_HEADER_LENGTH]; |
| int total, length, ret; |
| int ftm_rerr_code; |
| enum ehandler_mode dmode; |
| |
| total = 0; |
| length = 0; |
| if (count > MAX_BUFFER_SIZE) |
| count = MAX_BUFFER_SIZE; |
| |
| mutex_lock(&qca199x_dev->read_mutex); |
| memset(tmp, 0, sizeof(tmp)); |
| memset(len, 0, sizeof(len)); |
| dmode = device_mode.handle_flavour; |
| /* FTM-RAW-I2C RD/WR MODE - Special Case */ |
| if ((dmode == UNSOLICITED_FTM_RAW_MODE) || |
| (dmode == SOLICITED_FTM_RAW_MODE)) { |
| /* READ */ |
| if ((ftm_raw_write_mode == 0) && (ftm_werr_code == 0)) { |
| ftm_rerr_code = i2c_master_recv(qca199x_dev->client, |
| &rd_byte, sizeof(rd_byte)); |
| |
| if (ftm_rerr_code != sizeof(rd_byte)) { |
| total = -EMSGSIZE; |
| goto err; |
| } |
| if (ftm_rerr_code == 0x1) |
| ftm_rerr_code = 0; |
| tmp[0] = (unsigned char)ftm_rerr_code; |
| tmp[1] = rd_byte; |
| total = 2; |
| ret = copy_to_user(buf, tmp, total); |
| } |
| /* WRITE */ |
| else if ((ftm_raw_write_mode == 1) || (ftm_werr_code != 0)) { |
| tmp[0] = (unsigned char)ftm_werr_code; |
| total = 1; |
| ret = copy_to_user(buf, tmp, total); |
| } else { |
| /* Invalid case */ |
| total = 0; |
| ret = copy_to_user(buf, tmp, total); |
| } |
| mutex_unlock(&qca199x_dev->read_mutex); |
| goto done; |
| } |
| |
| /* NORMAL NCI Behaviour */ |
| /* Read the header */ |
| ret = i2c_master_recv(qca199x_dev->client, len, PAYLOAD_HEADER_LENGTH); |
| /* |
| * We ignore all packets of length PAYLOAD_HEADER_LENGTH |
| * or less (i.e <=3). In this case return a total length |
| * of ZERO. So ALL PACKETS MUST HAVE A PAYLOAD. |
| * If ret < 0 then this is an error code. |
| */ |
| if (ret != PAYLOAD_HEADER_LENGTH) { |
| if (ret < 0) |
| total = ret; |
| else |
| total = 0; |
| goto err; |
| } |
| length = len[PAYLOAD_HEADER_LENGTH - 1]; |
| if (length == 0) { |
| ret = 0; |
| total = ret; |
| goto err; |
| } |
| /** make sure full packet fits in the buffer **/ |
| if ((length > 0) && ((length + PAYLOAD_HEADER_LENGTH) <= count)) { |
| /* Read the packet */ |
| ret = i2c_master_recv(qca199x_dev->client, tmp, (length + |
| PAYLOAD_HEADER_LENGTH)); |
| total = ret; |
| if (ret < 0) |
| goto err; |
| } |
| |
| dev_dbg(&qca199x_dev->client->dev, "%s : NfcNciRx %x %x %x\n", |
| __func__, tmp[0], tmp[1], tmp[2]); |
| if (total > 0) { |
| if ((total > count) || copy_to_user(buf, tmp, total)) { |
| dev_err(&qca199x_dev->client->dev, |
| "%s: failed to copy to user space, total = %d\n", |
| __func__, total); |
| total = -EFAULT; |
| } |
| } |
| err: |
| mutex_unlock(&qca199x_dev->read_mutex); |
| done: |
| return total; |
| } |
| |
| /* |
| * Local routine to read from nfcc buffer. This is called to clear any |
| * pending receive messages in the nfcc's read buffer, which may be there |
| * following a POR. In this way, the upper layers (Device Transport) will |
| * associate the next rsp/ntf nci message with the next nci command to the |
| * nfcc. Otherwise, the DT may interpret a ntf from the nfcc as being from |
| * the nci core reset command when in fact it was already present in the |
| * nfcc read buffer following a POR. |
| */ |
| |
| int nfcc_read_buff_svc(struct qca199x_dev *qca199x_dev) |
| { |
| unsigned char tmp[PACKET_MAX_LENGTH]; |
| unsigned char len[PAYLOAD_HEADER_LENGTH]; |
| int total, length, ret; |
| total = 0; |
| length = 0; |
| mutex_lock(&qca199x_dev->read_mutex); |
| memset(tmp, 0, sizeof(tmp)); |
| memset(len, 0, sizeof(len)); |
| |
| /* Read the header */ |
| ret = i2c_master_recv(qca199x_dev->client, len, PAYLOAD_HEADER_LENGTH); |
| if (ret < PAYLOAD_HEADER_LENGTH) { |
| total = ret; |
| goto leave; |
| } |
| length = len[PAYLOAD_HEADER_LENGTH - 1]; |
| if (length == 0) { |
| ret = PAYLOAD_HEADER_LENGTH; |
| total = ret; |
| goto leave; |
| } |
| /** make sure full packet fits in the buffer **/ |
| if ((length > 0) && ((length + PAYLOAD_HEADER_LENGTH) <= PACKET_MAX_LENGTH)) { |
| /* Read the packet */ |
| ret = i2c_master_recv(qca199x_dev->client, tmp, (length + |
| PAYLOAD_HEADER_LENGTH)); |
| total = ret; |
| if (ret != (length + PAYLOAD_HEADER_LENGTH)) |
| goto leave; |
| |
| } |
| dev_dbg(&qca199x_dev->client->dev, "%s : NfcNciRx %x %x %x\n", |
| __func__, tmp[0], tmp[1], tmp[2]); |
| leave: |
| mutex_unlock(&qca199x_dev->read_mutex); |
| return total; |
| } |
| |
| static ssize_t nfc_write(struct file *filp, const char __user *buf, |
| size_t count, loff_t *offset) |
| { |
| struct qca199x_dev *qca199x_dev = filp->private_data; |
| char tmp[MAX_BUFFER_SIZE]; |
| int ret = 0; |
| enum ehandler_mode dmode; |
| int nfcc_buffer = 0; |
| |
| if (count > MAX_BUFFER_SIZE) { |
| dev_err(&qca199x_dev->client->dev, "%s: out of memory\n", |
| __func__); |
| return -ENOMEM; |
| } |
| if (copy_from_user(tmp, buf, count)) { |
| dev_err(&qca199x_dev->client->dev, |
| "%s: failed to copy from user space\n", __func__); |
| return -EFAULT; |
| } |
| /* |
| * A catch for when the DT is sending the initial NCI write |
| * following a hardware POR. In this case we should clear any |
| * pending messages in nfcc buffer and open the interrupt gate |
| * for new messages coming from the nfcc. |
| */ |
| if ((qca199x_dev->sent_first_nci_write == false) && |
| (qca199x_dev->irq_enabled == false)) { |
| /* check rsp/ntf from nfcc read-side buffer */ |
| nfcc_buffer = nfcc_read_buff_svc(qca199x_dev); |
| /* There has been an error while reading from nfcc */ |
| if (nfcc_buffer < 0) { |
| dev_err(&qca199x_dev->client->dev, |
| "%s: error while servicing nfcc read buffer\n" |
| , __func__); |
| } |
| qca199x_dev->sent_first_nci_write = true; |
| qca199x_enable_irq(qca199x_dev); |
| } |
| mutex_lock(&qca199x_dev->read_mutex); |
| dmode = device_mode.handle_flavour; |
| /* FTM-DIRECT-I2C RD/WR MODE */ |
| /* This is a special FTM-i2c mode case, where tester is not using NCI */ |
| if ((dmode == UNSOLICITED_FTM_RAW_MODE) || |
| (dmode == SOLICITED_FTM_RAW_MODE)) { |
| /* Read From Register */ |
| if (count == 1) { |
| ftm_raw_write_mode = 0; |
| ret = i2c_master_send(qca199x_dev->client, tmp, count); |
| if (ret == count) |
| ftm_werr_code = 0; |
| else |
| ftm_werr_code = ret; |
| ftm_raw_trigger_read(qca199x_dev); |
| } |
| /* Write to Register */ |
| if (count == 2) { |
| ftm_raw_write_mode = 1; |
| ret = i2c_master_send(qca199x_dev->client, tmp, count); |
| if (ret == count) |
| ftm_werr_code = 0; |
| else |
| ftm_werr_code = ret; |
| ftm_raw_trigger_read(qca199x_dev); |
| } |
| } else { |
| /* NORMAL NCI behaviour - NB : |
| We can be in FTM mode here also */ |
| ret = i2c_master_send(qca199x_dev->client, tmp, count); |
| } |
| if (ret != count) { |
| dev_err(&qca199x_dev->client->dev, |
| "%s: failed to write %d\n", __func__, ret); |
| ret = -EIO; |
| } |
| mutex_unlock(&qca199x_dev->read_mutex); |
| |
| /* If we detect a Region2 command prior to power-down */ |
| if ((tmp[0] == 0x2F) && (tmp[1] == 0x01) && (tmp[2] == 0x02) && |
| (tmp[3] == 0x08) && (tmp[4] == 0x00)) { |
| region2_sent = true; |
| } |
| dev_dbg(&qca199x_dev->client->dev, "%s : NfcNciTx %x %x %x\n", |
| __func__, tmp[0], tmp[1], tmp[2]); |
| return ret; |
| } |
| |
| static int nfc_open(struct inode *inode, struct file *filp) |
| { |
| int ret = 0; |
| |
| struct qca199x_dev *qca199x_dev = container_of(filp->private_data, |
| struct qca199x_dev, |
| qca199x_device); |
| |
| filp->private_data = qca199x_dev; |
| qca199x_init_stat(qca199x_dev); |
| /* Enable interrupts from NFCC NFC_INT new NCI data available */ |
| qca199x_enable_irq(qca199x_dev); |
| |
| if ((!strcmp(qca199x_dev->clk_src_name, "GPCLK")) || |
| (!strcmp(qca199x_dev->clk_src_name, "GPCLK2"))) { |
| /* Enable interrupts from NFCC CLK_REQ */ |
| qca199x_enable_irq_clk_req(qca199x_dev); |
| } |
| dev_dbg(&qca199x_dev->client->dev, |
| "%s: %d,%d\n", __func__, imajor(inode), iminor(inode)); |
| return ret; |
| } |
| |
| /* |
| * Wake/Sleep Mode |
| */ |
| int nfcc_wake(int level, struct file *filp) |
| { |
| int r = 0; |
| int time_taken = 0; |
| unsigned char raw_nci_sleep[] = {0x2F, 0x03, 0x00}; |
| unsigned char raw_nci_wake[] = {0x10, 0x0F}; |
| /* Change slave address to 0xE */ |
| unsigned short slave_addr = 0xE; |
| unsigned short curr_addr; |
| unsigned char wake_status = WAKE_REG; |
| struct qca199x_dev *qca199x_dev = filp->private_data; |
| |
| dev_dbg(&qca199x_dev->client->dev, "%s: info: %p\n", |
| __func__, qca199x_dev); |
| |
| curr_addr = qca199x_dev->client->addr; |
| if (level == NFCC_SLEEP) { |
| /* |
| * Normal NCI write |
| */ |
| r = i2c_master_send(qca199x_dev->client, &raw_nci_sleep[0], |
| sizeof(raw_nci_sleep)); |
| |
| if (r != sizeof(raw_nci_sleep)) |
| return -EMSGSIZE; |
| qca199x_dev->state = NFCC_STATE_NORMAL_SLEEP; |
| } else { |
| qca199x_dev->client->addr = slave_addr; |
| r = nfc_i2c_write(qca199x_dev->client, &raw_nci_wake[0], |
| sizeof(raw_nci_wake)); |
| if (r != sizeof(raw_nci_wake)) { |
| r = -EMSGSIZE; |
| dev_err(&qca199x_dev->client->dev, |
| "%s: nci wake write failed. Check hardware\n", |
| __func__); |
| goto leave; |
| } |
| do { |
| wake_status = WAKE_REG; |
| r = nfc_i2c_write(qca199x_dev->client, &wake_status, |
| sizeof(wake_status)); |
| if (r != sizeof(wake_status)) { |
| r = -EMSGSIZE; |
| dev_err(&qca199x_dev->client->dev, |
| "%s: wake status write fail.Check hardware\n", |
| __func__); |
| goto leave; |
| } |
| /* |
| * I2C line is low after ~10 usec |
| */ |
| usleep_range(10, 15); |
| r = i2c_master_recv(qca199x_dev->client, &wake_status, |
| sizeof(wake_status)); |
| if (r != sizeof(wake_status)) { |
| r = -EMSGSIZE; |
| dev_err(&qca199x_dev->client->dev, |
| "%s: wake status read fail.Check hardware\n", |
| __func__); |
| goto leave; |
| } |
| |
| time_taken++; |
| /* |
| * Each NFCC wakeup cycle |
| * takes about 0.5 ms |
| */ |
| if ((wake_status & NCI_WAKE) != 0) |
| /* NFCC wakeup time is between 0.5 and .52 ms */ |
| usleep_range(500, 550); |
| |
| } while ((wake_status & NCI_WAKE) |
| && (time_taken < WAKE_TIMEOUT)); |
| if (time_taken >= WAKE_TIMEOUT) { |
| dev_err(&qca199x_dev->client->dev, |
| "%s: timed out to get wakeup bit\n", __func__); |
| r = -EIO; |
| goto leave; |
| } |
| r = 0; |
| qca199x_dev->state = NFCC_STATE_NORMAL_WAKE; |
| } |
| leave: |
| /* Restore original NFCC slave I2C address */ |
| qca199x_dev->client->addr = curr_addr; |
| return r; |
| } |
| |
| /* |
| * Inside nfc_ioctl_power_states |
| * |
| * @brief ioctl functions |
| * |
| * |
| * Device control |
| * remove control via ioctl |
| * (arg = 0): NFC_DISABLE GPIO = 0 |
| * (arg = 1): NFC_DISABLE GPIO = 1 |
| * NOT USED (arg = 2): FW_DL GPIO = 0 |
| * NOT USED (arg = 3): FW_DL GPIO = 1 |
| * (arg = 4): NFCC_WAKE = 1 |
| * (arg = 5): NFCC_WAKE = 0 |
| * |
| * |
| */ |
| int nfc_ioctl_power_states(struct file *filp, unsigned int cmd, |
| unsigned long arg) |
| { |
| int r = 0; |
| struct qca199x_dev *qca199x_dev = filp->private_data; |
| |
| if (arg == 0) { |
| r = qca199x_clock_select(qca199x_dev); |
| if (r < 0) |
| goto err_req; |
| dev_dbg(&qca199x_dev->client->dev, "gpio_set_value disable: %s: info: %p\n", |
| __func__, qca199x_dev); |
| gpio_set_value(qca199x_dev->dis_gpio, 0); |
| usleep_range(1000, 1100); |
| } else if (arg == 1) { |
| /* |
| * We are attempting a hardware reset so let us disable |
| * interrupts to avoid spurious notifications to upper |
| * layers. |
| */ |
| qca199x_disable_irq(qca199x_dev); |
| /* Deselection of clock */ |
| r = qca199x_clock_deselect(qca199x_dev); |
| if (r < 0) |
| goto err_req; |
| /* |
| * Also, set flag for initial NCI write following resetas |
| * may wish to do some house keeping. Ensure no pending |
| * messages in NFCC buffers which may be wrongly |
| * construed as response to initial message |
| */ |
| qca199x_dev->sent_first_nci_write = false; |
| dev_dbg(&qca199x_dev->client->dev, "gpio_set_value enable: %s: info: %p\n", |
| __func__, qca199x_dev); |
| gpio_set_value(qca199x_dev->dis_gpio, 1); |
| /* NFCC needs at least 100 ms to power cycle*/ |
| msleep(100); |
| } else if (arg == 2) { |
| mutex_lock(&qca199x_dev->read_mutex); |
| dev_dbg(&qca199x_dev->client->dev, "before nfcc_initialise: %s: info: %p\n", |
| __func__, qca199x_dev); |
| r = nfcc_initialise(qca199x_dev->client, 0xE, qca199x_dev); |
| dev_dbg(&qca199x_dev->client->dev, "after nfcc_initialise: %s: info: %p\n", |
| __func__, qca199x_dev); |
| /* Also reset first NCI write */ |
| qca199x_dev->sent_first_nci_write = false; |
| mutex_unlock(&qca199x_dev->read_mutex); |
| if (r) { |
| dev_err(&qca199x_dev->client->dev, |
| "nfc_ioctl_power_states: request nfcc initialise failed\n"); |
| goto err_req; |
| } |
| } else if (arg == 3) { |
| msleep(20); |
| } else if (arg == 4) { |
| mutex_lock(&qca199x_dev->read_mutex); |
| r = nfcc_wake(NFCC_WAKE, filp); |
| dev_dbg(&qca199x_dev->client->dev, "nfcc wake: %s: info: %p\n", |
| __func__, qca199x_dev); |
| mutex_unlock(&qca199x_dev->read_mutex); |
| } else if (arg == 5) { |
| r = nfcc_wake(NFCC_SLEEP, filp); |
| } else { |
| r = -ENOIOCTLCMD; |
| } |
| |
| err_req: |
| return r; |
| } |
| |
| /* |
| * Inside nfc_ioctl_nfcc_mode |
| * |
| * @brief nfc_ioctl_nfcc_mode |
| * |
| * (arg = 0) ; NORMAL_MODE - Standard mode, unsolicited read behaviour |
| * (arg = 1) ; SOLICITED_MODE - As above but reads are solicited from User Land |
| * (arg = 2) ; UNSOLICITED_FTM_RAW MODE - NORMAL_MODE but messages from FTM and |
| * not NCI Host. |
| * (arg = 2) ; SOLICITED_FTM_RAW_MODE - As SOLICITED_MODE but messages from FTM |
| * and not NCI Host. |
| * |
| * |
| * |
| */ |
| int nfc_ioctl_nfcc_mode(struct file *filp, unsigned int cmd, unsigned long arg) |
| { |
| int retval = 0; |
| |
| static unsigned short nci_addr; |
| struct qca199x_dev *qca199x_dev = filp->private_data; |
| struct qca199x_platform_data *platform_data; |
| |
| platform_data = qca199x_dev->client->dev.platform_data; |
| |
| if (arg == 0) { |
| device_mode.handle_flavour = UNSOLICITED_MODE; |
| qca199x_dev->client->addr = NCI_I2C_SLAVE; |
| /* enable interrupts again */ |
| qca199x_enable_irq(qca199x_dev); |
| } else if (arg == 1) { |
| device_mode.handle_flavour = SOLICITED_MODE; |
| qca199x_dev->client->addr = qca199x_dev->client->addr; |
| /* enable interrupts again */ |
| qca199x_enable_irq(qca199x_dev); |
| } else if (arg == 2) { |
| device_mode.handle_flavour = UNSOLICITED_FTM_RAW_MODE; |
| nci_addr = qca199x_dev->client->addr; |
| /* replace with new client slave address*/ |
| qca199x_dev->client->addr = 0xE; |
| /* We also need to disable interrupts */ |
| qca199x_disable_irq(qca199x_dev); |
| } else if (arg == 3) { |
| device_mode.handle_flavour = SOLICITED_FTM_RAW_MODE; |
| nci_addr = qca199x_dev->client->addr; |
| /* replace with new client slave address*/ |
| qca199x_dev->client->addr = 0xE; |
| /* We also need to disable interrupts */ |
| qca199x_disable_irq(qca199x_dev); |
| } else { |
| device_mode.handle_flavour = UNSOLICITED_MODE; |
| qca199x_dev->client->addr = NCI_I2C_SLAVE; |
| } |
| return retval; |
| } |
| |
| /* |
| * Inside nfc_ioctl_nfcc_efuse |
| * |
| * @brief nfc_ioctl_nfcc_efuse |
| * |
| * |
| */ |
| int nfc_ioctl_nfcc_efuse(struct file *filp, unsigned int cmd, |
| unsigned long arg) |
| { |
| int r = 0; |
| unsigned short slave_addr = 0xE; |
| unsigned short curr_addr; |
| unsigned char efuse_addr = EFUSE_REG; |
| unsigned char efuse_value = 0xFF; |
| |
| struct qca199x_dev *qca199x_dev = filp->private_data; |
| |
| curr_addr = qca199x_dev->client->addr; |
| qca199x_dev->client->addr = slave_addr; |
| |
| r = nfc_i2c_write(qca199x_dev->client, |
| &efuse_addr, 1); |
| if (r < 0) { |
| /* Restore original NFCC slave I2C address */ |
| qca199x_dev->client->addr = curr_addr; |
| dev_err(&qca199x_dev->client->dev, |
| "ERROR_WRITE_FAIL : i2c write fail\n"); |
| return -EIO; |
| } |
| |
| /* |
| * NFCC chip needs to be at least |
| * 10usec high before make it low |
| */ |
| usleep_range(10, 15); |
| |
| r = i2c_master_recv(qca199x_dev->client, &efuse_value, |
| sizeof(efuse_value)); |
| if (r < 0) { |
| /* Restore original NFCC slave I2C address */ |
| qca199x_dev->client->addr = curr_addr; |
| dev_err(&qca199x_dev->client->dev, |
| "ERROR_I2C_RCV_FAIL : i2c recv fail\n"); |
| return -EIO; |
| } |
| |
| dev_dbg(&qca199x_dev->client->dev, "%s : EFUSE_VALUE %02x\n", |
| __func__, efuse_value); |
| |
| /* Restore original NFCC slave I2C address */ |
| qca199x_dev->client->addr = curr_addr; |
| return efuse_value; |
| } |
| |
| /* |
| * Inside nfc_ioctl_nfcc_version |
| * |
| * @brief nfc_ioctl_nfcc_version |
| * |
| * |
| */ |
| int nfc_ioctl_nfcc_version(struct file *filp, unsigned int cmd, |
| unsigned long arg) |
| { |
| int r = 0; |
| unsigned short slave_addr = 0xE; |
| unsigned short curr_addr; |
| unsigned char raw_chip_version_addr = 0x00; |
| unsigned char raw_chip_rev_id_addr = 0x9C; |
| unsigned char raw_chip_version = 0xFF; |
| |
| struct qca199x_dev *qca199x_dev = filp->private_data; |
| struct qca199x_platform_data *platform_data; |
| |
| platform_data = qca199x_dev->client->dev.platform_data; |
| |
| /* |
| * Always wake up chip when reading 0x9C, otherwise this |
| * register is not updated |
| */ |
| r = nfcc_wake(NFCC_WAKE, filp); |
| curr_addr = qca199x_dev->client->addr; |
| qca199x_dev->client->addr = slave_addr; |
| |
| |
| if (r) { |
| dev_err(&qca199x_dev->client->dev, |
| "%s: nfcc wake failed: %d\n", __func__, r); |
| r = -EIO; |
| goto leave; |
| } |
| |
| if (arg == 0) { |
| r = nfc_i2c_write(qca199x_dev->client, |
| &raw_chip_version_addr, sizeof(raw_chip_version_addr)); |
| if (r != sizeof(raw_chip_version_addr)) { |
| r = -EMSGSIZE; |
| goto err; |
| } |
| } else if (arg == 1) { |
| r = nfc_i2c_write(qca199x_dev->client, |
| &raw_chip_rev_id_addr, sizeof(raw_chip_rev_id_addr)); |
| if (r != sizeof(raw_chip_version_addr)) { |
| r = -EMSGSIZE; |
| goto err; |
| } |
| } else { |
| r = -EINVAL; |
| goto err; |
| } |
| |
| if (r < 0) { |
| r = -EIO; |
| goto err; |
| } |
| /* |
| * I2C line is low after ~10 usec |
| */ |
| usleep_range(10, 15); |
| r = i2c_master_recv(qca199x_dev->client, &raw_chip_version, |
| sizeof(raw_chip_version)); |
| if (r != sizeof(raw_chip_version)) { |
| r = -EMSGSIZE; |
| goto err; |
| } |
| goto leave; |
| err: |
| dev_err(&qca199x_dev->client->dev, |
| "%s: i2c access failed\n", __func__); |
| leave: |
| /* Restore original NFCC slave I2C address */ |
| qca199x_dev->client->addr = curr_addr; |
| return raw_chip_version; |
| } |
| |
| /* |
| * Inside nfc_ioctl_kernel_logging |
| * |
| * @brief nfc_ioctl_kernel_logging |
| * |
| * (arg = 0) ; NO_LOGGING |
| * (arg = 1) ; COMMS_LOGGING - BASIC LOGGING - Mainly just comms over I2C |
| * (arg = 2) ; FULL_LOGGING - ENABLE ALL - DBG messages for handlers etc. |
| * ; ! Be aware as amount of logging could impact behaviour ! |
| * |
| * |
| */ |
| int nfc_ioctl_kernel_logging(unsigned long arg, struct file *filp) |
| { |
| int retval = 0; |
| struct qca199x_dev *qca199x_dev = container_of(filp->private_data, |
| struct qca199x_dev, |
| qca199x_device); |
| if (arg == 0) { |
| dev_dbg(&qca199x_dev->client->dev, |
| "%s : level = NO_LOGGING\n", __func__); |
| logging_level = 0; |
| } else if (arg == 1) { |
| dev_dbg(&qca199x_dev->client->dev, |
| "%s: level = COMMS_LOGGING only\n", __func__); |
| logging_level = 1; |
| } else if (arg == 2) { |
| dev_dbg(&qca199x_dev->client->dev, |
| "%s: level = FULL_LOGGING\n", __func__); |
| logging_level = 2; |
| } |
| return retval; |
| } |
| |
| /* |
| * Inside nfc_ioctl_core_reset_ntf |
| * |
| * @brief nfc_ioctl_core_reset_ntf |
| * |
| * Allows callers to determine if a CORE_RESET_NTF has arrived |
| * |
| * Returns the value of variable core_reset_ntf |
| * |
| */ |
| int nfc_ioctl_core_reset_ntf(struct file *filp, unsigned int cmd, |
| unsigned long arg) |
| { |
| struct qca199x_dev *qca199x_dev = filp->private_data; |
| dev_dbg(&qca199x_dev->client->dev, |
| "%s: returning = %d\n", |
| __func__, |
| qca199x_dev->core_reset_ntf); |
| return qca199x_dev->core_reset_ntf; |
| } |
| |
| static long nfc_ioctl(struct file *pfile, unsigned int cmd, |
| unsigned long arg) |
| { |
| int r = 0; |
| struct qca199x_dev *qca199x_dev = pfile->private_data; |
| switch (cmd) { |
| case NFC_SET_PWR: |
| r = nfc_ioctl_power_states(pfile, cmd, arg); |
| break; |
| case NFCC_MODE: |
| r = nfc_ioctl_nfcc_mode(pfile, cmd, arg); |
| break; |
| case NFCC_VERSION: |
| r = nfc_ioctl_nfcc_version(pfile, cmd, arg); |
| break; |
| case NFC_KERNEL_LOGGING_MODE: |
| nfc_ioctl_kernel_logging(arg, pfile); |
| break; |
| case SET_RX_BLOCK: |
| break; |
| case SET_EMULATOR_TEST_POINT: |
| break; |
| case NFCC_INITIAL_CORE_RESET_NTF: |
| r = nfc_ioctl_core_reset_ntf(pfile, cmd, arg); |
| break; |
| case NFC_GET_EFUSE: |
| r = nfc_ioctl_nfcc_efuse(pfile, cmd, arg); |
| if (r < 0) { |
| r = 0xFF; |
| dev_err(&qca199x_dev->client->dev, |
| "nfc_ioctl : FAILED TO READ EFUSE TYPE\n"); |
| } |
| break; |
| default: |
| r = -ENOIOCTLCMD; |
| } |
| return r; |
| } |
| |
| static const struct file_operations nfc_dev_fops = { |
| .owner = THIS_MODULE, |
| .llseek = no_llseek, |
| .poll = nfc_poll, |
| .read = nfc_read, |
| .write = nfc_write, |
| .open = nfc_open, |
| .unlocked_ioctl = nfc_ioctl |
| }; |
| |
| void dumpqca1990(struct i2c_client *client) |
| { |
| int r = 0; |
| int i = 0; |
| unsigned char raw_reg_rd = {0x0}; |
| unsigned short temp_addr; |
| |
| temp_addr = client->addr; |
| client->addr = 0x0E; |
| |
| for (i = 0; i < MAX_QCA_REG; i++) { |
| raw_reg_rd = i; |
| if (((i >= 0x0) && (i < 0x4)) || ((i > 0x7) && (i < 0xA)) || |
| ((i > 0xF) && (i < 0x12)) || ((i > 0x39) && (i < 0x4d)) || |
| ((i > 0x69) && (i < 0x74)) || (i == 0x18) || (i == 0x30) || |
| (i == 0x58)) { |
| r = nfc_i2c_write(client, &raw_reg_rd, |
| sizeof(raw_reg_rd)); |
| if (r != sizeof(raw_reg_rd)) |
| break; |
| msleep(20); |
| r = i2c_master_recv(client, &raw_reg_rd, |
| sizeof(raw_reg_rd)); |
| if (r != sizeof(raw_reg_rd)) |
| break; |
| } |
| } |
| client->addr = temp_addr; |
| } |
| |
| static int nfc_i2c_write(struct i2c_client *client, u8 *buf, int len) |
| { |
| int r; |
| |
| r = i2c_master_send(client, buf, len); |
| dev_dbg(&client->dev, "%s: send: %d\n", __func__, r); |
| if (r == -EREMOTEIO) { /* Retry, chip was in standby */ |
| usleep_range(6000, 10000); |
| r = i2c_master_send(client, buf, len); |
| dev_dbg(&client->dev, "%s: send attempt 2: %d\n", __func__, r); |
| } |
| if (r != len) |
| return -EREMOTEIO; |
| |
| return r; |
| } |
| |
| /* Check for availability of qca199x_ NFC controller hardware */ |
| static int nfcc_hw_check(struct i2c_client *client, unsigned short curr_addr) |
| { |
| int r = 0; |
| unsigned char buf = 0; |
| |
| client->addr = curr_addr; |
| /* Set-up Addr 0. No data written */ |
| r = i2c_master_send(client, &buf, sizeof(buf)); |
| if (r < 0) |
| goto err_presence_check; |
| buf = 0; |
| /* Read back from Addr 0 */ |
| r = i2c_master_recv(client, &buf, sizeof(buf)); |
| if (r < 0) |
| goto err_presence_check; |
| |
| r = 0; |
| goto leave; |
| |
| err_presence_check: |
| r = -ENXIO; |
| dev_err(&client->dev, |
| "%s: - no NFCC available\n", __func__); |
| leave: |
| return r; |
| } |
| /* Initialise qca199x_ NFC controller hardware */ |
| static int nfcc_initialise(struct i2c_client *client, unsigned short curr_addr, |
| struct qca199x_dev *qca199x_dev) |
| { |
| int r = 0; |
| unsigned char raw_1P8_CONTROL_011[] = {0x11, XTAL_CLOCK}; |
| unsigned char raw_1P8_CONTROL_010[] = {0x10, PWR_EN}; |
| unsigned char raw_1P8_X0_0B0[] = {0xB0, (FREQ_SEL)}; |
| unsigned char raw_slave1[] = {0x09, NCI_I2C_SLAVE}; |
| unsigned char raw_slave2[] = {0x8, 0x10}; |
| unsigned char raw_s73[] = {0x73, 0x02}; |
| unsigned char raw_slave1_rd = {0x0}; |
| unsigned char raw_1P8_PAD_CFG_CLK_REQ[] = {0xA5, 0x1}; |
| unsigned char raw_1P8_PAD_CFG_PWR_REQ[] = {0xA7, 0x1}; |
| unsigned char buf = 0; |
| bool core_reset_completed = false; |
| unsigned char rsp[6]; |
| int time_taken = 0; |
| int ret = 0; |
| |
| client->addr = curr_addr; |
| qca199x_dev->core_reset_ntf = DEFAULT_INITIAL_CORE_RESET_NTF; |
| r = i2c_master_send(client, &buf, sizeof(buf)); |
| if (r < 0) |
| goto err_init; |
| |
| /* |
| * I2C line is low after ~10 usec |
| */ |
| usleep_range(10, 15); |
| |
| buf = 0; |
| r = i2c_master_recv(client, &buf, sizeof(buf)); |
| if (r < 0) |
| goto err_init; |
| |
| RAW(s73, 0x02); |
| |
| r = nfc_i2c_write(client, &raw_s73[0], sizeof(raw_s73)); |
| if (r < 0) |
| goto err_init; |
| |
| usleep_range(1000, 1100); |
| |
| RAW(1P8_CONTROL_011, XTAL_CLOCK | 0x01); |
| |
| r = nfc_i2c_write(client, &raw_1P8_CONTROL_011[0], |
| sizeof(raw_1P8_CONTROL_011)); |
| if (r < 0) |
| goto err_init; |
| |
| usleep_range(1000, 1100); /* 1 ms wait */ |
| RAW(1P8_CONTROL_010, (0x8)); |
| r = nfc_i2c_write(client, &raw_1P8_CONTROL_010[0], |
| sizeof(raw_1P8_CONTROL_010)); |
| if (r < 0) |
| goto err_init; |
| |
| usleep_range(10000, 11000); /* 10 ms wait */ |
| RAW(1P8_CONTROL_010, (0xC)); |
| r = nfc_i2c_write(client, &raw_1P8_CONTROL_010[0], |
| sizeof(raw_1P8_CONTROL_010)); |
| if (r < 0) |
| goto err_init; |
| |
| usleep_range(100, 110); /* 100 us wait */ |
| RAW(1P8_X0_0B0, (FREQ_SEL_19)); |
| r = nfc_i2c_write(client, &raw_1P8_X0_0B0[0], |
| sizeof(raw_1P8_X0_0B0)); |
| if (r < 0) |
| goto err_init; |
| |
| usleep_range(1000, 1100); /* 1 ms wait */ |
| |
| /* PWR_EN = 1 */ |
| RAW(1P8_CONTROL_010, (0xd)); |
| r = nfc_i2c_write(client, &raw_1P8_CONTROL_010[0], |
| sizeof(raw_1P8_CONTROL_010)); |
| if (r < 0) |
| goto err_init; |
| |
| |
| msleep(20); /* 20ms wait */ |
| /* LS_EN = 1 */ |
| RAW(1P8_CONTROL_010, 0xF); |
| r = nfc_i2c_write(client, &raw_1P8_CONTROL_010[0], |
| sizeof(raw_1P8_CONTROL_010)); |
| if (r < 0) |
| goto err_init; |
| |
| msleep(20); /* 20ms wait */ |
| |
| /* Enable the PMIC clock */ |
| RAW(1P8_PAD_CFG_CLK_REQ, (0x1)); |
| r = nfc_i2c_write(client, &raw_1P8_PAD_CFG_CLK_REQ[0], |
| sizeof(raw_1P8_PAD_CFG_CLK_REQ)); |
| if (r < 0) |
| goto err_init; |
| |
| usleep_range(1000, 1100); /* 1 ms wait */ |
| |
| RAW(1P8_PAD_CFG_PWR_REQ, (0x1)); |
| r = nfc_i2c_write(client, &raw_1P8_PAD_CFG_PWR_REQ[0], |
| sizeof(raw_1P8_PAD_CFG_PWR_REQ)); |
| if (r < 0) |
| goto err_init; |
| |
| usleep_range(1000, 1100); /* 1 ms wait */ |
| |
| RAW(slave2, 0x10); |
| r = nfc_i2c_write(client, &raw_slave2[0], sizeof(raw_slave2)); |
| if (r < 0) |
| goto err_init; |
| |
| usleep_range(1000, 1100); /* 1 ms wait */ |
| |
| RAW(slave1, NCI_I2C_SLAVE); |
| r = nfc_i2c_write(client, &raw_slave1[0], sizeof(raw_slave1)); |
| if (r < 0) |
| goto err_init; |
| |
| usleep_range(1000, 1100); /* 1 ms wait */ |
| |
| /* QCA199x NFCC CPU should now boot... */ |
| r = i2c_master_recv(client, &raw_slave1_rd, sizeof(raw_slave1_rd)); |
| if (r < 0) |
| goto err_init; |
| /* Talk on NCI slave address NCI_I2C_SLAVE 0x2C*/ |
| client->addr = NCI_I2C_SLAVE; |
| |
| /* |
| * Start with small delay and then we will poll until we |
| * get a core reset notification - This is time for chip |
| * & NFCC controller to come-up. |
| */ |
| usleep_range(15000, 16500); /* 15 ms */ |
| |
| do { |
| ret = i2c_master_recv(client, rsp, sizeof(rsp)); |
| if (ret < 0) |
| goto err_init; |
| /* Found core reset notification */ |
| if ((rsp[0] == CORE_RESET_RSP_GID) && |
| (rsp[1] == CORE_RESET_OID) && |
| (rsp[2] == CORE_RST_NTF_LENGTH)) { |
| dev_dbg(&client->dev, |
| "NFC core reset recvd: %s: info: %p\n", |
| __func__, client); |
| core_reset_completed = true; |
| } else { |
| usleep_range(2000, 2200); /* 2 ms wait before retry */ |
| } |
| time_taken++; |
| } while (!core_reset_completed && (time_taken < NTF_TIMEOUT)); |
| if (time_taken >= NTF_TIMEOUT) { |
| qca199x_dev->core_reset_ntf = TIMEDOUT_INITIAL_CORE_RESET_NTF; |
| goto err_init; |
| } |
| qca199x_dev->core_reset_ntf = ARRIVED_INITIAL_CORE_RESET_NTF; |
| |
| r = 0; |
| return r; |
| err_init: |
| r = 1; |
| dev_err(&client->dev, |
| "%s: failed. Check Hardware\n", __func__); |
| return r; |
| } |
| /* |
| Routine to Select clocks |
| */ |
| static int qca199x_clock_select(struct qca199x_dev *qca199x_dev) |
| { |
| int r = 0; |
| |
| if (!strcmp(qca199x_dev->clk_src_name, "BBCLK2")) { |
| qca199x_dev->s_clk = |
| clk_get(&qca199x_dev->client->dev, "ref_clk"); |
| if (qca199x_dev->s_clk == NULL) |
| goto err_invalid_dis_gpio; |
| } else if (!strcmp(qca199x_dev->clk_src_name, "RFCLK3")) { |
| qca199x_dev->s_clk = |
| clk_get(&qca199x_dev->client->dev, "ref_clk_rf"); |
| if (qca199x_dev->s_clk == NULL) |
| goto err_invalid_dis_gpio; |
| } else if (!strcmp(qca199x_dev->clk_src_name, "GPCLK")) { |
| if (gpio_is_valid(qca199x_dev->clk_src_gpio)) { |
| qca199x_dev->s_clk = |
| clk_get(&qca199x_dev->client->dev, |
| "core_clk"); |
| if (qca199x_dev->s_clk == NULL) |
| goto err_invalid_dis_gpio; |
| } else { |
| goto err_invalid_dis_gpio; |
| } |
| } else if (!strcmp(qca199x_dev->clk_src_name, "GPCLK2")) { |
| if (gpio_is_valid(qca199x_dev->clk_src_gpio)) { |
| qca199x_dev->s_clk = |
| clk_get(&qca199x_dev->client->dev, |
| "core_clk_pvt"); |
| if (qca199x_dev->s_clk == NULL) |
| goto err_invalid_dis_gpio; |
| } else { |
| goto err_invalid_dis_gpio; |
| } |
| } else { |
| qca199x_dev->s_clk = NULL; |
| goto err_invalid_dis_gpio; |
| } |
| if (qca199x_dev->clk_run == false) { |
| /* Set clock rate */ |
| if ((!strcmp(qca199x_dev->clk_src_name, "GPCLK")) || |
| (!strcmp(qca199x_dev->clk_src_name, "GPCLK2"))) { |
| r = clk_set_rate(qca199x_dev->s_clk, NFC_RF_CLK_FREQ); |
| if (r) |
| goto err_invalid_clk; |
| } |
| |
| r = clk_prepare_enable(qca199x_dev->s_clk); |
| if (r) |
| goto err_invalid_clk; |
| qca199x_dev->clk_run = true; |
| } |
| r = 0; |
| return r; |
| |
| err_invalid_clk: |
| r = -1; |
| return r; |
| err_invalid_dis_gpio: |
| r = -2; |
| return r; |
| } |
| /* |
| Routine to De-Select clocks |
| */ |
| |
| static int qca199x_clock_deselect(struct qca199x_dev *qca199x_dev) |
| { |
| int r = -1; |
| if (qca199x_dev->s_clk != NULL) { |
| if (qca199x_dev->clk_run == true) { |
| clk_disable_unprepare(qca199x_dev->s_clk); |
| qca199x_dev->clk_run = false; |
| } |
| return 0; |
| } |
| return r; |
| } |
| |
| static int nfc_parse_dt(struct device *dev, struct qca199x_platform_data *pdata) |
| { |
| int r = 0; |
| struct device_node *np = dev->of_node; |
| |
| r = of_property_read_u32(np, "reg", &pdata->reg); |
| if (r) |
| return -EINVAL; |
| |
| pdata->dis_gpio = of_get_named_gpio(np, "qcom,dis-gpio", 0); |
| if ((!gpio_is_valid(pdata->dis_gpio))) |
| return -EINVAL; |
| disable_ctrl = pdata->dis_gpio; |
| |
| pdata->irq_gpio = of_get_named_gpio(np, "qcom,irq-gpio", 0); |
| if ((!gpio_is_valid(pdata->irq_gpio))) |
| return -EINVAL; |
| |
| r = of_property_read_string(np, "qcom,clk-src", &pdata->clk_src_name); |
| |
| if (strcmp(pdata->clk_src_name, "GPCLK2")) { |
| pdata->clkreq_gpio = of_get_named_gpio(np, "qcom,clk-gpio", 0); |
| } |
| |
| if ((!strcmp(pdata->clk_src_name, "GPCLK")) || |
| (!strcmp(pdata->clk_src_name, "GPCLK2"))) { |
| pdata->clk_src_gpio = of_get_named_gpio(np, |
| "qcom,clk-src-gpio", 0); |
| if ((!gpio_is_valid(pdata->clk_src_gpio))) |
| return -EINVAL; |
| pdata->irq_gpio_clk_req = of_get_named_gpio(np, |
| "qcom,clk-req-gpio", 0); |
| if ((!gpio_is_valid(pdata->irq_gpio_clk_req))) |
| return -EINVAL; |
| } |
| |
| if (r) |
| return -EINVAL; |
| return r; |
| } |
| |
| static int qca199x_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| int r = 0; |
| int irqn = 0; |
| struct qca199x_platform_data *platform_data; |
| struct qca199x_dev *qca199x_dev; |
| |
| if (client->dev.of_node) { |
| platform_data = devm_kzalloc(&client->dev, |
| sizeof(struct qca199x_platform_data), GFP_KERNEL); |
| if (!platform_data) { |
| dev_err(&client->dev, |
| "%s: Failed to allocate memory\n", __func__); |
| return -ENOMEM; |
| } |
| r = nfc_parse_dt(&client->dev, platform_data); |
| if (r) |
| return r; |
| } else { |
| platform_data = client->dev.platform_data; |
| } |
| if (!platform_data) |
| return -EINVAL; |
| dev_dbg(&client->dev, |
| "%s, inside nfc-nci flags = %x\n", |
| __func__, client->flags); |
| if (platform_data == NULL) { |
| dev_err(&client->dev, "%s: failed\n", __func__); |
| return -ENODEV; |
| } |
| if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { |
| dev_err(&client->dev, "%s: need I2C_FUNC_I2C\n", __func__); |
| return -ENODEV; |
| } |
| qca199x_dev = kzalloc(sizeof(*qca199x_dev), GFP_KERNEL); |
| if (qca199x_dev == NULL) { |
| dev_err(&client->dev, |
| "%s: failed to allocate memory for module data\n", __func__); |
| return -ENOMEM; |
| } |
| qca199x_dev->client = client; |
| |
| /* |
| * To be efficient we need to test whether nfcc hardware is physically |
| * present before attempting further hardware initialisation. |
| * For this we need to be sure the device is in ULPM state by |
| * setting disable line low early on. |
| * |
| */ |
| |
| |
| if (gpio_is_valid(platform_data->dis_gpio)) { |
| r = gpio_request(platform_data->dis_gpio, "nfc_reset_gpio"); |
| if (r) { |
| dev_err(&client->dev, |
| "%s: unable to request gpio [%d]\n", |
| __func__, |
| platform_data->dis_gpio); |
| goto err_free_dev; |
| } |
| r = gpio_direction_output(platform_data->dis_gpio, 1); |
| if (r) { |
| dev_err(&client->dev, |
| "%s: unable to set direction for gpio [%d]\n", |
| __func__, |
| platform_data->dis_gpio); |
| goto err_dis_gpio; |
| } |
| } else { |
| dev_err(&client->dev, "%s: dis gpio not provided\n", __func__); |
| goto err_free_dev; |
| } |
| |
| /* Guarantee that the NFCC starts in a clean state. */ |
| gpio_set_value(platform_data->dis_gpio, 1);/* HPD */ |
| usleep_range(200, 220); |
| gpio_set_value(platform_data->dis_gpio, 0);/* ULPM */ |
| usleep_range(200, 220); |
| |
| r = nfcc_hw_check(client, platform_data->reg); |
| if (r) { |
| /* We don't think there is hardware but just in case HPD */ |
| gpio_set_value(platform_data->dis_gpio, 1); |
| goto err_dis_gpio; |
| } |
| |
| if (gpio_is_valid(platform_data->irq_gpio)) { |
| r = gpio_request(platform_data->irq_gpio, "nfc_irq_gpio"); |
| if (r) { |
| dev_err(&client->dev, "%s: unable to request gpio [%d]\n", |
| __func__, |
| platform_data->irq_gpio); |
| goto err_dis_gpio; |
| } |
| r = gpio_direction_input(platform_data->irq_gpio); |
| if (r) { |
| |
| dev_err(&client->dev, |
| "%s: unable to set direction for gpio [%d]\n", |
| __func__, |
| platform_data->irq_gpio); |
| goto err_irq; |
| } |
| irqn = gpio_to_irq(platform_data->irq_gpio); |
| if (irqn < 0) { |
| r = irqn; |
| goto err_irq; |
| } |
| client->irq = irqn; |
| |
| } else { |
| dev_err(&client->dev, "%s: irq gpio not provided\n", __func__); |
| goto err_dis_gpio; |
| } |
| /* Interrupt from NFCC CLK_REQ to handle REF_CLK |
| o/p gating/selection */ |
| if ((!strcmp(platform_data->clk_src_name, "GPCLK")) || |
| (!strcmp(platform_data->clk_src_name, "GPCLK2"))) { |
| if (gpio_is_valid(platform_data->irq_gpio_clk_req)) { |
| r = gpio_request(platform_data->irq_gpio_clk_req, |
| "nfc_irq_gpio_clk_en"); |
| if (r) { |
| dev_err(&client->dev, |
| "%s: unable to request CLK_EN gpio [%d]\n", |
| __func__, |
| platform_data->irq_gpio_clk_req); |
| goto err_irq; |
| } |
| r = gpio_direction_input( |
| platform_data->irq_gpio_clk_req); |
| if (r) { |
| dev_err(&client->dev, |
| "%s: cannot set direction CLK_EN gpio [%d]\n", |
| __func__, platform_data->irq_gpio_clk_req); |
| goto err_irq_clk; |
| } |
| gpio_to_irq(0); |
| irqn = gpio_to_irq(platform_data->irq_gpio_clk_req); |
| if (irqn < 0) { |
| r = irqn; |
| goto err_irq_clk; |
| } |
| platform_data->clk_req_irq_num = irqn; |
| } else { |
| dev_err(&client->dev, |
| "%s: irq CLK_EN gpio not provided\n", __func__); |
| goto err_irq; |
| } |
| } |
| /* Get the clock source name and gpio from from Device Tree */ |
| qca199x_dev->clk_src_name = platform_data->clk_src_name; |
| qca199x_dev->clk_src_gpio = platform_data->clk_src_gpio; |
| qca199x_dev->clk_run = false; |
| r = qca199x_clock_select(qca199x_dev); |
| if (r != 0) { |
| if (r == -1) |
| goto err_clk; |
| else |
| goto err_irq_clk; |
| } |
| |
| if (strcmp(platform_data->clk_src_name, "GPCLK2")) { |
| if (gpio_is_valid(platform_data->clkreq_gpio)) { |
| r = gpio_request(platform_data->clkreq_gpio, |
| "nfc_clkreq_gpio"); |
| if (r) { |
| dev_err(&client->dev, |
| "%s: unable to request gpio [%d]\n", |
| __func__, platform_data->clkreq_gpio); |
| goto err_clkreq_gpio; |
| } |
| r = gpio_direction_input(platform_data->clkreq_gpio); |
| if (r) { |
| dev_err(&client->dev, |
| "%s: cannot set direction for gpio [%d]\n", |
| __func__, platform_data->clkreq_gpio); |
| goto err_clkreq_gpio; |
| } |
| } else { |
| dev_err(&client->dev, |
| "%s: clkreq gpio not provided\n", __func__); |
| goto err_clk; |
| } |
| qca199x_dev->clkreq_gpio = platform_data->clkreq_gpio; |
| } |
| qca199x_dev->dis_gpio = platform_data->dis_gpio; |
| qca199x_dev->irq_gpio = platform_data->irq_gpio; |
| if ((!strcmp(platform_data->clk_src_name, "GPCLK")) || |
| (!strcmp(platform_data->clk_src_name, "GPCLK2"))) { |
| qca199x_dev->irq_gpio_clk_req = |
| platform_data->irq_gpio_clk_req; |
| qca199x_dev->clk_req_irq_num = |
| platform_data->clk_req_irq_num; |
| } |
| |
| /* init mutex and queues */ |
| init_waitqueue_head(&qca199x_dev->read_wq); |
| mutex_init(&qca199x_dev->read_mutex); |
| spin_lock_init(&qca199x_dev->irq_enabled_lock); |
| spin_lock_init(&qca199x_dev->irq_enabled_lock_clk_req); |
| |
| qca199x_dev->qca199x_device.minor = MISC_DYNAMIC_MINOR; |
| qca199x_dev->qca199x_device.name = "nfc-nci"; |
| qca199x_dev->qca199x_device.fops = &nfc_dev_fops; |
| |
| r = misc_register(&qca199x_dev->qca199x_device); |
| if (r) { |
| dev_err(&client->dev, "%s: misc_register failed\n", __func__); |
| goto err_misc_register; |
| } |
| |
| |
| /* |
| * Reboot the NFCC now that all resources are ready |
| * |
| * The NFCC takes time to transition between power states. |
| * We wait 20uS for the NFCC to shutdown. (HPD) |
| * We wait 100uS for the NFCC to boot into ULPM. |
| */ |
| gpio_set_value(platform_data->dis_gpio, 1);/* HPD */ |
| msleep(20); |
| gpio_set_value(platform_data->dis_gpio, 0);/* ULPM */ |
| msleep(100); |
| |
| |
| /* Here we perform a second presence check. */ |
| r = nfcc_hw_check(client, platform_data->reg); |
| if (r) { |
| /* We don't think there is hardware but just in case HPD */ |
| gpio_set_value(platform_data->dis_gpio, 1); |
| goto err_nfcc_not_present; |
| } |
| |
| logging_level = 0; |
| /* |
| * request irq. The irq is set whenever the chip has data available |
| * for reading. It is cleared when all data has been read. |
| */ |
| device_mode.handle_flavour = UNSOLICITED_MODE; |
| /* NFC_INT IRQ */ |
| qca199x_dev->irq_enabled = true; |
| r = request_irq(client->irq, qca199x_dev_irq_handler, |
| IRQF_TRIGGER_RISING, client->name, qca199x_dev); |
| if (r) { |
| dev_err(&client->dev, "%s: request_irq failed\n", __func__); |
| goto err_request_irq_failed; |
| } |
| qca199x_disable_irq(qca199x_dev); |
| /* CLK_REQ IRQ */ |
| if ((!strcmp(platform_data->clk_src_name, "GPCLK")) || |
| (!strcmp(platform_data->clk_src_name, "GPCLK2"))) { |
| r = request_irq(qca199x_dev->clk_req_irq_num, |
| qca199x_dev_irq_handler_clk_req, |
| (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING), |
| client->name, qca199x_dev); |
| if (r) { |
| dev_err(&client->dev, |
| "%s: request_irq failed. irq no = %d\n, main irq = %d", |
| __func__, |
| qca199x_dev->clk_req_irq_num, client->irq); |
| goto err_request_irq_failed; |
| } |
| qca199x_dev->irq_enabled_clk_req = true; |
| qca199x_disable_irq_clk_req(qca199x_dev); |
| |
| |
| qca199x_dev->my_wq = |
| create_singlethread_workqueue("qca1990x_CLK_REQ_queue"); |
| if (!qca199x_dev->my_wq) |
| goto err_create_workq; |
| |
| INIT_WORK(&qca199x_dev->msm_clock_controll_work, |
| clk_req_update); |
| } |
| device_init_wakeup(&client->dev, true); |
| device_set_wakeup_capable(&client->dev, true); |
| i2c_set_clientdata(client, qca199x_dev); |
| gpio_set_value(platform_data->dis_gpio, 1); |
| |
| /* To keep track if region2 command has been sent to controller */ |
| region2_sent = false; |
| |
| dev_dbg(&client->dev, |
| "%s: probing qca1990 exited successfully\n", |
| __func__); |
| return 0; |
| |
| err_create_workq: |
| dev_err(&client->dev, |
| "%s: work_queue creation failure\n", |
| __func__); |
| free_irq(client->irq, qca199x_dev); |
| err_nfcc_not_present: |
| err_request_irq_failed: |
| misc_deregister(&qca199x_dev->qca199x_device); |
| err_misc_register: |
| mutex_destroy(&qca199x_dev->read_mutex); |
| err_clkreq_gpio: |
| if (strcmp(platform_data->clk_src_name, "GPCLK2")) |
| gpio_free(platform_data->clkreq_gpio); |
| err_clk: |
| qca199x_clock_deselect(qca199x_dev); |
| err_irq_clk: |
| if ((!strcmp(platform_data->clk_src_name, "GPCLK")) || |
| (!strcmp(platform_data->clk_src_name, "GPCLK2"))) { |
| r = gpio_direction_input(platform_data->irq_gpio_clk_req); |
| if (r) |
| dev_err(&client->dev, |
| "%s: Unable to set direction\n", __func__); |
| gpio_free(platform_data->irq_gpio_clk_req); |
| } |
| err_irq: |
| gpio_free(platform_data->irq_gpio); |
| err_dis_gpio: |
| gpio_free(platform_data->dis_gpio); |
| err_free_dev: |
| kfree(qca199x_dev); |
| return r; |
| } |
| |
| static int qca199x_remove(struct i2c_client *client) |
| { |
| struct qca199x_dev *qca199x_dev; |
| |
| qca199x_dev = i2c_get_clientdata(client); |
| free_irq(client->irq, qca199x_dev); |
| misc_deregister(&qca199x_dev->qca199x_device); |
| mutex_destroy(&qca199x_dev->read_mutex); |
| gpio_free(qca199x_dev->irq_gpio); |
| if ((!strcmp(qca199x_dev->clk_src_name, "GPCLK")) || |
| (!strcmp(qca199x_dev->clk_src_name, "GPCLK2"))) { |
| gpio_free(qca199x_dev->irq_gpio_clk_req); |
| } |
| gpio_free(qca199x_dev->dis_gpio); |
| if (strcmp(qca199x_dev->clk_src_name, "GPCLK2")) |
| gpio_free(qca199x_dev->clkreq_gpio); |
| kfree(qca199x_dev); |
| |
| return 0; |
| } |
| |
| static int qca199x_suspend(struct device *device) |
| { |
| struct i2c_client *client = to_i2c_client(device); |
| |
| if (device_may_wakeup(&client->dev)) |
| enable_irq_wake(client->irq); |
| return 0; |
| } |
| |
| static int qca199x_resume(struct device *device) |
| { |
| struct i2c_client *client = to_i2c_client(device); |
| |
| if (device_may_wakeup(&client->dev)) |
| disable_irq_wake(client->irq); |
| return 0; |
| } |
| |
| static const struct i2c_device_id qca199x_id[] = { |
| {"qca199x-i2c", 0}, |
| {} |
| }; |
| |
| static const struct dev_pm_ops nfc_pm_ops = { |
| SET_SYSTEM_SLEEP_PM_OPS(qca199x_suspend, qca199x_resume) |
| }; |
| |
| static struct i2c_driver qca199x = { |
| .id_table = qca199x_id, |
| .probe = qca199x_probe, |
| .remove = qca199x_remove, |
| .driver = { |
| .owner = THIS_MODULE, |
| .name = "nfc-nci", |
| .of_match_table = msm_match_table, |
| .pm = &nfc_pm_ops, |
| }, |
| }; |
| |
| |
| static int nfcc_reboot(struct notifier_block *notifier, unsigned long val, |
| void *v) |
| { |
| /* |
| * Set DISABLE=1 *ONLY* if the NFC service has been disabled. |
| * This will put NFCC into HPD(Hard Power Down) state for power |
| * saving when powering down(Low Batt. or Power off handset) |
| * If user requires NFC and CE mode when powered down(PD) the |
| * middleware puts NFCC into region2 prior to PD. In this case |
| * we DO NOT HPD chip as this will trash Region2 and CE support |
| * when handset is PD. |
| */ |
| if (region2_sent == false) { |
| /* HPD the NFCC */ |
| gpio_set_value(disable_ctrl, 1); |
| } |
| return NOTIFY_OK; |
| } |
| |
| |
| static struct notifier_block nfcc_notifier = { |
| .notifier_call = nfcc_reboot, |
| .next = NULL, |
| .priority = 0 |
| }; |
| |
| /* |
| * module load/unload record keeping |
| */ |
| static int __init qca199x_dev_init(void) |
| { |
| int ret; |
| |
| ret = register_reboot_notifier(&nfcc_notifier); |
| if (ret) { |
| pr_err("cannot register reboot notifier (err=%d)\n", ret); |
| return ret; |
| } |
| return i2c_add_driver(&qca199x); |
| } |
| module_init(qca199x_dev_init); |
| |
| static void __exit qca199x_dev_exit(void) |
| { |
| unregister_reboot_notifier(&nfcc_notifier); |
| i2c_del_driver(&qca199x); |
| } |
| module_exit(qca199x_dev_exit); |
| |
| MODULE_DESCRIPTION("NFC QCA199x"); |
| MODULE_LICENSE("GPL v2"); |
| |