| /* 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"); |