[PATCH linux dev-5.8 v2 02/11] iio: adc: add calibration support to npcm ADC
Joel Stanley
joel at jms.id.au
Mon Jan 11 11:52:58 AEDT 2021
On Tue, 5 Jan 2021 at 13:45, Tomer Maimon <tmaimon77 at gmail.com> wrote:
>
> Add calibration to improve accuracy measurement when using
> internal referance voltage.
reference
>
> the calibration values taken from the FUSE module.
>
> Signed-off-by: Tomer Maimon <tmaimon77 at gmail.com>
I recommend taking a look at the proposed device tree changes before
we merge this one, so we don't have an incompatibility between future
code and device tree.
> ---
> drivers/iio/adc/npcm_adc.c | 191 +++++++++++++++++++++++++++++++++++++
> 1 file changed, 191 insertions(+)
>
> diff --git a/drivers/iio/adc/npcm_adc.c b/drivers/iio/adc/npcm_adc.c
> index 83bad2d5575d..02628b7eaca1 100644
> --- a/drivers/iio/adc/npcm_adc.c
> +++ b/drivers/iio/adc/npcm_adc.c
> @@ -17,6 +17,8 @@
> #include <linux/reset.h>
>
> struct npcm_adc {
> + u32 R05;
> + u32 R15;
> bool int_status;
> u32 adc_sample_hz;
> struct device *dev;
> @@ -51,6 +53,40 @@ struct npcm_adc {
> #define NPCM_RESOLUTION_BITS 10
> #define NPCM_INT_VREF_MV 2000
>
> +/* FUSE registers */
> +#define NPCM7XX_FST 0x00
> +#define NPCM7XX_FADDR 0x04
> +#define NPCM7XX_FDATA 0x08
> +#define NPCM7XX_FCFG 0x0C
> +#define NPCM7XX_FCTL 0x14
> +
> +/* FST Register Bits */
> +#define NPCM7XX_FST_RDY BIT(0)
> +#define NPCM7XX_FST_RDST BIT(1)
> +
> +/* FADDR Register Bits */
> +#define NPCM7XX_FADDR_BYTEADDR BIT(0)
> +#define NPCM7XX_FADDR_BYTEADDR_MASK GENMASK(9, 0)
> +
> +/* FADDR Register Bits */
> +#define NPCM7XX_FDATA_DATA BIT(0)
> +#define NPCM7XX_FDATA_CLEAN_VALUE BIT(1)
> +#define NPCM7XX_FDATA_DATA_MASK GENMASK(7, 0)
> +
> +/* FCTL Register Bits */
> +#define NPCM7XX_FCTL_RDST BIT(1)
> +
> +/* ADC Calibration Definition */
> +#define NPCM_INT_1500MV 768
> +#define NPCM_INT_1000MV 512
> +#define NPCM_ADC_MIN_VAL 0
> +#define NPCM_ADC_MAX_VAL 1023
> +
> +#define FUSE_CALIB_ADDR 24
> +#define FUSE_CALIB_SIZE 8
> +#define DATA_CALIB_SIZE 4
> +#define FUSE_READ_TIMEOUT 0xDEADBEEF
> +
> #define NPCM_ADC_CHAN(ch) { \
> .type = IIO_VOLTAGE, \
> .indexed = 1, \
> @@ -71,6 +107,133 @@ static const struct iio_chan_spec npcm_adc_iio_channels[] = {
> NPCM_ADC_CHAN(7),
> };
>
> +static int npcm750_fuse_wait_for_ready(struct regmap *fuse_regmap, u32 timeout)
> +{
> + u32 time = timeout;
> + u32 fstreg;
> +
> + while (--time > 1) {
> + regmap_read(fuse_regmap, NPCM7XX_FST, &fstreg);
regmap_read_poll_timeout
> + if (fstreg & NPCM7XX_FST_RDY) {
> + regmap_write_bits(fuse_regmap, NPCM7XX_FST,
> + NPCM7XX_FST_RDST, NPCM7XX_FST_RDST);
> + return 0;
> + }
> + }
> +
> + /* try to clear the status in case it was set */
> + regmap_write_bits(fuse_regmap, NPCM7XX_FST, NPCM7XX_FST_RDST,
> + NPCM7XX_FST_RDST);
> +
> + return -EINVAL;
> +}
> +
> +static void npcm750_fuse_read(struct regmap *fuse_regmap, u32 addr, u8 *data)
> +{
> + u32 val;
> +
> + npcm750_fuse_wait_for_ready(fuse_regmap, FUSE_READ_TIMEOUT);
This ignores the time out.
> +
> + regmap_write_bits(fuse_regmap, NPCM7XX_FADDR,
> + NPCM7XX_FADDR_BYTEADDR_MASK, addr);
> + regmap_read(fuse_regmap, NPCM7XX_FADDR, &val);
> + regmap_write(fuse_regmap, NPCM7XX_FCTL, NPCM7XX_FCTL_RDST);
> +
> + npcm750_fuse_wait_for_ready(fuse_regmap, FUSE_READ_TIMEOUT);
...as does this.
> + regmap_read(fuse_regmap, NPCM7XX_FDATA, &val);
> + *data = (u8)val;
> +
> + regmap_write_bits(fuse_regmap, NPCM7XX_FDATA, NPCM7XX_FDATA_DATA_MASK,
> + NPCM7XX_FDATA_CLEAN_VALUE);
> +}
> +
> +static int npcm750_ECC_to_nibble(u8 ECC, u8 nibble)
> +{
> + u8 nibble_b0 = (nibble >> 0) & BIT(0);
> + u8 nibble_b1 = (nibble >> 1) & BIT(0);
> + u8 nibble_b2 = (nibble >> 2) & BIT(0);
> + u8 nibble_b3 = (nibble >> 3) & BIT(0);
> + u8 tmp_ECC = nibble;
> +
> + tmp_ECC |= (nibble_b0 ^ nibble_b1) << 4 | (nibble_b2 ^ nibble_b3) << 5 |
> + (nibble_b0 ^ nibble_b2) << 6 | (nibble_b1 ^ nibble_b3) << 7;
> +
> + if (tmp_ECC != ECC)
> + return -EINVAL;
> +
> + return 0;
> +}
> +
> +static int npcm750_ECC_to_byte(u16 ECC, u8 *Byte)
> +{
> + u8 nibble_L, nibble_H;
> + u8 ECC_L, ECC_H;
> +
> + ECC_H = ECC >> 8;
> + nibble_H = ECC_H & 0x0F;
> + ECC_L = ECC >> 0;
> + nibble_L = ECC_L & 0x0F;
> +
> + if (npcm750_ECC_to_nibble(ECC_H, nibble_H) != 0 ||
> + npcm750_ECC_to_nibble(ECC_L, nibble_L) != 0)
> + return -EINVAL;
> +
> + *Byte = nibble_H << 4 | nibble_L << 0;
> +
> + return 0;
> +}
> +
> +static int npcm750_read_nibble_parity(u8 *block_ECC, u8 *ADC_calib)
> +{
> + int i;
> + u16 ECC;
> +
> + for (i = 0; i < DATA_CALIB_SIZE; i++) {
> + memcpy(&ECC, block_ECC + (i * 2), 2);
> + if (npcm750_ECC_to_byte(ECC, &ADC_calib[i]) != 0)
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +static int npcm750_fuse_calibration_read(struct platform_device *pdev,
> + struct npcm_adc *info)
> +{
> + struct device_node *np = pdev->dev.of_node;
> + struct regmap *fuse_regmap;
> + ssize_t bytes_read = 0;
> + u8 read_buf[8];
> + u32 ADC_calib;
> + u32 addr = FUSE_CALIB_ADDR;
> +
> + if (of_device_is_compatible(np, "nuvoton,npcm750-adc")) {
This will always be true.
> + fuse_regmap = syscon_regmap_lookup_by_compatible
> + ("nuvoton,npcm750-fuse");
If you use a phandle to the fuse node, you can have the one code base
support multiple families of chips. Use
syscon_regmap_lookup_by_phandle(np, "syscon")
and in your device tree:
adc: adc at c000 {
compatible = "nuvoton,npcm750-adc";
reg = <0xc000 0x8>;
interrupts = <GIC_SPI 0 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clk NPCM7XX_CLK_ADC>;
resets = <&rstc NPCM7XX_RESET_IPSRST1
NPCM7XX_RESET_ADC>;
syscon = <&fuse_syscon>;
};
> + if (IS_ERR(fuse_regmap)) {
> + dev_warn(&pdev->dev, "Failed to find nuvoton,npcm750-fuse\n");
> + return PTR_ERR(fuse_regmap);
> + }
> +
> + while (bytes_read < FUSE_CALIB_SIZE) {
> + npcm750_fuse_read(fuse_regmap, addr,
> + &read_buf[bytes_read]);
> + bytes_read++;
> + addr++;
> + }
> +
> + if (npcm750_read_nibble_parity(read_buf, (u8 *)&ADC_calib)) {
> + dev_warn(info->dev, "FUSE Clibration read failed\n");
calibration
> + return -EINVAL;
> + }
> +
> + info->R05 = ADC_calib & 0xFFFF;
> + info->R15 = ADC_calib >> 16;
> + }
> +
> + return 0;
> +}
> +
> static irqreturn_t npcm_adc_isr(int irq, void *data)
> {
> u32 regtemp;
> @@ -125,6 +288,29 @@ static int npcm_adc_read(struct npcm_adc *info, int *val, u8 channel)
> return 0;
> }
>
> +static void npcm_adc_calibration(int *val, struct npcm_adc *info)
> +{
> + int mul_val;
> + int offset_val;
> +
> + mul_val = NPCM_INT_1000MV * (*val - info->R15);
> + if (mul_val < 0) {
> + mul_val = mul_val * -1;
> + offset_val = DIV_ROUND_CLOSEST(mul_val,
> + (info->R15 - info->R05));
> + *val = NPCM_INT_1500MV - offset_val;
> + } else {
> + offset_val = DIV_ROUND_CLOSEST(mul_val,
> + (info->R15 - info->R05));
> + *val = NPCM_INT_1500MV + offset_val;
> + }
> +
> + if (*val < NPCM_ADC_MIN_VAL)
> + *val = NPCM_ADC_MIN_VAL;
> + if (*val > NPCM_ADC_MAX_VAL)
> + *val = NPCM_ADC_MAX_VAL;
> +}
> +
> static int npcm_adc_read_raw(struct iio_dev *indio_dev,
> struct iio_chan_spec const *chan, int *val,
> int *val2, long mask)
> @@ -142,6 +328,10 @@ static int npcm_adc_read_raw(struct iio_dev *indio_dev,
> dev_err(info->dev, "NPCM ADC read failed\n");
> return ret;
> }
> +
> + if ((info->R05 || info->R15) && IS_ERR(info->vref))
> + npcm_adc_calibration(val, info);
> +
> return IIO_VAL_INT;
> case IIO_CHAN_INFO_SCALE:
> if (!IS_ERR(info->vref)) {
> @@ -248,6 +438,7 @@ static int npcm_adc_probe(struct platform_device *pdev)
> info->regs + NPCM_ADCCON);
> }
>
> + npcm750_fuse_calibration_read(pdev, info);
> init_waitqueue_head(&info->wq);
>
> reg_con = ioread32(info->regs + NPCM_ADCCON);
> --
> 2.22.0
>
More information about the openbmc
mailing list