diff options
Diffstat (limited to 'drivers/hwmon/epm_adc.c')
-rw-r--r-- | drivers/hwmon/epm_adc.c | 496 |
1 files changed, 496 insertions, 0 deletions
diff --git a/drivers/hwmon/epm_adc.c b/drivers/hwmon/epm_adc.c new file mode 100644 index 000000000000..905e6f3252c1 --- /dev/null +++ b/drivers/hwmon/epm_adc.c @@ -0,0 +1,496 @@ +/* Copyright (c) 2012-2015, 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/init.h> +#include <linux/fs.h> +#include <linux/mutex.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/of_gpio.h> +#include <linux/hwmon.h> +#include <linux/delay.h> +#include <linux/epm_adc.h> +#include <linux/uaccess.h> +#include <linux/spi/spi.h> +#include <linux/hwmon-sysfs.h> +#include <linux/miscdevice.h> +#include <linux/platform_device.h> + +#define EPM_ADC_DRIVER_NAME "epm_adc" +#define EPM_ADC_MAX_FNAME 20 +#define EPM_ADC_CONVERSION_DELAY 100 /* milliseconds */ + +#define EPM_ADC_SPI_BITS_PER_WORD 8 +#define GPIO_EPM_GLOBAL_ENABLE 86 +#define GPIO_EPM_MARKER1 96 +#define GPIO_EPM_MARKER2 85 +#define EPM_ADC_CONVERSION_TIME_MIN 50000 +#define EPM_ADC_CONVERSION_TIME_MAX 51000 +/* PSoc Commands */ + +#define EPM_PSOC_GLOBAL_ENABLE 81 +#define EPM_PSOC_VREF_VOLTAGE 2048 +#define EPM_PSOC_MAX_ADC_CODE_15_BIT 32767 +#define EPM_PSOC_MAX_ADC_CODE_12_BIT 4096 +#define EPM_GLOBAL_ENABLE_MIN_DELAY 5000 +#define EPM_GLOBAL_ENABLE_MAX_DELAY 5100 + +struct epm_adc_drv { + struct platform_device *pdev; + struct device *hwmon; + struct spi_device *epm_spi_client; + struct mutex conv_lock; + uint32_t bus_id; + struct miscdevice misc; + uint32_t channel_mask; + uint32_t epm_global_en_gpio; + struct epm_chan_properties epm_psoc_ch_prop[0]; +}; + +static struct epm_adc_drv *epm_adc_drv; + +static int epm_adc_psoc_gpio_init(struct epm_adc_drv *epm_adc, + bool enable) +{ + int rc = 0; + + if (enable) { + rc = gpio_request(epm_adc->epm_global_en_gpio, + "EPM_PSOC_GLOBAL_EN"); + if (!rc) { + gpio_direction_output(epm_adc->epm_global_en_gpio, 1); + } else { + pr_err("%s: Configure EPM_GLOBAL_EN Failed\n", + __func__); + return rc; + } + } else { + gpio_direction_output(epm_adc->epm_global_en_gpio, 0); + gpio_free(epm_adc->epm_global_en_gpio); + } + + return 0; +} + +static int epm_request_marker1(void) +{ + int rc = 0; + + rc = gpio_request(GPIO_EPM_MARKER1, "EPM_MARKER1"); + if (!rc) { + gpio_direction_output(GPIO_EPM_MARKER1, 1); + } else { + pr_err("%s: Configure MARKER1 GPIO Failed\n", + __func__); + return rc; + } + + return 0; +} + +static int epm_set_marker1(struct epm_marker_level *marker_init) +{ + gpio_set_value(GPIO_EPM_MARKER1, marker_init->level); + + return 0; +} + +static int epm_request_marker2(void) +{ + int rc = 0; + + rc = gpio_request(GPIO_EPM_MARKER2, "EPM_MARKER2"); + if (!rc) { + gpio_direction_output(GPIO_EPM_MARKER2, 1); + } else { + pr_err("%s: Configure MARKER2 GPIO Failed\n", + __func__); + return rc; + } + + return 0; +} + +static int epm_set_marker2(struct epm_marker_level *marker_init) +{ + gpio_set_value(GPIO_EPM_MARKER2, marker_init->level); + + return 0; +} + +static int epm_marker1_release(void) +{ + gpio_free(GPIO_EPM_MARKER1); + + return 0; +} + +static int epm_marker2_release(void) +{ + gpio_free(GPIO_EPM_MARKER2); + + return 0; +} + +static int epm_psoc_generic_request(struct epm_adc_drv *epm_adc, + struct epm_generic_request *psoc_get_data) +{ + struct spi_message m; + struct spi_transfer t; + char tx_buf[64], rx_buf[64]; + int rc = 0, data_loop = 0; + + spi_setup(epm_adc->epm_spi_client); + + memset(&t, 0, sizeof(t)); + memset(tx_buf, 0, sizeof(tx_buf)); + memset(rx_buf, 0, sizeof(tx_buf)); + t.tx_buf = tx_buf; + t.rx_buf = rx_buf; + spi_message_init(&m); + spi_message_add_tail(&t, &m); + + for (data_loop = 0; data_loop < 64; data_loop++) + tx_buf[data_loop] = psoc_get_data->buf[data_loop]; + + t.len = sizeof(tx_buf); + t.bits_per_word = EPM_ADC_SPI_BITS_PER_WORD; + + rc = spi_sync(epm_adc->epm_spi_client, &m); + if (rc) + return rc; + + for (data_loop = 0; data_loop < 64; data_loop++) + psoc_get_data->buf[data_loop] = rx_buf[data_loop]; + + return rc; +} + +static long epm_adc_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct epm_adc_drv *epm_adc = epm_adc_drv; + + switch (cmd) { + case EPM_MARKER1_REQUEST: + { + uint32_t result; + result = epm_request_marker1(); + + if (copy_to_user((void __user *)arg, &result, + sizeof(uint32_t))) + return -EFAULT; + break; + } + case EPM_MARKER2_REQUEST: + { + uint32_t result; + result = epm_request_marker2(); + + if (copy_to_user((void __user *)arg, &result, + sizeof(uint32_t))) + return -EFAULT; + break; + } + case EPM_MARKER1_SET_LEVEL: + { + struct epm_marker_level marker_init; + uint32_t result; + + if (copy_from_user(&marker_init, (void __user *)arg, + sizeof(struct epm_marker_level))) + return -EFAULT; + + result = epm_set_marker1(&marker_init); + + if (copy_to_user((void __user *)arg, &result, + sizeof(uint32_t))) + return -EFAULT; + break; + } + case EPM_MARKER2_SET_LEVEL: + { + struct epm_marker_level marker_init; + uint32_t result; + + if (copy_from_user(&marker_init, (void __user *)arg, + sizeof(struct epm_marker_level))) + return -EFAULT; + + result = epm_set_marker2(&marker_init); + + if (copy_to_user((void __user *)arg, &result, + sizeof(uint32_t))) + return -EFAULT; + break; + } + case EPM_MARKER1_RELEASE: + { + uint32_t result; + result = epm_marker1_release(); + + if (copy_to_user((void __user *)arg, &result, + sizeof(uint32_t))) + return -EFAULT; + break; + } + case EPM_MARKER2_RELEASE: + { + uint32_t result; + result = epm_marker2_release(); + + if (copy_to_user((void __user *)arg, &result, + sizeof(uint32_t))) + return -EFAULT; + break; + } + case EPM_PSOC_ADC_INIT: + { + int rc; + + rc = epm_adc_psoc_gpio_init(epm_adc, true); + if (rc) + pr_err("GPIO init failed with %d\n", rc); + + if (copy_to_user((void __user *)arg, &rc, + sizeof(int))) + return -EFAULT; + break; + } + case EPM_PSOC_ADC_DEINIT: + { + int rc; + rc = epm_adc_psoc_gpio_init(epm_adc, false); + + if (copy_to_user((void __user *)arg, &rc, + sizeof(int))) + return -EFAULT; + break; + } + case EPM_PSOC_GENERIC_REQUEST: + { + struct epm_generic_request psoc_get_data; + int rc; + + if (copy_from_user(&psoc_get_data, + (void __user *)arg, + sizeof(struct + epm_generic_request))) + return -EFAULT; + + rc = epm_psoc_generic_request(epm_adc, &psoc_get_data); + if (rc) + pr_err("Generic request failed\n"); + + if (copy_to_user((void __user *)arg, &psoc_get_data, + sizeof(struct + epm_generic_request))) + return -EFAULT; + break; + } + default: + return -EINVAL; + } + + return 0; +} + +#ifdef CONFIG_COMPAT +static long epm_adc_compat_ioctl_process(struct file *filep, + unsigned int cmd, unsigned long arg) +{ + arg = (unsigned long)compat_ptr(arg); + return epm_adc_ioctl(filep, cmd, arg); +} +#endif /* CONFIG_COMPAT */ + +const struct file_operations epm_adc_fops = { + .unlocked_ioctl = epm_adc_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = epm_adc_compat_ioctl_process, +#endif /* CONFIG_COMPAT */ +}; + +static int get_device_tree_data(struct spi_device *spi) +{ + const struct device_node *node = spi->dev.of_node; + struct epm_adc_drv *epm_adc; + u32 *epm_ch_gain, *epm_ch_rsense; + u32 rc = 0, epm_num_channels, i, channel_mask, epm_gpio_num; + + if (!node) + return -EINVAL; + + rc = of_property_read_u32(node, + "qcom,channels", &epm_num_channels); + if (rc) { + dev_err(&spi->dev, "missing channel numbers\n"); + return -ENODEV; + } + + epm_ch_gain = devm_kzalloc(&spi->dev, + epm_num_channels * sizeof(u32), GFP_KERNEL); + if (!epm_ch_gain) { + dev_err(&spi->dev, "cannot allocate gain\n"); + return -ENOMEM; + } + + epm_ch_rsense = devm_kzalloc(&spi->dev, + epm_num_channels * sizeof(u32), GFP_KERNEL); + if (!epm_ch_rsense) { + dev_err(&spi->dev, "cannot allocate rsense\n"); + return -ENOMEM; + } + + rc = of_property_read_u32_array(node, + "qcom,gain", epm_ch_gain, epm_num_channels); + if (rc) { + dev_err(&spi->dev, "invalid gain property:%d\n", rc); + return rc; + } + + rc = of_property_read_u32_array(node, + "qcom,rsense", epm_ch_rsense, epm_num_channels); + if (rc) { + dev_err(&spi->dev, "invalid rsense property:%d\n", rc); + return rc; + } + + rc = of_property_read_u32(node, + "qcom,channel-type", &channel_mask); + if (rc) { + dev_err(&spi->dev, "missing channel mask\n"); + return -ENODEV; + } + + epm_gpio_num = of_get_named_gpio(spi->dev.of_node, + "qcom,epm-enable-gpio", 0); + if (epm_gpio_num < 0) { + dev_err(&spi->dev, "missing global en gpio num\n"); + return -ENODEV; + } + + epm_adc = devm_kzalloc(&spi->dev, + sizeof(struct epm_adc_drv) + + (epm_num_channels * + sizeof(struct epm_chan_properties)), + GFP_KERNEL); + if (!epm_adc) { + dev_err(&spi->dev, "Unable to allocate memory\n"); + return -ENOMEM; + } + + for (i = 0; i < epm_num_channels; i++) { + epm_adc->epm_psoc_ch_prop[i].resistorvalue = + epm_ch_rsense[i]; + epm_adc->epm_psoc_ch_prop[i].gain = + epm_ch_gain[i]; + } + + epm_adc->channel_mask = channel_mask; + epm_adc->epm_global_en_gpio = epm_gpio_num; + epm_adc_drv = epm_adc; + + return 0; +} + +static int epm_adc_psoc_spi_probe(struct spi_device *spi) +{ + + struct epm_adc_drv *epm_adc; + struct device_node *node = spi->dev.of_node; + int rc = 0; + + if (node) { + rc = get_device_tree_data(spi); + if (rc) + return rc; + } else { + epm_adc = epm_adc_drv; + epm_adc_drv->epm_spi_client = spi; + epm_adc_drv->epm_spi_client->bits_per_word = + EPM_ADC_SPI_BITS_PER_WORD; + return rc; + } + + epm_adc = epm_adc_drv; + epm_adc->misc.name = EPM_ADC_DRIVER_NAME; + epm_adc->misc.minor = MISC_DYNAMIC_MINOR; + + if (node) { + epm_adc->misc.fops = &epm_adc_fops; + if (misc_register(&epm_adc->misc)) { + pr_err("Unable to register misc device!\n"); + return -EFAULT; + } + } + + epm_adc_drv->epm_spi_client = spi; + epm_adc_drv->epm_spi_client->bits_per_word = + EPM_ADC_SPI_BITS_PER_WORD; + + epm_adc->hwmon = hwmon_device_register(&spi->dev); + if (IS_ERR(epm_adc->hwmon)) { + dev_err(&spi->dev, "hwmon_device_register failed\n"); + return rc; + } + + mutex_init(&epm_adc->conv_lock); + return rc; +} + +static int epm_adc_psoc_spi_remove(struct spi_device *spi) +{ + epm_adc_drv->epm_spi_client = NULL; + return 0; +} + +static const struct of_device_id epm_adc_psoc_match_table[] = { + { .compatible = "cy,epm-adc-cy8c5568lti-114", + }, + {} +}; + +static struct spi_driver epm_spi_driver = { + .probe = epm_adc_psoc_spi_probe, + .remove = epm_adc_psoc_spi_remove, + .driver = { + .name = EPM_ADC_DRIVER_NAME, + .of_match_table = epm_adc_psoc_match_table, + }, +}; + +static int __init epm_adc_init(void) +{ + int ret = 0; + + ret = spi_register_driver(&epm_spi_driver); + if (ret) + pr_err("%s: spi register failed: rc=%d\n", __func__, ret); + + return ret; +} + +static void __exit epm_adc_exit(void) +{ + spi_unregister_driver(&epm_spi_driver); +} + +module_init(epm_adc_init); +module_exit(epm_adc_exit); + +MODULE_DESCRIPTION("EPM ADC Driver"); +MODULE_ALIAS("platform:epm_adc"); +MODULE_LICENSE("GPL v2"); |