| /* |
| * Copyright (C) 2010 Trusted Logic S.A. |
| * modifications copyright (C) 2015 NXP B.V. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * 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. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/fs.h> |
| #include <linux/slab.h> |
| #include <linux/init.h> |
| #include <linux/list.h> |
| #include <linux/i2c.h> |
| #include <linux/irq.h> |
| #include <linux/jiffies.h> |
| #include <linux/uaccess.h> |
| #include <linux/delay.h> |
| #include <linux/interrupt.h> |
| #include <linux/io.h> |
| #include <linux/platform_device.h> |
| #include <linux/gpio.h> |
| #include <linux/miscdevice.h> |
| #include <linux/spinlock.h> |
| #include <linux/of_gpio.h> |
| #include <linux/clk.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/err.h> |
| #include <linux/time.h> |
| #include <linux/spinlock_types.h> |
| #include <linux/kthread.h> |
| #include <linux/qpnp/pwm.h> |
| #include "pn548.h" |
| #include <linux/wakelock.h> |
| |
| #define NFC_TRY_NUM 3 |
| #define MAX_BUFFER_SIZE 512 |
| |
| #define MODE_OFF 0 |
| #define MODE_RUN 1 |
| #define MODE_FW 2 |
| |
| #define CHIP "pn54x" |
| #define DRIVER_DESC "NFC driver for PN54x Family" |
| |
| struct pn548_dev { |
| wait_queue_head_t read_wq; |
| struct mutex read_mutex; |
| struct mutex irq_wake_mutex; |
| struct device *dev; |
| struct i2c_client *client; |
| struct miscdevice pn548_device; |
| struct clk *nfc_clk; |
| unsigned int ven_gpio; |
| unsigned int firm_gpio; |
| unsigned int irq_gpio; |
| unsigned int clk_req_gpio; |
| bool irq_enabled; |
| bool irq_wake_enabled; |
| spinlock_t irq_enabled_lock; |
| bool do_reading; |
| struct wake_lock wl; |
| bool cancel_read; |
| }; |
| /* |
| *FUNCTION: pn548_disable_irq_wake |
| *DESCRIPTION: disable irq wakeup function |
| *Parameters |
| * struct pn548_dev *: device structure |
| *RETURN VALUE |
| * none |
| */ |
| static void pn548_disable_irq_wake(struct pn548_dev *pn548_dev) |
| { |
| int ret = 0; |
| |
| mutex_lock(&pn548_dev->irq_wake_mutex); |
| if (pn548_dev->irq_wake_enabled) { |
| pn548_dev->irq_wake_enabled = false; |
| ret = irq_set_irq_wake(pn548_dev->client->irq,0); |
| if (ret) { |
| pr_err("%s failed: ret=%d\n", __func__, ret); |
| } |
| } |
| mutex_unlock(&pn548_dev->irq_wake_mutex); |
| } |
| /* |
| *FUNCTION: pn548_enable_irq_wake |
| *DESCRIPTION: enable irq wakeup function |
| *Parameters |
| * struct pn548_dev *: device structure |
| *RETURN VALUE |
| * none |
| */ |
| static void pn548_enable_irq_wake(struct pn548_dev *pn548_dev) |
| { |
| int ret = 0; |
| |
| mutex_lock(&pn548_dev->irq_wake_mutex); |
| if (!pn548_dev->irq_wake_enabled) { |
| pn548_dev->irq_wake_enabled = true; |
| ret = irq_set_irq_wake(pn548_dev->client->irq,1); |
| if (ret) { |
| pr_err("%s failed: ret=%d\n", __func__, ret); |
| } |
| } |
| mutex_unlock(&pn548_dev->irq_wake_mutex); |
| } |
| |
| /* |
| *FUNCTION: pn548_disable_irq |
| *DESCRIPTION: disable irq function |
| *Parameters |
| * struct pn548_dev *: device structure |
| *RETURN VALUE |
| * none |
| */ |
| static void pn548_disable_irq(struct pn548_dev *pn548_dev) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&pn548_dev->irq_enabled_lock, flags); |
| if (pn548_dev->irq_enabled) { |
| disable_irq_nosync(pn548_dev->client->irq); |
| pn548_dev->irq_enabled = false; |
| } |
| spin_unlock_irqrestore(&pn548_dev->irq_enabled_lock, flags); |
| } |
| /* |
| *FUNCTION: pn548_enable_irq |
| *DESCRIPTION: enable irq function |
| *Parameters |
| * struct pn548_dev *: device structure |
| *RETURN VALUE |
| * none |
| */ |
| static void pn548_enable_irq(struct pn548_dev *pn548_dev) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&pn548_dev->irq_enabled_lock, flags); |
| if (!pn548_dev->irq_enabled) { |
| pn548_dev->irq_enabled = true; |
| enable_irq(pn548_dev->client->irq); |
| } |
| spin_unlock_irqrestore(&pn548_dev->irq_enabled_lock, flags); |
| } |
| /* |
| *FUNCTION: pn548_dev_irq_handler |
| *DESCRIPTION: irq handler, jump here when receive an irq request from NFC chip |
| *Parameters |
| * int irq: irq number |
| * void *dev_id:device structure |
| *RETURN VALUE |
| * irqreturn_t: irq handle result |
| */ |
| static irqreturn_t pn548_dev_irq_handler(int irq, void *dev_id) |
| { |
| struct pn548_dev *pn548_dev = dev_id; |
| |
| if (gpio_get_value(pn548_dev->irq_gpio) != 1) { |
| return IRQ_HANDLED; |
| } |
| |
| pn548_disable_irq(pn548_dev); |
| |
| wake_lock_timeout(&pn548_dev->wl, 1 * HZ); |
| |
| /*set flag for do reading*/ |
| pn548_dev->do_reading = 1; |
| |
| /* Wake up waiting readers */ |
| wake_up(&pn548_dev->read_wq); |
| |
| return IRQ_HANDLED; |
| } |
| /* |
| *FUNCTION: pn548_enable |
| *DESCRIPTION: reset cmd sequence to enable pn548 |
| *Parameters |
| * struct pn548_dev *pdev: device structure |
| * int mode:run mode |
| *RETURN VALUE |
| * none |
| */ |
| static void pn548_enable(struct pn548_dev *pdev, int mode) |
| { |
| /* power on */ |
| if (MODE_RUN == mode) { |
| pr_info("%s power on\n", __func__); |
| if (gpio_is_valid(pdev->firm_gpio)) { |
| gpio_set_value(pdev->firm_gpio, 0); |
| } |
| gpio_set_value(pdev->ven_gpio, 1); |
| msleep(100); |
| } |
| else if (MODE_FW == mode) { |
| if (!gpio_is_valid(pdev->firm_gpio)) { |
| pr_err("%s invalid firm_gpio, mode %d\n", __func__, mode); |
| return; |
| } |
| /* power on with firmware download (requires hw reset) |
| */ |
| pr_info("%s power on with firmware\n", __func__); |
| gpio_set_value(pdev->ven_gpio, 1); |
| msleep(20); |
| gpio_set_value(pdev->firm_gpio, 1); |
| msleep(20); |
| gpio_set_value(pdev->ven_gpio, 0); |
| msleep(100); |
| gpio_set_value(pdev->ven_gpio, 1); |
| msleep(20); |
| } |
| else { |
| pr_err("%s bad arg %d\n", __func__, mode); |
| return; |
| } |
| |
| return; |
| } |
| |
| /* |
| *FUNCTION: pn548_disable |
| *DESCRIPTION: power off pn548 |
| *Parameters |
| * struct pn548_dev *pdev: device structure |
| *RETURN VALUE |
| * none |
| */ |
| static void pn548_disable(struct pn548_dev *pdev) |
| { |
| /* power off */ |
| pr_info("%s power off\n", __func__); |
| if (gpio_is_valid(pdev->firm_gpio)) { |
| gpio_set_value(pdev->firm_gpio, 0); |
| } |
| gpio_set_value(pdev->ven_gpio, 0); |
| msleep(100); |
| |
| return; |
| } |
| |
| /* |
| *FUNCTION: pn548_dev_read |
| *DESCRIPTION: read i2c data |
| *Parameters |
| * struct file *filp:device structure |
| * char __user *buf:return to user buffer |
| * size_t count:read data count |
| * loff_t *offset:offset |
| *RETURN VALUE |
| * ssize_t: result |
| */ |
| static ssize_t pn548_dev_read(struct file *filp, char __user *buf, |
| size_t count, loff_t *offset) |
| { |
| struct pn548_dev *pn548_dev = filp->private_data; |
| char tmp[MAX_BUFFER_SIZE]; |
| int ret; |
| int retry = 0; |
| |
| /*max size is 512*/ |
| if (count > MAX_BUFFER_SIZE) { |
| count = MAX_BUFFER_SIZE; |
| } |
| |
| mutex_lock(&pn548_dev->read_mutex); |
| |
| /*read data when interrupt occur*/ |
| if (!gpio_get_value(pn548_dev->irq_gpio)) { |
| if (filp->f_flags & O_NONBLOCK) { |
| ret = -EAGAIN; |
| goto fail; |
| } |
| |
| pn548_dev->do_reading = 0; |
| pn548_enable_irq(pn548_dev); |
| |
| ret = wait_event_interruptible(pn548_dev->read_wq, |
| pn548_dev->do_reading); |
| |
| pn548_disable_irq(pn548_dev); |
| |
| /*user cancel data read op*/ |
| if (pn548_dev->cancel_read) { |
| pn548_dev->cancel_read = false; |
| ret = -1; |
| goto fail; |
| } |
| |
| if (ret) { |
| goto fail; |
| } |
| } |
| |
| /* Read data, at most tree times */ |
| for (retry = 0; retry < NFC_TRY_NUM; retry++) { |
| ret = i2c_master_recv(pn548_dev->client, tmp, count); |
| if (ret == (int)count) { |
| break; |
| } else { |
| if (retry > 0) { |
| pr_info("%s : read retry times =%d returned %d\n", __func__,retry,ret); |
| } |
| msleep(10); |
| continue; |
| } |
| } |
| |
| mutex_unlock(&pn548_dev->read_mutex); |
| |
| /* pn548 seems to be slow in handling I2C read requests |
| * so add 1ms delay after recv operation */ |
| udelay(1000); |
| |
| if (ret < 0) { |
| pr_err("%s: PN548 i2c_master_recv returned %d\n", __func__, ret); |
| return ret; |
| } |
| |
| if (ret > count) { |
| pr_err("%s: received too many bytes from i2c (%d)\n", __func__, ret); |
| return -EIO; |
| } |
| |
| /*copy data to user*/ |
| if (copy_to_user(buf, tmp, ret)) { |
| pr_warning("%s : failed to copy to user space\n", __func__); |
| return -EFAULT; |
| } |
| return ret; |
| |
| fail: |
| mutex_unlock(&pn548_dev->read_mutex); |
| if (ret != -ERESTARTSYS) |
| pr_err("%s : goto fail, and ret : %d \n", __func__, ret); |
| |
| return ret; |
| } |
| /* |
| *FUNCTION: pn548_dev_write |
| *DESCRIPTION: write i2c data |
| *Parameters |
| * struct file *filp:device structure |
| * char __user *buf:user buffer to write |
| * size_t count:write data count |
| * loff_t *offset:offset |
| *RETURN VALUE |
| * ssize_t: result |
| */ |
| static ssize_t pn548_dev_write(struct file *filp, const char __user *buf, |
| size_t count, loff_t *offset) |
| { |
| struct pn548_dev *pn548_dev = filp->private_data; |
| char tmp[MAX_BUFFER_SIZE]; |
| int ret; |
| int retry = 0; |
| |
| /*max size is 512*/ |
| if (count > MAX_BUFFER_SIZE) { |
| count = MAX_BUFFER_SIZE; |
| } |
| |
| /*copy data from user*/ |
| if (copy_from_user(tmp, buf, count)) { |
| pr_err("%s : failed to copy from user space\n", __func__); |
| return -EFAULT; |
| } |
| |
| /* Write data */ |
| /* Write data, at most tree times */ |
| for (retry = 0; retry < NFC_TRY_NUM; retry++) { |
| ret = i2c_master_send(pn548_dev->client, tmp, count); |
| if (ret == (int)count) { |
| break; |
| } else { |
| if (retry > 0) { |
| pr_info("%s : send retry times =%d returned %d\n", __func__,retry,ret); |
| } |
| msleep(50); |
| continue; |
| } |
| } |
| |
| if (ret != count) { |
| pr_err("%s : i2c_master_send returned %d\n", __func__, ret); |
| ret = -EIO; |
| } |
| |
| /* pn548 seems to be slow in handling I2C write requests |
| * so add 1ms delay after I2C send oparation */ |
| udelay(1000); |
| |
| return ret; |
| } |
| /* |
| *FUNCTION: pn548_reset |
| *DESCRIPTION: reset pn548 |
| *Parameters |
| * struct pn548_dev *pdev: device structure |
| *RETURN VALUE |
| * none |
| */ |
| static void pn548_reset(struct pn548_dev *pdev) |
| { |
| /*hardware reset*/ |
| /* power on */ |
| gpio_set_value(pdev->ven_gpio, 1); |
| msleep(20); |
| |
| /* power off */ |
| gpio_set_value(pdev->ven_gpio, 0); |
| msleep(60); |
| |
| /* power on */ |
| gpio_set_value(pdev->ven_gpio, 1); |
| msleep(20); |
| |
| return; |
| } |
| /* |
| *FUNCTION: check_pn548 |
| *DESCRIPTION: To test if nfc chip is ok |
| *Parameters |
| * struct i2c_client *client:i2c device structure |
| * struct pn548_dev *pdev:device structure |
| *RETURN VALUE |
| * int: check result |
| */ |
| static int check_pn548(struct i2c_client *client, struct pn548_dev *pdev) |
| { |
| int ret = -1; |
| int count = 0; |
| const char host_to_pn548[1] = {0x20}; |
| const char firm_dload_cmd[8]={0x00, 0x04, 0xD0, 0x09, 0x00, 0x00, 0xB1, 0x84}; |
| |
| /* reset chip */ |
| gpio_set_value(pdev->firm_gpio, 0); |
| pn548_reset(pdev); |
| |
| do { |
| ret = i2c_master_send(client, host_to_pn548, sizeof(host_to_pn548)); |
| if (ret < 0) { |
| pr_err("%s:pn548_i2c_write failed and ret = %d,at %d times\n", __func__,ret,count); |
| } else { |
| pr_info("%s:pn548_i2c_write success and ret = %d,at %d times\n",__func__,ret,count); |
| msleep(10); |
| pn548_reset(pdev); |
| break; |
| } |
| count++; |
| msleep(10); |
| } while (count <NFC_TRY_NUM); |
| |
| /*In case firmware dload failed, will cause host_to_pn548 cmd send failed*/ |
| if (count == NFC_TRY_NUM) { |
| for (count = 0; count < NFC_TRY_NUM;count++) { |
| gpio_set_value(pdev->firm_gpio, 1); |
| pn548_reset(pdev); |
| |
| ret = i2c_master_send(client, firm_dload_cmd, sizeof(firm_dload_cmd)); |
| if (ret < 0) { |
| pr_err("%s:pn548_i2c_write download cmd failed:%d, ret = %d\n", __func__, count, ret); |
| continue; |
| } |
| gpio_set_value(pdev->firm_gpio, 0); |
| pn548_reset(pdev); |
| break; |
| } |
| } |
| |
| gpio_set_value(pdev->firm_gpio, 0); |
| gpio_set_value(pdev->ven_gpio, 0); |
| |
| return ret; |
| } |
| /* |
| *FUNCTION: pn548_dev_open |
| *DESCRIPTION: pn548_dev_open, used by user space to enable pn548 |
| *Parameters |
| * struct inode *inode:device inode |
| * struct file *filp:device file |
| *RETURN VALUE |
| * int: result |
| */ |
| static int pn548_dev_open(struct inode *inode, struct file *filp) |
| { |
| struct pn548_dev *pn548_dev = container_of(filp->private_data, |
| struct pn548_dev, |
| pn548_device); |
| |
| filp->private_data = pn548_dev; |
| pn548_enable_irq(pn548_dev); |
| pr_info("%s : %d,%d\n", __func__, imajor(inode), iminor(inode)); |
| |
| return 0; |
| } |
| /* |
| *FUNCTION: pn548_dev_release |
| *DESCRIPTION: pn548_dev_release, used by user space to release pn548 |
| *Parameters |
| * struct inode *inode:device inode |
| * struct file *filp:device file |
| *RETURN VALUE |
| * int: result |
| */ |
| static int pn548_dev_release(struct inode *inode, struct file *filp) |
| { |
| pr_info("%s : closing %d,%d\n", __func__, imajor(inode), iminor(inode)); |
| |
| return 0; |
| } |
| /* |
| *FUNCTION: pn548_dev_ioctl |
| *DESCRIPTION: pn548_dev_ioctl, used by user space |
| *Parameters |
| * struct file *filp:device file |
| * unsigned int cmd:command |
| * unsigned long arg:parameters |
| *RETURN VALUE |
| * long: result |
| */ |
| static long pn548_dev_ioctl(struct file *filp, |
| unsigned int cmd, unsigned long arg) |
| { |
| struct pn548_dev *pn548_dev = filp->private_data; |
| |
| pr_info("%s, cmd=%d, arg=%lu\n", __func__, cmd, arg); |
| |
| switch (cmd) { |
| case PN548_SET_PWR: |
| if (2 == arg) { |
| /* power on with firmware download (requires hw reset) |
| */ |
| pr_err("%s power on with firmware\n", __func__); |
| pn548_enable(pn548_dev, MODE_FW); |
| } else if (1 == arg) { |
| /* power on */ |
| pr_err("%s power on\n", __func__); |
| pn548_enable(pn548_dev, MODE_RUN); |
| pn548_enable_irq_wake(pn548_dev); |
| msleep(20); |
| } else if (0 == arg) { |
| /* power off */ |
| pr_err("%s power off\n", __func__); |
| pn548_disable(pn548_dev); |
| pn548_disable_irq_wake(pn548_dev); |
| msleep(60); |
| } else if (3 == arg) { |
| pr_info("%s Read Cancel\n", __func__); |
| pn548_dev->cancel_read = true; |
| pn548_dev->do_reading = 1; |
| wake_up(&pn548_dev->read_wq); |
| } else { |
| pr_err("%s bad SET_PWR arg %lu\n", __func__, arg); |
| return -EINVAL; |
| } |
| break; |
| case PN548_CLK_REQ: |
| if(1 == arg){ |
| if(gpio_is_valid(pn548_dev->clk_req_gpio)){ |
| gpio_set_value(pn548_dev->clk_req_gpio, 1); |
| } |
| } |
| else if(0 == arg) { |
| if(gpio_is_valid(pn548_dev->clk_req_gpio)){ |
| gpio_set_value(pn548_dev->clk_req_gpio, 0); |
| } |
| } else { |
| pr_err("%s bad CLK_REQ arg %lu\n", __func__, arg); |
| return -EINVAL; |
| } |
| break; |
| default: |
| pr_err("%s bad ioctl 0x%x\n", __func__, cmd); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static const struct file_operations pn548_dev_fops = { |
| .owner = THIS_MODULE, |
| .llseek = no_llseek, |
| .read = pn548_dev_read, |
| .write = pn548_dev_write, |
| .open = pn548_dev_open, |
| .release = pn548_dev_release, |
| .unlocked_ioctl = pn548_dev_ioctl, |
| #ifdef CONFIG_COMPAT |
| .compat_ioctl = pn548_dev_ioctl, |
| #endif |
| }; |
| /* |
| *FUNCTION: pn548_parse_dt |
| *DESCRIPTION: pn548_parse_dt, get gpio configuration from device tree system |
| *Parameters |
| * struct device *dev:device data |
| * struct pn548_i2c_platform_data *pdata:i2c data |
| *RETURN VALUE |
| * int: result |
| */ |
| static int pn548_parse_dt(struct device *dev, |
| struct pn548_i2c_platform_data *pdata) |
| { |
| struct device_node *np = dev->of_node; |
| int ret = 0; |
| |
| /*int gpio*/ |
| pdata->irq_gpio = of_get_named_gpio_flags(np, "nxp,nfc_int", 0,NULL); |
| if (pdata->irq_gpio < 0) { |
| pr_err( "failed to get \"huawei,nfc_int\"\n"); |
| goto err; |
| } |
| |
| /*nfc_fm_dload gpio*/ |
| pdata->fwdl_en_gpio = of_get_named_gpio_flags(np, "nxp,nfc_firm", 0,NULL); |
| if (pdata->fwdl_en_gpio< 0) { |
| pr_err( "failed to get \"huawei,nfc_firm\"\n"); |
| goto err; |
| } |
| |
| /*nfc_ven gpio*/ |
| pdata->ven_gpio = of_get_named_gpio_flags(np, "nxp,nfc_ven", 0,NULL); |
| if (pdata->ven_gpio < 0) { |
| pr_err( "failed to get \"huawei,nfc_ven\"\n"); |
| goto err; |
| } |
| |
| /*nfc_clk_req gpio*/ |
| pdata->clk_req_gpio = of_get_named_gpio(np, "nxp,nfc_clk", 0); |
| if (pdata->clk_req_gpio < 0) { |
| pr_err( "failed to get \"huawei,nfc_clk\"\n"); |
| goto err; |
| } |
| |
| pr_info("%s : huawei,clk-req-gpio=%d\n",__func__,pdata->clk_req_gpio); |
| |
| err: |
| return ret; |
| } |
| /* |
| *FUNCTION: pn548_gpio_request |
| *DESCRIPTION: pn548_gpio_request, nfc gpio configuration |
| *Parameters |
| * struct device *dev:device data |
| * struct pn548_i2c_platform_data *pdata:i2c data |
| *RETURN VALUE |
| * int: result |
| */ |
| static int pn548_gpio_request(struct device *dev, |
| struct pn548_i2c_platform_data *pdata) |
| { |
| int ret; |
| |
| pr_info("%s : pn548_gpio_request enter\n", __func__); |
| |
| /*NFC_INT*/ |
| ret = gpio_request(pdata->irq_gpio, "nfc_int"); |
| if (ret) { |
| goto err_irq; |
| } |
| |
| ret = gpio_direction_input(pdata->irq_gpio); |
| if (ret) { |
| goto err_fwdl_en; |
| } |
| |
| /*NFC_FWDL*/ |
| ret = gpio_request(pdata->fwdl_en_gpio, "nfc_wake"); |
| if (ret) { |
| goto err_fwdl_en; |
| } |
| |
| ret = gpio_direction_output(pdata->fwdl_en_gpio,0); |
| if (ret) { |
| goto err_ven; |
| } |
| |
| /*NFC_VEN*/ |
| ret = gpio_request(pdata->ven_gpio,"nfc_ven"); |
| if (ret) { |
| goto err_ven; |
| } |
| ret = gpio_direction_output(pdata->ven_gpio, 0); |
| if (ret) { |
| goto err_clk_req; |
| } |
| |
| /*NFC_CLKReq*/ |
| ret = gpio_request(pdata->clk_req_gpio,"nfc_clk_req"); |
| if (ret) { |
| goto err_clk_req; |
| } |
| |
| ret = gpio_direction_input(pdata->clk_req_gpio); |
| if (ret) { |
| goto err_clk_input; |
| } |
| |
| return 0; |
| |
| err_clk_input: |
| gpio_free(pdata->clk_req_gpio); |
| err_clk_req: |
| gpio_free(pdata->ven_gpio); |
| err_ven: |
| gpio_free(pdata->fwdl_en_gpio); |
| err_fwdl_en: |
| gpio_free(pdata->irq_gpio); |
| err_irq: |
| |
| pr_err( "%s: gpio request err %d\n", __func__, ret); |
| return ret; |
| } |
| /* |
| *FUNCTION: pn548_gpio_release |
| *DESCRIPTION: pn548_gpio_release, release nfc gpio |
| *Parameters |
| * struct pn548_i2c_platform_data *pdata:i2c data |
| *RETURN VALUE |
| * none |
| */ |
| static void pn548_gpio_release(struct pn548_i2c_platform_data *pdata) |
| { |
| gpio_free(pdata->ven_gpio); |
| gpio_free(pdata->irq_gpio); |
| gpio_free(pdata->fwdl_en_gpio); |
| gpio_free(pdata->clk_req_gpio); |
| } |
| /* |
| *FUNCTION: pn548_probe |
| *DESCRIPTION: pn548_probe |
| *Parameters |
| * struct i2c_client *client:i2c client data |
| * const struct i2c_device_id *id:i2c device id |
| *RETURN VALUE |
| * int: result |
| */ |
| static int pn548_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| int ret = 0; |
| struct clk *nfc_clk = NULL; |
| struct pn548_i2c_platform_data *platform_data; |
| struct pn548_dev *pn548_dev; |
| |
| pr_info("%s\n", __func__); |
| |
| if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { |
| pr_err("%s : need I2C_FUNC_I2C\n", __func__); |
| return -ENODEV; |
| } |
| |
| platform_data = kzalloc(sizeof(struct pn548_i2c_platform_data), |
| GFP_KERNEL); |
| if (platform_data == NULL) { |
| dev_err(&client->dev, "failed to allocate memory\n"); |
| ret = -ENOMEM; |
| goto err_platform_data; |
| } |
| |
| /*get gpio config*/ |
| ret = pn548_parse_dt(&client->dev, platform_data); |
| if (ret < 0) { |
| dev_err(&client->dev, "failed to parse device tree: %d\n", ret); |
| goto err_parse_dt; |
| } |
| |
| /*config nfc clock*/ |
| nfc_clk = clk_get(&client->dev, "pn548_clk"); |
| if (nfc_clk == NULL) { |
| dev_err(&client->dev, "failed to get clk: %d\n", ret); |
| goto err_parse_dt; |
| } |
| clk_set_rate(nfc_clk,19200000); |
| |
| ret = clk_prepare_enable(nfc_clk); |
| if (ret) { |
| dev_err(&client->dev, "failed to enable clk: %d\n", ret); |
| goto err_gpio_request; |
| } |
| |
| /*config nfc gpio*/ |
| ret = pn548_gpio_request(&client->dev, platform_data); |
| if (ret) { |
| dev_err(&client->dev, "failed to request gpio\n"); |
| goto err_gpio_request; |
| } |
| |
| pn548_dev = kzalloc(sizeof(*pn548_dev), GFP_KERNEL); |
| if (pn548_dev == NULL) { |
| dev_err(&client->dev, |
| "failed to allocate memory for module data\n"); |
| ret = -ENOMEM; |
| goto err_exit; |
| } |
| client->irq = gpio_to_irq(platform_data->irq_gpio); |
| |
| pn548_dev->irq_gpio = platform_data->irq_gpio; |
| pn548_dev->ven_gpio = platform_data->ven_gpio; |
| pn548_dev->firm_gpio = platform_data->fwdl_en_gpio; |
| pn548_dev->clk_req_gpio = platform_data->clk_req_gpio; |
| pn548_dev->client = client; |
| pn548_dev->dev = &client->dev; |
| pn548_dev->do_reading = 0; |
| pn548_dev->nfc_clk = nfc_clk; |
| pn548_dev->irq_wake_enabled = false; |
| |
| /*check if nfc chip is ok*/ |
| ret = check_pn548(client, pn548_dev); |
| if (ret < 0) { |
| pr_err("%s: pn548 check failed \n",__func__); |
| goto err_i2c; |
| } |
| |
| gpio_set_value(pn548_dev->firm_gpio, 0); |
| gpio_set_value(pn548_dev->ven_gpio, 0); //0 |
| |
| /* Initialise mutex and work queue */ |
| init_waitqueue_head(&pn548_dev->read_wq); |
| mutex_init(&pn548_dev->read_mutex); |
| mutex_init(&pn548_dev->irq_wake_mutex); |
| spin_lock_init(&pn548_dev->irq_enabled_lock); |
| wake_lock_init(&pn548_dev->wl,WAKE_LOCK_SUSPEND,"nfc_locker"); |
| |
| /* register as a misc device - character based with one entry point */ |
| pn548_dev->pn548_device.minor = MISC_DYNAMIC_MINOR; |
| pn548_dev->pn548_device.name = CHIP; |
| pn548_dev->pn548_device.fops = &pn548_dev_fops; |
| |
| ret = misc_register(&pn548_dev->pn548_device); |
| if (ret) { |
| dev_err(&client->dev, "%s: misc_register err %d\n", |
| __func__, ret); |
| goto err_misc_register; |
| } |
| |
| /* request irq. the irq is set whenever the chip has data available |
| * for reading. it is cleared when all data has been read. |
| */ |
| pr_info("%s : requesting IRQ %d\n", __func__, client->irq); |
| pn548_dev->irq_enabled = true; |
| ret = request_irq(client->irq, pn548_dev_irq_handler, |
| IRQF_TRIGGER_HIGH | IRQF_NO_SUSPEND | IRQF_ONESHOT, |
| client->name, pn548_dev); |
| if (ret) { |
| dev_err(&client->dev, "request_irq failed\n"); |
| goto err_request_irq_failed; |
| } |
| pn548_disable_irq(pn548_dev); |
| i2c_set_clientdata(client, pn548_dev); |
| |
| pr_info("%s success.\n", __func__); |
| return 0; |
| |
| err_request_irq_failed: |
| misc_deregister(&pn548_dev->pn548_device); |
| err_misc_register: |
| mutex_destroy(&pn548_dev->read_mutex); |
| mutex_destroy(&pn548_dev->irq_wake_mutex); |
| wake_lock_destroy(&pn548_dev->wl); |
| kfree(pn548_dev); |
| err_exit: |
| err_i2c: |
| pn548_gpio_release(platform_data); |
| err_gpio_request: |
| if (nfc_clk) { |
| clk_put(nfc_clk); |
| nfc_clk = NULL; |
| } |
| err_parse_dt: |
| kfree(platform_data); |
| err_platform_data: |
| dev_err(&client->dev, "%s: err %d\n", __func__, ret); |
| return ret; |
| } |
| /* |
| *FUNCTION: pn548_remove |
| *DESCRIPTION: pn548_remove |
| *Parameters |
| * struct i2c_client *client:i2c client data |
| *RETURN VALUE |
| * int: result |
| */ |
| static int pn548_remove(struct i2c_client *client) |
| { |
| struct pn548_dev *pn548_dev; |
| |
| pr_info("%s\n", __func__); |
| |
| pn548_dev = i2c_get_clientdata(client); |
| free_irq(client->irq, pn548_dev); |
| misc_deregister(&pn548_dev->pn548_device); |
| mutex_destroy(&pn548_dev->read_mutex); |
| mutex_destroy(&pn548_dev->irq_wake_mutex); |
| wake_lock_destroy(&pn548_dev->wl); |
| if (pn548_dev->nfc_clk) { |
| clk_put(pn548_dev->nfc_clk); |
| pn548_dev->nfc_clk = NULL; |
| } |
| gpio_free(pn548_dev->irq_gpio); |
| gpio_free(pn548_dev->ven_gpio); |
| gpio_free(pn548_dev->firm_gpio); |
| gpio_free(pn548_dev->clk_req_gpio); |
| kfree(pn548_dev); |
| |
| return 0; |
| } |
| |
| static struct of_device_id pn548_match_table[] = { |
| { .compatible = "nxp,pn548", }, |
| { }, |
| }; |
| |
| MODULE_DEVICE_TABLE(of, pn548_match_table); |
| |
| static const struct i2c_device_id pn548_id[] = { |
| { "pn548", 0 }, |
| { } |
| }; |
| |
| MODULE_DEVICE_TABLE(i2c, pn548_id); |
| |
| static struct i2c_driver pn548_driver = { |
| .id_table = pn548_id, |
| .probe = pn548_probe, |
| .remove = pn548_remove, |
| .driver = { |
| .owner = THIS_MODULE, |
| .name = "pn548", |
| .of_match_table = pn548_match_table, |
| }, |
| }; |
| /* |
| * module load/unload record keeping |
| */ |
| static int __init pn548_dev_init(void) |
| { |
| pr_info("%s\n", __func__); |
| return i2c_add_driver(&pn548_driver); |
| } |
| |
| static void __exit pn548_dev_exit(void) |
| { |
| pr_info("%s\n", __func__); |
| i2c_del_driver(&pn548_driver); |
| } |
| |
| module_init(pn548_dev_init); |
| module_exit(pn548_dev_exit); |
| |
| MODULE_AUTHOR("Sylvain Fonteneau"); |
| MODULE_DESCRIPTION(DRIVER_DESC); |
| MODULE_LICENSE("GPL"); |