]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
iio: adc: ad7124: add external clock support
authorDavid Lechner <dlechner@baylibre.com>
Thu, 28 Aug 2025 21:54:54 +0000 (16:54 -0500)
committerJonathan Cameron <Jonathan.Cameron@huawei.com>
Sat, 30 Aug 2025 19:46:48 +0000 (20:46 +0100)
Add support for an external clock source to the AD7124 ADC driver.

Previously, the driver only supported using the internal clock and had
bad devicetree bindings that used a fake clock to essentially select
the power mode. This is preserved for backwards compatibility.

If the clock is not named "mclk", then we know that the devicetree is
using the correct bindings and we can configure the chip to use an
external clock source rather than internal.

Also drop a redundant comment when configuring the register fields
instead of adding more.

Signed-off-by: David Lechner <dlechner@baylibre.com>
Link: https://patch.msgid.link/20250828-iio-adc-ad7124-proper-clock-support-v3-3-0b317b4605e5@baylibre.com
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
drivers/iio/adc/ad7124.c

index 934437bda97b0ed5ee27465f069aabee58d6d92d..1a9a8d6f1943398fa6cd257527aed99559d3e8d0 100644 (file)
@@ -18,6 +18,7 @@
 #include <linux/property.h>
 #include <linux/regulator/consumer.h>
 #include <linux/spi/spi.h>
+#include <linux/units.h>
 
 #include <linux/iio/iio.h>
 #include <linux/iio/adc/ad_sigma_delta.h>
 #define AD7124_STATUS_POR_FLAG                 BIT(4)
 
 /* AD7124_ADC_CONTROL */
+#define AD7124_ADC_CONTROL_CLK_SEL             GENMASK(1, 0)
+#define AD7124_ADC_CONTROL_CLK_SEL_INT                 0
+#define AD7124_ADC_CONTROL_CLK_SEL_INT_OUT             1
+#define AD7124_ADC_CONTROL_CLK_SEL_EXT                 2
+#define AD7124_ADC_CONTROL_CLK_SEL_EXT_DIV4            3
 #define AD7124_ADC_CONTROL_MODE                        GENMASK(5, 2)
 #define AD7124_ADC_CONTROL_MODE_CONTINUOUS             0
 #define AD7124_ADC_CONTROL_MODE_SINGLE                 1
@@ -92,6 +98,8 @@
 #define AD7124_MAX_CONFIGS     8
 #define AD7124_MAX_CHANNELS    16
 
+#define AD7124_INT_CLK_HZ      614400
+
 /* AD7124 input sources */
 
 enum ad7124_ref_sel {
@@ -120,9 +128,9 @@ static const unsigned int ad7124_reg_size[] = {
 };
 
 static const int ad7124_master_clk_freq_hz[3] = {
-       [AD7124_LOW_POWER] = 76800,
-       [AD7124_MID_POWER] = 153600,
-       [AD7124_FULL_POWER] = 614400,
+       [AD7124_LOW_POWER] = AD7124_INT_CLK_HZ / 8,
+       [AD7124_MID_POWER] = AD7124_INT_CLK_HZ / 4,
+       [AD7124_FULL_POWER] = AD7124_INT_CLK_HZ,
 };
 
 static const char * const ad7124_ref_names[] = {
@@ -174,6 +182,7 @@ struct ad7124_state {
        struct ad_sigma_delta sd;
        struct ad7124_channel *channels;
        struct regulator *vref[4];
+       u32 clk_hz;
        unsigned int adc_control;
        unsigned int num_channels;
        struct mutex cfgs_lock; /* lock for configs access */
@@ -249,12 +258,33 @@ static int ad7124_set_mode(struct ad_sigma_delta *sd,
        return ad_sd_write_reg(&st->sd, AD7124_ADC_CONTROL, 2, st->adc_control);
 }
 
+static u32 ad7124_get_fclk_hz(struct ad7124_state *st)
+{
+       enum ad7124_power_mode power_mode;
+       u32 fclk_hz;
+
+       power_mode = FIELD_GET(AD7124_ADC_CONTROL_POWER_MODE, st->adc_control);
+       fclk_hz = st->clk_hz;
+
+       switch (power_mode) {
+       case AD7124_LOW_POWER:
+               fclk_hz /= 8;
+               break;
+       case AD7124_MID_POWER:
+               fclk_hz /= 4;
+               break;
+       default:
+               break;
+       }
+
+       return fclk_hz;
+}
+
 static void ad7124_set_channel_odr(struct ad7124_state *st, unsigned int channel, unsigned int odr)
 {
        unsigned int fclk, odr_sel_bits;
 
-       fclk = ad7124_master_clk_freq_hz[FIELD_GET(AD7124_ADC_CONTROL_POWER_MODE,
-                                                  st->adc_control)];
+       fclk = ad7124_get_fclk_hz(st);
 
        /*
         * FS[10:0] = fCLK / (fADC x 32) where:
@@ -1112,7 +1142,7 @@ static int ad7124_parse_channel_config(struct iio_dev *indio_dev,
 static int ad7124_setup(struct ad7124_state *st)
 {
        struct device *dev = &st->sd.spi->dev;
-       unsigned int power_mode;
+       unsigned int power_mode, clk_sel;
        struct clk *mclk;
        int i, ret;
 
@@ -1156,9 +1186,48 @@ static int ad7124_setup(struct ad7124_state *st)
                                return dev_err_probe(dev, ret,
                                                     "Failed to set mclk rate\n");
                }
+
+               clk_sel = AD7124_ADC_CONTROL_CLK_SEL_INT;
+               st->clk_hz = AD7124_INT_CLK_HZ;
+       } else {
+               struct clk *clk;
+
+               clk = devm_clk_get_optional_enabled(dev, NULL);
+               if (IS_ERR(clk))
+                       return dev_err_probe(dev, PTR_ERR(clk),
+                                            "Failed to get external clock\n");
+
+               if (clk) {
+                       unsigned long clk_hz;
+
+                       clk_hz = clk_get_rate(clk);
+                       if (!clk_hz)
+                               return dev_err_probe(dev, -EINVAL,
+                                       "Failed to get external clock rate\n");
+
+                       /*
+                        * The external clock may be 4x the nominal clock rate,
+                        * in which case the ADC needs to be configured to
+                        * divide it by 4. Using MEGA is a bit arbitrary, but
+                        * the expected clock rates are either 614.4 kHz or
+                        * 2.4576 MHz, so this should work.
+                        */
+                       if (clk_hz > (1 * HZ_PER_MHZ)) {
+                               clk_sel = AD7124_ADC_CONTROL_CLK_SEL_EXT_DIV4;
+                               st->clk_hz = clk_hz / 4;
+                       } else {
+                               clk_sel = AD7124_ADC_CONTROL_CLK_SEL_EXT;
+                               st->clk_hz = clk_hz;
+                       }
+               } else {
+                       clk_sel = AD7124_ADC_CONTROL_CLK_SEL_INT;
+                       st->clk_hz = AD7124_INT_CLK_HZ;
+               }
        }
 
-       /* Set the power mode */
+       st->adc_control &= ~AD7124_ADC_CONTROL_CLK_SEL;
+       st->adc_control |= FIELD_PREP(AD7124_ADC_CONTROL_CLK_SEL, clk_sel);
+
        st->adc_control &= ~AD7124_ADC_CONTROL_POWER_MODE;
        st->adc_control |= FIELD_PREP(AD7124_ADC_CONTROL_POWER_MODE, power_mode);