GK SDK 源码库: XMIPCLinuxV100R005C00SPC030 (kernel/tools/open_source excluded)
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
config ADC
|
||||
bool "Enable ADC drivers using Driver Model"
|
||||
help
|
||||
This enables ADC API for drivers, which allows driving ADC features
|
||||
by single and multi-channel methods for:
|
||||
- start/stop/get data for conversion of a single-channel selected by
|
||||
a number or multi-channels selected by a bitmask
|
||||
- get data mask (ADC resolution)
|
||||
ADC reference Voltage supply options:
|
||||
- methods for get Vdd/Vss reference Voltage values with polarity
|
||||
- support supply's phandle with auto-enable
|
||||
- supply polarity setting in fdt
|
||||
|
||||
config ADC_EXYNOS
|
||||
bool "Enable Exynos 54xx ADC driver"
|
||||
help
|
||||
This enables basic driver for Exynos ADC compatible with Exynos54xx.
|
||||
It provides:
|
||||
- 10 analog input channels
|
||||
- 12-bit resolution
|
||||
- 600 KSPS of sample rate
|
||||
|
||||
config ADC_SANDBOX
|
||||
bool "Enable Sandbox ADC test driver"
|
||||
help
|
||||
This enables driver for Sandbox ADC device emulation.
|
||||
It provides:
|
||||
- 4 analog input channels
|
||||
- 16-bit resolution
|
||||
- single and multi-channel conversion mode
|
||||
|
||||
config SARADC_MESON
|
||||
bool "Enable Amlogic Meson SARADC driver"
|
||||
imply REGMAP
|
||||
help
|
||||
This enables driver for Amlogic Meson SARADC.
|
||||
It provides:
|
||||
- 8 analog input channels
|
||||
- 1O or 12 bits resolution
|
||||
- Up to 1MSPS of sample rate
|
||||
|
||||
config SARADC_ROCKCHIP
|
||||
bool "Enable Rockchip SARADC driver"
|
||||
help
|
||||
This enables driver for Rockchip SARADC.
|
||||
It provides:
|
||||
- 2~6 analog input channels
|
||||
- 1O or 12 bits resolution
|
||||
- Up to 1MSPS of sample rate
|
||||
|
||||
config STM32_ADC
|
||||
bool "Enable STMicroelectronics STM32 ADC driver"
|
||||
depends on ADC && (STM32H7 || ARCH_STM32MP)
|
||||
help
|
||||
This enables driver for STMicroelectronics STM32 analog-to-digital
|
||||
converter (ADC).
|
||||
A STM32 ADC block can be composed of several individual ADCs.
|
||||
Each has its own private registers, but shares some resources:
|
||||
- clock selection and prescaler
|
||||
- voltage reference
|
||||
- common registers area.
|
||||
STM32 ADC driver is composed of:
|
||||
- core driver to deal with common resources
|
||||
- child driver to deal with individual ADC resources (declare ADC
|
||||
device and associated channels, start/stop conversions)
|
||||
@@ -0,0 +1,12 @@
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
#
|
||||
# Copyright (C) 2015 Samsung Electronics
|
||||
# Przemyslaw Marczak <p.marczak@samsung.com>
|
||||
#
|
||||
|
||||
obj-$(CONFIG_ADC) += adc-uclass.o
|
||||
obj-$(CONFIG_ADC_EXYNOS) += exynos-adc.o
|
||||
obj-$(CONFIG_ADC_SANDBOX) += sandbox.o
|
||||
obj-$(CONFIG_SARADC_ROCKCHIP) += rockchip-saradc.o
|
||||
obj-$(CONFIG_SARADC_MESON) += meson-saradc.o
|
||||
obj-$(CONFIG_STM32_ADC) += stm32-adc.o stm32-adc-core.o
|
||||
@@ -0,0 +1,440 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (C) 2015 Samsung Electronics
|
||||
* Przemyslaw Marczak <p.marczak@samsung.com>
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <errno.h>
|
||||
#include <div64.h>
|
||||
#include <dm.h>
|
||||
#include <dm/lists.h>
|
||||
#include <dm/device-internal.h>
|
||||
#include <dm/uclass-internal.h>
|
||||
#include <adc.h>
|
||||
#include <power/regulator.h>
|
||||
|
||||
#define ADC_UCLASS_PLATDATA_SIZE sizeof(struct adc_uclass_platdata)
|
||||
#define CHECK_NUMBER true
|
||||
#define CHECK_MASK (!CHECK_NUMBER)
|
||||
|
||||
/* TODO: add support for timer uclass (for early calls) */
|
||||
#ifdef CONFIG_SANDBOX_ARCH
|
||||
#define sdelay(x) udelay(x)
|
||||
#else
|
||||
extern void sdelay(unsigned long loops);
|
||||
#endif
|
||||
|
||||
static int check_channel(struct udevice *dev, int value, bool number_or_mask,
|
||||
const char *caller_function)
|
||||
{
|
||||
struct adc_uclass_platdata *uc_pdata = dev_get_uclass_platdata(dev);
|
||||
unsigned mask = number_or_mask ? (1 << value) : value;
|
||||
|
||||
/* For the real ADC hardware, some ADC channels can be inactive.
|
||||
* For example if device has 4 analog channels, and only channels
|
||||
* 1-st and 3-rd are valid, then channel mask is: 0b1010, so request
|
||||
* with mask 0b1110 should return an error.
|
||||
*/
|
||||
if ((uc_pdata->channel_mask >= mask) && (uc_pdata->channel_mask & mask))
|
||||
return 0;
|
||||
|
||||
printf("Error in %s/%s().\nWrong channel selection for device: %s\n",
|
||||
__FILE__, caller_function, dev->name);
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int adc_supply_enable(struct udevice *dev)
|
||||
{
|
||||
struct adc_uclass_platdata *uc_pdata = dev_get_uclass_platdata(dev);
|
||||
const char *supply_type;
|
||||
int ret = 0;
|
||||
|
||||
if (uc_pdata->vdd_supply) {
|
||||
supply_type = "vdd";
|
||||
ret = regulator_set_enable(uc_pdata->vdd_supply, true);
|
||||
}
|
||||
|
||||
if (!ret && uc_pdata->vss_supply) {
|
||||
supply_type = "vss";
|
||||
ret = regulator_set_enable(uc_pdata->vss_supply, true);
|
||||
}
|
||||
|
||||
if (ret)
|
||||
pr_err("%s: can't enable %s-supply!", dev->name, supply_type);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int adc_data_mask(struct udevice *dev, unsigned int *data_mask)
|
||||
{
|
||||
struct adc_uclass_platdata *uc_pdata = dev_get_uclass_platdata(dev);
|
||||
|
||||
if (!uc_pdata)
|
||||
return -ENOSYS;
|
||||
|
||||
*data_mask = uc_pdata->data_mask;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int adc_channel_mask(struct udevice *dev, unsigned int *channel_mask)
|
||||
{
|
||||
struct adc_uclass_platdata *uc_pdata = dev_get_uclass_platdata(dev);
|
||||
|
||||
if (!uc_pdata)
|
||||
return -ENOSYS;
|
||||
|
||||
*channel_mask = uc_pdata->channel_mask;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int adc_stop(struct udevice *dev)
|
||||
{
|
||||
const struct adc_ops *ops = dev_get_driver_ops(dev);
|
||||
|
||||
if (!ops->stop)
|
||||
return -ENOSYS;
|
||||
|
||||
return ops->stop(dev);
|
||||
}
|
||||
|
||||
int adc_start_channel(struct udevice *dev, int channel)
|
||||
{
|
||||
const struct adc_ops *ops = dev_get_driver_ops(dev);
|
||||
int ret;
|
||||
|
||||
if (!ops->start_channel)
|
||||
return -ENOSYS;
|
||||
|
||||
ret = check_channel(dev, channel, CHECK_NUMBER, __func__);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = adc_supply_enable(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return ops->start_channel(dev, channel);
|
||||
}
|
||||
|
||||
int adc_start_channels(struct udevice *dev, unsigned int channel_mask)
|
||||
{
|
||||
const struct adc_ops *ops = dev_get_driver_ops(dev);
|
||||
int ret;
|
||||
|
||||
if (!ops->start_channels)
|
||||
return -ENOSYS;
|
||||
|
||||
ret = check_channel(dev, channel_mask, CHECK_MASK, __func__);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = adc_supply_enable(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return ops->start_channels(dev, channel_mask);
|
||||
}
|
||||
|
||||
int adc_channel_data(struct udevice *dev, int channel, unsigned int *data)
|
||||
{
|
||||
struct adc_uclass_platdata *uc_pdata = dev_get_uclass_platdata(dev);
|
||||
const struct adc_ops *ops = dev_get_driver_ops(dev);
|
||||
unsigned int timeout_us = uc_pdata->data_timeout_us;
|
||||
int ret;
|
||||
|
||||
if (!ops->channel_data)
|
||||
return -ENOSYS;
|
||||
|
||||
ret = check_channel(dev, channel, CHECK_NUMBER, __func__);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
do {
|
||||
ret = ops->channel_data(dev, channel, data);
|
||||
if (!ret || ret != -EBUSY)
|
||||
break;
|
||||
|
||||
/* TODO: use timer uclass (for early calls). */
|
||||
sdelay(5);
|
||||
} while (timeout_us--);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int adc_channels_data(struct udevice *dev, unsigned int channel_mask,
|
||||
struct adc_channel *channels)
|
||||
{
|
||||
struct adc_uclass_platdata *uc_pdata = dev_get_uclass_platdata(dev);
|
||||
unsigned int timeout_us = uc_pdata->multidata_timeout_us;
|
||||
const struct adc_ops *ops = dev_get_driver_ops(dev);
|
||||
int ret;
|
||||
|
||||
if (!ops->channels_data)
|
||||
return -ENOSYS;
|
||||
|
||||
ret = check_channel(dev, channel_mask, CHECK_MASK, __func__);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
do {
|
||||
ret = ops->channels_data(dev, channel_mask, channels);
|
||||
if (!ret || ret != -EBUSY)
|
||||
break;
|
||||
|
||||
/* TODO: use timer uclass (for early calls). */
|
||||
sdelay(5);
|
||||
} while (timeout_us--);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int adc_channel_single_shot(const char *name, int channel, unsigned int *data)
|
||||
{
|
||||
struct udevice *dev;
|
||||
int ret;
|
||||
|
||||
ret = uclass_get_device_by_name(UCLASS_ADC, name, &dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = adc_start_channel(dev, channel);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = adc_channel_data(dev, channel, data);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _adc_channels_single_shot(struct udevice *dev,
|
||||
unsigned int channel_mask,
|
||||
struct adc_channel *channels)
|
||||
{
|
||||
unsigned int data;
|
||||
int channel, ret;
|
||||
|
||||
for (channel = 0; channel <= ADC_MAX_CHANNEL; channel++) {
|
||||
/* Check channel bit. */
|
||||
if (!((channel_mask >> channel) & 0x1))
|
||||
continue;
|
||||
|
||||
ret = adc_start_channel(dev, channel);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = adc_channel_data(dev, channel, &data);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
channels->id = channel;
|
||||
channels->data = data;
|
||||
channels++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int adc_channels_single_shot(const char *name, unsigned int channel_mask,
|
||||
struct adc_channel *channels)
|
||||
{
|
||||
struct udevice *dev;
|
||||
int ret;
|
||||
|
||||
ret = uclass_get_device_by_name(UCLASS_ADC, name, &dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = adc_start_channels(dev, channel_mask);
|
||||
if (ret)
|
||||
goto try_manual;
|
||||
|
||||
ret = adc_channels_data(dev, channel_mask, channels);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
|
||||
try_manual:
|
||||
if (ret != -ENOSYS)
|
||||
return ret;
|
||||
|
||||
return _adc_channels_single_shot(dev, channel_mask, channels);
|
||||
}
|
||||
|
||||
static int adc_vdd_platdata_update(struct udevice *dev)
|
||||
{
|
||||
struct adc_uclass_platdata *uc_pdata = dev_get_uclass_platdata(dev);
|
||||
int ret;
|
||||
|
||||
/* Warning!
|
||||
* This function can't return supply device before its bind.
|
||||
* Please pay attention to proper fdt scan sequence. If ADC device
|
||||
* will bind before its supply regulator device, then the below 'get'
|
||||
* will return an error.
|
||||
*/
|
||||
if (!uc_pdata->vdd_supply)
|
||||
return 0;
|
||||
|
||||
ret = regulator_get_value(uc_pdata->vdd_supply);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
uc_pdata->vdd_microvolts = ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int adc_vss_platdata_update(struct udevice *dev)
|
||||
{
|
||||
struct adc_uclass_platdata *uc_pdata = dev_get_uclass_platdata(dev);
|
||||
int ret;
|
||||
|
||||
if (!uc_pdata->vss_supply)
|
||||
return 0;
|
||||
|
||||
ret = regulator_get_value(uc_pdata->vss_supply);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
uc_pdata->vss_microvolts = ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int adc_vdd_value(struct udevice *dev, int *uV)
|
||||
{
|
||||
struct adc_uclass_platdata *uc_pdata = dev_get_uclass_platdata(dev);
|
||||
int ret, value_sign = uc_pdata->vdd_polarity_negative ? -1 : 1;
|
||||
|
||||
/* Update the regulator Value. */
|
||||
ret = adc_vdd_platdata_update(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (uc_pdata->vdd_microvolts == -ENODATA)
|
||||
return -ENODATA;
|
||||
|
||||
*uV = uc_pdata->vdd_microvolts * value_sign;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int adc_vss_value(struct udevice *dev, int *uV)
|
||||
{
|
||||
struct adc_uclass_platdata *uc_pdata = dev_get_uclass_platdata(dev);
|
||||
int ret, value_sign = uc_pdata->vss_polarity_negative ? -1 : 1;
|
||||
|
||||
/* Update the regulator Value. */
|
||||
ret = adc_vss_platdata_update(dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (uc_pdata->vss_microvolts == -ENODATA)
|
||||
return -ENODATA;
|
||||
|
||||
*uV = uc_pdata->vss_microvolts * value_sign;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int adc_raw_to_uV(struct udevice *dev, unsigned int raw, int *uV)
|
||||
{
|
||||
unsigned int data_mask;
|
||||
int ret, val, vref;
|
||||
u64 raw64 = raw;
|
||||
|
||||
ret = adc_vdd_value(dev, &vref);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!adc_vss_value(dev, &val))
|
||||
vref -= val;
|
||||
|
||||
ret = adc_data_mask(dev, &data_mask);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
raw64 *= vref;
|
||||
do_div(raw64, data_mask);
|
||||
*uV = raw64;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int adc_vdd_platdata_set(struct udevice *dev)
|
||||
{
|
||||
struct adc_uclass_platdata *uc_pdata = dev_get_uclass_platdata(dev);
|
||||
int ret;
|
||||
char *prop;
|
||||
|
||||
prop = "vdd-polarity-negative";
|
||||
uc_pdata->vdd_polarity_negative = dev_read_bool(dev, prop);
|
||||
|
||||
/* Optionally get regulators */
|
||||
ret = device_get_supply_regulator(dev, "vdd-supply",
|
||||
&uc_pdata->vdd_supply);
|
||||
if (!ret)
|
||||
return adc_vdd_platdata_update(dev);
|
||||
|
||||
if (ret != -ENOENT)
|
||||
return ret;
|
||||
|
||||
/* No vdd-supply phandle. */
|
||||
prop = "vdd-microvolts";
|
||||
uc_pdata->vdd_microvolts = dev_read_u32_default(dev, prop, -ENODATA);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int adc_vss_platdata_set(struct udevice *dev)
|
||||
{
|
||||
struct adc_uclass_platdata *uc_pdata = dev_get_uclass_platdata(dev);
|
||||
int ret;
|
||||
char *prop;
|
||||
|
||||
prop = "vss-polarity-negative";
|
||||
uc_pdata->vss_polarity_negative = dev_read_bool(dev, prop);
|
||||
|
||||
ret = device_get_supply_regulator(dev, "vss-supply",
|
||||
&uc_pdata->vss_supply);
|
||||
if (!ret)
|
||||
return adc_vss_platdata_update(dev);
|
||||
|
||||
if (ret != -ENOENT)
|
||||
return ret;
|
||||
|
||||
/* No vss-supply phandle. */
|
||||
prop = "vss-microvolts";
|
||||
uc_pdata->vss_microvolts = dev_read_u32_default(dev, prop, -ENODATA);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int adc_pre_probe(struct udevice *dev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Set ADC VDD platdata: polarity, uV, regulator (phandle). */
|
||||
ret = adc_vdd_platdata_set(dev);
|
||||
if (ret)
|
||||
pr_err("%s: Can't update Vdd. Error: %d", dev->name, ret);
|
||||
|
||||
/* Set ADC VSS platdata: polarity, uV, regulator (phandle). */
|
||||
ret = adc_vss_platdata_set(dev);
|
||||
if (ret)
|
||||
pr_err("%s: Can't update Vss. Error: %d", dev->name, ret);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
UCLASS_DRIVER(adc) = {
|
||||
.id = UCLASS_ADC,
|
||||
.name = "adc",
|
||||
.pre_probe = adc_pre_probe,
|
||||
.per_device_platdata_auto_alloc_size = ADC_UCLASS_PLATDATA_SIZE,
|
||||
};
|
||||
@@ -0,0 +1,144 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (C) 2015 Samsung Electronics
|
||||
* Przemyslaw Marczak <p.marczak@samsung.com>
|
||||
*/
|
||||
#include <common.h>
|
||||
#include <errno.h>
|
||||
#include <dm.h>
|
||||
#include <adc.h>
|
||||
#include <asm/arch/adc.h>
|
||||
|
||||
struct exynos_adc_priv {
|
||||
int active_channel;
|
||||
struct exynos_adc_v2 *regs;
|
||||
};
|
||||
|
||||
int exynos_adc_channel_data(struct udevice *dev, int channel,
|
||||
unsigned int *data)
|
||||
{
|
||||
struct exynos_adc_priv *priv = dev_get_priv(dev);
|
||||
struct exynos_adc_v2 *regs = priv->regs;
|
||||
|
||||
if (channel != priv->active_channel) {
|
||||
pr_err("Requested channel is not active!");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (ADC_V2_GET_STATUS_FLAG(readl(®s->status)) != FLAG_CONV_END)
|
||||
return -EBUSY;
|
||||
|
||||
*data = readl(®s->dat) & ADC_V2_DAT_MASK;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exynos_adc_start_channel(struct udevice *dev, int channel)
|
||||
{
|
||||
struct exynos_adc_priv *priv = dev_get_priv(dev);
|
||||
struct exynos_adc_v2 *regs = priv->regs;
|
||||
unsigned int cfg;
|
||||
|
||||
/* Choose channel */
|
||||
cfg = readl(®s->con2);
|
||||
cfg &= ~ADC_V2_CON2_CHAN_SEL_MASK;
|
||||
cfg |= ADC_V2_CON2_CHAN_SEL(channel);
|
||||
writel(cfg, ®s->con2);
|
||||
|
||||
/* Start conversion */
|
||||
cfg = readl(®s->con1);
|
||||
writel(cfg | ADC_V2_CON1_STC_EN, ®s->con1);
|
||||
|
||||
priv->active_channel = channel;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exynos_adc_stop(struct udevice *dev)
|
||||
{
|
||||
struct exynos_adc_priv *priv = dev_get_priv(dev);
|
||||
struct exynos_adc_v2 *regs = priv->regs;
|
||||
unsigned int cfg;
|
||||
|
||||
/* Stop conversion */
|
||||
cfg = readl(®s->con1);
|
||||
cfg &= ~ADC_V2_CON1_STC_EN;
|
||||
|
||||
writel(cfg, ®s->con1);
|
||||
|
||||
priv->active_channel = -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exynos_adc_probe(struct udevice *dev)
|
||||
{
|
||||
struct exynos_adc_priv *priv = dev_get_priv(dev);
|
||||
struct exynos_adc_v2 *regs = priv->regs;
|
||||
unsigned int cfg;
|
||||
|
||||
/* Check HW version */
|
||||
if (readl(®s->version) != ADC_V2_VERSION) {
|
||||
pr_err("This driver supports only ADC v2!");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
/* ADC Reset */
|
||||
writel(ADC_V2_CON1_SOFT_RESET, ®s->con1);
|
||||
|
||||
/* Disable INT - will read status only */
|
||||
writel(0x0, ®s->int_en);
|
||||
|
||||
/* CON2 - set conversion parameters */
|
||||
cfg = ADC_V2_CON2_C_TIME(3); /* Conversion times: (1 << 3) = 8 */
|
||||
cfg |= ADC_V2_CON2_OSEL(OSEL_BINARY);
|
||||
cfg |= ADC_V2_CON2_ESEL(ESEL_ADC_EVAL_TIME_20CLK);
|
||||
cfg |= ADC_V2_CON2_HIGHF(HIGHF_CONV_RATE_600KSPS);
|
||||
writel(cfg, ®s->con2);
|
||||
|
||||
priv->active_channel = -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exynos_adc_ofdata_to_platdata(struct udevice *dev)
|
||||
{
|
||||
struct adc_uclass_platdata *uc_pdata = dev_get_uclass_platdata(dev);
|
||||
struct exynos_adc_priv *priv = dev_get_priv(dev);
|
||||
|
||||
priv->regs = (struct exynos_adc_v2 *)devfdt_get_addr(dev);
|
||||
if (priv->regs == (struct exynos_adc_v2 *)FDT_ADDR_T_NONE) {
|
||||
pr_err("Dev: %s - can't get address!", dev->name);
|
||||
return -ENODATA;
|
||||
}
|
||||
|
||||
uc_pdata->data_mask = ADC_V2_DAT_MASK;
|
||||
uc_pdata->data_format = ADC_DATA_FORMAT_BIN;
|
||||
uc_pdata->data_timeout_us = ADC_V2_CONV_TIMEOUT_US;
|
||||
|
||||
/* Mask available channel bits: [0:9] */
|
||||
uc_pdata->channel_mask = (2 << ADC_V2_MAX_CHANNEL) - 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct adc_ops exynos_adc_ops = {
|
||||
.start_channel = exynos_adc_start_channel,
|
||||
.channel_data = exynos_adc_channel_data,
|
||||
.stop = exynos_adc_stop,
|
||||
};
|
||||
|
||||
static const struct udevice_id exynos_adc_ids[] = {
|
||||
{ .compatible = "samsung,exynos-adc-v2" },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(exynos_adc) = {
|
||||
.name = "exynos-adc",
|
||||
.id = UCLASS_ADC,
|
||||
.of_match = exynos_adc_ids,
|
||||
.ops = &exynos_adc_ops,
|
||||
.probe = exynos_adc_probe,
|
||||
.ofdata_to_platdata = exynos_adc_ofdata_to_platdata,
|
||||
.priv_auto_alloc_size = sizeof(struct exynos_adc_priv),
|
||||
};
|
||||
@@ -0,0 +1,723 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (C) 2017 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
|
||||
* Copyright (C) 2018 BayLibre, SAS
|
||||
* Author: Neil Armstrong <narmstrong@baylibre.com>
|
||||
*
|
||||
* Amlogic Meson Successive Approximation Register (SAR) A/D Converter
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <adc.h>
|
||||
#include <clk.h>
|
||||
#include <dm.h>
|
||||
#include <regmap.h>
|
||||
#include <errno.h>
|
||||
#include <asm/io.h>
|
||||
#include <linux/math64.h>
|
||||
#include <linux/bitfield.h>
|
||||
|
||||
#define MESON_SAR_ADC_REG0 0x00
|
||||
#define MESON_SAR_ADC_REG0_PANEL_DETECT BIT(31)
|
||||
#define MESON_SAR_ADC_REG0_BUSY_MASK GENMASK(30, 28)
|
||||
#define MESON_SAR_ADC_REG0_DELTA_BUSY BIT(30)
|
||||
#define MESON_SAR_ADC_REG0_AVG_BUSY BIT(29)
|
||||
#define MESON_SAR_ADC_REG0_SAMPLE_BUSY BIT(28)
|
||||
#define MESON_SAR_ADC_REG0_FIFO_FULL BIT(27)
|
||||
#define MESON_SAR_ADC_REG0_FIFO_EMPTY BIT(26)
|
||||
#define MESON_SAR_ADC_REG0_FIFO_COUNT_MASK GENMASK(25, 21)
|
||||
#define MESON_SAR_ADC_REG0_ADC_BIAS_CTRL_MASK GENMASK(20, 19)
|
||||
#define MESON_SAR_ADC_REG0_CURR_CHAN_ID_MASK GENMASK(18, 16)
|
||||
#define MESON_SAR_ADC_REG0_ADC_TEMP_SEN_SEL BIT(15)
|
||||
#define MESON_SAR_ADC_REG0_SAMPLING_STOP BIT(14)
|
||||
#define MESON_SAR_ADC_REG0_CHAN_DELTA_EN_MASK GENMASK(13, 12)
|
||||
#define MESON_SAR_ADC_REG0_DETECT_IRQ_POL BIT(10)
|
||||
#define MESON_SAR_ADC_REG0_DETECT_IRQ_EN BIT(9)
|
||||
#define MESON_SAR_ADC_REG0_FIFO_CNT_IRQ_MASK GENMASK(8, 4)
|
||||
#define MESON_SAR_ADC_REG0_FIFO_IRQ_EN BIT(3)
|
||||
#define MESON_SAR_ADC_REG0_SAMPLING_START BIT(2)
|
||||
#define MESON_SAR_ADC_REG0_CONTINUOUS_EN BIT(1)
|
||||
#define MESON_SAR_ADC_REG0_SAMPLE_ENGINE_ENABLE BIT(0)
|
||||
|
||||
#define MESON_SAR_ADC_CHAN_LIST 0x04
|
||||
#define MESON_SAR_ADC_CHAN_LIST_MAX_INDEX_MASK GENMASK(26, 24)
|
||||
#define MESON_SAR_ADC_CHAN_LIST_ENTRY_MASK(_chan) \
|
||||
(GENMASK(2, 0) << ((_chan) * 3))
|
||||
|
||||
#define MESON_SAR_ADC_AVG_CNTL 0x08
|
||||
#define MESON_SAR_ADC_AVG_CNTL_AVG_MODE_SHIFT(_chan) \
|
||||
(16 + ((_chan) * 2))
|
||||
#define MESON_SAR_ADC_AVG_CNTL_AVG_MODE_MASK(_chan) \
|
||||
(GENMASK(17, 16) << ((_chan) * 2))
|
||||
#define MESON_SAR_ADC_AVG_CNTL_NUM_SAMPLES_SHIFT(_chan) \
|
||||
(0 + ((_chan) * 2))
|
||||
#define MESON_SAR_ADC_AVG_CNTL_NUM_SAMPLES_MASK(_chan) \
|
||||
(GENMASK(1, 0) << ((_chan) * 2))
|
||||
|
||||
#define MESON_SAR_ADC_REG3 0x0c
|
||||
#define MESON_SAR_ADC_REG3_CNTL_USE_SC_DLY BIT(31)
|
||||
#define MESON_SAR_ADC_REG3_CLK_EN BIT(30)
|
||||
#define MESON_SAR_ADC_REG3_BL30_INITIALIZED BIT(28)
|
||||
#define MESON_SAR_ADC_REG3_CTRL_CONT_RING_COUNTER_EN BIT(27)
|
||||
#define MESON_SAR_ADC_REG3_CTRL_SAMPLING_CLOCK_PHASE BIT(26)
|
||||
#define MESON_SAR_ADC_REG3_CTRL_CHAN7_MUX_SEL_MASK GENMASK(25, 23)
|
||||
#define MESON_SAR_ADC_REG3_DETECT_EN BIT(22)
|
||||
#define MESON_SAR_ADC_REG3_ADC_EN BIT(21)
|
||||
#define MESON_SAR_ADC_REG3_PANEL_DETECT_COUNT_MASK GENMASK(20, 18)
|
||||
#define MESON_SAR_ADC_REG3_PANEL_DETECT_FILTER_TB_MASK GENMASK(17, 16)
|
||||
#define MESON_SAR_ADC_REG3_ADC_CLK_DIV_SHIFT 10
|
||||
#define MESON_SAR_ADC_REG3_ADC_CLK_DIV_WIDTH 5
|
||||
#define MESON_SAR_ADC_REG3_BLOCK_DLY_SEL_MASK GENMASK(9, 8)
|
||||
#define MESON_SAR_ADC_REG3_BLOCK_DLY_MASK GENMASK(7, 0)
|
||||
|
||||
#define MESON_SAR_ADC_DELAY 0x10
|
||||
#define MESON_SAR_ADC_DELAY_INPUT_DLY_SEL_MASK GENMASK(25, 24)
|
||||
#define MESON_SAR_ADC_DELAY_BL30_BUSY BIT(15)
|
||||
#define MESON_SAR_ADC_DELAY_KERNEL_BUSY BIT(14)
|
||||
#define MESON_SAR_ADC_DELAY_INPUT_DLY_CNT_MASK GENMASK(23, 16)
|
||||
#define MESON_SAR_ADC_DELAY_SAMPLE_DLY_SEL_MASK GENMASK(9, 8)
|
||||
#define MESON_SAR_ADC_DELAY_SAMPLE_DLY_CNT_MASK GENMASK(7, 0)
|
||||
|
||||
#define MESON_SAR_ADC_LAST_RD 0x14
|
||||
#define MESON_SAR_ADC_LAST_RD_LAST_CHANNEL1_MASK GENMASK(23, 16)
|
||||
#define MESON_SAR_ADC_LAST_RD_LAST_CHANNEL0_MASK GENMASK(9, 0)
|
||||
|
||||
#define MESON_SAR_ADC_FIFO_RD 0x18
|
||||
#define MESON_SAR_ADC_FIFO_RD_CHAN_ID_MASK GENMASK(14, 12)
|
||||
#define MESON_SAR_ADC_FIFO_RD_SAMPLE_VALUE_MASK GENMASK(11, 0)
|
||||
|
||||
#define MESON_SAR_ADC_AUX_SW 0x1c
|
||||
#define MESON_SAR_ADC_AUX_SW_MUX_SEL_CHAN_SHIFT(_chan) \
|
||||
(8 + (((_chan) - 2) * 3))
|
||||
#define MESON_SAR_ADC_AUX_SW_VREF_P_MUX BIT(6)
|
||||
#define MESON_SAR_ADC_AUX_SW_VREF_N_MUX BIT(5)
|
||||
#define MESON_SAR_ADC_AUX_SW_MODE_SEL BIT(4)
|
||||
#define MESON_SAR_ADC_AUX_SW_YP_DRIVE_SW BIT(3)
|
||||
#define MESON_SAR_ADC_AUX_SW_XP_DRIVE_SW BIT(2)
|
||||
#define MESON_SAR_ADC_AUX_SW_YM_DRIVE_SW BIT(1)
|
||||
#define MESON_SAR_ADC_AUX_SW_XM_DRIVE_SW BIT(0)
|
||||
|
||||
#define MESON_SAR_ADC_CHAN_10_SW 0x20
|
||||
#define MESON_SAR_ADC_CHAN_10_SW_CHAN1_MUX_SEL_MASK GENMASK(25, 23)
|
||||
#define MESON_SAR_ADC_CHAN_10_SW_CHAN1_VREF_P_MUX BIT(22)
|
||||
#define MESON_SAR_ADC_CHAN_10_SW_CHAN1_VREF_N_MUX BIT(21)
|
||||
#define MESON_SAR_ADC_CHAN_10_SW_CHAN1_MODE_SEL BIT(20)
|
||||
#define MESON_SAR_ADC_CHAN_10_SW_CHAN1_YP_DRIVE_SW BIT(19)
|
||||
#define MESON_SAR_ADC_CHAN_10_SW_CHAN1_XP_DRIVE_SW BIT(18)
|
||||
#define MESON_SAR_ADC_CHAN_10_SW_CHAN1_YM_DRIVE_SW BIT(17)
|
||||
#define MESON_SAR_ADC_CHAN_10_SW_CHAN1_XM_DRIVE_SW BIT(16)
|
||||
#define MESON_SAR_ADC_CHAN_10_SW_CHAN0_MUX_SEL_MASK GENMASK(9, 7)
|
||||
#define MESON_SAR_ADC_CHAN_10_SW_CHAN0_VREF_P_MUX BIT(6)
|
||||
#define MESON_SAR_ADC_CHAN_10_SW_CHAN0_VREF_N_MUX BIT(5)
|
||||
#define MESON_SAR_ADC_CHAN_10_SW_CHAN0_MODE_SEL BIT(4)
|
||||
#define MESON_SAR_ADC_CHAN_10_SW_CHAN0_YP_DRIVE_SW BIT(3)
|
||||
#define MESON_SAR_ADC_CHAN_10_SW_CHAN0_XP_DRIVE_SW BIT(2)
|
||||
#define MESON_SAR_ADC_CHAN_10_SW_CHAN0_YM_DRIVE_SW BIT(1)
|
||||
#define MESON_SAR_ADC_CHAN_10_SW_CHAN0_XM_DRIVE_SW BIT(0)
|
||||
|
||||
#define MESON_SAR_ADC_DETECT_IDLE_SW 0x24
|
||||
#define MESON_SAR_ADC_DETECT_IDLE_SW_DETECT_SW_EN BIT(26)
|
||||
#define MESON_SAR_ADC_DETECT_IDLE_SW_DETECT_MUX_MASK GENMASK(25, 23)
|
||||
#define MESON_SAR_ADC_DETECT_IDLE_SW_DETECT_VREF_P_MUX BIT(22)
|
||||
#define MESON_SAR_ADC_DETECT_IDLE_SW_DETECT_VREF_N_MUX BIT(21)
|
||||
#define MESON_SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_SEL BIT(20)
|
||||
#define MESON_SAR_ADC_DETECT_IDLE_SW_DETECT_YP_DRIVE_SW BIT(19)
|
||||
#define MESON_SAR_ADC_DETECT_IDLE_SW_DETECT_XP_DRIVE_SW BIT(18)
|
||||
#define MESON_SAR_ADC_DETECT_IDLE_SW_DETECT_YM_DRIVE_SW BIT(17)
|
||||
#define MESON_SAR_ADC_DETECT_IDLE_SW_DETECT_XM_DRIVE_SW BIT(16)
|
||||
#define MESON_SAR_ADC_DETECT_IDLE_SW_IDLE_MUX_SEL_MASK GENMASK(9, 7)
|
||||
#define MESON_SAR_ADC_DETECT_IDLE_SW_IDLE_VREF_P_MUX BIT(6)
|
||||
#define MESON_SAR_ADC_DETECT_IDLE_SW_IDLE_VREF_N_MUX BIT(5)
|
||||
#define MESON_SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_SEL BIT(4)
|
||||
#define MESON_SAR_ADC_DETECT_IDLE_SW_IDLE_YP_DRIVE_SW BIT(3)
|
||||
#define MESON_SAR_ADC_DETECT_IDLE_SW_IDLE_XP_DRIVE_SW BIT(2)
|
||||
#define MESON_SAR_ADC_DETECT_IDLE_SW_IDLE_YM_DRIVE_SW BIT(1)
|
||||
#define MESON_SAR_ADC_DETECT_IDLE_SW_IDLE_XM_DRIVE_SW BIT(0)
|
||||
|
||||
#define MESON_SAR_ADC_DELTA_10 0x28
|
||||
#define MESON_SAR_ADC_DELTA_10_TEMP_SEL BIT(27)
|
||||
#define MESON_SAR_ADC_DELTA_10_TS_REVE1 BIT(26)
|
||||
#define MESON_SAR_ADC_DELTA_10_CHAN1_DELTA_VALUE_MASK GENMASK(25, 16)
|
||||
#define MESON_SAR_ADC_DELTA_10_TS_REVE0 BIT(15)
|
||||
#define MESON_SAR_ADC_DELTA_10_TS_C_SHIFT 11
|
||||
#define MESON_SAR_ADC_DELTA_10_TS_C_MASK GENMASK(14, 11)
|
||||
#define MESON_SAR_ADC_DELTA_10_TS_VBG_EN BIT(10)
|
||||
#define MESON_SAR_ADC_DELTA_10_CHAN0_DELTA_VALUE_MASK GENMASK(9, 0)
|
||||
|
||||
/*
|
||||
* NOTE: registers from here are undocumented (the vendor Linux kernel driver
|
||||
* and u-boot source served as reference). These only seem to be relevant on
|
||||
* GXBB and newer.
|
||||
*/
|
||||
#define MESON_SAR_ADC_REG11 0x2c
|
||||
#define MESON_SAR_ADC_REG11_BANDGAP_EN BIT(13)
|
||||
|
||||
#define MESON_SAR_ADC_REG13 0x34
|
||||
#define MESON_SAR_ADC_REG13_12BIT_CALIBRATION_MASK GENMASK(13, 8)
|
||||
|
||||
#define MESON_SAR_ADC_MAX_FIFO_SIZE 32
|
||||
#define MESON_SAR_ADC_TIMEOUT 100 /* ms */
|
||||
|
||||
#define NUM_CHANNELS 8
|
||||
|
||||
#define MILLION 1000000
|
||||
|
||||
struct meson_saradc_data {
|
||||
int num_bits;
|
||||
};
|
||||
|
||||
struct meson_saradc_priv {
|
||||
const struct meson_saradc_data *data;
|
||||
struct regmap *regmap;
|
||||
struct clk core_clk;
|
||||
struct clk adc_clk;
|
||||
bool initialized;
|
||||
int active_channel;
|
||||
int calibbias;
|
||||
int calibscale;
|
||||
};
|
||||
|
||||
static unsigned int
|
||||
meson_saradc_get_fifo_count(struct meson_saradc_priv *priv)
|
||||
{
|
||||
u32 regval;
|
||||
|
||||
regmap_read(priv->regmap, MESON_SAR_ADC_REG0, ®val);
|
||||
|
||||
return FIELD_GET(MESON_SAR_ADC_REG0_FIFO_COUNT_MASK, regval);
|
||||
}
|
||||
|
||||
static int meson_saradc_lock(struct meson_saradc_priv *priv)
|
||||
{
|
||||
uint val, timeout = 10000;
|
||||
|
||||
/* prevent BL30 from using the SAR ADC while we are using it */
|
||||
regmap_update_bits(priv->regmap, MESON_SAR_ADC_DELAY,
|
||||
MESON_SAR_ADC_DELAY_KERNEL_BUSY,
|
||||
MESON_SAR_ADC_DELAY_KERNEL_BUSY);
|
||||
|
||||
/*
|
||||
* wait until BL30 releases it's lock (so we can use the SAR ADC)
|
||||
*/
|
||||
do {
|
||||
udelay(1);
|
||||
regmap_read(priv->regmap, MESON_SAR_ADC_DELAY, &val);
|
||||
} while (val & MESON_SAR_ADC_DELAY_BL30_BUSY && timeout--);
|
||||
|
||||
if (timeout < 0) {
|
||||
printf("Timeout while waiting for BL30 unlock\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void meson_saradc_unlock(struct meson_saradc_priv *priv)
|
||||
{
|
||||
/* allow BL30 to use the SAR ADC again */
|
||||
regmap_update_bits(priv->regmap, MESON_SAR_ADC_DELAY,
|
||||
MESON_SAR_ADC_DELAY_KERNEL_BUSY, 0);
|
||||
}
|
||||
|
||||
static void meson_saradc_clear_fifo(struct meson_saradc_priv *priv)
|
||||
{
|
||||
unsigned int count, tmp;
|
||||
|
||||
for (count = 0; count < MESON_SAR_ADC_MAX_FIFO_SIZE; count++) {
|
||||
if (!meson_saradc_get_fifo_count(priv))
|
||||
break;
|
||||
|
||||
regmap_read(priv->regmap, MESON_SAR_ADC_FIFO_RD, &tmp);
|
||||
}
|
||||
}
|
||||
|
||||
static int meson_saradc_calib_val(struct meson_saradc_priv *priv, int val)
|
||||
{
|
||||
int tmp;
|
||||
|
||||
/* use val_calib = scale * val_raw + offset calibration function */
|
||||
tmp = div_s64((s64)val * priv->calibscale, MILLION) + priv->calibbias;
|
||||
|
||||
return clamp(tmp, 0, (1 << priv->data->num_bits) - 1);
|
||||
}
|
||||
|
||||
static int meson_saradc_wait_busy_clear(struct meson_saradc_priv *priv)
|
||||
{
|
||||
uint regval, timeout = 10000;
|
||||
|
||||
/*
|
||||
* NOTE: we need a small delay before reading the status, otherwise
|
||||
* the sample engine may not have started internally (which would
|
||||
* seem to us that sampling is already finished).
|
||||
*/
|
||||
do {
|
||||
udelay(1);
|
||||
regmap_read(priv->regmap, MESON_SAR_ADC_REG0, ®val);
|
||||
} while (FIELD_GET(MESON_SAR_ADC_REG0_BUSY_MASK, regval) && timeout--);
|
||||
|
||||
if (timeout < 0)
|
||||
return -ETIMEDOUT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int meson_saradc_read_raw_sample(struct meson_saradc_priv *priv,
|
||||
unsigned int channel, uint *val)
|
||||
{
|
||||
uint regval, fifo_chan, fifo_val, count;
|
||||
int ret;
|
||||
|
||||
ret = meson_saradc_wait_busy_clear(priv);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
count = meson_saradc_get_fifo_count(priv);
|
||||
if (count != 1) {
|
||||
printf("ADC FIFO has %d element(s) instead of one\n", count);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
regmap_read(priv->regmap, MESON_SAR_ADC_FIFO_RD, ®val);
|
||||
fifo_chan = FIELD_GET(MESON_SAR_ADC_FIFO_RD_CHAN_ID_MASK, regval);
|
||||
if (fifo_chan != channel) {
|
||||
printf("ADC FIFO entry belongs to channel %d instead of %d\n",
|
||||
fifo_chan, channel);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
fifo_val = FIELD_GET(MESON_SAR_ADC_FIFO_RD_SAMPLE_VALUE_MASK, regval);
|
||||
fifo_val &= GENMASK(priv->data->num_bits - 1, 0);
|
||||
*val = meson_saradc_calib_val(priv, fifo_val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void meson_saradc_start_sample_engine(struct meson_saradc_priv *priv)
|
||||
{
|
||||
regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG0,
|
||||
MESON_SAR_ADC_REG0_FIFO_IRQ_EN,
|
||||
MESON_SAR_ADC_REG0_FIFO_IRQ_EN);
|
||||
|
||||
regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG0,
|
||||
MESON_SAR_ADC_REG0_SAMPLE_ENGINE_ENABLE,
|
||||
MESON_SAR_ADC_REG0_SAMPLE_ENGINE_ENABLE);
|
||||
|
||||
regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG0,
|
||||
MESON_SAR_ADC_REG0_SAMPLING_START,
|
||||
MESON_SAR_ADC_REG0_SAMPLING_START);
|
||||
}
|
||||
|
||||
static void meson_saradc_stop_sample_engine(struct meson_saradc_priv *priv)
|
||||
{
|
||||
regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG0,
|
||||
MESON_SAR_ADC_REG0_FIFO_IRQ_EN, 0);
|
||||
|
||||
regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG0,
|
||||
MESON_SAR_ADC_REG0_SAMPLING_STOP,
|
||||
MESON_SAR_ADC_REG0_SAMPLING_STOP);
|
||||
|
||||
/* wait until all modules are stopped */
|
||||
meson_saradc_wait_busy_clear(priv);
|
||||
|
||||
regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG0,
|
||||
MESON_SAR_ADC_REG0_SAMPLE_ENGINE_ENABLE, 0);
|
||||
}
|
||||
|
||||
enum meson_saradc_avg_mode {
|
||||
NO_AVERAGING = 0x0,
|
||||
MEAN_AVERAGING = 0x1,
|
||||
MEDIAN_AVERAGING = 0x2,
|
||||
};
|
||||
|
||||
enum meson_saradc_num_samples {
|
||||
ONE_SAMPLE = 0x0,
|
||||
TWO_SAMPLES = 0x1,
|
||||
FOUR_SAMPLES = 0x2,
|
||||
EIGHT_SAMPLES = 0x3,
|
||||
};
|
||||
|
||||
static void meson_saradc_set_averaging(struct meson_saradc_priv *priv,
|
||||
unsigned int channel,
|
||||
enum meson_saradc_avg_mode mode,
|
||||
enum meson_saradc_num_samples samples)
|
||||
{
|
||||
int val;
|
||||
|
||||
val = samples << MESON_SAR_ADC_AVG_CNTL_NUM_SAMPLES_SHIFT(channel);
|
||||
regmap_update_bits(priv->regmap, MESON_SAR_ADC_AVG_CNTL,
|
||||
MESON_SAR_ADC_AVG_CNTL_NUM_SAMPLES_MASK(channel),
|
||||
val);
|
||||
|
||||
val = mode << MESON_SAR_ADC_AVG_CNTL_AVG_MODE_SHIFT(channel);
|
||||
regmap_update_bits(priv->regmap, MESON_SAR_ADC_AVG_CNTL,
|
||||
MESON_SAR_ADC_AVG_CNTL_AVG_MODE_MASK(channel), val);
|
||||
}
|
||||
|
||||
static void meson_saradc_enable_channel(struct meson_saradc_priv *priv,
|
||||
unsigned int channel)
|
||||
{
|
||||
uint regval;
|
||||
|
||||
/*
|
||||
* the SAR ADC engine allows sampling multiple channels at the same
|
||||
* time. to keep it simple we're only working with one *internal*
|
||||
* channel, which starts counting at index 0 (which means: count = 1).
|
||||
*/
|
||||
regval = FIELD_PREP(MESON_SAR_ADC_CHAN_LIST_MAX_INDEX_MASK, 0);
|
||||
regmap_update_bits(priv->regmap, MESON_SAR_ADC_CHAN_LIST,
|
||||
MESON_SAR_ADC_CHAN_LIST_MAX_INDEX_MASK, regval);
|
||||
|
||||
/* map channel index 0 to the channel which we want to read */
|
||||
regval = FIELD_PREP(MESON_SAR_ADC_CHAN_LIST_ENTRY_MASK(0), channel);
|
||||
regmap_update_bits(priv->regmap, MESON_SAR_ADC_CHAN_LIST,
|
||||
MESON_SAR_ADC_CHAN_LIST_ENTRY_MASK(0), regval);
|
||||
|
||||
regval = FIELD_PREP(MESON_SAR_ADC_DETECT_IDLE_SW_DETECT_MUX_MASK,
|
||||
channel);
|
||||
regmap_update_bits(priv->regmap, MESON_SAR_ADC_DETECT_IDLE_SW,
|
||||
MESON_SAR_ADC_DETECT_IDLE_SW_DETECT_MUX_MASK,
|
||||
regval);
|
||||
|
||||
regval = FIELD_PREP(MESON_SAR_ADC_DETECT_IDLE_SW_IDLE_MUX_SEL_MASK,
|
||||
channel);
|
||||
regmap_update_bits(priv->regmap, MESON_SAR_ADC_DETECT_IDLE_SW,
|
||||
MESON_SAR_ADC_DETECT_IDLE_SW_IDLE_MUX_SEL_MASK,
|
||||
regval);
|
||||
|
||||
if (channel == 6)
|
||||
regmap_update_bits(priv->regmap, MESON_SAR_ADC_DELTA_10,
|
||||
MESON_SAR_ADC_DELTA_10_TEMP_SEL, 0);
|
||||
}
|
||||
|
||||
static int meson_saradc_get_sample(struct meson_saradc_priv *priv,
|
||||
int chan, uint *val)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = meson_saradc_lock(priv);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* clear the FIFO to make sure we're not reading old values */
|
||||
meson_saradc_clear_fifo(priv);
|
||||
|
||||
meson_saradc_set_averaging(priv, chan, MEAN_AVERAGING, EIGHT_SAMPLES);
|
||||
|
||||
meson_saradc_enable_channel(priv, chan);
|
||||
|
||||
meson_saradc_start_sample_engine(priv);
|
||||
ret = meson_saradc_read_raw_sample(priv, chan, val);
|
||||
meson_saradc_stop_sample_engine(priv);
|
||||
|
||||
meson_saradc_unlock(priv);
|
||||
|
||||
if (ret) {
|
||||
printf("failed to read sample for channel %d: %d\n",
|
||||
chan, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int meson_saradc_channel_data(struct udevice *dev, int channel,
|
||||
unsigned int *data)
|
||||
{
|
||||
struct meson_saradc_priv *priv = dev_get_priv(dev);
|
||||
|
||||
if (channel != priv->active_channel) {
|
||||
pr_err("Requested channel is not active!");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return meson_saradc_get_sample(priv, channel, data);
|
||||
}
|
||||
|
||||
enum meson_saradc_chan7_mux_sel {
|
||||
CHAN7_MUX_VSS = 0x0,
|
||||
CHAN7_MUX_VDD_DIV4 = 0x1,
|
||||
CHAN7_MUX_VDD_DIV2 = 0x2,
|
||||
CHAN7_MUX_VDD_MUL3_DIV4 = 0x3,
|
||||
CHAN7_MUX_VDD = 0x4,
|
||||
CHAN7_MUX_CH7_INPUT = 0x7,
|
||||
};
|
||||
|
||||
static void meson_saradc_set_chan7_mux(struct meson_saradc_priv *priv,
|
||||
enum meson_saradc_chan7_mux_sel sel)
|
||||
{
|
||||
u32 regval;
|
||||
|
||||
regval = FIELD_PREP(MESON_SAR_ADC_REG3_CTRL_CHAN7_MUX_SEL_MASK, sel);
|
||||
regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG3,
|
||||
MESON_SAR_ADC_REG3_CTRL_CHAN7_MUX_SEL_MASK, regval);
|
||||
|
||||
udelay(20);
|
||||
}
|
||||
|
||||
static int meson_saradc_calib(struct meson_saradc_priv *priv)
|
||||
{
|
||||
uint nominal0, nominal1, value0, value1;
|
||||
int ret;
|
||||
|
||||
/* use points 25% and 75% for calibration */
|
||||
nominal0 = (1 << priv->data->num_bits) / 4;
|
||||
nominal1 = (1 << priv->data->num_bits) * 3 / 4;
|
||||
|
||||
meson_saradc_set_chan7_mux(priv, CHAN7_MUX_VDD_DIV4);
|
||||
udelay(20);
|
||||
ret = meson_saradc_get_sample(priv, 7, &value0);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
meson_saradc_set_chan7_mux(priv, CHAN7_MUX_VDD_MUL3_DIV4);
|
||||
udelay(20);
|
||||
ret = meson_saradc_get_sample(priv, 7, &value1);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
if (value1 <= value0) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
priv->calibscale = div_s64((nominal1 - nominal0) * (s64)MILLION,
|
||||
value1 - value0);
|
||||
priv->calibbias = nominal0 - div_s64((s64)value0 * priv->calibscale,
|
||||
MILLION);
|
||||
ret = 0;
|
||||
out:
|
||||
meson_saradc_set_chan7_mux(priv, CHAN7_MUX_CH7_INPUT);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int meson_saradc_init(struct meson_saradc_priv *priv)
|
||||
{
|
||||
uint regval;
|
||||
int ret, i;
|
||||
|
||||
priv->calibscale = MILLION;
|
||||
|
||||
/*
|
||||
* make sure we start at CH7 input since the other muxes are only used
|
||||
* for internal calibration.
|
||||
*/
|
||||
meson_saradc_set_chan7_mux(priv, CHAN7_MUX_CH7_INPUT);
|
||||
|
||||
/*
|
||||
* leave sampling delay and the input clocks as configured by
|
||||
* BL30 to make sure BL30 gets the values it expects when
|
||||
* reading the temperature sensor.
|
||||
*/
|
||||
regmap_read(priv->regmap, MESON_SAR_ADC_REG3, ®val);
|
||||
if (regval & MESON_SAR_ADC_REG3_BL30_INITIALIZED)
|
||||
return 0;
|
||||
|
||||
meson_saradc_stop_sample_engine(priv);
|
||||
|
||||
/* update the channel 6 MUX to select the temperature sensor */
|
||||
regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG0,
|
||||
MESON_SAR_ADC_REG0_ADC_TEMP_SEN_SEL,
|
||||
MESON_SAR_ADC_REG0_ADC_TEMP_SEN_SEL);
|
||||
|
||||
/* disable all channels by default */
|
||||
regmap_write(priv->regmap, MESON_SAR_ADC_CHAN_LIST, 0x0);
|
||||
|
||||
regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG3,
|
||||
MESON_SAR_ADC_REG3_CTRL_SAMPLING_CLOCK_PHASE, 0);
|
||||
regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG3,
|
||||
MESON_SAR_ADC_REG3_CNTL_USE_SC_DLY,
|
||||
MESON_SAR_ADC_REG3_CNTL_USE_SC_DLY);
|
||||
|
||||
/* delay between two samples = (10+1) * 1uS */
|
||||
regmap_update_bits(priv->regmap, MESON_SAR_ADC_DELAY,
|
||||
MESON_SAR_ADC_DELAY_INPUT_DLY_CNT_MASK,
|
||||
FIELD_PREP(MESON_SAR_ADC_DELAY_SAMPLE_DLY_CNT_MASK,
|
||||
10));
|
||||
regmap_update_bits(priv->regmap, MESON_SAR_ADC_DELAY,
|
||||
MESON_SAR_ADC_DELAY_SAMPLE_DLY_SEL_MASK,
|
||||
FIELD_PREP(MESON_SAR_ADC_DELAY_SAMPLE_DLY_SEL_MASK,
|
||||
0));
|
||||
|
||||
/* delay between two samples = (10+1) * 1uS */
|
||||
regmap_update_bits(priv->regmap, MESON_SAR_ADC_DELAY,
|
||||
MESON_SAR_ADC_DELAY_INPUT_DLY_CNT_MASK,
|
||||
FIELD_PREP(MESON_SAR_ADC_DELAY_INPUT_DLY_CNT_MASK,
|
||||
10));
|
||||
regmap_update_bits(priv->regmap, MESON_SAR_ADC_DELAY,
|
||||
MESON_SAR_ADC_DELAY_INPUT_DLY_SEL_MASK,
|
||||
FIELD_PREP(MESON_SAR_ADC_DELAY_INPUT_DLY_SEL_MASK,
|
||||
1));
|
||||
|
||||
/*
|
||||
* set up the input channel muxes in MESON_SAR_ADC_CHAN_10_SW
|
||||
* (0 = SAR_ADC_CH0, 1 = SAR_ADC_CH1)
|
||||
*/
|
||||
regval = FIELD_PREP(MESON_SAR_ADC_CHAN_10_SW_CHAN0_MUX_SEL_MASK, 0);
|
||||
regmap_update_bits(priv->regmap, MESON_SAR_ADC_CHAN_10_SW,
|
||||
MESON_SAR_ADC_CHAN_10_SW_CHAN0_MUX_SEL_MASK,
|
||||
regval);
|
||||
regval = FIELD_PREP(MESON_SAR_ADC_CHAN_10_SW_CHAN1_MUX_SEL_MASK, 1);
|
||||
regmap_update_bits(priv->regmap, MESON_SAR_ADC_CHAN_10_SW,
|
||||
MESON_SAR_ADC_CHAN_10_SW_CHAN1_MUX_SEL_MASK,
|
||||
regval);
|
||||
|
||||
/*
|
||||
* set up the input channel muxes in MESON_SAR_ADC_AUX_SW
|
||||
* (2 = SAR_ADC_CH2, 3 = SAR_ADC_CH3, ...) and enable
|
||||
* MESON_SAR_ADC_AUX_SW_YP_DRIVE_SW and
|
||||
* MESON_SAR_ADC_AUX_SW_XP_DRIVE_SW like the vendor driver.
|
||||
*/
|
||||
regval = 0;
|
||||
for (i = 2; i <= 7; i++)
|
||||
regval |= i << MESON_SAR_ADC_AUX_SW_MUX_SEL_CHAN_SHIFT(i);
|
||||
regval |= MESON_SAR_ADC_AUX_SW_YP_DRIVE_SW;
|
||||
regval |= MESON_SAR_ADC_AUX_SW_XP_DRIVE_SW;
|
||||
regmap_write(priv->regmap, MESON_SAR_ADC_AUX_SW, regval);
|
||||
|
||||
ret = meson_saradc_lock(priv);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
#if CONFIG_IS_ENABLED(CLK)
|
||||
ret = clk_enable(&priv->core_clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
#endif
|
||||
|
||||
regval = FIELD_PREP(MESON_SAR_ADC_REG0_FIFO_CNT_IRQ_MASK, 1);
|
||||
regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG0,
|
||||
MESON_SAR_ADC_REG0_FIFO_CNT_IRQ_MASK, regval);
|
||||
|
||||
regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG11,
|
||||
MESON_SAR_ADC_REG11_BANDGAP_EN,
|
||||
MESON_SAR_ADC_REG11_BANDGAP_EN);
|
||||
|
||||
regmap_update_bits(priv->regmap, MESON_SAR_ADC_REG3,
|
||||
MESON_SAR_ADC_REG3_ADC_EN,
|
||||
MESON_SAR_ADC_REG3_ADC_EN);
|
||||
|
||||
udelay(5);
|
||||
|
||||
#if CONFIG_IS_ENABLED(CLK)
|
||||
ret = clk_enable(&priv->adc_clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
#endif
|
||||
|
||||
meson_saradc_unlock(priv);
|
||||
|
||||
ret = meson_saradc_calib(priv);
|
||||
if (ret) {
|
||||
printf("calibration failed\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int meson_saradc_start_channel(struct udevice *dev, int channel)
|
||||
{
|
||||
struct meson_saradc_priv *priv = dev_get_priv(dev);
|
||||
|
||||
if (channel < 0 || channel >= NUM_CHANNELS) {
|
||||
printf("Requested channel is invalid!");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!priv->initialized) {
|
||||
int ret;
|
||||
|
||||
ret = meson_saradc_init(priv);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
priv->initialized = true;
|
||||
}
|
||||
|
||||
priv->active_channel = channel;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int meson_saradc_stop(struct udevice *dev)
|
||||
{
|
||||
struct meson_saradc_priv *priv = dev_get_priv(dev);
|
||||
|
||||
priv->active_channel = -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int meson_saradc_probe(struct udevice *dev)
|
||||
{
|
||||
struct meson_saradc_priv *priv = dev_get_priv(dev);
|
||||
int ret;
|
||||
|
||||
ret = regmap_init_mem(dev_ofnode(dev), &priv->regmap);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
#if CONFIG_IS_ENABLED(CLK)
|
||||
ret = clk_get_by_name(dev, "core", &priv->core_clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = clk_get_by_name(dev, "adc_clk", &priv->adc_clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
#endif
|
||||
|
||||
priv->active_channel = -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int meson_saradc_ofdata_to_platdata(struct udevice *dev)
|
||||
{
|
||||
struct adc_uclass_platdata *uc_pdata = dev_get_uclass_platdata(dev);
|
||||
struct meson_saradc_priv *priv = dev_get_priv(dev);
|
||||
|
||||
priv->data = (struct meson_saradc_data *)dev_get_driver_data(dev);
|
||||
|
||||
uc_pdata->data_mask = GENMASK(priv->data->num_bits - 1, 0);
|
||||
uc_pdata->data_format = ADC_DATA_FORMAT_BIN;
|
||||
uc_pdata->data_timeout_us = MESON_SAR_ADC_TIMEOUT * 1000;
|
||||
uc_pdata->channel_mask = GENMASK(NUM_CHANNELS - 1, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct adc_ops meson_saradc_ops = {
|
||||
.start_channel = meson_saradc_start_channel,
|
||||
.channel_data = meson_saradc_channel_data,
|
||||
.stop = meson_saradc_stop,
|
||||
};
|
||||
|
||||
static const struct meson_saradc_data gxbb_saradc_data = {
|
||||
.num_bits = 10,
|
||||
};
|
||||
|
||||
static const struct meson_saradc_data gxl_saradc_data = {
|
||||
.num_bits = 12,
|
||||
};
|
||||
|
||||
static const struct udevice_id meson_saradc_ids[] = {
|
||||
{ .compatible = "amlogic,meson-gxbb-saradc",
|
||||
.data = (ulong)&gxbb_saradc_data },
|
||||
{ .compatible = "amlogic,meson-gxl-saradc",
|
||||
.data = (ulong)&gxl_saradc_data },
|
||||
{ .compatible = "amlogic,meson-gxm-saradc",
|
||||
.data = (ulong)&gxl_saradc_data },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(meson_saradc) = {
|
||||
.name = "meson_saradc",
|
||||
.id = UCLASS_ADC,
|
||||
.of_match = meson_saradc_ids,
|
||||
.ops = &meson_saradc_ops,
|
||||
.probe = meson_saradc_probe,
|
||||
.ofdata_to_platdata = meson_saradc_ofdata_to_platdata,
|
||||
.priv_auto_alloc_size = sizeof(struct meson_saradc_priv),
|
||||
};
|
||||
@@ -0,0 +1,182 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* (C) Copyright 2017, Fuzhou Rockchip Electronics Co., Ltd
|
||||
*
|
||||
* Rockchip SARADC driver for U-Boot
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <adc.h>
|
||||
#include <clk.h>
|
||||
#include <dm.h>
|
||||
#include <errno.h>
|
||||
#include <asm/io.h>
|
||||
|
||||
#define SARADC_CTRL_CHN_MASK GENMASK(2, 0)
|
||||
#define SARADC_CTRL_POWER_CTRL BIT(3)
|
||||
#define SARADC_CTRL_IRQ_ENABLE BIT(5)
|
||||
#define SARADC_CTRL_IRQ_STATUS BIT(6)
|
||||
|
||||
#define SARADC_TIMEOUT (100 * 1000)
|
||||
|
||||
struct rockchip_saradc_regs {
|
||||
unsigned int data;
|
||||
unsigned int stas;
|
||||
unsigned int ctrl;
|
||||
unsigned int dly_pu_soc;
|
||||
};
|
||||
|
||||
struct rockchip_saradc_data {
|
||||
int num_bits;
|
||||
int num_channels;
|
||||
unsigned long clk_rate;
|
||||
};
|
||||
|
||||
struct rockchip_saradc_priv {
|
||||
struct rockchip_saradc_regs *regs;
|
||||
int active_channel;
|
||||
const struct rockchip_saradc_data *data;
|
||||
};
|
||||
|
||||
int rockchip_saradc_channel_data(struct udevice *dev, int channel,
|
||||
unsigned int *data)
|
||||
{
|
||||
struct rockchip_saradc_priv *priv = dev_get_priv(dev);
|
||||
struct adc_uclass_platdata *uc_pdata = dev_get_uclass_platdata(dev);
|
||||
|
||||
if (channel != priv->active_channel) {
|
||||
pr_err("Requested channel is not active!");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if ((readl(&priv->regs->ctrl) & SARADC_CTRL_IRQ_STATUS) !=
|
||||
SARADC_CTRL_IRQ_STATUS)
|
||||
return -EBUSY;
|
||||
|
||||
/* Read value */
|
||||
*data = readl(&priv->regs->data);
|
||||
*data &= uc_pdata->data_mask;
|
||||
|
||||
/* Power down adc */
|
||||
writel(0, &priv->regs->ctrl);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rockchip_saradc_start_channel(struct udevice *dev, int channel)
|
||||
{
|
||||
struct rockchip_saradc_priv *priv = dev_get_priv(dev);
|
||||
|
||||
if (channel < 0 || channel >= priv->data->num_channels) {
|
||||
pr_err("Requested channel is invalid!");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* 8 clock periods as delay between power up and start cmd */
|
||||
writel(8, &priv->regs->dly_pu_soc);
|
||||
|
||||
/* Select the channel to be used and trigger conversion */
|
||||
writel(SARADC_CTRL_POWER_CTRL | (channel & SARADC_CTRL_CHN_MASK) |
|
||||
SARADC_CTRL_IRQ_ENABLE, &priv->regs->ctrl);
|
||||
|
||||
priv->active_channel = channel;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rockchip_saradc_stop(struct udevice *dev)
|
||||
{
|
||||
struct rockchip_saradc_priv *priv = dev_get_priv(dev);
|
||||
|
||||
/* Power down adc */
|
||||
writel(0, &priv->regs->ctrl);
|
||||
|
||||
priv->active_channel = -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rockchip_saradc_probe(struct udevice *dev)
|
||||
{
|
||||
struct rockchip_saradc_priv *priv = dev_get_priv(dev);
|
||||
struct clk clk;
|
||||
int ret;
|
||||
|
||||
ret = clk_get_by_index(dev, 0, &clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = clk_set_rate(&clk, priv->data->clk_rate);
|
||||
if (IS_ERR_VALUE(ret))
|
||||
return ret;
|
||||
|
||||
priv->active_channel = -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rockchip_saradc_ofdata_to_platdata(struct udevice *dev)
|
||||
{
|
||||
struct adc_uclass_platdata *uc_pdata = dev_get_uclass_platdata(dev);
|
||||
struct rockchip_saradc_priv *priv = dev_get_priv(dev);
|
||||
struct rockchip_saradc_data *data;
|
||||
|
||||
data = (struct rockchip_saradc_data *)dev_get_driver_data(dev);
|
||||
priv->regs = (struct rockchip_saradc_regs *)dev_read_addr(dev);
|
||||
if (priv->regs == (struct rockchip_saradc_regs *)FDT_ADDR_T_NONE) {
|
||||
pr_err("Dev: %s - can't get address!", dev->name);
|
||||
return -ENODATA;
|
||||
}
|
||||
|
||||
priv->data = data;
|
||||
uc_pdata->data_mask = (1 << priv->data->num_bits) - 1;;
|
||||
uc_pdata->data_format = ADC_DATA_FORMAT_BIN;
|
||||
uc_pdata->data_timeout_us = SARADC_TIMEOUT / 5;
|
||||
uc_pdata->channel_mask = (1 << priv->data->num_channels) - 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct adc_ops rockchip_saradc_ops = {
|
||||
.start_channel = rockchip_saradc_start_channel,
|
||||
.channel_data = rockchip_saradc_channel_data,
|
||||
.stop = rockchip_saradc_stop,
|
||||
};
|
||||
|
||||
static const struct rockchip_saradc_data saradc_data = {
|
||||
.num_bits = 10,
|
||||
.num_channels = 3,
|
||||
.clk_rate = 1000000,
|
||||
};
|
||||
|
||||
static const struct rockchip_saradc_data rk3066_tsadc_data = {
|
||||
.num_bits = 12,
|
||||
.num_channels = 2,
|
||||
.clk_rate = 50000,
|
||||
};
|
||||
|
||||
static const struct rockchip_saradc_data rk3399_saradc_data = {
|
||||
.num_bits = 10,
|
||||
.num_channels = 6,
|
||||
.clk_rate = 1000000,
|
||||
};
|
||||
|
||||
static const struct udevice_id rockchip_saradc_ids[] = {
|
||||
{ .compatible = "rockchip,saradc",
|
||||
.data = (ulong)&saradc_data },
|
||||
{ .compatible = "rockchip,rk3066-tsadc",
|
||||
.data = (ulong)&rk3066_tsadc_data },
|
||||
{ .compatible = "rockchip,rk3399-saradc",
|
||||
.data = (ulong)&rk3399_saradc_data },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(rockchip_saradc) = {
|
||||
.name = "rockchip_saradc",
|
||||
.id = UCLASS_ADC,
|
||||
.of_match = rockchip_saradc_ids,
|
||||
.ops = &rockchip_saradc_ops,
|
||||
.probe = rockchip_saradc_probe,
|
||||
.ofdata_to_platdata = rockchip_saradc_ofdata_to_platdata,
|
||||
.priv_auto_alloc_size = sizeof(struct rockchip_saradc_priv),
|
||||
};
|
||||
@@ -0,0 +1,173 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (C) 2015 Samsung Electronics
|
||||
* Przemyslaw Marczak <p.marczak@samsung.com>
|
||||
*/
|
||||
#include <common.h>
|
||||
#include <errno.h>
|
||||
#include <dm.h>
|
||||
#include <adc.h>
|
||||
#include <sandbox-adc.h>
|
||||
|
||||
/**
|
||||
* struct sandbox_adc_priv - sandbox ADC device's operation status and data
|
||||
*
|
||||
* @conversion_status - conversion status: ACTIVE (started) / INACTIVE (stopped)
|
||||
* @conversion_mode - conversion mode: single or multi-channel
|
||||
* @active_channel - active channel number, valid for single channel mode
|
||||
* data[] - channels data
|
||||
*/
|
||||
struct sandbox_adc_priv {
|
||||
int conversion_status;
|
||||
int conversion_mode;
|
||||
int active_channel_mask;
|
||||
unsigned int data[4];
|
||||
};
|
||||
|
||||
int sandbox_adc_start_channel(struct udevice *dev, int channel)
|
||||
{
|
||||
struct sandbox_adc_priv *priv = dev_get_priv(dev);
|
||||
|
||||
/* Set single-channel mode */
|
||||
priv->conversion_mode = SANDBOX_ADC_MODE_SINGLE_CHANNEL;
|
||||
/* Select channel */
|
||||
priv->active_channel_mask = 1 << channel;
|
||||
/* Start conversion */
|
||||
priv->conversion_status = SANDBOX_ADC_ACTIVE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sandbox_adc_start_channels(struct udevice *dev, unsigned int channel_mask)
|
||||
{
|
||||
struct sandbox_adc_priv *priv = dev_get_priv(dev);
|
||||
|
||||
/* Set single-channel mode */
|
||||
priv->conversion_mode = SANDBOX_ADC_MODE_MULTI_CHANNEL;
|
||||
/* Select channel */
|
||||
priv->active_channel_mask = channel_mask;
|
||||
/* Start conversion */
|
||||
priv->conversion_status = SANDBOX_ADC_ACTIVE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sandbox_adc_channel_data(struct udevice *dev, int channel,
|
||||
unsigned int *data)
|
||||
{
|
||||
struct sandbox_adc_priv *priv = dev_get_priv(dev);
|
||||
|
||||
/* For single-channel conversion mode, check if channel was selected */
|
||||
if ((priv->conversion_mode == SANDBOX_ADC_MODE_SINGLE_CHANNEL) &&
|
||||
!(priv->active_channel_mask & (1 << channel))) {
|
||||
pr_err("Request for an inactive channel!");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* The conversion must be started before reading the data */
|
||||
if (priv->conversion_status == SANDBOX_ADC_INACTIVE)
|
||||
return -EIO;
|
||||
|
||||
*data = priv->data[channel];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sandbox_adc_channels_data(struct udevice *dev, unsigned int channel_mask,
|
||||
struct adc_channel *channels)
|
||||
{
|
||||
struct sandbox_adc_priv *priv = dev_get_priv(dev);
|
||||
int i;
|
||||
|
||||
/* Return error for single-channel conversion mode */
|
||||
if (priv->conversion_mode == SANDBOX_ADC_MODE_SINGLE_CHANNEL) {
|
||||
pr_err("ADC in single-channel mode!");
|
||||
return -EPERM;
|
||||
}
|
||||
/* Check channel selection */
|
||||
if (!(priv->active_channel_mask & channel_mask)) {
|
||||
pr_err("Request for an inactive channel!");
|
||||
return -EINVAL;
|
||||
}
|
||||
/* The conversion must be started before reading the data */
|
||||
if (priv->conversion_status == SANDBOX_ADC_INACTIVE)
|
||||
return -EIO;
|
||||
|
||||
for (i = 0; i < SANDBOX_ADC_CHANNELS; i++) {
|
||||
if (!((channel_mask >> i) & 0x1))
|
||||
continue;
|
||||
|
||||
channels->data = priv->data[i];
|
||||
channels->id = i;
|
||||
channels++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sandbox_adc_stop(struct udevice *dev)
|
||||
{
|
||||
struct sandbox_adc_priv *priv = dev_get_priv(dev);
|
||||
|
||||
/* Start conversion */
|
||||
priv->conversion_status = SANDBOX_ADC_INACTIVE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sandbox_adc_probe(struct udevice *dev)
|
||||
{
|
||||
struct sandbox_adc_priv *priv = dev_get_priv(dev);
|
||||
|
||||
/* Stop conversion */
|
||||
priv->conversion_status = SANDBOX_ADC_INACTIVE;
|
||||
/* Set single-channel mode */
|
||||
priv->conversion_mode = SANDBOX_ADC_MODE_SINGLE_CHANNEL;
|
||||
/* Deselect all channels */
|
||||
priv->active_channel_mask = 0;
|
||||
|
||||
/* Set sandbox test data */
|
||||
priv->data[0] = SANDBOX_ADC_CHANNEL0_DATA;
|
||||
priv->data[1] = SANDBOX_ADC_CHANNEL1_DATA;
|
||||
priv->data[2] = SANDBOX_ADC_CHANNEL2_DATA;
|
||||
priv->data[3] = SANDBOX_ADC_CHANNEL3_DATA;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sandbox_adc_ofdata_to_platdata(struct udevice *dev)
|
||||
{
|
||||
struct adc_uclass_platdata *uc_pdata = dev_get_uclass_platdata(dev);
|
||||
|
||||
uc_pdata->data_mask = SANDBOX_ADC_DATA_MASK;
|
||||
uc_pdata->data_format = ADC_DATA_FORMAT_BIN;
|
||||
uc_pdata->data_timeout_us = 0;
|
||||
|
||||
/* Mask available channel bits: [0:3] */
|
||||
uc_pdata->channel_mask = (1 << SANDBOX_ADC_CHANNELS) - 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct adc_ops sandbox_adc_ops = {
|
||||
.start_channel = sandbox_adc_start_channel,
|
||||
.start_channels = sandbox_adc_start_channels,
|
||||
.channel_data = sandbox_adc_channel_data,
|
||||
.channels_data = sandbox_adc_channels_data,
|
||||
.stop = sandbox_adc_stop,
|
||||
};
|
||||
|
||||
static const struct udevice_id sandbox_adc_ids[] = {
|
||||
{ .compatible = "sandbox,adc" },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(sandbox_adc) = {
|
||||
.name = "sandbox-adc",
|
||||
.id = UCLASS_ADC,
|
||||
.of_match = sandbox_adc_ids,
|
||||
.ops = &sandbox_adc_ops,
|
||||
.probe = sandbox_adc_probe,
|
||||
.ofdata_to_platdata = sandbox_adc_ofdata_to_platdata,
|
||||
.priv_auto_alloc_size = sizeof(struct sandbox_adc_priv),
|
||||
};
|
||||
@@ -0,0 +1,210 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2018, STMicroelectronics - All Rights Reserved
|
||||
* Author: Fabrice Gasnier <fabrice.gasnier@st.com>
|
||||
*
|
||||
* Originally based on the Linux kernel v4.18 drivers/iio/adc/stm32-adc-core.c.
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <asm/io.h>
|
||||
#include <power/regulator.h>
|
||||
#include "stm32-adc-core.h"
|
||||
|
||||
/* STM32H7 - common registers for all ADC instances */
|
||||
#define STM32H7_ADC_CCR (STM32_ADCX_COMN_OFFSET + 0x08)
|
||||
|
||||
/* STM32H7_ADC_CCR - bit fields */
|
||||
#define STM32H7_PRESC_SHIFT 18
|
||||
#define STM32H7_PRESC_MASK GENMASK(21, 18)
|
||||
#define STM32H7_CKMODE_SHIFT 16
|
||||
#define STM32H7_CKMODE_MASK GENMASK(17, 16)
|
||||
|
||||
/* STM32 H7 maximum analog clock rate (from datasheet) */
|
||||
#define STM32H7_ADC_MAX_CLK_RATE 36000000
|
||||
|
||||
/**
|
||||
* struct stm32h7_adc_ck_spec - specification for stm32h7 adc clock
|
||||
* @ckmode: ADC clock mode, Async or sync with prescaler.
|
||||
* @presc: prescaler bitfield for async clock mode
|
||||
* @div: prescaler division ratio
|
||||
*/
|
||||
struct stm32h7_adc_ck_spec {
|
||||
u32 ckmode;
|
||||
u32 presc;
|
||||
int div;
|
||||
};
|
||||
|
||||
static const struct stm32h7_adc_ck_spec stm32h7_adc_ckmodes_spec[] = {
|
||||
/* 00: CK_ADC[1..3]: Asynchronous clock modes */
|
||||
{ 0, 0, 1 },
|
||||
{ 0, 1, 2 },
|
||||
{ 0, 2, 4 },
|
||||
{ 0, 3, 6 },
|
||||
{ 0, 4, 8 },
|
||||
{ 0, 5, 10 },
|
||||
{ 0, 6, 12 },
|
||||
{ 0, 7, 16 },
|
||||
{ 0, 8, 32 },
|
||||
{ 0, 9, 64 },
|
||||
{ 0, 10, 128 },
|
||||
{ 0, 11, 256 },
|
||||
/* HCLK used: Synchronous clock modes (1, 2 or 4 prescaler) */
|
||||
{ 1, 0, 1 },
|
||||
{ 2, 0, 2 },
|
||||
{ 3, 0, 4 },
|
||||
};
|
||||
|
||||
static int stm32h7_adc_clk_sel(struct udevice *dev,
|
||||
struct stm32_adc_common *common)
|
||||
{
|
||||
u32 ckmode, presc;
|
||||
unsigned long rate;
|
||||
unsigned int i;
|
||||
int div;
|
||||
|
||||
/* stm32h7 bus clock is common for all ADC instances (mandatory) */
|
||||
if (!clk_valid(&common->bclk)) {
|
||||
dev_err(dev, "No bclk clock found\n");
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
/*
|
||||
* stm32h7 can use either 'bus' or 'adc' clock for analog circuitry.
|
||||
* So, choice is to have bus clock mandatory and adc clock optional.
|
||||
* If optional 'adc' clock has been found, then try to use it first.
|
||||
*/
|
||||
if (clk_valid(&common->aclk)) {
|
||||
/*
|
||||
* Asynchronous clock modes (e.g. ckmode == 0)
|
||||
* From spec: PLL output musn't exceed max rate
|
||||
*/
|
||||
rate = clk_get_rate(&common->aclk);
|
||||
if (!rate) {
|
||||
dev_err(dev, "Invalid aclk rate: 0\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(stm32h7_adc_ckmodes_spec); i++) {
|
||||
ckmode = stm32h7_adc_ckmodes_spec[i].ckmode;
|
||||
presc = stm32h7_adc_ckmodes_spec[i].presc;
|
||||
div = stm32h7_adc_ckmodes_spec[i].div;
|
||||
|
||||
if (ckmode)
|
||||
continue;
|
||||
|
||||
if ((rate / div) <= STM32H7_ADC_MAX_CLK_RATE)
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
/* Synchronous clock modes (e.g. ckmode is 1, 2 or 3) */
|
||||
rate = clk_get_rate(&common->bclk);
|
||||
if (!rate) {
|
||||
dev_err(dev, "Invalid bus clock rate: 0\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(stm32h7_adc_ckmodes_spec); i++) {
|
||||
ckmode = stm32h7_adc_ckmodes_spec[i].ckmode;
|
||||
presc = stm32h7_adc_ckmodes_spec[i].presc;
|
||||
div = stm32h7_adc_ckmodes_spec[i].div;
|
||||
|
||||
if (!ckmode)
|
||||
continue;
|
||||
|
||||
if ((rate / div) <= STM32H7_ADC_MAX_CLK_RATE)
|
||||
goto out;
|
||||
}
|
||||
|
||||
dev_err(dev, "clk selection failed\n");
|
||||
return -EINVAL;
|
||||
|
||||
out:
|
||||
/* rate used later by each ADC instance to control BOOST mode */
|
||||
common->rate = rate / div;
|
||||
|
||||
/* Set common clock mode and prescaler */
|
||||
clrsetbits_le32(common->base + STM32H7_ADC_CCR,
|
||||
STM32H7_CKMODE_MASK | STM32H7_PRESC_MASK,
|
||||
ckmode << STM32H7_CKMODE_SHIFT |
|
||||
presc << STM32H7_PRESC_SHIFT);
|
||||
|
||||
dev_dbg(dev, "Using %s clock/%d source at %ld kHz\n",
|
||||
ckmode ? "bus" : "adc", div, common->rate / 1000);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_adc_core_probe(struct udevice *dev)
|
||||
{
|
||||
struct stm32_adc_common *common = dev_get_priv(dev);
|
||||
int ret;
|
||||
|
||||
common->base = dev_read_addr_ptr(dev);
|
||||
if (!common->base) {
|
||||
dev_err(dev, "can't get address\n");
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
ret = device_get_supply_regulator(dev, "vref-supply", &common->vref);
|
||||
if (ret) {
|
||||
dev_err(dev, "can't get vref-supply: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = regulator_get_value(common->vref);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "can't get vref-supply value: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
common->vref_uv = ret;
|
||||
|
||||
ret = clk_get_by_name(dev, "adc", &common->aclk);
|
||||
if (!ret) {
|
||||
ret = clk_enable(&common->aclk);
|
||||
if (ret) {
|
||||
dev_err(dev, "Can't enable aclk: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = clk_get_by_name(dev, "bus", &common->bclk);
|
||||
if (!ret) {
|
||||
ret = clk_enable(&common->bclk);
|
||||
if (ret) {
|
||||
dev_err(dev, "Can't enable bclk: %d\n", ret);
|
||||
goto err_aclk_disable;
|
||||
}
|
||||
}
|
||||
|
||||
ret = stm32h7_adc_clk_sel(dev, common);
|
||||
if (ret)
|
||||
goto err_bclk_disable;
|
||||
|
||||
return ret;
|
||||
|
||||
err_bclk_disable:
|
||||
if (clk_valid(&common->bclk))
|
||||
clk_disable(&common->bclk);
|
||||
|
||||
err_aclk_disable:
|
||||
if (clk_valid(&common->aclk))
|
||||
clk_disable(&common->aclk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct udevice_id stm32_adc_core_ids[] = {
|
||||
{ .compatible = "st,stm32h7-adc-core" },
|
||||
{ .compatible = "st,stm32mp1-adc-core" },
|
||||
{}
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(stm32_adc_core) = {
|
||||
.name = "stm32-adc-core",
|
||||
.id = UCLASS_SIMPLE_BUS,
|
||||
.of_match = stm32_adc_core_ids,
|
||||
.probe = stm32_adc_core_probe,
|
||||
.priv_auto_alloc_size = sizeof(struct stm32_adc_common),
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Copyright (C) 2018, STMicroelectronics - All Rights Reserved
|
||||
* Author: Fabrice Gasnier <fabrice.gasnier@st.com>.
|
||||
*
|
||||
* Originally based on the Linux kernel v4.18 drivers/iio/adc/stm32-adc-core.h.
|
||||
*/
|
||||
|
||||
#ifndef __STM32_ADC_H
|
||||
#define __STM32_ADC_H
|
||||
|
||||
/*
|
||||
* STM32 - ADC global register map
|
||||
* ________________________________________________________
|
||||
* | Offset | Register |
|
||||
* --------------------------------------------------------
|
||||
* | 0x000 | Master ADC1 |
|
||||
* --------------------------------------------------------
|
||||
* | 0x100 | Slave ADC2 |
|
||||
* --------------------------------------------------------
|
||||
* | 0x200 | Slave ADC3 |
|
||||
* --------------------------------------------------------
|
||||
* | 0x300 | Master & Slave common regs |
|
||||
* --------------------------------------------------------
|
||||
*/
|
||||
#define STM32_ADC_MAX_ADCS 3
|
||||
#define STM32_ADCX_COMN_OFFSET 0x300
|
||||
|
||||
#include <common.h>
|
||||
#include <clk.h>
|
||||
#include <dm.h>
|
||||
|
||||
/**
|
||||
* struct stm32_adc_common - stm32 ADC driver common data (for all instances)
|
||||
* @base: control registers base cpu addr
|
||||
* @rate: clock rate used for analog circuitry
|
||||
* @aclk: clock for the analog circuitry
|
||||
* @bclk: bus clock common for all ADCs
|
||||
* @vref: regulator reference
|
||||
* @vref_uv: reference supply voltage (uV)
|
||||
*/
|
||||
struct stm32_adc_common {
|
||||
void __iomem *base;
|
||||
unsigned long rate;
|
||||
struct clk aclk;
|
||||
struct clk bclk;
|
||||
struct udevice *vref;
|
||||
int vref_uv;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,258 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2018, STMicroelectronics - All Rights Reserved
|
||||
* Author: Fabrice Gasnier <fabrice.gasnier@st.com>
|
||||
*
|
||||
* Originally based on the Linux kernel v4.18 drivers/iio/adc/stm32-adc.c.
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <adc.h>
|
||||
#include <asm/io.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include "stm32-adc-core.h"
|
||||
|
||||
/* STM32H7 - Registers for each ADC instance */
|
||||
#define STM32H7_ADC_ISR 0x00
|
||||
#define STM32H7_ADC_CR 0x08
|
||||
#define STM32H7_ADC_CFGR 0x0C
|
||||
#define STM32H7_ADC_SMPR1 0x14
|
||||
#define STM32H7_ADC_SMPR2 0x18
|
||||
#define STM32H7_ADC_PCSEL 0x1C
|
||||
#define STM32H7_ADC_SQR1 0x30
|
||||
#define STM32H7_ADC_DR 0x40
|
||||
#define STM32H7_ADC_DIFSEL 0xC0
|
||||
|
||||
/* STM32H7_ADC_ISR - bit fields */
|
||||
#define STM32MP1_VREGREADY BIT(12)
|
||||
#define STM32H7_EOC BIT(2)
|
||||
#define STM32H7_ADRDY BIT(0)
|
||||
|
||||
/* STM32H7_ADC_CR - bit fields */
|
||||
#define STM32H7_DEEPPWD BIT(29)
|
||||
#define STM32H7_ADVREGEN BIT(28)
|
||||
#define STM32H7_BOOST BIT(8)
|
||||
#define STM32H7_ADSTART BIT(2)
|
||||
#define STM32H7_ADDIS BIT(1)
|
||||
#define STM32H7_ADEN BIT(0)
|
||||
|
||||
/* STM32H7_ADC_CFGR bit fields */
|
||||
#define STM32H7_EXTEN GENMASK(11, 10)
|
||||
#define STM32H7_DMNGT GENMASK(1, 0)
|
||||
|
||||
/* STM32H7_ADC_SQR1 - bit fields */
|
||||
#define STM32H7_SQ1_SHIFT 6
|
||||
|
||||
/* BOOST bit must be set on STM32H7 when ADC clock is above 20MHz */
|
||||
#define STM32H7_BOOST_CLKRATE 20000000UL
|
||||
|
||||
#define STM32_ADC_CH_MAX 20 /* max number of channels */
|
||||
#define STM32_ADC_TIMEOUT_US 100000
|
||||
|
||||
struct stm32_adc_cfg {
|
||||
unsigned int max_channels;
|
||||
unsigned int num_bits;
|
||||
bool has_vregready;
|
||||
};
|
||||
|
||||
struct stm32_adc {
|
||||
void __iomem *regs;
|
||||
int active_channel;
|
||||
const struct stm32_adc_cfg *cfg;
|
||||
};
|
||||
|
||||
static int stm32_adc_stop(struct udevice *dev)
|
||||
{
|
||||
struct stm32_adc *adc = dev_get_priv(dev);
|
||||
|
||||
setbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_ADDIS);
|
||||
clrbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_BOOST);
|
||||
/* Setting DEEPPWD disables ADC vreg and clears ADVREGEN */
|
||||
setbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_DEEPPWD);
|
||||
adc->active_channel = -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_adc_start_channel(struct udevice *dev, int channel)
|
||||
{
|
||||
struct adc_uclass_platdata *uc_pdata = dev_get_uclass_platdata(dev);
|
||||
struct stm32_adc_common *common = dev_get_priv(dev_get_parent(dev));
|
||||
struct stm32_adc *adc = dev_get_priv(dev);
|
||||
int ret;
|
||||
u32 val;
|
||||
|
||||
/* Exit deep power down, then enable ADC voltage regulator */
|
||||
clrbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_DEEPPWD);
|
||||
setbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_ADVREGEN);
|
||||
if (common->rate > STM32H7_BOOST_CLKRATE)
|
||||
setbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_BOOST);
|
||||
|
||||
/* Wait for startup time */
|
||||
if (!adc->cfg->has_vregready) {
|
||||
udelay(20);
|
||||
} else {
|
||||
ret = readl_poll_timeout(adc->regs + STM32H7_ADC_ISR, val,
|
||||
val & STM32MP1_VREGREADY,
|
||||
STM32_ADC_TIMEOUT_US);
|
||||
if (ret < 0) {
|
||||
stm32_adc_stop(dev);
|
||||
dev_err(dev, "Failed to enable vreg: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/* Only use single ended channels */
|
||||
writel(0, adc->regs + STM32H7_ADC_DIFSEL);
|
||||
|
||||
/* Enable ADC, Poll for ADRDY to be set (after adc startup time) */
|
||||
setbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_ADEN);
|
||||
ret = readl_poll_timeout(adc->regs + STM32H7_ADC_ISR, val,
|
||||
val & STM32H7_ADRDY, STM32_ADC_TIMEOUT_US);
|
||||
if (ret < 0) {
|
||||
stm32_adc_stop(dev);
|
||||
dev_err(dev, "Failed to enable ADC: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Preselect channels */
|
||||
writel(uc_pdata->channel_mask, adc->regs + STM32H7_ADC_PCSEL);
|
||||
|
||||
/* Set sampling time to max value by default */
|
||||
writel(0xffffffff, adc->regs + STM32H7_ADC_SMPR1);
|
||||
writel(0xffffffff, adc->regs + STM32H7_ADC_SMPR2);
|
||||
|
||||
/* Program regular sequence: chan in SQ1 & len = 0 for one channel */
|
||||
writel(channel << STM32H7_SQ1_SHIFT, adc->regs + STM32H7_ADC_SQR1);
|
||||
|
||||
/* Trigger detection disabled (conversion can be launched in SW) */
|
||||
clrbits_le32(adc->regs + STM32H7_ADC_CFGR, STM32H7_EXTEN |
|
||||
STM32H7_DMNGT);
|
||||
adc->active_channel = channel;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_adc_channel_data(struct udevice *dev, int channel,
|
||||
unsigned int *data)
|
||||
{
|
||||
struct stm32_adc *adc = dev_get_priv(dev);
|
||||
int ret;
|
||||
u32 val;
|
||||
|
||||
if (channel != adc->active_channel) {
|
||||
dev_err(dev, "Requested channel is not active!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
setbits_le32(adc->regs + STM32H7_ADC_CR, STM32H7_ADSTART);
|
||||
ret = readl_poll_timeout(adc->regs + STM32H7_ADC_ISR, val,
|
||||
val & STM32H7_EOC, STM32_ADC_TIMEOUT_US);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "conversion timed out: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
*data = readl(adc->regs + STM32H7_ADC_DR);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_adc_chan_of_init(struct udevice *dev)
|
||||
{
|
||||
struct adc_uclass_platdata *uc_pdata = dev_get_uclass_platdata(dev);
|
||||
struct stm32_adc *adc = dev_get_priv(dev);
|
||||
u32 chans[STM32_ADC_CH_MAX];
|
||||
unsigned int i, num_channels;
|
||||
int ret;
|
||||
|
||||
/* Retrieve single ended channels listed in device tree */
|
||||
ret = dev_read_size(dev, "st,adc-channels");
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "can't get st,adc-channels: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
num_channels = ret / sizeof(u32);
|
||||
|
||||
if (num_channels > adc->cfg->max_channels) {
|
||||
dev_err(dev, "too many st,adc-channels: %d\n", num_channels);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = dev_read_u32_array(dev, "st,adc-channels", chans, num_channels);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "can't read st,adc-channels: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (i = 0; i < num_channels; i++) {
|
||||
if (chans[i] >= adc->cfg->max_channels) {
|
||||
dev_err(dev, "bad channel %u\n", chans[i]);
|
||||
return -EINVAL;
|
||||
}
|
||||
uc_pdata->channel_mask |= 1 << chans[i];
|
||||
}
|
||||
|
||||
uc_pdata->data_mask = (1 << adc->cfg->num_bits) - 1;
|
||||
uc_pdata->data_format = ADC_DATA_FORMAT_BIN;
|
||||
uc_pdata->data_timeout_us = 100000;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int stm32_adc_probe(struct udevice *dev)
|
||||
{
|
||||
struct adc_uclass_platdata *uc_pdata = dev_get_uclass_platdata(dev);
|
||||
struct stm32_adc_common *common = dev_get_priv(dev_get_parent(dev));
|
||||
struct stm32_adc *adc = dev_get_priv(dev);
|
||||
int offset;
|
||||
|
||||
offset = dev_read_u32_default(dev, "reg", -ENODATA);
|
||||
if (offset < 0) {
|
||||
dev_err(dev, "Can't read reg property\n");
|
||||
return offset;
|
||||
}
|
||||
adc->regs = common->base + offset;
|
||||
adc->cfg = (const struct stm32_adc_cfg *)dev_get_driver_data(dev);
|
||||
|
||||
/* VDD supplied by common vref pin */
|
||||
uc_pdata->vdd_supply = common->vref;
|
||||
uc_pdata->vdd_microvolts = common->vref_uv;
|
||||
uc_pdata->vss_microvolts = 0;
|
||||
|
||||
return stm32_adc_chan_of_init(dev);
|
||||
}
|
||||
|
||||
static const struct adc_ops stm32_adc_ops = {
|
||||
.start_channel = stm32_adc_start_channel,
|
||||
.channel_data = stm32_adc_channel_data,
|
||||
.stop = stm32_adc_stop,
|
||||
};
|
||||
|
||||
static const struct stm32_adc_cfg stm32h7_adc_cfg = {
|
||||
.num_bits = 16,
|
||||
.max_channels = STM32_ADC_CH_MAX,
|
||||
};
|
||||
|
||||
static const struct stm32_adc_cfg stm32mp1_adc_cfg = {
|
||||
.num_bits = 16,
|
||||
.max_channels = STM32_ADC_CH_MAX,
|
||||
.has_vregready = true,
|
||||
};
|
||||
|
||||
static const struct udevice_id stm32_adc_ids[] = {
|
||||
{ .compatible = "st,stm32h7-adc",
|
||||
.data = (ulong)&stm32h7_adc_cfg },
|
||||
{ .compatible = "st,stm32mp1-adc",
|
||||
.data = (ulong)&stm32mp1_adc_cfg },
|
||||
{}
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(stm32_adc) = {
|
||||
.name = "stm32-adc",
|
||||
.id = UCLASS_ADC,
|
||||
.of_match = stm32_adc_ids,
|
||||
.probe = stm32_adc_probe,
|
||||
.ops = &stm32_adc_ops,
|
||||
.priv_auto_alloc_size = sizeof(struct stm32_adc),
|
||||
};
|
||||
Reference in New Issue
Block a user