* [PATCH RFC] mmc: mxs-mmc: Implement CMD23 support
From: Fabio Estevam @ 2017-01-14 17:40 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1484414958-2665-1-git-send-email-stefan.wahren@i2se.com>
Hi Stefan,
On Sat, Jan 14, 2017 at 3:29 PM, Stefan Wahren <stefan.wahren@i2se.com> wrote:
> This patch implements support for multiblock transfers bounded
> by SET_BLOCK_COUNT (CMD23) on the MXS MMC host driver.
>
> Signed-off-by: Stefan Wahren <stefan.wahren@i2se.com>
Just curious: what is the throughput gain you notice with this patch?
Thanks
^ permalink raw reply
* [PATCH 3/4] iio: adc: add a driver for the SAR ADC found in Amlogic Meson SoCs
From: Martin Blumenstingl @ 2017-01-14 17:44 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <870f8899-b3a1-153a-5953-88ac23ff6942@kernel.org>
Hi Jonathan,
thank you for the review!
(further comments from me inline)
I think I'll send an updated version on Monday.
On Sat, Jan 14, 2017 at 3:46 PM, Jonathan Cameron <jic23@kernel.org> wrote:
> On 11/01/17 17:43, Martin Blumenstingl wrote:
>> This adds support for the SAR (Successive Approximation Register) ADC
>> on the Amlogic Meson SoCs.
>>
>> The code is based on the public S805 (Meson8b) and S905 (GXBB)
>> datasheets, as well as by reading (various versions of) the vendor
>> driver and by inspecting the registers on the vendor kernels of my
>> testing-hardware.
>>
>> Currently the GXBB, GXL and GXM SoCs are supported. GXBB hardware has
>> 10-bit ADC resolution, while GXL and GXM have 12-bit ADC resolution.
>> The code was written to support older SoCs (Meson8 and Meson8b) as well,
>> but due to lack of actual testing-hardware no of_device_id was added for
>> these.
>>
>> Two "features" from the vendor driver are currently missing:
>> - the vendor driver uses channel #7 for calibration (this improves the
>> accuracy of the results - in my tests the results were less than 3%
>> off without calibration compared to the vendor driver). Adding support
>> for this should be easy, but is not required for most applications.
>> - channel #6 is connected to the SoCs internal temperature sensor.
>> Adding support for this is probably not so easy since (based on the
>> u-boot sources) most SoC versions are using different registers and
>> algorithms for the conversion from "ADC value" to temperature.
>>
>> Supported by the hardware but currently not supported by the driver:
>> - reading multiple channels at the same time (the hardware has a FIFO
>> buffer which stores multiple results)
>> - continuous sampling (this would require a way to enable this
>> individually because otherwise the ADC would be drawing power
>> constantly)
>> - interrupt support (similar to the vendor driver this new driver is
>> polling the results. It is unclear if the IRQ-mode is supported on
>> older (Meson6 or Meson8) hardware as well or if there are any errata)
>>
> Russell Cc'd for a quick question on the clk api.
a quick side-note the clk API: my driver is a clock consumer and
provider at the same time. This seems to be a recurring pattern in
Amlogic hardware designs (as the MMC and DWMAC glue drivers are doing
this also), see [0]
> Ideally include a source for datasheets if available. Saves time googling and
> perhaps getting the wrong thing!
OK, will do this in v2
> A few other minor comments inline. Pretty good V1.
thanks :-)
> Jonathan
>> Signed-off-by: Martin Blumenstingl <martin.blumenstingl@googlemail.com>
>> ---
>> drivers/iio/adc/Kconfig | 12 +
>> drivers/iio/adc/Makefile | 1 +
>> drivers/iio/adc/meson_saradc.c | 860 +++++++++++++++++++++++++++++++++++++++++
>> 3 files changed, 873 insertions(+)
>> create mode 100644 drivers/iio/adc/meson_saradc.c
>>
>> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
>> index 9c8b558ba19e..86059b9b91bf 100644
>> --- a/drivers/iio/adc/Kconfig
>> +++ b/drivers/iio/adc/Kconfig
>> @@ -371,6 +371,18 @@ config MEN_Z188_ADC
>> This driver can also be built as a module. If so, the module will be
>> called men_z188_adc.
>>
>> +config MESON_SARADC
>> + tristate "Amlogic Meson SAR ADC driver"
>> + default ARCH_MESON
>> + depends on OF && COMMON_CLK && (ARCH_MESON || COMPILE_TEST)
>> + select REGMAP_MMIO
>> + help
>> + Say yes here to build support for the SAR ADC found in Amlogic Meson
>> + SoCs.
>> +
>> + To compile this driver as a module, choose M here: the
>> + module will be called meson_saradc.
>> +
>> config MXS_LRADC
>> tristate "Freescale i.MX23/i.MX28 LRADC"
>> depends on (ARCH_MXS || COMPILE_TEST) && HAS_IOMEM
>> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
>> index d36c4be8d1fc..de05b9e75f8f 100644
>> --- a/drivers/iio/adc/Makefile
>> +++ b/drivers/iio/adc/Makefile
>> @@ -36,6 +36,7 @@ obj-$(CONFIG_MCP320X) += mcp320x.o
>> obj-$(CONFIG_MCP3422) += mcp3422.o
>> obj-$(CONFIG_MEDIATEK_MT6577_AUXADC) += mt6577_auxadc.o
>> obj-$(CONFIG_MEN_Z188_ADC) += men_z188_adc.o
>> +obj-$(CONFIG_MESON_SARADC) += meson_saradc.o
>> obj-$(CONFIG_MXS_LRADC) += mxs-lradc.o
>> obj-$(CONFIG_NAU7802) += nau7802.o
>> obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
>> diff --git a/drivers/iio/adc/meson_saradc.c b/drivers/iio/adc/meson_saradc.c
>> new file mode 100644
>> index 000000000000..06e8ac620385
>> --- /dev/null
>> +++ b/drivers/iio/adc/meson_saradc.c
>> @@ -0,0 +1,860 @@
>> +/*
>> + * Amlogic Meson Successive Approximation Register (SAR) A/D Converter
>> + *
>> + * Copyright (C) 2017 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License version 2 as
>> + * published by the Free Software Foundation.
>> + *
>> + * You should have received a copy of the GNU General Public License
>> + * along with this program. If not, see <http://www.gnu.org/licenses/>.
>> + */
>> +
>> +#include <linux/bitfield.h>
>> +#include <linux/clk-provider.h>
>> +#include <linux/module.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/io.h>
>> +#include <linux/iio/iio.h>
>> +#include <linux/of.h>
>> +#include <linux/of_device.h>
>> +#include <linux/clk.h>
>> +#include <linux/completion.h>
>> +#include <linux/delay.h>
>> +#include <linux/reset.h>
>> +#include <linux/regmap.h>
>> +#include <linux/regulator/consumer.h>
>> +
>> +#define SAR_ADC_REG0 0x00
>> + #define SAR_ADC_REG0_PANEL_DETECT BIT(31)
>> + #define SAR_ADC_REG0_BUSY_MASK GENMASK(30, 28)
>> + #define SAR_ADC_REG0_DELTA_BUSY BIT(30)
>> + #define SAR_ADC_REG0_AVG_BUSY BIT(29)
>> + #define SAR_ADC_REG0_SAMPLE_BUSY BIT(28)
>> + #define SAR_ADC_REG0_FIFO_FULL BIT(27)
>> + #define SAR_ADC_REG0_FIFO_EMPTY BIT(26)
>> + #define SAR_ADC_REG0_FIFO_COUNT_MASK GENMASK(25, 21)
>> + #define SAR_ADC_REG0_ADC_BIAS_CTRL_MASK GENMASK(20, 19)
>> + #define SAR_ADC_REG0_CURR_CHAN_ID_MASK GENMASK(18, 16)
>> + #define SAR_ADC_REG0_ADC_TEMP_SEN_SEL BIT(15)
>> + #define SAR_ADC_REG0_SAMPLING_STOP BIT(14)
>> + #define SAR_ADC_REG0_CHAN_DELTA_EN_MASK GENMASK(13, 12)
>> + #define SAR_ADC_REG0_DETECT_IRQ_POL BIT(10)
>> + #define SAR_ADC_REG0_DETECT_IRQ_EN BIT(9)
>> + #define SAR_ADC_REG0_FIFO_CNT_IRQ_MASK GENMASK(8, 4)
>> + #define SAR_ADC_REG0_FIFO_IRQ_EN BIT(3)
>> + #define SAR_ADC_REG0_SAMPLING_START BIT(2)
>> + #define SAR_ADC_REG0_CONTINUOUS_EN BIT(1)
>> + #define SAR_ADC_REG0_SAMPLE_ENGINE_ENABLE BIT(0)
>> +
>> +#define SAR_ADC_CHAN_LIST 0x04
>> + #define SAR_ADC_CHAN_LIST_MAX_INDEX_MASK GENMASK(26, 24)
>> + #define SAR_ADC_CHAN_CHAN_ENTRY_MASK(_chan) \
>> + (GENMASK(2, 0) << (_chan * 3))
>> +
>> +#define SAR_ADC_AVG_CNTL 0x08
>> + #define SAR_ADC_AVG_CNTL_AVG_MODE_SHIFT(_chan) \
>> + (16 + (_chan * 2))
>> + #define SAR_ADC_AVG_CNTL_AVG_MODE_MASK(_chan) \
>> + (GENMASK(17, 16) << (_chan * 2))
>> + #define SAR_ADC_AVG_CNTL_NUM_SAMPLES_SHIFT(_chan) \
>> + (0 + (_chan * 2))
>> + #define SAR_ADC_AVG_CNTL_NUM_SAMPLES_MASK(_chan) \
>> + (GENMASK(1, 0) << (_chan * 2))
>> +
>> +#define SAR_ADC_REG3 0x0c
>> + #define SAR_ADC_REG3_CNTL_USE_SC_DLY BIT(31)
>> + #define SAR_ADC_REG3_CLK_EN BIT(30)
>> + #define SAR_ADC_REG3_BL30_INITIALIZED BIT(28)
>> + #define SAR_ADC_REG3_CTRL_CONT_RING_COUNTER_EN BIT(27)
>> + #define SAR_ADC_REG3_CTRL_SAMPLING_CLOCK_PHASE BIT(26)
>> + #define SAR_ADC_REG3_CTRL_CHAN7_MUX_SEL_MASK GENMASK(25, 23)
>> + #define SAR_ADC_REG3_DETECT_EN BIT(22)
>> + #define SAR_ADC_REG3_ADC_EN BIT(21)
>> + #define SAR_ADC_REG3_PANEL_DETECT_COUNT_MASK GENMASK(20, 18)
>> + #define SAR_ADC_REG3_PANEL_DETECT_FILTER_TB_MASK GENMASK(17, 16)
>> + #define SAR_ADC_REG3_ADC_CLK_DIV_SHIFT 10
>> + #define SAR_ADC_REG3_ADC_CLK_DIV_WIDTH 5
>> + #define SAR_ADC_REG3_ADC_CLK_DIV_MASK GENMASK(15, 10)
>> + #define SAR_ADC_REG3_BLOCK_DLY_SEL_MASK GENMASK(9, 8)
>> + #define SAR_ADC_REG3_BLOCK_DLY_MASK GENMASK(7, 0)
>> +
>> +#define SAR_ADC_DELAY 0x10
>> + #define SAR_ADC_DELAY_INPUT_DLY_SEL_MASK GENMASK(25, 24)
>> + #define SAR_ADC_DELAY_BL30_BUSY BIT(15)
>> + #define SAR_ADC_DELAY_KERNEL_BUSY BIT(14)
>> + #define SAR_ADC_DELAY_INPUT_DLY_CNT_MASK GENMASK(23, 16)
>> + #define SAR_ADC_DELAY_SAMPLE_DLY_SEL_MASK GENMASK(9, 8)
>> + #define SAR_ADC_DELAY_SAMPLE_DLY_CNT_MASK GENMASK(7, 0)
>> +
>> +#define SAR_ADC_LAST_RD 0x14
>> + #define SAR_ADC_LAST_RD_LAST_CHANNEL1_MASK GENMASK(23, 16)
>> + #define SAR_ADC_LAST_RD_LAST_CHANNEL0_MASK GENMASK(9, 0)
>> +
>> +#define SAR_ADC_FIFO_RD 0x18
>> + #define SAR_ADC_FIFO_RD_CHAN_ID_MASK GENMASK(14, 12)
>> + #define SAR_ADC_FIFO_RD_SAMPLE_VALUE_MASK GENMASK(11, 0)
>> +
>> +#define SAR_ADC_AUX_SW 0x1c
>> + #define SAR_ADC_AUX_SW_MUX_SEL_CHAN_MASK(_chan) \
>> + (GENMASK(10, 8) << ((_chan - 2) * 2))
>> + #define SAR_ADC_AUX_SW_VREF_P_MUX BIT(6)
>> + #define SAR_ADC_AUX_SW_VREF_N_MUX BIT(5)
>> + #define SAR_ADC_AUX_SW_MODE_SEL BIT(4)
>> + #define SAR_ADC_AUX_SW_YP_DRIVE_SW BIT(3)
>> + #define SAR_ADC_AUX_SW_XP_DRIVE_SW BIT(2)
>> + #define SAR_ADC_AUX_SW_YM_DRIVE_SW BIT(1)
>> + #define SAR_ADC_AUX_SW_XM_DRIVE_SW BIT(0)
>> +
>> +#define SAR_ADC_CHAN_10_SW 0x20
>> + #define SAR_ADC_CHAN_10_SW_CHAN1_MUX_SEL_MASK GENMASK(25, 23)
>> + #define SAR_ADC_CHAN_10_SW_CHAN1_VREF_P_MUX BIT(22)
>> + #define SAR_ADC_CHAN_10_SW_CHAN1_VREF_N_MUX BIT(21)
>> + #define SAR_ADC_CHAN_10_SW_CHAN1_MODE_SEL BIT(20)
>> + #define SAR_ADC_CHAN_10_SW_CHAN1_YP_DRIVE_SW BIT(19)
>> + #define SAR_ADC_CHAN_10_SW_CHAN1_XP_DRIVE_SW BIT(18)
>> + #define SAR_ADC_CHAN_10_SW_CHAN1_YM_DRIVE_SW BIT(17)
>> + #define SAR_ADC_CHAN_10_SW_CHAN1_XM_DRIVE_SW BIT(16)
>> + #define SAR_ADC_CHAN_10_SW_CHAN0_MUX_SEL_MASK GENMASK(9, 7)
>> + #define SAR_ADC_CHAN_10_SW_CHAN0_VREF_P_MUX BIT(6)
>> + #define SAR_ADC_CHAN_10_SW_CHAN0_VREF_N_MUX BIT(5)
>> + #define SAR_ADC_CHAN_10_SW_CHAN0_MODE_SEL BIT(4)
>> + #define SAR_ADC_CHAN_10_SW_CHAN0_YP_DRIVE_SW BIT(3)
>> + #define SAR_ADC_CHAN_10_SW_CHAN0_XP_DRIVE_SW BIT(2)
>> + #define SAR_ADC_CHAN_10_SW_CHAN0_YM_DRIVE_SW BIT(1)
>> + #define SAR_ADC_CHAN_10_SW_CHAN0_XM_DRIVE_SW BIT(0)
>> +
>> +#define SAR_ADC_DETECT_IDLE_SW 0x24
>> + #define SAR_ADC_DETECT_IDLE_SW_DETECT_SW_EN BIT(26)
>> + #define SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_MUX_MASK GENMASK(25, 23)
>> + #define SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_VREF_P_MUX BIT(22)
>> + #define SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_VREF_N_MUX BIT(21)
>> + #define SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_SEL BIT(20)
>> + #define SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_YP_DRIVE_SW BIT(19)
>> + #define SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_XP_DRIVE_SW BIT(18)
>> + #define SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_YM_DRIVE_SW BIT(17)
>> + #define SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_XM_DRIVE_SW BIT(16)
>> + #define SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_MUX_SEL_MASK GENMASK(9, 7)
>> + #define SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_VREF_P_MUX BIT(6)
>> + #define SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_VREF_N_MUX BIT(5)
>> + #define SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_SEL BIT(4)
>> + #define SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_YP_DRIVE_SW BIT(3)
>> + #define SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_XP_DRIVE_SW BIT(2)
>> + #define SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_YM_DRIVE_SW BIT(1)
>> + #define SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_XM_DRIVE_SW BIT(0)
>> +
>> +#define SAR_ADC_DELTA_10 0x28
>> + #define SAR_ADC_DELTA_10_TEMP_SEL BIT(27)
>> + #define SAR_ADC_DELTA_10_TS_REVE1 BIT(26)
>> + #define SAR_ADC_DELTA_10_CHAN1_DELTA_VALUE_SHIFT 16
>> + #define SAR_ADC_DELTA_10_CHAN1_DELTA_VALUE_MASK GENMASK(25, 16)
>> + #define SAR_ADC_DELTA_10_TS_REVE0 BIT(15)
>> + #define SAR_ADC_DELTA_10_TS_C_SHIFT 11
>> + #define SAR_ADC_DELTA_10_TS_C_MASK GENMASK(14, 11)
>> + #define SAR_ADC_DELTA_10_TS_VBG_EN BIT(10)
>> + #define SAR_ADC_DELTA_10_CHAN0_DELTA_VALUE_SHIFT 0
>> + #define 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 SAR_ADC_REG11 0x2c
>> + #define SAR_ADC_REG11_BANDGAP_EN BIT(13)
>> +
>> +#define SAR_ADC_REG13 0x34
>> + #define SAR_ADC_REG13_12BIT_CALIBRATION_MASK GENMASK(13, 8)
>> +
>> +#define SAR_ADC_MAX_FIFO_SIZE 32
>> +#define SAR_ADC_NUM_CHANNELS ARRAY_SIZE(meson_saradc_iio_channels)
>> +#define SAR_ADC_VALUE_MASK(_priv) (BIT(_priv->resolution) - 1)
>> +
>> +#define MESON_SAR_ADC_CHAN(_chan, _type) { \
>> + .type = _type, \
>> + .indexed = true, \
>> + .channel = _chan, \
>> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
>> + BIT(IIO_CHAN_INFO_AVERAGE_RAW), \
>> + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
>> + .datasheet_name = "SAR_ADC_CH"#_chan, \
>> +}
>> +
>> +/* TODO: the hardware supports IIO_TEMP for channel 6 as well which is
> Multline comment syntax.
I got this wrong in 2 other places as well. will be fixed in v2, thanks!
>> + * currently not supported by this driver.
>> + */
>> +static const struct iio_chan_spec meson_saradc_iio_channels[] = {
>> + MESON_SAR_ADC_CHAN(0, IIO_VOLTAGE),
>> + MESON_SAR_ADC_CHAN(1, IIO_VOLTAGE),
>> + MESON_SAR_ADC_CHAN(2, IIO_VOLTAGE),
>> + MESON_SAR_ADC_CHAN(3, IIO_VOLTAGE),
>> + MESON_SAR_ADC_CHAN(4, IIO_VOLTAGE),
>> + MESON_SAR_ADC_CHAN(5, IIO_VOLTAGE),
>> + MESON_SAR_ADC_CHAN(6, IIO_VOLTAGE),
>> + MESON_SAR_ADC_CHAN(7, IIO_VOLTAGE),
>> + IIO_CHAN_SOFT_TIMESTAMP(8),
>> +};
>> +
>> +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,
>> +};
>> +
>> +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,
>> +};
>> +
>> +struct meson_saradc_priv {
>> + struct regmap *regmap;
>> + struct clk *clkin;
>> + struct clk *core_clk;
>> + struct clk *sana_clk;
>> + struct clk *adc_sel_clk;
>> + struct clk *adc_clk;
>> + struct clk_gate clk_gate;
>> + struct clk *adc_div_clk;
>> + struct clk_divider clk_div;
>> + struct regulator *vref;
>> + struct completion completion;
>> + u8 resolution;
>> +};
>> +
>> +static const struct regmap_config meson_saradc_regmap_config = {
>> + .reg_bits = 8,
>> + .val_bits = 32,
>> + .reg_stride = 4,
>> + .max_register = SAR_ADC_REG13,
>> +};
>> +
>> +static unsigned int meson_saradc_get_fifo_count(struct iio_dev *indio_dev)
>> +{
>> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
>> + u32 regval;
>> +
>> + regmap_read(priv->regmap, SAR_ADC_REG0, ®val);
>> +
>> + return FIELD_GET(SAR_ADC_REG0_FIFO_COUNT_MASK, regval);
>> +}
>> +
>> +static int meson_saradc_wait_busy_clear(struct iio_dev *indio_dev)
>> +{
>> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
>> + int regval, timeout = 10000;
>> +
>> + do {
>> + udelay(1);
>> + regmap_read(priv->regmap, SAR_ADC_REG0, ®val);
>> + } while (FIELD_GET(SAR_ADC_REG0_BUSY_MASK, regval) && timeout--);
>> +
>> + if (timeout < 0)
>> + return -ETIMEDOUT;
>> +
>> + return 0;
>> +}
>> +
>> +static int meson_saradc_read_raw_sample(struct iio_dev *indio_dev,
>> + const struct iio_chan_spec *chan,
>> + int *val)
>> +{
>> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
>> + int ret, regval, fifo_chan, fifo_val, sum = 0, count = 0;
>> +
>> + ret = meson_saradc_wait_busy_clear(indio_dev);
>> + if (ret)
>> + return ret;
>> +
>> + regmap_read(priv->regmap, SAR_ADC_REG0, ®val);
>> +
>> + while (meson_saradc_get_fifo_count(indio_dev) > 0 &&
>> + count < SAR_ADC_MAX_FIFO_SIZE) {
>> + regmap_read(priv->regmap, SAR_ADC_FIFO_RD, ®val);
>> +
>> + fifo_chan = FIELD_GET(SAR_ADC_FIFO_RD_CHAN_ID_MASK, regval);
>> + if (fifo_chan == chan->channel) {
>> + fifo_val = FIELD_GET(SAR_ADC_FIFO_RD_SAMPLE_VALUE_MASK,
>> + regval) & SAR_ADC_VALUE_MASK(priv);
>> + sum += fifo_val;
>> + count++;
>> + }
>> + }
>> +
>> + if (!count)
>> + return -ENOENT;
>> +
>> + *val = sum / count;
>> +
>> + return 0;
>> +}
>> +
>> +static void meson_saradc_set_averaging(struct iio_dev *indio_dev,
>> + const struct iio_chan_spec *chan,
>> + enum meson_saradc_avg_mode mode,
>> + enum meson_saradc_num_samples samples)
>> +{
>> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
>> + u32 val;
>> +
>> + val = samples << SAR_ADC_AVG_CNTL_NUM_SAMPLES_SHIFT(chan->channel);
>> + regmap_update_bits(priv->regmap, SAR_ADC_AVG_CNTL,
>> + SAR_ADC_AVG_CNTL_NUM_SAMPLES_MASK(chan->channel),
>> + val);
>> +
>> + val = mode << SAR_ADC_AVG_CNTL_AVG_MODE_SHIFT(chan->channel);
>> + regmap_update_bits(priv->regmap, SAR_ADC_AVG_CNTL,
>> + SAR_ADC_AVG_CNTL_AVG_MODE_MASK(chan->channel), val);
>> +}
>> +
>> +static void meson_saradc_enable_channel(struct iio_dev *indio_dev,
>> + const struct iio_chan_spec *chan)
>> +{
>> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
>> + u32 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(SAR_ADC_CHAN_LIST_MAX_INDEX_MASK, 0);
>> + regmap_update_bits(priv->regmap, SAR_ADC_CHAN_LIST,
>> + SAR_ADC_CHAN_LIST_MAX_INDEX_MASK, regval);
>> +
>> + /* map channel index 0 to the channel which we want to read */
>> + regval = FIELD_PREP(SAR_ADC_CHAN_CHAN_ENTRY_MASK(0), chan->channel);
>> + regmap_update_bits(priv->regmap, SAR_ADC_CHAN_LIST,
>> + SAR_ADC_CHAN_CHAN_ENTRY_MASK(0), regval);
>> +
>> + regval = FIELD_PREP(SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_MUX_MASK,
>> + chan->channel);
>> + regmap_update_bits(priv->regmap, SAR_ADC_DETECT_IDLE_SW,
>> + SAR_ADC_DETECT_IDLE_SW_DETECT_MODE_MUX_MASK,
>> + regval);
>> +
>> + regval = FIELD_PREP(SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_MUX_SEL_MASK,
>> + chan->channel);
>> + regmap_update_bits(priv->regmap, SAR_ADC_DETECT_IDLE_SW,
>> + SAR_ADC_DETECT_IDLE_SW_IDLE_MODE_MUX_SEL_MASK,
>> + regval);
>> +
>> + if (chan->channel == 6)
>> + regmap_update_bits(priv->regmap, SAR_ADC_DELTA_10,
>> + SAR_ADC_DELTA_10_TEMP_SEL, 0);
>> +}
>> +
>> +static void meson_saradc_set_channel7_mux(struct iio_dev *indio_dev,
>> + enum meson_saradc_chan7_mux_sel sel)
>> +{
>> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
>> + u32 regval;
>> +
>> + regval = FIELD_PREP(SAR_ADC_REG3_CTRL_CHAN7_MUX_SEL_MASK, sel);
>> + regmap_update_bits(priv->regmap, SAR_ADC_REG3,
>> + SAR_ADC_REG3_CTRL_CHAN7_MUX_SEL_MASK, regval);
>> +
>> + usleep_range(10, 20);
>> +}
>> +
>> +static void meson_saradc_start_sample_engine(struct iio_dev *indio_dev)
>> +{
>> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
>> +
>> + regmap_update_bits(priv->regmap, SAR_ADC_REG0,
>> + SAR_ADC_REG0_SAMPLE_ENGINE_ENABLE,
>> + SAR_ADC_REG0_SAMPLE_ENGINE_ENABLE);
>> +
>> + regmap_update_bits(priv->regmap, SAR_ADC_REG0,
>> + SAR_ADC_REG0_SAMPLING_START,
>> + SAR_ADC_REG0_SAMPLING_START);
>> +}
>> +
>> +static void meson_saradc_stop_sample_engine(struct iio_dev *indio_dev)
>> +{
>> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
>> +
>> + regmap_update_bits(priv->regmap, SAR_ADC_REG0,
>> + SAR_ADC_REG0_SAMPLING_STOP,
>> + SAR_ADC_REG0_SAMPLING_STOP);
>> +
>> + /* wait until all modules are stopped */
>> + meson_saradc_wait_busy_clear(indio_dev);
>> +
>> + regmap_update_bits(priv->regmap, SAR_ADC_REG0,
>> + SAR_ADC_REG0_SAMPLE_ENGINE_ENABLE, 0);
>> +}
>> +
>> +static void meson_saradc_lock(struct iio_dev *indio_dev)
>> +{
>> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
>> + int val;
>> +
>> + mutex_lock(&indio_dev->mlock);
>> +
>> + /* prevent BL30 from using the SAR ADC while we are using it */
>> + regmap_update_bits(priv->regmap, SAR_ADC_DELAY,
>> + SAR_ADC_DELAY_KERNEL_BUSY,
>> + 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, SAR_ADC_DELAY, &val);
>> + } while (val & SAR_ADC_DELAY_BL30_BUSY);
>> +}
>> +
>> +static void meson_saradc_unlock(struct iio_dev *indio_dev)
>> +{
>> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
>> +
>> + /* allow BL30 to use the SAR ADC again */
>> + regmap_update_bits(priv->regmap, SAR_ADC_DELAY,
>> + SAR_ADC_DELAY_KERNEL_BUSY, 0);
>> +
>> + mutex_unlock(&indio_dev->mlock);
>> +}
>> +
>> +static int meson_saradc_get_sample(struct iio_dev *indio_dev,
>> + const struct iio_chan_spec *chan,
>> + enum meson_saradc_avg_mode avg_mode,
>> + enum meson_saradc_num_samples avg_samples,
>> + int *val)
>> +{
>> + int ret, tmp;
>> +
>> + meson_saradc_lock(indio_dev);
>> +
>> + /* clear old values from the FIFO buffer, ignoring errors */
>> + meson_saradc_read_raw_sample(indio_dev, chan, &tmp);
>> +
>> + meson_saradc_set_averaging(indio_dev, chan, avg_mode, avg_samples);
>> +
>> + meson_saradc_enable_channel(indio_dev, chan);
>> +
>> + meson_saradc_start_sample_engine(indio_dev);
>> + ret = meson_saradc_read_raw_sample(indio_dev, chan, val);
>> + meson_saradc_stop_sample_engine(indio_dev);
>> +
>> + meson_saradc_unlock(indio_dev);
>> +
>> + if (ret) {
>> + dev_warn(&indio_dev->dev,
>> + "failed to read sample for channel %d: %d\n",
>> + chan->channel, ret);
>> + return ret;
>> + }
>> +
>> + return IIO_VAL_INT;
>> +}
>> +
>> +static int meson_saradc_iio_info_read_raw(struct iio_dev *indio_dev,
>> + const struct iio_chan_spec *chan,
>> + int *val, int *val2, long mask)
>> +{
>> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
>> + int ret;
>> +
>> + switch (mask) {
>> + case IIO_CHAN_INFO_RAW:
>> + return meson_saradc_get_sample(indio_dev, chan, NO_AVERAGING,
>> + ONE_SAMPLE, val);
>> + break;
>> +
>> + case IIO_CHAN_INFO_AVERAGE_RAW:
>> + return meson_saradc_get_sample(indio_dev, chan, MEAN_AVERAGING,
>> + EIGHT_SAMPLES, val);
>> + break;
>> +
>> + case IIO_CHAN_INFO_SCALE:
>> + ret = regulator_get_voltage(priv->vref);
>> + if (ret < 0) {
>> + dev_err(&indio_dev->dev,
>> + "failed to get vref voltage: %d\n", ret);
>> + return ret;
>> + }
>> +
>> + *val = ret / 1000;
>> + *val2 = priv->resolution;
>> + return IIO_VAL_FRACTIONAL_LOG2;
>> +
>> + default:
>> + return -EINVAL;
>> + }
>> +}
>> +
>> +static int meson_saradc_clk_init(struct iio_dev *indio_dev, void __iomem *base)
>> +{
>> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
>> + struct clk_init_data init;
>> + char clk_name[32];
>> + const char *clk_parents[1];
>> +
>> + snprintf(clk_name, sizeof(clk_name), "%s#adc_div",
>> + of_node_full_name(indio_dev->dev.of_node));
>> + init.name = devm_kstrdup(&indio_dev->dev, clk_name, GFP_KERNEL);
>> + init.flags = 0;
>> + init.ops = &clk_divider_ops;
>> + clk_parents[0] = __clk_get_name(priv->clkin);
>> + init.parent_names = clk_parents;
>> + init.num_parents = 1;
>> +
>> + priv->clk_div.reg = base + SAR_ADC_REG3;
>> + priv->clk_div.shift = SAR_ADC_REG3_ADC_CLK_DIV_SHIFT;
>> + priv->clk_div.width = SAR_ADC_REG3_ADC_CLK_DIV_WIDTH;
>> + priv->clk_div.hw.init = &init;
>> + priv->clk_div.flags = 0;
>> +
>> + priv->adc_div_clk = devm_clk_register(&indio_dev->dev,
>> + &priv->clk_div.hw);
>> + if (WARN_ON(IS_ERR(priv->adc_div_clk)))
>> + return PTR_ERR(priv->adc_div_clk);
>> +
>> + snprintf(clk_name, sizeof(clk_name), "%s#adc_en",
>> + of_node_full_name(indio_dev->dev.of_node));
>> + init.name = devm_kstrdup(&indio_dev->dev, clk_name, GFP_KERNEL);
>> + init.flags = CLK_SET_RATE_PARENT;
>> + init.ops = &clk_gate_ops;
>> + clk_parents[0] = __clk_get_name(priv->adc_div_clk);
>> + init.parent_names = clk_parents;
>> + init.num_parents = 1;
>> +
>> + priv->clk_gate.reg = base + SAR_ADC_REG3;
>> + priv->clk_gate.bit_idx = fls(SAR_ADC_REG3_CLK_EN);
>> + priv->clk_gate.hw.init = &init;
>> +
>> + priv->adc_clk = devm_clk_register(&indio_dev->dev, &priv->clk_gate.hw);
>> + if (WARN_ON(IS_ERR(priv->adc_clk)))
>> + return PTR_ERR(priv->adc_clk);
>> +
>> + return 0;
>> +}
>> +
>> +static int meson_saradc_init(struct iio_dev *indio_dev)
>> +{
>> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
>> + int regval, ret;
>> +
>> + /* make sure we start at CH7 input */
> why? Seems like a little more detail would be good here ;)
I'll change this to "make sure we start at CH7 input since the other
muxes are only used for internal calibration." in v2
>> + meson_saradc_set_channel7_mux(indio_dev, CHAN7_MUX_CH7_INPUT);
>> +
>> + regmap_read(priv->regmap, SAR_ADC_REG3, ®val);
>> + if (regval & SAR_ADC_REG3_BL30_INITIALIZED) {
>> + dev_info(&indio_dev->dev, "already initialized by BL30\n");
>> + return 0;
>> + }
>> +
>> + dev_info(&indio_dev->dev, "initializing SAR ADC\n");
> I'd argue this provides no useful info so should be dropped.
> Useful for debugging no doubt, but just noise going forward.
do you want me to remove them or should I turn them into dev_dbg() (so
they can be enabled for debugging purposes)?
>> +
>> + meson_saradc_stop_sample_engine(indio_dev);
>> +
>> + /* update the channel 6 MUX to select the temperature sensor */
>> + regmap_update_bits(priv->regmap, SAR_ADC_REG0,
>> + SAR_ADC_REG0_ADC_TEMP_SEN_SEL,
>> + SAR_ADC_REG0_ADC_TEMP_SEN_SEL);
>> +
>> + /* disable all channels by default */
>> + regmap_write(priv->regmap, SAR_ADC_CHAN_LIST, 0x0);
>> +
>> + regmap_update_bits(priv->regmap, SAR_ADC_REG3,
>> + SAR_ADC_REG3_CTRL_SAMPLING_CLOCK_PHASE, 0);
>> + regmap_update_bits(priv->regmap, SAR_ADC_REG3,
>> + SAR_ADC_REG3_CNTL_USE_SC_DLY,
>> + SAR_ADC_REG3_CNTL_USE_SC_DLY);
>> +
>> + /* delay between two samples = (10+1) * 1uS */
>> + regmap_update_bits(priv->regmap, SAR_ADC_DELAY,
>> + SAR_ADC_DELAY_INPUT_DLY_CNT_MASK,
>> + FIELD_PREP(SAR_ADC_DELAY_SAMPLE_DLY_CNT_MASK, 10));
>> + regmap_update_bits(priv->regmap, SAR_ADC_DELAY,
>> + SAR_ADC_DELAY_SAMPLE_DLY_SEL_MASK,
>> + FIELD_PREP(SAR_ADC_DELAY_SAMPLE_DLY_SEL_MASK, 0));
>> +
>> + /* delay between two samples = (10+1) * 1uS */
>> + regmap_update_bits(priv->regmap, SAR_ADC_DELAY,
>> + SAR_ADC_DELAY_INPUT_DLY_CNT_MASK,
>> + FIELD_PREP(SAR_ADC_DELAY_INPUT_DLY_CNT_MASK, 10));
>> + regmap_update_bits(priv->regmap, SAR_ADC_DELAY,
>> + SAR_ADC_DELAY_INPUT_DLY_SEL_MASK,
>> + FIELD_PREP(SAR_ADC_DELAY_INPUT_DLY_SEL_MASK, 1));
>> +
> Cool. I hadn't come across FIELD_PREP before. Neater and tidier than having
> a shift and a mask for at least some usecases.
I think these were introduced with v4.9. I like them because I tend
use GENMASK() incorrectly and with those macros I get an error at
compile-time (without having to debug my code at all)
>> + ret = clk_set_parent(priv->adc_sel_clk, priv->clkin);
>> + if (ret) {
>> + dev_err(&indio_dev->dev,
>> + "failed to set adc parent to clkin\n");
>> + return ret;
>> + }
>> +
>> + ret = clk_set_rate(priv->adc_clk, 1200000);
>> + if (ret) {
>> + dev_err(&indio_dev->dev, "failed to set adc clock rate\n");
>> + return ret;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static int meson_saradc_hw_enable(struct iio_dev *indio_dev)
>> +{
>> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
>> + int ret;
>> +
>> + meson_saradc_lock(indio_dev);
>> +
>> + ret = regulator_enable(priv->vref);
>> + if (ret < 0) {
>> + dev_err(&indio_dev->dev, "failed to enable vref regulator\n");
>> + goto err_vref;
>> + }
>> +
>> + ret = clk_prepare_enable(priv->core_clk);
>> + if (ret) {
>> + dev_err(&indio_dev->dev, "failed to enable core clk\n");
>> + goto err_core_clk;
>> + }
>> +
>> + ret = clk_prepare_enable(priv->sana_clk);
>> + if (ret) {
>> + dev_err(&indio_dev->dev, "failed to enable sana clk\n");
>> + goto err_sana_clk;
>> + }
>> +
>> + regmap_update_bits(priv->regmap, SAR_ADC_REG11,
>> + SAR_ADC_REG11_BANDGAP_EN, SAR_ADC_REG11_BANDGAP_EN);
> Is this controlling an offset for a bandgap or some actual electronics?
> Not sure if it should be disabled on error and the datasheets I've found are
> far from great! You disable it in the disable, so I'd expect it to be
> unwound on error in here too.
actually the bandgap is not documented at all :(
>> + regmap_update_bits(priv->regmap, SAR_ADC_REG3, SAR_ADC_REG3_ADC_EN,
>> + SAR_ADC_REG3_ADC_EN);
> The fact you turn this of in the disable suggests to me that on error
> you should be doing it in here too.
I will disable this along with SAR_ADC_REG11_BANDGAP_EN in the
err_adc_clk label, thanks for spotting this.
>> +
>> + udelay(5);
>> +
>> + ret = clk_prepare_enable(priv->adc_clk);
>> + if (ret) {
>> + dev_err(&indio_dev->dev, "failed to enable adc_en clk\n");
>> + goto err_adc_clk;
>> + }
>> +
>> + meson_saradc_unlock(indio_dev);
>> +
>> + return 0;
>> +
>> +err_adc_clk:
>> + clk_disable_unprepare(priv->sana_clk);
>> +err_sana_clk:
>> + clk_disable_unprepare(priv->core_clk);
>> +err_core_clk:
>> + regulator_disable(priv->vref);
>> +err_vref:
>> + meson_saradc_unlock(indio_dev);
>> + return ret;
>> +}
>> +
>> +static void meson_saradc_hw_disable(struct iio_dev *indio_dev)
>> +{
>> + struct meson_saradc_priv *priv = iio_priv(indio_dev);
>> +
>> + meson_saradc_lock(indio_dev);
>> +
>> + clk_disable_unprepare(priv->adc_clk);
>> +
>> + regmap_update_bits(priv->regmap, SAR_ADC_REG3, SAR_ADC_REG3_ADC_EN, 0);
>> + regmap_update_bits(priv->regmap, SAR_ADC_REG11,
>> + SAR_ADC_REG11_BANDGAP_EN, 0);
>> +
>> + clk_disable_unprepare(priv->sana_clk);
>> + clk_disable_unprepare(priv->core_clk);
>> +
>> + regulator_disable(priv->vref);
>> +
>> + meson_saradc_unlock(indio_dev);
>> +}
>> +
>> +static const struct iio_info meson_saradc_iio_info = {
>> + .read_raw = meson_saradc_iio_info_read_raw,
>> + .driver_module = THIS_MODULE,
>> +};
>> +
>> +static const struct of_device_id meson_saradc_of_match[] = {
>> + {
>> + .compatible = "amlogic,meson-gxbb-saradc",
>> + .data = (void *)10,
> Might have been worth having a structure array indexed from an enum.
> For now it is overkill, but seems likely there are a few other differences
> that aren't supported yet?
what do you mean with "structure array indexed from an enum"? I can
introduce some match-specific struct if you want (just like it's done
in rockchip_saradc.c with "struct rockchip_saradc_data").
>> + }, {
>> + .compatible = "amlogic,meson-gxl-saradc",
>> + .data = (void *)12,
>> + },
>> + {},
>> +};
>> +MODULE_DEVICE_TABLE(of, meson_saradc_of_match);
>> +
>> +static int meson_saradc_probe(struct platform_device *pdev)
>> +{
>> + struct meson_saradc_priv *priv;
>> + struct iio_dev *indio_dev;
>> + struct resource *res;
>> + void __iomem *base;
>> + const struct of_device_id *match;
>> + int ret;
>> +
>> + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*priv));
>> + if (!indio_dev) {
>> + dev_err(&pdev->dev, "failed allocating iio device\n");
>> + return -ENOMEM;
>> + }
>> +
>> + priv = iio_priv(indio_dev);
>> +
>> + match = of_match_device(meson_saradc_of_match, &pdev->dev);
>> + priv->resolution = (unsigned long)match->data;
>> +
>> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> + base = devm_ioremap_resource(&pdev->dev, res);
>> + if (IS_ERR(base))
>> + return PTR_ERR(base);
>> +
>> + priv->regmap = devm_regmap_init_mmio(&pdev->dev, base,
>> + &meson_saradc_regmap_config);
>> + if (IS_ERR(priv->regmap))
>> + return PTR_ERR(priv->regmap);
>> +
>> + init_completion(&priv->completion);
>> +
>> + priv->clkin = devm_clk_get(&pdev->dev, "clkin");
>> + if (IS_ERR(priv->clkin)) {
>> + dev_err(&pdev->dev, "failed to get clkin\n");
>> + return PTR_ERR(priv->clkin);
>> + }
>> +
>> + priv->core_clk = devm_clk_get(&pdev->dev, "core");
>> + if (IS_ERR(priv->core_clk)) {
>> + dev_err(&pdev->dev, "failed to get core clk\n");
>> + return PTR_ERR(priv->core_clk);
>> + }
>> +
>> + priv->sana_clk = devm_clk_get(&pdev->dev, "sana");
> Oh for a devm_clk_get_optional to handle this boiler plate neatly.
> It's been suggested before, but nothing seems to have come of it.
I guess quite a few drivers would benefit from that. maybe we should
take this to the linux-clk list again?
> Some array clk get functions might also clean this up a touch.
yes, unfortunately in this case it's not that easy as it would have to
allow a mix of mandatory and optional clocks. Additionally I cannot
bulk-enable them unconditionally since some of these are simple gates,
others need to be reparented and for some the rate has to be set.
>> + if (IS_ERR(priv->sana_clk)) {
>> + if (PTR_ERR(priv->sana_clk) == -ENOENT) {
>> + priv->sana_clk = NULL;
>> + } else {
>> + dev_err(&pdev->dev, "failed to get sana clk\n");
>> + return PTR_ERR(priv->sana_clk);
>> + }
>> + }
>> +
>> + priv->adc_clk = devm_clk_get(&pdev->dev, "adc_clk");
>> + if (IS_ERR(priv->adc_clk)) {
>> + if (PTR_ERR(priv->adc_clk) == -ENOENT) {
>> + priv->adc_clk = NULL;
>> + } else {
>> + dev_err(&pdev->dev, "failed to get adc clk\n");
>> + return PTR_ERR(priv->adc_clk);
>> + }
>> + }
>> +
>> + priv->adc_sel_clk = devm_clk_get(&pdev->dev, "adc_sel");
>> + if (IS_ERR(priv->adc_sel_clk)) {
>> + if (PTR_ERR(priv->adc_sel_clk) == -ENOENT) {
>> + priv->adc_sel_clk = NULL;
>> + } else {
>> + dev_err(&pdev->dev, "failed to get adc_sel clk\n");
>> + return PTR_ERR(priv->adc_sel_clk);
>> + }
>> + }
>> +
>> + /* on pre-GXBB SoCs the SAR ADC itself provides the ADC clock: */
>> + if (!priv->adc_clk) {
>> + ret = meson_saradc_clk_init(indio_dev, base);
>> + if (ret)
>> + return ret;
>> + }
>> +
>> + priv->vref = devm_regulator_get(&pdev->dev, "vref");
>> + if (IS_ERR(priv->vref)) {
>> + dev_err(&pdev->dev, "failed to get vref regulator\n");
>> + return PTR_ERR(priv->vref);
>> + }
>> +
>> + ret = meson_saradc_init(indio_dev);
>> + if (ret)
>> + goto err;
>> +
>> + ret = meson_saradc_hw_enable(indio_dev);
>> + if (ret)
>> + goto err;
>> +
>> + platform_set_drvdata(pdev, indio_dev);
>> +
>> + indio_dev->name = dev_name(&pdev->dev);
>> + indio_dev->dev.parent = &pdev->dev;
>> + indio_dev->dev.of_node = pdev->dev.of_node;
>> + indio_dev->modes = INDIO_DIRECT_MODE;
>> + indio_dev->info = &meson_saradc_iio_info;
>> +
>> + indio_dev->channels = meson_saradc_iio_channels;
>> + indio_dev->num_channels = SAR_ADC_NUM_CHANNELS;
>> +
>> + ret = iio_device_register(indio_dev);
>> + if (ret)
>> + goto err_hw;
>> +
>> + return 0;
>> +
>> +err_hw:
>> + meson_saradc_hw_disable(indio_dev);
>> +err:
>> + return ret;
>> +}
>> +
>> +static int meson_saradc_remove(struct platform_device *pdev)
>> +{
>> + struct iio_dev *indio_dev = platform_get_drvdata(pdev);
>> +
>> + meson_saradc_hw_disable(indio_dev);
>> + iio_device_unregister(indio_dev);
>> +
>> + return 0;
>> +}
>> +
>> +#ifdef CONFIG_PM_SLEEP
>> +static int meson_saradc_suspend(struct device *dev)
>> +{
>> + struct iio_dev *indio_dev = dev_get_drvdata(dev);
>> +
>> + meson_saradc_hw_disable(indio_dev);
>> +
>> + return 0;
>> +}
>> +
>> +static int meson_saradc_resume(struct device *dev)
>> +{
>> + struct iio_dev *indio_dev = dev_get_drvdata(dev);
>> +
>> + return meson_saradc_hw_enable(indio_dev);
>> +}
>> +#endif /* CONFIG_PM_SLEEP */
>> +
>> +static SIMPLE_DEV_PM_OPS(meson_saradc_pm_ops,
>> + meson_saradc_suspend, meson_saradc_resume);
>> +
>> +static struct platform_driver meson_saradc_driver = {
>> + .probe = meson_saradc_probe,
>> + .remove = meson_saradc_remove,
>> + .driver = {
>> + .name = "meson-saradc",
>> + .of_match_table = meson_saradc_of_match,
>> + .pm = &meson_saradc_pm_ops,
>> + },
>> +};
>> +
>> +module_platform_driver(meson_saradc_driver);
>> +
>> +MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>");
>> +MODULE_DESCRIPTION("Amlogic Meson SAR ADC driver");
>> +MODULE_LICENSE("GPL v2");
>>
>
Regards,
Martin
[0] http://lists.infradead.org/pipermail/linux-amlogic/2016-August/000986.html
^ permalink raw reply
* [PATCH RFC] mmc: mxs-mmc: Implement CMD23 support
From: Stefan Wahren @ 2017-01-14 18:10 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <CAOMZO5Czy17FytjyHP_pHHqCetDiYyOHaVOFu5hZHjAD=8rHUA@mail.gmail.com>
Hi Fabio,
> Fabio Estevam <festevam@gmail.com> hat am 14. Januar 2017 um 18:40 geschrieben:
>
>
> Hi Stefan,
>
> On Sat, Jan 14, 2017 at 3:29 PM, Stefan Wahren <stefan.wahren@i2se.com> wrote:
> > This patch implements support for multiblock transfers bounded
> > by SET_BLOCK_COUNT (CMD23) on the MXS MMC host driver.
> >
> > Signed-off-by: Stefan Wahren <stefan.wahren@i2se.com>
>
> Just curious: what is the throughput gain you notice with this patch?
unfortunately i didn't see any noticeable changes on throughput.
>
> Thanks
^ permalink raw reply
* [BISECT] ARM build errors on GCC v6.2 (crypto: arm/aes - replace scalar AES cipher)
From: Russell King - ARM Linux @ 2017-01-14 18:20 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170114142435.ashdj2xm5b3kzg2p@kozik-lap>
On Sat, Jan 14, 2017 at 04:24:35PM +0200, Krzysztof Kozlowski wrote:
> Hi,
>
> allyesconfig and multi_v7_defconfig fail to build on recent linux-next
> on GCC 6.2.0.
>
> Errors:
> ../arch/arm/crypto/aes-cipher-core.S: Assembler messages:
> ../arch/arm/crypto/aes-cipher-core.S:21: Error: selected processor does not support `tt .req ip' in ARM mode
> ../arch/arm/crypto/aes-cipher-core.S:174: Error: ARM register expected -- `movw tt,#:lower16:crypto_ft_tab'
> ../arch/arm/crypto/aes-cipher-core.S:174: Error: ARM register expected -- `movt tt,#:upper16:crypto_ft_tab'
>
> Compiler: arm-linux-gnueabi-gcc (Ubuntu 6.2.0-5ubuntu12) 6.2.0 20161005
It's worth pointing out that _assembler_ messages come from _binutils_
rather than _gcc_. Please advise which version of _binutils_ you
are using. Thanks.
--
RMK's Patch system: http://www.armlinux.org.uk/developer/patches/
FTTC broadband for 0.8mile line: currently at 9.6Mbps down 400kbps up
according to speedtest.net.
^ permalink raw reply
* [BISECT] ARM build errors on GCC v6.2 (crypto: arm/aes - replace scalar AES cipher)
From: Krzysztof Kozlowski @ 2017-01-14 18:52 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170114182011.GX14217@n2100.armlinux.org.uk>
On Sat, Jan 14, 2017 at 06:20:12PM +0000, Russell King - ARM Linux wrote:
> On Sat, Jan 14, 2017 at 04:24:35PM +0200, Krzysztof Kozlowski wrote:
> > Hi,
> >
> > allyesconfig and multi_v7_defconfig fail to build on recent linux-next
> > on GCC 6.2.0.
> >
> > Errors:
> > ../arch/arm/crypto/aes-cipher-core.S: Assembler messages:
> > ../arch/arm/crypto/aes-cipher-core.S:21: Error: selected processor does not support `tt .req ip' in ARM mode
> > ../arch/arm/crypto/aes-cipher-core.S:174: Error: ARM register expected -- `movw tt,#:lower16:crypto_ft_tab'
> > ../arch/arm/crypto/aes-cipher-core.S:174: Error: ARM register expected -- `movt tt,#:upper16:crypto_ft_tab'
> >
> > Compiler: arm-linux-gnueabi-gcc (Ubuntu 6.2.0-5ubuntu12) 6.2.0 20161005
>
> It's worth pointing out that _assembler_ messages come from _binutils_
> rather than _gcc_. Please advise which version of _binutils_ you
> are using. Thanks.
Ah, yes, so the binutils 2.27 (Ubuntu package: 2.27-8ubuntu2).
However Ard mentioned that this is already fixed.
Best regards,
Krzysztof
^ permalink raw reply
* [PATCH net-next v2 05/10] drivers: base: Add device_find_class()
From: Florian Fainelli @ 2017-01-14 19:01 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <063D6719AE5E284EB5DD2968C1650D6DB02620D3@AcuExch.aculab.com>
On 01/13/2017 02:55 AM, David Laight wrote:
> From: Florian Fainelli
>> Sent: 12 January 2017 22:51
>> On 01/12/2017 01:21 PM, David Miller wrote:
>>> From: Florian Fainelli <f.fainelli@gmail.com>
>>> Date: Wed, 11 Jan 2017 19:41:16 -0800
>>>
>>>> Add a helper function to lookup a device reference given a class name.
>>>> This is a preliminary patch to remove adhoc code from net/dsa/dsa.c and
>>>> make it more generic.
> ...
>>>> +static int dev_is_class(struct device *dev, void *class)
>>>
>>> I know you are just moving code, but this class argumnet is a string
>>> and thus should be "char *" or even "const char *".
>>
>> Well, this is really so that we don't need to cast the arguments passed
>> to device_find_child(), which takes a void *data as well. If we made
>> that a const char *class, we'd get warnings that look like these:
>>
>> drivers/base/core.c: In function 'device_find_class':
>> drivers/base/core.c:2083:2: warning: passing argument 2 of
>> 'device_find_child' discards 'const' qualifier from pointer target type
>> [enabled by default]
>> return device_find_child(parent, class, dev_is_class);
>> ^
>> drivers/base/core.c:2050:16: note: expected 'void *' but argument is of
>> type 'const char *'
>> struct device *device_find_child(struct device *parent, void *data,
>> ^
> ...
>
> Maybe device_find_child() needs changing to take 'const void *' ?
As a separate patch set, sure, I will add that to my TODO. Thanks!
--
Florian
^ permalink raw reply
* [PATCH] reset: uniphier: add compatible string for LD11 SD-reset block
From: Masahiro Yamada @ 2017-01-14 19:04 UTC (permalink / raw)
To: linux-arm-kernel
The LD11 SoC is equipped with not only MIO-reset but also SD-reset
for controlling RST_n pin of the eMMC device.
Update the binding document and remove unneeded "." from each line
in itemization.
Signed-off-by: Masahiro Yamada <yamada.masahiro@socionext.com>
---
.../devicetree/bindings/reset/uniphier-reset.txt | 47 +++++++++++-----------
drivers/reset/reset-uniphier.c | 4 ++
2 files changed, 28 insertions(+), 23 deletions(-)
diff --git a/Documentation/devicetree/bindings/reset/uniphier-reset.txt b/Documentation/devicetree/bindings/reset/uniphier-reset.txt
index 5020524..83ab0f5 100644
--- a/Documentation/devicetree/bindings/reset/uniphier-reset.txt
+++ b/Documentation/devicetree/bindings/reset/uniphier-reset.txt
@@ -6,14 +6,14 @@ System reset
Required properties:
- compatible: should be one of the following:
- "socionext,uniphier-sld3-reset" - for sLD3 SoC.
- "socionext,uniphier-ld4-reset" - for LD4 SoC.
- "socionext,uniphier-pro4-reset" - for Pro4 SoC.
- "socionext,uniphier-sld8-reset" - for sLD8 SoC.
- "socionext,uniphier-pro5-reset" - for Pro5 SoC.
- "socionext,uniphier-pxs2-reset" - for PXs2/LD6b SoC.
- "socionext,uniphier-ld11-reset" - for LD11 SoC.
- "socionext,uniphier-ld20-reset" - for LD20 SoC.
+ "socionext,uniphier-sld3-reset" - for sLD3 SoC
+ "socionext,uniphier-ld4-reset" - for LD4 SoC
+ "socionext,uniphier-pro4-reset" - for Pro4 SoC
+ "socionext,uniphier-sld8-reset" - for sLD8 SoC
+ "socionext,uniphier-pro5-reset" - for Pro5 SoC
+ "socionext,uniphier-pxs2-reset" - for PXs2/LD6b SoC
+ "socionext,uniphier-ld11-reset" - for LD11 SoC
+ "socionext,uniphier-ld20-reset" - for LD20 SoC
- #reset-cells: should be 1.
Example:
@@ -37,14 +37,15 @@ Media I/O (MIO) reset, SD reset
Required properties:
- compatible: should be one of the following:
- "socionext,uniphier-sld3-mio-reset" - for sLD3 SoC.
- "socionext,uniphier-ld4-mio-reset" - for LD4 SoC.
- "socionext,uniphier-pro4-mio-reset" - for Pro4 SoC.
- "socionext,uniphier-sld8-mio-reset" - for sLD8 SoC.
- "socionext,uniphier-pro5-sd-reset" - for Pro5 SoC.
- "socionext,uniphier-pxs2-sd-reset" - for PXs2/LD6b SoC.
- "socionext,uniphier-ld11-mio-reset" - for LD11 SoC.
- "socionext,uniphier-ld20-sd-reset" - for LD20 SoC.
+ "socionext,uniphier-sld3-mio-reset" - for sLD3 SoC
+ "socionext,uniphier-ld4-mio-reset" - for LD4 SoC
+ "socionext,uniphier-pro4-mio-reset" - for Pro4 SoC
+ "socionext,uniphier-sld8-mio-reset" - for sLD8 SoC
+ "socionext,uniphier-pro5-sd-reset" - for Pro5 SoC
+ "socionext,uniphier-pxs2-sd-reset" - for PXs2/LD6b SoC
+ "socionext,uniphier-ld11-mio-reset" - for LD11 SoC (MIO)
+ "socionext,uniphier-ld11-sd-reset" - for LD11 SoC (SD)
+ "socionext,uniphier-ld20-sd-reset" - for LD20 SoC
- #reset-cells: should be 1.
Example:
@@ -68,13 +69,13 @@ Peripheral reset
Required properties:
- compatible: should be one of the following:
- "socionext,uniphier-ld4-peri-reset" - for LD4 SoC.
- "socionext,uniphier-pro4-peri-reset" - for Pro4 SoC.
- "socionext,uniphier-sld8-peri-reset" - for sLD8 SoC.
- "socionext,uniphier-pro5-peri-reset" - for Pro5 SoC.
- "socionext,uniphier-pxs2-peri-reset" - for PXs2/LD6b SoC.
- "socionext,uniphier-ld11-peri-reset" - for LD11 SoC.
- "socionext,uniphier-ld20-peri-reset" - for LD20 SoC.
+ "socionext,uniphier-ld4-peri-reset" - for LD4 SoC
+ "socionext,uniphier-pro4-peri-reset" - for Pro4 SoC
+ "socionext,uniphier-sld8-peri-reset" - for sLD8 SoC
+ "socionext,uniphier-pro5-peri-reset" - for Pro5 SoC
+ "socionext,uniphier-pxs2-peri-reset" - for PXs2/LD6b SoC
+ "socionext,uniphier-ld11-peri-reset" - for LD11 SoC
+ "socionext,uniphier-ld20-peri-reset" - for LD20 SoC
- #reset-cells: should be 1.
Example:
diff --git a/drivers/reset/reset-uniphier.c b/drivers/reset/reset-uniphier.c
index 968c3ae..9c11be3 100644
--- a/drivers/reset/reset-uniphier.c
+++ b/drivers/reset/reset-uniphier.c
@@ -390,6 +390,10 @@ static const struct of_device_id uniphier_reset_match[] = {
.data = uniphier_sld3_mio_reset_data,
},
{
+ .compatible = "socionext,uniphier-ld11-sd-reset",
+ .data = uniphier_pro5_sd_reset_data,
+ },
+ {
.compatible = "socionext,uniphier-ld20-sd-reset",
.data = uniphier_pro5_sd_reset_data,
},
--
2.7.4
^ permalink raw reply related
* [PATCH net-next v2 08/10] net: dsa: Add support for platform data
From: Florian Fainelli @ 2017-01-14 19:12 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <173cffe2-f407-ac43-c5ee-85a70576dd36@gmail.com>
On 01/13/2017 02:37 PM, Florian Fainelli wrote:
> On 01/13/2017 06:04 AM, Andrew Lunn wrote:
>>> index cd91070b5467..d326fc4afad7 100644
>>> --- a/net/dsa/dsa2.c
>>> +++ b/net/dsa/dsa2.c
>>> @@ -81,17 +81,23 @@ static void dsa_dst_del_ds(struct dsa_switch_tree *dst,
>>>
>>> static bool dsa_port_is_valid(struct dsa_port *port)
>>> {
>>> - return !!port->dn;
>>> + return !!(port->dn || port->name);
>>> }
>>
>> Does this clash with Viviens recent change to make names optional and
>> have the kernel assign it?
>
> So there were two ways to look at this, one was that could check here
> that ds->pd is assigned and port->name is assigned, which means that
> platform data has to provide valid port name. We can also eliminate this
> check entirely because we now support NULL names just fines.
Considering that the comment above struct dsa_chip_data::port_names in
net/dsa/dsa.h is pretty clear about the port_names usage, I am tempted
to keep the code as-is since without a name, for platform data, we would
not have a way to tell if a port is disabled or not.
Does that work for you?
--
Florian
^ permalink raw reply
* [PATCH v9 3/3] iio: adc: add support for Allwinner SoCs ADC
From: Quentin Schulz @ 2017-01-14 19:19 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <f8285a90-656f-0da4-ee81-47b79ce4cd2c@kernel.org>
Hi Jonathan,
On 08/01/2017 12:17, Jonathan Cameron wrote:
> On 30/12/16 14:40, Jonathan Cameron wrote:
>> On 13/12/16 14:33, Quentin Schulz wrote:
>>> The Allwinner SoCs all have an ADC that can also act as a touchscreen
>>> controller and a thermal sensor. This patch adds the ADC driver which is
>>> based on the MFD for the same SoCs ADC.
>>>
>>> This also registers the thermal adc channel in the iio map array so
>>> iio_hwmon could use it without modifying the Device Tree. This registers
>>> the driver in the thermal framework.
>>>
>>> The thermal sensor requires the IP to be in touchscreen mode to return
>>> correct values. Therefore, if the user is continuously reading the ADC
>>> channel(s), the thermal framework in which the thermal sensor is
>>> registered will switch the IP in touchscreen mode to get a temperature
>>> value and requires a delay of 100ms (because of the mode switching),
>>> then the ADC will switch back to ADC mode and requires also a delay of
>>> 100ms. If the ADC readings are critical to user and the SoC temperature
>>> is not, this driver is capable of not registering the thermal sensor in
>>> the thermal framework and thus, "quicken" the ADC readings.
>>>
>>> This driver probes on three different platform_device_id to take into
>>> account slight differences (registers bit and temperature computation)
>>> between Allwinner SoCs ADCs.
>>>
>>> Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
>>> Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>
>>> Acked-by: Jonathan Cameron <jic23@kernel.org>
>>> Acked-for-MFD-by: Lee Jones <lee.jones@linaro.org>
>> One comment inline but not a blocker.
>>
>> I would ideally like an ack from the thermal side. The relevant code
>> is small, but best to be sure and keep them in the loop as well.
>>
>> It does feel a little convoluted to have both this directly providing
>> a thermal zone and being able to create one indirectly through hwmon as
>> well but this solution works for me I think...
>>
>> Cc'd Zang and Eduardo.
> Nothing seems to have come through on that front.
>
> I need to get a pull request out to Greg and rebase my tree before I have
> the precursor patch in place. Give me a bump if you haven't heard anything by
> the time next week.
>
Kindly "giving you a bump" you as requested since I haven't heard from
you for a week.
Thanks,
Quentin
> Thanks,
>
> Jonathan
>>
>> Jonathan
>>> ---
>>>
>>> v9:
>>> - clarify comment on why we have to use the parent node as node for
>>> registering in thermal framework, (backward compatibility)
>>> - clarify comment on why we can disable CONFIG_THERMAL_OF,
>>> - clarify Kconfig help to say that CONFIG_THERMAL_OF can be disabled
>>> but should not in most cases,
>>> - make return value of devm_thermal_zone_of_sensor_register a local
>>> variable of the condition block,
>>> - correct scale from _PLUS_MICRO to _PLUS_NANO for ADC raw readings
>>> scale,
>>>
>>> v8:
>>> - remove Kconfig depends on !TOUCHSCREEN_SUN4I (moved to
>>> MFD_SUN4I_GPADC),
>>> - fix return values of regmap_irq_get_virq and platform_get_irq_byname
>>> stored in an unsigned int and then check if negative,
>>> - fix uninitialized ret value when an error occurs while registering
>>> the thermal sensor in the framework,
>>>
>>> v7:
>>> - add Kconfig depends on !TOUCHSCREEN_SUN4I,
>>> - remove Kconfig selects THERMAL_OF,
>>> - do not register thermal sensor if CONFIG_THERMAL_OF is disabled,
>>> - disable irq in irq_handler rather than in read_raw,
>>> - add delay when switching the IP's mode or channel (delay empirically found),
>>> - quicken thermal sensor interrupt period,
>>> - add masks for channel bits,
>>> - fix deadlock in sun4i_gpadc_read if regmap_read/write fails,
>>> - move some logic from sun4i_gpadc_read to sun4i_prepare_for_irq,
>>> - mark last busy for runtime_pm only on success in sun4i_gpadc_read,
>>> - remove cached values,
>>> - increase wait_for_completion_timeout timeout to 1s to be sure to not miss the
>>> thermal interrupt,
>>> - add voltage scale,
>>> - use devm_iio_device_register,
>>>
>>> v6:
>>> - remove "-mfd" from filenames and variables inside MFD driver,
>>> - use DEFINE_RES_IRQ_NAMED instead of setting resources manually,
>>> - cosmetic changes,
>>> - use IDs and switch over ID to get cells specific to an architecture, instead
>>> of using cells direclty, in of_device_id.data,
>>> - compute size of mfd_cells array instead of hardcoded one,
>>>
>>> v5:
>>> - correct mail address,
>>>
>>> v4:
>>> - rename files and variables from sunxi* to sun4i*,
>>> - rename defines from SUNXI_* to SUN4I_* or SUN6I_*,
>>> - remove TP in defines name,
>>> - rename SUNXI_IRQ_* to SUN4I_GPADC_IRQ_* for consistency,
>>> - use devm functions for regmap_add_irq_chip and mfd_add_devices,
>>> - remove remove functions (now empty thanks to devm functions),
>>>
>>> v3:
>>> - use defines in regmap_irq instead of hard coded BITs,
>>> - use of_device_id data field to chose which MFD cells to add considering
>>> the compatible responsible of the MFD probe,
>>> - remove useless initializations,
>>> - disable all interrupts before adding them to regmap_irqchip,
>>> - add goto error label in probe,
>>> - correct wrapping in header license,
>>> - move defines from IIO driver to header,
>>> - use GENMASK to limit the size of the variable passed to a macro,
>>> - prefix register BIT defines with the name of the register,
>>> - reorder defines,
>>>
>>> v2:
>>> - add license headers,
>>> - reorder alphabetically includes,
>>> - add SUNXI_GPADC_ prefixes for defines,
>>>
>>> drivers/iio/adc/Kconfig | 17 ++
>>> drivers/iio/adc/Makefile | 1 +
>>> drivers/iio/adc/sun4i-gpadc-iio.c | 613 ++++++++++++++++++++++++++++++++++++++
>>> include/linux/mfd/sun4i-gpadc.h | 2 +
>>> 4 files changed, 633 insertions(+)
>>> create mode 100644 drivers/iio/adc/sun4i-gpadc-iio.c
>>>
>>> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
>>> index 99c0514..6a6d369 100644
>>> --- a/drivers/iio/adc/Kconfig
>>> +++ b/drivers/iio/adc/Kconfig
>>> @@ -434,6 +434,23 @@ config STX104
>>> The base port addresses for the devices may be configured via the base
>>> array module parameter.
>>>
>>> +config SUN4I_GPADC
>>> + tristate "Support for the Allwinner SoCs GPADC"
>>> + depends on IIO
>>> + depends on MFD_SUN4I_GPADC
>>> + help
>>> + Say yes here to build support for Allwinner (A10, A13 and A31) SoCs
>>> + GPADC. This ADC provides 4 channels which can be used as an ADC or as
>>> + a touchscreen input and one channel for thermal sensor.
>>> +
>>> + The thermal sensor slows down ADC readings and can be disabled by
>>> + disabling CONFIG_THERMAL_OF. However, the thermal sensor should be
>>> + enabled by default since the SoC temperature is usually more critical
>>> + than ADC readings.
>>> +
>>> + To compile this driver as a module, choose M here: the module will be
>>> + called sun4i-gpadc-iio.
>>> +
>>> config TI_ADC081C
>>> tristate "Texas Instruments ADC081C/ADC101C/ADC121C family"
>>> depends on I2C
>>> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
>>> index 7a40c04..18ce8d6 100644
>>> --- a/drivers/iio/adc/Makefile
>>> +++ b/drivers/iio/adc/Makefile
>>> @@ -41,6 +41,7 @@ obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o
>>> obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o
>>> obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
>>> obj-$(CONFIG_STX104) += stx104.o
>>> +obj-$(CONFIG_SUN4I_GPADC) += sun4i-gpadc-iio.o
>>> obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
>>> obj-$(CONFIG_TI_ADC0832) += ti-adc0832.o
>>> obj-$(CONFIG_TI_ADC12138) += ti-adc12138.o
>>> diff --git a/drivers/iio/adc/sun4i-gpadc-iio.c b/drivers/iio/adc/sun4i-gpadc-iio.c
>>> new file mode 100644
>>> index 0000000..a8e134f
>>> --- /dev/null
>>> +++ b/drivers/iio/adc/sun4i-gpadc-iio.c
>>> @@ -0,0 +1,613 @@
>>> +/* ADC driver for sunxi platforms' (A10, A13 and A31) GPADC
>>> + *
>>> + * Copyright (c) 2016 Quentin Schulz <quentin.schulz@free-electrons.com>
>>> + *
>>> + * This program is free software; you can redistribute it and/or modify it under
>>> + * the terms of the GNU General Public License version 2 as published by the
>>> + * Free Software Foundation.
>>> + *
>>> + * 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.
>>> + */
>>> +
>>> +#include <linux/completion.h>
>>> +#include <linux/interrupt.h>
>>> +#include <linux/io.h>
>>> +#include <linux/module.h>
>>> +#include <linux/of.h>
>>> +#include <linux/of_device.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/pm_runtime.h>
>>> +#include <linux/regmap.h>
>>> +#include <linux/thermal.h>
>>> +#include <linux/delay.h>
>>> +
>>> +#include <linux/iio/iio.h>
>>> +#include <linux/iio/driver.h>
>>> +#include <linux/iio/machine.h>
>>> +#include <linux/mfd/sun4i-gpadc.h>
>>> +
>>> +static unsigned int sun4i_gpadc_chan_select(unsigned int chan)
>>> +{
>>> + return SUN4I_GPADC_CTRL1_ADC_CHAN_SELECT(chan);
>>> +}
>>> +
>>> +static unsigned int sun6i_gpadc_chan_select(unsigned int chan)
>>> +{
>>> + return SUN6I_GPADC_CTRL1_ADC_CHAN_SELECT(chan);
>>> +}
>>> +
>>> +struct gpadc_data {
>>> + int temp_offset;
>>> + int temp_scale;
>>> + unsigned int tp_mode_en;
>>> + unsigned int tp_adc_select;
>>> + unsigned int (*adc_chan_select)(unsigned int chan);
>>> + unsigned int adc_chan_mask;
>>> +};
>>> +
>>> +static const struct gpadc_data sun4i_gpadc_data = {
>>> + .temp_offset = -1932,
>>> + .temp_scale = 133,
>>> + .tp_mode_en = SUN4I_GPADC_CTRL1_TP_MODE_EN,
>>> + .tp_adc_select = SUN4I_GPADC_CTRL1_TP_ADC_SELECT,
>>> + .adc_chan_select = &sun4i_gpadc_chan_select,
>>> + .adc_chan_mask = SUN4I_GPADC_CTRL1_ADC_CHAN_MASK,
>>> +};
>>> +
>>> +static const struct gpadc_data sun5i_gpadc_data = {
>>> + .temp_offset = -1447,
>>> + .temp_scale = 100,
>>> + .tp_mode_en = SUN4I_GPADC_CTRL1_TP_MODE_EN,
>>> + .tp_adc_select = SUN4I_GPADC_CTRL1_TP_ADC_SELECT,
>>> + .adc_chan_select = &sun4i_gpadc_chan_select,
>>> + .adc_chan_mask = SUN4I_GPADC_CTRL1_ADC_CHAN_MASK,
>>> +};
>>> +
>>> +static const struct gpadc_data sun6i_gpadc_data = {
>>> + .temp_offset = -1623,
>>> + .temp_scale = 167,
>>> + .tp_mode_en = SUN6I_GPADC_CTRL1_TP_MODE_EN,
>>> + .tp_adc_select = SUN6I_GPADC_CTRL1_TP_ADC_SELECT,
>>> + .adc_chan_select = &sun6i_gpadc_chan_select,
>>> + .adc_chan_mask = SUN6I_GPADC_CTRL1_ADC_CHAN_MASK,
>>> +};
>>> +
>>> +struct sun4i_gpadc_iio {
>>> + struct iio_dev *indio_dev;
>>> + struct completion completion;
>>> + int temp_data;
>>> + u32 adc_data;
>>> + struct regmap *regmap;
>>> + unsigned int fifo_data_irq;
>>> + atomic_t ignore_fifo_data_irq;
>>> + unsigned int temp_data_irq;
>>> + atomic_t ignore_temp_data_irq;
>>> + const struct gpadc_data *data;
>>> + /* prevents concurrent reads of temperature and ADC */
>>> + struct mutex mutex;
>>> +};
>>> +
>>> +#define SUN4I_GPADC_ADC_CHANNEL(_channel, _name) { \
>>> + .type = IIO_VOLTAGE, \
>>> + .indexed = 1, \
>>> + .channel = _channel, \
>>> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
>>> + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
>>> + .datasheet_name = _name, \
>>> +}
>>> +
>>> +static struct iio_map sun4i_gpadc_hwmon_maps[] = {
>>> + {
>>> + .adc_channel_label = "temp_adc",
>>> + .consumer_dev_name = "iio_hwmon.0",
>> It's theoretically possible we have another one of these which will make
>> life interesting. Oh well, no easy way around that at the mo...
>>> + },
>>> + { /* sentinel */ },
>>> +};
>>> +
>>> +static const struct iio_chan_spec sun4i_gpadc_channels[] = {
>>> + SUN4I_GPADC_ADC_CHANNEL(0, "adc_chan0"),
>>> + SUN4I_GPADC_ADC_CHANNEL(1, "adc_chan1"),
>>> + SUN4I_GPADC_ADC_CHANNEL(2, "adc_chan2"),
>>> + SUN4I_GPADC_ADC_CHANNEL(3, "adc_chan3"),
>>> + {
>>> + .type = IIO_TEMP,
>>> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
>>> + BIT(IIO_CHAN_INFO_SCALE) |
>>> + BIT(IIO_CHAN_INFO_OFFSET),
>>> + .datasheet_name = "temp_adc",
>>> + },
>>> +};
>>> +
>>> +static const struct iio_chan_spec sun4i_gpadc_channels_no_temp[] = {
>>> + SUN4I_GPADC_ADC_CHANNEL(0, "adc_chan0"),
>>> + SUN4I_GPADC_ADC_CHANNEL(1, "adc_chan1"),
>>> + SUN4I_GPADC_ADC_CHANNEL(2, "adc_chan2"),
>>> + SUN4I_GPADC_ADC_CHANNEL(3, "adc_chan3"),
>>> +};
>>> +
>>> +static int sun4i_prepare_for_irq(struct iio_dev *indio_dev, int channel,
>>> + unsigned int 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;
>>> +}
>>> +
>>> +static int sun4i_gpadc_read(struct iio_dev *indio_dev, int channel, int *val,
>>> + unsigned int 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;
>>> + }
>>> +
>>> + if (irq == info->fifo_data_irq)
>>> + *val = info->adc_data;
>>> + else
>>> + *val = info->temp_data;
>>> +
>>> + ret = 0;
>>> + pm_runtime_mark_last_busy(indio_dev->dev.parent);
>>> +
>>> +err:
>>> + pm_runtime_put_autosuspend(indio_dev->dev.parent);
>>> + mutex_unlock(&info->mutex);
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static int sun4i_gpadc_adc_read(struct iio_dev *indio_dev, int channel,
>>> + int *val)
>>> +{
>>> + struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>>> +
>>> + return sun4i_gpadc_read(indio_dev, channel, val, info->fifo_data_irq);
>>> +}
>>> +
>>> +static int sun4i_gpadc_temp_read(struct iio_dev *indio_dev, int *val)
>>> +{
>>> + struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>>> +
>>> + return sun4i_gpadc_read(indio_dev, 0, val, info->temp_data_irq);
>>> +}
>>> +
>>> +static int sun4i_gpadc_temp_offset(struct iio_dev *indio_dev, int *val)
>>> +{
>>> + struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>>> +
>>> + *val = info->data->temp_offset;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int sun4i_gpadc_temp_scale(struct iio_dev *indio_dev, int *val)
>>> +{
>>> + struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>>> +
>>> + *val = info->data->temp_scale;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int 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;
>>> +
>>> + return IIO_VAL_INT;
>>> + default:
>>> + return -EINVAL;
>>> + }
>>> +
>>> + return -EINVAL;
>>> +}
>>> +
>>> +static const struct iio_info sun4i_gpadc_iio_info = {
>>> + .read_raw = sun4i_gpadc_read_raw,
>>> + .driver_module = THIS_MODULE,
>>> +};
>>> +
>>> +static irqreturn_t sun4i_gpadc_temp_data_irq_handler(int irq, void *dev_id)
>>> +{
>>> + struct sun4i_gpadc_iio *info = dev_id;
>>> +
>>> + if (atomic_read(&info->ignore_temp_data_irq))
>>> + goto out;
>>> +
>>> + if (!regmap_read(info->regmap, SUN4I_GPADC_TEMP_DATA, &info->temp_data))
>>> + complete(&info->completion);
>>> +
>>> +out:
>>> + disable_irq_nosync(info->temp_data_irq);
>>> + return IRQ_HANDLED;
>>> +}
>>> +
>>> +static irqreturn_t sun4i_gpadc_fifo_data_irq_handler(int irq, void *dev_id)
>>> +{
>>> + struct sun4i_gpadc_iio *info = dev_id;
>>> +
>>> + if (atomic_read(&info->ignore_fifo_data_irq))
>>> + goto out;
>>> +
>>> + if (!regmap_read(info->regmap, SUN4I_GPADC_DATA, &info->adc_data))
>>> + complete(&info->completion);
>>> +
>>> +out:
>>> + disable_irq_nosync(info->fifo_data_irq);
>>> + return IRQ_HANDLED;
>>> +}
>>> +
>>> +static int sun4i_gpadc_runtime_suspend(struct device *dev)
>>> +{
>>> + struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(dev));
>>> +
>>> + /* 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);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int sun4i_gpadc_runtime_resume(struct device *dev)
>>> +{
>>> + struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(dev));
>>> +
>>> + /* clkin = 6MHz */
>>> + regmap_write(info->regmap, SUN4I_GPADC_CTRL0,
>>> + SUN4I_GPADC_CTRL0_ADC_CLK_DIVIDER(2) |
>>> + SUN4I_GPADC_CTRL0_FS_DIV(7) |
>>> + SUN4I_GPADC_CTRL0_T_ACQ(63));
>>> + regmap_write(info->regmap, SUN4I_GPADC_CTRL1, info->data->tp_mode_en);
>>> + regmap_write(info->regmap, SUN4I_GPADC_CTRL3,
>>> + SUN4I_GPADC_CTRL3_FILTER_EN |
>>> + SUN4I_GPADC_CTRL3_FILTER_TYPE(1));
>>> + /* period = SUN4I_GPADC_TPR_TEMP_PERIOD * 256 * 16 / clkin; ~0.6s */
>>> + regmap_write(info->regmap, SUN4I_GPADC_TPR,
>>> + SUN4I_GPADC_TPR_TEMP_ENABLE |
>>> + SUN4I_GPADC_TPR_TEMP_PERIOD(800));
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int sun4i_gpadc_get_temp(void *data, int *temp)
>>> +{
>>> + struct sun4i_gpadc_iio *info = (struct sun4i_gpadc_iio *)data;
>>> + int val, scale, offset;
>>> +
>>> + if (sun4i_gpadc_temp_read(info->indio_dev, &val))
>>> + return -ETIMEDOUT;
>>> +
>>> + sun4i_gpadc_temp_scale(info->indio_dev, &scale);
>>> + sun4i_gpadc_temp_offset(info->indio_dev, &offset);
>>> +
>>> + *temp = (val + offset) * scale;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static const struct thermal_zone_of_device_ops sun4i_ts_tz_ops = {
>>> + .get_temp = &sun4i_gpadc_get_temp,
>>> +};
>>> +
>>> +static const struct dev_pm_ops sun4i_gpadc_pm_ops = {
>>> + .runtime_suspend = &sun4i_gpadc_runtime_suspend,
>>> + .runtime_resume = &sun4i_gpadc_runtime_resume,
>>> +};
>>> +
>>> +static int sun4i_irq_init(struct platform_device *pdev, const char *name,
>>> + irq_handler_t handler, const char *devname,
>>> + unsigned int *irq, atomic_t *atomic)
>>> +{
>>> + int ret;
>>> + struct sun4i_gpadc_dev *mfd_dev = dev_get_drvdata(pdev->dev.parent);
>>> + struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(&pdev->dev));
>>> +
>>> + /*
>>> + * 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) {
>>> + dev_err(&pdev->dev, "no %s interrupt registered\n", name);
>>> + 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, 0,
>>> + devname, info);
>>> + if (ret < 0) {
>>> + dev_err(&pdev->dev, "could not request %s interrupt: %d\n",
>>> + name, ret);
>>> + return ret;
>>> + }
>>> +
>>> + disable_irq(*irq);
>>> + atomic_set(atomic, 0);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int sun4i_gpadc_probe(struct platform_device *pdev)
>>> +{
>>> + struct sun4i_gpadc_iio *info;
>>> + struct iio_dev *indio_dev;
>>> + int ret;
>>> + struct sun4i_gpadc_dev *sun4i_gpadc_dev;
>>> +
>>> + sun4i_gpadc_dev = dev_get_drvdata(pdev->dev.parent);
>>> +
>>> + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info));
>>> + if (!indio_dev)
>>> + return -ENOMEM;
>>> +
>>> + info = iio_priv(indio_dev);
>>> + platform_set_drvdata(pdev, indio_dev);
>>> +
>>> + mutex_init(&info->mutex);
>>> + info->regmap = sun4i_gpadc_dev->regmap;
>>> + info->indio_dev = indio_dev;
>>> + init_completion(&info->completion);
>>> + indio_dev->name = dev_name(&pdev->dev);
>>> + indio_dev->dev.parent = &pdev->dev;
>>> + indio_dev->dev.of_node = pdev->dev.of_node;
>>> + indio_dev->info = &sun4i_gpadc_iio_info;
>>> + indio_dev->modes = INDIO_DIRECT_MODE;
>>> + indio_dev->num_channels = ARRAY_SIZE(sun4i_gpadc_channels);
>>> + indio_dev->channels = sun4i_gpadc_channels;
>>> +
>>> + info->data = (struct gpadc_data *)platform_get_device_id(pdev)->driver_data;
>>> +
>>> + /*
>>> + * 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.
>>> + */
>>> + struct thermal_zone_device *tzd;
>>> + tzd = devm_thermal_zone_of_sensor_register(pdev->dev.parent, 0,
>>> + info,
>>> + &sun4i_ts_tz_ops);
>>> + if (IS_ERR(tzd)) {
>>> + dev_err(&pdev->dev,
>>> + "could not register thermal sensor: %ld\n",
>>> + PTR_ERR(tzd));
>>> + ret = PTR_ERR(tzd);
>>> + goto err;
>>> + }
>>> + } else {
>>> + indio_dev->num_channels =
>>> + ARRAY_SIZE(sun4i_gpadc_channels_no_temp);
>>> + indio_dev->channels = sun4i_gpadc_channels_no_temp;
>>> + }
>>> +
>>> + pm_runtime_set_autosuspend_delay(&pdev->dev,
>>> + SUN4I_GPADC_AUTOSUSPEND_DELAY);
>>> + pm_runtime_use_autosuspend(&pdev->dev);
>>> + pm_runtime_set_suspended(&pdev->dev);
>>> + pm_runtime_enable(&pdev->dev);
>>> +
>>> + 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)
>>> + goto err;
>>> + }
>>> +
>>> + 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)
>>> + goto err;
>>> +
>>> + 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");
>>> + goto err;
>>> + }
>>> + }
>>> +
>>> + 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 (IS_ENABLED(CONFIG_THERMAL_OF))
>>> + iio_map_array_unregister(indio_dev);
>>> +
>>> +err:
>>> + pm_runtime_put(&pdev->dev);
>>> + pm_runtime_disable(&pdev->dev);
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static int sun4i_gpadc_remove(struct platform_device *pdev)
>>> +{
>>> + struct iio_dev *indio_dev = platform_get_drvdata(pdev);
>>> +
>>> + pm_runtime_put(&pdev->dev);
>>> + pm_runtime_disable(&pdev->dev);
>>> + if (IS_ENABLED(CONFIG_THERMAL_OF))
>>> + iio_map_array_unregister(indio_dev);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static const struct platform_device_id sun4i_gpadc_id[] = {
>>> + { "sun4i-a10-gpadc-iio", (kernel_ulong_t)&sun4i_gpadc_data },
>>> + { "sun5i-a13-gpadc-iio", (kernel_ulong_t)&sun5i_gpadc_data },
>>> + { "sun6i-a31-gpadc-iio", (kernel_ulong_t)&sun6i_gpadc_data },
>>> + { /* sentinel */ },
>>> +};
>>> +
>>> +static struct platform_driver sun4i_gpadc_driver = {
>>> + .driver = {
>>> + .name = "sun4i-gpadc-iio",
>>> + .pm = &sun4i_gpadc_pm_ops,
>>> + },
>>> + .id_table = sun4i_gpadc_id,
>>> + .probe = sun4i_gpadc_probe,
>>> + .remove = sun4i_gpadc_remove,
>>> +};
>>> +
>>> +module_platform_driver(sun4i_gpadc_driver);
>>> +
>>> +MODULE_DESCRIPTION("ADC driver for sunxi platforms");
>>> +MODULE_AUTHOR("Quentin Schulz <quentin.schulz@free-electrons.com>");
>>> +MODULE_LICENSE("GPL v2");
>>> diff --git a/include/linux/mfd/sun4i-gpadc.h b/include/linux/mfd/sun4i-gpadc.h
>>> index d7a29f2..509e736 100644
>>> --- a/include/linux/mfd/sun4i-gpadc.h
>>> +++ b/include/linux/mfd/sun4i-gpadc.h
>>> @@ -28,6 +28,7 @@
>>> #define SUN4I_GPADC_CTRL1_TP_MODE_EN BIT(4)
>>> #define SUN4I_GPADC_CTRL1_TP_ADC_SELECT BIT(3)
>>> #define SUN4I_GPADC_CTRL1_ADC_CHAN_SELECT(x) (GENMASK(2, 0) & (x))
>>> +#define SUN4I_GPADC_CTRL1_ADC_CHAN_MASK GENMASK(2, 0)
>>>
>>> /* TP_CTRL1 bits for sun6i SOCs */
>>> #define SUN6I_GPADC_CTRL1_TOUCH_PAN_CALI_EN BIT(7)
>>> @@ -35,6 +36,7 @@
>>> #define SUN6I_GPADC_CTRL1_TP_MODE_EN BIT(5)
>>> #define SUN6I_GPADC_CTRL1_TP_ADC_SELECT BIT(4)
>>> #define SUN6I_GPADC_CTRL1_ADC_CHAN_SELECT(x) (GENMASK(3, 0) & BIT(x))
>>> +#define SUN6I_GPADC_CTRL1_ADC_CHAN_MASK GENMASK(3, 0)
>>>
>>> #define SUN4I_GPADC_CTRL2 0x08
>>>
>>>
>>
>> --
>> To unsubscribe from this list: send the line "unsubscribe linux-iio" in
>> the body of a message to majordomo at vger.kernel.org
>> More majordomo info at http://vger.kernel.org/majordomo-info.html
>>
>
--
Quentin Schulz, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
^ permalink raw reply
* [PATCH v9 3/3] iio: adc: add support for Allwinner SoCs ADC
From: Jonathan Cameron @ 2017-01-14 19:28 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <9e1d1e4d-84e6-ba9d-f1da-4d2bb81c2d40@free-electrons.com>
On 14 January 2017 19:19:58 GMT+00:00, Quentin Schulz <quentin.schulz@free-electrons.com> wrote:
>Hi Jonathan,
>
>On 08/01/2017 12:17, Jonathan Cameron wrote:
>> On 30/12/16 14:40, Jonathan Cameron wrote:
>>> On 13/12/16 14:33, Quentin Schulz wrote:
>>>> The Allwinner SoCs all have an ADC that can also act as a
>touchscreen
>>>> controller and a thermal sensor. This patch adds the ADC driver
>which is
>>>> based on the MFD for the same SoCs ADC.
>>>>
>>>> This also registers the thermal adc channel in the iio map array so
>>>> iio_hwmon could use it without modifying the Device Tree. This
>registers
>>>> the driver in the thermal framework.
>>>>
>>>> The thermal sensor requires the IP to be in touchscreen mode to
>return
>>>> correct values. Therefore, if the user is continuously reading the
>ADC
>>>> channel(s), the thermal framework in which the thermal sensor is
>>>> registered will switch the IP in touchscreen mode to get a
>temperature
>>>> value and requires a delay of 100ms (because of the mode
>switching),
>>>> then the ADC will switch back to ADC mode and requires also a delay
>of
>>>> 100ms. If the ADC readings are critical to user and the SoC
>temperature
>>>> is not, this driver is capable of not registering the thermal
>sensor in
>>>> the thermal framework and thus, "quicken" the ADC readings.
>>>>
>>>> This driver probes on three different platform_device_id to take
>into
>>>> account slight differences (registers bit and temperature
>computation)
>>>> between Allwinner SoCs ADCs.
>>>>
>>>> Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
>>>> Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com>
>>>> Acked-by: Jonathan Cameron <jic23@kernel.org>
>>>> Acked-for-MFD-by: Lee Jones <lee.jones@linaro.org>
>>> One comment inline but not a blocker.
>>>
>>> I would ideally like an ack from the thermal side. The relevant
>code
>>> is small, but best to be sure and keep them in the loop as well.
>>>
>>> It does feel a little convoluted to have both this directly
>providing
>>> a thermal zone and being able to create one indirectly through hwmon
>as
>>> well but this solution works for me I think...
>>>
>>> Cc'd Zang and Eduardo.
>> Nothing seems to have come through on that front.
>>
>> I need to get a pull request out to Greg and rebase my tree before I
>have
>> the precursor patch in place. Give me a bump if you haven't heard
>anything by
>> the time next week.
>>
>
>Kindly "giving you a bump" you as requested since I haven't heard from
>you for a week.
Greg hasn't pulled yet, so may be a few more days.
J
>
>Thanks,
>Quentin
>
>> Thanks,
>>
>> Jonathan
>>>
>>> Jonathan
>>>> ---
>>>>
>>>> v9:
>>>> - clarify comment on why we have to use the parent node as node
>for
>>>> registering in thermal framework, (backward compatibility)
>>>> - clarify comment on why we can disable CONFIG_THERMAL_OF,
>>>> - clarify Kconfig help to say that CONFIG_THERMAL_OF can be
>disabled
>>>> but should not in most cases,
>>>> - make return value of devm_thermal_zone_of_sensor_register a
>local
>>>> variable of the condition block,
>>>> - correct scale from _PLUS_MICRO to _PLUS_NANO for ADC raw
>readings
>>>> scale,
>>>>
>>>> v8:
>>>> - remove Kconfig depends on !TOUCHSCREEN_SUN4I (moved to
>>>> MFD_SUN4I_GPADC),
>>>> - fix return values of regmap_irq_get_virq and
>platform_get_irq_byname
>>>> stored in an unsigned int and then check if negative,
>>>> - fix uninitialized ret value when an error occurs while
>registering
>>>> the thermal sensor in the framework,
>>>>
>>>> v7:
>>>> - add Kconfig depends on !TOUCHSCREEN_SUN4I,
>>>> - remove Kconfig selects THERMAL_OF,
>>>> - do not register thermal sensor if CONFIG_THERMAL_OF is disabled,
>>>> - disable irq in irq_handler rather than in read_raw,
>>>> - add delay when switching the IP's mode or channel (delay
>empirically found),
>>>> - quicken thermal sensor interrupt period,
>>>> - add masks for channel bits,
>>>> - fix deadlock in sun4i_gpadc_read if regmap_read/write fails,
>>>> - move some logic from sun4i_gpadc_read to sun4i_prepare_for_irq,
>>>> - mark last busy for runtime_pm only on success in
>sun4i_gpadc_read,
>>>> - remove cached values,
>>>> - increase wait_for_completion_timeout timeout to 1s to be sure to
>not miss the
>>>> thermal interrupt,
>>>> - add voltage scale,
>>>> - use devm_iio_device_register,
>>>>
>>>> v6:
>>>> - remove "-mfd" from filenames and variables inside MFD driver,
>>>> - use DEFINE_RES_IRQ_NAMED instead of setting resources manually,
>>>> - cosmetic changes,
>>>> - use IDs and switch over ID to get cells specific to an
>architecture, instead
>>>> of using cells direclty, in of_device_id.data,
>>>> - compute size of mfd_cells array instead of hardcoded one,
>>>>
>>>> v5:
>>>> - correct mail address,
>>>>
>>>> v4:
>>>> - rename files and variables from sunxi* to sun4i*,
>>>> - rename defines from SUNXI_* to SUN4I_* or SUN6I_*,
>>>> - remove TP in defines name,
>>>> - rename SUNXI_IRQ_* to SUN4I_GPADC_IRQ_* for consistency,
>>>> - use devm functions for regmap_add_irq_chip and mfd_add_devices,
>>>> - remove remove functions (now empty thanks to devm functions),
>>>>
>>>> v3:
>>>> - use defines in regmap_irq instead of hard coded BITs,
>>>> - use of_device_id data field to chose which MFD cells to add
>considering
>>>> the compatible responsible of the MFD probe,
>>>> - remove useless initializations,
>>>> - disable all interrupts before adding them to regmap_irqchip,
>>>> - add goto error label in probe,
>>>> - correct wrapping in header license,
>>>> - move defines from IIO driver to header,
>>>> - use GENMASK to limit the size of the variable passed to a macro,
>>>> - prefix register BIT defines with the name of the register,
>>>> - reorder defines,
>>>>
>>>> v2:
>>>> - add license headers,
>>>> - reorder alphabetically includes,
>>>> - add SUNXI_GPADC_ prefixes for defines,
>>>>
>>>> drivers/iio/adc/Kconfig | 17 ++
>>>> drivers/iio/adc/Makefile | 1 +
>>>> drivers/iio/adc/sun4i-gpadc-iio.c | 613
>++++++++++++++++++++++++++++++++++++++
>>>> include/linux/mfd/sun4i-gpadc.h | 2 +
>>>> 4 files changed, 633 insertions(+)
>>>> create mode 100644 drivers/iio/adc/sun4i-gpadc-iio.c
>>>>
>>>> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
>>>> index 99c0514..6a6d369 100644
>>>> --- a/drivers/iio/adc/Kconfig
>>>> +++ b/drivers/iio/adc/Kconfig
>>>> @@ -434,6 +434,23 @@ config STX104
>>>> The base port addresses for the devices may be configured via
>the base
>>>> array module parameter.
>>>>
>>>> +config SUN4I_GPADC
>>>> + tristate "Support for the Allwinner SoCs GPADC"
>>>> + depends on IIO
>>>> + depends on MFD_SUN4I_GPADC
>>>> + help
>>>> + Say yes here to build support for Allwinner (A10, A13 and A31)
>SoCs
>>>> + GPADC. This ADC provides 4 channels which can be used as an ADC
>or as
>>>> + a touchscreen input and one channel for thermal sensor.
>>>> +
>>>> + The thermal sensor slows down ADC readings and can be disabled
>by
>>>> + disabling CONFIG_THERMAL_OF. However, the thermal sensor should
>be
>>>> + enabled by default since the SoC temperature is usually more
>critical
>>>> + than ADC readings.
>>>> +
>>>> + To compile this driver as a module, choose M here: the module
>will be
>>>> + called sun4i-gpadc-iio.
>>>> +
>>>> config TI_ADC081C
>>>> tristate "Texas Instruments ADC081C/ADC101C/ADC121C family"
>>>> depends on I2C
>>>> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
>>>> index 7a40c04..18ce8d6 100644
>>>> --- a/drivers/iio/adc/Makefile
>>>> +++ b/drivers/iio/adc/Makefile
>>>> @@ -41,6 +41,7 @@ obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o
>>>> obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o
>>>> obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o
>>>> obj-$(CONFIG_STX104) += stx104.o
>>>> +obj-$(CONFIG_SUN4I_GPADC) += sun4i-gpadc-iio.o
>>>> obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
>>>> obj-$(CONFIG_TI_ADC0832) += ti-adc0832.o
>>>> obj-$(CONFIG_TI_ADC12138) += ti-adc12138.o
>>>> diff --git a/drivers/iio/adc/sun4i-gpadc-iio.c
>b/drivers/iio/adc/sun4i-gpadc-iio.c
>>>> new file mode 100644
>>>> index 0000000..a8e134f
>>>> --- /dev/null
>>>> +++ b/drivers/iio/adc/sun4i-gpadc-iio.c
>>>> @@ -0,0 +1,613 @@
>>>> +/* ADC driver for sunxi platforms' (A10, A13 and A31) GPADC
>>>> + *
>>>> + * Copyright (c) 2016 Quentin Schulz
><quentin.schulz@free-electrons.com>
>>>> + *
>>>> + * This program is free software; you can redistribute it and/or
>modify it under
>>>> + * the terms of the GNU General Public License version 2 as
>published by the
>>>> + * Free Software Foundation.
>>>> + *
>>>> + * 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.
>>>> + */
>>>> +
>>>> +#include <linux/completion.h>
>>>> +#include <linux/interrupt.h>
>>>> +#include <linux/io.h>
>>>> +#include <linux/module.h>
>>>> +#include <linux/of.h>
>>>> +#include <linux/of_device.h>
>>>> +#include <linux/platform_device.h>
>>>> +#include <linux/pm_runtime.h>
>>>> +#include <linux/regmap.h>
>>>> +#include <linux/thermal.h>
>>>> +#include <linux/delay.h>
>>>> +
>>>> +#include <linux/iio/iio.h>
>>>> +#include <linux/iio/driver.h>
>>>> +#include <linux/iio/machine.h>
>>>> +#include <linux/mfd/sun4i-gpadc.h>
>>>> +
>>>> +static unsigned int sun4i_gpadc_chan_select(unsigned int chan)
>>>> +{
>>>> + return SUN4I_GPADC_CTRL1_ADC_CHAN_SELECT(chan);
>>>> +}
>>>> +
>>>> +static unsigned int sun6i_gpadc_chan_select(unsigned int chan)
>>>> +{
>>>> + return SUN6I_GPADC_CTRL1_ADC_CHAN_SELECT(chan);
>>>> +}
>>>> +
>>>> +struct gpadc_data {
>>>> + int temp_offset;
>>>> + int temp_scale;
>>>> + unsigned int tp_mode_en;
>>>> + unsigned int tp_adc_select;
>>>> + unsigned int (*adc_chan_select)(unsigned int chan);
>>>> + unsigned int adc_chan_mask;
>>>> +};
>>>> +
>>>> +static const struct gpadc_data sun4i_gpadc_data = {
>>>> + .temp_offset = -1932,
>>>> + .temp_scale = 133,
>>>> + .tp_mode_en = SUN4I_GPADC_CTRL1_TP_MODE_EN,
>>>> + .tp_adc_select = SUN4I_GPADC_CTRL1_TP_ADC_SELECT,
>>>> + .adc_chan_select = &sun4i_gpadc_chan_select,
>>>> + .adc_chan_mask = SUN4I_GPADC_CTRL1_ADC_CHAN_MASK,
>>>> +};
>>>> +
>>>> +static const struct gpadc_data sun5i_gpadc_data = {
>>>> + .temp_offset = -1447,
>>>> + .temp_scale = 100,
>>>> + .tp_mode_en = SUN4I_GPADC_CTRL1_TP_MODE_EN,
>>>> + .tp_adc_select = SUN4I_GPADC_CTRL1_TP_ADC_SELECT,
>>>> + .adc_chan_select = &sun4i_gpadc_chan_select,
>>>> + .adc_chan_mask = SUN4I_GPADC_CTRL1_ADC_CHAN_MASK,
>>>> +};
>>>> +
>>>> +static const struct gpadc_data sun6i_gpadc_data = {
>>>> + .temp_offset = -1623,
>>>> + .temp_scale = 167,
>>>> + .tp_mode_en = SUN6I_GPADC_CTRL1_TP_MODE_EN,
>>>> + .tp_adc_select = SUN6I_GPADC_CTRL1_TP_ADC_SELECT,
>>>> + .adc_chan_select = &sun6i_gpadc_chan_select,
>>>> + .adc_chan_mask = SUN6I_GPADC_CTRL1_ADC_CHAN_MASK,
>>>> +};
>>>> +
>>>> +struct sun4i_gpadc_iio {
>>>> + struct iio_dev *indio_dev;
>>>> + struct completion completion;
>>>> + int temp_data;
>>>> + u32 adc_data;
>>>> + struct regmap *regmap;
>>>> + unsigned int fifo_data_irq;
>>>> + atomic_t ignore_fifo_data_irq;
>>>> + unsigned int temp_data_irq;
>>>> + atomic_t ignore_temp_data_irq;
>>>> + const struct gpadc_data *data;
>>>> + /* prevents concurrent reads of temperature and ADC */
>>>> + struct mutex mutex;
>>>> +};
>>>> +
>>>> +#define SUN4I_GPADC_ADC_CHANNEL(_channel, _name) { \
>>>> + .type = IIO_VOLTAGE, \
>>>> + .indexed = 1, \
>>>> + .channel = _channel, \
>>>> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
>>>> + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
>>>> + .datasheet_name = _name, \
>>>> +}
>>>> +
>>>> +static struct iio_map sun4i_gpadc_hwmon_maps[] = {
>>>> + {
>>>> + .adc_channel_label = "temp_adc",
>>>> + .consumer_dev_name = "iio_hwmon.0",
>>> It's theoretically possible we have another one of these which will
>make
>>> life interesting. Oh well, no easy way around that at the mo...
>>>> + },
>>>> + { /* sentinel */ },
>>>> +};
>>>> +
>>>> +static const struct iio_chan_spec sun4i_gpadc_channels[] = {
>>>> + SUN4I_GPADC_ADC_CHANNEL(0, "adc_chan0"),
>>>> + SUN4I_GPADC_ADC_CHANNEL(1, "adc_chan1"),
>>>> + SUN4I_GPADC_ADC_CHANNEL(2, "adc_chan2"),
>>>> + SUN4I_GPADC_ADC_CHANNEL(3, "adc_chan3"),
>>>> + {
>>>> + .type = IIO_TEMP,
>>>> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
>>>> + BIT(IIO_CHAN_INFO_SCALE) |
>>>> + BIT(IIO_CHAN_INFO_OFFSET),
>>>> + .datasheet_name = "temp_adc",
>>>> + },
>>>> +};
>>>> +
>>>> +static const struct iio_chan_spec sun4i_gpadc_channels_no_temp[] =
>{
>>>> + SUN4I_GPADC_ADC_CHANNEL(0, "adc_chan0"),
>>>> + SUN4I_GPADC_ADC_CHANNEL(1, "adc_chan1"),
>>>> + SUN4I_GPADC_ADC_CHANNEL(2, "adc_chan2"),
>>>> + SUN4I_GPADC_ADC_CHANNEL(3, "adc_chan3"),
>>>> +};
>>>> +
>>>> +static int sun4i_prepare_for_irq(struct iio_dev *indio_dev, int
>channel,
>>>> + unsigned int 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;
>>>> +}
>>>> +
>>>> +static int sun4i_gpadc_read(struct iio_dev *indio_dev, int
>channel, int *val,
>>>> + unsigned int 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;
>>>> + }
>>>> +
>>>> + if (irq == info->fifo_data_irq)
>>>> + *val = info->adc_data;
>>>> + else
>>>> + *val = info->temp_data;
>>>> +
>>>> + ret = 0;
>>>> + pm_runtime_mark_last_busy(indio_dev->dev.parent);
>>>> +
>>>> +err:
>>>> + pm_runtime_put_autosuspend(indio_dev->dev.parent);
>>>> + mutex_unlock(&info->mutex);
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static int sun4i_gpadc_adc_read(struct iio_dev *indio_dev, int
>channel,
>>>> + int *val)
>>>> +{
>>>> + struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>>>> +
>>>> + return sun4i_gpadc_read(indio_dev, channel, val,
>info->fifo_data_irq);
>>>> +}
>>>> +
>>>> +static int sun4i_gpadc_temp_read(struct iio_dev *indio_dev, int
>*val)
>>>> +{
>>>> + struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>>>> +
>>>> + return sun4i_gpadc_read(indio_dev, 0, val, info->temp_data_irq);
>>>> +}
>>>> +
>>>> +static int sun4i_gpadc_temp_offset(struct iio_dev *indio_dev, int
>*val)
>>>> +{
>>>> + struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>>>> +
>>>> + *val = info->data->temp_offset;
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int sun4i_gpadc_temp_scale(struct iio_dev *indio_dev, int
>*val)
>>>> +{
>>>> + struct sun4i_gpadc_iio *info = iio_priv(indio_dev);
>>>> +
>>>> + *val = info->data->temp_scale;
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int 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;
>>>> +
>>>> + return IIO_VAL_INT;
>>>> + default:
>>>> + return -EINVAL;
>>>> + }
>>>> +
>>>> + return -EINVAL;
>>>> +}
>>>> +
>>>> +static const struct iio_info sun4i_gpadc_iio_info = {
>>>> + .read_raw = sun4i_gpadc_read_raw,
>>>> + .driver_module = THIS_MODULE,
>>>> +};
>>>> +
>>>> +static irqreturn_t sun4i_gpadc_temp_data_irq_handler(int irq, void
>*dev_id)
>>>> +{
>>>> + struct sun4i_gpadc_iio *info = dev_id;
>>>> +
>>>> + if (atomic_read(&info->ignore_temp_data_irq))
>>>> + goto out;
>>>> +
>>>> + if (!regmap_read(info->regmap, SUN4I_GPADC_TEMP_DATA,
>&info->temp_data))
>>>> + complete(&info->completion);
>>>> +
>>>> +out:
>>>> + disable_irq_nosync(info->temp_data_irq);
>>>> + return IRQ_HANDLED;
>>>> +}
>>>> +
>>>> +static irqreturn_t sun4i_gpadc_fifo_data_irq_handler(int irq, void
>*dev_id)
>>>> +{
>>>> + struct sun4i_gpadc_iio *info = dev_id;
>>>> +
>>>> + if (atomic_read(&info->ignore_fifo_data_irq))
>>>> + goto out;
>>>> +
>>>> + if (!regmap_read(info->regmap, SUN4I_GPADC_DATA,
>&info->adc_data))
>>>> + complete(&info->completion);
>>>> +
>>>> +out:
>>>> + disable_irq_nosync(info->fifo_data_irq);
>>>> + return IRQ_HANDLED;
>>>> +}
>>>> +
>>>> +static int sun4i_gpadc_runtime_suspend(struct device *dev)
>>>> +{
>>>> + struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(dev));
>>>> +
>>>> + /* 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);
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int sun4i_gpadc_runtime_resume(struct device *dev)
>>>> +{
>>>> + struct sun4i_gpadc_iio *info = iio_priv(dev_get_drvdata(dev));
>>>> +
>>>> + /* clkin = 6MHz */
>>>> + regmap_write(info->regmap, SUN4I_GPADC_CTRL0,
>>>> + SUN4I_GPADC_CTRL0_ADC_CLK_DIVIDER(2) |
>>>> + SUN4I_GPADC_CTRL0_FS_DIV(7) |
>>>> + SUN4I_GPADC_CTRL0_T_ACQ(63));
>>>> + regmap_write(info->regmap, SUN4I_GPADC_CTRL1,
>info->data->tp_mode_en);
>>>> + regmap_write(info->regmap, SUN4I_GPADC_CTRL3,
>>>> + SUN4I_GPADC_CTRL3_FILTER_EN |
>>>> + SUN4I_GPADC_CTRL3_FILTER_TYPE(1));
>>>> + /* period = SUN4I_GPADC_TPR_TEMP_PERIOD * 256 * 16 / clkin; ~0.6s
>*/
>>>> + regmap_write(info->regmap, SUN4I_GPADC_TPR,
>>>> + SUN4I_GPADC_TPR_TEMP_ENABLE |
>>>> + SUN4I_GPADC_TPR_TEMP_PERIOD(800));
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int sun4i_gpadc_get_temp(void *data, int *temp)
>>>> +{
>>>> + struct sun4i_gpadc_iio *info = (struct sun4i_gpadc_iio *)data;
>>>> + int val, scale, offset;
>>>> +
>>>> + if (sun4i_gpadc_temp_read(info->indio_dev, &val))
>>>> + return -ETIMEDOUT;
>>>> +
>>>> + sun4i_gpadc_temp_scale(info->indio_dev, &scale);
>>>> + sun4i_gpadc_temp_offset(info->indio_dev, &offset);
>>>> +
>>>> + *temp = (val + offset) * scale;
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static const struct thermal_zone_of_device_ops sun4i_ts_tz_ops = {
>>>> + .get_temp = &sun4i_gpadc_get_temp,
>>>> +};
>>>> +
>>>> +static const struct dev_pm_ops sun4i_gpadc_pm_ops = {
>>>> + .runtime_suspend = &sun4i_gpadc_runtime_suspend,
>>>> + .runtime_resume = &sun4i_gpadc_runtime_resume,
>>>> +};
>>>> +
>>>> +static int sun4i_irq_init(struct platform_device *pdev, const char
>*name,
>>>> + irq_handler_t handler, const char *devname,
>>>> + unsigned int *irq, atomic_t *atomic)
>>>> +{
>>>> + int ret;
>>>> + struct sun4i_gpadc_dev *mfd_dev =
>dev_get_drvdata(pdev->dev.parent);
>>>> + struct sun4i_gpadc_iio *info =
>iio_priv(dev_get_drvdata(&pdev->dev));
>>>> +
>>>> + /*
>>>> + * 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) {
>>>> + dev_err(&pdev->dev, "no %s interrupt registered\n", name);
>>>> + 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, 0,
>>>> + devname, info);
>>>> + if (ret < 0) {
>>>> + dev_err(&pdev->dev, "could not request %s interrupt: %d\n",
>>>> + name, ret);
>>>> + return ret;
>>>> + }
>>>> +
>>>> + disable_irq(*irq);
>>>> + atomic_set(atomic, 0);
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int sun4i_gpadc_probe(struct platform_device *pdev)
>>>> +{
>>>> + struct sun4i_gpadc_iio *info;
>>>> + struct iio_dev *indio_dev;
>>>> + int ret;
>>>> + struct sun4i_gpadc_dev *sun4i_gpadc_dev;
>>>> +
>>>> + sun4i_gpadc_dev = dev_get_drvdata(pdev->dev.parent);
>>>> +
>>>> + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info));
>>>> + if (!indio_dev)
>>>> + return -ENOMEM;
>>>> +
>>>> + info = iio_priv(indio_dev);
>>>> + platform_set_drvdata(pdev, indio_dev);
>>>> +
>>>> + mutex_init(&info->mutex);
>>>> + info->regmap = sun4i_gpadc_dev->regmap;
>>>> + info->indio_dev = indio_dev;
>>>> + init_completion(&info->completion);
>>>> + indio_dev->name = dev_name(&pdev->dev);
>>>> + indio_dev->dev.parent = &pdev->dev;
>>>> + indio_dev->dev.of_node = pdev->dev.of_node;
>>>> + indio_dev->info = &sun4i_gpadc_iio_info;
>>>> + indio_dev->modes = INDIO_DIRECT_MODE;
>>>> + indio_dev->num_channels = ARRAY_SIZE(sun4i_gpadc_channels);
>>>> + indio_dev->channels = sun4i_gpadc_channels;
>>>> +
>>>> + info->data = (struct gpadc_data
>*)platform_get_device_id(pdev)->driver_data;
>>>> +
>>>> + /*
>>>> + * 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.
>>>> + */
>>>> + struct thermal_zone_device *tzd;
>>>> + tzd = devm_thermal_zone_of_sensor_register(pdev->dev.parent, 0,
>>>> + info,
>>>> + &sun4i_ts_tz_ops);
>>>> + if (IS_ERR(tzd)) {
>>>> + dev_err(&pdev->dev,
>>>> + "could not register thermal sensor: %ld\n",
>>>> + PTR_ERR(tzd));
>>>> + ret = PTR_ERR(tzd);
>>>> + goto err;
>>>> + }
>>>> + } else {
>>>> + indio_dev->num_channels =
>>>> + ARRAY_SIZE(sun4i_gpadc_channels_no_temp);
>>>> + indio_dev->channels = sun4i_gpadc_channels_no_temp;
>>>> + }
>>>> +
>>>> + pm_runtime_set_autosuspend_delay(&pdev->dev,
>>>> + SUN4I_GPADC_AUTOSUSPEND_DELAY);
>>>> + pm_runtime_use_autosuspend(&pdev->dev);
>>>> + pm_runtime_set_suspended(&pdev->dev);
>>>> + pm_runtime_enable(&pdev->dev);
>>>> +
>>>> + 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)
>>>> + goto err;
>>>> + }
>>>> +
>>>> + 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)
>>>> + goto err;
>>>> +
>>>> + 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");
>>>> + goto err;
>>>> + }
>>>> + }
>>>> +
>>>> + 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 (IS_ENABLED(CONFIG_THERMAL_OF))
>>>> + iio_map_array_unregister(indio_dev);
>>>> +
>>>> +err:
>>>> + pm_runtime_put(&pdev->dev);
>>>> + pm_runtime_disable(&pdev->dev);
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static int sun4i_gpadc_remove(struct platform_device *pdev)
>>>> +{
>>>> + struct iio_dev *indio_dev = platform_get_drvdata(pdev);
>>>> +
>>>> + pm_runtime_put(&pdev->dev);
>>>> + pm_runtime_disable(&pdev->dev);
>>>> + if (IS_ENABLED(CONFIG_THERMAL_OF))
>>>> + iio_map_array_unregister(indio_dev);
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static const struct platform_device_id sun4i_gpadc_id[] = {
>>>> + { "sun4i-a10-gpadc-iio", (kernel_ulong_t)&sun4i_gpadc_data },
>>>> + { "sun5i-a13-gpadc-iio", (kernel_ulong_t)&sun5i_gpadc_data },
>>>> + { "sun6i-a31-gpadc-iio", (kernel_ulong_t)&sun6i_gpadc_data },
>>>> + { /* sentinel */ },
>>>> +};
>>>> +
>>>> +static struct platform_driver sun4i_gpadc_driver = {
>>>> + .driver = {
>>>> + .name = "sun4i-gpadc-iio",
>>>> + .pm = &sun4i_gpadc_pm_ops,
>>>> + },
>>>> + .id_table = sun4i_gpadc_id,
>>>> + .probe = sun4i_gpadc_probe,
>>>> + .remove = sun4i_gpadc_remove,
>>>> +};
>>>> +
>>>> +module_platform_driver(sun4i_gpadc_driver);
>>>> +
>>>> +MODULE_DESCRIPTION("ADC driver for sunxi platforms");
>>>> +MODULE_AUTHOR("Quentin Schulz
><quentin.schulz@free-electrons.com>");
>>>> +MODULE_LICENSE("GPL v2");
>>>> diff --git a/include/linux/mfd/sun4i-gpadc.h
>b/include/linux/mfd/sun4i-gpadc.h
>>>> index d7a29f2..509e736 100644
>>>> --- a/include/linux/mfd/sun4i-gpadc.h
>>>> +++ b/include/linux/mfd/sun4i-gpadc.h
>>>> @@ -28,6 +28,7 @@
>>>> #define SUN4I_GPADC_CTRL1_TP_MODE_EN BIT(4)
>>>> #define SUN4I_GPADC_CTRL1_TP_ADC_SELECT BIT(3)
>>>> #define SUN4I_GPADC_CTRL1_ADC_CHAN_SELECT(x) (GENMASK(2, 0) &
>(x))
>>>> +#define SUN4I_GPADC_CTRL1_ADC_CHAN_MASK GENMASK(2, 0)
>>>>
>>>> /* TP_CTRL1 bits for sun6i SOCs */
>>>> #define SUN6I_GPADC_CTRL1_TOUCH_PAN_CALI_EN BIT(7)
>>>> @@ -35,6 +36,7 @@
>>>> #define SUN6I_GPADC_CTRL1_TP_MODE_EN BIT(5)
>>>> #define SUN6I_GPADC_CTRL1_TP_ADC_SELECT BIT(4)
>>>> #define SUN6I_GPADC_CTRL1_ADC_CHAN_SELECT(x) (GENMASK(3, 0) &
>BIT(x))
>>>> +#define SUN6I_GPADC_CTRL1_ADC_CHAN_MASK GENMASK(3, 0)
>>>>
>>>> #define SUN4I_GPADC_CTRL2 0x08
>>>>
>>>>
>>>
>>> --
>>> To unsubscribe from this list: send the line "unsubscribe linux-iio"
>in
>>> the body of a message to majordomo at vger.kernel.org
>>> More majordomo info at http://vger.kernel.org/majordomo-info.html
>>>
>>
--
Sent from my Android device with K-9 Mail. Please excuse my brevity.
^ permalink raw reply
* [PATCH net-next v3 00/10] net: dsa: Support for pdata in dsa2
From: Florian Fainelli @ 2017-01-14 21:47 UTC (permalink / raw)
To: linux-arm-kernel
Hi all,
This is not exactly new, and was sent before, although back then, I did not
have an user of the pre-declared MDIO board information, but now we do. Note
that I have additional changes queued up to have b53 register platform data for
MIPS bcm47xx and bcm63xx.
Yes I know that we should have the Orion platforms eventually be converted to
Device Tree, but until that happens, I don't want any remaining users of the
old "dsa" platform device (hence the previous DTS submissions for ARM/mvebu)
and, there will be platforms out there that most likely won't never see DT
coming their way (BCM47xx is almost 100% sure, BCM63xx maybe not in a distant
future).
We would probably want the whole series to be merged via David Miller's tree
to simplify things.
Greg, can you Ack/Nack patch 5 since it touched the core LDD?
Vivien, since some patches did change, I did not apply your Tested-by tag
to all patches.
Thanks!
Changes in v3:
- Tested EPROBE_DEFER from a mockup MDIO/DSA switch driver and everything
is fine, once the driver finally probes we have access to platform data
as expected
- added comment above dsa_port_is_valid() that port->name is mandatory
for platform data cases
- added an extra check in dsa_parse_member() for a NULL pdata pointer
- fixed a bunch of checkpatch errors and warnings
Changes in v2:
- Rebased against latest net-next/master
- Moved dev_find_class() to device_find_class() into drivers/base/core.c
- Moved dev_to_net_device into net/core/dev.c
- Utilize dsa_chip_data directly instead of dsa_platform_data
- Augmented dsa_chip_data to be multi-CPU port ready
Changes from last submission (few months back):
- rebased against latest net-next
- do not introduce dsa2_platform_data which was overkill and was meant to
allow us to do exaclty the same things with platform data and Device Tree
we use the existing dsa_platform_data instead
- properly register MDIO devices when the MDIO bus is registered and associate
platform_data with them
- add a change to the Orion platform code to demonstrate how this can be used
Thank you
Florian Fainelli (10):
net: dsa: Pass device pointer to dsa_register_switch
net: dsa: Make most functions take a dsa_port argument
net: dsa: Suffix function manipulating device_node with _dn
net: dsa: Move ports assignment closer to error checking
drivers: base: Add device_find_class()
net: dsa: Migrate to device_find_class()
net: Relocate dev_to_net_device() into core
net: dsa: Add support for platform data
net: phy: Allow pre-declaration of MDIO devices
ARM: orion: Register DSA switch as a MDIO device
arch/arm/mach-orion5x/common.c | 2 +-
arch/arm/mach-orion5x/common.h | 4 +-
arch/arm/mach-orion5x/rd88f5181l-fxo-setup.c | 7 +-
arch/arm/mach-orion5x/rd88f5181l-ge-setup.c | 7 +-
arch/arm/mach-orion5x/rd88f6183ap-ge-setup.c | 7 +-
arch/arm/mach-orion5x/wnr854t-setup.c | 2 +-
arch/arm/mach-orion5x/wrt350n-v2-setup.c | 7 +-
arch/arm/plat-orion/common.c | 25 +++-
arch/arm/plat-orion/include/plat/common.h | 4 +-
drivers/base/core.c | 19 +++
drivers/net/dsa/b53/b53_common.c | 2 +-
drivers/net/dsa/mv88e6xxx/chip.c | 11 +-
drivers/net/dsa/qca8k.c | 2 +-
drivers/net/phy/Makefile | 3 +-
drivers/net/phy/mdio-boardinfo.c | 86 +++++++++++++
drivers/net/phy/mdio-boardinfo.h | 19 +++
drivers/net/phy/mdio_bus.c | 4 +
drivers/net/phy/mdio_device.c | 11 ++
include/linux/device.h | 1 +
include/linux/mdio.h | 3 +
include/linux/mod_devicetable.h | 1 +
include/linux/netdevice.h | 2 +
include/linux/phy.h | 19 +++
include/net/dsa.h | 8 +-
net/core/dev.c | 19 +++
net/dsa/dsa.c | 53 ++------
net/dsa/dsa2.c | 174 +++++++++++++++++++--------
net/dsa/dsa_priv.h | 4 +-
28 files changed, 365 insertions(+), 141 deletions(-)
create mode 100644 drivers/net/phy/mdio-boardinfo.c
create mode 100644 drivers/net/phy/mdio-boardinfo.h
--
2.9.3
^ permalink raw reply
* [PATCH net-next v3 01/10] net: dsa: Pass device pointer to dsa_register_switch
From: Florian Fainelli @ 2017-01-14 21:47 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170114214713.28109-1-f.fainelli@gmail.com>
In preparation for allowing dsa_register_switch() to be supplied with
device/platform data, pass down a struct device pointer instead of a
struct device_node.
Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
---
drivers/net/dsa/b53/b53_common.c | 2 +-
drivers/net/dsa/mv88e6xxx/chip.c | 11 ++++++-----
drivers/net/dsa/qca8k.c | 2 +-
include/net/dsa.h | 2 +-
net/dsa/dsa2.c | 7 ++++---
5 files changed, 13 insertions(+), 11 deletions(-)
diff --git a/drivers/net/dsa/b53/b53_common.c b/drivers/net/dsa/b53/b53_common.c
index 5102a3701a1a..7179eed9ee6d 100644
--- a/drivers/net/dsa/b53/b53_common.c
+++ b/drivers/net/dsa/b53/b53_common.c
@@ -1882,7 +1882,7 @@ int b53_switch_register(struct b53_device *dev)
pr_info("found switch: %s, rev %i\n", dev->name, dev->core_rev);
- return dsa_register_switch(dev->ds, dev->ds->dev->of_node);
+ return dsa_register_switch(dev->ds, dev->ds->dev);
}
EXPORT_SYMBOL(b53_switch_register);
diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 987b2dbbd35a..3238a4752b98 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -4421,8 +4421,7 @@ static struct dsa_switch_driver mv88e6xxx_switch_drv = {
.ops = &mv88e6xxx_switch_ops,
};
-static int mv88e6xxx_register_switch(struct mv88e6xxx_chip *chip,
- struct device_node *np)
+static int mv88e6xxx_register_switch(struct mv88e6xxx_chip *chip)
{
struct device *dev = chip->dev;
struct dsa_switch *ds;
@@ -4437,7 +4436,7 @@ static int mv88e6xxx_register_switch(struct mv88e6xxx_chip *chip,
dev_set_drvdata(dev, ds);
- return dsa_register_switch(ds, np);
+ return dsa_register_switch(ds, dev);
}
static void mv88e6xxx_unregister_switch(struct mv88e6xxx_chip *chip)
@@ -4521,9 +4520,11 @@ static int mv88e6xxx_probe(struct mdio_device *mdiodev)
if (err)
goto out_g2_irq;
- err = mv88e6xxx_register_switch(chip, np);
- if (err)
+ err = mv88e6xxx_register_switch(chip);
+ if (err) {
+ mv88e6xxx_mdio_unregister(chip);
goto out_mdio;
+ }
return 0;
diff --git a/drivers/net/dsa/qca8k.c b/drivers/net/dsa/qca8k.c
index 54d270d59eb0..c084aa484d2b 100644
--- a/drivers/net/dsa/qca8k.c
+++ b/drivers/net/dsa/qca8k.c
@@ -964,7 +964,7 @@ qca8k_sw_probe(struct mdio_device *mdiodev)
mutex_init(&priv->reg_mutex);
dev_set_drvdata(&mdiodev->dev, priv);
- return dsa_register_switch(priv->ds, priv->ds->dev->of_node);
+ return dsa_register_switch(priv->ds, &mdiodev->dev);
}
static void
diff --git a/include/net/dsa.h b/include/net/dsa.h
index b94d1f2ef912..16a502a6c26a 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -403,7 +403,7 @@ static inline bool dsa_uses_tagged_protocol(struct dsa_switch_tree *dst)
}
void dsa_unregister_switch(struct dsa_switch *ds);
-int dsa_register_switch(struct dsa_switch *ds, struct device_node *np);
+int dsa_register_switch(struct dsa_switch *ds, struct device *dev);
#ifdef CONFIG_PM_SLEEP
int dsa_switch_suspend(struct dsa_switch *ds);
int dsa_switch_resume(struct dsa_switch *ds);
diff --git a/net/dsa/dsa2.c b/net/dsa/dsa2.c
index 42a41d84053c..4170f7ea8e28 100644
--- a/net/dsa/dsa2.c
+++ b/net/dsa/dsa2.c
@@ -579,8 +579,9 @@ static struct device_node *dsa_get_ports(struct dsa_switch *ds,
return ports;
}
-static int _dsa_register_switch(struct dsa_switch *ds, struct device_node *np)
+static int _dsa_register_switch(struct dsa_switch *ds, struct device *dev)
{
+ struct device_node *np = dev->of_node;
struct device_node *ports = dsa_get_ports(ds, np);
struct dsa_switch_tree *dst;
u32 tree, index;
@@ -660,12 +661,12 @@ static int _dsa_register_switch(struct dsa_switch *ds, struct device_node *np)
return err;
}
-int dsa_register_switch(struct dsa_switch *ds, struct device_node *np)
+int dsa_register_switch(struct dsa_switch *ds, struct device *dev)
{
int err;
mutex_lock(&dsa2_mutex);
- err = _dsa_register_switch(ds, np);
+ err = _dsa_register_switch(ds, dev);
mutex_unlock(&dsa2_mutex);
return err;
--
2.9.3
^ permalink raw reply related
* [PATCH net-next v3 02/10] net: dsa: Make most functions take a dsa_port argument
From: Florian Fainelli @ 2017-01-14 21:47 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170114214713.28109-1-f.fainelli@gmail.com>
In preparation for allowing platform data, and therefore no valid
device_node pointer, make most DSA functions takes a pointer to a
dsa_port structure whenever possible. While at it, introduce a
dsa_port_is_valid() helper function which checks whether port->dn is
NULL or not at the moment.
Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
---
net/dsa/dsa.c | 15 ++++++++------
net/dsa/dsa2.c | 61 +++++++++++++++++++++++++++++-------------------------
net/dsa/dsa_priv.h | 4 ++--
3 files changed, 44 insertions(+), 36 deletions(-)
diff --git a/net/dsa/dsa.c b/net/dsa/dsa.c
index fd532487dfdf..2306d1b87c83 100644
--- a/net/dsa/dsa.c
+++ b/net/dsa/dsa.c
@@ -110,8 +110,9 @@ dsa_switch_probe(struct device *parent, struct device *host_dev, int sw_addr,
/* basic switch operations **************************************************/
int dsa_cpu_dsa_setup(struct dsa_switch *ds, struct device *dev,
- struct device_node *port_dn, int port)
+ struct dsa_port *dport, int port)
{
+ struct device_node *port_dn = dport->dn;
struct phy_device *phydev;
int ret, mode;
@@ -141,15 +142,15 @@ int dsa_cpu_dsa_setup(struct dsa_switch *ds, struct device *dev,
static int dsa_cpu_dsa_setups(struct dsa_switch *ds, struct device *dev)
{
- struct device_node *port_dn;
+ struct dsa_port *dport;
int ret, port;
for (port = 0; port < DSA_MAX_PORTS; port++) {
if (!(dsa_is_cpu_port(ds, port) || dsa_is_dsa_port(ds, port)))
continue;
- port_dn = ds->ports[port].dn;
- ret = dsa_cpu_dsa_setup(ds, dev, port_dn, port);
+ dport = &ds->ports[port];
+ ret = dsa_cpu_dsa_setup(ds, dev, dport, port);
if (ret)
return ret;
}
@@ -366,8 +367,10 @@ dsa_switch_setup(struct dsa_switch_tree *dst, int index,
return ds;
}
-void dsa_cpu_dsa_destroy(struct device_node *port_dn)
+void dsa_cpu_dsa_destroy(struct dsa_port *port)
{
+ struct device_node *port_dn = port->dn;
+
if (of_phy_is_fixed_link(port_dn))
of_phy_deregister_fixed_link(port_dn);
}
@@ -393,7 +396,7 @@ static void dsa_switch_destroy(struct dsa_switch *ds)
for (port = 0; port < DSA_MAX_PORTS; port++) {
if (!(dsa_is_cpu_port(ds, port) || dsa_is_dsa_port(ds, port)))
continue;
- dsa_cpu_dsa_destroy(ds->ports[port].dn);
+ dsa_cpu_dsa_destroy(&ds->ports[port]);
/* Clearing a bit which is not set does no harm */
ds->cpu_port_mask |= ~(1 << port);
diff --git a/net/dsa/dsa2.c b/net/dsa/dsa2.c
index 4170f7ea8e28..6e3675220fef 100644
--- a/net/dsa/dsa2.c
+++ b/net/dsa/dsa2.c
@@ -79,14 +79,19 @@ static void dsa_dst_del_ds(struct dsa_switch_tree *dst,
kref_put(&dst->refcount, dsa_free_dst);
}
-static bool dsa_port_is_dsa(struct device_node *port)
+static bool dsa_port_is_valid(struct dsa_port *port)
{
- return !!of_parse_phandle(port, "link", 0);
+ return !!port->dn;
}
-static bool dsa_port_is_cpu(struct device_node *port)
+static bool dsa_port_is_dsa(struct dsa_port *port)
{
- return !!of_parse_phandle(port, "ethernet", 0);
+ return !!of_parse_phandle(port->dn, "link", 0);
+}
+
+static bool dsa_port_is_cpu(struct dsa_port *port)
+{
+ return !!of_parse_phandle(port->dn, "ethernet", 0);
}
static bool dsa_ds_find_port(struct dsa_switch *ds,
@@ -120,7 +125,7 @@ static struct dsa_switch *dsa_dst_find_port(struct dsa_switch_tree *dst,
static int dsa_port_complete(struct dsa_switch_tree *dst,
struct dsa_switch *src_ds,
- struct device_node *port,
+ struct dsa_port *port,
u32 src_port)
{
struct device_node *link;
@@ -128,7 +133,7 @@ static int dsa_port_complete(struct dsa_switch_tree *dst,
struct dsa_switch *dst_ds;
for (index = 0;; index++) {
- link = of_parse_phandle(port, "link", index);
+ link = of_parse_phandle(port->dn, "link", index);
if (!link)
break;
@@ -151,13 +156,13 @@ static int dsa_port_complete(struct dsa_switch_tree *dst,
*/
static int dsa_ds_complete(struct dsa_switch_tree *dst, struct dsa_switch *ds)
{
- struct device_node *port;
+ struct dsa_port *port;
u32 index;
int err;
for (index = 0; index < DSA_MAX_PORTS; index++) {
- port = ds->ports[index].dn;
- if (!port)
+ port = &ds->ports[index];
+ if (!dsa_port_is_valid(port))
continue;
if (!dsa_port_is_dsa(port))
@@ -197,7 +202,7 @@ static int dsa_dst_complete(struct dsa_switch_tree *dst)
return 0;
}
-static int dsa_dsa_port_apply(struct device_node *port, u32 index,
+static int dsa_dsa_port_apply(struct dsa_port *port, u32 index,
struct dsa_switch *ds)
{
int err;
@@ -212,13 +217,13 @@ static int dsa_dsa_port_apply(struct device_node *port, u32 index,
return 0;
}
-static void dsa_dsa_port_unapply(struct device_node *port, u32 index,
+static void dsa_dsa_port_unapply(struct dsa_port *port, u32 index,
struct dsa_switch *ds)
{
dsa_cpu_dsa_destroy(port);
}
-static int dsa_cpu_port_apply(struct device_node *port, u32 index,
+static int dsa_cpu_port_apply(struct dsa_port *port, u32 index,
struct dsa_switch *ds)
{
int err;
@@ -235,7 +240,7 @@ static int dsa_cpu_port_apply(struct device_node *port, u32 index,
return 0;
}
-static void dsa_cpu_port_unapply(struct device_node *port, u32 index,
+static void dsa_cpu_port_unapply(struct dsa_port *port, u32 index,
struct dsa_switch *ds)
{
dsa_cpu_dsa_destroy(port);
@@ -243,13 +248,13 @@ static void dsa_cpu_port_unapply(struct device_node *port, u32 index,
}
-static int dsa_user_port_apply(struct device_node *port, u32 index,
+static int dsa_user_port_apply(struct dsa_port *port, u32 index,
struct dsa_switch *ds)
{
const char *name;
int err;
- name = of_get_property(port, "label", NULL);
+ name = of_get_property(port->dn, "label", NULL);
if (!name)
name = "eth%d";
@@ -263,7 +268,7 @@ static int dsa_user_port_apply(struct device_node *port, u32 index,
return 0;
}
-static void dsa_user_port_unapply(struct device_node *port, u32 index,
+static void dsa_user_port_unapply(struct dsa_port *port, u32 index,
struct dsa_switch *ds)
{
if (ds->ports[index].netdev) {
@@ -275,7 +280,7 @@ static void dsa_user_port_unapply(struct device_node *port, u32 index,
static int dsa_ds_apply(struct dsa_switch_tree *dst, struct dsa_switch *ds)
{
- struct device_node *port;
+ struct dsa_port *port;
u32 index;
int err;
@@ -309,8 +314,8 @@ static int dsa_ds_apply(struct dsa_switch_tree *dst, struct dsa_switch *ds)
}
for (index = 0; index < DSA_MAX_PORTS; index++) {
- port = ds->ports[index].dn;
- if (!port)
+ port = &ds->ports[index];
+ if (!dsa_port_is_valid(port))
continue;
if (dsa_port_is_dsa(port)) {
@@ -337,12 +342,12 @@ static int dsa_ds_apply(struct dsa_switch_tree *dst, struct dsa_switch *ds)
static void dsa_ds_unapply(struct dsa_switch_tree *dst, struct dsa_switch *ds)
{
- struct device_node *port;
+ struct dsa_port *port;
u32 index;
for (index = 0; index < DSA_MAX_PORTS; index++) {
- port = ds->ports[index].dn;
- if (!port)
+ port = &ds->ports[index];
+ if (!dsa_port_is_valid(port))
continue;
if (dsa_port_is_dsa(port)) {
@@ -426,7 +431,7 @@ static void dsa_dst_unapply(struct dsa_switch_tree *dst)
dst->applied = false;
}
-static int dsa_cpu_parse(struct device_node *port, u32 index,
+static int dsa_cpu_parse(struct dsa_port *port, u32 index,
struct dsa_switch_tree *dst,
struct dsa_switch *ds)
{
@@ -434,7 +439,7 @@ static int dsa_cpu_parse(struct device_node *port, u32 index,
struct net_device *ethernet_dev;
struct device_node *ethernet;
- ethernet = of_parse_phandle(port, "ethernet", 0);
+ ethernet = of_parse_phandle(port->dn, "ethernet", 0);
if (!ethernet)
return -EINVAL;
@@ -467,13 +472,13 @@ static int dsa_cpu_parse(struct device_node *port, u32 index,
static int dsa_ds_parse(struct dsa_switch_tree *dst, struct dsa_switch *ds)
{
- struct device_node *port;
+ struct dsa_port *port;
u32 index;
int err;
for (index = 0; index < DSA_MAX_PORTS; index++) {
- port = ds->ports[index].dn;
- if (!port)
+ port = &ds->ports[index];
+ if (!dsa_port_is_valid(port))
continue;
if (dsa_port_is_cpu(port)) {
@@ -534,7 +539,7 @@ static int dsa_parse_ports_dn(struct device_node *ports, struct dsa_switch *ds)
* to have access to a correct value, just like what
* net/dsa/dsa.c::dsa_switch_setup_one does.
*/
- if (!dsa_port_is_cpu(port))
+ if (!dsa_port_is_cpu(&ds->ports[reg]))
ds->enabled_port_mask |= 1 << reg;
}
diff --git a/net/dsa/dsa_priv.h b/net/dsa/dsa_priv.h
index 7e3385ec73f4..a015ec97c289 100644
--- a/net/dsa/dsa_priv.h
+++ b/net/dsa/dsa_priv.h
@@ -50,8 +50,8 @@ struct dsa_slave_priv {
/* dsa.c */
int dsa_cpu_dsa_setup(struct dsa_switch *ds, struct device *dev,
- struct device_node *port_dn, int port);
-void dsa_cpu_dsa_destroy(struct device_node *port_dn);
+ struct dsa_port *dport, int port);
+void dsa_cpu_dsa_destroy(struct dsa_port *dport);
const struct dsa_device_ops *dsa_resolve_tag_protocol(int tag_protocol);
int dsa_cpu_port_ethtool_setup(struct dsa_switch *ds);
void dsa_cpu_port_ethtool_restore(struct dsa_switch *ds);
--
2.9.3
^ permalink raw reply related
* [PATCH net-next v3 03/10] net: dsa: Suffix function manipulating device_node with _dn
From: Florian Fainelli @ 2017-01-14 21:47 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170114214713.28109-1-f.fainelli@gmail.com>
Make it clear that these functions take a device_node structure pointer
Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
---
net/dsa/dsa2.c | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/net/dsa/dsa2.c b/net/dsa/dsa2.c
index 6e3675220fef..04ab62251fe3 100644
--- a/net/dsa/dsa2.c
+++ b/net/dsa/dsa2.c
@@ -94,8 +94,8 @@ static bool dsa_port_is_cpu(struct dsa_port *port)
return !!of_parse_phandle(port->dn, "ethernet", 0);
}
-static bool dsa_ds_find_port(struct dsa_switch *ds,
- struct device_node *port)
+static bool dsa_ds_find_port_dn(struct dsa_switch *ds,
+ struct device_node *port)
{
u32 index;
@@ -105,8 +105,8 @@ static bool dsa_ds_find_port(struct dsa_switch *ds,
return false;
}
-static struct dsa_switch *dsa_dst_find_port(struct dsa_switch_tree *dst,
- struct device_node *port)
+static struct dsa_switch *dsa_dst_find_port_dn(struct dsa_switch_tree *dst,
+ struct device_node *port)
{
struct dsa_switch *ds;
u32 index;
@@ -116,7 +116,7 @@ static struct dsa_switch *dsa_dst_find_port(struct dsa_switch_tree *dst,
if (!ds)
continue;
- if (dsa_ds_find_port(ds, port))
+ if (dsa_ds_find_port_dn(ds, port))
return ds;
}
@@ -137,7 +137,7 @@ static int dsa_port_complete(struct dsa_switch_tree *dst,
if (!link)
break;
- dst_ds = dsa_dst_find_port(dst, link);
+ dst_ds = dsa_dst_find_port_dn(dst, link);
of_node_put(link);
if (!dst_ds)
@@ -546,7 +546,7 @@ static int dsa_parse_ports_dn(struct device_node *ports, struct dsa_switch *ds)
return 0;
}
-static int dsa_parse_member(struct device_node *np, u32 *tree, u32 *index)
+static int dsa_parse_member_dn(struct device_node *np, u32 *tree, u32 *index)
{
int err;
@@ -592,7 +592,7 @@ static int _dsa_register_switch(struct dsa_switch *ds, struct device *dev)
u32 tree, index;
int i, err;
- err = dsa_parse_member(np, &tree, &index);
+ err = dsa_parse_member_dn(np, &tree, &index);
if (err)
return err;
--
2.9.3
^ permalink raw reply related
* [PATCH net-next v3 04/10] net: dsa: Move ports assignment closer to error checking
From: Florian Fainelli @ 2017-01-14 21:47 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170114214713.28109-1-f.fainelli@gmail.com>
Move the assignment of ports in _dsa_register_switch() closer to where
it is checked, no functional change. Re-order declarations to be
preserve the inverted christmas tree style.
Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
---
net/dsa/dsa2.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/net/dsa/dsa2.c b/net/dsa/dsa2.c
index 04ab62251fe3..cd91070b5467 100644
--- a/net/dsa/dsa2.c
+++ b/net/dsa/dsa2.c
@@ -587,8 +587,8 @@ static struct device_node *dsa_get_ports(struct dsa_switch *ds,
static int _dsa_register_switch(struct dsa_switch *ds, struct device *dev)
{
struct device_node *np = dev->of_node;
- struct device_node *ports = dsa_get_ports(ds, np);
struct dsa_switch_tree *dst;
+ struct device_node *ports;
u32 tree, index;
int i, err;
@@ -596,6 +596,7 @@ static int _dsa_register_switch(struct dsa_switch *ds, struct device *dev)
if (err)
return err;
+ ports = dsa_get_ports(ds, np);
if (IS_ERR(ports))
return PTR_ERR(ports);
--
2.9.3
^ permalink raw reply related
* [PATCH net-next v3 05/10] drivers: base: Add device_find_class()
From: Florian Fainelli @ 2017-01-14 21:47 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170114214713.28109-1-f.fainelli@gmail.com>
Add a helper function to lookup a device reference given a class name.
This is a preliminary patch to remove adhoc code from net/dsa/dsa.c and
make it more generic.
Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
---
drivers/base/core.c | 19 +++++++++++++++++++
include/linux/device.h | 1 +
2 files changed, 20 insertions(+)
diff --git a/drivers/base/core.c b/drivers/base/core.c
index 020ea7f05520..3dd6047c10d8 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -2065,6 +2065,25 @@ struct device *device_find_child(struct device *parent, void *data,
}
EXPORT_SYMBOL_GPL(device_find_child);
+static int dev_is_class(struct device *dev, void *class)
+{
+ if (dev->class != NULL && !strcmp(dev->class->name, class))
+ return 1;
+
+ return 0;
+}
+
+struct device *device_find_class(struct device *parent, char *class)
+{
+ if (dev_is_class(parent, class)) {
+ get_device(parent);
+ return parent;
+ }
+
+ return device_find_child(parent, class, dev_is_class);
+}
+EXPORT_SYMBOL_GPL(device_find_class);
+
int __init devices_init(void)
{
devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);
diff --git a/include/linux/device.h b/include/linux/device.h
index 491b4c0ca633..8d37f5ecb972 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -1120,6 +1120,7 @@ extern int device_for_each_child_reverse(struct device *dev, void *data,
int (*fn)(struct device *dev, void *data));
extern struct device *device_find_child(struct device *dev, void *data,
int (*match)(struct device *dev, void *data));
+extern struct device *device_find_class(struct device *parent, char *class);
extern int device_rename(struct device *dev, const char *new_name);
extern int device_move(struct device *dev, struct device *new_parent,
enum dpm_order dpm_order);
--
2.9.3
^ permalink raw reply related
* [PATCH net-next v3 06/10] net: dsa: Migrate to device_find_class()
From: Florian Fainelli @ 2017-01-14 21:47 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170114214713.28109-1-f.fainelli@gmail.com>
Now that the base device driver code provides an identical
implementation of dev_find_class() utilize device_find_class() instead
of our own version of it.
Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
---
net/dsa/dsa.c | 22 ++--------------------
1 file changed, 2 insertions(+), 20 deletions(-)
diff --git a/net/dsa/dsa.c b/net/dsa/dsa.c
index 2306d1b87c83..77fa4c4f5828 100644
--- a/net/dsa/dsa.c
+++ b/net/dsa/dsa.c
@@ -455,29 +455,11 @@ EXPORT_SYMBOL_GPL(dsa_switch_resume);
#endif
/* platform driver init and cleanup *****************************************/
-static int dev_is_class(struct device *dev, void *class)
-{
- if (dev->class != NULL && !strcmp(dev->class->name, class))
- return 1;
-
- return 0;
-}
-
-static struct device *dev_find_class(struct device *parent, char *class)
-{
- if (dev_is_class(parent, class)) {
- get_device(parent);
- return parent;
- }
-
- return device_find_child(parent, class, dev_is_class);
-}
-
struct mii_bus *dsa_host_dev_to_mii_bus(struct device *dev)
{
struct device *d;
- d = dev_find_class(dev, "mdio_bus");
+ d = device_find_class(dev, "mdio_bus");
if (d != NULL) {
struct mii_bus *bus;
@@ -495,7 +477,7 @@ static struct net_device *dev_to_net_device(struct device *dev)
{
struct device *d;
- d = dev_find_class(dev, "net");
+ d = device_find_class(dev, "net");
if (d != NULL) {
struct net_device *nd;
--
2.9.3
^ permalink raw reply related
* [PATCH net-next v3 07/10] net: Relocate dev_to_net_device() into core
From: Florian Fainelli @ 2017-01-14 21:47 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170114214713.28109-1-f.fainelli@gmail.com>
dev_to_net_device() is moved from net/dsa/dsa.c to net/core/dev.c since
it going to be used by net/dsa/dsa2.c and the namespace of the function
justifies making it available to other users potentially.
Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
---
include/linux/netdevice.h | 2 ++
net/core/dev.c | 19 +++++++++++++++++++
net/dsa/dsa.c | 18 ------------------
3 files changed, 21 insertions(+), 18 deletions(-)
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 97ae0ac513ee..6d021c37b774 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -4390,4 +4390,6 @@ do { \
#define PTYPE_HASH_SIZE (16)
#define PTYPE_HASH_MASK (PTYPE_HASH_SIZE - 1)
+struct net_device *dev_to_net_device(struct device *dev);
+
#endif /* _LINUX_NETDEVICE_H */
diff --git a/net/core/dev.c b/net/core/dev.c
index ad5959e56116..7547e2ccc06b 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -8128,6 +8128,25 @@ const char *netdev_drivername(const struct net_device *dev)
return empty;
}
+struct net_device *dev_to_net_device(struct device *dev)
+{
+ struct device *d;
+
+ d = device_find_class(dev, "net");
+ if (d) {
+ struct net_device *nd;
+
+ nd = to_net_dev(d);
+ dev_hold(nd);
+ put_device(d);
+
+ return nd;
+ }
+
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(dev_to_net_device);
+
static void __netdev_printk(const char *level, const struct net_device *dev,
struct va_format *vaf)
{
diff --git a/net/dsa/dsa.c b/net/dsa/dsa.c
index 77fa4c4f5828..6c264f92fec5 100644
--- a/net/dsa/dsa.c
+++ b/net/dsa/dsa.c
@@ -473,24 +473,6 @@ struct mii_bus *dsa_host_dev_to_mii_bus(struct device *dev)
}
EXPORT_SYMBOL_GPL(dsa_host_dev_to_mii_bus);
-static struct net_device *dev_to_net_device(struct device *dev)
-{
- struct device *d;
-
- d = device_find_class(dev, "net");
- if (d != NULL) {
- struct net_device *nd;
-
- nd = to_net_dev(d);
- dev_hold(nd);
- put_device(d);
-
- return nd;
- }
-
- return NULL;
-}
-
#ifdef CONFIG_OF
static int dsa_of_setup_routing_table(struct dsa_platform_data *pd,
struct dsa_chip_data *cd,
--
2.9.3
^ permalink raw reply related
* [PATCH net-next v3 08/10] net: dsa: Add support for platform data
From: Florian Fainelli @ 2017-01-14 21:47 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170114214713.28109-1-f.fainelli@gmail.com>
Allow drivers to use the new DSA API with platform data. Most of the
code in net/dsa/dsa2.c does not rely so much on device_nodes and can get
the same information from platform_data instead.
We purposely do not support distributed configurations with platform
data, so drivers should be providing a pointer to a 'struct
dsa_chip_data' structure if they wish to communicate per-port layout.
Multiple CPUs port could potentially be supported and dsa_chip_data is
extended to receive up to one reference to an upstream network device
per port described by a dsa_chip_data structure.
Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
---
include/net/dsa.h | 6 ++++
net/dsa/dsa2.c | 101 ++++++++++++++++++++++++++++++++++++++++++++----------
2 files changed, 89 insertions(+), 18 deletions(-)
diff --git a/include/net/dsa.h b/include/net/dsa.h
index 16a502a6c26a..491008792e4d 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -42,6 +42,11 @@ struct dsa_chip_data {
struct device *host_dev;
int sw_addr;
+ /*
+ * Reference to network devices
+ */
+ struct device *netdev[DSA_MAX_PORTS];
+
/* set to size of eeprom if supported by the switch */
int eeprom_len;
@@ -140,6 +145,7 @@ struct dsa_switch_tree {
};
struct dsa_port {
+ const char *name;
struct net_device *netdev;
struct device_node *dn;
unsigned int ageing_time;
diff --git a/net/dsa/dsa2.c b/net/dsa/dsa2.c
index cd91070b5467..598229b02fd3 100644
--- a/net/dsa/dsa2.c
+++ b/net/dsa/dsa2.c
@@ -79,19 +79,28 @@ static void dsa_dst_del_ds(struct dsa_switch_tree *dst,
kref_put(&dst->refcount, dsa_free_dst);
}
+/* For platform data configurations, we need to have a valid name argument to
+ * differentiate a disabled port from an enabled one
+ */
static bool dsa_port_is_valid(struct dsa_port *port)
{
- return !!port->dn;
+ return !!(port->dn || port->name);
}
static bool dsa_port_is_dsa(struct dsa_port *port)
{
- return !!of_parse_phandle(port->dn, "link", 0);
+ if (port->name && !strcmp(port->name, "dsa"))
+ return true;
+ else
+ return !!of_parse_phandle(port->dn, "link", 0);
}
static bool dsa_port_is_cpu(struct dsa_port *port)
{
- return !!of_parse_phandle(port->dn, "ethernet", 0);
+ if (port->name && !strcmp(port->name, "cpu"))
+ return true;
+ else
+ return !!of_parse_phandle(port->dn, "ethernet", 0);
}
static bool dsa_ds_find_port_dn(struct dsa_switch *ds,
@@ -251,10 +260,11 @@ static void dsa_cpu_port_unapply(struct dsa_port *port, u32 index,
static int dsa_user_port_apply(struct dsa_port *port, u32 index,
struct dsa_switch *ds)
{
- const char *name;
+ const char *name = port->name;
int err;
- name = of_get_property(port->dn, "label", NULL);
+ if (port->dn)
+ name = of_get_property(port->dn, "label", NULL);
if (!name)
name = "eth%d";
@@ -439,11 +449,15 @@ static int dsa_cpu_parse(struct dsa_port *port, u32 index,
struct net_device *ethernet_dev;
struct device_node *ethernet;
- ethernet = of_parse_phandle(port->dn, "ethernet", 0);
- if (!ethernet)
- return -EINVAL;
+ if (port->dn) {
+ ethernet = of_parse_phandle(port->dn, "ethernet", 0);
+ if (!ethernet)
+ return -EINVAL;
+ ethernet_dev = of_find_net_device_by_node(ethernet);
+ } else {
+ ethernet_dev = dev_to_net_device(ds->cd->netdev[index]);
+ }
- ethernet_dev = of_find_net_device_by_node(ethernet);
if (!ethernet_dev)
return -EPROBE_DEFER;
@@ -546,6 +560,33 @@ static int dsa_parse_ports_dn(struct device_node *ports, struct dsa_switch *ds)
return 0;
}
+static int dsa_parse_ports(struct dsa_chip_data *cd, struct dsa_switch *ds)
+{
+ bool valid_name_found = false;
+ unsigned int i;
+
+ for (i = 0; i < DSA_MAX_PORTS; i++) {
+ if (!cd->port_names[i])
+ continue;
+
+ ds->ports[i].name = cd->port_names[i];
+
+ /* Initialize enabled_port_mask now for drv->setup()
+ * to have access to a correct value, just like what
+ * net/dsa/dsa.c::dsa_switch_setup_one does.
+ */
+ if (!dsa_port_is_cpu(&ds->ports[i]))
+ ds->enabled_port_mask |= 1 << i;
+
+ valid_name_found = true;
+ }
+
+ if (!valid_name_found && i == DSA_MAX_PORTS)
+ return -EINVAL;
+
+ return 0;
+}
+
static int dsa_parse_member_dn(struct device_node *np, u32 *tree, u32 *index)
{
int err;
@@ -570,6 +611,18 @@ static int dsa_parse_member_dn(struct device_node *np, u32 *tree, u32 *index)
return 0;
}
+static int dsa_parse_member(struct dsa_chip_data *pd, u32 *tree, u32 *index)
+{
+ if (!pd)
+ return -ENODEV;
+
+ /* We do not support complex trees with dsa_chip_data */
+ *tree = 0;
+ *index = 0;
+
+ return 0;
+}
+
static struct device_node *dsa_get_ports(struct dsa_switch *ds,
struct device_node *np)
{
@@ -586,23 +639,34 @@ static struct device_node *dsa_get_ports(struct dsa_switch *ds,
static int _dsa_register_switch(struct dsa_switch *ds, struct device *dev)
{
+ struct dsa_chip_data *pdata = dev->platform_data;
struct device_node *np = dev->of_node;
struct dsa_switch_tree *dst;
struct device_node *ports;
u32 tree, index;
int i, err;
- err = dsa_parse_member_dn(np, &tree, &index);
- if (err)
- return err;
+ if (np) {
+ err = dsa_parse_member_dn(np, &tree, &index);
+ if (err)
+ return err;
- ports = dsa_get_ports(ds, np);
- if (IS_ERR(ports))
- return PTR_ERR(ports);
+ ports = dsa_get_ports(ds, np);
+ if (IS_ERR(ports))
+ return PTR_ERR(ports);
- err = dsa_parse_ports_dn(ports, ds);
- if (err)
- return err;
+ err = dsa_parse_ports_dn(ports, ds);
+ if (err)
+ return err;
+ } else {
+ err = dsa_parse_member(pdata, &tree, &index);
+ if (err)
+ return err;
+
+ err = dsa_parse_ports(pdata, ds);
+ if (err)
+ return err;
+ }
dst = dsa_get_dst(tree);
if (!dst) {
@@ -618,6 +682,7 @@ static int _dsa_register_switch(struct dsa_switch *ds, struct device *dev)
ds->dst = dst;
ds->index = index;
+ ds->cd = pdata;
/* Initialize the routing table */
for (i = 0; i < DSA_MAX_SWITCHES; ++i)
--
2.9.3
^ permalink raw reply related
* [PATCH net-next v3 09/10] net: phy: Allow pre-declaration of MDIO devices
From: Florian Fainelli @ 2017-01-14 21:47 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170114214713.28109-1-f.fainelli@gmail.com>
Allow board support code to collect pre-declarations for MDIO devices by
registering them with mdiobus_register_board_info(). SPI and I2C buses
have a similar feature, we were missing this for MDIO devices, but this
is particularly useful for e.g: MDIO-connected switches which need to
provide their port layout (often board-specific) to a MDIO Ethernet
switch driver.
Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
---
drivers/net/phy/Makefile | 3 +-
drivers/net/phy/mdio-boardinfo.c | 86 ++++++++++++++++++++++++++++++++++++++++
drivers/net/phy/mdio-boardinfo.h | 19 +++++++++
drivers/net/phy/mdio_bus.c | 4 ++
drivers/net/phy/mdio_device.c | 11 +++++
include/linux/mdio.h | 3 ++
include/linux/mod_devicetable.h | 1 +
include/linux/phy.h | 19 +++++++++
8 files changed, 145 insertions(+), 1 deletion(-)
create mode 100644 drivers/net/phy/mdio-boardinfo.c
create mode 100644 drivers/net/phy/mdio-boardinfo.h
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index 356859ac7c18..407b0b601ea8 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -1,6 +1,7 @@
# Makefile for Linux PHY drivers and MDIO bus drivers
-libphy-y := phy.o phy_device.o mdio_bus.o mdio_device.o
+libphy-y := phy.o phy_device.o mdio_bus.o mdio_device.o \
+ mdio-boardinfo.o
libphy-$(CONFIG_SWPHY) += swphy.o
libphy-$(CONFIG_LED_TRIGGER_PHY) += phy_led_triggers.o
diff --git a/drivers/net/phy/mdio-boardinfo.c b/drivers/net/phy/mdio-boardinfo.c
new file mode 100644
index 000000000000..6b988f77da08
--- /dev/null
+++ b/drivers/net/phy/mdio-boardinfo.c
@@ -0,0 +1,86 @@
+/*
+ * mdio-boardinfo - Collect pre-declarations for MDIO devices
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/export.h>
+#include <linux/mutex.h>
+#include <linux/list.h>
+
+#include "mdio-boardinfo.h"
+
+static LIST_HEAD(mdio_board_list);
+static DEFINE_MUTEX(mdio_board_lock);
+
+/**
+ * mdiobus_setup_mdiodev_from_board_info - create and setup MDIO devices
+ * from pre-collected board specific MDIO information
+ * @mdiodev: MDIO device pointer
+ * Context: can sleep
+ */
+void mdiobus_setup_mdiodev_from_board_info(struct mii_bus *bus)
+{
+ struct mdio_board_entry *be;
+ struct mdio_device *mdiodev;
+ struct mdio_board_info *bi;
+ int ret;
+
+ mutex_lock(&mdio_board_lock);
+ list_for_each_entry(be, &mdio_board_list, list) {
+ bi = &be->board_info;
+
+ if (strcmp(bus->id, bi->bus_id))
+ continue;
+
+ mdiodev = mdio_device_create(bus, bi->mdio_addr);
+ if (IS_ERR(mdiodev))
+ continue;
+
+ strncpy(mdiodev->modalias, bi->modalias,
+ sizeof(mdiodev->modalias));
+ mdiodev->bus_match = mdio_device_bus_match;
+ mdiodev->dev.platform_data = (void *)bi->platform_data;
+
+ ret = mdio_device_register(mdiodev);
+ if (ret) {
+ mdio_device_free(mdiodev);
+ continue;
+ }
+ }
+ mutex_unlock(&mdio_board_lock);
+}
+
+/**
+ * mdio_register_board_info - register MDIO devices for a given board
+ * @info: array of devices descriptors
+ * @n: number of descriptors provided
+ * Context: can sleep
+ *
+ * The board info passed can be marked with __initdata but be pointers
+ * such as platform_data etc. are copied as-is
+ */
+int mdiobus_register_board_info(const struct mdio_board_info *info,
+ unsigned int n)
+{
+ struct mdio_board_entry *be;
+ unsigned int i;
+
+ be = kcalloc(n, sizeof(*be), GFP_KERNEL);
+ if (!be)
+ return -ENOMEM;
+
+ for (i = 0; i < n; i++, be++, info++) {
+ memcpy(&be->board_info, info, sizeof(*info));
+ mutex_lock(&mdio_board_lock);
+ list_add_tail(&be->list, &mdio_board_list);
+ mutex_unlock(&mdio_board_lock);
+ }
+
+ return 0;
+}
diff --git a/drivers/net/phy/mdio-boardinfo.h b/drivers/net/phy/mdio-boardinfo.h
new file mode 100644
index 000000000000..00f98163e90e
--- /dev/null
+++ b/drivers/net/phy/mdio-boardinfo.h
@@ -0,0 +1,19 @@
+/*
+ * mdio-boardinfo.h - board info interface internal to the mdio_bus
+ * component
+ */
+
+#ifndef __MDIO_BOARD_INFO_H
+#define __MDIO_BOARD_INFO_H
+
+#include <linux/phy.h>
+#include <linux/mutex.h>
+
+struct mdio_board_entry {
+ struct list_head list;
+ struct mdio_board_info board_info;
+};
+
+void mdiobus_setup_mdiodev_from_board_info(struct mii_bus *bus);
+
+#endif /* __MDIO_BOARD_INFO_H */
diff --git a/drivers/net/phy/mdio_bus.c b/drivers/net/phy/mdio_bus.c
index 653d076eafe5..fa7d51f14869 100644
--- a/drivers/net/phy/mdio_bus.c
+++ b/drivers/net/phy/mdio_bus.c
@@ -41,6 +41,8 @@
#define CREATE_TRACE_POINTS
#include <trace/events/mdio.h>
+#include "mdio-boardinfo.h"
+
int mdiobus_register_device(struct mdio_device *mdiodev)
{
if (mdiodev->bus->mdio_map[mdiodev->addr])
@@ -343,6 +345,8 @@ int __mdiobus_register(struct mii_bus *bus, struct module *owner)
}
}
+ mdiobus_setup_mdiodev_from_board_info(bus);
+
bus->state = MDIOBUS_REGISTERED;
pr_info("%s: probed\n", bus->name);
return 0;
diff --git a/drivers/net/phy/mdio_device.c b/drivers/net/phy/mdio_device.c
index fc3aaaa36b1d..e24f28924af8 100644
--- a/drivers/net/phy/mdio_device.c
+++ b/drivers/net/phy/mdio_device.c
@@ -34,6 +34,17 @@ static void mdio_device_release(struct device *dev)
kfree(to_mdio_device(dev));
}
+int mdio_device_bus_match(struct device *dev, struct device_driver *drv)
+{
+ struct mdio_device *mdiodev = to_mdio_device(dev);
+ struct mdio_driver *mdiodrv = to_mdio_driver(drv);
+
+ if (mdiodrv->mdiodrv.flags & MDIO_DEVICE_IS_PHY)
+ return 0;
+
+ return strcmp(mdiodev->modalias, drv->name) == 0;
+}
+
struct mdio_device *mdio_device_create(struct mii_bus *bus, int addr)
{
struct mdio_device *mdiodev;
diff --git a/include/linux/mdio.h b/include/linux/mdio.h
index b6587a4b32e7..00a7f43c1482 100644
--- a/include/linux/mdio.h
+++ b/include/linux/mdio.h
@@ -10,6 +10,7 @@
#define __LINUX_MDIO_H__
#include <uapi/linux/mdio.h>
+#include <linux/mod_devicetable.h>
struct mii_bus;
@@ -29,6 +30,7 @@ struct mdio_device {
const struct dev_pm_ops *pm_ops;
struct mii_bus *bus;
+ char modalias[MDIO_NAME_SIZE];
int (*bus_match)(struct device *dev, struct device_driver *drv);
void (*device_free)(struct mdio_device *mdiodev);
@@ -71,6 +73,7 @@ int mdio_device_register(struct mdio_device *mdiodev);
void mdio_device_remove(struct mdio_device *mdiodev);
int mdio_driver_register(struct mdio_driver *drv);
void mdio_driver_unregister(struct mdio_driver *drv);
+int mdio_device_bus_match(struct device *dev, struct device_driver *drv);
static inline bool mdio_phy_id_is_c45(int phy_id)
{
diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h
index 8a57f0b1242d..8850fcaf50db 100644
--- a/include/linux/mod_devicetable.h
+++ b/include/linux/mod_devicetable.h
@@ -501,6 +501,7 @@ struct platform_device_id {
kernel_ulong_t driver_data;
};
+#define MDIO_NAME_SIZE 32
#define MDIO_MODULE_PREFIX "mdio:"
#define MDIO_ID_FMT "%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d"
diff --git a/include/linux/phy.h b/include/linux/phy.h
index f7d95f644eed..7745f7391d3e 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -882,6 +882,25 @@ void mdio_bus_exit(void);
extern struct bus_type mdio_bus_type;
+struct mdio_board_info {
+ const char *bus_id;
+ char modalias[MDIO_NAME_SIZE];
+ int mdio_addr;
+ const void *platform_data;
+};
+
+#if IS_ENABLED(CONFIG_PHYLIB)
+int mdiobus_register_board_info(const struct mdio_board_info *info,
+ unsigned int n);
+#else
+static inline int mdiobus_register_board_info(const struct mdio_board_info *i,
+ unsigned int n)
+{
+ return 0;
+}
+#endif
+
+
/**
* module_phy_driver() - Helper macro for registering PHY drivers
* @__phy_drivers: array of PHY drivers to register
--
2.9.3
^ permalink raw reply related
* [PATCH net-next v3 10/10] ARM: orion: Register DSA switch as a MDIO device
From: Florian Fainelli @ 2017-01-14 21:47 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170114214713.28109-1-f.fainelli@gmail.com>
Utilize the ability to pass board specific MDIO bus information towards a
particular MDIO device thus allowing us to provide the per-port switch layout
to the Marvell 88E6XXX switch driver.
Since we would end-up with conflicting registration paths, do not register the
"dsa" platform device anymore.
Note that the MDIO devices registered by code in net/dsa/dsa2.c does not
parse a dsa_platform_data, but directly take a dsa_chip_data (specific
to a single switch chip), so we update the different call sites to pass
this structure down to orion_ge00_switch_init().
Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
---
arch/arm/mach-orion5x/common.c | 2 +-
arch/arm/mach-orion5x/common.h | 4 ++--
arch/arm/mach-orion5x/rd88f5181l-fxo-setup.c | 7 +------
arch/arm/mach-orion5x/rd88f5181l-ge-setup.c | 7 +------
arch/arm/mach-orion5x/rd88f6183ap-ge-setup.c | 7 +------
arch/arm/mach-orion5x/wnr854t-setup.c | 2 +-
arch/arm/mach-orion5x/wrt350n-v2-setup.c | 7 +------
arch/arm/plat-orion/common.c | 25 +++++++++++++++++++------
arch/arm/plat-orion/include/plat/common.h | 4 ++--
9 files changed, 29 insertions(+), 36 deletions(-)
diff --git a/arch/arm/mach-orion5x/common.c b/arch/arm/mach-orion5x/common.c
index 04910764c385..83a7ec4c16d0 100644
--- a/arch/arm/mach-orion5x/common.c
+++ b/arch/arm/mach-orion5x/common.c
@@ -105,7 +105,7 @@ void __init orion5x_eth_init(struct mv643xx_eth_platform_data *eth_data)
/*****************************************************************************
* Ethernet switch
****************************************************************************/
-void __init orion5x_eth_switch_init(struct dsa_platform_data *d)
+void __init orion5x_eth_switch_init(struct dsa_chip_data *d)
{
orion_ge00_switch_init(d);
}
diff --git a/arch/arm/mach-orion5x/common.h b/arch/arm/mach-orion5x/common.h
index 8a4115bd441d..efeffc6b4ebb 100644
--- a/arch/arm/mach-orion5x/common.h
+++ b/arch/arm/mach-orion5x/common.h
@@ -3,7 +3,7 @@
#include <linux/reboot.h>
-struct dsa_platform_data;
+struct dsa_chip_data;
struct mv643xx_eth_platform_data;
struct mv_sata_platform_data;
@@ -41,7 +41,7 @@ void orion5x_setup_wins(void);
void orion5x_ehci0_init(void);
void orion5x_ehci1_init(void);
void orion5x_eth_init(struct mv643xx_eth_platform_data *eth_data);
-void orion5x_eth_switch_init(struct dsa_platform_data *d);
+void orion5x_eth_switch_init(struct dsa_chip_data *d);
void orion5x_i2c_init(void);
void orion5x_sata_init(struct mv_sata_platform_data *sata_data);
void orion5x_spi_init(void);
diff --git a/arch/arm/mach-orion5x/rd88f5181l-fxo-setup.c b/arch/arm/mach-orion5x/rd88f5181l-fxo-setup.c
index dccadf68ea2b..a3c1336d30c9 100644
--- a/arch/arm/mach-orion5x/rd88f5181l-fxo-setup.c
+++ b/arch/arm/mach-orion5x/rd88f5181l-fxo-setup.c
@@ -101,11 +101,6 @@ static struct dsa_chip_data rd88f5181l_fxo_switch_chip_data = {
.port_names[7] = "lan3",
};
-static struct dsa_platform_data __initdata rd88f5181l_fxo_switch_plat_data = {
- .nr_chips = 1,
- .chip = &rd88f5181l_fxo_switch_chip_data,
-};
-
static void __init rd88f5181l_fxo_init(void)
{
/*
@@ -120,7 +115,7 @@ static void __init rd88f5181l_fxo_init(void)
*/
orion5x_ehci0_init();
orion5x_eth_init(&rd88f5181l_fxo_eth_data);
- orion5x_eth_switch_init(&rd88f5181l_fxo_switch_plat_data);
+ orion5x_eth_switch_init(&rd88f5181l_fxo_switch_chip_data);
orion5x_uart0_init();
mvebu_mbus_add_window_by_id(ORION_MBUS_DEVBUS_BOOT_TARGET,
diff --git a/arch/arm/mach-orion5x/rd88f5181l-ge-setup.c b/arch/arm/mach-orion5x/rd88f5181l-ge-setup.c
index affe5ec825de..252efe29bd1a 100644
--- a/arch/arm/mach-orion5x/rd88f5181l-ge-setup.c
+++ b/arch/arm/mach-orion5x/rd88f5181l-ge-setup.c
@@ -102,11 +102,6 @@ static struct dsa_chip_data rd88f5181l_ge_switch_chip_data = {
.port_names[7] = "lan3",
};
-static struct dsa_platform_data __initdata rd88f5181l_ge_switch_plat_data = {
- .nr_chips = 1,
- .chip = &rd88f5181l_ge_switch_chip_data,
-};
-
static struct i2c_board_info __initdata rd88f5181l_ge_i2c_rtc = {
I2C_BOARD_INFO("ds1338", 0x68),
};
@@ -125,7 +120,7 @@ static void __init rd88f5181l_ge_init(void)
*/
orion5x_ehci0_init();
orion5x_eth_init(&rd88f5181l_ge_eth_data);
- orion5x_eth_switch_init(&rd88f5181l_ge_switch_plat_data);
+ orion5x_eth_switch_init(&rd88f5181l_ge_switch_chip_data);
orion5x_i2c_init();
orion5x_uart0_init();
diff --git a/arch/arm/mach-orion5x/rd88f6183ap-ge-setup.c b/arch/arm/mach-orion5x/rd88f6183ap-ge-setup.c
index 67ee8571b03c..f4f1dbe1d91d 100644
--- a/arch/arm/mach-orion5x/rd88f6183ap-ge-setup.c
+++ b/arch/arm/mach-orion5x/rd88f6183ap-ge-setup.c
@@ -40,11 +40,6 @@ static struct dsa_chip_data rd88f6183ap_ge_switch_chip_data = {
.port_names[5] = "cpu",
};
-static struct dsa_platform_data __initdata rd88f6183ap_ge_switch_plat_data = {
- .nr_chips = 1,
- .chip = &rd88f6183ap_ge_switch_chip_data,
-};
-
static struct mtd_partition rd88f6183ap_ge_partitions[] = {
{
.name = "kernel",
@@ -89,7 +84,7 @@ static void __init rd88f6183ap_ge_init(void)
*/
orion5x_ehci0_init();
orion5x_eth_init(&rd88f6183ap_ge_eth_data);
- orion5x_eth_switch_init(&rd88f6183ap_ge_switch_plat_data);
+ orion5x_eth_switch_init(&rd88f6183ap_ge_switch_chip_data);
spi_register_board_info(rd88f6183ap_ge_spi_slave_info,
ARRAY_SIZE(rd88f6183ap_ge_spi_slave_info));
orion5x_spi_init();
diff --git a/arch/arm/mach-orion5x/wnr854t-setup.c b/arch/arm/mach-orion5x/wnr854t-setup.c
index 4dbcdbe1de7c..ac3376fc269f 100644
--- a/arch/arm/mach-orion5x/wnr854t-setup.c
+++ b/arch/arm/mach-orion5x/wnr854t-setup.c
@@ -124,7 +124,7 @@ static void __init wnr854t_init(void)
* Configure peripherals.
*/
orion5x_eth_init(&wnr854t_eth_data);
- orion5x_eth_switch_init(&wnr854t_switch_plat_data);
+ orion5x_eth_switch_init(&wnr854t_switch_chip_data);
orion5x_uart0_init();
mvebu_mbus_add_window_by_id(ORION_MBUS_DEVBUS_BOOT_TARGET,
diff --git a/arch/arm/mach-orion5x/wrt350n-v2-setup.c b/arch/arm/mach-orion5x/wrt350n-v2-setup.c
index a6a8c4648d74..9250bb2e429c 100644
--- a/arch/arm/mach-orion5x/wrt350n-v2-setup.c
+++ b/arch/arm/mach-orion5x/wrt350n-v2-setup.c
@@ -191,11 +191,6 @@ static struct dsa_chip_data wrt350n_v2_switch_chip_data = {
.port_names[7] = "lan4",
};
-static struct dsa_platform_data __initdata wrt350n_v2_switch_plat_data = {
- .nr_chips = 1,
- .chip = &wrt350n_v2_switch_chip_data,
-};
-
static void __init wrt350n_v2_init(void)
{
/*
@@ -210,7 +205,7 @@ static void __init wrt350n_v2_init(void)
*/
orion5x_ehci0_init();
orion5x_eth_init(&wrt350n_v2_eth_data);
- orion5x_eth_switch_init(&wrt350n_v2_switch_plat_data);
+ orion5x_eth_switch_init(&wrt350n_v2_switch_chip_data);
orion5x_uart0_init();
mvebu_mbus_add_window_by_id(ORION_MBUS_DEVBUS_BOOT_TARGET,
diff --git a/arch/arm/plat-orion/common.c b/arch/arm/plat-orion/common.c
index 272f49b2c68f..9255b6d67ba5 100644
--- a/arch/arm/plat-orion/common.c
+++ b/arch/arm/plat-orion/common.c
@@ -22,6 +22,7 @@
#include <linux/platform_data/dma-mv_xor.h>
#include <linux/platform_data/usb-ehci-orion.h>
#include <plat/common.h>
+#include <linux/phy.h>
/* Create a clkdev entry for a given device/clk */
void __init orion_clkdev_add(const char *con_id, const char *dev_id,
@@ -470,15 +471,27 @@ void __init orion_ge11_init(struct mv643xx_eth_platform_data *eth_data,
/*****************************************************************************
* Ethernet switch
****************************************************************************/
-void __init orion_ge00_switch_init(struct dsa_platform_data *d)
+static __initconst const char *orion_ge00_mvmdio_bus_name = "orion-mii";
+static __initdata struct mdio_board_info
+ orion_ge00_switch_board_info;
+
+void __init orion_ge00_switch_init(struct dsa_chip_data *d)
{
- int i;
+ struct mdio_board_info *bd;
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(d->port_names); i++)
+ if (!strcmp(d->port_names[i], "cpu"))
+ break;
- d->netdev = &orion_ge00.dev;
- for (i = 0; i < d->nr_chips; i++)
- d->chip[i].host_dev = &orion_ge_mvmdio.dev;
+ bd = &orion_ge00_switch_board_info;
+ bd->bus_id = orion_ge00_mvmdio_bus_name;
+ bd->mdio_addr = d->sw_addr;
+ d->netdev[i] = &orion_ge00.dev;
+ strcpy(bd->modalias, "mv88e6085");
+ bd->platform_data = d;
- platform_device_register_data(NULL, "dsa", 0, d, sizeof(d));
+ mdiobus_register_board_info(&orion_ge00_switch_board_info, 1);
}
/*****************************************************************************
diff --git a/arch/arm/plat-orion/include/plat/common.h b/arch/arm/plat-orion/include/plat/common.h
index 9347f3c58a6d..3647d3b33c20 100644
--- a/arch/arm/plat-orion/include/plat/common.h
+++ b/arch/arm/plat-orion/include/plat/common.h
@@ -12,7 +12,7 @@
#include <linux/mv643xx_eth.h>
#include <linux/platform_data/usb-ehci-orion.h>
-struct dsa_platform_data;
+struct dsa_chip_data;
struct mv_sata_platform_data;
void __init orion_uart0_init(void __iomem *membase,
@@ -57,7 +57,7 @@ void __init orion_ge11_init(struct mv643xx_eth_platform_data *eth_data,
unsigned long mapbase,
unsigned long irq);
-void __init orion_ge00_switch_init(struct dsa_platform_data *d);
+void __init orion_ge00_switch_init(struct dsa_chip_data *d);
void __init orion_i2c_init(unsigned long mapbase,
unsigned long irq,
--
2.9.3
^ permalink raw reply related
* [PATCH v3 16/24] media: Add i.MX media core driver
From: Steve Longerbeam @ 2017-01-14 22:46 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1484320822.31475.96.camel@pengutronix.de>
(sorry sending again w/o html)
On 01/13/2017 07:20 AM, Philipp Zabel wrote:
> Am Freitag, den 06.01.2017, 18:11 -0800 schrieb Steve Longerbeam:
>> +For image capture, the IPU contains the following internal subunits:
>> +
>> +- Image DMA Controller (IDMAC)
>> +- Camera Serial Interface (CSI)
>> +- Image Converter (IC)
>> +- Sensor Multi-FIFO Controller (SMFC)
>> +- Image Rotator (IRT)
>> +- Video De-Interlace Controller (VDIC)
> Nitpick: Video De-Interlacing or Combining Block (VDIC)
done.
>> +
>> +The IDMAC is the DMA controller for transfer of image frames to and from
>> +memory. Various dedicated DMA channels exist for both video capture and
>> +display paths.
>> +
>> +The CSI is the frontend capture unit that interfaces directly with
>> +capture sensors over Parallel, BT.656/1120, and MIPI CSI-2 busses.
>> +
>> +The IC handles color-space conversion, resizing, and rotation
>> +operations.
> And horizontal flipping.
done.
>> There are three independent "tasks" within the IC that can
>> +carry out conversions concurrently: pre-processing encoding,
>> +pre-processing preview, and post-processing.
> s/preview/viewfinder/ seems to be the commonly used name.
replaced everywhere in the doc.
> This paragraph could mention that a single hardware unit is used
> transparently time multiplexed by the three tasks at different
> granularity for the downsizing, main processing, and rotation sections.
> The downscale unit switches between tasks at 8-pixel burst granularity,
> the main processing unit at line granularity. The rotation units switch
> only at frame granularity.
I've added that info.
>> +The SMFC is composed of four independent channels that each can transfer
>> +captured frames from sensors directly to memory concurrently.
>> +
>> +The IRT carries out 90 and 270 degree image rotation operations.
> ... on 8x8 pixel blocks, supported by the IDMAC which handles block
> transfers, block reordering, and vertical flipping.
done.
>> +The VDIC handles the conversion of interlaced video to progressive, with
>> +support for different motion compensation modes (low, medium, and high
>> +motion). The deinterlaced output frames from the VDIC can be sent to the
>> +IC pre-process preview task for further conversions.
>> +
>> +In addition to the IPU internal subunits, there are also two units
>> +outside the IPU that are also involved in video capture on i.MX:
>> +
>> +- MIPI CSI-2 Receiver for camera sensors with the MIPI CSI-2 bus
>> + interface. This is a Synopsys DesignWare core.
>> +- A video multiplexer for selecting among multiple sensor inputs to
>> + send to a CSI.
> Two of them, actually.
done.
>> +
>> +- Includes a Frame Interval Monitor (FIM) that can correct vertical sync
>> + problems with the ADV718x video decoders. See below for a description
>> + of the FIM.
> Could this also be used to calculate more precise capture timestamps?
An input capture function could do that, triggered off a VSYNC or FIELD
signal such as on the ADV718x. The FIM is only used to calculate
frame intervals at this point, but its input capture method could be
used to also record more accurate timestamps.
>> +Capture Pipelines
>> +-----------------
>> +
>> +The following describe the various use-cases supported by the pipelines.
>> +
>> +The links shown do not include the frontend sensor, video mux, or mipi
>> +csi-2 receiver links. This depends on the type of sensor interface
>> +(parallel or mipi csi-2). So in all cases, these pipelines begin with:
>> +
>> +sensor -> ipu_csi_mux -> ipu_csi -> ...
>> +
>> +for parallel sensors, or:
>> +
>> +sensor -> imx-mipi-csi2 -> (ipu_csi_mux) -> ipu_csi -> ...
>> +
>> +for mipi csi-2 sensors. The imx-mipi-csi2 receiver may need to route
>> +to the video mux (ipu_csi_mux) before sending to the CSI, depending
>> +on the mipi csi-2 virtual channel, hence ipu_csi_mux is shown in
>> +parenthesis.
>> +
>> +Unprocessed Video Capture:
>> +--------------------------
>> +
>> +Send frames directly from sensor to camera interface, with no
>> +conversions:
>> +
>> +-> ipu_smfc -> camif
> I'd call this capture interface, this is not just for cameras. Or maybe
> idmac if you want to mirror hardware names?
Camif is so named because it is the V4L2 user interface for video
capture. I suppose it could be named "capif", but that doesn't role
off the tongue quite as well.
>> +Note the ipu_smfc can do pixel reordering within the same colorspace.
> That isn't a feature of the SMFC, but of the IDMAC (FCW & FCR).
yes, the doc is re-worded to make that more clear.
>> +For example, its sink pad can take UYVY2X8, but its source pad can
>> +output YUYV2X8.
> I don't think this is correct. Re-reading "37.4.3.7 Packing to memory"
> in the CSI chapter, for 8-bit per component data, the internal format
> between CSI, SMFC, and IDMAC is always some 32-bit RGBx/YUVx variant
> (or "bayer/generic data"). In either case, the internal format does not
> change along the way.
these are pixels in memory buffers, not the IPU internal formats.
>> +IC Direct Conversions:
>> +----------------------
>> +
>> +This pipeline uses the preprocess encode entity to route frames directly
>> +from the CSI to the IC (bypassing the SMFC), to carry out scaling up to
>> +1024x1024 resolution, CSC, and image rotation:
>> +
>> +-> ipu_ic_prpenc -> camif
>> +
>> +This can be a useful capture pipeline for heavily loaded memory bus
>> +traffic environments, since it has minimal IDMAC channel usage.
> Note that if rotation is enabled, transfers between IC processing and
> rotation still have to go through memory once.
yep.
>> +Post-Processing Conversions:
>> +----------------------------
>> +
>> +This pipeline routes frames from the SMFC to the post-processing
>> +entity.
> No, frames written by the CSI -> SMFC -> IDMAC path are read back into
> the post-processing entity.
that's true. The post-processing entity kicks off its read channels
to transfer those frames into the post-processor. Anyway this wording
will change after doing away with the SMFC entity.
>> + media-ctl -V "\"ipu1_csi0\":1 [fmt:YUYV2X8/640x480]"
>> + media-ctl -V "\"ipu1_smfc0\":0 [fmt:YUYV2X8/640x480]"
>> + media-ctl -V "\"ipu1_smfc0\":1 [fmt:UYVY2X8/640x480]"
> I think the smfc entities should be dropped.
yes working on that.
>> + media-ctl -V "\"camif0\":0 [fmt:UYVY2X8/640x480]"
>> + media-ctl -V "\"camif0\":1 [fmt:UYVY2X8/640x480]"
>> + # Configure pads for OV5640 pipeline
>> + media-ctl -V "\"ov5640_mipi 1-0040\":0 [fmt:UYVY2X8/640x480]"
>> + media-ctl -V "\"imx-mipi-csi2\":0 [fmt:UYVY2X8/640x480]"
>> + media-ctl -V "\"imx-mipi-csi2\":2 [fmt:UYVY2X8/640x480]"
>> + media-ctl -V "\"ipu1_csi1\":0 [fmt:UYVY2X8/640x480]"
>> + media-ctl -V "\"ipu1_csi1\":1 [fmt:UYVY2X8/640x480]"
> [...]
>> + media-ctl -V "\"camif1\":0 [fmt:UYVY2X8/640x480]"
> I agree this looks very intuitive, but technically correct for the
> csi1:1 and camif1:0 pads would be a 32-bit YUV format.
> (MEDIA_BUS_FMT_YUV8_1X32_PADLO doesn't exist yet).
>
> I think it would be better to use the correct format
I'm not sure I follow you here.
> as that will allow
> to chose the regular vs. companded packings in the future for formats
> with more than 8 bits per component.
>
>> + media-ctl -V "\"camif1\":1 [fmt:UYVY2X8/640x480]"
>> +
>> +Streaming can then begin independently on device nodes /dev/video0
>> +and /dev/video1.
>> +
>> +SabreAuto with ADV7180 decoder
>> +------------------------------
>> +
>> +On the SabreAuto, an on-board ADV7180 SD decoder is connected to the
>> +parallel bus input on the internal video mux to IPU1 CSI0.
>> +
>> +The following example configures a pipeline to capture from the ADV7180
>> +video decoder, assuming NTSC 720x480 input signals, with Motion
>> +Compensated de-interlacing (not shown: all pad field types should be set
>> +as indicated). $outputfmt can be any format supported by the
>> +ipu1_ic_prpvf entity at its output pad:
>> +
>> +.. code-block:: none
>> +
>> + # Setup links
>> + media-ctl -l '"adv7180 3-0021":0 -> "ipu1_csi0_mux":1[1]'
>> + media-ctl -l '"ipu1_csi0_mux":2 -> "ipu1_csi0":0[1]'
>> + media-ctl -l '"ipu1_csi0":1 -> "ipu1_smfc0":0[1]'
>> + media-ctl -l '"ipu1_smfc0":1 -> "ipu1_ic_prpvf":0[1]'
>> + media-ctl -l '"ipu1_ic_prpvf":1 -> "camif0":0[1]'
>> + media-ctl -l '"camif0":1 -> "camif0 devnode":0[1]'
>> + # Configure pads
>> + # pad field types for below pads must be an interlaced type
>> + # such as "ALTERNATE"
> I think alternate should only extend as far as the CSI, since the CSI
> can only capture NTSC/PAL fields in a fixed order.
Agreed, I'm doing the translation from alternate to seq_bt/seq_tb depending
on sensor std in imx-vdic.c, but it should move upstream. Will fix.
>> + media-ctl -V "\"adv7180 3-0021\":0 [fmt:UYVY2X8/720x480]"
>> + media-ctl -V "\"ipu1_csi0_mux\":1 [fmt:UYVY2X8/720x480]"
> From here the interlaced field type should be sequential in the correct
> order depending on NTSC/PAL.
right.
>> +
>> +Frame Interval Monitor
>> +----------------------
>> +
>> +The adv718x decoders can occasionally send corrupt fields during
>> +NTSC/PAL signal re-sync (too little or too many video lines). When
>> +this happens, the IPU triggers a mechanism to re-establish vertical
>> +sync by adding 1 dummy line every frame, which causes a rolling effect
>> +from image to image, and can last a long time before a stable image is
>> +recovered. Or sometimes the mechanism doesn't work at all, causing a
>> +permanent split image (one frame contains lines from two consecutive
>> +captured images).
> Is it only SabreAuto on which the FIM mechanism can be used due to the
> pad routing?
No, FIM can be used on any target, the fim child node of ipu_csi just
needs to be enabled via status property.
> [...]
>> +/*
>> + * DMA buffer ring handling
>> + */
>> +struct imx_media_dma_buf_ring {
>> + struct imx_media_dev *imxmd;
>> +
>> + /* the ring */
>> + struct imx_media_dma_buf buf[IMX_MEDIA_MAX_RING_BUFS];
>> + /* the scratch buffer for underruns */
>> + struct imx_media_dma_buf scratch;
>> +
>> + /* buffer generator */
>> + struct media_entity *src;
>> + /* buffer receiver */
>> + struct media_entity *sink;
>> +
>> + spinlock_t lock;
>> +
>> + int num_bufs;
>> + unsigned long last_seq;
>> +};
> I don't think this belongs in the capture driver at all.
> Memory-to-memory transfers should be handled at the videobuf2 level.
see below.
> [...]
>> +static struct imx_media_dma_buf *
>> +__dma_buf_queue(struct imx_media_dma_buf_ring *ring, int index)
>> +{
>> + struct imx_media_dma_buf *buf;
>> +
>> + if (index >= ring->num_bufs)
>> + return ERR_PTR(-EINVAL);
>> +
>> + buf = &ring->buf[index];
>> + if (WARN_ON(buf->state != IMX_MEDIA_BUF_STATUS_PREPARED))
>> + return ERR_PTR(-EINVAL);
>> +
>> + buf->state = IMX_MEDIA_BUF_STATUS_QUEUED;
>> + buf->seq = ring->last_seq++;
>> +
>> + return buf;
>> +}
> Is this a whole software buffer queue implementation? I thought the
> whole point of putting the custom mem2mem framework into the capture
> driver was to use the hardware FSU channel linking?
see below.
> What is the purpose of this if the sink should be triggered by the FSU?
Ok, here is where I need to make an admission.
The only FSU links I have attempted (and which currently have entries
in the fsu_link_info[] table), are the enc/vf/pp --> IRT links for rotation.
There does not appear to be support in the FSU for linking a write channel
to the VDIC read channels (8, 9, 10) according to VDI_SRC_SEL field. There
is support for the direct link from CSI (which I am using), but that's
not an
IDMAC channel link.
There is a PRP_SRC_SEL field, with linking from IDMAC (SMFC) channels
0..2 (and 3? it's not clear, and not clear whether this includes channel 1).
But I think this links to channel 12, and not to channels 8,9,10 to the
VDIC.
Or will it? It's worth experimenting. It would have helped if FSL listed
which
IDMAC channels these FSU links correspond to, instead of making us guess
at it.
In any event, the docs are not clear enough to implement a real FSU
link to the VDIC read channels, if it's even possible. And trying to get
programming help from FSL can be difficult, and no coding examples
for this link AFAIK.
So I ended resorted to linking to VDIC channels 8,9,10 with a software
approach, instead of attempting a hardware FSU link.
The EOF interrupt handler for the SMFC channels informs the VDIC
entity via a v4l2_subdev_ioctl() call that a buffer is available. The
VDIC then manually kicks off its read channels to bring that buffer
(and a previous buffer for F(n-1) field) into the VDIC.
There is a small amount of extra overhead going this route compared
to a FSU hardware link: there is the EOF irq latency (a few usec), and
the CPU overhead for the VDIC to manually start the read channels,
which is also a few usec at most (see prepare_vdi_in_buffers() in
imx-vdic.c). So in total at most ~10 usec of extra overhead (CPU
use plus irq latency) under normal system load.
Of course, in order to implement this software link, I had to implement
a straightforward FIFO dma buffer ring. The sink (VDIC) allocates the ring
at stream on, and the source requests a pointer to this ring in its own
stream on. Passing buffers from source to sink then follows a
straightforward
FIFO queue/done/dequeue/queue model: sink queues buffers to src, src
grabs queued buffers and makes them active, src signals completed
buffers to sink, sink dequeues buffers in response, and sink queues
buffers back when it is finished with them.
> [...]
>> +/*
>> + * The subdevs have to be powered on/off, and streaming
>> + * enabled/disabled, in a specific sequence.
>> + */
>> +static const u32 stream_on_seq[] = {
>> + IMX_MEDIA_GRP_ID_IC_PP,
>> + IMX_MEDIA_GRP_ID_IC_PRPVF,
>> + IMX_MEDIA_GRP_ID_IC_PRPENC,
>> + IMX_MEDIA_GRP_ID_SMFC,
>> + IMX_MEDIA_GRP_ID_SENSOR,
>> + IMX_MEDIA_GRP_ID_CSI2,
>> + IMX_MEDIA_GRP_ID_VIDMUX,
>> + IMX_MEDIA_GRP_ID_CSI,
>> +};
>> +
>> +static const u32 stream_off_seq[] = {
>> + IMX_MEDIA_GRP_ID_IC_PP,
>> + IMX_MEDIA_GRP_ID_IC_PRPVF,
>> + IMX_MEDIA_GRP_ID_IC_PRPENC,
>> + IMX_MEDIA_GRP_ID_SMFC,
>> + IMX_MEDIA_GRP_ID_CSI,
>> + IMX_MEDIA_GRP_ID_VIDMUX,
>> + IMX_MEDIA_GRP_ID_CSI2,
>> + IMX_MEDIA_GRP_ID_SENSOR,
>> +};
>> +
>> +#define NUM_STREAM_ENTITIES ARRAY_SIZE(stream_on_seq)
>> +
>> +static const u32 power_on_seq[] = {
>> + IMX_MEDIA_GRP_ID_CSI2,
>> + IMX_MEDIA_GRP_ID_SENSOR,
>> + IMX_MEDIA_GRP_ID_VIDMUX,
>> + IMX_MEDIA_GRP_ID_CSI,
>> + IMX_MEDIA_GRP_ID_SMFC,
>> + IMX_MEDIA_GRP_ID_IC_PRPENC,
>> + IMX_MEDIA_GRP_ID_IC_PRPVF,
>> + IMX_MEDIA_GRP_ID_IC_PP,
>> +};
>> +
>> +static const u32 power_off_seq[] = {
>> + IMX_MEDIA_GRP_ID_IC_PP,
>> + IMX_MEDIA_GRP_ID_IC_PRPVF,
>> + IMX_MEDIA_GRP_ID_IC_PRPENC,
>> + IMX_MEDIA_GRP_ID_SMFC,
>> + IMX_MEDIA_GRP_ID_CSI,
>> + IMX_MEDIA_GRP_ID_VIDMUX,
>> + IMX_MEDIA_GRP_ID_SENSOR,
>> + IMX_MEDIA_GRP_ID_CSI2,
>> +};
> This seems somewhat arbitrary. Why is a power sequence needed?
The CSI-2 receiver must be powered up before the sensor, that's the
only requirement IIRC. The others have no s_power requirement. So I
can probably change this to power up in the frontend -> backend order
(IC_PP to sensor). And vice-versa for power off.
> [...]
>> +/*
>> + * Turn current pipeline power on/off starting from start_entity.
>> + * Must be called with mdev->graph_mutex held.
>> + */
>> +int imx_media_pipeline_set_power(struct imx_media_dev *imxmd,
>> + struct media_entity_graph *graph,
>> + struct media_entity *start_entity, bool on)
>> +{
>> + struct media_entity *entity;
>> + struct v4l2_subdev *sd;
>> + int i, ret = 0;
>> + u32 id;
>> +
>> + for (i = 0; i < NUM_POWER_ENTITIES; i++) {
>> + id = on ? power_on_seq[i] : power_off_seq[i];
>> + entity = find_pipeline_entity(imxmd, graph, start_entity, id);
>> + if (!entity)
>> + continue;
>> +
>> + sd = media_entity_to_v4l2_subdev(entity);
>> +
>> + ret = v4l2_subdev_call(sd, core, s_power, on);
>> + if (ret && ret != -ENOIOCTLCMD)
>> + break;
>> + }
>> +
>> + return (ret && ret != -ENOIOCTLCMD) ? ret : 0;
>> +}
>> +EXPORT_SYMBOL_GPL(imx_media_pipeline_set_power);
> This should really be handled by v4l2_pipeline_pm_use.
I thought about this earlier, but v4l2_pipeline_pm_use() seems to be
doing some other stuff that bothered me, at least that's what I remember.
I will revisit this.
>> +/*
>> + * Inherit the v4l2 controls from all entities in a pipeline
>> + * to the given video device.
>> + * Must be called with mdev->graph_mutex held.
>> + */
>> +int imx_media_inherit_controls(struct imx_media_dev *imxmd,
>> + struct video_device *vfd,
>> + struct media_entity *start_entity)
>> +{
>> + struct media_entity_graph graph;
>> + struct media_entity *entity;
>> + struct v4l2_subdev *sd;
>> + int ret;
>> +
>> + ret = media_entity_graph_walk_init(&graph, &imxmd->md);
>> + if (ret)
>> + return ret;
>> +
>> + media_entity_graph_walk_start(&graph, start_entity);
>> +
>> + while ((entity = media_entity_graph_walk_next(&graph))) {
>> + if (is_media_entity_v4l2_video_device(entity))
>> + continue;
>> +
>> + sd = media_entity_to_v4l2_subdev(entity);
>> +
>> + dev_dbg(imxmd->dev, "%s: adding controls from %s\n",
>> + __func__, sd->name);
>> +
>> + ret = v4l2_ctrl_add_handler(vfd->ctrl_handler,
>> + sd->ctrl_handler,
>> + NULL);
>> + if (ret)
>> + break;
>> + }
>> +
>> + media_entity_graph_walk_cleanup(&graph);
>> + return ret;
>> +}
>> +EXPORT_SYMBOL_GPL(imx_media_inherit_controls);
>> +
>> +MODULE_DESCRIPTION("i.MX5/6 v4l2 media controller driver");
>> +MODULE_AUTHOR("Steve Longerbeam<steve_longerbeam@mentor.com>");
>> +MODULE_LICENSE("GPL");
>> diff --git a/drivers/staging/media/imx/imx-media-dev.c b/drivers/staging/media/imx/imx-media-dev.c
>> new file mode 100644
>> index 0000000..357654d
>> --- /dev/null
>> +++ b/drivers/staging/media/imx/imx-media-dev.c
> This file is full of code that should live in the v4l2 core.
Agreed. Stuff like imx_media_inherit_controls() really should be widely
available.
>> +int imx_media_add_internal_subdevs(struct imx_media_dev *imxmd,
>> + struct imx_media_subdev *csi[4])
>> +{
>> + int ret;
>> +
>> + /* there must be at least one CSI in first IPU */
> Why?
Well yeah, imx-media doesn't necessarily need a CSI if things
like the VDIC or post-processor are being used by an output
overlay pipeline, for example. I'll fix this.
>> +
>> +/* parse inputs property from a sensor node */
>> +static void of_parse_sensor_inputs(struct imx_media_dev *imxmd,
>> + struct imx_media_subdev *sensor,
>> + struct device_node *sensor_np)
>> +{
>> + struct imx_media_sensor_input *sinput = &sensor->input;
>> + int ret, i;
>> +
>> + for (i = 0; i < IMX_MEDIA_MAX_SENSOR_INPUTS; i++) {
>> + const char *input_name;
>> + u32 val;
>> +
>> + ret = of_property_read_u32_index(sensor_np, "inputs", i, &val);
>> + if (ret)
>> + break;
>> +
>> + sinput->value[i] = val;
>> +
>> + ret = of_property_read_string_index(sensor_np, "input-names",
>> + i, &input_name);
>> + /*
>> + * if input-names not provided, they will be set using
>> + * the subdev name once the sensor is known during
>> + * async bind
>> + */
>> + if (!ret)
>> + strncpy(sinput->name[i], input_name,
>> + sizeof(sinput->name[i]));
>> + }
>> +
>> + sinput->num = i;
>> +
>> + /* if no inputs provided just assume a single input */
>> + if (sinput->num == 0)
>> + sinput->num = 1;
>> +}
> This should be parsed by the sensor driver, not imx-media.
you're probably right. I'll submit a patch for adv7180.c.
>> +static void of_parse_sensor(struct imx_media_dev *imxmd,
>> + struct imx_media_subdev *sensor,
>> + struct device_node *sensor_np)
>> +{
>> + struct device_node *endpoint;
>> +
>> + of_parse_sensor_inputs(imxmd, sensor, sensor_np);
>> +
>> + endpoint = of_graph_get_next_endpoint(sensor_np, NULL);
>> + if (endpoint) {
>> + v4l2_of_parse_endpoint(endpoint, &sensor->sensor_ep);
>> + of_node_put(endpoint);
>> + }
>> +}
>> +
>> +static int of_get_port_count(const struct device_node *np)
>> +{
>> + struct device_node *child;
>> + int num = 0;
>> +
>> + /* if this node is itself a port, return 1 */
>> + if (of_node_cmp(np->name, "port") == 0)
>> + return 1;
>> +
>> + for_each_child_of_node(np, child)
>> + if (of_node_cmp(child->name, "port") == 0)
>> + num++;
>> +
>> + return num;
>> +}
> If this is extended to handle the ports subnode properly, it could be
> moved into drivers/of/base.c.
More that eventually needs exporting, agreed.
Steve
^ permalink raw reply
* Nokia N900: mixers changed between 4.9 and 4.10-rc3, no longer can use in-call speaker
From: Pavel Machek @ 2017-01-14 22:56 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <201701121934.19810@pali>
Hi!
> > In v4.10 (and probably v4.9, too) I can no longer use the in-call
> > speaker. I can no longer use the wired headset, either.
> >
> > v4.4 (and probably v4.6) works ok.
> >
> > Any ideas? Does wired headset / in-call speaker work for you?
> >
> > "Mono" and "Mono DAC" options are still there.. but something else
> > changed, as alsamixer now shows way many more options (meaning they
> > are shorter?) and I get complains from alsactl:
> >
> > alsactl: set_control:1328: failed to obtain info for control #49 (No
> > such file or directory)
> > ...
> > alsactl: set_control:1328: failed to obtain info for control #229 (No
> > such file or directory)
>
> Looks like there are not so much commits related to
> sound/soc/omap/rx51.c Maybe it can be one of these twos?
>
> cb7e62256e99d285e415cf75db67558f0f8bb107
> 6d2de5ab4328718302c54b20222c6b1a574c3fce
Both are based on v4.7-rc1. v4.6 worked ok for me.
Lets test mini-v4.7: in-call speaker works ok there, and no alsactl
warnings.
mini-v4.7+cb7e62256e99d285e415cf75db67558f0f8bb107+6d2de5ab4328718302c54b20222c6b1a574c3fce
: something seems to be broken there already.
alsactl: set_control:1464: Cannot write control '2:0:0:TPA6130A2
Headphone Playback Volume:0' : Remote I/O error
But in-call speakers and wired headset still seems to work.
Lets test mini-v4.9... works ok. I don't even get the remote i/o
error. Interesting.
So regression seems to be between v4.9 and v4.10. Any ideas?
Pavel
--
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 181 bytes
Desc: Digital signature
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20170114/3faf5c9e/attachment.sig>
^ permalink raw reply
* Nokia N900: mixers changed between 4.9 and 4.10-rc3, no longer can use in-call speaker
From: Pali Rohár @ 2017-01-15 0:00 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170114225614.GA25321@amd>
On Saturday 14 January 2017 23:56:15 Pavel Machek wrote:
> Hi!
>
> > > In v4.10 (and probably v4.9, too) I can no longer use the in-call
> > > speaker. I can no longer use the wired headset, either.
> > >
> > > v4.4 (and probably v4.6) works ok.
> > >
> > > Any ideas? Does wired headset / in-call speaker work for you?
> > >
> > > "Mono" and "Mono DAC" options are still there.. but something
> > > else changed, as alsamixer now shows way many more options
> > > (meaning they are shorter?) and I get complains from alsactl:
> > >
> > > alsactl: set_control:1328: failed to obtain info for control #49
> > > (No such file or directory)
> > > ...
> > > alsactl: set_control:1328: failed to obtain info for control #229
> > > (No such file or directory)
> >
> > Looks like there are not so much commits related to
> > sound/soc/omap/rx51.c Maybe it can be one of these twos?
> >
> > cb7e62256e99d285e415cf75db67558f0f8bb107
> > 6d2de5ab4328718302c54b20222c6b1a574c3fce
>
> Both are based on v4.7-rc1. v4.6 worked ok for me.
>
> Lets test mini-v4.7: in-call speaker works ok there, and no alsactl
> warnings.
>
> mini-v4.7+cb7e62256e99d285e415cf75db67558f0f8bb107+6d2de5ab4328718302
> c54b20222c6b1a574c3fce
>
> : something seems to be broken there already.
>
> alsactl: set_control:1464: Cannot write control '2:0:0:TPA6130A2
> Headphone Playback Volume:0' : Remote I/O error
>
> But in-call speakers and wired headset still seems to work.
>
> Lets test mini-v4.9... works ok. I don't even get the remote i/o
> error. Interesting.
>
> So regression seems to be between v4.9 and v4.10. Any ideas?
Interesting... seems there are no sound relevant changes after v4.9.
Looks like there are only three commits after v4.9 for sound/soc which
are built for Nokia N900:
e411b0b5eb9b65257a050eac333d181d6e00e2c6
e7aa450fe17890e59db7d3c2d8eff5b6b41fc531
63c3194b82530bd71fd49db84eb7ab656b8d404a
Maybe something not related to sound/soc could broke it?
--
Pali Roh?r
pali.rohar at gmail.com
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 198 bytes
Desc: This is a digitally signed message part.
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20170115/1ff353d6/attachment.sig>
^ permalink raw reply
* [PATCH v3] ARM: dts: Add LEGO MINDSTORMS EV3 dts
From: kbuild test robot @ 2017-01-15 0:13 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1484253167-27568-1-git-send-email-david@lechnology.com>
Hi David,
[auto build test ERROR on robh/for-next]
[also build test ERROR on v4.10-rc3 next-20170113]
[if your patch is applied to the wrong git tree, please drop us a note to help improve the system]
url: https://github.com/0day-ci/linux/commits/David-Lechner/ARM-dts-Add-LEGO-MINDSTORMS-EV3-dts/20170114-145113
base: https://git.kernel.org/pub/scm/linux/kernel/git/robh/linux.git for-next
config: arm-u300_defconfig (attached as .config)
compiler: arm-linux-gnueabi-gcc (Debian 6.1.1-9) 6.1.1 20160705
reproduce:
wget https://git.kernel.org/cgit/linux/kernel/git/wfg/lkp-tests.git/plain/sbin/make.cross -O ~/bin/make.cross
chmod +x ~/bin/make.cross
# save the attached .config to linux build tree
make.cross ARCH=arm
All errors (new ones prefixed by >>):
>> Error: arch/arm/boot/dts/da850-lego-ev3.dts:310.1-6 Label or path usb1 not found
FATAL ERROR: Syntax error parsing input tree
---
0-DAY kernel test infrastructure Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all Intel Corporation
-------------- next part --------------
A non-text attachment was scrubbed...
Name: .config.gz
Type: application/gzip
Size: 12462 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20170115/a5b32497/attachment-0001.gz>
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox