162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* ADC driver for sunxi platforms' (A10, A13 and A31) GPADC 362306a36Sopenharmony_ci * 462306a36Sopenharmony_ci * Copyright (c) 2016 Quentin Schulz <quentin.schulz@free-electrons.com> 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * The Allwinner SoCs all have an ADC that can also act as a touchscreen 762306a36Sopenharmony_ci * controller and a thermal sensor. 862306a36Sopenharmony_ci * The thermal sensor works only when the ADC acts as a touchscreen controller 962306a36Sopenharmony_ci * and is configured to throw an interrupt every fixed periods of time (let say 1062306a36Sopenharmony_ci * every X seconds). 1162306a36Sopenharmony_ci * One would be tempted to disable the IP on the hardware side rather than 1262306a36Sopenharmony_ci * disabling interrupts to save some power but that resets the internal clock of 1362306a36Sopenharmony_ci * the IP, resulting in having to wait X seconds every time we want to read the 1462306a36Sopenharmony_ci * value of the thermal sensor. 1562306a36Sopenharmony_ci * This is also the reason of using autosuspend in pm_runtime. If there was no 1662306a36Sopenharmony_ci * autosuspend, the thermal sensor would need X seconds after every 1762306a36Sopenharmony_ci * pm_runtime_get_sync to get a value from the ADC. The autosuspend allows the 1862306a36Sopenharmony_ci * thermal sensor to be requested again in a certain time span before it gets 1962306a36Sopenharmony_ci * shutdown for not being used. 2062306a36Sopenharmony_ci */ 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci#include <linux/completion.h> 2362306a36Sopenharmony_ci#include <linux/interrupt.h> 2462306a36Sopenharmony_ci#include <linux/io.h> 2562306a36Sopenharmony_ci#include <linux/module.h> 2662306a36Sopenharmony_ci#include <linux/of.h> 2762306a36Sopenharmony_ci#include <linux/platform_device.h> 2862306a36Sopenharmony_ci#include <linux/pm_runtime.h> 2962306a36Sopenharmony_ci#include <linux/regmap.h> 3062306a36Sopenharmony_ci#include <linux/thermal.h> 3162306a36Sopenharmony_ci#include <linux/delay.h> 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci#include <linux/iio/iio.h> 3462306a36Sopenharmony_ci#include <linux/iio/driver.h> 3562306a36Sopenharmony_ci#include <linux/iio/machine.h> 3662306a36Sopenharmony_ci#include <linux/mfd/sun4i-gpadc.h> 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_cistatic unsigned int sun4i_gpadc_chan_select(unsigned int chan) 3962306a36Sopenharmony_ci{ 4062306a36Sopenharmony_ci return SUN4I_GPADC_CTRL1_ADC_CHAN_SELECT(chan); 4162306a36Sopenharmony_ci} 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_cistatic unsigned int sun6i_gpadc_chan_select(unsigned int chan) 4462306a36Sopenharmony_ci{ 4562306a36Sopenharmony_ci return SUN6I_GPADC_CTRL1_ADC_CHAN_SELECT(chan); 4662306a36Sopenharmony_ci} 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_cistruct gpadc_data { 4962306a36Sopenharmony_ci int temp_offset; 5062306a36Sopenharmony_ci int temp_scale; 5162306a36Sopenharmony_ci unsigned int tp_mode_en; 5262306a36Sopenharmony_ci unsigned int tp_adc_select; 5362306a36Sopenharmony_ci unsigned int (*adc_chan_select)(unsigned int chan); 5462306a36Sopenharmony_ci unsigned int adc_chan_mask; 5562306a36Sopenharmony_ci}; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_cistatic const struct gpadc_data sun4i_gpadc_data = { 5862306a36Sopenharmony_ci .temp_offset = -1932, 5962306a36Sopenharmony_ci .temp_scale = 133, 6062306a36Sopenharmony_ci .tp_mode_en = SUN4I_GPADC_CTRL1_TP_MODE_EN, 6162306a36Sopenharmony_ci .tp_adc_select = SUN4I_GPADC_CTRL1_TP_ADC_SELECT, 6262306a36Sopenharmony_ci .adc_chan_select = &sun4i_gpadc_chan_select, 6362306a36Sopenharmony_ci .adc_chan_mask = SUN4I_GPADC_CTRL1_ADC_CHAN_MASK, 6462306a36Sopenharmony_ci}; 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_cistatic const struct gpadc_data sun5i_gpadc_data = { 6762306a36Sopenharmony_ci .temp_offset = -1447, 6862306a36Sopenharmony_ci .temp_scale = 100, 6962306a36Sopenharmony_ci .tp_mode_en = SUN4I_GPADC_CTRL1_TP_MODE_EN, 7062306a36Sopenharmony_ci .tp_adc_select = SUN4I_GPADC_CTRL1_TP_ADC_SELECT, 7162306a36Sopenharmony_ci .adc_chan_select = &sun4i_gpadc_chan_select, 7262306a36Sopenharmony_ci .adc_chan_mask = SUN4I_GPADC_CTRL1_ADC_CHAN_MASK, 7362306a36Sopenharmony_ci}; 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_cistatic const struct gpadc_data sun6i_gpadc_data = { 7662306a36Sopenharmony_ci .temp_offset = -1623, 7762306a36Sopenharmony_ci .temp_scale = 167, 7862306a36Sopenharmony_ci .tp_mode_en = SUN6I_GPADC_CTRL1_TP_MODE_EN, 7962306a36Sopenharmony_ci .tp_adc_select = SUN6I_GPADC_CTRL1_TP_ADC_SELECT, 8062306a36Sopenharmony_ci .adc_chan_select = &sun6i_gpadc_chan_select, 8162306a36Sopenharmony_ci .adc_chan_mask = SUN6I_GPADC_CTRL1_ADC_CHAN_MASK, 8262306a36Sopenharmony_ci}; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_cistatic const struct gpadc_data sun8i_a33_gpadc_data = { 8562306a36Sopenharmony_ci .temp_offset = -1662, 8662306a36Sopenharmony_ci .temp_scale = 162, 8762306a36Sopenharmony_ci .tp_mode_en = SUN8I_GPADC_CTRL1_CHOP_TEMP_EN, 8862306a36Sopenharmony_ci}; 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_cistruct sun4i_gpadc_iio { 9162306a36Sopenharmony_ci struct iio_dev *indio_dev; 9262306a36Sopenharmony_ci struct completion completion; 9362306a36Sopenharmony_ci int temp_data; 9462306a36Sopenharmony_ci u32 adc_data; 9562306a36Sopenharmony_ci struct regmap *regmap; 9662306a36Sopenharmony_ci unsigned int fifo_data_irq; 9762306a36Sopenharmony_ci atomic_t ignore_fifo_data_irq; 9862306a36Sopenharmony_ci unsigned int temp_data_irq; 9962306a36Sopenharmony_ci atomic_t ignore_temp_data_irq; 10062306a36Sopenharmony_ci const struct gpadc_data *data; 10162306a36Sopenharmony_ci bool no_irq; 10262306a36Sopenharmony_ci /* prevents concurrent reads of temperature and ADC */ 10362306a36Sopenharmony_ci struct mutex mutex; 10462306a36Sopenharmony_ci struct thermal_zone_device *tzd; 10562306a36Sopenharmony_ci struct device *sensor_device; 10662306a36Sopenharmony_ci}; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci#define SUN4I_GPADC_ADC_CHANNEL(_channel, _name) { \ 10962306a36Sopenharmony_ci .type = IIO_VOLTAGE, \ 11062306a36Sopenharmony_ci .indexed = 1, \ 11162306a36Sopenharmony_ci .channel = _channel, \ 11262306a36Sopenharmony_ci .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ 11362306a36Sopenharmony_ci .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ 11462306a36Sopenharmony_ci .datasheet_name = _name, \ 11562306a36Sopenharmony_ci} 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_cistatic struct iio_map sun4i_gpadc_hwmon_maps[] = { 11862306a36Sopenharmony_ci { 11962306a36Sopenharmony_ci .adc_channel_label = "temp_adc", 12062306a36Sopenharmony_ci .consumer_dev_name = "iio_hwmon.0", 12162306a36Sopenharmony_ci }, 12262306a36Sopenharmony_ci { /* sentinel */ }, 12362306a36Sopenharmony_ci}; 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_cistatic const struct iio_chan_spec sun4i_gpadc_channels[] = { 12662306a36Sopenharmony_ci SUN4I_GPADC_ADC_CHANNEL(0, "adc_chan0"), 12762306a36Sopenharmony_ci SUN4I_GPADC_ADC_CHANNEL(1, "adc_chan1"), 12862306a36Sopenharmony_ci SUN4I_GPADC_ADC_CHANNEL(2, "adc_chan2"), 12962306a36Sopenharmony_ci SUN4I_GPADC_ADC_CHANNEL(3, "adc_chan3"), 13062306a36Sopenharmony_ci { 13162306a36Sopenharmony_ci .type = IIO_TEMP, 13262306a36Sopenharmony_ci .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | 13362306a36Sopenharmony_ci BIT(IIO_CHAN_INFO_SCALE) | 13462306a36Sopenharmony_ci BIT(IIO_CHAN_INFO_OFFSET), 13562306a36Sopenharmony_ci .datasheet_name = "temp_adc", 13662306a36Sopenharmony_ci }, 13762306a36Sopenharmony_ci}; 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_cistatic const struct iio_chan_spec sun4i_gpadc_channels_no_temp[] = { 14062306a36Sopenharmony_ci SUN4I_GPADC_ADC_CHANNEL(0, "adc_chan0"), 14162306a36Sopenharmony_ci SUN4I_GPADC_ADC_CHANNEL(1, "adc_chan1"), 14262306a36Sopenharmony_ci SUN4I_GPADC_ADC_CHANNEL(2, "adc_chan2"), 14362306a36Sopenharmony_ci SUN4I_GPADC_ADC_CHANNEL(3, "adc_chan3"), 14462306a36Sopenharmony_ci}; 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_cistatic const struct iio_chan_spec sun8i_a33_gpadc_channels[] = { 14762306a36Sopenharmony_ci { 14862306a36Sopenharmony_ci .type = IIO_TEMP, 14962306a36Sopenharmony_ci .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | 15062306a36Sopenharmony_ci BIT(IIO_CHAN_INFO_SCALE) | 15162306a36Sopenharmony_ci BIT(IIO_CHAN_INFO_OFFSET), 15262306a36Sopenharmony_ci .datasheet_name = "temp_adc", 15362306a36Sopenharmony_ci }, 15462306a36Sopenharmony_ci}; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_cistatic const struct regmap_config sun4i_gpadc_regmap_config = { 15762306a36Sopenharmony_ci .reg_bits = 32, 15862306a36Sopenharmony_ci .val_bits = 32, 15962306a36Sopenharmony_ci .reg_stride = 4, 16062306a36Sopenharmony_ci .fast_io = true, 16162306a36Sopenharmony_ci}; 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_cistatic int sun4i_prepare_for_irq(struct iio_dev *indio_dev, int channel, 16462306a36Sopenharmony_ci unsigned int irq) 16562306a36Sopenharmony_ci{ 16662306a36Sopenharmony_ci struct sun4i_gpadc_iio *info = iio_priv(indio_dev); 16762306a36Sopenharmony_ci int ret; 16862306a36Sopenharmony_ci u32 reg; 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci pm_runtime_get_sync(indio_dev->dev.parent); 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci reinit_completion(&info->completion); 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci ret = regmap_write(info->regmap, SUN4I_GPADC_INT_FIFOC, 17562306a36Sopenharmony_ci SUN4I_GPADC_INT_FIFOC_TP_FIFO_TRIG_LEVEL(1) | 17662306a36Sopenharmony_ci SUN4I_GPADC_INT_FIFOC_TP_FIFO_FLUSH); 17762306a36Sopenharmony_ci if (ret) 17862306a36Sopenharmony_ci return ret; 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci ret = regmap_read(info->regmap, SUN4I_GPADC_CTRL1, ®); 18162306a36Sopenharmony_ci if (ret) 18262306a36Sopenharmony_ci return ret; 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci if (irq == info->fifo_data_irq) { 18562306a36Sopenharmony_ci ret = regmap_write(info->regmap, SUN4I_GPADC_CTRL1, 18662306a36Sopenharmony_ci info->data->tp_mode_en | 18762306a36Sopenharmony_ci info->data->tp_adc_select | 18862306a36Sopenharmony_ci info->data->adc_chan_select(channel)); 18962306a36Sopenharmony_ci /* 19062306a36Sopenharmony_ci * When the IP changes channel, it needs a bit of time to get 19162306a36Sopenharmony_ci * correct values. 19262306a36Sopenharmony_ci */ 19362306a36Sopenharmony_ci if ((reg & info->data->adc_chan_mask) != 19462306a36Sopenharmony_ci info->data->adc_chan_select(channel)) 19562306a36Sopenharmony_ci mdelay(10); 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci } else { 19862306a36Sopenharmony_ci /* 19962306a36Sopenharmony_ci * The temperature sensor returns valid data only when the ADC 20062306a36Sopenharmony_ci * operates in touchscreen mode. 20162306a36Sopenharmony_ci */ 20262306a36Sopenharmony_ci ret = regmap_write(info->regmap, SUN4I_GPADC_CTRL1, 20362306a36Sopenharmony_ci info->data->tp_mode_en); 20462306a36Sopenharmony_ci } 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci if (ret) 20762306a36Sopenharmony_ci return ret; 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci /* 21062306a36Sopenharmony_ci * When the IP changes mode between ADC or touchscreen, it 21162306a36Sopenharmony_ci * needs a bit of time to get correct values. 21262306a36Sopenharmony_ci */ 21362306a36Sopenharmony_ci if ((reg & info->data->tp_adc_select) != info->data->tp_adc_select) 21462306a36Sopenharmony_ci mdelay(100); 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci return 0; 21762306a36Sopenharmony_ci} 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_cistatic int sun4i_gpadc_read(struct iio_dev *indio_dev, int channel, int *val, 22062306a36Sopenharmony_ci unsigned int irq) 22162306a36Sopenharmony_ci{ 22262306a36Sopenharmony_ci struct sun4i_gpadc_iio *info = iio_priv(indio_dev); 22362306a36Sopenharmony_ci int ret; 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci mutex_lock(&info->mutex); 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci ret = sun4i_prepare_for_irq(indio_dev, channel, irq); 22862306a36Sopenharmony_ci if (ret) 22962306a36Sopenharmony_ci goto err; 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci enable_irq(irq); 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci /* 23462306a36Sopenharmony_ci * The temperature sensor throws an interruption periodically (currently 23562306a36Sopenharmony_ci * set at periods of ~0.6s in sun4i_gpadc_runtime_resume). A 1s delay 23662306a36Sopenharmony_ci * makes sure an interruption occurs in normal conditions. If it doesn't 23762306a36Sopenharmony_ci * occur, then there is a timeout. 23862306a36Sopenharmony_ci */ 23962306a36Sopenharmony_ci if (!wait_for_completion_timeout(&info->completion, 24062306a36Sopenharmony_ci msecs_to_jiffies(1000))) { 24162306a36Sopenharmony_ci ret = -ETIMEDOUT; 24262306a36Sopenharmony_ci goto err; 24362306a36Sopenharmony_ci } 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci if (irq == info->fifo_data_irq) 24662306a36Sopenharmony_ci *val = info->adc_data; 24762306a36Sopenharmony_ci else 24862306a36Sopenharmony_ci *val = info->temp_data; 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci ret = 0; 25162306a36Sopenharmony_ci pm_runtime_mark_last_busy(indio_dev->dev.parent); 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_cierr: 25462306a36Sopenharmony_ci pm_runtime_put_autosuspend(indio_dev->dev.parent); 25562306a36Sopenharmony_ci disable_irq(irq); 25662306a36Sopenharmony_ci mutex_unlock(&info->mutex); 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci return ret; 25962306a36Sopenharmony_ci} 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_cistatic int sun4i_gpadc_adc_read(struct iio_dev *indio_dev, int channel, 26262306a36Sopenharmony_ci int *val) 26362306a36Sopenharmony_ci{ 26462306a36Sopenharmony_ci struct sun4i_gpadc_iio *info = iio_priv(indio_dev); 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci return sun4i_gpadc_read(indio_dev, channel, val, info->fifo_data_irq); 26762306a36Sopenharmony_ci} 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_cistatic int sun4i_gpadc_temp_read(struct iio_dev *indio_dev, int *val) 27062306a36Sopenharmony_ci{ 27162306a36Sopenharmony_ci struct sun4i_gpadc_iio *info = iio_priv(indio_dev); 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci if (info->no_irq) { 27462306a36Sopenharmony_ci pm_runtime_get_sync(indio_dev->dev.parent); 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci regmap_read(info->regmap, SUN4I_GPADC_TEMP_DATA, val); 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci pm_runtime_mark_last_busy(indio_dev->dev.parent); 27962306a36Sopenharmony_ci pm_runtime_put_autosuspend(indio_dev->dev.parent); 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci return 0; 28262306a36Sopenharmony_ci } 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_ci return sun4i_gpadc_read(indio_dev, 0, val, info->temp_data_irq); 28562306a36Sopenharmony_ci} 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_cistatic int sun4i_gpadc_temp_offset(struct iio_dev *indio_dev, int *val) 28862306a36Sopenharmony_ci{ 28962306a36Sopenharmony_ci struct sun4i_gpadc_iio *info = iio_priv(indio_dev); 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci *val = info->data->temp_offset; 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci return 0; 29462306a36Sopenharmony_ci} 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_cistatic int sun4i_gpadc_temp_scale(struct iio_dev *indio_dev, int *val) 29762306a36Sopenharmony_ci{ 29862306a36Sopenharmony_ci struct sun4i_gpadc_iio *info = iio_priv(indio_dev); 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ci *val = info->data->temp_scale; 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci return 0; 30362306a36Sopenharmony_ci} 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_cistatic int sun4i_gpadc_read_raw(struct iio_dev *indio_dev, 30662306a36Sopenharmony_ci struct iio_chan_spec const *chan, int *val, 30762306a36Sopenharmony_ci int *val2, long mask) 30862306a36Sopenharmony_ci{ 30962306a36Sopenharmony_ci int ret; 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ci switch (mask) { 31262306a36Sopenharmony_ci case IIO_CHAN_INFO_OFFSET: 31362306a36Sopenharmony_ci ret = sun4i_gpadc_temp_offset(indio_dev, val); 31462306a36Sopenharmony_ci if (ret) 31562306a36Sopenharmony_ci return ret; 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_ci return IIO_VAL_INT; 31862306a36Sopenharmony_ci case IIO_CHAN_INFO_RAW: 31962306a36Sopenharmony_ci if (chan->type == IIO_VOLTAGE) 32062306a36Sopenharmony_ci ret = sun4i_gpadc_adc_read(indio_dev, chan->channel, 32162306a36Sopenharmony_ci val); 32262306a36Sopenharmony_ci else 32362306a36Sopenharmony_ci ret = sun4i_gpadc_temp_read(indio_dev, val); 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ci if (ret) 32662306a36Sopenharmony_ci return ret; 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_ci return IIO_VAL_INT; 32962306a36Sopenharmony_ci case IIO_CHAN_INFO_SCALE: 33062306a36Sopenharmony_ci if (chan->type == IIO_VOLTAGE) { 33162306a36Sopenharmony_ci /* 3000mV / 4096 * raw */ 33262306a36Sopenharmony_ci *val = 0; 33362306a36Sopenharmony_ci *val2 = 732421875; 33462306a36Sopenharmony_ci return IIO_VAL_INT_PLUS_NANO; 33562306a36Sopenharmony_ci } 33662306a36Sopenharmony_ci 33762306a36Sopenharmony_ci ret = sun4i_gpadc_temp_scale(indio_dev, val); 33862306a36Sopenharmony_ci if (ret) 33962306a36Sopenharmony_ci return ret; 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_ci return IIO_VAL_INT; 34262306a36Sopenharmony_ci default: 34362306a36Sopenharmony_ci return -EINVAL; 34462306a36Sopenharmony_ci } 34562306a36Sopenharmony_ci 34662306a36Sopenharmony_ci return -EINVAL; 34762306a36Sopenharmony_ci} 34862306a36Sopenharmony_ci 34962306a36Sopenharmony_cistatic const struct iio_info sun4i_gpadc_iio_info = { 35062306a36Sopenharmony_ci .read_raw = sun4i_gpadc_read_raw, 35162306a36Sopenharmony_ci}; 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_cistatic irqreturn_t sun4i_gpadc_temp_data_irq_handler(int irq, void *dev_id) 35462306a36Sopenharmony_ci{ 35562306a36Sopenharmony_ci struct sun4i_gpadc_iio *info = dev_id; 35662306a36Sopenharmony_ci 35762306a36Sopenharmony_ci if (atomic_read(&info->ignore_temp_data_irq)) 35862306a36Sopenharmony_ci goto out; 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_ci if (!regmap_read(info->regmap, SUN4I_GPADC_TEMP_DATA, &info->temp_data)) 36162306a36Sopenharmony_ci complete(&info->completion); 36262306a36Sopenharmony_ci 36362306a36Sopenharmony_ciout: 36462306a36Sopenharmony_ci return IRQ_HANDLED; 36562306a36Sopenharmony_ci} 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_cistatic irqreturn_t sun4i_gpadc_fifo_data_irq_handler(int irq, void *dev_id) 36862306a36Sopenharmony_ci{ 36962306a36Sopenharmony_ci struct sun4i_gpadc_iio *info = dev_id; 37062306a36Sopenharmony_ci 37162306a36Sopenharmony_ci if (atomic_read(&info->ignore_fifo_data_irq)) 37262306a36Sopenharmony_ci goto out; 37362306a36Sopenharmony_ci 37462306a36Sopenharmony_ci if (!regmap_read(info->regmap, SUN4I_GPADC_DATA, &info->adc_data)) 37562306a36Sopenharmony_ci complete(&info->completion); 37662306a36Sopenharmony_ci 37762306a36Sopenharmony_ciout: 37862306a36Sopenharmony_ci return IRQ_HANDLED; 37962306a36Sopenharmony_ci} 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_cistatic int sun4i_gpadc_runtime_suspend(struct device *dev) 38262306a36Sopenharmony_ci{ 38362306a36Sopenharmony_ci struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(dev)); 38462306a36Sopenharmony_ci 38562306a36Sopenharmony_ci /* Disable the ADC on IP */ 38662306a36Sopenharmony_ci regmap_write(info->regmap, SUN4I_GPADC_CTRL1, 0); 38762306a36Sopenharmony_ci /* Disable temperature sensor on IP */ 38862306a36Sopenharmony_ci regmap_write(info->regmap, SUN4I_GPADC_TPR, 0); 38962306a36Sopenharmony_ci 39062306a36Sopenharmony_ci return 0; 39162306a36Sopenharmony_ci} 39262306a36Sopenharmony_ci 39362306a36Sopenharmony_cistatic int sun4i_gpadc_runtime_resume(struct device *dev) 39462306a36Sopenharmony_ci{ 39562306a36Sopenharmony_ci struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(dev)); 39662306a36Sopenharmony_ci 39762306a36Sopenharmony_ci /* clkin = 6MHz */ 39862306a36Sopenharmony_ci regmap_write(info->regmap, SUN4I_GPADC_CTRL0, 39962306a36Sopenharmony_ci SUN4I_GPADC_CTRL0_ADC_CLK_DIVIDER(2) | 40062306a36Sopenharmony_ci SUN4I_GPADC_CTRL0_FS_DIV(7) | 40162306a36Sopenharmony_ci SUN4I_GPADC_CTRL0_T_ACQ(63)); 40262306a36Sopenharmony_ci regmap_write(info->regmap, SUN4I_GPADC_CTRL1, info->data->tp_mode_en); 40362306a36Sopenharmony_ci regmap_write(info->regmap, SUN4I_GPADC_CTRL3, 40462306a36Sopenharmony_ci SUN4I_GPADC_CTRL3_FILTER_EN | 40562306a36Sopenharmony_ci SUN4I_GPADC_CTRL3_FILTER_TYPE(1)); 40662306a36Sopenharmony_ci /* period = SUN4I_GPADC_TPR_TEMP_PERIOD * 256 * 16 / clkin; ~0.6s */ 40762306a36Sopenharmony_ci regmap_write(info->regmap, SUN4I_GPADC_TPR, 40862306a36Sopenharmony_ci SUN4I_GPADC_TPR_TEMP_ENABLE | 40962306a36Sopenharmony_ci SUN4I_GPADC_TPR_TEMP_PERIOD(800)); 41062306a36Sopenharmony_ci 41162306a36Sopenharmony_ci return 0; 41262306a36Sopenharmony_ci} 41362306a36Sopenharmony_ci 41462306a36Sopenharmony_cistatic int sun4i_gpadc_get_temp(struct thermal_zone_device *tz, int *temp) 41562306a36Sopenharmony_ci{ 41662306a36Sopenharmony_ci struct sun4i_gpadc_iio *info = thermal_zone_device_priv(tz); 41762306a36Sopenharmony_ci int val, scale, offset; 41862306a36Sopenharmony_ci 41962306a36Sopenharmony_ci if (sun4i_gpadc_temp_read(info->indio_dev, &val)) 42062306a36Sopenharmony_ci return -ETIMEDOUT; 42162306a36Sopenharmony_ci 42262306a36Sopenharmony_ci sun4i_gpadc_temp_scale(info->indio_dev, &scale); 42362306a36Sopenharmony_ci sun4i_gpadc_temp_offset(info->indio_dev, &offset); 42462306a36Sopenharmony_ci 42562306a36Sopenharmony_ci *temp = (val + offset) * scale; 42662306a36Sopenharmony_ci 42762306a36Sopenharmony_ci return 0; 42862306a36Sopenharmony_ci} 42962306a36Sopenharmony_ci 43062306a36Sopenharmony_cistatic const struct thermal_zone_device_ops sun4i_ts_tz_ops = { 43162306a36Sopenharmony_ci .get_temp = &sun4i_gpadc_get_temp, 43262306a36Sopenharmony_ci}; 43362306a36Sopenharmony_ci 43462306a36Sopenharmony_cistatic const struct dev_pm_ops sun4i_gpadc_pm_ops = { 43562306a36Sopenharmony_ci .runtime_suspend = &sun4i_gpadc_runtime_suspend, 43662306a36Sopenharmony_ci .runtime_resume = &sun4i_gpadc_runtime_resume, 43762306a36Sopenharmony_ci}; 43862306a36Sopenharmony_ci 43962306a36Sopenharmony_cistatic int sun4i_irq_init(struct platform_device *pdev, const char *name, 44062306a36Sopenharmony_ci irq_handler_t handler, const char *devname, 44162306a36Sopenharmony_ci unsigned int *irq, atomic_t *atomic) 44262306a36Sopenharmony_ci{ 44362306a36Sopenharmony_ci int ret; 44462306a36Sopenharmony_ci struct sun4i_gpadc_dev *mfd_dev = dev_get_drvdata(pdev->dev.parent); 44562306a36Sopenharmony_ci struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(&pdev->dev)); 44662306a36Sopenharmony_ci 44762306a36Sopenharmony_ci /* 44862306a36Sopenharmony_ci * Once the interrupt is activated, the IP continuously performs 44962306a36Sopenharmony_ci * conversions thus throws interrupts. The interrupt is activated right 45062306a36Sopenharmony_ci * after being requested but we want to control when these interrupts 45162306a36Sopenharmony_ci * occur thus we disable it right after being requested. However, an 45262306a36Sopenharmony_ci * interrupt might occur between these two instructions and we have to 45362306a36Sopenharmony_ci * make sure that does not happen, by using atomic flags. We set the 45462306a36Sopenharmony_ci * flag before requesting the interrupt and unset it right after 45562306a36Sopenharmony_ci * disabling the interrupt. When an interrupt occurs between these two 45662306a36Sopenharmony_ci * instructions, reading the atomic flag will tell us to ignore the 45762306a36Sopenharmony_ci * interrupt. 45862306a36Sopenharmony_ci */ 45962306a36Sopenharmony_ci atomic_set(atomic, 1); 46062306a36Sopenharmony_ci 46162306a36Sopenharmony_ci ret = platform_get_irq_byname(pdev, name); 46262306a36Sopenharmony_ci if (ret < 0) 46362306a36Sopenharmony_ci return ret; 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_ci ret = regmap_irq_get_virq(mfd_dev->regmap_irqc, ret); 46662306a36Sopenharmony_ci if (ret < 0) { 46762306a36Sopenharmony_ci dev_err(&pdev->dev, "failed to get virq for irq %s\n", name); 46862306a36Sopenharmony_ci return ret; 46962306a36Sopenharmony_ci } 47062306a36Sopenharmony_ci 47162306a36Sopenharmony_ci *irq = ret; 47262306a36Sopenharmony_ci ret = devm_request_any_context_irq(&pdev->dev, *irq, handler, 47362306a36Sopenharmony_ci IRQF_NO_AUTOEN, 47462306a36Sopenharmony_ci devname, info); 47562306a36Sopenharmony_ci if (ret < 0) { 47662306a36Sopenharmony_ci dev_err(&pdev->dev, "could not request %s interrupt: %d\n", 47762306a36Sopenharmony_ci name, ret); 47862306a36Sopenharmony_ci return ret; 47962306a36Sopenharmony_ci } 48062306a36Sopenharmony_ci 48162306a36Sopenharmony_ci atomic_set(atomic, 0); 48262306a36Sopenharmony_ci 48362306a36Sopenharmony_ci return 0; 48462306a36Sopenharmony_ci} 48562306a36Sopenharmony_ci 48662306a36Sopenharmony_cistatic const struct of_device_id sun4i_gpadc_of_id[] = { 48762306a36Sopenharmony_ci { 48862306a36Sopenharmony_ci .compatible = "allwinner,sun8i-a33-ths", 48962306a36Sopenharmony_ci .data = &sun8i_a33_gpadc_data, 49062306a36Sopenharmony_ci }, 49162306a36Sopenharmony_ci { /* sentinel */ } 49262306a36Sopenharmony_ci}; 49362306a36Sopenharmony_ci 49462306a36Sopenharmony_cistatic int sun4i_gpadc_probe_dt(struct platform_device *pdev, 49562306a36Sopenharmony_ci struct iio_dev *indio_dev) 49662306a36Sopenharmony_ci{ 49762306a36Sopenharmony_ci struct sun4i_gpadc_iio *info = iio_priv(indio_dev); 49862306a36Sopenharmony_ci void __iomem *base; 49962306a36Sopenharmony_ci int ret; 50062306a36Sopenharmony_ci 50162306a36Sopenharmony_ci info->data = of_device_get_match_data(&pdev->dev); 50262306a36Sopenharmony_ci if (!info->data) 50362306a36Sopenharmony_ci return -ENODEV; 50462306a36Sopenharmony_ci 50562306a36Sopenharmony_ci info->no_irq = true; 50662306a36Sopenharmony_ci indio_dev->num_channels = ARRAY_SIZE(sun8i_a33_gpadc_channels); 50762306a36Sopenharmony_ci indio_dev->channels = sun8i_a33_gpadc_channels; 50862306a36Sopenharmony_ci 50962306a36Sopenharmony_ci base = devm_platform_ioremap_resource(pdev, 0); 51062306a36Sopenharmony_ci if (IS_ERR(base)) 51162306a36Sopenharmony_ci return PTR_ERR(base); 51262306a36Sopenharmony_ci 51362306a36Sopenharmony_ci info->regmap = devm_regmap_init_mmio(&pdev->dev, base, 51462306a36Sopenharmony_ci &sun4i_gpadc_regmap_config); 51562306a36Sopenharmony_ci if (IS_ERR(info->regmap)) { 51662306a36Sopenharmony_ci ret = PTR_ERR(info->regmap); 51762306a36Sopenharmony_ci dev_err(&pdev->dev, "failed to init regmap: %d\n", ret); 51862306a36Sopenharmony_ci return ret; 51962306a36Sopenharmony_ci } 52062306a36Sopenharmony_ci 52162306a36Sopenharmony_ci if (IS_ENABLED(CONFIG_THERMAL_OF)) 52262306a36Sopenharmony_ci info->sensor_device = &pdev->dev; 52362306a36Sopenharmony_ci 52462306a36Sopenharmony_ci return 0; 52562306a36Sopenharmony_ci} 52662306a36Sopenharmony_ci 52762306a36Sopenharmony_cistatic int sun4i_gpadc_probe_mfd(struct platform_device *pdev, 52862306a36Sopenharmony_ci struct iio_dev *indio_dev) 52962306a36Sopenharmony_ci{ 53062306a36Sopenharmony_ci struct sun4i_gpadc_iio *info = iio_priv(indio_dev); 53162306a36Sopenharmony_ci struct sun4i_gpadc_dev *sun4i_gpadc_dev = 53262306a36Sopenharmony_ci dev_get_drvdata(pdev->dev.parent); 53362306a36Sopenharmony_ci int ret; 53462306a36Sopenharmony_ci 53562306a36Sopenharmony_ci info->no_irq = false; 53662306a36Sopenharmony_ci info->regmap = sun4i_gpadc_dev->regmap; 53762306a36Sopenharmony_ci 53862306a36Sopenharmony_ci indio_dev->num_channels = ARRAY_SIZE(sun4i_gpadc_channels); 53962306a36Sopenharmony_ci indio_dev->channels = sun4i_gpadc_channels; 54062306a36Sopenharmony_ci 54162306a36Sopenharmony_ci info->data = (struct gpadc_data *)platform_get_device_id(pdev)->driver_data; 54262306a36Sopenharmony_ci 54362306a36Sopenharmony_ci /* 54462306a36Sopenharmony_ci * Since the controller needs to be in touchscreen mode for its thermal 54562306a36Sopenharmony_ci * sensor to operate properly, and that switching between the two modes 54662306a36Sopenharmony_ci * needs a delay, always registering in the thermal framework will 54762306a36Sopenharmony_ci * significantly slow down the conversion rate of the ADCs. 54862306a36Sopenharmony_ci * 54962306a36Sopenharmony_ci * Therefore, instead of depending on THERMAL_OF in Kconfig, we only 55062306a36Sopenharmony_ci * register the sensor if that option is enabled, eventually leaving 55162306a36Sopenharmony_ci * that choice to the user. 55262306a36Sopenharmony_ci */ 55362306a36Sopenharmony_ci 55462306a36Sopenharmony_ci if (IS_ENABLED(CONFIG_THERMAL_OF)) { 55562306a36Sopenharmony_ci /* 55662306a36Sopenharmony_ci * This driver is a child of an MFD which has a node in the DT 55762306a36Sopenharmony_ci * but not its children, because of DT backward compatibility 55862306a36Sopenharmony_ci * for A10, A13 and A31 SoCs. Therefore, the resulting devices 55962306a36Sopenharmony_ci * of this driver do not have an of_node variable. 56062306a36Sopenharmony_ci * However, its parent (the MFD driver) has an of_node variable 56162306a36Sopenharmony_ci * and since devm_thermal_zone_of_sensor_register uses its first 56262306a36Sopenharmony_ci * argument to match the phandle defined in the node of the 56362306a36Sopenharmony_ci * thermal driver with the of_node of the device passed as first 56462306a36Sopenharmony_ci * argument and the third argument to call ops from 56562306a36Sopenharmony_ci * thermal_zone_of_device_ops, the solution is to use the parent 56662306a36Sopenharmony_ci * device as first argument to match the phandle with its 56762306a36Sopenharmony_ci * of_node, and the device from this driver as third argument to 56862306a36Sopenharmony_ci * return the temperature. 56962306a36Sopenharmony_ci */ 57062306a36Sopenharmony_ci info->sensor_device = pdev->dev.parent; 57162306a36Sopenharmony_ci } else { 57262306a36Sopenharmony_ci indio_dev->num_channels = 57362306a36Sopenharmony_ci ARRAY_SIZE(sun4i_gpadc_channels_no_temp); 57462306a36Sopenharmony_ci indio_dev->channels = sun4i_gpadc_channels_no_temp; 57562306a36Sopenharmony_ci } 57662306a36Sopenharmony_ci 57762306a36Sopenharmony_ci if (IS_ENABLED(CONFIG_THERMAL_OF)) { 57862306a36Sopenharmony_ci ret = sun4i_irq_init(pdev, "TEMP_DATA_PENDING", 57962306a36Sopenharmony_ci sun4i_gpadc_temp_data_irq_handler, 58062306a36Sopenharmony_ci "temp_data", &info->temp_data_irq, 58162306a36Sopenharmony_ci &info->ignore_temp_data_irq); 58262306a36Sopenharmony_ci if (ret < 0) 58362306a36Sopenharmony_ci return ret; 58462306a36Sopenharmony_ci } 58562306a36Sopenharmony_ci 58662306a36Sopenharmony_ci ret = sun4i_irq_init(pdev, "FIFO_DATA_PENDING", 58762306a36Sopenharmony_ci sun4i_gpadc_fifo_data_irq_handler, "fifo_data", 58862306a36Sopenharmony_ci &info->fifo_data_irq, &info->ignore_fifo_data_irq); 58962306a36Sopenharmony_ci if (ret < 0) 59062306a36Sopenharmony_ci return ret; 59162306a36Sopenharmony_ci 59262306a36Sopenharmony_ci if (IS_ENABLED(CONFIG_THERMAL_OF)) { 59362306a36Sopenharmony_ci ret = iio_map_array_register(indio_dev, sun4i_gpadc_hwmon_maps); 59462306a36Sopenharmony_ci if (ret < 0) { 59562306a36Sopenharmony_ci dev_err(&pdev->dev, 59662306a36Sopenharmony_ci "failed to register iio map array\n"); 59762306a36Sopenharmony_ci return ret; 59862306a36Sopenharmony_ci } 59962306a36Sopenharmony_ci } 60062306a36Sopenharmony_ci 60162306a36Sopenharmony_ci return 0; 60262306a36Sopenharmony_ci} 60362306a36Sopenharmony_ci 60462306a36Sopenharmony_cistatic int sun4i_gpadc_probe(struct platform_device *pdev) 60562306a36Sopenharmony_ci{ 60662306a36Sopenharmony_ci struct sun4i_gpadc_iio *info; 60762306a36Sopenharmony_ci struct iio_dev *indio_dev; 60862306a36Sopenharmony_ci int ret; 60962306a36Sopenharmony_ci 61062306a36Sopenharmony_ci indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info)); 61162306a36Sopenharmony_ci if (!indio_dev) 61262306a36Sopenharmony_ci return -ENOMEM; 61362306a36Sopenharmony_ci 61462306a36Sopenharmony_ci info = iio_priv(indio_dev); 61562306a36Sopenharmony_ci platform_set_drvdata(pdev, indio_dev); 61662306a36Sopenharmony_ci 61762306a36Sopenharmony_ci mutex_init(&info->mutex); 61862306a36Sopenharmony_ci info->indio_dev = indio_dev; 61962306a36Sopenharmony_ci init_completion(&info->completion); 62062306a36Sopenharmony_ci indio_dev->name = dev_name(&pdev->dev); 62162306a36Sopenharmony_ci indio_dev->info = &sun4i_gpadc_iio_info; 62262306a36Sopenharmony_ci indio_dev->modes = INDIO_DIRECT_MODE; 62362306a36Sopenharmony_ci 62462306a36Sopenharmony_ci if (pdev->dev.of_node) 62562306a36Sopenharmony_ci ret = sun4i_gpadc_probe_dt(pdev, indio_dev); 62662306a36Sopenharmony_ci else 62762306a36Sopenharmony_ci ret = sun4i_gpadc_probe_mfd(pdev, indio_dev); 62862306a36Sopenharmony_ci 62962306a36Sopenharmony_ci if (ret) 63062306a36Sopenharmony_ci return ret; 63162306a36Sopenharmony_ci 63262306a36Sopenharmony_ci pm_runtime_set_autosuspend_delay(&pdev->dev, 63362306a36Sopenharmony_ci SUN4I_GPADC_AUTOSUSPEND_DELAY); 63462306a36Sopenharmony_ci pm_runtime_use_autosuspend(&pdev->dev); 63562306a36Sopenharmony_ci pm_runtime_set_suspended(&pdev->dev); 63662306a36Sopenharmony_ci pm_runtime_enable(&pdev->dev); 63762306a36Sopenharmony_ci 63862306a36Sopenharmony_ci if (IS_ENABLED(CONFIG_THERMAL_OF)) { 63962306a36Sopenharmony_ci info->tzd = devm_thermal_of_zone_register(info->sensor_device, 64062306a36Sopenharmony_ci 0, info, 64162306a36Sopenharmony_ci &sun4i_ts_tz_ops); 64262306a36Sopenharmony_ci /* 64362306a36Sopenharmony_ci * Do not fail driver probing when failing to register in 64462306a36Sopenharmony_ci * thermal because no thermal DT node is found. 64562306a36Sopenharmony_ci */ 64662306a36Sopenharmony_ci if (IS_ERR(info->tzd) && PTR_ERR(info->tzd) != -ENODEV) { 64762306a36Sopenharmony_ci dev_err(&pdev->dev, 64862306a36Sopenharmony_ci "could not register thermal sensor: %ld\n", 64962306a36Sopenharmony_ci PTR_ERR(info->tzd)); 65062306a36Sopenharmony_ci return PTR_ERR(info->tzd); 65162306a36Sopenharmony_ci } 65262306a36Sopenharmony_ci } 65362306a36Sopenharmony_ci 65462306a36Sopenharmony_ci ret = devm_iio_device_register(&pdev->dev, indio_dev); 65562306a36Sopenharmony_ci if (ret < 0) { 65662306a36Sopenharmony_ci dev_err(&pdev->dev, "could not register the device\n"); 65762306a36Sopenharmony_ci goto err_map; 65862306a36Sopenharmony_ci } 65962306a36Sopenharmony_ci 66062306a36Sopenharmony_ci return 0; 66162306a36Sopenharmony_ci 66262306a36Sopenharmony_cierr_map: 66362306a36Sopenharmony_ci if (!info->no_irq && IS_ENABLED(CONFIG_THERMAL_OF)) 66462306a36Sopenharmony_ci iio_map_array_unregister(indio_dev); 66562306a36Sopenharmony_ci 66662306a36Sopenharmony_ci pm_runtime_put(&pdev->dev); 66762306a36Sopenharmony_ci pm_runtime_disable(&pdev->dev); 66862306a36Sopenharmony_ci 66962306a36Sopenharmony_ci return ret; 67062306a36Sopenharmony_ci} 67162306a36Sopenharmony_ci 67262306a36Sopenharmony_cistatic int sun4i_gpadc_remove(struct platform_device *pdev) 67362306a36Sopenharmony_ci{ 67462306a36Sopenharmony_ci struct iio_dev *indio_dev = platform_get_drvdata(pdev); 67562306a36Sopenharmony_ci struct sun4i_gpadc_iio *info = iio_priv(indio_dev); 67662306a36Sopenharmony_ci 67762306a36Sopenharmony_ci pm_runtime_put(&pdev->dev); 67862306a36Sopenharmony_ci pm_runtime_disable(&pdev->dev); 67962306a36Sopenharmony_ci 68062306a36Sopenharmony_ci if (!IS_ENABLED(CONFIG_THERMAL_OF)) 68162306a36Sopenharmony_ci return 0; 68262306a36Sopenharmony_ci 68362306a36Sopenharmony_ci if (!info->no_irq) 68462306a36Sopenharmony_ci iio_map_array_unregister(indio_dev); 68562306a36Sopenharmony_ci 68662306a36Sopenharmony_ci return 0; 68762306a36Sopenharmony_ci} 68862306a36Sopenharmony_ci 68962306a36Sopenharmony_cistatic const struct platform_device_id sun4i_gpadc_id[] = { 69062306a36Sopenharmony_ci { "sun4i-a10-gpadc-iio", (kernel_ulong_t)&sun4i_gpadc_data }, 69162306a36Sopenharmony_ci { "sun5i-a13-gpadc-iio", (kernel_ulong_t)&sun5i_gpadc_data }, 69262306a36Sopenharmony_ci { "sun6i-a31-gpadc-iio", (kernel_ulong_t)&sun6i_gpadc_data }, 69362306a36Sopenharmony_ci { /* sentinel */ }, 69462306a36Sopenharmony_ci}; 69562306a36Sopenharmony_ciMODULE_DEVICE_TABLE(platform, sun4i_gpadc_id); 69662306a36Sopenharmony_ci 69762306a36Sopenharmony_cistatic struct platform_driver sun4i_gpadc_driver = { 69862306a36Sopenharmony_ci .driver = { 69962306a36Sopenharmony_ci .name = "sun4i-gpadc-iio", 70062306a36Sopenharmony_ci .of_match_table = sun4i_gpadc_of_id, 70162306a36Sopenharmony_ci .pm = &sun4i_gpadc_pm_ops, 70262306a36Sopenharmony_ci }, 70362306a36Sopenharmony_ci .id_table = sun4i_gpadc_id, 70462306a36Sopenharmony_ci .probe = sun4i_gpadc_probe, 70562306a36Sopenharmony_ci .remove = sun4i_gpadc_remove, 70662306a36Sopenharmony_ci}; 70762306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, sun4i_gpadc_of_id); 70862306a36Sopenharmony_ci 70962306a36Sopenharmony_cimodule_platform_driver(sun4i_gpadc_driver); 71062306a36Sopenharmony_ci 71162306a36Sopenharmony_ciMODULE_DESCRIPTION("ADC driver for sunxi platforms"); 71262306a36Sopenharmony_ciMODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>"); 71362306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 714