diff options
Diffstat (limited to 'drivers/aiodev')
-rw-r--r-- | drivers/aiodev/Kconfig | 24 | ||||
-rw-r--r-- | drivers/aiodev/Makefile | 4 | ||||
-rw-r--r-- | drivers/aiodev/am335x_adc.c | 9 | ||||
-rw-r--r-- | drivers/aiodev/core.c | 24 | ||||
-rw-r--r-- | drivers/aiodev/imx7d_adc.c | 432 | ||||
-rw-r--r-- | drivers/aiodev/imx_thermal.c | 44 | ||||
-rw-r--r-- | drivers/aiodev/lm75.c | 6 | ||||
-rw-r--r-- | drivers/aiodev/mc13xxx_adc.c | 12 | ||||
-rw-r--r-- | drivers/aiodev/qoriq_thermal.c | 13 | ||||
-rw-r--r-- | drivers/aiodev/rockchip_saradc.c | 7 | ||||
-rw-r--r-- | drivers/aiodev/st_gyro.c | 123 | ||||
-rw-r--r-- | drivers/aiodev/stm32-adc-core.c | 11 | ||||
-rw-r--r-- | drivers/aiodev/stm32-adc-core.h | 2 | ||||
-rw-r--r-- | drivers/aiodev/stm32-adc.c | 39 | ||||
-rw-r--r-- | drivers/aiodev/vf610_adc.c | 620 |
15 files changed, 1276 insertions, 94 deletions
diff --git a/drivers/aiodev/Kconfig b/drivers/aiodev/Kconfig index 03c688ae61..e1edc25320 100644 --- a/drivers/aiodev/Kconfig +++ b/drivers/aiodev/Kconfig @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only # # Misc strange devices # @@ -29,6 +30,12 @@ config LM75 help Support for LM75 and similar devices +config ST_GYRO + tristate "ST L3GD20 SPI gyro driver" + depends on SPI + help + Support for L3GD20 three-axis angular rate sensor. + config MC13XXX_ADC tristate "MC13XXX ADC driver" depends on MFD_MC13XXX @@ -45,7 +52,7 @@ config AM335X_ADC config STM32_ADC tristate "STM32 ADC driver" - depends on ARCH_STM32MP || COMPILE_TEST + depends on ARCH_STM32 || COMPILE_TEST help Support for ADC on STM32. Supports simple one-shot readings rather than continuous sampling with DMA, etc. ADC channels should be @@ -58,4 +65,19 @@ config ROCKCHIP_SARADC help Support for Successive Approximation Register (SAR) ADC in Rockchip SoCs. + +config IMX7D_ADC + tristate "Freescale IMX7D ADC driver" + depends on ARCH_IMX7 || COMPILE_TEST + depends on OFDEVICE + help + Say yes here to build support for IMX7D ADC. + +config VF610_ADC + tristate "Freescale vf610 ADC driver" + depends on ARCH_IMX6 || COMPILE_TEST + help + Say yes here to support for Vybrid board analog-to-digital converter. + Since the IP is used for i.MX6SLX, the driver also support i.MX6SLX. + endif diff --git a/drivers/aiodev/Makefile b/drivers/aiodev/Makefile index 1b480f6fa3..ce95d5be2f 100644 --- a/drivers/aiodev/Makefile +++ b/drivers/aiodev/Makefile @@ -1,9 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_AIODEV) += core.o obj-$(CONFIG_IMX_THERMAL) += imx_thermal.o obj-$(CONFIG_LM75) += lm75.o +obj-$(CONFIG_ST_GYRO) += st_gyro.o obj-$(CONFIG_MC13XXX_ADC) += mc13xxx_adc.o obj-$(CONFIG_QORIQ_THERMAL) += qoriq_thermal.o obj-$(CONFIG_AM335X_ADC) += am335x_adc.o obj-$(CONFIG_STM32_ADC) += stm32-adc.o stm32-adc-core.o obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o +obj-$(CONFIG_IMX7D_ADC) += imx7d_adc.o +obj-$(CONFIG_VF610_ADC) += vf610_adc.o diff --git a/drivers/aiodev/am335x_adc.c b/drivers/aiodev/am335x_adc.c index 0d6cc426eb..512f0a86fa 100644 --- a/drivers/aiodev/am335x_adc.c +++ b/drivers/aiodev/am335x_adc.c @@ -19,7 +19,7 @@ #include <io.h> #include <linux/log2.h> #include <aiodev.h> -#include <mach/am33xx-clock.h> +#include <mach/omap/am33xx-clock.h> #include "ti_am335x_tscadc.h" struct am335x_adc_data { @@ -72,7 +72,7 @@ static int am335x_adc_read(struct aiochannel *chan, int *val) return 0; } -static int am335x_adc_probe(struct device_d *dev) +static int am335x_adc_probe(struct device *dev) { struct device_node *node; struct am335x_adc_data *data; @@ -87,7 +87,7 @@ static int am335x_adc_probe(struct device_d *dev) goto fail_data; } - node = of_find_compatible_node(dev->device_node, NULL, "ti,am3359-adc"); + node = of_find_compatible_node(dev->of_node, NULL, "ti,am3359-adc"); if (!node) { ret = -EINVAL; goto fail_data; @@ -174,8 +174,9 @@ static const struct of_device_id of_am335x_adc_match[] = { { .compatible = "ti,am3359-tscadc", }, { /* end */ } }; +MODULE_DEVICE_TABLE(of, of_am335x_adc_match); -static struct driver_d am335x_adc_driver = { +static struct driver am335x_adc_driver = { .name = "am335x_adc", .probe = am335x_adc_probe, .of_compatible = DRV_OF_COMPAT(of_am335x_adc_match), diff --git a/drivers/aiodev/core.c b/drivers/aiodev/core.c index 7240de2c40..1fbb7b9188 100644 --- a/drivers/aiodev/core.c +++ b/drivers/aiodev/core.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * core.c - Code implementing core functionality of AIODEV susbsystem * @@ -5,15 +6,6 @@ * * Copyright (c) 2015 Zodiac Inflight Innovation * Author: Andrey Smirnov <andrew.smirnov@gmail.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License 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 <common.h> @@ -37,18 +29,18 @@ struct aiochannel *aiochannel_by_name(const char *name) return ERR_PTR(-ENOENT); } -EXPORT_SYMBOL(aiochannel_get_by_name); +EXPORT_SYMBOL(aiochannel_by_name); -struct aiochannel *aiochannel_get(struct device_d *dev, int index) +struct aiochannel *aiochannel_get(struct device *dev, int index) { struct of_phandle_args spec; struct aiodevice *aiodev; int ret, chnum = 0; - if (!dev->device_node) + if (!dev->of_node) return ERR_PTR(-EINVAL); - ret = of_parse_phandle_with_args(dev->device_node, + ret = of_parse_phandle_with_args(dev->of_node, "io-channels", "#io-channel-cells", index, &spec); @@ -56,7 +48,7 @@ struct aiochannel *aiochannel_get(struct device_d *dev, int index) return ERR_PTR(ret); list_for_each_entry(aiodev, &aiodevices, list) { - if (aiodev->hwdev->device_node == spec.np) + if (aiodev->hwdev->of_node == spec.np) goto found; } @@ -99,10 +91,10 @@ int aiodevice_register(struct aiodevice *aiodev) int i, ret; if (!aiodev->name && aiodev->hwdev && - aiodev->hwdev->device_node) { + aiodev->hwdev->of_node) { aiodev->dev.id = DEVICE_ID_SINGLE; - aiodev->name = of_alias_get(aiodev->hwdev->device_node); + aiodev->name = of_alias_get(aiodev->hwdev->of_node); } if (!aiodev->name) { diff --git a/drivers/aiodev/imx7d_adc.c b/drivers/aiodev/imx7d_adc.c new file mode 100644 index 0000000000..4d1f902031 --- /dev/null +++ b/drivers/aiodev/imx7d_adc.c @@ -0,0 +1,432 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Freescale i.MX7D ADC driver + * + * Copyright (C) 2015 Freescale Semiconductor, Inc. + */ + +#include <linux/clk.h> +#include <linux/err.h> +#include <io.h> +#include <linux/printk.h> +#include <driver.h> +#include <init.h> +#include <linux/kernel.h> +#include <linux/bitops.h> +#include <regulator.h> +#include <linux/barebox-wrapper.h> + +#include <linux/iopoll.h> +#include <aiodev.h> + +/* ADC register */ +#define IMX7D_REG_ADC_TIMER_UNIT 0x90 +#define IMX7D_REG_ADC_INT_STATUS 0xe0 +#define IMX7D_REG_ADC_CHA_B_CNV_RSLT 0xf0 +#define IMX7D_REG_ADC_CHC_D_CNV_RSLT 0x100 +#define IMX7D_REG_ADC_ADC_CFG 0x130 + +#define IMX7D_REG_ADC_CFG1(ch) ((ch) * 0x20) +#define IMX7D_REG_ADC_CFG2(ch) ((ch) * 0x20 + 0x10) + +#define IMX7D_REG_ADC_CH_CFG1_CHANNEL_EN (0x1 << 31) +#define IMX7D_REG_ADC_CH_CFG1_CHANNEL_SINGLE BIT(30) +#define IMX7D_REG_ADC_CH_CFG1_CHANNEL_AVG_EN BIT(29) +#define IMX7D_REG_ADC_CH_CFG1_CHANNEL_SEL(x) ((x) << 24) + +#define IMX7D_REG_ADC_CH_CFG2_AVG_NUM_4 (0x0 << 12) +#define IMX7D_REG_ADC_CH_CFG2_AVG_NUM_8 (0x1 << 12) +#define IMX7D_REG_ADC_CH_CFG2_AVG_NUM_16 (0x2 << 12) +#define IMX7D_REG_ADC_CH_CFG2_AVG_NUM_32 (0x3 << 12) + +#define IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_4 (0x0 << 29) +#define IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_8 (0x1 << 29) +#define IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_16 (0x2 << 29) +#define IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_32 (0x3 << 29) +#define IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_64 (0x4 << 29) +#define IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_128 (0x5 << 29) + +#define IMX7D_REG_ADC_ADC_CFG_ADC_CLK_DOWN BIT(31) +#define IMX7D_REG_ADC_ADC_CFG_ADC_POWER_DOWN BIT(1) +#define IMX7D_REG_ADC_ADC_CFG_ADC_EN BIT(0) + +#define IMX7D_REG_ADC_INT_STATUS_CHANNEL_INT_STATUS 0xf00 +#define IMX7D_REG_ADC_INT_STATUS_CHANNEL_CONV_TIME_OUT 0xf0000 + +#define IMX7D_ADC_TIMEOUT_NSEC (100 * NSEC_PER_MSEC) +#define IMX7D_ADC_INPUT_CLK 24000000 + +enum imx7d_adc_clk_pre_div { + IMX7D_ADC_ANALOG_CLK_PRE_DIV_4, + IMX7D_ADC_ANALOG_CLK_PRE_DIV_8, + IMX7D_ADC_ANALOG_CLK_PRE_DIV_16, + IMX7D_ADC_ANALOG_CLK_PRE_DIV_32, + IMX7D_ADC_ANALOG_CLK_PRE_DIV_64, + IMX7D_ADC_ANALOG_CLK_PRE_DIV_128, +}; + +enum imx7d_adc_average_num { + IMX7D_ADC_AVERAGE_NUM_4, + IMX7D_ADC_AVERAGE_NUM_8, + IMX7D_ADC_AVERAGE_NUM_16, + IMX7D_ADC_AVERAGE_NUM_32, +}; + +struct imx7d_adc_feature { + enum imx7d_adc_clk_pre_div clk_pre_div; + enum imx7d_adc_average_num avg_num; + + u32 core_time_unit; /* impact the sample rate */ +}; + +struct imx7d_adc { + struct device *dev; + void __iomem *regs; + struct clk *clk; + struct aiodevice aiodev; + void (*aiodev_info)(struct device *); + + u32 vref_uv; + u32 pre_div_num; + + struct regulator *vref; + struct imx7d_adc_feature adc_feature; + + struct aiochannel aiochan[16]; +}; + +struct imx7d_adc_analogue_core_clk { + u32 pre_div; + u32 reg_config; +}; + +#define IMX7D_ADC_ANALOGUE_CLK_CONFIG(_pre_div, _reg_conf) { \ + .pre_div = (_pre_div), \ + .reg_config = (_reg_conf), \ +} + +static const struct imx7d_adc_analogue_core_clk imx7d_adc_analogue_clk[] = { + IMX7D_ADC_ANALOGUE_CLK_CONFIG(4, IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_4), + IMX7D_ADC_ANALOGUE_CLK_CONFIG(8, IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_8), + IMX7D_ADC_ANALOGUE_CLK_CONFIG(16, IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_16), + IMX7D_ADC_ANALOGUE_CLK_CONFIG(32, IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_32), + IMX7D_ADC_ANALOGUE_CLK_CONFIG(64, IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_64), + IMX7D_ADC_ANALOGUE_CLK_CONFIG(128, IMX7D_REG_ADC_TIMER_UNIT_PRE_DIV_128), +}; + +static const u32 imx7d_adc_average_num[] = { + IMX7D_REG_ADC_CH_CFG2_AVG_NUM_4, + IMX7D_REG_ADC_CH_CFG2_AVG_NUM_8, + IMX7D_REG_ADC_CH_CFG2_AVG_NUM_16, + IMX7D_REG_ADC_CH_CFG2_AVG_NUM_32, +}; + +static void imx7d_adc_feature_config(struct imx7d_adc *info) +{ + info->adc_feature.clk_pre_div = IMX7D_ADC_ANALOG_CLK_PRE_DIV_4; + info->adc_feature.avg_num = IMX7D_ADC_AVERAGE_NUM_32; + info->adc_feature.core_time_unit = 1; +} + +static void imx7d_adc_sample_rate_set(struct imx7d_adc *info) +{ + struct imx7d_adc_feature *adc_feature = &info->adc_feature; + struct imx7d_adc_analogue_core_clk adc_analogue_clk; + unsigned i; + u32 tmp_cfg1; + u32 sample_rate = 0; + + /* + * Before sample set, disable channel A,B,C,D. Here we + * clear the bit 31 of register REG_ADC_CH_A\B\C\D_CFG1. + */ + for (i = 0; i < 4; i++) { + tmp_cfg1 = readl(info->regs + IMX7D_REG_ADC_CFG1(i)); + tmp_cfg1 &= ~IMX7D_REG_ADC_CH_CFG1_CHANNEL_EN; + writel(tmp_cfg1, info->regs + IMX7D_REG_ADC_CFG1(i)); + } + + adc_analogue_clk = imx7d_adc_analogue_clk[adc_feature->clk_pre_div]; + sample_rate |= adc_analogue_clk.reg_config; + info->pre_div_num = adc_analogue_clk.pre_div; + + sample_rate |= adc_feature->core_time_unit; + writel(sample_rate, info->regs + IMX7D_REG_ADC_TIMER_UNIT); +} + +static void imx7d_adc_hw_init(struct imx7d_adc *info) +{ + u32 cfg; + + /* power up and enable adc analogue core */ + cfg = readl(info->regs + IMX7D_REG_ADC_ADC_CFG); + cfg &= ~(IMX7D_REG_ADC_ADC_CFG_ADC_CLK_DOWN | + IMX7D_REG_ADC_ADC_CFG_ADC_POWER_DOWN); + cfg |= IMX7D_REG_ADC_ADC_CFG_ADC_EN; + writel(cfg, info->regs + IMX7D_REG_ADC_ADC_CFG); + + imx7d_adc_sample_rate_set(info); +} + +static void imx7d_adc_channel_set(struct imx7d_adc *info, u32 channel) +{ + u32 cfg1 = 0; + u32 cfg2; + + /* the channel choose single conversion, and enable average mode */ + cfg1 |= (IMX7D_REG_ADC_CH_CFG1_CHANNEL_EN | + IMX7D_REG_ADC_CH_CFG1_CHANNEL_SINGLE | + IMX7D_REG_ADC_CH_CFG1_CHANNEL_AVG_EN); + + /* + * physical channel 0 chose logical channel A + * physical channel 1 chose logical channel B + * physical channel 2 chose logical channel C + * physical channel 3 chose logical channel D + */ + cfg1 |= IMX7D_REG_ADC_CH_CFG1_CHANNEL_SEL(channel); + + /* + * read register REG_ADC_CH_A\B\C\D_CFG2, according to the + * channel chosen + */ + cfg2 = readl(info->regs + IMX7D_REG_ADC_CFG2(channel)); + + cfg2 |= imx7d_adc_average_num[info->adc_feature.avg_num]; + + /* + * write the register REG_ADC_CH_A\B\C\D_CFG2, according to + * the channel chosen + */ + writel(cfg2, info->regs + IMX7D_REG_ADC_CFG2(channel)); + writel(cfg1, info->regs + IMX7D_REG_ADC_CFG1(channel)); +} + +static u16 __imx7d_adc_read_data(struct imx7d_adc *info, u32 channel) +{ + u32 value; + + /* + * channel A and B conversion result share one register, + * bit[27~16] is the channel B conversion result, + * bit[11~0] is the channel A conversion result. + * channel C and D is the same. + */ + if (channel < 2) + value = readl(info->regs + IMX7D_REG_ADC_CHA_B_CNV_RSLT); + else + value = readl(info->regs + IMX7D_REG_ADC_CHC_D_CNV_RSLT); + if (channel & 0x1) /* channel B or D */ + value = (value >> 16) & 0xFFF; + else /* channel A or C */ + value &= 0xFFF; + + return value; +} + +static int imx7d_adc_read_data(struct imx7d_adc *info, u32 channel) +{ + int ret = -EAGAIN; + int status; + + status = readl(info->regs + IMX7D_REG_ADC_INT_STATUS); + if (status & IMX7D_REG_ADC_INT_STATUS_CHANNEL_INT_STATUS) { + ret = __imx7d_adc_read_data(info, channel); + + /* + * The register IMX7D_REG_ADC_INT_STATUS can't clear + * itself after read operation, need software to write + * 0 to the related bit. Here we clear the channel A/B/C/D + * conversion finished flag. + */ + status &= ~IMX7D_REG_ADC_INT_STATUS_CHANNEL_INT_STATUS; + writel(status, info->regs + IMX7D_REG_ADC_INT_STATUS); + } + + /* + * If the channel A/B/C/D conversion timeout, report it and clear these + * timeout flags. + */ + if (status & IMX7D_REG_ADC_INT_STATUS_CHANNEL_CONV_TIME_OUT) { + dev_err(info->dev, + "ADC got conversion time out interrupt: 0x%08x\n", + status); + status &= ~IMX7D_REG_ADC_INT_STATUS_CHANNEL_CONV_TIME_OUT; + writel(status, info->regs + IMX7D_REG_ADC_INT_STATUS); + ret = -ETIMEDOUT; + } + + return ret; +} + + +static int imx7d_adc_read_raw(struct aiochannel *chan, int *data) +{ + struct imx7d_adc *info = container_of(chan->aiodev, struct imx7d_adc, aiodev); + u64 raw64, start; + u32 channel; + int ret; + + channel = chan->index & 0x03; + imx7d_adc_channel_set(info, channel); + + start = get_time_ns(); + do { + if (is_timeout(start, IMX7D_ADC_TIMEOUT_NSEC)) { + ret = -ETIMEDOUT; + break; + } + + ret = imx7d_adc_read_data(info, channel); + } while (ret == -EAGAIN); + + if (ret < 0) + return ret; + + raw64 = ret; + raw64 *= info->vref_uv; + raw64 = div_u64(raw64, 1000); + *data = div_u64(raw64, (1 << 12)); + + return 0; +} + +static const struct of_device_id imx7d_adc_match[] = { + { .compatible = "fsl,imx7d-adc", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx7d_adc_match); + +static void imx7d_adc_power_down(struct imx7d_adc *info) +{ + u32 adc_cfg; + + adc_cfg = readl(info->regs + IMX7D_REG_ADC_ADC_CFG); + adc_cfg |= IMX7D_REG_ADC_ADC_CFG_ADC_CLK_DOWN | + IMX7D_REG_ADC_ADC_CFG_ADC_POWER_DOWN; + adc_cfg &= ~IMX7D_REG_ADC_ADC_CFG_ADC_EN; + writel(adc_cfg, info->regs + IMX7D_REG_ADC_ADC_CFG); +} + +static int imx7d_adc_enable(struct imx7d_adc *info) +{ + struct device *dev = info->dev; + int ret; + + ret = regulator_enable(info->vref); + if (ret) + return dev_err_probe(dev, ret, + "Can't enable adc reference top voltage\n"); + + ret = clk_enable(info->clk); + if (ret) { + regulator_disable(info->vref); + return dev_err_probe(dev, ret, "Could not enable clock.\n"); + } + + imx7d_adc_hw_init(info); + + ret = regulator_get_voltage(info->vref); + if (ret < 0) + return dev_err_probe(dev, ret, "can't get vref-supply value\n"); + + info->vref_uv = ret; + return 0; +} + +static u32 imx7d_adc_get_sample_rate(struct imx7d_adc *info) +{ + u32 analogue_core_clk; + u32 core_time_unit = info->adc_feature.core_time_unit; + u32 tmp; + + analogue_core_clk = IMX7D_ADC_INPUT_CLK / info->pre_div_num; + tmp = (core_time_unit + 1) * 6; + + return analogue_core_clk / tmp; +} + +static void imx7d_adc_devinfo(struct device *dev) +{ + struct imx7d_adc *info = dev->parent->priv; + + if (info->aiodev_info) + info->aiodev_info(dev); + + printf("Sample Rate: %u\n", imx7d_adc_get_sample_rate(info)); +} + +static int imx7d_adc_probe(struct device *dev) +{ + struct aiodevice *aiodev; + struct imx7d_adc *info; + int ret, i; + + info = xzalloc(sizeof(*info)); + + info->dev = dev; + + info->clk = clk_get(dev, "adc"); + if (IS_ERR(info->clk)) + return dev_err_probe(dev, PTR_ERR(info->clk), "Failed getting clock\n"); + + info->vref = regulator_get(dev, "vref"); + if (IS_ERR(info->vref)) + return dev_err_probe(dev, PTR_ERR(info->vref), + "Failed getting reference voltage\n"); + + info->regs = dev_request_mem_region(dev, 0); + if (IS_ERR(info->regs)) + return dev_err_probe(dev, PTR_ERR(info->regs), + "Failed to get memory region\n"); + + dev->priv = info; + aiodev = &info->aiodev; + + aiodev->num_channels = 4; + aiodev->hwdev = dev; + aiodev->read = imx7d_adc_read_raw; + aiodev->channels = xzalloc(aiodev->num_channels * sizeof(aiodev->channels[0])); + + for (i = 0; i < aiodev->num_channels; i++) { + aiodev->channels[i] = &info->aiochan[i]; + info->aiochan[i].unit = "mV"; + } + + imx7d_adc_feature_config(info); + + ret = imx7d_adc_enable(info); + if (ret) + return ret; + + ret = aiodevice_register(aiodev); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to register aiodev\n"); + + info->aiodev_info = aiodev->dev.info; + aiodev->dev.info = imx7d_adc_devinfo; + + return 0; +} + +static void imx7d_adc_disable(struct device *dev) +{ + struct imx7d_adc *info = dev->priv; + + imx7d_adc_power_down(info); + + clk_disable(info->clk); + regulator_disable(info->vref); +} + +static struct driver imx7d_adc_driver = { + .probe = imx7d_adc_probe, + .name = "imx7d_adc", + .of_compatible = imx7d_adc_match, + .remove = imx7d_adc_disable, +}; +device_platform_driver(imx7d_adc_driver); + +MODULE_AUTHOR("Haibo Chen <haibo.chen@freescale.com>"); +MODULE_DESCRIPTION("Freescale IMX7D ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/aiodev/imx_thermal.c b/drivers/aiodev/imx_thermal.c index 6dd2baa36c..2693ad05e0 100644 --- a/drivers/aiodev/imx_thermal.c +++ b/drivers/aiodev/imx_thermal.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * imx_thermal * @@ -8,15 +9,6 @@ * (C) Copyright 2014-2015 Freescale Semiconductor, Inc. * Author: Nitin Garg <nitin.garg@freescale.com> * Ye Li <Ye.Li@freescale.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License 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 <common.h> @@ -30,14 +22,14 @@ #include <linux/math64.h> #include <linux/log2.h> #include <linux/clk.h> -#include <mach/imx6-anadig.h> +#include <linux/nvmem-consumer.h> +#include <mach/imx/imx6-anadig.h> #include <io.h> #include <aiodev.h> #include <mfd/syscon.h> #define FACTOR0 10000000 #define MEASURE_FREQ 327 -#define OCOTP_ANA1_OFFSET (0xE * sizeof(uint32_t)) struct imx_thermal_data { int c1, c2; @@ -114,36 +106,19 @@ static int imx_thermal_read(struct aiochannel *chan, int *val) return 0; } -static int imx_thermal_probe(struct device_d *dev) +static int imx_thermal_probe(struct device *dev) { uint32_t ocotp_ana1; - struct device_node *node; struct imx_thermal_data *imx_thermal; - struct cdev *ocotp; int t1, n1, t2, n2; int ret; - node = of_parse_phandle(dev->device_node, "fsl,tempmon-data", 0); - if (!node) { - dev_err(dev, "No calibration data source\n"); - return -ENODEV; - } - - ocotp = cdev_by_device_node(node); - if (!ocotp) { - dev_err(dev, "No OCOTP character device\n"); - return -ENODEV; - } - - ret = cdev_read(ocotp, &ocotp_ana1, sizeof(ocotp_ana1), - OCOTP_ANA1_OFFSET, 0); - if (ret != sizeof(ocotp_ana1)) { - dev_err(dev, "Failed to read calibration data\n"); - return ret < 0 ? ret : -EIO; - } + ret = nvmem_cell_read_variable_le_u32(dev, "calib", &ocotp_ana1); + if (ret) + return ret; imx_thermal = xzalloc(sizeof(*imx_thermal)); - imx_thermal->base = syscon_base_lookup_by_phandle(dev->device_node, + imx_thermal->base = syscon_base_lookup_by_phandle(dev->of_node, "fsl,tempmon"); if (IS_ERR(imx_thermal->base)) { dev_err(dev, "Could not get ANATOP address\n"); @@ -200,9 +175,10 @@ static const struct of_device_id of_imx_thermal_match[] = { { .compatible = "fsl,imx6sx-tempmon", }, { /* end */ } }; +MODULE_DEVICE_TABLE(of, of_imx_thermal_match); -static struct driver_d imx_thermal_driver = { +static struct driver imx_thermal_driver = { .name = "imx_thermal", .probe = imx_thermal_probe, .of_compatible = DRV_OF_COMPAT(of_imx_thermal_match), diff --git a/drivers/aiodev/lm75.c b/drivers/aiodev/lm75.c index 1900636555..13b7ac4710 100644 --- a/drivers/aiodev/lm75.c +++ b/drivers/aiodev/lm75.c @@ -57,7 +57,7 @@ static const u8 LM75_REG_TEMP[3] = { /* Each client has this additional data */ struct lm75_data { struct i2c_client *client; - struct device_d dev; + struct device dev; u8 resolution; /* In bits, between 9 and 12 */ struct aiochannel aiochan; struct aiodevice aiodev; @@ -102,7 +102,7 @@ static int lm75_get_temp(struct aiochannel *chan, int *val) return 0; } -static int lm75_probe(struct device_d *dev) +static int lm75_probe(struct device *dev) { struct lm75_data *data; int status; @@ -237,7 +237,7 @@ static const struct platform_device_id lm75_ids[] = { { /* LIST END */ } }; -static struct driver_d lm75_driver = { +static struct driver lm75_driver = { .name = "lm75", .probe = lm75_probe, .id_table = lm75_ids, diff --git a/drivers/aiodev/mc13xxx_adc.c b/drivers/aiodev/mc13xxx_adc.c index c5d1efdf8d..21eea1f525 100644 --- a/drivers/aiodev/mc13xxx_adc.c +++ b/drivers/aiodev/mc13xxx_adc.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * mc13xxx_adc * @@ -6,15 +7,6 @@ * Based on the code of analogous driver from Linux: * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. * Copyright (C) 2009 Sascha Hauer, Pengutronix - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License 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 <common.h> @@ -186,7 +178,7 @@ err: return ret; } -int mc13xxx_adc_probe(struct device_d *dev, struct mc13xxx *mc_dev) +int mc13xxx_adc_probe(struct device *dev, struct mc13xxx *mc_dev) { int i; int ret; diff --git a/drivers/aiodev/qoriq_thermal.c b/drivers/aiodev/qoriq_thermal.c index d62048ef13..20998a0f97 100644 --- a/drivers/aiodev/qoriq_thermal.c +++ b/drivers/aiodev/qoriq_thermal.c @@ -67,7 +67,7 @@ struct qoriq_tmu_regs { * Thermal zone data */ struct qoriq_tmu_data { - struct device_d *dev; + struct device *dev; struct clk *clk; struct qoriq_tmu_regs __iomem *regs; int sensor_id; @@ -118,7 +118,7 @@ static int qoriq_tmu_get_sensor_id(void) struct of_phandle_args sensor_specs; struct device_node *np, *sensor_np; - np = of_find_node_by_name(NULL, "thermal-zones"); + np = of_find_node_by_name_address(NULL, "thermal-zones"); if (!np) return -ENODEV; @@ -146,7 +146,7 @@ static int qoriq_tmu_calibration(struct qoriq_tmu_data *data) int i, val, len; u32 range[4]; const u32 *calibration; - struct device_node *np = data->dev->device_node; + struct device_node *np = data->dev->of_node; if (of_property_read_u32_array(np, "fsl,tmu-range", range, 4)) { dev_err(data->dev, "missing calibration range.\n"); @@ -187,9 +187,9 @@ static void qoriq_tmu_init_device(struct qoriq_tmu_data *data) tmu_write(data, TMR_DISABLE, &data->regs->tmr); } -static int qoriq_tmu_probe(struct device_d *dev) +static int qoriq_tmu_probe(struct device *dev) { - struct device_node *np = dev->device_node; + struct device_node *np = dev->of_node; struct qoriq_tmu_data *data; u32 site; int ret; @@ -245,8 +245,9 @@ static const struct of_device_id qoriq_tmu_match[] = { { .compatible = "fsl,imx8mq-tmu",}, {}, }; +MODULE_DEVICE_TABLE(of, qoriq_tmu_match); -static struct driver_d imx_thermal_driver = { +static struct driver imx_thermal_driver = { .name = "qoriq_thermal", .probe = qoriq_tmu_probe, .of_compatible = DRV_OF_COMPAT(qoriq_tmu_match), diff --git a/drivers/aiodev/rockchip_saradc.c b/drivers/aiodev/rockchip_saradc.c index 302f73c2c3..3c5c0e94da 100644 --- a/drivers/aiodev/rockchip_saradc.c +++ b/drivers/aiodev/rockchip_saradc.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2021, WolfVision GmbH * Author: Michael Riesch <michael.riesch@wolfvision.net> @@ -85,7 +85,7 @@ static int rockchip_saradc_read(struct aiochannel *chan, int *val) return 0; } -static int rockchip_saradc_probe(struct device_d *dev) +static int rockchip_saradc_probe(struct device *dev) { struct rockchip_saradc_data *data; int i, ret; @@ -188,8 +188,9 @@ static const struct of_device_id of_rockchip_saradc_match[] = { { .compatible = "rockchip,rk3568-saradc", .data = &rk3568_saradc_cfg }, { /* end */ } }; +MODULE_DEVICE_TABLE(of, of_rockchip_saradc_match); -static struct driver_d rockchip_saradc_driver = { +static struct driver rockchip_saradc_driver = { .name = "rockchip_saradc", .probe = rockchip_saradc_probe, .of_compatible = DRV_OF_COMPAT(of_rockchip_saradc_match), diff --git a/drivers/aiodev/st_gyro.c b/drivers/aiodev/st_gyro.c new file mode 100644 index 0000000000..3a8b6a7761 --- /dev/null +++ b/drivers/aiodev/st_gyro.c @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: 2022 Ahmad Fatoum + +#include <common.h> +#include <driver.h> +#include <xfuncs.h> +#include <spi/spi.h> +#include <aiodev.h> + +#define ST_GYRO_WHO_AM_I 0x0F +#define ST_GYRO_CTRL_REG1 0x20 + +#define ST_GYRO_DEFAULT_OUT_TEMP_ADDR 0x26 +#define ST_GYRO_DEFAULT_OUT_X_L_ADDR 0x28 +#define ST_GYRO_DEFAULT_OUT_Y_L_ADDR 0x2a +#define ST_GYRO_DEFAULT_OUT_Z_L_ADDR 0x2c + +#define ST_GYRO_OUT_L_ADDR(idx) \ + (ST_GYRO_DEFAULT_OUT_X_L_ADDR + 2 * (idx)) + +#define ST_GYRO_OUT_H_ADDR(idx) \ + (ST_GYRO_OUT_L_ADDR(idx) + 1) + +#define ST_GYRO_READ 0x80 +#define ST_GYRO_WRITE 0x00 +#define ST_GYRO_MULTI 0x40 + +struct st_gyro { + struct aiodevice aiodev; + struct aiochannel aiochan[4]; + struct spi_device *spi; +}; +#define to_st_gyro(chan) container_of(chan->aiodev, struct st_gyro, aiodev) + +static int st_gyro_read(struct aiochannel *chan, int *val) +{ + struct st_gyro *gyro = to_st_gyro(chan); + int ret; + u8 tx; + u8 rx_h, rx_l; + + if (chan->index == 3) { + tx = ST_GYRO_DEFAULT_OUT_TEMP_ADDR | ST_GYRO_READ; + ret = spi_write_then_read(gyro->spi, &tx, 1, &rx_l, 1); + if (ret) + return ret; + + *val = (s8)rx_l; + return 0; + } + + tx = ST_GYRO_OUT_H_ADDR(chan->index) | ST_GYRO_READ; + ret = spi_write_then_read(gyro->spi, &tx, 1, &rx_h, 1); + if (ret) + return ret; + + tx = ST_GYRO_OUT_L_ADDR(chan->index) | ST_GYRO_READ; + ret = spi_write_then_read(gyro->spi, &tx, 1, &rx_l, 1); + if (ret) + return ret; + + *val = (s16)((rx_h << 8) | rx_l); + *val *= 250; + *val >>= 16; + + return 0; +} + +static int st_gyro_probe(struct device *dev) +{ + u8 tx[2], rx[2]; + struct st_gyro *gyro; + int ret, i; + + gyro = xzalloc(sizeof(*gyro)); + gyro->spi = to_spi_device(dev); + + tx[0] = ST_GYRO_WHO_AM_I | ST_GYRO_READ; + ret = spi_write_then_read(gyro->spi, tx, 1, rx, 1); + if (ret) + return -EIO; + if (rx[0] != 0xD4) + return dev_err_probe(dev, -ENODEV, "unexpected device WAI: %02x\n", rx[0]); + + /* initialize device */ + tx[0] = ST_GYRO_CTRL_REG1 | ST_GYRO_WRITE; + tx[1] = 0xF; /* normal mode, 3 channels */ + ret = spi_write(gyro->spi, tx, 2); + if (ret) + return -EIO; + + gyro->aiodev.num_channels = 4; + gyro->aiodev.hwdev = dev; + gyro->aiodev.read = st_gyro_read; + gyro->aiodev.name = "gyroscope"; + gyro->aiodev.channels = + xmalloc(gyro->aiodev.num_channels * + sizeof(gyro->aiodev.channels[0])); + for (i = 0; i < 3; i++) { + gyro->aiodev.channels[i] = &gyro->aiochan[i]; + gyro->aiochan[i].unit = "dps"; + gyro->aiochan[i].index = i; + } + + gyro->aiodev.channels[3] = &gyro->aiochan[3]; + gyro->aiochan[3].unit = "C"; + gyro->aiochan[3].index = 3; + + return aiodevice_register(&gyro->aiodev); +} + +static const struct of_device_id st_gyro_match[] = { + { .compatible = "st,l3gd20-gyro" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, st_gyro_match); + +static struct driver st_gyro_driver = { + .name = "st_gyro", + .probe = st_gyro_probe, + .of_compatible = st_gyro_match, +}; +device_spi_driver(st_gyro_driver); diff --git a/drivers/aiodev/stm32-adc-core.c b/drivers/aiodev/stm32-adc-core.c index 410e2a894e..f4e22d47af 100644 --- a/drivers/aiodev/stm32-adc-core.c +++ b/drivers/aiodev/stm32-adc-core.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2018, STMicroelectronics - All Rights Reserved * Author: Fabrice Gasnier <fabrice.gasnier@st.com> @@ -56,7 +56,7 @@ static const struct stm32h7_adc_ck_spec stm32h7_adc_ckmodes_spec[] = { { 3, 0, 4 }, }; -static int stm32h7_adc_clk_sel(struct device_d *dev, +static int stm32h7_adc_clk_sel(struct device *dev, struct stm32_adc_common *common) { u32 ckmode, presc; @@ -137,7 +137,7 @@ out: return 0; } -static int stm32_adc_core_probe(struct device_d *dev) +static int stm32_adc_core_probe(struct device *dev) { struct stm32_adc_common *common; int ret; @@ -186,7 +186,7 @@ static int stm32_adc_core_probe(struct device_d *dev) goto err_bclk_disable; dev->priv = common; - return of_platform_populate(dev->device_node, NULL, dev); + return of_platform_populate(dev->of_node, NULL, dev); err_bclk_disable: clk_disable(common->bclk); @@ -202,8 +202,9 @@ static const struct of_device_id stm32_adc_core_ids[] = { { .compatible = "st,stm32mp1-adc-core" }, {} }; +MODULE_DEVICE_TABLE(of, stm32_adc_core_ids); -static struct driver_d stm32_adc_core_driver = { +static struct driver stm32_adc_core_driver = { .name = "stm32-adc-core", .probe = stm32_adc_core_probe, .of_compatible = DRV_OF_COMPAT(stm32_adc_core_ids), diff --git a/drivers/aiodev/stm32-adc-core.h b/drivers/aiodev/stm32-adc-core.h index de6c0b9495..29695c3e58 100644 --- a/drivers/aiodev/stm32-adc-core.h +++ b/drivers/aiodev/stm32-adc-core.h @@ -1,4 +1,4 @@ -/* SPDX-License-Identifier: GPL-2.0 */ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * Copyright (C) 2018, STMicroelectronics - All Rights Reserved * Author: Fabrice Gasnier <fabrice.gasnier@st.com>. diff --git a/drivers/aiodev/stm32-adc.c b/drivers/aiodev/stm32-adc.c index c99b995eaf..a1998da62c 100644 --- a/drivers/aiodev/stm32-adc.c +++ b/drivers/aiodev/stm32-adc.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2018, STMicroelectronics - All Rights Reserved * Author: Fabrice Gasnier <fabrice.gasnier@st.com> @@ -91,7 +91,7 @@ static void stm32_adc_stop(struct stm32_adc *adc) static int stm32_adc_start_channel(struct stm32_adc *adc, int channel) { - struct device_d *dev = adc->aiodev.hwdev; + struct device *dev = adc->aiodev.hwdev; struct stm32_adc_common *common = adc->common; int ret; u32 val; @@ -153,7 +153,7 @@ static int stm32_adc_start_channel(struct stm32_adc *adc, int channel) static int stm32_adc_channel_data(struct stm32_adc *adc, int channel, int *data) { - struct device_d *dev = &adc->aiodev.dev; + struct device *dev = &adc->aiodev.dev; int ret; u32 val; @@ -211,7 +211,7 @@ static void stm32_adc_smpr_init(struct stm32_adc *adc, int channel, u32 smp_ns) adc->smpr_val[r] = (adc->smpr_val[r] & ~mask) | (smp << shift); } -static int stm32_adc_chan_of_init(struct device_d *dev, struct stm32_adc *adc) +static int stm32_adc_chan_of_init(struct device *dev, struct stm32_adc *adc) { unsigned int i; int num_channels = 0, num_times = 0; @@ -219,7 +219,7 @@ static int stm32_adc_chan_of_init(struct device_d *dev, struct stm32_adc *adc) int ret; /* Retrieve single ended channels listed in device tree */ - of_get_property(dev->device_node, "st,adc-channels", &num_channels); + of_get_property(dev->of_node, "st,adc-channels", &num_channels); num_channels /= sizeof(__be32); if (num_channels > adc->cfg->max_channels) { @@ -228,7 +228,7 @@ static int stm32_adc_chan_of_init(struct device_d *dev, struct stm32_adc *adc) } /* Optional sample time is provided either for each, or all channels */ - of_get_property(dev->device_node, "st,min-sample-time-nsecs", &num_times); + of_get_property(dev->of_node, "st,min-sample-time-nsecs", &num_times); num_times /= sizeof(__be32); if (num_times > 1 && num_times != num_channels) { dev_err(dev, "Invalid st,min-sample-time-nsecs\n"); @@ -252,7 +252,8 @@ static int stm32_adc_chan_of_init(struct device_d *dev, struct stm32_adc *adc) for (i = 0; i < num_channels; i++) { u32 chan; - ret = of_property_read_u32_index(dev->device_node, "st,adc-channels", i, &chan); + ret = of_property_read_u32_index(dev->of_node, + "st,adc-channels", i, &chan); if (ret) return ret; @@ -273,7 +274,8 @@ static int stm32_adc_chan_of_init(struct device_d *dev, struct stm32_adc *adc) * get either no value, 1 shared value for all indexes, or one * value per channel. */ - of_property_read_u32_index(dev->device_node, "st,min-sample-time-nsecs", + of_property_read_u32_index(dev->of_node, + "st,min-sample-time-nsecs", i, &smp); /* Prepare sampling time settings */ stm32_adc_smpr_init(adc, chan, smp); @@ -288,14 +290,14 @@ static int stm32_adc_chan_of_init(struct device_d *dev, struct stm32_adc *adc) return ret; } -static int stm32_adc_probe(struct device_d *dev) +static int stm32_adc_probe(struct device *dev) { struct stm32_adc_common *common = dev->parent->priv; struct stm32_adc *adc; u32 offset; int ret; - ret = of_property_read_u32(dev->device_node, "reg", &offset); + ret = of_property_read_u32(dev->of_node, "reg", &offset); if (ret) { dev_err(dev, "Can't read reg property\n"); return ret; @@ -360,13 +362,28 @@ static const struct stm32_adc_cfg stm32mp1_adc_cfg = { .has_vregready = true, }; +/* STM32MP13 programmable sampling time (ADC clock cycles, rounded down) */ +static const unsigned int stm32mp13_adc_smp_cycles[STM32_ADC_MAX_SMP + 1] = { + 2, 6, 12, 24, 47, 92, 247, 640, +}; + +static const struct stm32_adc_cfg stm32mp13_adc_cfg = { + .num_bits = 16, + .max_channels = 19, + .smp_bits = stm32h7_smp_bits, + .smp_cycles = stm32mp13_adc_smp_cycles, + .has_vregready = false, +}; + static const struct of_device_id stm32_adc_match[] = { { .compatible = "st,stm32h7-adc", .data = &stm32h7_adc_cfg }, { .compatible = "st,stm32mp1-adc", .data = &stm32mp1_adc_cfg }, + { .compatible = "st,stm32mp13-adc", .data = &stm32mp13_adc_cfg }, {} }; +MODULE_DEVICE_TABLE(of, stm32_adc_match); -static struct driver_d stm32_adc_driver = { +static struct driver stm32_adc_driver = { .name = "stm32-adc", .probe = stm32_adc_probe, .of_compatible = DRV_OF_COMPAT(stm32_adc_match), diff --git a/drivers/aiodev/vf610_adc.c b/drivers/aiodev/vf610_adc.c new file mode 100644 index 0000000000..cb062e0846 --- /dev/null +++ b/drivers/aiodev/vf610_adc.c @@ -0,0 +1,620 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Freescale Vybrid vf610 ADC driver + * + * Copyright 2013 Freescale Semiconductor, Inc. + */ + +#include <clock.h> +#include <io.h> +#include <linux/printk.h> +#include <driver.h> +#include <init.h> + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <regulator.h> +#include <linux/err.h> + +#include <aiodev.h> + +/* This will be the driver name the kernel reports */ +#define DRIVER_NAME "vf610-adc" + +/* Vybrid/IMX ADC registers */ +#define VF610_REG_ADC_HC0 0x00 +#define VF610_REG_ADC_HS 0x08 +#define VF610_REG_ADC_R0 0x0c +#define VF610_REG_ADC_CFG 0x14 +#define VF610_REG_ADC_GC 0x18 +#define VF610_REG_ADC_GS 0x1c + +/* Configuration register field define */ +#define VF610_ADC_MODE_BIT8 0x00 +#define VF610_ADC_MODE_BIT10 0x04 +#define VF610_ADC_MODE_BIT12 0x08 +#define VF610_ADC_MODE_MASK 0x0c +#define VF610_ADC_BUSCLK2_SEL 0x01 +#define VF610_ADC_ALTCLK_SEL 0x02 +#define VF610_ADC_ADACK_SEL 0x03 +#define VF610_ADC_ADCCLK_MASK 0x03 +#define VF610_ADC_CLK_DIV2 0x20 +#define VF610_ADC_CLK_DIV4 0x40 +#define VF610_ADC_CLK_DIV8 0x60 +#define VF610_ADC_CLK_MASK 0x60 +#define VF610_ADC_ADLSMP_LONG 0x10 +#define VF610_ADC_ADSTS_SHORT 0x100 +#define VF610_ADC_ADSTS_NORMAL 0x200 +#define VF610_ADC_ADSTS_LONG 0x300 +#define VF610_ADC_ADSTS_MASK 0x300 +#define VF610_ADC_ADLPC_EN 0x80 +#define VF610_ADC_ADHSC_EN 0x400 +#define VF610_ADC_REFSEL_VALT 0x800 +#define VF610_ADC_REFSEL_VBG 0x1000 +#define VF610_ADC_AVGS_8 0x4000 +#define VF610_ADC_AVGS_16 0x8000 +#define VF610_ADC_AVGS_32 0xC000 +#define VF610_ADC_AVGS_MASK 0xC000 +#define VF610_ADC_OVWREN 0x10000 + +/* General control register field define */ +#define VF610_ADC_AVGEN 0x20 +#define VF610_ADC_CAL 0x80 + +/* Other field define */ +#define VF610_ADC_ADCHC(x) ((x) & 0x1F) +#define VF610_ADC_CONV_DISABLE 0x1F +#define VF610_ADC_HS_COCO0 0x1 +#define VF610_ADC_CALF 0x2 +#define VF610_ADC_TIMEOUT_NSEC (100 * NSEC_PER_MSEC) + +#define DEFAULT_SAMPLE_TIME 1000 + +enum clk_sel { + VF610_ADCIOC_BUSCLK_SET, + VF610_ADCIOC_ALTCLK_SET, + VF610_ADCIOC_ADACK_SET, +}; + +enum vol_ref { + VF610_ADCIOC_VR_VREF_SET, + VF610_ADCIOC_VR_VALT_SET, + VF610_ADCIOC_VR_VBG_SET, +}; + +enum average_sel { + VF610_ADC_SAMPLE_1, + VF610_ADC_SAMPLE_4, + VF610_ADC_SAMPLE_8, + VF610_ADC_SAMPLE_16, + VF610_ADC_SAMPLE_32, +}; + +enum conversion_mode_sel { + VF610_ADC_CONV_NORMAL, + VF610_ADC_CONV_HIGH_SPEED, + VF610_ADC_CONV_LOW_POWER, +}; + +enum lst_adder_sel { + VF610_ADCK_CYCLES_3, + VF610_ADCK_CYCLES_5, + VF610_ADCK_CYCLES_7, + VF610_ADCK_CYCLES_9, + VF610_ADCK_CYCLES_13, + VF610_ADCK_CYCLES_17, + VF610_ADCK_CYCLES_21, + VF610_ADCK_CYCLES_25, +}; + +struct vf610_adc_feature { + enum clk_sel clk_sel; + enum vol_ref vol_ref; + enum conversion_mode_sel conv_mode; + + int clk_div; + int sample_rate; + int res_mode; + u32 lst_adder_index; + u32 default_sample_time; + + bool calibration; + bool ovwren; +}; + +struct vf610_adc { + struct device *dev; + void __iomem *regs; + struct clk *clk; + struct aiodevice aiodev; + void (*aiodev_info)(struct device *); + + u32 vref_uv; + u32 value; + struct regulator *vref; + + u32 max_adck_rate[3]; + struct vf610_adc_feature adc_feature; + + u32 sample_freq_avail[5]; + + struct aiochannel aiochan[16]; +}; + +static const u32 vf610_hw_avgs[] = { 1, 4, 8, 16, 32 }; +static const u32 vf610_lst_adder[] = { 3, 5, 7, 9, 13, 17, 21, 25 }; + +static inline void vf610_adc_calculate_rates(struct vf610_adc *info) +{ + struct vf610_adc_feature *adc_feature = &info->adc_feature; + unsigned long adck_rate, ipg_rate = clk_get_rate(info->clk); + u32 adck_period, lst_addr_min; + int divisor, i; + + adck_rate = info->max_adck_rate[adc_feature->conv_mode]; + + if (adck_rate) { + /* calculate clk divider which is within specification */ + divisor = ipg_rate / adck_rate; + adc_feature->clk_div = 1 << fls(divisor + 1); + } else { + /* fall-back value using a safe divisor */ + adc_feature->clk_div = 8; + } + + adck_rate = ipg_rate / adc_feature->clk_div; + + /* + * Determine the long sample time adder value to be used based + * on the default minimum sample time provided. + */ + adck_period = NSEC_PER_SEC / adck_rate; + lst_addr_min = adc_feature->default_sample_time / adck_period; + for (i = 0; i < ARRAY_SIZE(vf610_lst_adder); i++) { + if (vf610_lst_adder[i] > lst_addr_min) { + adc_feature->lst_adder_index = i; + break; + } + } + + /* + * Calculate ADC sample frequencies + * Sample time unit is ADCK cycles. ADCK clk source is ipg clock, + * which is the same as bus clock. + * + * ADC conversion time = SFCAdder + AverageNum x (BCT + LSTAdder) + * SFCAdder: fixed to 6 ADCK cycles + * AverageNum: 1, 4, 8, 16, 32 samples for hardware average. + * BCT (Base Conversion Time): fixed to 25 ADCK cycles for 12 bit mode + * LSTAdder(Long Sample Time): 3, 5, 7, 9, 13, 17, 21, 25 ADCK cycles + */ + for (i = 0; i < ARRAY_SIZE(vf610_hw_avgs); i++) + info->sample_freq_avail[i] = + adck_rate / (6 + vf610_hw_avgs[i] * + (25 + vf610_lst_adder[adc_feature->lst_adder_index])); +} + +static inline void vf610_adc_cfg_init(struct vf610_adc *info) +{ + struct vf610_adc_feature *adc_feature = &info->adc_feature; + + /* set default Configuration for ADC controller */ + adc_feature->clk_sel = VF610_ADCIOC_BUSCLK_SET; + adc_feature->vol_ref = VF610_ADCIOC_VR_VREF_SET; + + adc_feature->calibration = true; + adc_feature->ovwren = true; + + adc_feature->res_mode = 12; + adc_feature->sample_rate = 1; + + adc_feature->conv_mode = VF610_ADC_CONV_LOW_POWER; + + vf610_adc_calculate_rates(info); +} + +static void vf610_adc_cfg_post_set(struct vf610_adc *info) +{ + struct vf610_adc_feature *adc_feature = &info->adc_feature; + int cfg_data = 0; + int gc_data = 0; + + switch (adc_feature->clk_sel) { + case VF610_ADCIOC_ALTCLK_SET: + cfg_data |= VF610_ADC_ALTCLK_SEL; + break; + case VF610_ADCIOC_ADACK_SET: + cfg_data |= VF610_ADC_ADACK_SEL; + break; + default: + break; + } + + /* low power set for calibration */ + cfg_data |= VF610_ADC_ADLPC_EN; + + /* enable high speed for calibration */ + cfg_data |= VF610_ADC_ADHSC_EN; + + /* voltage reference */ + switch (adc_feature->vol_ref) { + case VF610_ADCIOC_VR_VREF_SET: + break; + case VF610_ADCIOC_VR_VALT_SET: + cfg_data |= VF610_ADC_REFSEL_VALT; + break; + case VF610_ADCIOC_VR_VBG_SET: + cfg_data |= VF610_ADC_REFSEL_VBG; + break; + default: + dev_err(info->dev, "error voltage reference\n"); + } + + /* data overwrite enable */ + if (adc_feature->ovwren) + cfg_data |= VF610_ADC_OVWREN; + + writel(cfg_data, info->regs + VF610_REG_ADC_CFG); + writel(gc_data, info->regs + VF610_REG_ADC_GC); +} + +static void vf610_adc_calibration(struct vf610_adc *info) +{ + int adc_gc, hc_cfg; + u64 start; + int coco; + + if (!info->adc_feature.calibration) + return; + + hc_cfg = VF610_ADC_CONV_DISABLE; + writel(hc_cfg, info->regs + VF610_REG_ADC_HC0); + + adc_gc = readl(info->regs + VF610_REG_ADC_GC); + writel(adc_gc | VF610_ADC_CAL, info->regs + VF610_REG_ADC_GC); + + start = get_time_ns(); + do { + if (is_timeout(start, VF610_ADC_TIMEOUT_NSEC)) { + dev_err(info->dev, "Timeout for adc calibration\n"); + break; + } + + coco = readl(info->regs + VF610_REG_ADC_HS); + } while (!(coco & VF610_ADC_HS_COCO0)); + + adc_gc = readl(info->regs + VF610_REG_ADC_GS); + if (adc_gc & VF610_ADC_CALF) + dev_err(info->dev, "ADC calibration failed\n"); + + info->adc_feature.calibration = false; +} + +static void vf610_adc_cfg_set(struct vf610_adc *info) +{ + struct vf610_adc_feature *adc_feature = &(info->adc_feature); + int cfg_data; + + cfg_data = readl(info->regs + VF610_REG_ADC_CFG); + + cfg_data &= ~VF610_ADC_ADLPC_EN; + if (adc_feature->conv_mode == VF610_ADC_CONV_LOW_POWER) + cfg_data |= VF610_ADC_ADLPC_EN; + + cfg_data &= ~VF610_ADC_ADHSC_EN; + if (adc_feature->conv_mode == VF610_ADC_CONV_HIGH_SPEED) + cfg_data |= VF610_ADC_ADHSC_EN; + + writel(cfg_data, info->regs + VF610_REG_ADC_CFG); +} + +static void vf610_adc_sample_set(struct vf610_adc *info) +{ + struct vf610_adc_feature *adc_feature = &(info->adc_feature); + int cfg_data, gc_data; + + cfg_data = readl(info->regs + VF610_REG_ADC_CFG); + gc_data = readl(info->regs + VF610_REG_ADC_GC); + + /* resolution mode */ + cfg_data &= ~VF610_ADC_MODE_MASK; + switch (adc_feature->res_mode) { + case 8: + cfg_data |= VF610_ADC_MODE_BIT8; + break; + case 10: + cfg_data |= VF610_ADC_MODE_BIT10; + break; + case 12: + cfg_data |= VF610_ADC_MODE_BIT12; + break; + default: + dev_err(info->dev, "error resolution mode\n"); + break; + } + + /* clock select and clock divider */ + cfg_data &= ~(VF610_ADC_CLK_MASK | VF610_ADC_ADCCLK_MASK); + switch (adc_feature->clk_div) { + case 1: + break; + case 2: + cfg_data |= VF610_ADC_CLK_DIV2; + break; + case 4: + cfg_data |= VF610_ADC_CLK_DIV4; + break; + case 8: + cfg_data |= VF610_ADC_CLK_DIV8; + break; + case 16: + switch (adc_feature->clk_sel) { + case VF610_ADCIOC_BUSCLK_SET: + cfg_data |= VF610_ADC_BUSCLK2_SEL | VF610_ADC_CLK_DIV8; + break; + default: + dev_err(info->dev, "error clk divider\n"); + break; + } + break; + } + + /* + * Set ADLSMP and ADSTS based on the Long Sample Time Adder value + * determined. + */ + switch (adc_feature->lst_adder_index) { + case VF610_ADCK_CYCLES_3: + break; + case VF610_ADCK_CYCLES_5: + cfg_data |= VF610_ADC_ADSTS_SHORT; + break; + case VF610_ADCK_CYCLES_7: + cfg_data |= VF610_ADC_ADSTS_NORMAL; + break; + case VF610_ADCK_CYCLES_9: + cfg_data |= VF610_ADC_ADSTS_LONG; + break; + case VF610_ADCK_CYCLES_13: + cfg_data |= VF610_ADC_ADLSMP_LONG; + break; + case VF610_ADCK_CYCLES_17: + cfg_data |= VF610_ADC_ADLSMP_LONG; + cfg_data |= VF610_ADC_ADSTS_SHORT; + break; + case VF610_ADCK_CYCLES_21: + cfg_data |= VF610_ADC_ADLSMP_LONG; + cfg_data |= VF610_ADC_ADSTS_NORMAL; + break; + case VF610_ADCK_CYCLES_25: + cfg_data |= VF610_ADC_ADLSMP_LONG; + cfg_data |= VF610_ADC_ADSTS_NORMAL; + break; + default: + dev_err(info->dev, "error in sample time select\n"); + } + + /* update hardware average selection */ + cfg_data &= ~VF610_ADC_AVGS_MASK; + gc_data &= ~VF610_ADC_AVGEN; + switch (adc_feature->sample_rate) { + case VF610_ADC_SAMPLE_1: + break; + case VF610_ADC_SAMPLE_4: + gc_data |= VF610_ADC_AVGEN; + break; + case VF610_ADC_SAMPLE_8: + gc_data |= VF610_ADC_AVGEN; + cfg_data |= VF610_ADC_AVGS_8; + break; + case VF610_ADC_SAMPLE_16: + gc_data |= VF610_ADC_AVGEN; + cfg_data |= VF610_ADC_AVGS_16; + break; + case VF610_ADC_SAMPLE_32: + gc_data |= VF610_ADC_AVGEN; + cfg_data |= VF610_ADC_AVGS_32; + break; + default: + dev_err(info->dev, + "error hardware sample average select\n"); + } + + writel(cfg_data, info->regs + VF610_REG_ADC_CFG); + writel(gc_data, info->regs + VF610_REG_ADC_GC); +} + +static void vf610_adc_hw_init(struct vf610_adc *info) +{ + /* CFG: Feature set */ + vf610_adc_cfg_post_set(info); + vf610_adc_sample_set(info); + + /* adc calibration */ + vf610_adc_calibration(info); + + /* CFG: power and speed set */ + vf610_adc_cfg_set(info); +} + +static int __vf610_adc_read_data(struct vf610_adc *info) +{ + int result; + + result = readl(info->regs + VF610_REG_ADC_R0); + + switch (info->adc_feature.res_mode) { + case 8: + result &= 0xFF; + break; + case 10: + result &= 0x3FF; + break; + case 12: + result &= 0xFFF; + break; + default: + break; + } + + return result; +} + +static int vf610_adc_read_data(struct vf610_adc *info) +{ + int coco; + int ret = -EAGAIN; + + coco = readl(info->regs + VF610_REG_ADC_HS); + if (coco & VF610_ADC_HS_COCO0) { + ret = __vf610_adc_read_data(info); + } + + return ret; +} + +static int vf610_read_sample(struct aiochannel *chan, int *val) +{ + struct vf610_adc *info = container_of(chan->aiodev, struct vf610_adc, aiodev); + unsigned int hc_cfg; + u64 raw64, start; + int ret; + + hc_cfg = VF610_ADC_ADCHC(chan->index & 0x0f); + writel(hc_cfg, info->regs + VF610_REG_ADC_HC0); + + start = get_time_ns(); + do { + if (is_timeout(start, VF610_ADC_TIMEOUT_NSEC)) { + ret = -ETIMEDOUT; + break; + } + + ret = vf610_adc_read_data(info); + } while (ret == -EAGAIN); + + if (ret < 0) + return ret; + + raw64 = ret; + raw64 *= info->vref_uv; + raw64 = div_u64(raw64, 1000); + *val = div_u64(raw64, (1 << 12)); + + return 0; +} + + +static const struct of_device_id vf610_adc_match[] = { + { .compatible = "fsl,vf610-adc", }, + { /* sentinel */ } +}; + +static void vf610_adc_devinfo(struct device *dev) +{ + struct vf610_adc *info = dev->parent->priv; + + if (info->aiodev_info) + info->aiodev_info(dev); + + pr_info("Sample Rate: %u\n", info->sample_freq_avail[info->adc_feature.sample_rate]); +} + +static int vf610_adc_probe(struct device *dev) +{ + struct aiodevice *aiodev; + struct vf610_adc *info; + int ret, i; + + info = xzalloc(sizeof(*info)); + info->dev = dev; + + info->regs = dev_request_mem_region(dev, 0); + if (IS_ERR(info->regs)) + return PTR_ERR(info->regs); + + info->clk = clk_get(dev, "adc"); + if (IS_ERR(info->clk)) { + dev_err(dev, "failed getting clock, err = %ld\n", + PTR_ERR(info->clk)); + return PTR_ERR(info->clk); + } + + info->vref = regulator_get(dev, "vref"); + if (IS_ERR(info->vref)) + return PTR_ERR(info->vref); + + of_property_read_u32_array(dev->device_node, "fsl,adck-max-frequency", info->max_adck_rate, 3); + + info->adc_feature.default_sample_time = DEFAULT_SAMPLE_TIME; + of_property_read_u32(dev->device_node, "min-sample-time", &info->adc_feature.default_sample_time); + + dev->priv = info; + aiodev = &info->aiodev; + + aiodev->num_channels = 16; + aiodev->hwdev = dev; + aiodev->read = vf610_read_sample; + aiodev->channels = xzalloc(aiodev->num_channels * sizeof(aiodev->channels[0])); + + for (i = 0; i < aiodev->num_channels; i++) { + aiodev->channels[i] = &info->aiochan[i]; + info->aiochan[i].unit = "mV"; + } + + ret = regulator_enable(info->vref); + if (ret) + return ret; + + info->vref_uv = regulator_get_voltage(info->vref); + + ret = clk_enable(info->clk); + if (ret) { + dev_err(dev, + "Could not prepare or enable the clock.\n"); + goto error_adc_clk_enable; + } + + vf610_adc_cfg_init(info); + vf610_adc_hw_init(info); + + ret = aiodevice_register(aiodev); + if (ret < 0) { + dev_err(dev, "Couldn't register the device.\n"); + goto error_adc_buffer_init; + } + + info->aiodev_info = aiodev->dev.info; + aiodev->dev.info = vf610_adc_devinfo; + + return 0; + +error_adc_buffer_init: + clk_disable(info->clk); +error_adc_clk_enable: + regulator_disable(info->vref); + + return ret; +} + +static void vf610_adc_remove(struct device *dev) +{ + struct vf610_adc *info = dev->priv; + + regulator_disable(info->vref); + clk_disable(info->clk); +} + +static struct driver vf610_adc_driver = { + .probe = vf610_adc_probe, + .remove = vf610_adc_remove, + .name = DRIVER_NAME, + .of_compatible = vf610_adc_match, +}; + +device_platform_driver(vf610_adc_driver); + +MODULE_AUTHOR("Fugang Duan <B38611@freescale.com>"); +MODULE_DESCRIPTION("Freescale VF610 ADC driver"); +MODULE_LICENSE("GPL v2"); |