Add ADC driver for the ADC controller found on HiSilicon BVT SOCs, like Hi3516CV300, etc. The ADC controller is primarily in charge of detecting voltage. Reviewed-by: Jiancheng Xue <xuejiancheng@xxxxxxxxxxxxx> Signed-off-by: Allen Liu <liurenzhong@xxxxxxxxxxxxx> --- .../devicetree/bindings/iio/adc/hibvt-lsadc.txt | 26 ++ drivers/iio/adc/Kconfig | 10 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/hibvt_lsadc.c | 344 +++++++++++++++++++++ 4 files changed, 381 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/hibvt-lsadc.txt create mode 100644 drivers/iio/adc/hibvt_lsadc.c diff --git a/Documentation/devicetree/bindings/iio/adc/hibvt-lsadc.txt b/Documentation/devicetree/bindings/iio/adc/hibvt-lsadc.txt new file mode 100644 index 0000000..63de46e --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/hibvt-lsadc.txt @@ -0,0 +1,26 @@ +Hisilicon BVT Low Speed (LS) A/D Converter bindings + +Required properties: +- compatible: should be "hisilicon,<name>-lsadc" + - "hisilicon,hibvt-lsadc": for hi3516cv300 + +- reg: physical base address of the controller and length of memory mapped + region. +- interrupts: The interrupt number to the cpu. The interrupt specifier format + depends on the interrupt controller. +- #io-channel-cells: Should be 1, see ../iio-bindings.txt + +Optional properties: +- resets: Must contain an entry for each entry in reset-names if need support + this option. See ../reset/reset.txt for details. +- reset-names: Must include the name "saradc-apb". + +Example: + lsadc: hibvt-lsadc@120e0000 { + compatible = "hisilicon,hibvt-lsadc"; + reg = <0x120e0000 0x1000>; + interrupts = <19>; + resets = <&crg 0x7c 3>; + reset-names = "lsadc-crg"; + status = "disabled"; + }; diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 99c0514..0443f51 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -225,6 +225,16 @@ config HI8435 This driver can also be built as a module. If so, the module will be called hi8435. +config HIBVT_LSADC + tristate "HIBVT LSADC driver" + depends on ARCH_HISI || COMPILE_TEST + help + Say yes here to build support for the LSADC found in SoCs from + hisilicon BVT chip. + + To compile this driver as a module, choose M here: the + module will be called hibvt_lsadc. + config INA2XX_ADC tristate "Texas Instruments INA2xx Power Monitors IIO driver" depends on I2C && !SENSORS_INA2XX diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 7a40c04..6554d92 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -23,6 +23,7 @@ obj-$(CONFIG_DA9150_GPADC) += da9150-gpadc.o obj-$(CONFIG_EXYNOS_ADC) += exynos_adc.o obj-$(CONFIG_FSL_MX25_ADC) += fsl-imx25-gcq.o obj-$(CONFIG_HI8435) += hi8435.o +obj-$(CONFIG_HIBVT_LSADC) += hibvt_lsadc.o obj-$(CONFIG_IMX7D_ADC) += imx7d_adc.o obj-$(CONFIG_INA2XX_ADC) += ina2xx-adc.o obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o diff --git a/drivers/iio/adc/hibvt_lsadc.c b/drivers/iio/adc/hibvt_lsadc.c new file mode 100644 index 0000000..a20afe8 --- /dev/null +++ b/drivers/iio/adc/hibvt_lsadc.c @@ -0,0 +1,344 @@ +/* + * Hisilicon BVT Low Speed (LS) A/D Converter + * Copyright (C) 2016 HiSilicon Technologies Co., Ltd. + * + * 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/module.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/clk.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/reset.h> +#include <linux/regulator/consumer.h> +#include <linux/iio/iio.h> + +/* hisilicon bvt adc registers definitions */ +#define LSADC_CONFIG 0x00 +#define CONFIG_DEGLITCH BIT(17) +#define CONFIG_RESET BIT(15) +#define CONFIG_POWERDOWN BIT(14) +#define CONFIG_MODE BIT(13) +#define CONFIG_CHC_VALID BIT(10) +#define CONFIG_CHB_VALID BIT(9) +#define CONFIG_CHA_VALID BIT(8) + +#define LSADC_TIMESCAN 0x08 +#define LSADC_INTEN 0x10 +#define LSADC_INTSTATUS 0x14 +#define LSADC_INTCLR 0x18 +#define LSADC_START 0x1C +#define LSADC_STOP 0x20 +#define LSADC_ACTBIT 0x24 +#define LSADC_CHNDATA 0x2C + +#define ADC_CON_EN (1u << 0) +#define ADC_CON_DEN (0u << 0) + +#define ADC_NUM_BITS 10 + +/* fix clk:3000000, default tscan set 10ms */ +#define DEF_ADC_TSCAN_MS (10*3000) + +#define LSADC_CHN_MASK 0x7 + +#define LSADC_TIMEOUT msecs_to_jiffies(100) + +/* default voltage scale for every channel <mv> */ +static int g_voltage[] = { + 3300, 3300, 3300 +}; + +struct hibvt_lsadc { + void __iomem *regs; + struct completion completion; + struct reset_control *reset; + const struct hibvt_lsadc_data *data; + unsigned int cur_chn; + unsigned int value; +}; + +struct hibvt_lsadc_data { + int num_bits; + const struct iio_chan_spec *channels; + int num_channels; + + void (*clear_irq)(struct hibvt_lsadc *info, int mask); + void (*start_conv)(struct hibvt_lsadc *info); + void (*stop_conv)(struct hibvt_lsadc *info); +}; + +static int hibvt_lsadc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct hibvt_lsadc *info = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&indio_dev->mlock); + + reinit_completion(&info->completion); + + /* Select the channel to be used */ + info->cur_chn = chan->channel; + + if (info->data->start_conv) + info->data->start_conv(info); + + if (!wait_for_completion_timeout(&info->completion, + LSADC_TIMEOUT)) { + if (info->data->stop_conv) + info->data->stop_conv(info); + mutex_unlock(&indio_dev->mlock); + return -ETIMEDOUT; + } + + *val = info->value; + mutex_unlock(&indio_dev->mlock); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = g_voltage[chan->channel]; + *val2 = info->data->num_bits; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static irqreturn_t hibvt_lsadc_isr(int irq, void *dev_id) +{ + struct hibvt_lsadc *info = (struct hibvt_lsadc *)dev_id; + int mask; + + mask = readl(info->regs + LSADC_INTSTATUS); + mask &= LSADC_CHN_MASK; + + /* Clear irq */ + if (info->data->clear_irq) + info->data->clear_irq(info, mask); + + /* Read value */ + info->value = readl(info->regs + LSADC_CHNDATA + (info->cur_chn << 2)); + info->value &= GENMASK(info->data->num_bits - 1, 0); + + /* stop adc */ + if (info->data->stop_conv) + info->data->stop_conv(info); + + complete(&info->completion); + + return IRQ_HANDLED; +} + +static const struct iio_info hibvt_lsadc_iio_info = { + .read_raw = hibvt_lsadc_read_raw, + .driver_module = THIS_MODULE, +}; + +#define ADC_CHANNEL(_index, _id) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = _index, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .datasheet_name = _id, \ +} + +static const struct iio_chan_spec hibvt_lsadc_iio_channels[] = { + ADC_CHANNEL(0, "adc0"), + ADC_CHANNEL(1, "adc1"), + ADC_CHANNEL(2, "adc2"), +}; + +static void hibvt_lsadc_clear_irq(struct hibvt_lsadc *info, int mask) +{ + writel(mask, info->regs + LSADC_INTCLR); +} + +static void hibvt_lsadc_start_conv(struct hibvt_lsadc *info) +{ + unsigned int con; + + /* set number bit */ + con = GENMASK(info->data->num_bits - 1, 0); + writel(con, (info->regs + LSADC_ACTBIT)); + + /* config */ + con = readl(info->regs + LSADC_CONFIG); + con &= ~CONFIG_RESET; + con |= (CONFIG_POWERDOWN | CONFIG_DEGLITCH | CONFIG_MODE); + con &= ~(CONFIG_CHA_VALID | CONFIG_CHB_VALID | CONFIG_CHC_VALID); + con |= (CONFIG_CHA_VALID << info->cur_chn); + writel(con, (info->regs + LSADC_CONFIG)); + + /* set timescan */ + writel(DEF_ADC_TSCAN_MS, (info->regs + LSADC_TIMESCAN)); + + /* clear interrupt */ + writel(LSADC_CHN_MASK, info->regs + LSADC_INTCLR); + + /* enable interrupt */ + writel(ADC_CON_EN, (info->regs + LSADC_INTEN)); + + /* start scan */ + writel(ADC_CON_EN, (info->regs + LSADC_START)); +} + +static void hibvt_lsadc_stop_conv(struct hibvt_lsadc *info) +{ + /* reset the timescan */ + writel(ADC_CON_DEN, (info->regs + LSADC_TIMESCAN)); + + /* disable interrupt */ + writel(ADC_CON_DEN, (info->regs + LSADC_INTEN)); + + /* stop scan */ + writel(ADC_CON_EN, (info->regs + LSADC_STOP)); +} + +static const struct hibvt_lsadc_data lsadc_data = { + .num_bits = ADC_NUM_BITS, + .channels = hibvt_lsadc_iio_channels, + .num_channels = ARRAY_SIZE(hibvt_lsadc_iio_channels), + + .clear_irq = hibvt_lsadc_clear_irq, + .start_conv = hibvt_lsadc_start_conv, + .stop_conv = hibvt_lsadc_stop_conv, +}; + +static const struct of_device_id hibvt_lsadc_match[] = { + { + .compatible = "hisilicon,hibvt-lsadc", + .data = &lsadc_data, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, hibvt_lsadc_match); + +/** + * Reset LSADC Controller. + */ +static void hibvt_lsadc_reset_controller(struct reset_control *reset) +{ + reset_control_assert(reset); + usleep_range(10, 20); + reset_control_deassert(reset); +} + +static int hibvt_lsadc_probe(struct platform_device *pdev) +{ + struct hibvt_lsadc *info = NULL; + struct device_node *np = pdev->dev.of_node; + struct iio_dev *indio_dev = NULL; + struct resource *mem; + const struct of_device_id *match; + int ret; + int irq; + + if (!np) + return -ENODEV; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info)); + if (!indio_dev) { + dev_err(&pdev->dev, "failed allocating iio device\n"); + return -ENOMEM; + } + info = iio_priv(indio_dev); + + match = of_match_device(hibvt_lsadc_match, &pdev->dev); + info->data = match->data; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + info->regs = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(info->regs)) + return PTR_ERR(info->regs); + + /* + * The reset should be an optional property, as it should work + * with old devicetrees as well + */ + info->reset = devm_reset_control_get(&pdev->dev, "lsadc-crg"); + if (IS_ERR(info->reset)) { + ret = PTR_ERR(info->reset); + if (ret != -ENOENT) + return ret; + + dev_dbg(&pdev->dev, "no reset control found\n"); + info->reset = NULL; + } + + init_completion(&info->completion); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "no irq resource?\n"); + return irq; + } + + ret = devm_request_irq(&pdev->dev, irq, hibvt_lsadc_isr, + 0, dev_name(&pdev->dev), info); + if (ret < 0) { + dev_err(&pdev->dev, "failed requesting irq %d\n", irq); + return ret; + } + + if (info->reset) + hibvt_lsadc_reset_controller(info->reset); + + platform_set_drvdata(pdev, indio_dev); + + indio_dev->name = dev_name(&pdev->dev); + indio_dev->dev.parent = &pdev->dev; + indio_dev->dev.of_node = pdev->dev.of_node; + indio_dev->info = &hibvt_lsadc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + indio_dev->channels = info->data->channels; + indio_dev->num_channels = info->data->num_channels; + + ret = iio_device_register(indio_dev); + if (ret < 0) { + dev_err(&pdev->dev, "failed register iio device\n"); + return ret; + } + + return 0; +} + +static int hibvt_lsadc_remove(struct platform_device *pdev) +{ + struct iio_dev *indio_dev = platform_get_drvdata(pdev); + + iio_device_unregister(indio_dev); + + return 0; +} + +static struct platform_driver hibvt_lsadc_driver = { + .probe = hibvt_lsadc_probe, + .remove = hibvt_lsadc_remove, + .driver = { + .name = "hibvt-lsadc", + .of_match_table = hibvt_lsadc_match, + }, +}; + +module_platform_driver(hibvt_lsadc_driver); + +MODULE_AUTHOR("Allen Liu <liurenzhong@xxxxxxxxxxxxx>"); +MODULE_DESCRIPTION("hisilicon BVT LSADC driver"); +MODULE_LICENSE("GPL v2"); -- 2.1.4 -- To unsubscribe from this list: send the line "unsubscribe linux-iio" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html