// SPDX-License-Identifier: GPL-2.0 /* ADC driver for sunxi platforms' (A10, A13 and A31) GPADC * * Copyright (c) 2016 Quentin Schulz <quentin.schulz@free-electrons.com> * * The Allwinner SoCs all have an ADC that can also act as a touchscreen * controller and a thermal sensor. * The thermal sensor works only when the ADC acts as a touchscreen controller * and is configured to throw an interrupt every fixed periods of time (let say * every X seconds). * One would be tempted to disable the IP on the hardware side rather than * disabling interrupts to save some power but that resets the internal clock of * the IP, resulting in having to wait X seconds every time we want to read the * value of the thermal sensor. * This is also the reason of using autosuspend in pm_runtime. If there was no * autosuspend, the thermal sensor would need X seconds after every * pm_runtime_get_sync to get a value from the ADC. The autosuspend allows the * thermal sensor to be requested again in a certain time span before it gets * shutdown for not being used.
*/
staticint sun4i_prepare_for_irq(struct iio_dev *indio_dev, int channel, unsignedint irq)
{ struct sun4i_gpadc_iio *info = iio_priv(indio_dev); int ret;
u32 reg;
pm_runtime_get_sync(indio_dev->dev.parent);
reinit_completion(&info->completion);
ret = regmap_write(info->regmap, SUN4I_GPADC_INT_FIFOC,
SUN4I_GPADC_INT_FIFOC_TP_FIFO_TRIG_LEVEL(1) |
SUN4I_GPADC_INT_FIFOC_TP_FIFO_FLUSH); if (ret) return ret;
ret = regmap_read(info->regmap, SUN4I_GPADC_CTRL1, ®); if (ret) return ret;
if (irq == info->fifo_data_irq) {
ret = regmap_write(info->regmap, SUN4I_GPADC_CTRL1,
info->data->tp_mode_en |
info->data->tp_adc_select |
info->data->adc_chan_select(channel)); /* * When the IP changes channel, it needs a bit of time to get * correct values.
*/ if ((reg & info->data->adc_chan_mask) !=
info->data->adc_chan_select(channel))
mdelay(10);
} else { /* * The temperature sensor returns valid data only when the ADC * operates in touchscreen mode.
*/
ret = regmap_write(info->regmap, SUN4I_GPADC_CTRL1,
info->data->tp_mode_en);
}
if (ret) return ret;
/* * When the IP changes mode between ADC or touchscreen, it * needs a bit of time to get correct values.
*/ if ((reg & info->data->tp_adc_select) != info->data->tp_adc_select)
mdelay(100);
return 0;
}
staticint sun4i_gpadc_read(struct iio_dev *indio_dev, int channel, int *val, unsignedint irq)
{ struct sun4i_gpadc_iio *info = iio_priv(indio_dev); int ret;
mutex_lock(&info->mutex);
ret = sun4i_prepare_for_irq(indio_dev, channel, irq); if (ret) goto err;
enable_irq(irq);
/* * The temperature sensor throws an interruption periodically (currently * set at periods of ~0.6s in sun4i_gpadc_runtime_resume). A 1s delay * makes sure an interruption occurs in normal conditions. If it doesn't * occur, then there is a timeout.
*/ if (!wait_for_completion_timeout(&info->completion,
msecs_to_jiffies(1000))) {
ret = -ETIMEDOUT; goto err;
}
staticint sun4i_gpadc_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask)
{ int ret;
switch (mask) { case IIO_CHAN_INFO_OFFSET:
ret = sun4i_gpadc_temp_offset(indio_dev, val); if (ret) return ret;
return IIO_VAL_INT; case IIO_CHAN_INFO_RAW: if (chan->type == IIO_VOLTAGE)
ret = sun4i_gpadc_adc_read(indio_dev, chan->channel,
val); else
ret = sun4i_gpadc_temp_read(indio_dev, val);
if (ret) return ret;
return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: if (chan->type == IIO_VOLTAGE) { /* 3000mV / 4096 * raw */
*val = 0;
*val2 = 732421875; return IIO_VAL_INT_PLUS_NANO;
}
ret = sun4i_gpadc_temp_scale(indio_dev, val); if (ret) return ret;
/* Disable the ADC on IP */
regmap_write(info->regmap, SUN4I_GPADC_CTRL1, 0); /* Disable temperature sensor on IP */
regmap_write(info->regmap, SUN4I_GPADC_TPR, 0);
/* * Once the interrupt is activated, the IP continuously performs * conversions thus throws interrupts. The interrupt is activated right * after being requested but we want to control when these interrupts * occur thus we disable it right after being requested. However, an * interrupt might occur between these two instructions and we have to * make sure that does not happen, by using atomic flags. We set the * flag before requesting the interrupt and unset it right after * disabling the interrupt. When an interrupt occurs between these two * instructions, reading the atomic flag will tell us to ignore the * interrupt.
*/
atomic_set(atomic, 1);
ret = platform_get_irq_byname(pdev, name); if (ret < 0) return ret;
ret = regmap_irq_get_virq(mfd_dev->regmap_irqc, ret); if (ret < 0) {
dev_err(&pdev->dev, "failed to get virq for irq %s\n", name); return ret;
}
*irq = ret;
ret = devm_request_any_context_irq(&pdev->dev, *irq, handler,
IRQF_NO_AUTOEN,
devname, info); if (ret < 0) {
dev_err(&pdev->dev, "could not request %s interrupt: %d\n",
name, ret); return ret;
}
/* * Since the controller needs to be in touchscreen mode for its thermal * sensor to operate properly, and that switching between the two modes * needs a delay, always registering in the thermal framework will * significantly slow down the conversion rate of the ADCs. * * Therefore, instead of depending on THERMAL_OF in Kconfig, we only * register the sensor if that option is enabled, eventually leaving * that choice to the user.
*/
if (IS_ENABLED(CONFIG_THERMAL_OF)) { /* * This driver is a child of an MFD which has a node in the DT * but not its children, because of DT backward compatibility * for A10, A13 and A31 SoCs. Therefore, the resulting devices * of this driver do not have an of_node variable. * However, its parent (the MFD driver) has an of_node variable * and since devm_thermal_zone_of_sensor_register uses its first * argument to match the phandle defined in the node of the * thermal driver with the of_node of the device passed as first * argument and the third argument to call ops from * thermal_zone_of_device_ops, the solution is to use the parent * device as first argument to match the phandle with its * of_node, and the device from this driver as third argument to * return the temperature.
*/
info->sensor_device = pdev->dev.parent;
} else {
indio_dev->num_channels =
ARRAY_SIZE(sun4i_gpadc_channels_no_temp);
indio_dev->channels = sun4i_gpadc_channels_no_temp;
}
if (IS_ENABLED(CONFIG_THERMAL_OF)) {
ret = sun4i_irq_init(pdev, "TEMP_DATA_PENDING",
sun4i_gpadc_temp_data_irq_handler, "temp_data", &info->temp_data_irq,
&info->ignore_temp_data_irq); if (ret < 0) return ret;
}
ret = sun4i_irq_init(pdev, "FIFO_DATA_PENDING",
sun4i_gpadc_fifo_data_irq_handler, "fifo_data",
&info->fifo_data_irq, &info->ignore_fifo_data_irq); if (ret < 0) return ret;
if (IS_ENABLED(CONFIG_THERMAL_OF)) {
ret = iio_map_array_register(indio_dev, sun4i_gpadc_hwmon_maps); if (ret < 0) {
dev_err(&pdev->dev, "failed to register iio map array\n"); return ret;
}
}
if (IS_ENABLED(CONFIG_THERMAL_OF)) {
info->tzd = devm_thermal_of_zone_register(info->sensor_device,
0, info,
&sun4i_ts_tz_ops); /* * Do not fail driver probing when failing to register in * thermal because no thermal DT node is found.
*/ if (IS_ERR(info->tzd) && PTR_ERR(info->tzd) != -ENODEV) {
dev_err(&pdev->dev, "could not register thermal sensor: %ld\n",
PTR_ERR(info->tzd)); return PTR_ERR(info->tzd);
}
}
ret = devm_iio_device_register(&pdev->dev, indio_dev); if (ret < 0) {
dev_err(&pdev->dev, "could not register the device\n"); goto err_map;
}
return 0;
err_map: if (!info->no_irq && IS_ENABLED(CONFIG_THERMAL_OF))
iio_map_array_unregister(indio_dev);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.