summaryrefslogtreecommitdiff
path: root/drivers/hwmon/epm_adc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/hwmon/epm_adc.c')
-rw-r--r--drivers/hwmon/epm_adc.c496
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");