diff options
Diffstat (limited to 'sound/soc/codecs/tfa9890.c')
-rw-r--r-- | sound/soc/codecs/tfa9890.c | 476 |
1 files changed, 476 insertions, 0 deletions
diff --git a/sound/soc/codecs/tfa9890.c b/sound/soc/codecs/tfa9890.c new file mode 100644 index 000000000000..d5aa6975c453 --- /dev/null +++ b/sound/soc/codecs/tfa9890.c @@ -0,0 +1,476 @@ +/* + * Synaptics DSX touchscreen driver + * + * Copyright (C) 2012 Synaptics Incorporated + * + * Copyright (C) 2012 Alexandra Chin <alexandra.chin@tw.synaptics.com> + * Copyright (C) 2012 Scott Lin <scott.lin@tw.synaptics.com> + * + * 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. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/io.h> +#include <asm/uaccess.h> +#include <linux/fs.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/input.h> +#include <linux/gpio.h> +#include <linux/miscdevice.h> +#include <linux/regulator/consumer.h> +#include <linux/of_gpio.h> +#include "tfa9890.h" +#ifdef KERNEL_ABOVE_2_6_38 +#include <linux/input/mt.h> +#endif + +#define DRIVER_NAME "tfa9890" +#define MAX_BUFFER_SIZE 512 +#define GPIO_SLEEP_LOW_US 10 +#define RESET_DELAY 500 + +struct tfa9890_dev { + wait_queue_head_t read_wq; + struct mutex read_mutex; + struct device *dev; + struct i2c_client *i2c_client; + struct miscdevice tfa9890_device; + unsigned int ven_gpio; + unsigned int firm_gpio; + unsigned int irq_gpio; + unsigned int irq_flags; + unsigned int irq; + unsigned int reset_gpio; + unsigned int reset_flags; + bool i2c_pull_up; + struct regulator *vdd; + struct regulator *vcc_i2c; + bool do_reading; + bool irq_enabled; + bool cancel_read; + bool first_open; +}; + +static ssize_t tfa9890_dev_read(struct file *filp, char __user *buf, + size_t count, loff_t *offset) +{ + struct tfa9890_dev *tfa9890_dev = filp->private_data; + char tmp[MAX_BUFFER_SIZE]; + int ret; + + if (count > MAX_BUFFER_SIZE) + count = MAX_BUFFER_SIZE; + + + mutex_lock(&tfa9890_dev->read_mutex); + + /* Read data */ + ret = i2c_master_recv(tfa9890_dev->i2c_client, tmp, count); + + if (ret < 0) { + pr_err("%s: i2c_master_recv returned %d\n", __func__, ret); + mutex_unlock(&tfa9890_dev->read_mutex); + return ret; + } + if (ret > count) { + pr_err("%s: received too many bytes from i2c (%d)\n", + __func__, ret); + mutex_unlock(&tfa9890_dev->read_mutex); + return -EIO; + } + + if (copy_to_user(buf, tmp, ret)) { + pr_warning("%s : failed to copy to user space\n", __func__); + mutex_unlock(&tfa9890_dev->read_mutex); + return -EFAULT; + } + mutex_unlock(&tfa9890_dev->read_mutex); + return ret; + +} + +static ssize_t tfa9890_dev_write(struct file *filp, const char __user *buf, + size_t count, loff_t *offset) +{ + struct tfa9890_dev *tfa9890_dev = filp->private_data; + char tmp[MAX_BUFFER_SIZE]; + int ret; + + if (count > MAX_BUFFER_SIZE) + count = MAX_BUFFER_SIZE; + + if (copy_from_user(tmp, buf, count)) { + pr_err("%s : failed to copy from user space\n", __func__); + return -EFAULT; + } + + /* Write data */ + ret = i2c_master_send(tfa9890_dev->i2c_client, tmp, count); + if (ret != count) { + pr_err("%s : i2c_master_send returned %d\n", __func__, ret); + ret = -EIO; + } + return ret; +} + +static int tfa9890_dev_open(struct inode *inode, struct file *filp) +{ + int ret; + + struct tfa9890_dev *tfa9890_dev = container_of(filp->private_data, + struct tfa9890_dev, + tfa9890_device); + + filp->private_data = tfa9890_dev; + pr_err("%s : %d,%d\n", __func__, imajor(inode), iminor(inode)); + + if (tfa9890_dev->first_open) { + if (gpio_is_valid(tfa9890_dev->reset_gpio)) { + /* configure tfa9890s reset out gpio */ + ret = gpio_request(tfa9890_dev->reset_gpio, + "tfa9890_reset_gpio"); + if (ret) { + dev_err(&tfa9890_dev->i2c_client->dev, "unable to request gpio [%d]\n", + tfa9890_dev->reset_gpio); + goto err_reset_gpio_dir; + } + + ret = gpio_direction_output(tfa9890_dev->reset_gpio, 1); + if (ret) { + dev_err(&tfa9890_dev->i2c_client->dev, + "unable to set direction for gpio [%d]\n", + tfa9890_dev->reset_gpio); + goto err_reset_gpio_dir; + } + + gpio_set_value(tfa9890_dev->reset_gpio, 1); + mdelay(GPIO_SLEEP_LOW_US); + gpio_set_value(tfa9890_dev->reset_gpio, 0); + mdelay(RESET_DELAY); + } + tfa9890_dev->first_open = false; + printk("%s enter, reset PA at first open\n", __func__); + } + + return 0; + +err_reset_gpio_dir: + if (gpio_is_valid(tfa9890_dev->reset_gpio)) + gpio_free(tfa9890_dev->reset_gpio); + + return ret; +} + +static long tfa9890_dev_ioctl(struct file *filp, + unsigned int cmd, unsigned long arg) +{ + struct tfa9890_dev *tfa9890_dev = filp->private_data; + switch(cmd) + { + case I2C_SLAVE: + { + tfa9890_dev->i2c_client->addr = arg; + break; + } + case ENABLE_MI2S_CLK: + { + udelay(500); + msm_q6_enable_mi2s_clocks(arg); + //printk("[%s][%d]\n",__func__,__LINE__); + break; + } + default: + break; + } + + return 0; +} + +static const struct file_operations tfa9890_dev_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = tfa9890_dev_read, + .write = tfa9890_dev_write, + .open = tfa9890_dev_open, + .unlocked_ioctl = tfa9890_dev_ioctl, +}; + +static int tfa9890_parse_dt(struct device *dev, + struct tfa9890_i2c_platform_data *pdata) +{ + int ret = 0; + struct device_node *np = dev->of_node; + + /* reset, irq gpio info */ + pdata->reset_gpio = of_get_named_gpio_flags(np, + "reset-gpio", 0, &pdata->reset_flags); + pr_info("%s,pdata->reset_gpio = %d\n", __func__, pdata->reset_gpio); + pdata->irq_gpio = of_get_named_gpio_flags(np, + "irq-gpio", 0, &pdata->irq_flags); + pr_info("%s,pdata->irq_gpio = %d\n", __func__, pdata->irq_gpio); + + pdata->i2c_pull_up = of_property_read_bool(np, + "tfa9890,i2c-pull-up"); + pr_info("%s,pdata->i2c_pull_up = %d\n", __func__, pdata->i2c_pull_up); + return ret; +} + + /** + * nxp_tfa9890_probe() + * + * Called by the kernel when an association with an I2C device of the + * same name is made (after doing i2c_add_driver). + * + * This funtion allocates and initializes the resources for the driver + * as an input driver, turns on the power to the sensor, queries the + * sensor for its supported Functions and characteristics, registers + * the driver to the input subsystem, sets up the interrupt, handles + * the registration of the early_suspend and late_resume functions, + * and creates a work queue for detection of other expansion Function + * modules. + */ +static int nxp_tfa9890_probe(struct i2c_client *client, + const struct i2c_device_id *dev_id) +{ + int ret = 0; + struct tfa9890_i2c_platform_data *platform_data; + struct tfa9890_dev *tfa9890_dev; + + printk("%s\n", __func__); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + pr_err("%s: i2c check failed\n", __func__); + ret = -ENODEV; + goto err_i2c; + } + + if (client->dev.of_node) { + platform_data = devm_kzalloc(&client->dev, + sizeof(*platform_data), + GFP_KERNEL); + if (!platform_data) { + dev_err(&client->dev, "Failed to allocate memory\n"); + return -ENOMEM; + } + + ret = tfa9890_parse_dt(&client->dev, platform_data); + if (ret) + return ret; + } else { + platform_data = client->dev.platform_data; + } + + + tfa9890_dev = kzalloc(sizeof(*tfa9890_dev), GFP_KERNEL); + if (tfa9890_dev == NULL) { + pr_err("failed to allocate memory for module data\n"); + ret = -ENOMEM; + goto err_exit; + } + + tfa9890_dev->i2c_client = client; + tfa9890_dev->dev = &client->dev; + tfa9890_dev->do_reading = 0; + + /* Initialise mutex and work queue */ + init_waitqueue_head(&tfa9890_dev->read_wq); + mutex_init(&tfa9890_dev->read_mutex); + + tfa9890_dev->irq_enabled = false; + tfa9890_dev->tfa9890_device.minor = MISC_DYNAMIC_MINOR; + tfa9890_dev->tfa9890_device.name = "tfa9890"; + tfa9890_dev->tfa9890_device.fops = &tfa9890_dev_fops; + + if (gpio_is_valid(platform_data->reset_gpio)) { + /* configure tfa9890s reset out gpio */ + ret = gpio_request(platform_data->reset_gpio, + "tfa9890_reset_gpio"); + if (ret) { + dev_err(&client->dev, "unable to request gpio [%d]\n", + platform_data->reset_gpio); + goto err_reset_gpio_dir; + } + + ret = gpio_direction_output(platform_data->reset_gpio, 1); + if (ret) { + dev_err(&client->dev, + "unable to set direction for gpio [%d]\n", + platform_data->reset_gpio); + goto err_reset_gpio_dir; + } + + gpio_set_value(platform_data->reset_gpio, 1); + mdelay(GPIO_SLEEP_LOW_US); + gpio_set_value(platform_data->reset_gpio, 0); + mdelay(RESET_DELAY); + } + + ret = misc_register(&tfa9890_dev->tfa9890_device); + if (ret) { + pr_err("%s : misc_register failed\n", __FILE__); + goto err_misc_register; + } + + printk("%s Done\n", __func__); + + return 0; + +err_misc_register: + misc_deregister(&tfa9890_dev->tfa9890_device); + mutex_destroy(&tfa9890_dev->read_mutex); + kfree(tfa9890_dev); + +#if 1 +err_reset_gpio_dir: + if (gpio_is_valid(platform_data->reset_gpio)) + gpio_free(platform_data->reset_gpio); +#endif + +err_exit: +err_i2c: + kfree(platform_data); + + return ret; +} + + /** + * nxp_tfa9890_remove() + * + * Called by the kernel when the association with an I2C device of the + * same name is broken (when the driver is unloaded). + * + * This funtion terminates the work queue, stops sensor data acquisition, + * frees the interrupt, unregisters the driver from the input subsystem, + * turns off the power to the sensor, and frees other allocated resources. + */ +static int nxp_tfa9890_remove(struct i2c_client *client) +{ + struct tfa9890_dev *tfa9890_dev; + + pr_info("%s\n", __func__); + tfa9890_dev = i2c_get_clientdata(client); + misc_deregister(&tfa9890_dev->tfa9890_device); + mutex_destroy(&tfa9890_dev->read_mutex); + + kfree(tfa9890_dev); + + return 0; +} + +#ifdef CONFIG_PM + /** + * nxp_tfa9890_suspend() + * + * Called by the kernel during the suspend phase when the system + * enters suspend. + * + * This function stops finger data acquisition and puts the sensor to + * sleep (if not already done so during the early suspend phase), + * disables the interrupt, and turns off the power to the sensor. + */ +static int nxp_tfa9890_suspend(struct device *dev) +{ + pr_info(KERN_ALERT "----------------suspend"); + + return 0; +} + + /** + * nxp_tfa9890_resume() + * + * Called by the kernel during the resume phase when the system + * wakes up from suspend. + * + * This function turns on the power to the sensor, wakes the sensor + * from sleep, enables the interrupt, and starts finger data + * acquisition. + */ +static int nxp_tfa9890_resume(struct device *dev) +{ + return 0; +} + +static const struct dev_pm_ops nxp_tfa9890_dev_pm_ops = { + .suspend = nxp_tfa9890_suspend, + .resume = nxp_tfa9890_resume, +}; +#endif + +static const struct i2c_device_id nxp_tfa9890_id_table[] = { + {DRIVER_NAME, 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, nxp_tfa9890_id_table); + +#ifdef CONFIG_OF +static struct of_device_id tfa9890_match_table[] = { + { .compatible = "nxp,tfa9890",}, + { }, +}; +#else +#define tfa9890_match_table NULL +#endif + +static struct i2c_driver nxp_tfa9890_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = tfa9890_match_table, +#ifdef CONFIG_PM + .pm = &nxp_tfa9890_dev_pm_ops, +#endif + }, + .probe = nxp_tfa9890_probe, + .remove =nxp_tfa9890_remove, + .id_table = nxp_tfa9890_id_table, +}; + + /** + * nxp_tfa9890_init() + * + * Called by the kernel during do_initcalls (if built-in) + * or when the driver is loaded (if a module). + * + * This function registers the driver to the I2C subsystem. + * + */ +static int __init nxp_tfa9890_init(void) +{ + printk("%s\n",__func__); + return i2c_add_driver(&nxp_tfa9890_driver); +} + + /** + * nxp_tfa9890_exit() + * + * Called by the kernel when the driver is unloaded. + * + * This funtion unregisters the driver from the I2C subsystem. + * + */ +static void __exit nxp_tfa9890_exit(void) +{ + i2c_del_driver(&nxp_tfa9890_driver); + + return; +} + +module_init(nxp_tfa9890_init); +module_exit(nxp_tfa9890_exit); + +MODULE_AUTHOR("NXP, Inc."); +MODULE_DESCRIPTION("NXP TFA9885 I2C Touch Driver"); +MODULE_LICENSE("GPL v2"); |