diff mbox series

[v1,2/2] iio: adc: adding support for pac193x

Message ID 20230220123232.413029-3-marius.cristea@microchip.com (mailing list archive)
State Changes Requested
Headers show
Series adding support for Microchip PAC193X Power Monitor | expand

Commit Message

marius.cristea@microchip.com Feb. 20, 2023, 12:32 p.m. UTC
From: Marius Cristea <marius.cristea@microchip.com>

This is the iio driver for Microchip
PAC193X series of Power Monitor with Accumulator chip family.

Signed-off-by: Marius Cristea <marius.cristea@microchip.com>
---
 MAINTAINERS               |    7 +
 drivers/iio/adc/Kconfig   |   12 +
 drivers/iio/adc/Makefile  |    1 +
 drivers/iio/adc/pac193x.c | 2072 +++++++++++++++++++++++++++++++++++++
 4 files changed, 2092 insertions(+)
 create mode 100644 drivers/iio/adc/pac193x.c

Comments

kernel test robot Feb. 20, 2023, 8:04 p.m. UTC | #1
Hi,

I love your patch! Perhaps something to improve:

[auto build test WARNING on jic23-iio/togreg]
[also build test WARNING on linus/master v6.2 next-20230220]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/marius-cristea-microchip-com/dt-bindings-iio-adc-adding-dt-bindings-for-PAC193X/20230220-203540
base:   https://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio.git togreg
patch link:    https://lore.kernel.org/r/20230220123232.413029-3-marius.cristea%40microchip.com
patch subject: [PATCH v1 2/2] iio: adc: adding support for pac193x
config: m68k-allmodconfig (https://download.01.org/0day-ci/archive/20230221/202302210331.iKaMm4co-lkp@intel.com/config)
compiler: m68k-linux-gcc (GCC) 12.1.0
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # https://github.com/intel-lab-lkp/linux/commit/fd3be916ffe18735a98bdc55ccc0cb5f3097582c
        git remote add linux-review https://github.com/intel-lab-lkp/linux
        git fetch --no-tags linux-review marius-cristea-microchip-com/dt-bindings-iio-adc-adding-dt-bindings-for-PAC193X/20230220-203540
        git checkout fd3be916ffe18735a98bdc55ccc0cb5f3097582c
        # save the config file
        mkdir build_dir && cp config build_dir/.config
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross W=1 O=build_dir ARCH=m68k olddefconfig
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross W=1 O=build_dir ARCH=m68k SHELL=/bin/bash drivers/iio/adc/

If you fix the issue, kindly add following tag where applicable
| Reported-by: kernel test robot <lkp@intel.com>
| Link: https://lore.kernel.org/oe-kbuild-all/202302210331.iKaMm4co-lkp@intel.com/

All warnings (new ones prefixed by >>):

   drivers/iio/adc/pac193x.c: In function 'pac193x_acpi_get_acpi_match_entry':
>> drivers/iio/adc/pac193x.c:1402:21: warning: variable 'status' set but not used [-Wunused-but-set-variable]
    1402 |         acpi_status status;
         |                     ^~~~~~


vim +/status +1402 drivers/iio/adc/pac193x.c

  1399	
  1400	static char *pac193x_acpi_get_acpi_match_entry(acpi_handle handle)
  1401	{
> 1402		acpi_status status;
  1403		union acpi_object *name_object;
  1404		struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL};
  1405	
  1406		status = acpi_evaluate_object(handle, "_HID", NULL, &buffer);
  1407		name_object = buffer.pointer;
  1408	
  1409		return name_object->string.pointer;
  1410	}
  1411
kernel test robot Feb. 20, 2023, 9:36 p.m. UTC | #2
Hi,

I love your patch! Perhaps something to improve:

[auto build test WARNING on jic23-iio/togreg]
[also build test WARNING on linus/master v6.2 next-20230220]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/marius-cristea-microchip-com/dt-bindings-iio-adc-adding-dt-bindings-for-PAC193X/20230220-203540
base:   https://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio.git togreg
patch link:    https://lore.kernel.org/r/20230220123232.413029-3-marius.cristea%40microchip.com
patch subject: [PATCH v1 2/2] iio: adc: adding support for pac193x
config: powerpc-allmodconfig (https://download.01.org/0day-ci/archive/20230221/202302210551.5yGSdcbc-lkp@intel.com/config)
compiler: powerpc-linux-gcc (GCC) 12.1.0
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # https://github.com/intel-lab-lkp/linux/commit/fd3be916ffe18735a98bdc55ccc0cb5f3097582c
        git remote add linux-review https://github.com/intel-lab-lkp/linux
        git fetch --no-tags linux-review marius-cristea-microchip-com/dt-bindings-iio-adc-adding-dt-bindings-for-PAC193X/20230220-203540
        git checkout fd3be916ffe18735a98bdc55ccc0cb5f3097582c
        # save the config file
        mkdir build_dir && cp config build_dir/.config
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross W=1 O=build_dir ARCH=powerpc olddefconfig
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross W=1 O=build_dir ARCH=powerpc SHELL=/bin/bash

If you fix the issue, kindly add following tag where applicable
| Reported-by: kernel test robot <lkp@intel.com>
| Link: https://lore.kernel.org/oe-kbuild-all/202302210551.5yGSdcbc-lkp@intel.com/

All warnings (new ones prefixed by >>):

   drivers/iio/adc/pac193x.c: In function 'pac193x_acpi_get_acpi_match_entry':
   drivers/iio/adc/pac193x.c:1402:21: warning: variable 'status' set but not used [-Wunused-but-set-variable]
    1402 |         acpi_status status;
         |                     ^~~~~~
   drivers/iio/adc/pac193x.c: In function 'pac193x_match_acpi_device':
>> drivers/iio/adc/pac193x.c:1469:57: warning: '<<' in boolean context, did you mean '<'? [-Wint-in-bool-context]
    1469 |         chip_info->bi_dir[0] = (bi_dir_mask & (1 << 1)) << 1;
         |                                ~~~~~~~~~~~~~~~~~~~~~~~~~^~~~
   drivers/iio/adc/pac193x.c:1470:57: warning: '<<' in boolean context, did you mean '<'? [-Wint-in-bool-context]
    1470 |         chip_info->bi_dir[0] = (bi_dir_mask & (1 << 2)) << 2;
         |                                ~~~~~~~~~~~~~~~~~~~~~~~~~^~~~
   drivers/iio/adc/pac193x.c:1471:57: warning: '<<' in boolean context, did you mean '<'? [-Wint-in-bool-context]
    1471 |         chip_info->bi_dir[0] = (bi_dir_mask & (1 << 3)) << 3;
         |                                ~~~~~~~~~~~~~~~~~~~~~~~~~^~~~


vim +1469 drivers/iio/adc/pac193x.c

  1411	
  1412	static const char *pac193x_match_acpi_device(struct i2c_client *client,
  1413						     struct pac193x_chip_info *chip_info)
  1414	{
  1415		char *name;
  1416		acpi_handle handle;
  1417		union acpi_object *rez;
  1418		unsigned short bi_dir_mask;
  1419		int idx, i;
  1420	
  1421		handle = ACPI_HANDLE(&client->dev);
  1422		name = pac193x_acpi_get_acpi_match_entry(handle);
  1423		if (!name)
  1424			return NULL;
  1425	
  1426		rez = pac193x_acpi_eval_function(handle, 0, PAC193X_ACPI_GET_NAMES_AND_MOHMS_VALS);
  1427	
  1428		if (!rez)
  1429			return NULL;
  1430	
  1431		for (i = 0; i < rez->package.count; i += 2) {
  1432			idx = i / 2;
  1433			chip_info->channel_names[idx] =
  1434				devm_kmemdup(&client->dev, rez->package.elements[i].string.pointer,
  1435					     (size_t)rez->package.elements[i].string.length + 1,
  1436					     GFP_KERNEL);
  1437			chip_info->channel_names[idx][rez->package.elements[i].string.length] = '\0';
  1438			chip_info->shunts[idx] =
  1439				rez->package.elements[i + 1].integer.value * 1000;
  1440			chip_info->active_channels[idx] = (chip_info->shunts[idx] != 0);
  1441		}
  1442	
  1443		kfree(rez);
  1444	
  1445		rez = pac193x_acpi_eval_function(handle, 1, PAC193X_ACPI_GET_UOHMS_VALS);
  1446		if (!rez) {
  1447			/*
  1448			 * initialising with default values
  1449			 * we assume all channels are unidectional(the mask is zero)
  1450			 * and assign the default sampling rate
  1451			 */
  1452			chip_info->sample_rate_value = PAC193X_DEFAULT_CHIP_SAMP_SPEED;
  1453			return name;
  1454		}
  1455	
  1456		for (i = 0; i < rez->package.count; i++) {
  1457			idx = i;
  1458			chip_info->shunts[idx] = rez->package.elements[i].integer.value;
  1459			chip_info->active_channels[idx] = (chip_info->shunts[idx] != 0);
  1460		}
  1461	
  1462		kfree(rez);
  1463	
  1464		rez = pac193x_acpi_eval_function(handle, 1, PAC193X_ACPI_GET_BIPOLAR_SETTINGS);
  1465		if (!rez)
  1466			return NULL;
  1467		bi_dir_mask = rez->package.elements[0].integer.value;
  1468		chip_info->bi_dir[0] = (bi_dir_mask & (1 << 0)) << 0;
> 1469		chip_info->bi_dir[0] = (bi_dir_mask & (1 << 1)) << 1;
  1470		chip_info->bi_dir[0] = (bi_dir_mask & (1 << 2)) << 2;
  1471		chip_info->bi_dir[0] = (bi_dir_mask & (1 << 3)) << 3;
  1472		kfree(rez);
  1473	
  1474		rez = pac193x_acpi_eval_function(handle, 1, PAC193X_ACPI_GET_SAMP);
  1475		if (!rez)
  1476			return NULL;
  1477	
  1478		chip_info->sample_rate_value = rez->package.elements[0].integer.value;
  1479		kfree(rez);
  1480	
  1481		return name;
  1482	}
  1483
Krzysztof Kozlowski Feb. 21, 2023, 1:46 p.m. UTC | #3
On 20/02/2023 13:32, marius.cristea@microchip.com wrote:
> From: Marius Cristea <marius.cristea@microchip.com>
> 
> This is the iio driver for Microchip
> PAC193X series of Power Monitor with Accumulator chip family.
> 
> Signed-off-by: Marius Cristea <marius.cristea@microchip.com>
> ---
>  MAINTAINERS               |    7 +
>  drivers/iio/adc/Kconfig   |   12 +
>  drivers/iio/adc/Makefile  |    1 +
>  drivers/iio/adc/pac193x.c | 2072 +++++++++++++++++++++++++++++++++++++
>  4 files changed, 2092 insertions(+)
>  create mode 100644 drivers/iio/adc/pac193x.c
> 


Thank you for your patch. There is something to discuss/improve.

> +
> +#define PAC193X_NEG_PWR_CH1_BIDI(x)	((x) ? BIT(7) : 0)
> +#define PAC193X_NEG_PWR_CH2_BIDI(x)	((x) ? BIT(6) : 0)
> +#define PAC193X_NEG_PWR_CH3_BIDI(x)	((x) ? BIT(5) : 0)
> +#define PAC193X_NEG_PWR_CH4_BIDI(x)	((x) ? BIT(4) : 0)
> +#define PAC193X_NEG_PWR_CH1_BIDV(x)	((x) ? BIT(3) : 0)
> +#define PAC193X_NEG_PWR_CH2_BIDV(x)	((x) ? BIT(2) : 0)
> +#define PAC193X_NEG_PWR_CH3_BIDV(x)	((x) ? BIT(1) : 0)
> +#define PAC193X_NEG_PWR_CH4_BIDV(x)	((x) ? BIT(0) : 0)
> +
> +/*
> + * Universal Unique Identifier (UUID),
> + * 033771E0-1705-47B4-9535-D1BBE14D9A09, is
> + * reserved to Microchip for the PAC193X and must not be changed
> + */
> +#define PAC193X_DSM_UUID		"033771E0-1705-47B4-9535-D1BBE14D9A09"
> +
> +enum pac193x_ids {
> +	pac1934,
> +	pac1933,
> +	pac1932,
> +	pac1931

Enums are usually uppercase.



Best regards,
Krzysztof
Jonathan Cameron Feb. 25, 2023, 5:19 p.m. UTC | #4
On Tue, 21 Feb 2023 14:46:08 +0100
Krzysztof Kozlowski <krzk@kernel.org> wrote:

> On 20/02/2023 13:32, marius.cristea@microchip.com wrote:
> > From: Marius Cristea <marius.cristea@microchip.com>
> > 
> > This is the iio driver for Microchip
> > PAC193X series of Power Monitor with Accumulator chip family.
> > 
> > Signed-off-by: Marius Cristea <marius.cristea@microchip.com>
> > ---
> >  MAINTAINERS               |    7 +
> >  drivers/iio/adc/Kconfig   |   12 +
> >  drivers/iio/adc/Makefile  |    1 +
> >  drivers/iio/adc/pac193x.c | 2072 +++++++++++++++++++++++++++++++++++++
> >  4 files changed, 2092 insertions(+)
> >  create mode 100644 drivers/iio/adc/pac193x.c
> >   
> 
> 
> Thank you for your patch. There is something to discuss/improve.
> 
> > +
> > +#define PAC193X_NEG_PWR_CH1_BIDI(x)	((x) ? BIT(7) : 0)
> > +#define PAC193X_NEG_PWR_CH2_BIDI(x)	((x) ? BIT(6) : 0)
> > +#define PAC193X_NEG_PWR_CH3_BIDI(x)	((x) ? BIT(5) : 0)
> > +#define PAC193X_NEG_PWR_CH4_BIDI(x)	((x) ? BIT(4) : 0)
> > +#define PAC193X_NEG_PWR_CH1_BIDV(x)	((x) ? BIT(3) : 0)
> > +#define PAC193X_NEG_PWR_CH2_BIDV(x)	((x) ? BIT(2) : 0)
> > +#define PAC193X_NEG_PWR_CH3_BIDV(x)	((x) ? BIT(1) : 0)
> > +#define PAC193X_NEG_PWR_CH4_BIDV(x)	((x) ? BIT(0) : 0)
> > +
> > +/*
> > + * Universal Unique Identifier (UUID),
> > + * 033771E0-1705-47B4-9535-D1BBE14D9A09, is
> > + * reserved to Microchip for the PAC193X and must not be changed
> > + */
> > +#define PAC193X_DSM_UUID		"033771E0-1705-47B4-9535-D1BBE14D9A09"
> > +
> > +enum pac193x_ids {
> > +	pac1934,
> > +	pac1933,
> > +	pac1932,
> > +	pac1931  
> 
> Enums are usually uppercase.

I'm not sure there is anything in coding standard around that and a grep finds
a mixture of the two when it comes to ones used for IDs.  Mind you uppercase
is fine :)


> 
> 
> 
> Best regards,
> Krzysztof
>
Jonathan Cameron Feb. 25, 2023, 5:22 p.m. UTC | #5
On Sat, 25 Feb 2023 17:19:54 +0000
Jonathan Cameron <jic23@kernel.org> wrote:

> On Tue, 21 Feb 2023 14:46:08 +0100
> Krzysztof Kozlowski <krzk@kernel.org> wrote:
> 
> > On 20/02/2023 13:32, marius.cristea@microchip.com wrote:  
> > > From: Marius Cristea <marius.cristea@microchip.com>
> > > 
> > > This is the iio driver for Microchip
> > > PAC193X series of Power Monitor with Accumulator chip family.
> > > 
> > > Signed-off-by: Marius Cristea <marius.cristea@microchip.com>
> > > ---
> > >  MAINTAINERS               |    7 +
> > >  drivers/iio/adc/Kconfig   |   12 +
> > >  drivers/iio/adc/Makefile  |    1 +
> > >  drivers/iio/adc/pac193x.c | 2072 +++++++++++++++++++++++++++++++++++++
> > >  4 files changed, 2092 insertions(+)
> > >  create mode 100644 drivers/iio/adc/pac193x.c
> > >     
> > 
> > 
> > Thank you for your patch. There is something to discuss/improve.
> >   
> > > +
> > > +#define PAC193X_NEG_PWR_CH1_BIDI(x)	((x) ? BIT(7) : 0)
> > > +#define PAC193X_NEG_PWR_CH2_BIDI(x)	((x) ? BIT(6) : 0)
> > > +#define PAC193X_NEG_PWR_CH3_BIDI(x)	((x) ? BIT(5) : 0)
> > > +#define PAC193X_NEG_PWR_CH4_BIDI(x)	((x) ? BIT(4) : 0)
> > > +#define PAC193X_NEG_PWR_CH1_BIDV(x)	((x) ? BIT(3) : 0)
> > > +#define PAC193X_NEG_PWR_CH2_BIDV(x)	((x) ? BIT(2) : 0)
> > > +#define PAC193X_NEG_PWR_CH3_BIDV(x)	((x) ? BIT(1) : 0)
> > > +#define PAC193X_NEG_PWR_CH4_BIDV(x)	((x) ? BIT(0) : 0)
> > > +
> > > +/*
> > > + * Universal Unique Identifier (UUID),
> > > + * 033771E0-1705-47B4-9535-D1BBE14D9A09, is
> > > + * reserved to Microchip for the PAC193X and must not be changed
> > > + */
> > > +#define PAC193X_DSM_UUID		"033771E0-1705-47B4-9535-D1BBE14D9A09"
> > > +
> > > +enum pac193x_ids {
> > > +	pac1934,
> > > +	pac1933,
> > > +	pac1932,
> > > +	pac1931    
> > 
> > Enums are usually uppercase.  
> 
> I'm not sure there is anything in coding standard around that and a grep finds
> a mixture of the two when it comes to ones used for IDs.  Mind you uppercase
> is fine :)
I take it back. Is indeed in coding style doc.  Glad checkpatch doesn't check for
this though as we'd get 1000s of 'fixes' if it did and in most cases it doesn't
hurt readability.  I'll try and be more consistent on this in review going forwards!

Thanks!

Jonathan

> 
> 
> > 
> > 
> > 
> > Best regards,
> > Krzysztof
> >   
>
Jonathan Cameron Feb. 25, 2023, 7:27 p.m. UTC | #6
On Mon, 20 Feb 2023 14:32:32 +0200
<marius.cristea@microchip.com> wrote:

> From: Marius Cristea <marius.cristea@microchip.com>
> 
> This is the iio driver for Microchip
> PAC193X series of Power Monitor with Accumulator chip family.
> 
> Signed-off-by: Marius Cristea <marius.cristea@microchip.com>

Hi Marius,

As mentioned in DT binding review, wild cards in file names and driver names
have caused us mess far too many times in the past.  So don't do it, just pick
a representative part from the list - if no other basis to chose go with the
lowest number currently supported.

The custom ABI in here needs documentation before we can review it properly.

Comments inline.

It's a big driver, so I'm sure more stuff will come up next round just due to
mental exhaustion reviewing it in one go!

Thanks,

Jonathan
> ---
>  MAINTAINERS               |    7 +
>  drivers/iio/adc/Kconfig   |   12 +
>  drivers/iio/adc/Makefile  |    1 +
>  drivers/iio/adc/pac193x.c | 2072 +++++++++++++++++++++++++++++++++++++
>  4 files changed, 2092 insertions(+)
>  create mode 100644 drivers/iio/adc/pac193x.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index c81bbb771678..6675dc1b6b19 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -8187,6 +8187,13 @@ S:	Maintained
>  F:	Documentation/ABI/testing/sysfs-driver-intel-m10-bmc-sec-update
>  F:	drivers/fpga/intel-m10-bmc-sec-update.c
>  
> +MICROCHIP PAC193X POWER/ENERGY MONITOR DRIVER
> +M:	Marius Cristea <marius.cristea@@microchip.com>
> +L:	linux-iio@vger.kernel.org
> +S:	Supported
> +F:	Documentation/devicetree/bindings/iio/adc/microchip,pac193x.yaml
> +F:	drivers/iio/adc/pac193x.c
> +
>  MICROCHIP POLARFIRE FPGA DRIVERS
>  M:	Conor Dooley <conor.dooley@microchip.com>
>  R:	Ivan Bornyakov <i.bornyakov@metrotek.ru>
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 4a95c843a91b..ae4f60e290c4 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -867,6 +867,18 @@ config NPCM_ADC
>  	  This driver can also be built as a module. If so, the module
>  	  will be called npcm_adc.
>  
> +config PAC193X
> +	tristate "Microchip Technology PAC193X driver"
> +	depends on I2C
> +	depends on IIO
> +	help
> +	  Say yes here to build support for Microchip Technology's PAC1934,
> +	  PAC1933, PAC1932, PAC1931 Single/Multi-Channel Power Monitor with
> +	  Accumulator.

Order preferred as 1, 2, 3, 4 
Nothing really wrong with this way around other than it not being the common
choice so being slightly unexpected if someone is reading this fast.


> +
> +	  This driver can also be built as a module. If so, the module
> +	  will be called pac193x.
> +
>  config PALMAS_GPADC
>  	tristate "TI Palmas General Purpose ADC"
>  	depends on MFD_PALMAS
> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> index 36c18177322a..81824128c6cd 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -80,6 +80,7 @@ obj-$(CONFIG_MP2629_ADC) += mp2629_adc.o
>  obj-$(CONFIG_MXS_LRADC_ADC) += mxs-lradc-adc.o
>  obj-$(CONFIG_NAU7802) += nau7802.o
>  obj-$(CONFIG_NPCM_ADC) += npcm_adc.o
> +obj-$(CONFIG_PAC193X) += pac193x.o
>  obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
>  obj-$(CONFIG_QCOM_SPMI_ADC5) += qcom-spmi-adc5.o
>  obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o
> diff --git a/drivers/iio/adc/pac193x.c b/drivers/iio/adc/pac193x.c
> new file mode 100644
> index 000000000000..ece6bf028fe2
> --- /dev/null
> +++ b/drivers/iio/adc/pac193x.c
> @@ -0,0 +1,2072 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * IIO driver for PAC193X Multi-Channel DC Power/Energy Monitor
> + *
> + * Copyright (C) 2017-2023 Microchip Technology Inc. and its subsidiaries
> + *
> + * Author: Bogdan Bolocan <bogdan.bolocan@microchip.com>
> + * Author: Victor Tudose
> + * Author: Marius Cristea <marius.cristea@microchip.com>
> + *
> + * Datasheet for PAC1931, PAC1932, PAC1933 and PAC1934 can be found here:
> + * https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/PAC1931-Family-Data-Sheet-DS20005850E.pdf
> + *
> + */
> +
> +#include <linux/acpi.h>
> +#include <linux/bitfield.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/i2c.h>
> +#include <linux/iio/iio.h>
> +#include <linux/iio/sysfs.h>
> +#include <linux/init.h>
> +#include <linux/jiffies.h>
> +#include <linux/kernel.h>
> +#include <linux/kthread.h>
Check these includes to make sure they are all still relevant.
> +#include <linux/math.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/timer.h>
> +#include <linux/util_macros.h>
> +
> +/*
> + * maxim should be (17 * 60 * 1000) around 17 minutes@1024 sps

Either spell it out of max

> + * till PAC193X register stars to saturate
> + */
> +#define PAC193X_MAX_RFSH_LIMIT_MS		60000
> +
> +/* 50msec is the timeout for validity of the cached registers */
> +#define PAC193X_MIN_POLLING_TIME_MS		50
> +
> +#define SHUNT_UOHMS_DEFAULT			100000
> +/* 32000mV */
> +#define PAC193X_VOLTAGE_MILLIVOLTS_MAX		32000
> +/* voltage bits resolution when set for unsigned values */
> +#define PAC193X_VOLTAGE_U_RES			16
> +/* voltage bits resolution when set for signed values */
> +#define PAC193X_VOLTAGE_S_RES			15
> +/* 100mV maximum for current shunts */
> +#define PAC193X_VSENSE_MILLIVOLTS_MAX		100
> +/* voltage bits resolution when set for unsigned values */
> +#define PAC193X_CURRENT_U_RES			16
> +/* voltage bits resolution when set for signed values */
> +#define PAC193X_CURRENT_S_RES			15
> +
> +/* power resolution is 28 bits when unsigned */
> +#define PAC193X_POWER_U_RES			 28
> +/* power resolution is 27 bits when signed */
> +#define PAC193X_POWER_S_RES			27
> +
> +/* energy accumulation is 48 bits long */
> +#define PAC193X_ENERGY_U_RES			48
> +
> +#define PAC193X_ENERGY_S_RES			47
> +#define PAC193X_ENERGY_SHIFT_MAIN_VAL		32
> +
> +#define BIT_INDEX_31				31
> +
> +/*
> + * max signed value that can be stored on 32 bits and 8 digits fractional value
> + * (2^31 - 1) * 10^8 + 99999999
> + */
> +#define PAC_193X_MAX_POWER_ACC			214748364799999999LL
> +/*
> + * min signed value that can be stored on 32 bits and 8 digits fractional value
> + * -(2^31) * 10^8 - 99999999
> + */
> +#define PAC_193X_MIN_POWER_ACC			-214748364899999999LL
> +
> +/* maximum value that device can measure - 32 V * 0.1 V */
> +#define PAC193X_PRODUCT_VOLTAGE_PV_FSR		3200000000000UL
> +
> +#define PAC193X_MAX_NUM_CHANNELS		4
> +#define PAC1931_NUM_CHANNELS			1
> +#define PAC1932_NUM_CHANNELS			2
> +#define PAC1933_NUM_CHANNELS			3
> +#define PAC1934_NUM_CHANNELS			4
> +#define PAC193X_CH_1				0
> +#define PAC193X_CH_2				1
> +#define PAC193X_CH_3				2
> +#define PAC193X_CH_4				3
> +#define PAC193X_MEAS_REG_LEN			76
> +#define PAC193X_CTRL_REG_LEN			12
> +
> +/*
> + * 1000usec is the minimum wait time for normal conversions when sample
> + * rate doesn't change
> + */
> +#define PAC193X_MIN_UPDATE_WAIT_TIME_MS		1000
> +
> +#define PAC193X_DEFAULT_CHIP_SAMP_SPEED		1024
> +
> +/* I2C address map */
> +#define PAC193X_REFRESH_REG			0x00

Naming with ADDR and without is confusing. What's the difference?

> +#define PAC193X_CTRL_REG			0x01
> +#define PAC193X_REFRESH_V_REG			0x1F
> +#define PAC193X_ACC_COUNT_REG			0x02
> +#define PAC193X_CTRL_STAT_REGS_ADDR		0x1C
> +#define PAC193X_PID_REG_ADDR			0xFD
> +#define PAC193X_MID_REG_ADDR			0xFE
> +#define PAC193X_RID_REG_ADDR			0xFF
> +
> +/* PRODUCT ID REGISTER + MANUFACTURER ID REGISTER + REVISION ID REGISTER */
> +#define PAC193X_ID_REG_LEN			0x03
> +#define PAC193X_PID_IDX				0
> +#define PAC193X_MID_IDX				1
> +#define PAC193X_RID_IDX				2
> +
> +#define PAC193X_ACPI_ARG_COUNT			4
> +#define PAC193X_ACPI_GET_NAMES_AND_MOHMS_VALS	1
> +#define PAC193X_ACPI_GET_UOHMS_VALS		2
> +#define PAC193X_ACPI_GET_BIPOLAR_SETTINGS	4
> +#define PAC193X_ACPI_GET_SAMP			5
> +
> +#define PAC193X_VPOWER_ACC_1_ADDR		0x03
> +#define PAC193X_VPOWER_ACC_2_ADDR		0x04
> +#define PAC193X_VPOWER_ACC_3_ADDR		0x05
> +#define PAC193X_VPOWER_ACC_4_ADDR		0x06
> +#define PAC193X_VBUS_1_ADDR			0x07
> +#define PAC193X_VBUS_2_ADDR			0x08
> +#define PAC193X_VBUS_3_ADDR			0x09
> +#define PAC193X_VBUS_4_ADDR			0x0A
> +#define PAC193X_VSENSE_1_ADDR			0x0B
> +#define PAC193X_VSENSE_2_ADDR			0x0C
> +#define PAC193X_VSENSE_3_ADDR			0x0D
> +#define PAC193X_VSENSE_4_ADDR			0x0E
> +#define PAC193X_VBUS_AVG_1_ADDR			0x0F
> +#define PAC193X_VBUS_AVG_2_ADDR			0x10
> +#define PAC193X_VBUS_AVG_3_ADDR			0x11
> +#define PAC193X_VBUS_AVG_4_ADDR			0x12
> +#define PAC193X_VSENSE_AVG_1_ADDR		0x13
> +#define PAC193X_VSENSE_AVG_2_ADDR		0x14
> +#define PAC193X_VSENSE_AVG_3_ADDR		0x15
> +#define PAC193X_VSENSE_AVG_4_ADDR		0x16
> +#define PAC193X_VPOWER_1_ADDR			0x17
> +#define PAC193X_VPOWER_2_ADDR			0x18
> +#define PAC193X_VPOWER_3_ADDR			0x19
> +#define PAC193X_VPOWER_4_ADDR			0x1A
> +
> +#define PAC193X_SAMPLE_RATE_SHIFT		6
> +
> +/*
> + * these indexes are exactly describing the element order within a single
> + * PAC193X phys channel IIO channel descriptor; see the static const struct
> + * iio_chan_spec pac193x_single_channel[] declaration
> + */
> +#define IIO_EN					0
> +#define IIO_POW					1
> +#define IIO_VOLT				2
> +#define IIO_CRT					3
> +#define IIO_VOLTAVG				4
> +#define IIO_CRTAVG				5
> +
> +#define PAC193X_VBUS_SENSE_REG_LEN		2
> +#define PAC193X_ACC_REG_LEN			3
> +#define PAC193X_VPOWER_REG_LEN			4
> +#define PAC193X_VPOWER_ACC_REG_LEN		6
> +#define PAC193X_MAX_REGISTER_LENGTH		6
> +
> +#define PAC193X_CUSTOM_ATTR_FOR_CHANNEL		2
> +#define PAC193X_SHARED_DEVATTRS_COUNT		1
> +
> +/*
> + * relative offsets when using multi-byte reads/writes even though these
> + * bytes are read one after the other, they are not at adjacent memory
> + * locations within the I2C memory map. The chip can skip some addresses
> + */
> +#define PAC193X_CHANNEL_DIS_REG_OFF		0
> +#define PAC193X_NEG_PWR_REG_OFF			1
> +
> +/*
> + * when reading/writing multiple bytes from offset PAC193X_CHANNEL_DIS_REG_OFF,
> + * the chip jumps over the 0x1E (REFRESH_G) and 0x1F (REFRESH_V) offsets
> + */
> +#define PAC193X_SLOW_REG_OFF			2
> +#define PAC193X_CTRL_ACT_REG_OFF		3
> +#define PAC193X_CHANNEL_DIS_ACT_REG_OFF		4
> +#define PAC193X_NEG_PWR_ACT_REG_OFF		5
> +#define PAC193X_CTRL_LAT_REG_OFF		6
> +#define PAC193X_CHANNEL_DIS_LAT_REG_OFF		7
> +#define PAC193X_NEG_PWR_LAT_REG_OFF		8
> +#define PAC193X_PID_REG_OFF			9
> +#define PAC193X_MID_REG_OFF			10
> +#define PAC193X_REV_REG_OFF			11
> +#define PAC193X_CTRL_STATUS_INFO_LEN		12
> +
> +#define PAC193X_MID				0x5D
> +#define PAC1934_PID				0x5B
> +#define PAC1933_PID				0x5A
> +#define PAC1932_PID				0x59
> +#define PAC1931_PID				0x58
> +
> +/* Scale constant = (10^8 / 2^28) * 3.2 * 10^9 */
> +#define PAC193X_SCALE_CONSTANT			1192092895UL
> +
> +#define PAC193X_MAX_VPOWER_RSHIFTED_BY_28B	11921
> +#define PAC193X_MAX_VSENSE_RSHIFTED_BY_16B	1525
> +
> +#define PAC193X_DEV_ATTR(name) (&iio_dev_attr_##name.dev_attr.attr)
> +
> +#define PAC193X_CRTL_SAMPLE_RATE_MASK	GENMASK(7, 6)
> +#define PAC193X_CRTL_SAMPLE_RATE_SET(x)	((u8)FIELD_PREP(PAC193X_CRTL_SAMPLE_RATE_MASK, (x)))
> +#define PAC193X_CHAN_SLEEP_MASK		BIT(5)
> +#define PAC193X_CHAN_SLEEP_SET		BIT(5)
> +#define PAC193X_CHAN_SINLE_MASK		BIT(4)
> +#define PAC193X_CHAN_SINLE_SHOT_SET	BIT(4)
> +#define PAC193X_CHAN_ALERT_MASK		BIT(3)
> +#define PAC193X_CHAN_ALERT_EN		BIT(3)
> +#define PAC193X_CHAN_ALERT_CC_MASK	BIT(2)
> +#define PAC193X_CHAN_ALERT_CC_EN	BIT(2)
> +#define PAC193X_CHAN_OVF_ALERT_MASK	BIT(1)
> +#define PAC193X_CHAN_OVF_ALERT_EN	BIT(1)
> +#define PAC193X_CHAN_OVF_MASK		BIT(0)
> +
> +#define PAC193X_CHAN_DIS_CH1_OFF_MASK	BIT(7)
> +#define PAC193X_CHAN_DIS_CH1_OFF(x)	((u8)FIELD_PREP(PAC193X_CHAN_DIS_CH1_OFF_MASK, !(x)))
> +#define PAC193X_CHAN_DIS_CH2_OFF_MASK	BIT(6)
> +#define PAC193X_CHAN_DIS_CH2_OFF(x)	((u8)FIELD_PREP(PAC193X_CHAN_DIS_CH2_OFF_MASK, !(x)))
> +#define PAC193X_CHAN_DIS_CH3_OFF_MASK	BIT(5)
> +#define PAC193X_CHAN_DIS_CH3_OFF(x)	((u8)FIELD_PREP(PAC193X_CHAN_DIS_CH3_OFF_MASK, !(x)))
> +#define PAC193X_CHAN_DIS_CH4_OFF_MASK	BIT(4)
> +#define PAC193X_CHAN_DIS_CH4_OFF(x)	((u8)FIELD_PREP(PAC193X_CHAN_DIS_CH4_OFF_MASK, !(x)))
> +#define PAC193X_SMBUS_TIMEOUT_MASK	BIT(3)
> +#define PAC193X_SMBUS_TIMEOUT_EN(x)	FIELD_PREP(PAC193X_SMBUS_TIMEOUT_MASK, x)
> +#define PAC193X_SMBUS_BYTECOUNT_MASK	BIT(2)
> +#define PAC193X_SMBUS_BYTECOUNT_EN(x)	FIELD_PREP(PAC193X_SMBUS_BYTECOUNT_MASK, x)
> +#define PAC193X_SMBUS_NO_SKIP_MASK	BIT(1)
> +#define PAC193X_SMBUS_NO_SKIP_EN(x)	FIELD_PREP(PAC193X_SMBUS_NO_SKIP_MASK, x)
> +
> +#define PAC193X_NEG_PWR_CH1_BIDI(x)	((x) ? BIT(7) : 0)

To be more standard for kernel code, define a mask and use FIELD_PREP() for these
as well.

> +#define PAC193X_NEG_PWR_CH2_BIDI(x)	((x) ? BIT(6) : 0)
> +#define PAC193X_NEG_PWR_CH3_BIDI(x)	((x) ? BIT(5) : 0)
> +#define PAC193X_NEG_PWR_CH4_BIDI(x)	((x) ? BIT(4) : 0)
> +#define PAC193X_NEG_PWR_CH1_BIDV(x)	((x) ? BIT(3) : 0)
> +#define PAC193X_NEG_PWR_CH2_BIDV(x)	((x) ? BIT(2) : 0)
> +#define PAC193X_NEG_PWR_CH3_BIDV(x)	((x) ? BIT(1) : 0)
> +#define PAC193X_NEG_PWR_CH4_BIDV(x)	((x) ? BIT(0) : 0)
> +
> +/*
> + * Universal Unique Identifier (UUID),
> + * 033771E0-1705-47B4-9535-D1BBE14D9A09, is
> + * reserved to Microchip for the PAC193X and must not be changed
> + */
> +#define PAC193X_DSM_UUID		"033771E0-1705-47B4-9535-D1BBE14D9A09"
> +
> +enum pac193x_ids {
> +	pac1934,
> +	pac1933,
> +	pac1932,
> +	pac1931
> +};
> +
> +enum pac193x_samps {
> +	pac193X_samp_1024sps,
> +	pac193X_samp_256sps,
> +	pac193X_samp_64sps,
> +	pac193X_samp_8sps
> +};
> +
> +/**
> + * struct pac193x_features - features of a pac193x instance
> + * @phys_channels: number of physical channels supported by the chip
> + * @prod_id: product ID
> + */
> +struct pac193x_features {
> +	u8 phys_channels;
> +	u8 prod_id;
> +};
> +
> +struct samp_rate_mapping {
> +	u16 samp_rate;
> +	u8 shift2value;
> +};
> +
> +static const unsigned int samp_rate_map_tbl[] = {
> +	[pac193X_samp_1024sps] = 1024,
> +	[pac193X_samp_256sps] = 256,
> +	[pac193X_samp_64sps] = 64,
> +	[pac193X_samp_8sps] = 8,
> +};
> +
> +static const struct pac193x_features pac193x_chip_config[] = {
> +	[pac1934] = {
> +	    .phys_channels = PAC193X_MAX_NUM_CHANNELS,

Better as a number than a define.

> +	    .prod_id = PAC1934_PID,
> +	},
> +	[pac1933] = {
> +	    .phys_channels = PAC193X_MAX_NUM_CHANNELS - 1,
> +	    .prod_id = PAC1933_PID,
> +	},
> +	[pac1932] = {
> +	    .phys_channels = PAC193X_MAX_NUM_CHANNELS - 2,
> +	    .prod_id = PAC1932_PID,
> +	},
> +	[pac1931] = {
> +	    .phys_channels = PAC193X_MAX_NUM_CHANNELS - 3,
> +	    .prod_id = PAC1931_PID,
> +	},
> +};
> +
> +/**
> + * struct reg_data - data from the registers
> + * @meas_regs: snapshot of raw measurements registers
> + * @ctrl_regs: snapshot of control registers
> + * @energy_sec_acc: snapshot of energy values
> + * @vpower_acc: accumulated vpower values
> + * @vpower: snapshot of vpower registers
> + * @vbus: snapshot of vbus registers
> + * @vbus_avg: averages of vbus registers
> + * @vsense: snapshot of vsense registers
> + * @vsense_avg: averages of vsense registers
> + * @num_enabled_channels: count of how many chip channels are currently enabled
> + */
> +struct reg_data {
> +	u8	meas_regs[PAC193X_MEAS_REG_LEN];
> +	u8	ctrl_regs[PAC193X_CTRL_REG_LEN];
> +	s64	energy_sec_acc[PAC193X_MAX_NUM_CHANNELS];
> +	s64	vpower_acc[PAC193X_MAX_NUM_CHANNELS];
> +	s32	vpower[PAC193X_MAX_NUM_CHANNELS];
> +	s32	vbus[PAC193X_MAX_NUM_CHANNELS];
> +	s32	vbus_avg[PAC193X_MAX_NUM_CHANNELS];
> +	s32	vsense[PAC193X_MAX_NUM_CHANNELS];
> +	s32	vsense_avg[PAC193X_MAX_NUM_CHANNELS];
> +	u8	num_enabled_channels;
> +};
> +
> +/**
> + * struct pac193x_chip_info - information about the chip
> + * @client: the i2c-client attached to the device
> + * @lock: lock used by the chip's mutex
> + * @tmr_forced_update: timers used
> + * @wq_chip: queued work used for refresh commands
> + * @work_chip_rfsh: work used for refresh commands
> + * @phys_channels: phys channels count
> + * @active_channels: array of values, true means that channel is active
> + * @bi_dir: array of bools, true means that channel is bidirectional
> + * @chip_variant: chip variant
> + * @chip_revision: chip revision
> + * @shunts: shunts
> + * @chip_reg_data: chip reg data
> + * @sample_rate_value: sampling frequency
> + * @channel_names: channel names
> + * @pac193x_info: pac193x_info
> + * @jiffies_tstamp: chip's uptime
> + * @crt_samp_spd_bitfield:the current sampling speed

Order should match below.

> + */
> +struct pac193x_chip_info {
> +	struct i2c_client	*client;
> +	struct mutex		lock; /*lock to prevent concurent reads/writes */
> +	struct timer_list	tmr_forced_update;
> +	struct workqueue_struct *wq_chip;
> +	struct work_struct	work_chip_rfsh;
> +	u8			phys_channels;
> +	bool			active_channels[PAC193X_MAX_NUM_CHANNELS];
> +	bool			bi_dir[PAC193X_MAX_NUM_CHANNELS];
> +	u8			chip_variant;
> +	u8			chip_revision;
> +	u32			shunts[PAC193X_MAX_NUM_CHANNELS];
> +	struct reg_data		chip_reg_data;
> +	s32			sample_rate_value;
> +	char			*channel_names[PAC193X_MAX_NUM_CHANNELS];
> +	u8			crt_samp_spd_bitfield;
> +	unsigned long		jiffies_tstamp;
> +	struct iio_info		pac193x_info;
> +};
> +
> +struct __packed pac193x_uuid_format {

Not a standard UUID?  uuid_t

> +	u32	data1;
> +	u16	data2;
> +	u16	data3;
> +	u8	data4[8];
> +};
> +
> +#define to_pac193x_chip_info(d) container_of(d, struct pac193x_chip_info, work_chip_rfsh)
> +
> +/* macros to extract the parameters */
> +#define MVACCS(x)		sign_extend64((u64)(x), 47)
> +#define MVPOWERS(x)		sign_extend32((u32)(x), 27)
> +#define MVBUS_SENSES(x)		sign_extend32((u32)(x), 15)
Prefix defines with PAC193X to avoid future namespace clasehs.
I'm not 100% convinced these aren't better handled inline though rther than vai these
macros.

> +
> +#define PAC193X_VPOWER_ACC_CHANNEL(_index, _si, _address) {			\
> +	.type = IIO_ENERGY,							\
> +	.address = (_address),							\
> +	.indexed = 1,								\
> +	.channel = (_index),							\
> +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW)	|			\
> +				BIT(IIO_CHAN_INFO_SCALE),			\
> +	.info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ),		\
> +	.info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),	\
> +	.scan_index = (_si),							\
> +	.scan_type = {								\
> +		.sign = 'u',							\
> +		.realbits = PAC193X_ENERGY_U_RES,				\
> +		.storagebits = PAC193X_ENERGY_U_RES,				\
> +		.shift = 0,							\
> +		.endianness = IIO_CPU,						\
> +	}									\
> +}
> +
> +#define PAC193X_VBUS_CHANNEL(_index, _si, _address) {				\
> +	.type = IIO_VOLTAGE,							\
> +	.address = (_address),							\
> +	.indexed = 1,								\
> +	.channel = (_index),							\
> +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),				\
> +	.info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ),		\
> +	.info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),	\
> +	.scan_index = (_si),							\
> +	.scan_type = {								\
> +		.sign = 'u',							\
> +		.realbits = PAC193X_VOLTAGE_U_RES,				\
> +		.storagebits = PAC193X_VOLTAGE_U_RES,				\
> +		.shift = 0,							\
> +		.endianness = IIO_CPU,						\
> +	}									\
> +}
> +
> +#define PAC193X_VBUS_AVG_CHANNEL(_index, _si, _address) {			\
> +	.type = IIO_VOLTAGE,							\
> +	.address = (_address),							\
> +	.indexed = 1,								\
> +	.channel = (_index),							\
> +	.info_mask_separate = BIT(IIO_CHAN_INFO_AVERAGE_RAW)			\
> +				| BIT(IIO_CHAN_INFO_SCALE),			\
> +	.info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ),		\
> +	.info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),	\
> +	.scan_index = (_si),							\
> +	.scan_type = {								\
> +		.sign = 'u',							\
> +		.realbits = PAC193X_VOLTAGE_U_RES,				\
> +		.storagebits = PAC193X_VOLTAGE_U_RES,				\
> +		.shift = 0,							\
> +		.endianness = IIO_CPU,						\
> +	}									\
> +}
> +
> +#define PAC193X_VSENSE_CHANNEL(_index, _si, _address) {				\
> +	.type = IIO_CURRENT,							\
> +	.address = (_address),							\
> +	.indexed = 1,								\
> +	.channel = (_index),							\
> +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),				\
> +	.info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ),		\
> +	.info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),	\
> +	.scan_index = (_si),							\
> +	.scan_type = {								\
> +		.sign = 'u',							\
> +		.realbits = PAC193X_CURRENT_U_RES,				\
> +		.storagebits = PAC193X_CURRENT_U_RES,				\
> +		.shift = 0,							\
> +		.endianness = IIO_CPU,						\
> +	}									\
> +}
> +
> +#define PAC193X_VSENSE_AVG_CHANNEL(_index, _si, _address) {			\
> +	.type = IIO_CURRENT,							\
> +	.address = (_address),							\
> +	.indexed = 1,								\
> +	.channel = (_index),							\
> +	.info_mask_separate = BIT(IIO_CHAN_INFO_AVERAGE_RAW) |			\
> +			BIT(IIO_CHAN_INFO_SCALE),				\
> +	.info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ),		\
> +	.info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),	\
> +	.scan_index = (_si),							\
> +	.scan_type = {								\
> +		.sign = 'u',							\
> +		.realbits = PAC193X_CURRENT_U_RES,				\
> +		.storagebits = PAC193X_CURRENT_U_RES,				\
> +		.shift = 0,	
When shift is 0, it can be assumed as 'obvious' default and skipped. Rely on C to set it to 0 for us.						\
> +		.endianness = IIO_CPU,						\
> +	}									\
> +}
> +
> +#define PAC193X_VPOWER_CHANNEL(_index, _si, _address) {				\
> +	.type = IIO_POWER,							\
> +	.address = (_address),							\
> +	.indexed = 1,								\
> +	.channel = (_index),							\
> +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |				\
> +			BIT(IIO_CHAN_INFO_SCALE),				\
> +	.info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ),		\
> +	.info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),	\
> +	.scan_index = (_si),							\
> +	.scan_type = {								\
> +		.sign = 'u',							\
> +		.realbits = PAC193X_POWER_U_RES,				\
> +		.storagebits = 32,						\
> +		.shift = 4,							\
> +		.endianness = IIO_CPU,						\
> +	}									\
> +}
> +
> +#define PAC193X_SOFT_TIMESTAMP(_index) {					\
> +	.type = IIO_TIMESTAMP,							\
> +	.channel = -1,								\

What is this for?  

> +	.scan_index = (_index),							\
> +}
> +
> +static const struct iio_chan_spec pac193x_single_channel[] = {
> +	PAC193X_VPOWER_ACC_CHANNEL(0, 0, PAC193X_VPOWER_ACC_1_ADDR),
> +	PAC193X_VPOWER_CHANNEL(0, 0, PAC193X_VPOWER_1_ADDR),
> +	PAC193X_VBUS_CHANNEL(0, 0, PAC193X_VBUS_1_ADDR),
> +	PAC193X_VSENSE_CHANNEL(0, 0, PAC193X_VSENSE_1_ADDR),
> +	PAC193X_VBUS_AVG_CHANNEL(0, 0, PAC193X_VBUS_AVG_1_ADDR),
> +	PAC193X_VSENSE_AVG_CHANNEL(0, 0, PAC193X_VSENSE_AVG_1_ADDR),
> +};
> +
> +static const struct iio_chan_spec pac193x_ts[] = {
> +	PAC193X_SOFT_TIMESTAMP(0),
> +};
> +
> +static inline u64 pac193x_get_mvaccu(u8 *addr)
> +{
> +	return (((u64)(*(u8 *)((addr) + 0)) << 40) |
> +		((u64)(*(u8 *)((addr) + 1)) << 32) |
> +		((u64)(*(u8 *)((addr) + 2)) << 24) |
> +		((u64)(*(u8 *)((addr) + 3)) << 16) |
> +		((u64)(*(u8 *)((addr) + 4)) << 8)  |
> +		((u64)(*(u8 *)((addr) + 5)) << 0));

get_unaligned_be48()?

> +}
> +
> +static inline u32 pac193x_get_vpoweru(u8 *addr)
> +{
> +	return (((u32)(*(u8 *)((addr) + 0)) << 20) |
> +		((u32)(*(u8 *)((addr) + 1)) << 12) |
> +		((u32)(*(u8 *)((addr) + 2)) << 4) |
> +		((u32)(*(u8 *)((addr) + 3)) >> 4));
Hmm. Does this do same as

get_unaligned_be32(addr) >> 4 ?

> +}
> +
> +static inline u16 pac193x_get_mvbus_senseu(u8 *addr)
> +{
> +	u16 tmp;
> +
> +	tmp = (u16)(*(u16 *)(addr));
> +	be16_to_cpus(&tmp);

get_unaligned_be16?

> +
> +	return tmp;
> +}
> +
> +/* Low-level I2c functions */
> +static int pac193x_i2c_read(struct i2c_client *client, u8 reg_addr, void *databuf, u8 len)
> +{
> +	int ret;
> +	struct i2c_msg msgs[2] = {
> +		{ .addr = client->addr,
> +		 .len = 1,
> +		 .buf = (u8 *)&reg_addr,
> +		 .flags = 0
> +		},
> +		{ .addr = client->addr,
> +		 .len = len,
> +		 .buf = databuf,
> +		 .flags = I2C_M_RD
> +		}
> +	};
> +
> +	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
> +	if (ret < 0)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int pac193x_i2c_write_byte(struct i2c_client *client, u8 reg_addr, u8 val)
> +{
> +	int ret;
> +	u8 buf[2];
> +	struct i2c_msg msg = {
> +		.addr = client->addr,
> +		.len = sizeof(buf),
> +		.buf = (u8 *)&buf,
> +		.flags = 0
> +	};
> +
> +	buf[0] = reg_addr;
> +	buf[1] = val;
> +
> +	ret = i2c_transfer(client->adapter, &msg, 1);

i2c_smbus_write_byte_data()?  

> +	if (ret < 0) {
> +		dev_err(&client->dev,
> +			"failed writing register 0x%02X\n", reg_addr);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int pac193x_i2c_send_byte(struct i2c_client *client, u8 reg_addr)
> +{
> +	int ret;
> +	u8 buf;
> +	struct i2c_msg msg = {
> +		.addr = client->addr,
> +		.len = sizeof(buf),
> +		.buf = (u8 *)&buf,
> +		.flags = 0
> +	};
> +
> +	buf = reg_addr;
> +	ret = i2c_transfer(client->adapter, &msg, 1);

looks like it might match i2c_smbus_write()

> +	if (ret < 0)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int pac193x_i2c_write(struct i2c_client *client, u8 reg_addr, u8 *data, int len)
> +{
> +	int ret;
> +	u8 send[PAC193X_MAX_REGISTER_LENGTH + 1];
> +	struct i2c_msg msg = {
> +		.addr = client->addr,
> +		.len = len + 1,
> +		.flags = 0
> +	};
> +
> +	send[0] = reg_addr;
> +	memcpy(&send[1], data, len * sizeof(u8));
> +	msg.buf = send;
> +
> +	ret = i2c_transfer(client->adapter, &msg, 1);

i2c_master_send()

> +	if (ret < 0) {
> +		dev_err(&client->dev,
> +			"failed writing data from register 0x%02X\n", reg_addr);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int pac193x_match_samp_rate(struct pac193x_chip_info *chip_info, u32 new_samp_rate)
> +{
> +	int cnt;
> +
> +	for (cnt = 0; cnt < ARRAY_SIZE(samp_rate_map_tbl); cnt++) {
> +		if (new_samp_rate == samp_rate_map_tbl[cnt]) {
> +			chip_info->crt_samp_spd_bitfield = cnt;
> +			break;
			return 0;
> +		}
> +	}

return -EINVAL;

> +
> +	if (cnt == ARRAY_SIZE(samp_rate_map_tbl))
> +		/* not a valid sample rate value */
> +		return -EINVAL;
> +
> +	return 0;
> +}
> +
> +static ssize_t shunt_value_show(struct device *dev, struct device_attribute *attr, char *buf)
> +{
> +	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> +	struct pac193x_chip_info *chip_info = iio_priv(indio_dev);
> +	int len = 0;
> +	int target = (int)(attr->attr.name[strlen(attr->attr.name) - 1] - '0') - 1;
> +
> +	len += sprintf(buf, "%u\n", chip_info->shunts[target]);
> +
> +	return len;
> +}
> +
> +static ssize_t channel_name_show(struct device *dev,

Looks like per channel label support provided by the read_label iio_info callback.

> +				 struct device_attribute *attr,
> +				 char *buf)
> +{
> +	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> +	struct pac193x_chip_info *chip_info = iio_priv(indio_dev);
> +	int len = 0;
> +	int target = (int)(attr->attr.name[strlen(attr->attr.name) - 1] - '0') - 1;
> +
> +	len += sprintf(buf, "%s\n", chip_info->channel_names[target]);
> +
> +	return len;
> +}
> +
> +static ssize_t shunt_value_store(struct device *dev,

Shunt values aren't normally dynamic.  Why do you need to write it from
userspace? I'd expect that to come from firmware.

If it's needed the ext_info framework might be a better fit than
direct implementation of the attribute.

> +				 struct device_attribute *attr,
> +				 const char *buf, size_t count)
> +{
> +	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> +	struct pac193x_chip_info *chip_info = iio_priv(indio_dev);
> +	int sh_val, target;
> +
> +	target = (int)(attr->attr.name[strlen(attr->attr.name) - 1] - '0') - 1;
> +	if (kstrtouint(buf, 10, &sh_val)) {
> +		dev_err(dev, "Shunt value is not valid\n");
> +		return -EINVAL;
> +	}
> +
> +	mutex_lock(&chip_info->lock);
> +	chip_info->shunts[target] = sh_val;
> +	mutex_unlock(&chip_info->lock);
> +
> +	return count;
> +}
> +

...

> +static int pac193x_send_refresh(struct pac193x_chip_info *chip_info,
> +				bool refresh_v, u32 wait_time)
> +{
> +	/* this function only sends REFRESH or REFRESH_V */
> +	struct i2c_client *client = chip_info->client;
> +	int ret;
> +	u8 refresh_cmd, bidir_reg;
> +	bool revision_bug = false;
> +
> +	if (chip_info->chip_revision == 2 || chip_info->chip_revision == 3) {
> +		/*
> +		 *chip rev 2 and 3 bug workaround

space after *

> +		 * see: PAC193X Family Data Sheet Errata DS80000836A.pdf
> +		 */
> +		revision_bug = true;
> +
> +		bidir_reg = PAC193X_NEG_PWR_CH1_BIDI(chip_info->bi_dir[PAC193X_CH_1]) |
> +			PAC193X_NEG_PWR_CH2_BIDI(chip_info->bi_dir[PAC193X_CH_2]) |
> +			PAC193X_NEG_PWR_CH3_BIDI(chip_info->bi_dir[PAC193X_CH_3]) |
> +			PAC193X_NEG_PWR_CH4_BIDI(chip_info->bi_dir[PAC193X_CH_4]) |
> +			PAC193X_NEG_PWR_CH1_BIDV(chip_info->bi_dir[PAC193X_CH_1]) |
> +			PAC193X_NEG_PWR_CH2_BIDV(chip_info->bi_dir[PAC193X_CH_2]) |
> +			PAC193X_NEG_PWR_CH3_BIDV(chip_info->bi_dir[PAC193X_CH_3]) |
> +			PAC193X_NEG_PWR_CH4_BIDV(chip_info->bi_dir[PAC193X_CH_4]);
> +
> +		/* write the updated registers back */
> +		ret = pac193x_i2c_write_byte(client,
> +					     PAC193X_CTRL_STAT_REGS_ADDR +
> +					     PAC193X_NEG_PWR_REG_OFF,
> +					     bidir_reg);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	/*
> +	 * if refresh_v is not false, send REFRESH_V instead of REFRESH
> +	 * (doesn't reset the accumulators)
> +	 */
> +	if (refresh_v)
> +		refresh_cmd = PAC193X_REFRESH_V_REG;
> +	else
> +		refresh_cmd = PAC193X_REFRESH_REG;
> +
> +	ret = pac193x_i2c_send_byte(client, refresh_cmd);
> +	if (ret) {
> +		dev_err(&client->dev, "%s - cannot send 0x%02X\n",
> +			__func__, refresh_cmd);
> +		return ret;
> +	}
> +
> +	if (revision_bug) {
> +		/*
> +		 * chip rev 2 and 3 bug workaround - write again the same register
> +		 * write the updated registers back
> +		 */
> +		ret = pac193x_i2c_write_byte(client,
> +					     PAC193X_CTRL_STAT_REGS_ADDR +
> +					     PAC193X_NEG_PWR_REG_OFF, bidir_reg);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	/* register data retrieval timestamp */
> +	chip_info->jiffies_tstamp = jiffies;
> +
> +	/* wait till the data is available */
> +	usleep_range(wait_time, wait_time + 100);
> +
> +	return ret;
> +}
> +


...

> +
> +/**
> + * pac193x_read_raw() - data read function.
> + * @indio_dev: the struct iio_dev associated with this device instance
> + * @chan: the channel whose data is to be read
> + * @val: first element of returned value (typically INT)
> + * @val2: second element of returned value (typically MICRO)
> + * @mask: what we actually want to read as per the info_mask_* in iio_chan_spec.

As below. This doc doesn't add a huge amount as it's documenting IIO rather than
this function.

> + */
> +static int pac193x_read_raw(struct iio_dev *indio_dev,
> +			    struct iio_chan_spec const *chan, int *val,
> +			    int *val2, long mask)
> +{
> +	struct pac193x_chip_info *chip_info = iio_priv(indio_dev);
> +	s64 curr_energy, int_part;
> +	int rem, ret, channel = chan->channel - 1;
> +
> +	ret = pac193x_retrieve_data(chip_info, PAC193X_MIN_UPDATE_WAIT_TIME_MS);
> +	if (ret < 0)
> +		return ret;
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_RAW:
> +		switch (chan->type) {
> +		case IIO_VOLTAGE:
> +			switch (chan->address) {
> +			case PAC193X_VBUS_1_ADDR:
> +			case PAC193X_VBUS_2_ADDR:
> +			case PAC193X_VBUS_3_ADDR:
> +			case PAC193X_VBUS_4_ADDR:
> +				*val = chip_info->chip_reg_data.vbus[channel];
> +				return IIO_VAL_INT;
> +			default:
> +				return -EINVAL;
> +			}
> +			break;
> +		case IIO_CURRENT:
> +			switch (chan->address) {
> +			case PAC193X_VSENSE_1_ADDR:
> +			case PAC193X_VSENSE_2_ADDR:
> +			case PAC193X_VSENSE_3_ADDR:
> +			case PAC193X_VSENSE_4_ADDR:
> +				*val = chip_info->chip_reg_data.vsense[channel];
> +				return IIO_VAL_INT;
> +			default:
> +				return -EINVAL;
> +			}
> +			break;
> +		case IIO_POWER:
> +			switch (chan->address) {
> +			case PAC193X_VPOWER_1_ADDR:
> +			case PAC193X_VPOWER_2_ADDR:
> +			case PAC193X_VPOWER_3_ADDR:
> +			case PAC193X_VPOWER_4_ADDR:
> +				*val = chip_info->chip_reg_data.vpower[channel];
> +				return IIO_VAL_INT;
> +			default:
> +				return -EINVAL;
> +			}
> +			break;
> +		case IIO_ENERGY:
> +			switch (chan->address) {
> +			case PAC193X_VPOWER_ACC_1_ADDR:
> +			case PAC193X_VPOWER_ACC_2_ADDR:
> +			case PAC193X_VPOWER_ACC_3_ADDR:
> +			case PAC193X_VPOWER_ACC_4_ADDR:
> +				/*
> +				 * expresses the 64 bit energy value as a 32 bit integer part and
> +				 * 32 bits (representing 8 digits) fractional value

I'm lost.  So it's a 64 bit value?
We have IIO_VAL_INT_64. Does that not work here?

> +				 */
> +				curr_energy = chip_info->chip_reg_data.energy_sec_acc[channel];
> +				int_part = div_s64_rem(curr_energy, 100000000, &rem);
> +
> +				/* rescale reminder to be printed as "nano" value */
> +				rem = rem * 10;
> +
> +				*val = int_part;
> +				*val2 = rem;
> +				return IIO_VAL_INT_PLUS_NANO;
> +			default:
> +				return -EINVAL;
> +			}
> +			break;
> +		default:
> +			return -EINVAL;
> +		}
> +		break;
> +	case IIO_CHAN_INFO_AVERAGE_RAW:

I'd forgotten we had this ABI and had to look up the docs.   Hmm. It's a bit clunky but I guess
still useful in this case.  Longer term we should think about how to describe that and how it
is different from oversampling. The problem with oversampling is it affects the main channel
so we can't easily associate both an averaged value  and a raw one like you have here.

Given lots of other changes requested and early in this kernel cycle, perhaps it will make sense to revisit
this question at a later version of this patch (or maybe just leave it alone as too hard
to do better!)

> +		switch (chan->type) {
> +		case IIO_VOLTAGE:
> +			switch (chan->address) {
> +			case PAC193X_VBUS_AVG_1_ADDR:
> +			case PAC193X_VBUS_AVG_2_ADDR:
> +			case PAC193X_VBUS_AVG_3_ADDR:
> +			case PAC193X_VBUS_AVG_4_ADDR:
> +				*val = chip_info->chip_reg_data.vbus_avg[channel];
> +				return IIO_VAL_INT;
> +
> +			default:
> +				return -EINVAL;
> +			}
> +			break;
> +		case IIO_CURRENT:
> +			switch (chan->address) {
> +			case PAC193X_VSENSE_AVG_1_ADDR:
> +			case PAC193X_VSENSE_AVG_2_ADDR:
> +			case PAC193X_VSENSE_AVG_3_ADDR:
> +			case PAC193X_VSENSE_AVG_4_ADDR:
> +				*val = chip_info->chip_reg_data.vsense_avg[channel];
> +				return IIO_VAL_INT;
> +
> +			default:
> +				return -EINVAL;
> +			}
> +			break;
> +		default:
> +			return -EINVAL;
> +		}
> +		break;
> +	case IIO_CHAN_INFO_SCALE:
> +		switch (chan->address) {
> +		/* Voltages - scale for millivolts */
> +		case PAC193X_VBUS_1_ADDR:
> +		case PAC193X_VBUS_2_ADDR:
> +		case PAC193X_VBUS_3_ADDR:
> +		case PAC193X_VBUS_4_ADDR:
> +		case PAC193X_VBUS_AVG_1_ADDR:
> +		case PAC193X_VBUS_AVG_2_ADDR:
> +		case PAC193X_VBUS_AVG_3_ADDR:
> +		case PAC193X_VBUS_AVG_4_ADDR:
> +			*val = PAC193X_VOLTAGE_MILLIVOLTS_MAX;
> +			if (chan->scan_type.sign == 'u')
> +				*val2 = PAC193X_VOLTAGE_U_RES;
> +			else
> +				*val2 = PAC193X_VOLTAGE_S_RES;
> +			return IIO_VAL_FRACTIONAL_LOG2;
> +
> +		/*
> +		 * Currents - scale for mA - depends on the
> +		 * channel's shunt value
> +		 * (100mV * 1000000) / (2^16 * shunt(uohm))
> +		 */
> +		case PAC193X_VSENSE_1_ADDR:
> +		case PAC193X_VSENSE_2_ADDR:
> +		case PAC193X_VSENSE_3_ADDR:
> +		case PAC193X_VSENSE_4_ADDR:
> +		case PAC193X_VSENSE_AVG_1_ADDR:
> +		case PAC193X_VSENSE_AVG_2_ADDR:
> +		case PAC193X_VSENSE_AVG_3_ADDR:
> +		case PAC193X_VSENSE_AVG_4_ADDR:
> +			*val = PAC193X_MAX_VSENSE_RSHIFTED_BY_16B;
> +			if (chan->scan_type.sign == 'u')
> +				*val2 = chip_info->shunts[channel];
> +			else
> +				*val2 = chip_info->shunts[channel] >> 1;
> +			return IIO_VAL_FRACTIONAL;
> +
> +		/*
> +		 * Power - mW - it will use the combined scale
> +		 * for current and voltage
> +		 * current(mA) * voltage(mV) = power (uW)
> +		 */
> +		case PAC193X_VPOWER_1_ADDR:
> +		case PAC193X_VPOWER_2_ADDR:
> +		case PAC193X_VPOWER_3_ADDR:
> +		case PAC193X_VPOWER_4_ADDR:
> +			*val = PAC193X_MAX_VPOWER_RSHIFTED_BY_28B;
> +			if (chan->scan_type.sign == 'u')
> +				*val2 = chip_info->shunts[channel];
> +			else
> +				*val2 = chip_info->shunts[channel] >> 1;
> +			return IIO_VAL_FRACTIONAL;
> +		case PAC193X_VPOWER_ACC_1_ADDR:
> +		case PAC193X_VPOWER_ACC_2_ADDR:
> +		case PAC193X_VPOWER_ACC_3_ADDR:
> +		case PAC193X_VPOWER_ACC_4_ADDR:
> +
> +			/*
> +			 * expresses the 32 bit scale value
> +			 * here compute the scale for energy (Watt-second or Joule)
> +			 */
> +			*val = PAC193X_SCALE_CONSTANT;
> +
> +			if (chan->scan_type.sign == 'u')
> +				*val2 = chip_info->shunts[channel];
> +			else
> +				*val2 = chip_info->shunts[channel] >> 1;
> +			return IIO_VAL_FRACTIONAL;
> +		default:
> +			return -EINVAL;
> +		}
> +		break;
> +	case IIO_CHAN_INFO_SAMP_FREQ:
> +		*val = chip_info->sample_rate_value;
> +		return IIO_VAL_INT;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +/*
> + * pac193x_write_raw() - data write function.
> + * @indio_dev:	the struct iio_dev associated with this device instance
> + * @chan:	the channel whose data is to be written
> + * @val:	first element of value to set (typically INT)
> + * @val2:	second element of value to set (typically MICRO)
> + * @mask:	what we actually want to write as per the info_mask_* in iio_chan_spec.
> + *
> + * Note that all raw writes are assumed IIO_VAL_INT and info mask elements
> + * are assumed to be IIO_INT_PLUS_MICRO unless the callback write_raw_get_fmt
> + * in struct iio_info is provided by the driver.

These docs are more of IIO callbacks in general than this particular function.
Not sure it's helpful to have them here.

> + */
> +static int pac193x_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan,
> +			     int val, int val2, long mask)
> +{
> +	struct pac193x_chip_info *chip_info = iio_priv(indio_dev);
> +	struct i2c_client *client = chip_info->client;
> +	int ret = -EINVAL;
> +	s32 old_samp_rate;
> +	u8 ctrl_reg;
> +
> +	if (iio_buffer_enabled(indio_dev))
If you need to not do it whilst the buffer is enabled
then you want iio_device_claim_direct_mode() and the release to ensure
that you don't have a race against buffer being enabled.

However you don't support buffered mode so far. Hence what is this really here for?


> +		return -EBUSY;
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_SAMP_FREQ:
> +		ret = pac193x_match_samp_rate(chip_info, (u16)val);
> +		if (ret)
> +			return ret;
> +
> +		/* store the old sampling rate */

Given you have a good variable name the comment doesn't add anything.

> +		old_samp_rate = chip_info->sample_rate_value;
> +
> +		/* we have a valid sample rate */
> +		chip_info->sample_rate_value = val;
> +
> +		/*
> +		 * now lock the access to the chip, write the new
> +		 * sampling value and trigger a snapshot(incl refresh)
> +		 */
> +		mutex_lock(&chip_info->lock);
> +
> +		/* enable ALERT pin */
Where is this wired to?
> +		ctrl_reg = PAC193X_CRTL_SAMPLE_RATE_SET(chip_info->crt_samp_spd_bitfield) |
> +			PAC193X_CHAN_ALERT_EN;
> +		ret = pac193x_i2c_write_byte(client, PAC193X_CTRL_REG, ctrl_reg);
> +		if (ret) {
> +			dev_err(&client->dev, "%s - can't update sample rate\n", __func__);
> +			chip_info->sample_rate_value = old_samp_rate;
> +			mutex_unlock(&chip_info->lock);

Fun thing about i2c bus write failures. You have no idea what failed. The value might be
the old value or the new one after the failed write... So normally we don't bother too much if our values are
out of sync. and that leads to easier unwinding.  However, if you want to keep this as it
is then that's fine.

> +			return ret;
> +		}
> +
> +		/*
> +		 * unlock the access towards the chip - register
> +		 * snapshot includes its own access lock
> +		 */
> +		mutex_unlock(&chip_info->lock);
> +
> +		/*
> +		 * now, force a snapshot with refresh - call retrieve
> +		 * data in order to update the refresh timer
> +		 * alter the timestamp in order to force trigger a
> +		 * register snapshot and a timestamp update
> +		 */
> +		chip_info->jiffies_tstamp -=
> +			msecs_to_jiffies(PAC193X_MIN_POLLING_TIME_MS);
> +		ret = pac193x_retrieve_data(chip_info, (1024 / old_samp_rate) * 1000);
> +		if (ret < 0) {
> +			dev_err(&client->dev,
> +				"%s - cannot snapshot ctrl and measurement regs\n",
> +				__func__);
> +			return ret;
> +		}
> +
> +		ret = 0;
> +		break;
> +	default:
> +		break;
> +	}
> +
> +	return ret;
> +}
> +
> +static void pac193x_work_periodic_rfsh(struct work_struct *work)
> +{
> +	struct pac193x_chip_info *chip_info = to_pac193x_chip_info(work);
> +
> +	/* do a REFRESH, then read */
> +	pac193x_reg_snapshot(chip_info, true, false, PAC193X_MIN_UPDATE_WAIT_TIME_MS);
> +}
> +
> +static void pac193x_read_reg_timeout(struct timer_list *t)
> +{
> +	struct pac193x_chip_info *chip_info = from_timer(chip_info, t, tmr_forced_update);
> +
> +	mod_timer(&chip_info->tmr_forced_update,
> +		  jiffies + msecs_to_jiffies(PAC193X_MAX_RFSH_LIMIT_MS));
> +
> +	/* schedule the periodic reading from the chip */
> +	queue_work(chip_info->wq_chip, &chip_info->work_chip_rfsh);

Why not queue_delayed_work()?

> +}
> +
> +static int pac193x_read_revision(struct pac193x_chip_info *chip_info, u8 *buf)
> +{
> +	int ret;
> +	struct i2c_client *client = chip_info->client;
> +
> +	/*
> +	 * try to identify the chip variant

why try?  Is there a reason (other than hardware failure) that this might not work?

> +	 * read the chip ID values
> +	 */
> +	ret = pac193x_i2c_read(client, PAC193X_PID_REG_ADDR, buf, PAC193X_ID_REG_LEN);
> +	if (ret) {
> +		dev_err(&client->dev, "cannot read revision\n");
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static int pac193x_chip_identify(struct pac193x_chip_info *chip_info)
> +{
> +	int ret;
> +	struct i2c_client *client = chip_info->client;
> +	u8 rev_info[PAC193X_ID_REG_LEN];
> +
> +	ret = pac193x_read_revision(chip_info, (u8 *)rev_info);
> +
Trivial: Drop blank line here. Keeps the error block next to the call and clearly
associated with it.
> +	if (ret) {
> +		dev_err(&client->dev, "cannot read revision\n");
> +		return ret;
> +	}
> +
> +	if (rev_info[PAC193X_PID_IDX] != pac193x_chip_config[chip_info->chip_variant].prod_id) {
> +		dev_err(&client->dev,
> +			"chip's product ID doesn't match the exact one for this part\n");
> +		return -EINVAL;
> +	}
> +
> +	dev_dbg(&client->dev, "Chip revision: 0x%02X\n", rev_info[PAC193X_RID_IDX]);
> +	chip_info->chip_revision = rev_info[PAC193X_RID_IDX];
> +
> +	return 0;
> +}
> +
> +static int pac193x_get_variant(struct pac193x_chip_info *chip_info)
> +{
> +	u8 rev_info[PAC193X_ID_REG_LEN];
> +	int ret;
> +
> +	ret = pac193x_read_revision(chip_info, (u8 *)rev_info);
> +

As above, no blank line here preferred.

Though in this case
	if (ret)
		return 0; 
probably better, though that wants a comment on why a return of 0 is
not an error.

> +	if (!ret) {
> +		chip_info->chip_variant = rev_info[PAC193X_PID_IDX];
> +		chip_info->chip_revision = rev_info[PAC193X_RID_IDX];
> +		switch (chip_info->chip_variant) {
> +		case PAC1934_PID:
You have an array of structures containing pids and number of channels. Better to search
that for these than end up with this duplication.

> +			chip_info->phys_channels = PAC1934_NUM_CHANNELS;
> +			break;
> +		case PAC1933_PID:
> +			chip_info->phys_channels = PAC1933_NUM_CHANNELS;
> +			break;
> +		case PAC1932_PID:
> +			chip_info->phys_channels = PAC1932_NUM_CHANNELS;
> +			break;
> +		case PAC1931_PID:
> +			chip_info->phys_channels = PAC1931_NUM_CHANNELS;
> +			break;
> +		default:
> +			return -EINVAL;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static int pac193x_setup_periodic_refresh(struct pac193x_chip_info *chip_info)
> +{
> +	chip_info->wq_chip = create_workqueue("wq_pac193x");

Add add a comment on why this needs it's own work queue rather than using
a system one.

here I'd expect a devm_add_action_or_reset() to flush and free the queue.

> +	INIT_WORK(&chip_info->work_chip_rfsh, pac193x_work_periodic_rfsh);
> +
> +	/* setup the latest moment for reading the regs before saturation */
> +	timer_setup(&chip_info->tmr_forced_update, pac193x_read_reg_timeout, 0);

And another devm_callback here to deal with the timer callback.
> +
> +	/* register the timer */
> +	mod_timer(&chip_info->tmr_forced_update,
> +		  jiffies + msecs_to_jiffies(PAC193X_MAX_RFSH_LIMIT_MS));
> +
> +	return 0;
> +}
> +
> +static union acpi_object *pac193x_acpi_eval_function(acpi_handle handle,
> +						     int revision,
> +						     int function)
> +{
> +	acpi_status status;
> +	struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL};
> +	union acpi_object args[PAC193X_ACPI_ARG_COUNT];
> +	struct acpi_object_list args_list;
> +	uuid_t uuid;
> +
> +	uuid_parse(PAC193X_DSM_UUID, &uuid);
> +
> +	args[0].type = ACPI_TYPE_BUFFER;
> +	args[0].buffer.length = sizeof(struct pac193x_uuid_format);
> +	args[0].buffer.pointer = (u8 *)&uuid;
> +
> +	args[1].type = ACPI_TYPE_INTEGER;
> +	args[1].integer.value = revision;
> +
> +	args[2].type = ACPI_TYPE_INTEGER;
> +	args[2].integer.value = function;
> +
> +	args[3].type = ACPI_TYPE_PACKAGE;
> +	args[3].package.count = 0;
> +
> +	args_list.count = PAC193X_ACPI_ARG_COUNT;
> +	args_list.pointer = &args[0];
> +
> +	status = acpi_evaluate_object(handle, "_DSM", &args_list, &buffer);
> +
> +	if (ACPI_FAILURE(status)) {
> +		kfree(buffer.pointer);
> +		return NULL;
> +	}
> +
> +	return buffer.pointer;
> +}
> +
> +static char *pac193x_acpi_get_acpi_match_entry(acpi_handle handle)
> +{
> +	acpi_status status;
> +	union acpi_object *name_object;
> +	struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL};
> +
> +	status = acpi_evaluate_object(handle, "_HID", NULL, &buffer);
> +	name_object = buffer.pointer;
> +
> +	return name_object->string.pointer;
> +}
> +
> +static const char *pac193x_match_acpi_device(struct i2c_client *client,
> +					     struct pac193x_chip_info *chip_info)
> +{
> +	char *name;
> +	acpi_handle handle;
> +	union acpi_object *rez;
> +	unsigned short bi_dir_mask;
> +	int idx, i;
> +

Any docs for the ACPI stuff?
It looks plausible, but an example DSDT blob would make it easier to review.

> +	handle = ACPI_HANDLE(&client->dev);
> +	name = pac193x_acpi_get_acpi_match_entry(handle);

Having to do this again is a bit nasty.  The tricky corner is you might get
here either by match against the ACPI match table or via a PRP0001 path.

You could use acpi_match_device_ids(acpi_companion(&client->dev)... )
to distinguish which case you have without rolling your own _HID read routine.


> +	if (!name)
> +		return NULL;
> +
> +	rez = pac193x_acpi_eval_function(handle, 0, PAC193X_ACPI_GET_NAMES_AND_MOHMS_VALS);
> +
> +	if (!rez)
> +		return NULL;
> +
> +	for (i = 0; i < rez->package.count; i += 2) {
> +		idx = i / 2;
> +		chip_info->channel_names[idx] =
> +			devm_kmemdup(&client->dev, rez->package.elements[i].string.pointer,
> +				     (size_t)rez->package.elements[i].string.length + 1,
> +				     GFP_KERNEL);
> +		chip_info->channel_names[idx][rez->package.elements[i].string.length] = '\0';
> +		chip_info->shunts[idx] =
> +			rez->package.elements[i + 1].integer.value * 1000;
> +		chip_info->active_channels[idx] = (chip_info->shunts[idx] != 0);
> +	}
> +
> +	kfree(rez);
> +
> +	rez = pac193x_acpi_eval_function(handle, 1, PAC193X_ACPI_GET_UOHMS_VALS);
> +	if (!rez) {
> +		/*
> +		 * initialising with default values
> +		 * we assume all channels are unidectional(the mask is zero)
> +		 * and assign the default sampling rate
> +		 */
> +		chip_info->sample_rate_value = PAC193X_DEFAULT_CHIP_SAMP_SPEED;
> +		return name;
> +	}
> +
> +	for (i = 0; i < rez->package.count; i++) {
> +		idx = i;
> +		chip_info->shunts[idx] = rez->package.elements[i].integer.value;
> +		chip_info->active_channels[idx] = (chip_info->shunts[idx] != 0);
> +	}
> +
> +	kfree(rez);
> +
> +	rez = pac193x_acpi_eval_function(handle, 1, PAC193X_ACPI_GET_BIPOLAR_SETTINGS);
> +	if (!rez)
> +		return NULL;
> +	bi_dir_mask = rez->package.elements[0].integer.value;
> +	chip_info->bi_dir[0] = (bi_dir_mask & (1 << 0)) << 0;
> +	chip_info->bi_dir[0] = (bi_dir_mask & (1 << 1)) << 1;
> +	chip_info->bi_dir[0] = (bi_dir_mask & (1 << 2)) << 2;
> +	chip_info->bi_dir[0] = (bi_dir_mask & (1 << 3)) << 3;
> +	kfree(rez);
> +
> +	rez = pac193x_acpi_eval_function(handle, 1, PAC193X_ACPI_GET_SAMP);
> +	if (!rez)
> +		return NULL;
> +
> +	chip_info->sample_rate_value = rez->package.elements[0].integer.value;
> +	kfree(rez);
> +
> +	return name;
> +}
> +
> +static const struct of_device_id pac193x_of_match[] = {
> +	{ .compatible = "microchip,pac1934",
> +	  .data = (void *)&pac193x_chip_config[pac1934]

Shouldn't need that cast as any const pointer should cast implicitly
to a const void * which is what data is.

Also prefer
	{
		.compatible...
	},
	{

> +	},
> +	{ .compatible = "microchip,pac1933",
> +	  .data = (void *)&pac193x_chip_config[pac1933]
> +	},
> +	{ .compatible = "microchip,pac1932",
> +	  .data = (void *)&pac193x_chip_config[pac1932]
> +	},
> +	{ .compatible = "microchip,pac1931",
> +	  .data = (void *)&pac193x_chip_config[pac1931]
> +	},
> +	{}
> +};
> +MODULE_DEVICE_TABLE(of, pac193x_of_match);
After other suggested changes you should be able to move this down
next to the driver structure which is a  more conventional place to find these
tables.

> +
> +static int pac193x_match_of_device(struct i2c_client *client,
> +				   struct pac193x_chip_info *chip_info)
> +{
> +	struct device_node *node;
> +	unsigned int current_channel;
> +	int idx, ret;

Use generic property accessors linux/property.h for all these.

> +
> +	ret = of_property_read_u32(client->dev.of_node, "microchip,samp-rate",
> +				   &chip_info->sample_rate_value);
> +	if (ret) {
> +		dev_err(&client->dev,
> +			"Cannot read sample rate value ...%d\n", ret);
only called from probe, so dev_err_probe better.

> +		goto err_of_node_put;

put the node in the caller not in here as this function didn't get it.

> +	}
> +
> +	ret = pac193x_match_samp_rate(chip_info, chip_info->sample_rate_value);
> +	if (ret) {
> +		dev_err(&client->dev,
> +			"The given sample rate value is not supported: %d\n",
> +			chip_info->sample_rate_value);
> +		goto err_of_node_put;
> +	}
> +
> +	current_channel = 1;
> +	for_each_child_of_node(client->dev.of_node, node) {
> +		ret = of_property_read_u32(node, "reg", &idx);
> +		if (ret) {
> +			dev_err(&client->dev,
> +				"invalid channel_index, error: %d\n",
> +				ret);
> +			goto err_of_node_put;
> +		}
> +		/* adjust idx to match channel index (1 to 4) from the datasheet */
> +		idx--;
> +		if (current_channel >= (chip_info->phys_channels + 1) ||
> +		    idx >= chip_info->phys_channels || idx < 0) {
> +			dev_err(&client->dev,
> +				"invalid channel_index %d value on %s\n",
> +				(idx + 1), node->full_name);
> +			goto err_of_node_put;
> +		}
> +
> +		/* enable channel */
> +		chip_info->active_channels[idx] = true;
> +
> +		ret = of_property_read_u32(node, "microchip,uohms-shunt-res",
> +					   &chip_info->shunts[idx]);
> +		if (ret) {
> +			dev_err(&client->dev,
> +				"%s: invalid shunt-resistor value: %d\n",
> +				node->full_name, ret);
> +			goto err_of_node_put;
> +		}
> +
> +		ret = of_property_read_string(node, "microchip,rail-name",
> +					      (const char **)&chip_info->channel_names[idx]);
> +		if (ret) {
> +			dev_err(&client->dev,
> +				"%s: invalid rail-name value: %d\n",
> +				node->full_name, ret);
> +			goto err_of_node_put;
> +		}
> +
> +		chip_info->bi_dir[idx] =
> +			of_property_read_bool(node, "microchip,bi-directional");
> +
> +		current_channel++;
> +	}
> +
> +	return 0;
> +
> +err_of_node_put:
> +	of_node_put(node);
> +	return ret;
> +}
> +
> +static int pac193x_chip_configure(struct pac193x_chip_info *chip_info)
> +{
> +	int cnt, ret;
> +	struct i2c_client *client = chip_info->client;
> +	u8 regs[PAC193X_CTRL_STATUS_INFO_LEN], idx, ctrl_reg;
> +	u32 wait_time;
> +
> +	/*
> +	 * count how many channels are enabled and store
> +	 * this information within the driver data
> +	 */
> +	cnt = 0;
> +	chip_info->chip_reg_data.num_enabled_channels = 0;
> +	while (cnt < chip_info->phys_channels) {
> +		if (chip_info->active_channels[cnt])
> +			chip_info->chip_reg_data.num_enabled_channels++;
> +		cnt++;
> +	}
> +
> +	/*
> +	 * read whatever information was gathered before the driver was loaded
> +	 * establish which channels are enabled/disabled and then establish the
> +	 * information retrieval mode (using SKIP or no).

It's unusual to have a driver carry on with existing values.  Much more common
to either have it reset the device (to known default state) or write all the
registers if such a reset doesn't exist.

One reason for that flow is that we really don't trust other software :)
Another is that often we don't know if the device had any power until we turned it
on.  So it's much easier to review code where we control all the state.

If there is a strong reason for carrying on with existing settings please add
comments here and something to the patch description to point out this unusual
behaviour. 

> +	 * Read the chip ID values
> +	 */
> +	ret = pac193x_i2c_read(client, PAC193X_CTRL_STAT_REGS_ADDR,
> +			       (u8 *)regs, PAC193X_CTRL_STATUS_INFO_LEN);

sizeof(regs)
so a reviewer doesn't need to go check there is space.

> +	if (ret) {
> +		dev_err(&client->dev,
> +			"%s - cannot read regs from 0x%02X\n",
> +			__func__, PAC193X_CTRL_STAT_REGS_ADDR);

As this is only called from probe, you can use dev_err_probe() for simpler code
in here.

> +		return ret;
> +	}
> +
> +	/* write the CHANNEL_DIS and the NEG_PWR registers */
> +	regs[PAC193X_CHANNEL_DIS_REG_OFF] =
> +		PAC193X_CHAN_DIS_CH1_OFF(chip_info->active_channels[PAC193X_CH_1]) |
> +		PAC193X_CHAN_DIS_CH2_OFF(chip_info->active_channels[PAC193X_CH_2]) |
> +		PAC193X_CHAN_DIS_CH3_OFF(chip_info->active_channels[PAC193X_CH_3]) |
> +		PAC193X_CHAN_DIS_CH4_OFF(chip_info->active_channels[PAC193X_CH_4]) |
> +		PAC193X_SMBUS_TIMEOUT_EN(0) |
> +		PAC193X_SMBUS_BYTECOUNT_EN(0) |
> +		PAC193X_SMBUS_NO_SKIP_EN(0);
> +
> +	regs[PAC193X_NEG_PWR_REG_OFF] =
> +		PAC193X_NEG_PWR_CH1_BIDI(chip_info->bi_dir[PAC193X_CH_1]) |
> +		PAC193X_NEG_PWR_CH2_BIDI(chip_info->bi_dir[PAC193X_CH_2]) |
> +		PAC193X_NEG_PWR_CH3_BIDI(chip_info->bi_dir[PAC193X_CH_3]) |
> +		PAC193X_NEG_PWR_CH4_BIDI(chip_info->bi_dir[PAC193X_CH_4]) |
> +		PAC193X_NEG_PWR_CH1_BIDV(chip_info->bi_dir[PAC193X_CH_1]) |
> +		PAC193X_NEG_PWR_CH2_BIDV(chip_info->bi_dir[PAC193X_CH_2]) |
> +		PAC193X_NEG_PWR_CH3_BIDV(chip_info->bi_dir[PAC193X_CH_3]) |
> +		PAC193X_NEG_PWR_CH4_BIDV(chip_info->bi_dir[PAC193X_CH_4]);
> +
> +	/*
> +	 * the current can be measured uni or bi-dir, but voltages are set only
> +	 * for uni-directional operation
> +	 * no SLOW triggered REFRESH, clear POR
> +	 */
> +	regs[PAC193X_SLOW_REG_OFF] = 0;
> +
> +	/* write the updated registers back */
> +	ret = pac193x_i2c_write(client, PAC193X_CTRL_STAT_REGS_ADDR, (u8 *)regs, 3);
> +	if (ret)
> +		return ret;
> +
> +	/* enable the ALERT pin functionality */

Where is the alert pin wired to?  I'd expect to see an interrupt and for this
to be optional as people have a habit of not writing interrupt lines up (sigh)

> +	ctrl_reg = PAC193X_CRTL_SAMPLE_RATE_SET(chip_info->crt_samp_spd_bitfield) |
> +		PAC193X_CHAN_ALERT_EN;
> +	ret = pac193x_i2c_write_byte(client, PAC193X_CTRL_REG, ctrl_reg);
> +	if (ret)
> +		return ret;
> +
> +	/*
> +	 * send a REFRESH to the chip, so the new settings take place
> +	 * as well as resetting the accumulators
> +	 */
> +	ret = pac193x_i2c_send_byte(client, PAC193X_REFRESH_REG);

Naming of write_byte vs send_byte is a bit confusing.
Perhaps call one write_reg()?

> +	if (ret) {
> +		dev_err(&client->dev,
> +			"%s - cannot send 0x%02X\n",
> +			__func__, PAC193X_REFRESH_REG);
> +		return ret;
> +	}
> +
> +	/*
> +	 * get the current(in the chip) sampling speed and compute the
> +	 * required timeout based on its value
> +	 * the timeout is 1/sampling_speed
> +	 */
> +	idx = regs[PAC193X_CTRL_ACT_REG_OFF] >> PAC193X_SAMPLE_RATE_SHIFT;
> +	wait_time = (1024 / samp_rate_map_tbl[idx]) * 1000;
> +
> +	/*
> +	 * wait the maximum amount of time to be on the safe side - the
> +	 * maximum wait time is for 8sps
> +	 */
> +	usleep_range(wait_time, wait_time + 100);
> +
> +	/* setup the refresh timeout */
As below, some of these comments are obvious, so drop them. Others
are much more useful of course and want to be kept.
> +	ret = pac193x_setup_periodic_refresh(chip_info);

return pac193x...

> +
> +	return ret;
> +}
> +
> +static int pac193x_prep_iio_channels(struct pac193x_chip_info *chip_info, struct iio_dev *indio_dev)
> +{
> +	struct i2c_client *client;
> +	struct iio_chan_spec *ch_sp;
> +	int channel_size, channel_attribute_count, attribute_count, cnt;
> +	void *dyn_ch_struct, *tmp_data;
> +
> +	client = chip_info->client;
> +
> +	/* find out dynamically how many IIO channels we need */
> +	channel_attribute_count = 0;
> +	channel_size = 0;
> +	for (cnt = 0; cnt < chip_info->phys_channels; cnt++) {
> +		if (chip_info->active_channels[cnt]) {
> +			/* add the size of the properties of one chip physical channel */
> +			channel_size += sizeof(pac193x_single_channel);
> +			/* count how many enabled channels we have */
> +			channel_attribute_count += ARRAY_SIZE(pac193x_single_channel);
> +			dev_info(&client->dev, ":%s: Channel %d active\n", __func__, cnt + 1);
> +		}
> +	}
> +
> +	/* now add the timestamp channel size */
> +	channel_size += sizeof(pac193x_ts);
> +
> +	/* add one more channel which is the timestamp */
> +	attribute_count = channel_attribute_count + 1;
> +
> +	dyn_ch_struct = kzalloc(channel_size, GFP_KERNEL);
> +	if (!dyn_ch_struct)
> +		return -EINVAL;
> +
> +	tmp_data = dyn_ch_struct;
> +
> +	/* populate the dynamic channels and make all the adjustments */
> +	for (cnt = 0; cnt < chip_info->phys_channels; cnt++) {
> +		if (chip_info->active_channels[cnt]) {
> +			memcpy(tmp_data, pac193x_single_channel, sizeof(pac193x_single_channel));
> +			ch_sp = (struct iio_chan_spec *)tmp_data;
> +			ch_sp[IIO_EN].channel = cnt  + 1;
> +			ch_sp[IIO_EN].scan_index = cnt;
> +			ch_sp[IIO_EN].address = cnt + PAC193X_VPOWER_ACC_1_ADDR;
> +			ch_sp[IIO_POW].channel = cnt  + 1;
> +			ch_sp[IIO_POW].scan_index = cnt;
> +			ch_sp[IIO_POW].address = cnt + PAC193X_VPOWER_1_ADDR;
> +			ch_sp[IIO_VOLT].channel = cnt  + 1;
> +			ch_sp[IIO_VOLT].scan_index = cnt;
> +			ch_sp[IIO_VOLT].address = cnt + PAC193X_VBUS_1_ADDR;
> +			ch_sp[IIO_CRT].channel = cnt  + 1;
> +			ch_sp[IIO_CRT].scan_index = cnt;
> +			ch_sp[IIO_CRT].address = cnt + PAC193X_VSENSE_1_ADDR;
> +			ch_sp[IIO_VOLTAVG].channel = cnt  + 1;
> +			ch_sp[IIO_VOLTAVG].scan_index = cnt;
> +			ch_sp[IIO_VOLTAVG].address = cnt + PAC193X_VBUS_AVG_1_ADDR;
> +			ch_sp[IIO_CRTAVG].channel = cnt  + 1;
> +			ch_sp[IIO_CRTAVG].scan_index = cnt;
> +			ch_sp[IIO_CRTAVG].address = cnt + PAC193X_VSENSE_AVG_1_ADDR;
> +
> +			/*
> +			 * now modify the parameters in all channels if the
> +			 * whole chip rail(channel) is bi-directional
> +			 */
> +			if (chip_info->bi_dir[cnt]) {
> +				ch_sp[IIO_EN].scan_type.sign = 's';
> +				ch_sp[IIO_EN].scan_type.realbits = PAC193X_ENERGY_S_RES;
> +				ch_sp[IIO_POW].scan_type.sign = 's';
> +				ch_sp[IIO_POW].scan_type.realbits = PAC193X_POWER_S_RES;
> +				ch_sp[IIO_VOLT].scan_type.sign = 's';
> +				ch_sp[IIO_VOLT].scan_type.realbits = PAC193X_VOLTAGE_S_RES;
> +				ch_sp[IIO_CRT].scan_type.sign = 's';
> +				ch_sp[IIO_CRT].scan_type.realbits = PAC193X_CURRENT_S_RES;
> +				ch_sp[IIO_VOLTAVG].scan_type.sign = 's';
> +				ch_sp[IIO_VOLTAVG].scan_type.realbits = PAC193X_VOLTAGE_S_RES;
> +				ch_sp[IIO_CRTAVG].scan_type.sign = 's';
> +				ch_sp[IIO_CRTAVG].scan_type.realbits = PAC193X_CURRENT_S_RES;
> +			}
> +			tmp_data += sizeof(pac193x_single_channel);
> +		}
> +	}
> +
> +	/* now copy the timestamp channel */
> +	memcpy(tmp_data, pac193x_ts, sizeof(pac193x_ts));
> +	ch_sp = (struct iio_chan_spec *)tmp_data;
> +	ch_sp[0].scan_index = attribute_count - 1;
> +
> +	/*
> +	 * send the updated dynamic channel structure information towards IIO
> +	 * prepare the required field for IIO class registration
> +	 */
> +	indio_dev->num_channels = attribute_count;
> +	indio_dev->channels = devm_kmemdup(&client->dev,
> +					   (const struct iio_chan_spec *)dyn_ch_struct,
> +					   channel_size, GFP_KERNEL);
> +
> +	kfree(dyn_ch_struct);
> +
> +	if (!indio_dev->channels)
> +		return -EINVAL;
> +
> +	return 0;
> +}
> +
> +static ssize_t reset_accumulators_store(struct device *dev,
> +					struct device_attribute *attr,
> +					const char *buf, size_t count)
> +{
> +	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> +	struct pac193x_chip_info *chip_info = iio_priv(indio_dev);
> +	int ret, i;
> +	u8 refresh_cmd = PAC193X_REFRESH_REG;
> +
> +	ret = pac193x_i2c_send_byte(chip_info->client, refresh_cmd);
> +	if (ret) {
> +		dev_err(&indio_dev->dev,
> +			"%s - cannot send 0x%02X\n",
> +			__func__, refresh_cmd);
> +	}
> +
> +	for (i = 0 ; i < chip_info->phys_channels; i++)
> +		chip_info->chip_reg_data.energy_sec_acc[i] = 0;
> +
> +	return count;
> +}
> +
> +static IIO_DEVICE_ATTR(shunt_value_1, 0644, shunt_value_show, shunt_value_store, 0);
> +static IIO_DEVICE_ATTR(shunt_value_2, 0644, shunt_value_show, shunt_value_store, 0);
> +static IIO_DEVICE_ATTR(shunt_value_3, 0644, shunt_value_show, shunt_value_store, 0);
> +static IIO_DEVICE_ATTR(shunt_value_4, 0644, shunt_value_show, shunt_value_store, 0);
> +
> +static IIO_DEVICE_ATTR(channel_name_1, 0444, channel_name_show, NULL, 0);
> +static IIO_DEVICE_ATTR(channel_name_2, 0444, channel_name_show, NULL, 0);
> +static IIO_DEVICE_ATTR(channel_name_3, 0444, channel_name_show, NULL, 0);
> +static IIO_DEVICE_ATTR(channel_name_4, 0444, channel_name_show, NULL, 0);
> +
> +static IIO_DEVICE_ATTR(reset_accumulators, 0200, NULL, reset_accumulators_store, 0);
> +
> +static struct attribute *pac193x_all_attributes[] = {
> +	PAC193X_DEV_ATTR(shunt_value_1),

These all need ABI documentation so that we can easily review what they do.
Documenation/ABI/testing/sysfs-bus-iio-pac1931
Note that if they overlap with ABI used elsewhere we may need to move it to a more
generic place (there can't be two lots of docs for the same ABI element)

> +	PAC193X_DEV_ATTR(channel_name_1),
> +	PAC193X_DEV_ATTR(shunt_value_2),
> +	PAC193X_DEV_ATTR(channel_name_2),
> +	PAC193X_DEV_ATTR(shunt_value_3),
> +	PAC193X_DEV_ATTR(channel_name_3),
> +	PAC193X_DEV_ATTR(shunt_value_4),
> +	PAC193X_DEV_ATTR(channel_name_4),
> +	PAC193X_DEV_ATTR(reset_accumulators),
> +	NULL
> +};
> +
> +static int pac193x_prep_custom_attributes(struct pac193x_chip_info *chip_info,
> +					  struct iio_dev *indio_dev)
> +{
> +	int i, j, active_channels_count = 0;
> +	struct attribute **pac193x_custom_attributes;
> +	struct attribute_group *pac193x_group;
> +	struct i2c_client *client = chip_info->client;
> +
> +	for (i = 0 ; i < chip_info->phys_channels; i++)
> +		if (chip_info->active_channels[i])
> +			active_channels_count++;
> +
> +	pac193x_group = devm_kzalloc(&client->dev, sizeof(*pac193x_group), GFP_KERNEL);
> +
> +	pac193x_custom_attributes = devm_kzalloc(&client->dev,
> +						 (PAC193X_CUSTOM_ATTR_FOR_CHANNEL *
> +						 active_channels_count +
> +						 PAC193X_SHARED_DEVATTRS_COUNT)
> +						 * sizeof(*pac193x_group) + 1,
> +						 GFP_KERNEL);
> +	j = 0;
> +
> +	for (i = 0 ; i < chip_info->phys_channels; i++) {
> +		if (chip_info->active_channels[i]) {
> +			pac193x_custom_attributes[PAC193X_CUSTOM_ATTR_FOR_CHANNEL * j] =
> +			pac193x_all_attributes[PAC193X_CUSTOM_ATTR_FOR_CHANNEL * i];
> +			pac193x_custom_attributes[PAC193X_CUSTOM_ATTR_FOR_CHANNEL * j + 1] =
> +			pac193x_all_attributes[PAC193X_CUSTOM_ATTR_FOR_CHANNEL * i + 1];
> +			j++;
> +		}
> +	}
> +
> +	for (i = 0; i < PAC193X_SHARED_DEVATTRS_COUNT; i++)
> +		pac193x_custom_attributes[PAC193X_CUSTOM_ATTR_FOR_CHANNEL *
> +			active_channels_count + i] =
> +			pac193x_all_attributes[PAC193X_CUSTOM_ATTR_FOR_CHANNEL *
> +			chip_info->phys_channels + i];
> +
> +	pac193x_group->attrs = pac193x_custom_attributes;
> +	chip_info->pac193x_info.attrs = pac193x_group;
> +
> +	return 0;
> +}
> +
> +static void pac193x_remove(struct i2c_client *client)
> +{
> +	int ret;
> +	struct iio_dev *indio_dev = dev_get_drvdata(&client->dev);
> +	struct pac193x_chip_info *chip_info = iio_priv(indio_dev);
> +
> +	ret = try_to_del_timer_sync(&chip_info->tmr_forced_update);
> +	if (ret < 0)
> +		dev_err(&client->dev,
> +			"%s - cannot delete the forced readout timer\n",
> +			__func__);
> +
> +	if (chip_info->wq_chip) {
> +		cancel_work_sync(&chip_info->work_chip_rfsh);
> +		flush_workqueue(chip_info->wq_chip);
> +		destroy_workqueue(chip_info->wq_chip);
> +	}
> +}
> +
> +static int pac193x_probe(struct i2c_client *client, const struct i2c_device_id *id)
> +{
> +	struct pac193x_chip_info *chip_info;
> +	struct iio_dev *indio_dev;
> +	const char *name = NULL;
> +	const struct of_device_id *match;
> +	int cnt, ret, dev_id = 0;
> +
> +	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip_info));
> +	if (!indio_dev) {
> +		dev_err_probe(&indio_dev->dev, PTR_ERR(indio_dev),
> +			      "Can't allocate iio device\n");
> +		return -ENOMEM;
> +	}
> +
> +	chip_info = iio_priv(indio_dev);
> +
> +	i2c_set_clientdata(client, indio_dev);

Assuming you follow suggestion to go fully devm managed handling of remove, I don't htink
you will need this.

> +	chip_info->client = client;
> +
> +	memset(&chip_info->chip_reg_data, 0, sizeof(chip_info->chip_reg_data));

The iio_priv space is allocated with kzalloc so no need to zero here.

> +
> +	if (ACPI_HANDLE(&client->dev)) {
> +		pac193x_get_variant(chip_info);

Why is this ACPI specific? 

If you can query the variant from the hardware, that is the right thing to use
for all registration types.  It can be helpful to call out if there is
a mismatch though with a warning print, so comparing the result of
the i2c call with the data is fine.

> +	} else {
> +		dev_id = id->driver_data;
> +
> +		/* store the type of chip */
> +		chip_info->chip_variant = dev_id;
> +
> +		/* get the maximum number of channels for the given chip id */
> +		chip_info->phys_channels = pac193x_chip_config[dev_id].phys_channels;
> +	}
> +
> +	/*
> +	 * load default settings - all channels disabled,
> +	 * uni directional flow, default shunt values

The concept of a default shunt value bothers me a little if this is
an external resistor.  How is there in any real sense a default?
Can we just fail if it's not provided - or wrap the default up for
just ACPI case where I guess there might be firmware that doesn't
specify it?

> +	 */
> +	for (cnt = 0; cnt < chip_info->phys_channels; cnt++) {
> +		chip_info->active_channels[cnt] = false;
> +		chip_info->bi_dir[cnt] = false;
> +		chip_info->shunts[cnt] = SHUNT_UOHMS_DEFAULT;
> +	}
> +
> +	chip_info->crt_samp_spd_bitfield = pac193X_samp_1024sps;
> +
> +	if (ACPI_HANDLE(&client->dev)) {
> +		switch (chip_info->chip_variant) {
> +		case PAC1934_PID:
> +			client->dev.init_name = "pac1934";

Fairly sure init_name is only relevant prior to driver_add() which is
long gone by the time probe is called.

> +			break;
> +		case PAC1933_PID:
> +			client->dev.init_name = "pac1933";
> +			break;
> +		case PAC1932_PID:
> +			client->dev.init_name = "pac1932";
> +			break;
> +		case PAC1931_PID:
> +			client->dev.init_name = "pac1931";
> +			break;
> +		default:
> +			return -EINVAL;
> +		}
> +		name = pac193x_match_acpi_device(client, chip_info);

This looks to be doing a lot more than matching (which has already happened
via the i2c query above). Rename the function.


> +	} else {
> +		/* identify the chip we have to deal with */
> +		ret = pac193x_chip_identify(chip_info);
> +		if (ret)
> +			return -EINVAL;
> +
> +		/* check if we find the device within DT */
> +		if (!client->dev.of_node || (!of_get_next_child(client->dev.of_node, NULL)))

You are checking for a child node?  This isn't checking for the device itself so the comment
doesn't match.

> +			return -EINVAL;
> +
> +		match = of_match_node(pac193x_of_match, client->dev.of_node);
> +		if (match) {
> +			ret = pac193x_match_of_device(client, chip_info);
> +			if (!ret)
> +				name = match->compatible;

I can't really follow why this complexity is all here.  This should be using
generic firmware properties from include/linux/property.h so that
ACPI PNP0001 and various other less common firmware types work.
Then you should be able to query the properties without a dance to match
the node. Given the ACPI specific handling it's fine to try that first
and then fallback to generic handling if it fails.  Though note, don't base
that decision on the ACPI_HANDLE as you may have a PRP0001 using firmware which
is also ACPI but uses device tree binding properties.

Name should not be the compatible, but rather just pac1931 etc (no vendor).
Given you have a bunch of different match codes, better to have a per type
constant structure with all the chip differences and just put a string
in there to use for the name.  That way same thing works for ACPI and DT.
Your pac193x_features structure is perfect for this.


> +		}
> +	}
> +
> +	if (!name) {
> +		dev_err_probe(&indio_dev->dev, PTR_ERR(indio_dev),
> +			      "parameter parsing returned an error\n");
> +		return -EINVAL;
> +	}
> +
> +	mutex_init(&chip_info->lock);
> +
> +	/*
> +	 * do now any chip specific initialization (e.g. read/write
> +	 * some registers), enable/disable certain channels, change the sampling
> +	 * rate to the requested value
> +	 */
> +	ret = pac193x_chip_configure(chip_info);
> +	if (ret < 0)
> +		goto free_chan_attr_mem;

I'm not totally sure which parts of remove match with this, but I'd expect
there to be devm_add_action_or_reset() calls to setup automated tear down
alongside each aspect.

The one thing the call doesn't seem to do is free memory!

> +
> +	/* prepare the channel information */
> +	ret = pac193x_prep_iio_channels(chip_info, indio_dev);
> +	if (ret < 0)
> +		goto free_chan_attr_mem;
> +
> +	ret = pac193x_prep_custom_attributes(chip_info, indio_dev);
> +	if (ret < 0) {
> +		dev_err_probe(&indio_dev->dev, ret,
> +			      "Can't configure custom attributes for PAC193X device\n");
> +		goto free_chan_attr_mem;
> +	}
> +
> +	chip_info->pac193x_info.read_raw = pac193x_read_raw;
> +	chip_info->pac193x_info.read_avail = pac193x_read_avail;
> +	chip_info->pac193x_info.write_raw = pac193x_write_raw;
> +
> +	indio_dev->info = &chip_info->pac193x_info;
> +	indio_dev->name = name;
> +	indio_dev->dev.parent = &client->dev;

Parent is set automatically by the devm_iio_device_alloc(). A driver should
only be doing this if it needs to change the default for some reason (a few drivers
do).

> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +
> +	/*
> +	 * read whatever it has been accumulated in the chip so far

whatever has been

> +	 * and reset the accumulators
> +	 */
> +	ret = pac193x_reg_snapshot(chip_info, true, false, PAC193X_MIN_UPDATE_WAIT_TIME_MS);
> +	if (ret < 0)
> +		goto free_chan_attr_mem;
> +
> +	/* register with IIO */

Drop all comments that say things that can be easily seen from the code.
They provide no value and can easily become wrong.

> +	ret = devm_iio_device_register(&client->dev, indio_dev);

This results in the user interface being remove after you've called
the pac193x_remove() which seems likely to be a nasty race.

There are two ways to use devm safely.
1) Stop using any devm_ calls at the first thing you manually undo in
   error paths / remove() callback.
2) Don't have any handing in error paths or remove callback because you
   have converted everything to device managed (devm_add_action_or_reset() etc
   to register custom callbacks).

2 is preferred unless things get really complex because it allows for
easy step by step review.

> +	if (ret < 0) {
> +		dev_err_probe(&indio_dev->dev, ret,
> +			      "Can't register IIO device\n");
> +		goto free_chan_attr_mem;
> +	}
> +
> +	return 0;
> +
> +free_chan_attr_mem:
> +	pac193x_remove(client);

devm_add_action_or_reset() allows you to register an extra callback
that will be called in the correct location.  Ideally a remove flow
is almost always the reverse of probe flow as that makes it much easier
to avoid use after free and similar races.  The code here is probably correct
but it is harder to review.

You will probably want to break the _remove() function up into a couple
of different callbacks, undoing individual steps in probe()

> +
> +	return ret;
> +}
> +
> +static const struct i2c_device_id pac193x_id[] = {
> +	{ "pac1934", pac1934 },

Opposite order is more common.

> +	{ "pac1933", pac1933 },
> +	{ "pac1932", pac1932 },
> +	{ "pac1931", pac1931 },
> +	{}
> +};
> +MODULE_DEVICE_TABLE(i2c, pac193x_id);
> +
> +#ifdef CONFIG_ACPI
> +static const struct acpi_device_id pac193x_acpi_match[] = {
> +	{"MCHP1930", 0},

Someone had a sense of humour if this ID doesn't actually cover a part
called 1930.. :)

> +	{ },
Trivial, but prefer no , on "NULL terminators" as we will never add
anything after them.
. 
> +};
> +MODULE_DEVICE_TABLE(acpi, pac193x_acpi_match);
> +#endif
> +
> +static struct i2c_driver pac193x_driver = {
> +	.driver	 = {
> +		.name = "pac193x",
> +		.of_match_table = pac193x_of_match,
> +		.acpi_match_table = ACPI_PTR(pac193x_acpi_match)

It is very rarely worth the complexity to save a tiny bit of size
that results from ACPI_PTR()  So unless I'm missing another reason
for this (missing stubs maybe) then I'd not bother with that protection
or the ifdef guards.


> +		},
> +	.probe = pac193x_probe,

Use probe_new
This has very nearly gone away in upstream kernel.

> +	.remove = pac193x_remove,
> +	.id_table = pac193x_id,
> +};
> +
> +module_i2c_driver(pac193x_driver);
> +
> +MODULE_AUTHOR("Bogdan Bolocan <bogdan.bolocan@microchip.com>");
> +MODULE_AUTHOR("Victor Tudose");
> +MODULE_AUTHOR("Marius Cristea <marius.cristea@microchip.com>");
> +MODULE_DESCRIPTION("IIO driver for PAC193X Multi-Channel DC Power/Energy Monitor");
> +MODULE_LICENSE("GPL");
marius.cristea@microchip.com March 6, 2023, 1:56 p.m. UTC | #7
Hi Krzysztof,


   Thank you for your review comments. I will handle all of them.

Thanks,
Marius

On Sat, 2023-02-25 at 17:22 +0000, Jonathan Cameron wrote:
> EXTERNAL EMAIL: Do not click links or open attachments unless you
> know the content is safe
> 
> On Sat, 25 Feb 2023 17:19:54 +0000
> Jonathan Cameron <jic23@kernel.org> wrote:
> 
> > On Tue, 21 Feb 2023 14:46:08 +0100
> > Krzysztof Kozlowski <krzk@kernel.org> wrote:
> > 
> > > On 20/02/2023 13:32, marius.cristea@microchip.com wrote:
> > > > From: Marius Cristea <marius.cristea@microchip.com>
> > > > 
> > > > This is the iio driver for Microchip
> > > > PAC193X series of Power Monitor with Accumulator chip family.
> > > > 
> > > > Signed-off-by: Marius Cristea <marius.cristea@microchip.com>
> > > > ---
> > > >  MAINTAINERS               |    7 +
> > > >  drivers/iio/adc/Kconfig   |   12 +
> > > >  drivers/iio/adc/Makefile  |    1 +
> > > >  drivers/iio/adc/pac193x.c | 2072
> > > > +++++++++++++++++++++++++++++++++++++
> > > >  4 files changed, 2092 insertions(+)
> > > >  create mode 100644 drivers/iio/adc/pac193x.c
> > > > 
> > > 
> > > 
> > > Thank you for your patch. There is something to discuss/improve.
> > > 
> > > > +
> > > > +#define PAC193X_NEG_PWR_CH1_BIDI(x)      ((x) ? BIT(7) : 0)
> > > > +#define PAC193X_NEG_PWR_CH2_BIDI(x)      ((x) ? BIT(6) : 0)
> > > > +#define PAC193X_NEG_PWR_CH3_BIDI(x)      ((x) ? BIT(5) : 0)
> > > > +#define PAC193X_NEG_PWR_CH4_BIDI(x)      ((x) ? BIT(4) : 0)
> > > > +#define PAC193X_NEG_PWR_CH1_BIDV(x)      ((x) ? BIT(3) : 0)
> > > > +#define PAC193X_NEG_PWR_CH2_BIDV(x)      ((x) ? BIT(2) : 0)
> > > > +#define PAC193X_NEG_PWR_CH3_BIDV(x)      ((x) ? BIT(1) : 0)
> > > > +#define PAC193X_NEG_PWR_CH4_BIDV(x)      ((x) ? BIT(0) : 0)
> > > > +
> > > > +/*
> > > > + * Universal Unique Identifier (UUID),
> > > > + * 033771E0-1705-47B4-9535-D1BBE14D9A09, is
> > > > + * reserved to Microchip for the PAC193X and must not be
> > > > changed
> > > > + */
> > > > +#define PAC193X_DSM_UUID         "033771E0-1705-47B4-9535-
> > > > D1BBE14D9A09"
> > > > +
> > > > +enum pac193x_ids {
> > > > + pac1934,
> > > > + pac1933,
> > > > + pac1932,
> > > > + pac1931
> > > 
> > > Enums are usually uppercase.
> > 
> > I'm not sure there is anything in coding standard around that and a
> > grep finds
> > a mixture of the two when it comes to ones used for IDs.  Mind you
> > uppercase
> > is fine :)
> I take it back. Is indeed in coding style doc.  Glad checkpatch
> doesn't check for
> this though as we'd get 1000s of 'fixes' if it did and in most cases
> it doesn't
> hurt readability.  I'll try and be more consistent on this in review
> going forwards!
> 
> Thanks!
> 
> Jonathan
> 
> > 
> > 
> > > 
> > > 
> > > 
> > > Best regards,
> > > Krzysztof
> > > 
> > 
>
marius.cristea@microchip.com March 6, 2023, 3:42 p.m. UTC | #8
Hi Jonathan,

  Please see my comments below. (I will take out section that I already
agree with):


On Sat, 2023-02-25 at 19:27 +0000, Jonathan Cameron wrote:
> 
> a representative part from the list - if no other basis to chose go
> with the
> lowest number currently supported.
> 
> The custom ABI in here needs documentation before we can review it
> properly.
> 

I will comment all new ABI that I have added.


> Comments inline.
> 
> It's a big driver, so I'm sure more stuff will come up next round
> just due to
> mental exhaustion reviewing it in one go!
> 
> 
> 
> 
> 




> 
> 
> > BIT(IIO_CHAN_INFO_SAMP_FREQ),      \
> > +     .scan_index =
> > (_si),                                                    \
> > +     .scan_type =
> > {                                                          \
> > +             .sign =
> > 'u',                                                    \
> > +             .realbits =
> > PAC193X_POWER_U_RES,                                \
> > +             .storagebits =
> > 32,                                              \
> > +             .shift =
> > 4,                                                     \
> > +             .endianness =
> > IIO_CPU,                                          \
> > +    
> > }                                                                  
> >      \
> > +}
> > +
> > +#define PAC193X_SOFT_TIMESTAMP(_index)
> > {                                     \
> > +     .type =
> > IIO_TIMESTAMP,                                                  \
> > +     .channel = -
> > 1,                                                          \
> 
> What is this for?
> 

  I will take it out. Initially I want to have also "data buffering"
into the driver, but this functionality I will add it later. Data
buffering (containing timestamp) will be useful when profiling power
consumption.



> 
> 
> 
> > +
> > +     len += sprintf(buf, "%u\n", chip_info->shunts[target]);
> > +
> > +     return len;
> > +}
> > +
> > +static ssize_t channel_name_show(struct device *dev,
> 
> Looks like per channel label support provided by the read_label
> iio_info callback.
> 

I was trying to use the read_label iio_info callback but I end it up
into a sysfs error related to duplicated labels.

Also I'm not sure if this read_label will help me in the end. What I
was aiming to obtain here with the "channel_name_show" and the custom
IIO attribute was to have "an umbrella" name/label for multiple IIO
channels. For example on physical "channel 1" we will measure the
voltage, the current and the power because all of those IIO channels
will point to one board power supply (like VDD, VIO, V_GPU, V_DDR,
etc).





> > +                              struct device_attribute *attr,
> > +                              char *buf)
> > +{
> > +     struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> > +     struct pac193x_chip_info *chip_info = iio_priv(indio_dev);
> > +     int len = 0;
> > +     int target = (int)(attr->attr.name[strlen(attr->attr.name) -
> > 1] - '0') - 1;
> > +
> > +     len += sprintf(buf, "%s\n", chip_info-
> > >channel_names[target]);
> > +
> > +     return len;
> > +}
> > +
> > +static ssize_t shunt_value_store(struct device *dev,
> 
> Shunt values aren't normally dynamic.  Why do you need to write it
> from
> userspace? I'd expect that to come from firmware.
> 
> If it's needed the ext_info framework might be a better fit than
> direct implementation of the attribute.


Yes, usually the shunt aren't normally dynamic, but what I'm aiming to
get here is to let the user to overwrite the value after a calibration
of the system. This will improve the accuracy of the reading even in
the case the shunts are not of high precision ones.


> 
> > +                     case PAC193X_VPOWER_4_ADDR:
> > +                             *val = chip_info-
> > >chip_reg_data.vpower[channel];
> > +                             return IIO_VAL_INT;
> > +                     default:
> > +                             return -EINVAL;
> > +                     }
> > +                     break;
> > +             case IIO_ENERGY:
> > +                     switch (chan->address) {
> > +                     case PAC193X_VPOWER_ACC_1_ADDR:
> > +                     case PAC193X_VPOWER_ACC_2_ADDR:
> > +                     case PAC193X_VPOWER_ACC_3_ADDR:
> > +                     case PAC193X_VPOWER_ACC_4_ADDR:
> > +                             /*
> > +                              * expresses the 64 bit energy value
> > as a 32 bit integer part and
> > +                              * 32 bits (representing 8 digits)
> > fractional value
> 
> I'm lost.  So it's a 64 bit value?
> We have IIO_VAL_INT_64. Does that not work here?
> 

Yes, we have the IIO_VAL_INT_64 and it will work here. The "issue" was
that the IIO_VAL_INT_64 is not available before kernel v6.0 and we
already release something to customers supporting Linux kernel v4.7 and
v5.15. Also our latest release of Linux4Sam is based on the Linux
kernel v5.15 (we are working to have a release based on v6.1).

Maybe I'm totally wrong here but my idea was to have something "more
generic" that could be easily backported to an older kernel and to
change this to IIO_VAL_INT_64 later. 


> > +                              */
> > +                             curr_energy = chip_info-
> > >chip_reg_data.energy_sec_acc[channel];
> > +                             int_part = div_s64_rem(curr_energy,
> > 100000000, &rem);
> > +
> > +                             /* rescale reminder to be printed as
> > "nano" value */
> > +                             rem = rem * 10;
> > +
> > +                             *val = int_part;
> > +                             *val2 = rem;
> > +                             return IIO_VAL_INT_PLUS_NANO;
> > +                     default:
> > +                             return -EINVAL;
> > +                     }
> > +                     break;
> > +             default:
> > +                     return -EINVAL;
> > +             }
> > +             break;
> > +     case IIO_CHAN_INFO_AVERAGE_RAW:
> 
> I'd forgotten we had this ABI and had to look up the docs.   Hmm.
> It's a bit clunky but I guess
> still useful in this case.  Longer term we should think about how to
> describe that and how it
> is different from oversampling. The problem with oversampling is it
> affects the main channel
> so we can't easily associate both an averaged value  and a raw one
> like you have here.
> 
> Given lots of other changes requested and early in this kernel cycle,
> perhaps it will make sense to revisit
> this question at a later version of this patch (or maybe just leave
> it alone as too hard
> to do better!)


Here I was trying to map the IIO ABI to the PAC internal registers. We
have some hardware registers that will do an average for us. Usually
with oversampling someone could increase the bit number of the
measurement, the average is just the sum of the numbers divided by how
many numbers are and it can eliminate some noise out of the
measurements.


Here the device has registers that will keep the current values (and
will calculate the power based on those values) and also some average
measurements registers (keeping the same precision) that could help the
user to identifies trends.


> 



> > +             ctrl_reg = PAC193X_CRTL_SAMPLE_RATE_SET(chip_info-
> > >crt_samp_spd_bitfield) |
> > +                     PAC193X_CHAN_ALERT_EN;
> > +             ret = pac193x_i2c_write_byte(client,
> > PAC193X_CTRL_REG, ctrl_reg);
> > +             if (ret) {
> > +                     dev_err(&client->dev, "%s - can't update
> > sample rate\n", __func__);
> > +                     chip_info->sample_rate_value = old_samp_rate;
> > +                     mutex_unlock(&chip_info->lock);
> 
> Fun thing about i2c bus write failures. You have no idea what failed.
> The value might be
> the old value or the new one after the failed write... So normally we
> don't bother too much if our values are
> out of sync. and that leads to easier unwinding.  However, if you
> want to keep this as it
> is then that's fine.
> 


I would like to keep it, maybe there are some corner cases when it will
help debugging (good to know in case of intermittent issues)


> > +static void pac193x_work_periodic_rfsh(struct work_struct *work)
> > +{
> > +     struct pac193x_chip_info *chip_info =
> > to_pac193x_chip_info(work);
> > +
> > +     /* do a REFRESH, then read */
> > +     pac193x_reg_snapshot(chip_info, true, false,
> > PAC193X_MIN_UPDATE_WAIT_TIME_MS);
> > +}
> > +
> > +static void pac193x_read_reg_timeout(struct timer_list *t)
> > +{
> > +     struct pac193x_chip_info *chip_info = from_timer(chip_info,
> > t, tmr_forced_update);
> > +
> > +     mod_timer(&chip_info->tmr_forced_update,
> > +               jiffies +
> > msecs_to_jiffies(PAC193X_MAX_RFSH_LIMIT_MS));
> > +
> > +     /* schedule the periodic reading from the chip */
> > +     queue_work(chip_info->wq_chip, &chip_info->work_chip_rfsh);
> 
> Why not queue_delayed_work()?
> 


I will need to look deeper.


> 
> 
> 
> 
> > +                                                  int revision,
> > +                                                  int function)
> > +{
> > +     acpi_status status;
> > +     struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL};
> > +     union acpi_object args[PAC193X_ACPI_ARG_COUNT];
> > +     struct acpi_object_list args_list;
> > +     uuid_t uuid;
> > +
> > +     uuid_parse(PAC193X_DSM_UUID, &uuid);
> > +
> > +     args[0].type = ACPI_TYPE_BUFFER;
> > +     args[0].buffer.length = sizeof(struct pac193x_uuid_format);
> > +     args[0].buffer.pointer = (u8 *)&uuid;
> > +
> > +     args[1].type = ACPI_TYPE_INTEGER;
> > +     args[1].integer.value = revision;
> > +
> > +     args[2].type = ACPI_TYPE_INTEGER;
> > +     args[2].integer.value = function;
> > +
> > +     args[3].type = ACPI_TYPE_PACKAGE;
> > +     args[3].package.count = 0;
> > +
> > +     args_list.count = PAC193X_ACPI_ARG_COUNT;
> > +     args_list.pointer = &args[0];
> > +
> > +     status = acpi_evaluate_object(handle, "_DSM", &args_list,
> > &buffer);
> > +
> > +     if (ACPI_FAILURE(status)) {
> > +             kfree(buffer.pointer);
> > +             return NULL;
> > +     }
> > +
> > +     return buffer.pointer;
> > +}
> > +
> > +static char *pac193x_acpi_get_acpi_match_entry(acpi_handle handle)
> > +{
> > +     acpi_status status;
> > +     union acpi_object *name_object;
> > +     struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL};
> > +
> > +     status = acpi_evaluate_object(handle, "_HID", NULL, &buffer);
> > +     name_object = buffer.pointer;
> > +
> > +     return name_object->string.pointer;
> > +}
> > +
> > +static const char *pac193x_match_acpi_device(struct i2c_client
> > *client,
> > +                                          struct pac193x_chip_info
> > *chip_info)
> > +{
> > +     char *name;
> > +     acpi_handle handle;
> > +     union acpi_object *rez;
> > +     unsigned short bi_dir_mask;
> > +     int idx, i;
> > +
> 
> Any docs for the ACPI stuff?
> It looks plausible, but an example DSDT blob would make it easier to
> review.
> 

Here we have:
https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ApplicationNotes/ApplicationNotes/PAC193X-Integration-Notes-for-Microsoft-Windows-10-and-Windows-11-Driver-Support-DS00002534.pdf


> 
> 
> > +     /*
> > +      * count how many channels are enabled and store
> > +      * this information within the driver data
> > +      */
> > +     cnt = 0;
> > +     chip_info->chip_reg_data.num_enabled_channels = 0;
> > +     while (cnt < chip_info->phys_channels) {
> > +             if (chip_info->active_channels[cnt])
> > +                     chip_info-
> > >chip_reg_data.num_enabled_channels++;
> > +             cnt++;
> > +     }
> > +
> > +     /*
> > +      * read whatever information was gathered before the driver
> > was loaded
> > +      * establish which channels are enabled/disabled and then
> > establish the
> > +      * information retrieval mode (using SKIP or no).
> 
> It's unusual to have a driver carry on with existing values.  Much
> more common
> to either have it reset the device (to known default state) or write
> all the
> registers if such a reset doesn't exist.
> 
> One reason for that flow is that we really don't trust other software
> :)
> Another is that often we don't know if the device had any power until
> we turned it
> on.  So it's much easier to review code where we control all the
> state.
> 
> If there is a strong reason for carrying on with existing settings
> please add
> comments here and something to the patch description to point out
> this unusual
> behaviour.
> 

There are some customers asking to measure the total power exchanged
from the power-up of the system (like if you are spending a lot of time
into the bootloader and drain the battery).




> 
> 
> 
> 
> > shunt_value_store, 0);
> > +static IIO_DEVICE_ATTR(shunt_value_2, 0644, shunt_value_show,
> > shunt_value_store, 0);
> > +static IIO_DEVICE_ATTR(shunt_value_3, 0644, shunt_value_show,
> > shunt_value_store, 0);
> > +static IIO_DEVICE_ATTR(shunt_value_4, 0644, shunt_value_show,
> > shunt_value_store, 0);
> > +
> > +static IIO_DEVICE_ATTR(channel_name_1, 0444, channel_name_show,
> > NULL, 0);
> > +static IIO_DEVICE_ATTR(channel_name_2, 0444, channel_name_show,
> > NULL, 0);
> > +static IIO_DEVICE_ATTR(channel_name_3, 0444, channel_name_show,
> > NULL, 0);
> > +static IIO_DEVICE_ATTR(channel_name_4, 0444, channel_name_show,
> > NULL, 0);
> > +
> > +static IIO_DEVICE_ATTR(reset_accumulators, 0200, NULL,
> > reset_accumulators_store, 0);
> > +
> > +static struct attribute *pac193x_all_attributes[] = {
> > +     PAC193X_DEV_ATTR(shunt_value_1),
> 
> These all need ABI documentation so that we can easily review what
> they do.
> Documenation/ABI/testing/sysfs-bus-iio-pac1931
> Note that if they overlap with ABI used elsewhere we may need to move
> it to a more
> generic place (there can't be two lots of docs for the same ABI
> element)
> 

I will add comments into the code. The "shunt_value_show" will print
the shunt value used to calculate current and power.
The "channel_name_show" is the name of the device channel used to
easily identify what we are measuring (V_DDR, V_GPU, V_IO, etc) and
it's an "umbrella" for multiple IIO measurements (voltage, current and
power)

"reset_accumulators" will help the user to reinitialize the power on
all channels.


> > +     if (!indio_dev) {
> > +             dev_err_probe(&indio_dev->dev, PTR_ERR(indio_dev),
> > +                           "Can't allocate iio device\n");
> > +             return -ENOMEM;
> > +     }
> > +
> > +     chip_info = iio_priv(indio_dev);
> > +
> > +     i2c_set_clientdata(client, indio_dev);
> 
> Assuming you follow suggestion to go fully devm managed handling of
> remove, I don't htink
> you will need this.
> 
> > +     chip_info->client = client;
> > +
> > +     memset(&chip_info->chip_reg_data, 0, sizeof(chip_info-
> > >chip_reg_data));
> 
> The iio_priv space is allocated with kzalloc so no need to zero here.
> 
> > +
> > +     if (ACPI_HANDLE(&client->dev)) {
> > +             pac193x_get_variant(chip_info);
> 
> Why is this ACPI specific?

Not really. I will change it to work in both cases.

> 
> If you can query the variant from the hardware, that is the right
> thing to use
> for all registration types.  It can be helpful to call out if there
> is
> a mismatch though with a warning print, so comparing the result of
> the i2c call with the data is fine.
> 
> > +     } else {
> > +             dev_id = id->driver_data;
> > +
> > +             /* store the type of chip */
> > +             chip_info->chip_variant = dev_id;
> > +
> > +             /* get the maximum number of channels for the given
> > chip id */
> > +             chip_info->phys_channels =
> > pac193x_chip_config[dev_id].phys_channels;
> > +     }
> > +
> > +     /*
> > +      * load default settings - all channels disabled,
> > +      * uni directional flow, default shunt values
> 
> The concept of a default shunt value bothers me a little if this is
> an external resistor.  How is there in any real sense a default?
> Can we just fail if it's not provided - or wrap the default up for
> just ACPI case where I guess there might be firmware that doesn't
> specify it?

I'm doing some math with that resistor later in code and I was just
making sure that I will not divide by 0, but indeed I could just exit
in case the value is not correctly set.

> 

Thanks,
Marius
Jonathan Cameron March 12, 2023, 4:42 p.m. UTC | #9
> 
> > 
> > 
> >   
> > > +
> > > +     len += sprintf(buf, "%u\n", chip_info->shunts[target]);
> > > +
> > > +     return len;
> > > +}
> > > +
> > > +static ssize_t channel_name_show(struct device *dev,  
> > 
> > Looks like per channel label support provided by the read_label
> > iio_info callback.
> >   
> 
> I was trying to use the read_label iio_info callback but I end it up
> into a sysfs error related to duplicated labels.

That's interesting.  Can you provide more info on that. I'd like to
understand why that's happening to you.

> 
> Also I'm not sure if this read_label will help me in the end. What I
> was aiming to obtain here with the "channel_name_show" and the custom
> IIO attribute was to have "an umbrella" name/label for multiple IIO
> channels. For example on physical "channel 1" we will measure the
> voltage, the current and the power because all of those IIO channels
> will point to one board power supply (like VDD, VIO, V_GPU, V_DDR,
> etc).

That should be possible with read_label as it's free form text.
Perhaps I'm missing why that doesn't provide enough information for what
you need.

> 
> 
> 
> 
> 
> > > +                              struct device_attribute *attr,
> > > +                              char *buf)
> > > +{
> > > +     struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> > > +     struct pac193x_chip_info *chip_info = iio_priv(indio_dev);
> > > +     int len = 0;
> > > +     int target = (int)(attr->attr.name[strlen(attr->attr.name) -
> > > 1] - '0') - 1;
> > > +
> > > +     len += sprintf(buf, "%s\n", chip_info-  
> > > >channel_names[target]);  
> > > +
> > > +     return len;
> > > +}
> > > +
> > > +static ssize_t shunt_value_store(struct device *dev,  
> > 
> > Shunt values aren't normally dynamic.  Why do you need to write it
> > from
> > userspace? I'd expect that to come from firmware.
> > 
> > If it's needed the ext_info framework might be a better fit than
> > direct implementation of the attribute.  
> 
> 
> Yes, usually the shunt aren't normally dynamic, but what I'm aiming to
> get here is to let the user to overwrite the value after a calibration
> of the system. This will improve the accuracy of the reading even in
> the case the shunts are not of high precision ones.

I'll go with maybe on this.  Perhaps not a feature for the initial
version of the driver, but one that is better to discuss in a follow
up thread along with details of the expected calibration process etc.

> 
> >   
> > > +                     case PAC193X_VPOWER_4_ADDR:
> > > +                             *val = chip_info-  
> > > >chip_reg_data.vpower[channel];  
> > > +                             return IIO_VAL_INT;
> > > +                     default:
> > > +                             return -EINVAL;
> > > +                     }
> > > +                     break;
> > > +             case IIO_ENERGY:
> > > +                     switch (chan->address) {
> > > +                     case PAC193X_VPOWER_ACC_1_ADDR:
> > > +                     case PAC193X_VPOWER_ACC_2_ADDR:
> > > +                     case PAC193X_VPOWER_ACC_3_ADDR:
> > > +                     case PAC193X_VPOWER_ACC_4_ADDR:
> > > +                             /*
> > > +                              * expresses the 64 bit energy value
> > > as a 32 bit integer part and
> > > +                              * 32 bits (representing 8 digits)
> > > fractional value  
> > 
> > I'm lost.  So it's a 64 bit value?
> > We have IIO_VAL_INT_64. Does that not work here?
> >   
> 
> Yes, we have the IIO_VAL_INT_64 and it will work here. The "issue" was
> that the IIO_VAL_INT_64 is not available before kernel v6.0 and we
> already release something to customers supporting Linux kernel v4.7 and
> v5.15. Also our latest release of Linux4Sam is based on the Linux
> kernel v5.15 (we are working to have a release based on v6.1).
> 
> Maybe I'm totally wrong here but my idea was to have something "more
> generic" that could be easily backported to an older kernel and to
> change this to IIO_VAL_INT_64 later. 

As a general rule, we don't do this because it stops us from ever
improving things like this.  If you need IIO_VAL_INT_64 on
an older kernel then backport that as well.  Or provide a backported
version of your driver doing something different from upstream version.


> 
> 
> > > +                              */
> > > +                             curr_energy = chip_info-  
> > > >chip_reg_data.energy_sec_acc[channel];  
> > > +                             int_part = div_s64_rem(curr_energy,
> > > 100000000, &rem);
> > > +
> > > +                             /* rescale reminder to be printed as
> > > "nano" value */
> > > +                             rem = rem * 10;
> > > +
> > > +                             *val = int_part;
> > > +                             *val2 = rem;
> > > +                             return IIO_VAL_INT_PLUS_NANO;
> > > +                     default:
> > > +                             return -EINVAL;
> > > +                     }
> > > +                     break;
> > > +             default:
> > > +                     return -EINVAL;
> > > +             }
> > > +             break;
> > > +     case IIO_CHAN_INFO_AVERAGE_RAW:  
> > 
> > I'd forgotten we had this ABI and had to look up the docs.   Hmm.
> > It's a bit clunky but I guess
> > still useful in this case.  Longer term we should think about how to
> > describe that and how it
> > is different from oversampling. The problem with oversampling is it
> > affects the main channel
> > so we can't easily associate both an averaged value  and a raw one
> > like you have here.
> > 
> > Given lots of other changes requested and early in this kernel cycle,
> > perhaps it will make sense to revisit
> > this question at a later version of this patch (or maybe just leave
> > it alone as too hard
> > to do better!)  
> 
> 
> Here I was trying to map the IIO ABI to the PAC internal registers. We
> have some hardware registers that will do an average for us. Usually
> with oversampling someone could increase the bit number of the
> measurement, the average is just the sum of the numbers divided by how
> many numbers are and it can eliminate some noise out of the
> measurements.
> 
> 
> Here the device has registers that will keep the current values (and
> will calculate the power based on those values) and also some average
> measurements registers (keeping the same precision) that could help the
> user to identifies trends.

Understood. It was similar hardware that lead to this ABI existing
in the first place.  Whilst you are right that the purpose is somewhat
different.  In some cases oversampling in hardware doesn't increase the
bit depth and ends up looking much the same as an average.

> 
> 
> >   
> 
> 
> 
> > > +             ctrl_reg = PAC193X_CRTL_SAMPLE_RATE_SET(chip_info-  
> > > >crt_samp_spd_bitfield) |  
> > > +                     PAC193X_CHAN_ALERT_EN;
> > > +             ret = pac193x_i2c_write_byte(client,
> > > PAC193X_CTRL_REG, ctrl_reg);
> > > +             if (ret) {
> > > +                     dev_err(&client->dev, "%s - can't update
> > > sample rate\n", __func__);
> > > +                     chip_info->sample_rate_value = old_samp_rate;
> > > +                     mutex_unlock(&chip_info->lock);  
> > 
> > Fun thing about i2c bus write failures. You have no idea what failed.
> > The value might be
> > the old value or the new one after the failed write... So normally we
> > don't bother too much if our values are
> > out of sync. and that leads to easier unwinding.  However, if you
> > want to keep this as it
> > is then that's fine.
> >   
> 
> 
> I would like to keep it, maybe there are some corner cases when it will
> help debugging (good to know in case of intermittent issues)

I'm fine seeing the error print, but unless you succesfully hammer back in
the old value with a new write, you have no guarantee of what the device
thinks the value is.


> > 
> > 
> >   
> > > +                                                  int revision,
> > > +                                                  int function)
> > > +{
> > > +     acpi_status status;
> > > +     struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL};
> > > +     union acpi_object args[PAC193X_ACPI_ARG_COUNT];
> > > +     struct acpi_object_list args_list;
> > > +     uuid_t uuid;
> > > +
> > > +     uuid_parse(PAC193X_DSM_UUID, &uuid);
> > > +
> > > +     args[0].type = ACPI_TYPE_BUFFER;
> > > +     args[0].buffer.length = sizeof(struct pac193x_uuid_format);
> > > +     args[0].buffer.pointer = (u8 *)&uuid;
> > > +
> > > +     args[1].type = ACPI_TYPE_INTEGER;
> > > +     args[1].integer.value = revision;
> > > +
> > > +     args[2].type = ACPI_TYPE_INTEGER;
> > > +     args[2].integer.value = function;
> > > +
> > > +     args[3].type = ACPI_TYPE_PACKAGE;
> > > +     args[3].package.count = 0;
> > > +
> > > +     args_list.count = PAC193X_ACPI_ARG_COUNT;
> > > +     args_list.pointer = &args[0];
> > > +
> > > +     status = acpi_evaluate_object(handle, "_DSM", &args_list,
> > > &buffer);
> > > +
> > > +     if (ACPI_FAILURE(status)) {
> > > +             kfree(buffer.pointer);
> > > +             return NULL;
> > > +     }
> > > +
> > > +     return buffer.pointer;
> > > +}
> > > +
> > > +static char *pac193x_acpi_get_acpi_match_entry(acpi_handle handle)
> > > +{
> > > +     acpi_status status;
> > > +     union acpi_object *name_object;
> > > +     struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL};
> > > +
> > > +     status = acpi_evaluate_object(handle, "_HID", NULL, &buffer);
> > > +     name_object = buffer.pointer;
> > > +
> > > +     return name_object->string.pointer;
> > > +}
> > > +
> > > +static const char *pac193x_match_acpi_device(struct i2c_client
> > > *client,
> > > +                                          struct pac193x_chip_info
> > > *chip_info)
> > > +{
> > > +     char *name;
> > > +     acpi_handle handle;
> > > +     union acpi_object *rez;
> > > +     unsigned short bi_dir_mask;
> > > +     int idx, i;
> > > +  
> > 
> > Any docs for the ACPI stuff?
> > It looks plausible, but an example DSDT blob would make it easier to
> > review.
> >   
> 
> Here we have:
> https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ApplicationNotes/ApplicationNotes/PAC193X-Integration-Notes-for-Microsoft-Windows-10-and-Windows-11-Driver-Support-DS00002534.pdf
Yikes that's a long file name ;)  Exactly what I was looking for. Thanks.

Please add that link to the driver somewhere.  + ignore the inevitable
checkpatch warning on line length.

> 
> 
> > 
> >   
> > > +     /*
> > > +      * count how many channels are enabled and store
> > > +      * this information within the driver data
> > > +      */
> > > +     cnt = 0;
> > > +     chip_info->chip_reg_data.num_enabled_channels = 0;
> > > +     while (cnt < chip_info->phys_channels) {
> > > +             if (chip_info->active_channels[cnt])
> > > +                     chip_info-  
> > > >chip_reg_data.num_enabled_channels++;  
> > > +             cnt++;
> > > +     }
> > > +
> > > +     /*
> > > +      * read whatever information was gathered before the driver
> > > was loaded
> > > +      * establish which channels are enabled/disabled and then
> > > establish the
> > > +      * information retrieval mode (using SKIP or no).  
> > 
> > It's unusual to have a driver carry on with existing values.  Much
> > more common
> > to either have it reset the device (to known default state) or write
> > all the
> > registers if such a reset doesn't exist.
> > 
> > One reason for that flow is that we really don't trust other software
> > :)
> > Another is that often we don't know if the device had any power until
> > we turned it
> > on.  So it's much easier to review code where we control all the
> > state.
> > 
> > If there is a strong reason for carrying on with existing settings
> > please add
> > comments here and something to the patch description to point out
> > this unusual
> > behaviour.
> >   
> 
> There are some customers asking to measure the total power exchanged
> from the power-up of the system (like if you are spending a lot of time
> into the bootloader and drain the battery).

Ok. Add a comment on that.


> 
> 
> 
> 
> > 
> > 
> > 
> >   
> > > shunt_value_store, 0);
> > > +static IIO_DEVICE_ATTR(shunt_value_2, 0644, shunt_value_show,
> > > shunt_value_store, 0);
> > > +static IIO_DEVICE_ATTR(shunt_value_3, 0644, shunt_value_show,
> > > shunt_value_store, 0);
> > > +static IIO_DEVICE_ATTR(shunt_value_4, 0644, shunt_value_show,
> > > shunt_value_store, 0);
> > > +
> > > +static IIO_DEVICE_ATTR(channel_name_1, 0444, channel_name_show,
> > > NULL, 0);
> > > +static IIO_DEVICE_ATTR(channel_name_2, 0444, channel_name_show,
> > > NULL, 0);
> > > +static IIO_DEVICE_ATTR(channel_name_3, 0444, channel_name_show,
> > > NULL, 0);
> > > +static IIO_DEVICE_ATTR(channel_name_4, 0444, channel_name_show,
> > > NULL, 0);
> > > +
> > > +static IIO_DEVICE_ATTR(reset_accumulators, 0200, NULL,
> > > reset_accumulators_store, 0);
> > > +
> > > +static struct attribute *pac193x_all_attributes[] = {
> > > +     PAC193X_DEV_ATTR(shunt_value_1),  
> > 
> > These all need ABI documentation so that we can easily review what
> > they do.
> > Documenation/ABI/testing/sysfs-bus-iio-pac1931
> > Note that if they overlap with ABI used elsewhere we may need to move
> > it to a more
> > generic place (there can't be two lots of docs for the same ABI
> > element)
> >   
> 
> I will add comments into the code. The "shunt_value_show" will print
> the shunt value used to calculate current and power.
I'd prefer handling shut calibration as a future patch because it needs
some thought and discussion + may delay the rest of the driver.  Without that
ability to tweak it I'm not sure it provides value to be able to read
it now.   

> The "channel_name_show" is the name of the device channel used to
> easily identify what we are measuring (V_DDR, V_GPU, V_IO, etc) and
> it's an "umbrella" for multiple IIO measurements (voltage, current and
> power)

As above. I'd like to think a bit more on this one.

> 
> "reset_accumulators" will help the user to reinitialize the power on
> all channels.

This sounds like the rare case where the ENABLE IIO ABI may fit.
We use that for things like step counters that also accumulate over
time.  If we can use that, I'd prefer it to custom ABI.

> 
> 
> > > +     if (!indio_dev) {
> > > +             dev_err_probe(&indio_dev->dev, PTR_ERR(indio_dev),
> > > +                           "Can't allocate iio device\n");
> > > +             return -ENOMEM;
> > > +     }
> > > +
> > > +     chip_info = iio_priv(indio_dev);
> > > +
> > > +     i2c_set_clientdata(client, indio_dev);  
> > 
> > Assuming you follow suggestion to go fully devm managed handling of
> > remove, I don't htink
> > you will need this.
> >   
> > > +     chip_info->client = client;
> > > +
> > > +     memset(&chip_info->chip_reg_data, 0, sizeof(chip_info-  
> > > >chip_reg_data));  
> > 
> > The iio_priv space is allocated with kzalloc so no need to zero here.
> >   
> > > +
> > > +     if (ACPI_HANDLE(&client->dev)) {
> > > +             pac193x_get_variant(chip_info);  
> > 
> > Why is this ACPI specific?  
> 
> Not really. I will change it to work in both cases.
> 
> > 
> > If you can query the variant from the hardware, that is the right
> > thing to use
> > for all registration types.  It can be helpful to call out if there
> > is
> > a mismatch though with a warning print, so comparing the result of
> > the i2c call with the data is fine.
> >   
> > > +     } else {
> > > +             dev_id = id->driver_data;
> > > +
> > > +             /* store the type of chip */
> > > +             chip_info->chip_variant = dev_id;
> > > +
> > > +             /* get the maximum number of channels for the given
> > > chip id */
> > > +             chip_info->phys_channels =
> > > pac193x_chip_config[dev_id].phys_channels;
> > > +     }
> > > +
> > > +     /*
> > > +      * load default settings - all channels disabled,
> > > +      * uni directional flow, default shunt values  
> > 
> > The concept of a default shunt value bothers me a little if this is
> > an external resistor.  How is there in any real sense a default?
> > Can we just fail if it's not provided - or wrap the default up for
> > just ACPI case where I guess there might be firmware that doesn't
> > specify it?  
> 
> I'm doing some math with that resistor later in code and I was just
> making sure that I will not divide by 0, but indeed I could just exit
> in case the value is not correctly set.

Exit is best thing to do I think.

Jonathan

> 
> >   
> 
> Thanks,
> Marius
>
marius.cristea@microchip.com March 23, 2023, 3:15 p.m. UTC | #10
On Sun, 2023-03-12 at 16:42 +0000, Jonathan Cameron wrote:
> EXTERNAL EMAIL: Do not click links or open attachments unless you
> know the content is safe
> 
> > 
> > > 
> > > 
> > > 
> > > > +
> > > > +     len += sprintf(buf, "%u\n", chip_info->shunts[target]);
> > > > +
> > > > +     return len;
> > > > +}
> > > > +
> > > > +static ssize_t channel_name_show(struct device *dev,
> > > 
> > > Looks like per channel label support provided by the read_label
> > > iio_info callback.
> > > 
> > 
> > I was trying to use the read_label iio_info callback but I end it
> > up
> > into a sysfs error related to duplicated labels.
> 
> That's interesting.  Can you provide more info on that. I'd like to
> understand why that's happening to you.
> 

I have add the ".read_label" and the function asociated with it to the
driver but here is the error:

root@sama5d27-wlsom1-ek-sd:~/work/pac193x# insmod pac1934.ko
pac193x 1-001c: :pac193x_prep_iio_channels: Channel 2 active
pac193x 1-001c: :pac193x_prep_iio_channels: Channel 3 active
pac193x 1-001c: :pac193x_prep_iio_channels: Channel 4 active
iio iio:device1: tried to double register : in_voltage2_label
pac193x 1-001c: Failed to register sysfs interfaces
iio iio:device1: error -EBUSY: Can't register IIO device
pac193x: probe of 1-001c failed with error -16



Here are the list of files in case I don't use read_label (only
physical channels 2 to 4 are available):

root@sama5d27-wlsom1-ek-sd:~/work/pac193x# ls
/sys/bus/iio/devices/iio\:device1/
channel_name_2        in_current4_mean_raw  in_power2_raw         
in_voltage2_scale     power
channel_name_3        in_current4_raw       in_power2_scale       
in_voltage3_mean_raw  reset_accumulators
channel_name_4        in_current4_scale     in_power3_raw         
in_voltage3_raw       sampling_frequency_available
in_current2_mean_raw  in_energy2_raw        in_power3_scale       
in_voltage3_scale     shunt_value_2
in_current2_raw       in_energy2_scale      in_power4_raw         
in_voltage4_mean_raw  shunt_value_3
in_current2_scale     in_energy3_raw        in_power4_scale       
in_voltage4_raw       shunt_value_4
in_current3_mean_raw  in_energy3_scale      in_sampling_frequency 
in_voltage4_scale     subsystem
in_current3_raw       in_energy4_raw        in_voltage2_mean_raw   name
uevent
in_current3_scale     in_energy4_scale      in_voltage2_raw       
of_node               waiting_for_supplier



> > 
> > Also I'm not sure if this read_label will help me in the end. What
> > I
> > was aiming to obtain here with the "channel_name_show" and the
> > custom
> > IIO attribute was to have "an umbrella" name/label for multiple IIO
> > channels. For example on physical "channel 1" we will measure the
> > voltage, the current and the power because all of those IIO
> > channels
> > will point to one board power supply (like VDD, VIO, V_GPU, V_DDR,
> > etc).
> 
> That should be possible with read_label as it's free form text.
> Perhaps I'm missing why that doesn't provide enough information for
> what
> you need.
> 
> > 

It seems it does more that I need. My concern is that the read_label
will do a label for all available channels (like _raw or scale_ will
do) and I want to  have just up to a maximum of 4 labels each one will
cover the multiple entries (e.g.: channel_name_2 will be a umbrela
"label" for:
in_power2_raw         in_voltage2_scale  in_power2_scale        
in_current2_mean_raw  in_energy2_raw     shunt_value_2
in_current2_raw       in_energy2_scale   in_current2_scale     
in_voltage2_mean_raw  in_voltage2_raw

> > 
> > 
> > 
> > 
> > > > +                              struct device_attribute *attr,
> > > > +                              char *buf)
> > > > +{
> > > > +     struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> > > > +     struct pac193x_chip_info *chip_info =
> > > > iio_priv(indio_dev);
> > > > +     int len = 0;
> > > > +     int target = (int)(attr->attr.name[strlen(attr-
> > > > >attr.name) -
> > > > 1] - '0') - 1;
> > > > +
> > > > +     len += sprintf(buf, "%s\n", chip_info-
> > > > > channel_names[target]);
> > > > +
> > > > +     return len;
> > > > +}
> > > > +
> > > > +static ssize_t shunt_value_store(struct device *dev,
> > > 
> > > Shunt values aren't normally dynamic.  Why do you need to write
> > > it
> > > from
> > > userspace? I'd expect that to come from firmware.
> > > 
> > > If it's needed the ext_info framework might be a better fit than
> > > direct implementation of the attribute.
> > 
> > 
> > Yes, usually the shunt aren't normally dynamic, but what I'm aiming
> > to
> > get here is to let the user to overwrite the value after a
> > calibration
> > of the system. This will improve the accuracy of the reading even
> > in
> > the case the shunts are not of high precision ones.
> 
> I'll go with maybe on this.  Perhaps not a feature for the initial
> version of the driver, but one that is better to discuss in a follow
> up thread along with details of the expected calibration process etc.
> > 

Calibration process could be easly be done. Just think that we have a
linux board that measure some dinamic load that uses a connector. The
load could be easily changed by the user with a "calibrated" load, read
the measurement do the math expected versus read values and update the
shunt value. Those values needs to be stored by the user somewhere to
be reused after a reboot. After the board is calibrated a "normal" load
could pluged in (one example is to monitor the charge/discharge current
of a battery).


> > 
> > > 
> > 
> > > 
> > > > shunt_value_store, 0);
> > > > +static IIO_DEVICE_ATTR(shunt_value_2, 0644, shunt_value_show,
> > > > shunt_value_store, 0);
> > > > +static IIO_DEVICE_ATTR(shunt_value_3, 0644, shunt_value_show,
> > > > shunt_value_store, 0);
> > > > +static IIO_DEVICE_ATTR(shunt_value_4, 0644, shunt_value_show,
> > > > shunt_value_store, 0);
> > > > +
> > > > +static IIO_DEVICE_ATTR(channel_name_1, 0444,
> > > > channel_name_show,
> > > > NULL, 0);
> > > > +static IIO_DEVICE_ATTR(channel_name_2, 0444,
> > > > channel_name_show,
> > > > NULL, 0);
> > > > +static IIO_DEVICE_ATTR(channel_name_3, 0444,
> > > > channel_name_show,
> > > > NULL, 0);
> > > > +static IIO_DEVICE_ATTR(channel_name_4, 0444,
> > > > channel_name_show,
> > > > NULL, 0);
> > > > +
> > > > +static IIO_DEVICE_ATTR(reset_accumulators, 0200, NULL,
> > > > reset_accumulators_store, 0);
> > > > +
> > > > +static struct attribute *pac193x_all_attributes[] = {
> > > > +     PAC193X_DEV_ATTR(shunt_value_1),
> > > 
> > > These all need ABI documentation so that we can easily review
> > > what
> > > they do.
> > > Documenation/ABI/testing/sysfs-bus-iio-pac1931
> > > Note that if they overlap with ABI used elsewhere we may need to
> > > move
> > > it to a more
> > > generic place (there can't be two lots of docs for the same ABI
> > > element)
> > > 
> > 
> > I will add comments into the code. The "shunt_value_show" will
> > print
> > the shunt value used to calculate current and power.
> I'd prefer handling shut calibration as a future patch because it
> needs
> some thought and discussion + may delay the rest of the driver. 
> Without that
> ability to tweak it I'm not sure it provides value to be able to read
> it now.
> 
> > The "channel_name_show" is the name of the device channel used to
> > easily identify what we are measuring (V_DDR, V_GPU, V_IO, etc) and
> > it's an "umbrella" for multiple IIO measurements (voltage, current
> > and
> > power)
> 
> As above. I'd like to think a bit more on this one.
> 

OK.


> > 
> > "reset_accumulators" will help the user to reinitialize the power
> > on
> > all channels.
> 
> This sounds like the rare case where the ENABLE IIO ABI may fit.
> We use that for things like step counters that also accumulate over
> time.  If we can use that, I'd prefer it to custom ABI.
> 
> > 
> > 
> > > > +     if (!indio_dev) {
> > > > +             dev_err_probe(&indio_dev->dev,
> > > > PTR_ERR(indio_dev),
> > > > +                           "Can't allocate iio device\n");
> > > > +             return -ENOMEM;
> > > > +     }
> > > > +
> > > > +     chip_info = iio_priv(indio_dev);
> > > > +
> > > > +     i2c_set_clientdata(client, indio_dev);
> > > 
> > > Assuming you follow suggestion to go fully devm managed handling
> > > of
> > > remove, I don't htink
> > > you will need this.
> > > 
> > > > +     chip_info->client = client;
> > > > +
> > > > +     memset(&chip_info->chip_reg_data, 0, sizeof(chip_info-
> > > > > chip_reg_data));
> > > 
> > > The iio_priv space is allocated with kzalloc so no need to zero
> > > here.
> > > 
> > > > +
> > > > +     if (ACPI_HANDLE(&client->dev)) {
> > > > +             pac193x_get_variant(chip_info);
> > > 
> > > Why is this ACPI specific?
> > 
> > Not really. I will change it to work in both cases.
> > 
> > > 
> > > If you can query the variant from the hardware, that is the right
> > > thing to use
> > > for all registration types.  It can be helpful to call out if
> > > there
> > > is
> > > a mismatch though with a warning print, so comparing the result
> > > of
> > > the i2c call with the data is fine.
> > > 
> > > > +     } else {
> > > > +             dev_id = id->driver_data;
> > > > +
> > > > +             /* store the type of chip */
> > > > +             chip_info->chip_variant = dev_id;
> > > > +
> > > > +             /* get the maximum number of channels for the
> > > > given
> > > > chip id */
> > > > +             chip_info->phys_channels =
> > > > pac193x_chip_config[dev_id].phys_channels;
> > > > +     }
> > > > +
> > > > +     /*
> > > > +      * load default settings - all channels disabled,
> > > > +      * uni directional flow, default shunt values
> > > 
> > > The concept of a default shunt value bothers me a little if this
> > > is
> > > an external resistor.  How is there in any real sense a default?
> > > Can we just fail if it's not provided - or wrap the default up
> > > for
> > > just ACPI case where I guess there might be firmware that doesn't
> > > specify it?
> > 
> > I'm doing some math with that resistor later in code and I was just
> > making sure that I will not divide by 0, but indeed I could just
> > exit
> > in case the value is not correctly set.
> 
> Exit is best thing to do I think.
> 
> 
OK.


> > 
Thanks,
Marius
Jonathan Cameron March 25, 2023, 6:06 p.m. UTC | #11
On Thu, 23 Mar 2023 15:15:18 +0000
<Marius.Cristea@microchip.com> wrote:

> On Sun, 2023-03-12 at 16:42 +0000, Jonathan Cameron wrote:
> > EXTERNAL EMAIL: Do not click links or open attachments unless you
> > know the content is safe
> >   
> > >   
> > > > 
> > > > 
> > > >   
> > > > > +
> > > > > +     len += sprintf(buf, "%u\n", chip_info->shunts[target]);
> > > > > +
> > > > > +     return len;
> > > > > +}
> > > > > +
> > > > > +static ssize_t channel_name_show(struct device *dev,  
> > > > 
> > > > Looks like per channel label support provided by the read_label
> > > > iio_info callback.
> > > >   
> > > 
> > > I was trying to use the read_label iio_info callback but I end it
> > > up
> > > into a sysfs error related to duplicated labels.  
> > 
> > That's interesting.  Can you provide more info on that. I'd like to
> > understand why that's happening to you.
> >   
> 
> I have add the ".read_label" and the function asociated with it to the
> driver but here is the error:
> 
> root@sama5d27-wlsom1-ek-sd:~/work/pac193x# insmod pac1934.ko
> pac193x 1-001c: :pac193x_prep_iio_channels: Channel 2 active
> pac193x 1-001c: :pac193x_prep_iio_channels: Channel 3 active
> pac193x 1-001c: :pac193x_prep_iio_channels: Channel 4 active
> iio iio:device1: tried to double register : in_voltage2_label
> pac193x 1-001c: Failed to register sysfs interfaces
> iio iio:device1: error -EBUSY: Can't register IIO device
> pac193x: probe of 1-001c failed with error -16
> 

There can only be one registered iio_chan_spec entry for a given
group of direction, type, channel, modifier (channel and modifier can
be optional). This comes from inherent design restrictions of the
events.  Now I think what has happened here is that you are
registering both _VBUS_CHANNEL and _VBUS_AVG channel with the
same index. You can't do that.  Whilst you don't currently have events,
if they come along later there will be no way to distinguish those
two.

So either roll them into one iio_chan_spec, or give them different
index values.

> 
> 
> Here are the list of files in case I don't use read_label (only
> physical channels 2 to 4 are available):
> 
> root@sama5d27-wlsom1-ek-sd:~/work/pac193x# ls
> /sys/bus/iio/devices/iio\:device1/
> channel_name_2        in_current4_mean_raw  in_power2_raw         
> in_voltage2_scale     power
> channel_name_3        in_current4_raw       in_power2_scale       
> in_voltage3_mean_raw  reset_accumulators
> channel_name_4        in_current4_scale     in_power3_raw         
> in_voltage3_raw       sampling_frequency_available
> in_current2_mean_raw  in_energy2_raw        in_power3_scale       
> in_voltage3_scale     shunt_value_2
> in_current2_raw       in_energy2_scale      in_power4_raw         
> in_voltage4_mean_raw  shunt_value_3
> in_current2_scale     in_energy3_raw        in_power4_scale       
> in_voltage4_raw       shunt_value_4
> in_current3_mean_raw  in_energy3_scale      in_sampling_frequency 
> in_voltage4_scale     subsystem
> in_current3_raw       in_energy4_raw        in_voltage2_mean_raw   name
> uevent
> in_current3_scale     in_energy4_scale      in_voltage2_raw       
> of_node               waiting_for_supplier
> 
> 
> 
> > > 
> > > Also I'm not sure if this read_label will help me in the end. What
> > > I
> > > was aiming to obtain here with the "channel_name_show" and the
> > > custom
> > > IIO attribute was to have "an umbrella" name/label for multiple IIO
> > > channels. For example on physical "channel 1" we will measure the
> > > voltage, the current and the power because all of those IIO
> > > channels
> > > will point to one board power supply (like VDD, VIO, V_GPU, V_DDR,
> > > etc).  
> > 
> > That should be possible with read_label as it's free form text.
> > Perhaps I'm missing why that doesn't provide enough information for
> > what
> > you need.
> >   
> > >   
> 
> It seems it does more that I need. My concern is that the read_label
> will do a label for all available channels (like _raw or scale_ will
> do) and I want to  have just up to a maximum of 4 labels each one will
> cover the multiple entries (e.g.: channel_name_2 will be a umbrela
> "label" for:
> in_power2_raw         in_voltage2_scale  in_power2_scale        
> in_current2_mean_raw  in_energy2_raw     shunt_value_2
> in_current2_raw       in_energy2_scale   in_current2_scale     
> in_voltage2_mean_raw  in_voltage2_raw

Whilst it might not be ideal, label is standard ABI that normal code
is going to know how to use.  channel_name_2 is custom ABI that only
code written against this one driver will use.

So go with the label version and don't worry about the duplication.

The association you are building between channel indexes is also unique
to this driver, so you should not rely on that as generic code will not
know that all the things numbered 2 refer to a particular set of inputs.

If each channel has a _label and those match, that association is explicit.

Channel numbers shouldn't really mean anything on their own.

> 
> > > 
> > > 
> > > 
> > >   
> > > > > +                              struct device_attribute *attr,
> > > > > +                              char *buf)
> > > > > +{
> > > > > +     struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> > > > > +     struct pac193x_chip_info *chip_info =
> > > > > iio_priv(indio_dev);
> > > > > +     int len = 0;
> > > > > +     int target = (int)(attr->attr.name[strlen(attr-  
> > > > > >attr.name) -  
> > > > > 1] - '0') - 1;
> > > > > +
> > > > > +     len += sprintf(buf, "%s\n", chip_info-  
> > > > > > channel_names[target]);  
> > > > > +
> > > > > +     return len;
> > > > > +}
> > > > > +
> > > > > +static ssize_t shunt_value_store(struct device *dev,  
> > > > 
> > > > Shunt values aren't normally dynamic.  Why do you need to write
> > > > it
> > > > from
> > > > userspace? I'd expect that to come from firmware.
> > > > 
> > > > If it's needed the ext_info framework might be a better fit than
> > > > direct implementation of the attribute.  
> > > 
> > > 
> > > Yes, usually the shunt aren't normally dynamic, but what I'm aiming
> > > to
> > > get here is to let the user to overwrite the value after a
> > > calibration
> > > of the system. This will improve the accuracy of the reading even
> > > in
> > > the case the shunts are not of high precision ones.  
> > 
> > I'll go with maybe on this.  Perhaps not a feature for the initial
> > version of the driver, but one that is better to discuss in a follow
> > up thread along with details of the expected calibration process etc.  
> > >   
> 
> Calibration process could be easly be done. Just think that we have a
> linux board that measure some dinamic load that uses a connector. The
> load could be easily changed by the user with a "calibrated" load, read
> the measurement do the math expected versus read values and update the
> shunt value. Those values needs to be stored by the user somewhere to
> be reused after a reboot. After the board is calibrated a "normal" load
> could pluged in (one example is to monitor the charge/discharge current
> of a battery).

OK. I'd still advise postponing control of this.  It's unusual and likely
to delay / distract from review of the rest of the driver.

Jonathan
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index c81bbb771678..6675dc1b6b19 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8187,6 +8187,13 @@  S:	Maintained
 F:	Documentation/ABI/testing/sysfs-driver-intel-m10-bmc-sec-update
 F:	drivers/fpga/intel-m10-bmc-sec-update.c
 
+MICROCHIP PAC193X POWER/ENERGY MONITOR DRIVER
+M:	Marius Cristea <marius.cristea@@microchip.com>
+L:	linux-iio@vger.kernel.org
+S:	Supported
+F:	Documentation/devicetree/bindings/iio/adc/microchip,pac193x.yaml
+F:	drivers/iio/adc/pac193x.c
+
 MICROCHIP POLARFIRE FPGA DRIVERS
 M:	Conor Dooley <conor.dooley@microchip.com>
 R:	Ivan Bornyakov <i.bornyakov@metrotek.ru>
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 4a95c843a91b..ae4f60e290c4 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -867,6 +867,18 @@  config NPCM_ADC
 	  This driver can also be built as a module. If so, the module
 	  will be called npcm_adc.
 
+config PAC193X
+	tristate "Microchip Technology PAC193X driver"
+	depends on I2C
+	depends on IIO
+	help
+	  Say yes here to build support for Microchip Technology's PAC1934,
+	  PAC1933, PAC1932, PAC1931 Single/Multi-Channel Power Monitor with
+	  Accumulator.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called pac193x.
+
 config PALMAS_GPADC
 	tristate "TI Palmas General Purpose ADC"
 	depends on MFD_PALMAS
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index 36c18177322a..81824128c6cd 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -80,6 +80,7 @@  obj-$(CONFIG_MP2629_ADC) += mp2629_adc.o
 obj-$(CONFIG_MXS_LRADC_ADC) += mxs-lradc-adc.o
 obj-$(CONFIG_NAU7802) += nau7802.o
 obj-$(CONFIG_NPCM_ADC) += npcm_adc.o
+obj-$(CONFIG_PAC193X) += pac193x.o
 obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o
 obj-$(CONFIG_QCOM_SPMI_ADC5) += qcom-spmi-adc5.o
 obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o
diff --git a/drivers/iio/adc/pac193x.c b/drivers/iio/adc/pac193x.c
new file mode 100644
index 000000000000..ece6bf028fe2
--- /dev/null
+++ b/drivers/iio/adc/pac193x.c
@@ -0,0 +1,2072 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * IIO driver for PAC193X Multi-Channel DC Power/Energy Monitor
+ *
+ * Copyright (C) 2017-2023 Microchip Technology Inc. and its subsidiaries
+ *
+ * Author: Bogdan Bolocan <bogdan.bolocan@microchip.com>
+ * Author: Victor Tudose
+ * Author: Marius Cristea <marius.cristea@microchip.com>
+ *
+ * Datasheet for PAC1931, PAC1932, PAC1933 and PAC1934 can be found here:
+ * https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ProductDocuments/DataSheets/PAC1931-Family-Data-Sheet-DS20005850E.pdf
+ *
+ */
+
+#include <linux/acpi.h>
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/init.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/math.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/timer.h>
+#include <linux/util_macros.h>
+
+/*
+ * maxim should be (17 * 60 * 1000) around 17 minutes@1024 sps
+ * till PAC193X register stars to saturate
+ */
+#define PAC193X_MAX_RFSH_LIMIT_MS		60000
+
+/* 50msec is the timeout for validity of the cached registers */
+#define PAC193X_MIN_POLLING_TIME_MS		50
+
+#define SHUNT_UOHMS_DEFAULT			100000
+/* 32000mV */
+#define PAC193X_VOLTAGE_MILLIVOLTS_MAX		32000
+/* voltage bits resolution when set for unsigned values */
+#define PAC193X_VOLTAGE_U_RES			16
+/* voltage bits resolution when set for signed values */
+#define PAC193X_VOLTAGE_S_RES			15
+/* 100mV maximum for current shunts */
+#define PAC193X_VSENSE_MILLIVOLTS_MAX		100
+/* voltage bits resolution when set for unsigned values */
+#define PAC193X_CURRENT_U_RES			16
+/* voltage bits resolution when set for signed values */
+#define PAC193X_CURRENT_S_RES			15
+
+/* power resolution is 28 bits when unsigned */
+#define PAC193X_POWER_U_RES			 28
+/* power resolution is 27 bits when signed */
+#define PAC193X_POWER_S_RES			27
+
+/* energy accumulation is 48 bits long */
+#define PAC193X_ENERGY_U_RES			48
+
+#define PAC193X_ENERGY_S_RES			47
+#define PAC193X_ENERGY_SHIFT_MAIN_VAL		32
+
+#define BIT_INDEX_31				31
+
+/*
+ * max signed value that can be stored on 32 bits and 8 digits fractional value
+ * (2^31 - 1) * 10^8 + 99999999
+ */
+#define PAC_193X_MAX_POWER_ACC			214748364799999999LL
+/*
+ * min signed value that can be stored on 32 bits and 8 digits fractional value
+ * -(2^31) * 10^8 - 99999999
+ */
+#define PAC_193X_MIN_POWER_ACC			-214748364899999999LL
+
+/* maximum value that device can measure - 32 V * 0.1 V */
+#define PAC193X_PRODUCT_VOLTAGE_PV_FSR		3200000000000UL
+
+#define PAC193X_MAX_NUM_CHANNELS		4
+#define PAC1931_NUM_CHANNELS			1
+#define PAC1932_NUM_CHANNELS			2
+#define PAC1933_NUM_CHANNELS			3
+#define PAC1934_NUM_CHANNELS			4
+#define PAC193X_CH_1				0
+#define PAC193X_CH_2				1
+#define PAC193X_CH_3				2
+#define PAC193X_CH_4				3
+#define PAC193X_MEAS_REG_LEN			76
+#define PAC193X_CTRL_REG_LEN			12
+
+/*
+ * 1000usec is the minimum wait time for normal conversions when sample
+ * rate doesn't change
+ */
+#define PAC193X_MIN_UPDATE_WAIT_TIME_MS		1000
+
+#define PAC193X_DEFAULT_CHIP_SAMP_SPEED		1024
+
+/* I2C address map */
+#define PAC193X_REFRESH_REG			0x00
+#define PAC193X_CTRL_REG			0x01
+#define PAC193X_REFRESH_V_REG			0x1F
+#define PAC193X_ACC_COUNT_REG			0x02
+#define PAC193X_CTRL_STAT_REGS_ADDR		0x1C
+#define PAC193X_PID_REG_ADDR			0xFD
+#define PAC193X_MID_REG_ADDR			0xFE
+#define PAC193X_RID_REG_ADDR			0xFF
+
+/* PRODUCT ID REGISTER + MANUFACTURER ID REGISTER + REVISION ID REGISTER */
+#define PAC193X_ID_REG_LEN			0x03
+#define PAC193X_PID_IDX				0
+#define PAC193X_MID_IDX				1
+#define PAC193X_RID_IDX				2
+
+#define PAC193X_ACPI_ARG_COUNT			4
+#define PAC193X_ACPI_GET_NAMES_AND_MOHMS_VALS	1
+#define PAC193X_ACPI_GET_UOHMS_VALS		2
+#define PAC193X_ACPI_GET_BIPOLAR_SETTINGS	4
+#define PAC193X_ACPI_GET_SAMP			5
+
+#define PAC193X_VPOWER_ACC_1_ADDR		0x03
+#define PAC193X_VPOWER_ACC_2_ADDR		0x04
+#define PAC193X_VPOWER_ACC_3_ADDR		0x05
+#define PAC193X_VPOWER_ACC_4_ADDR		0x06
+#define PAC193X_VBUS_1_ADDR			0x07
+#define PAC193X_VBUS_2_ADDR			0x08
+#define PAC193X_VBUS_3_ADDR			0x09
+#define PAC193X_VBUS_4_ADDR			0x0A
+#define PAC193X_VSENSE_1_ADDR			0x0B
+#define PAC193X_VSENSE_2_ADDR			0x0C
+#define PAC193X_VSENSE_3_ADDR			0x0D
+#define PAC193X_VSENSE_4_ADDR			0x0E
+#define PAC193X_VBUS_AVG_1_ADDR			0x0F
+#define PAC193X_VBUS_AVG_2_ADDR			0x10
+#define PAC193X_VBUS_AVG_3_ADDR			0x11
+#define PAC193X_VBUS_AVG_4_ADDR			0x12
+#define PAC193X_VSENSE_AVG_1_ADDR		0x13
+#define PAC193X_VSENSE_AVG_2_ADDR		0x14
+#define PAC193X_VSENSE_AVG_3_ADDR		0x15
+#define PAC193X_VSENSE_AVG_4_ADDR		0x16
+#define PAC193X_VPOWER_1_ADDR			0x17
+#define PAC193X_VPOWER_2_ADDR			0x18
+#define PAC193X_VPOWER_3_ADDR			0x19
+#define PAC193X_VPOWER_4_ADDR			0x1A
+
+#define PAC193X_SAMPLE_RATE_SHIFT		6
+
+/*
+ * these indexes are exactly describing the element order within a single
+ * PAC193X phys channel IIO channel descriptor; see the static const struct
+ * iio_chan_spec pac193x_single_channel[] declaration
+ */
+#define IIO_EN					0
+#define IIO_POW					1
+#define IIO_VOLT				2
+#define IIO_CRT					3
+#define IIO_VOLTAVG				4
+#define IIO_CRTAVG				5
+
+#define PAC193X_VBUS_SENSE_REG_LEN		2
+#define PAC193X_ACC_REG_LEN			3
+#define PAC193X_VPOWER_REG_LEN			4
+#define PAC193X_VPOWER_ACC_REG_LEN		6
+#define PAC193X_MAX_REGISTER_LENGTH		6
+
+#define PAC193X_CUSTOM_ATTR_FOR_CHANNEL		2
+#define PAC193X_SHARED_DEVATTRS_COUNT		1
+
+/*
+ * relative offsets when using multi-byte reads/writes even though these
+ * bytes are read one after the other, they are not at adjacent memory
+ * locations within the I2C memory map. The chip can skip some addresses
+ */
+#define PAC193X_CHANNEL_DIS_REG_OFF		0
+#define PAC193X_NEG_PWR_REG_OFF			1
+
+/*
+ * when reading/writing multiple bytes from offset PAC193X_CHANNEL_DIS_REG_OFF,
+ * the chip jumps over the 0x1E (REFRESH_G) and 0x1F (REFRESH_V) offsets
+ */
+#define PAC193X_SLOW_REG_OFF			2
+#define PAC193X_CTRL_ACT_REG_OFF		3
+#define PAC193X_CHANNEL_DIS_ACT_REG_OFF		4
+#define PAC193X_NEG_PWR_ACT_REG_OFF		5
+#define PAC193X_CTRL_LAT_REG_OFF		6
+#define PAC193X_CHANNEL_DIS_LAT_REG_OFF		7
+#define PAC193X_NEG_PWR_LAT_REG_OFF		8
+#define PAC193X_PID_REG_OFF			9
+#define PAC193X_MID_REG_OFF			10
+#define PAC193X_REV_REG_OFF			11
+#define PAC193X_CTRL_STATUS_INFO_LEN		12
+
+#define PAC193X_MID				0x5D
+#define PAC1934_PID				0x5B
+#define PAC1933_PID				0x5A
+#define PAC1932_PID				0x59
+#define PAC1931_PID				0x58
+
+/* Scale constant = (10^8 / 2^28) * 3.2 * 10^9 */
+#define PAC193X_SCALE_CONSTANT			1192092895UL
+
+#define PAC193X_MAX_VPOWER_RSHIFTED_BY_28B	11921
+#define PAC193X_MAX_VSENSE_RSHIFTED_BY_16B	1525
+
+#define PAC193X_DEV_ATTR(name) (&iio_dev_attr_##name.dev_attr.attr)
+
+#define PAC193X_CRTL_SAMPLE_RATE_MASK	GENMASK(7, 6)
+#define PAC193X_CRTL_SAMPLE_RATE_SET(x)	((u8)FIELD_PREP(PAC193X_CRTL_SAMPLE_RATE_MASK, (x)))
+#define PAC193X_CHAN_SLEEP_MASK		BIT(5)
+#define PAC193X_CHAN_SLEEP_SET		BIT(5)
+#define PAC193X_CHAN_SINLE_MASK		BIT(4)
+#define PAC193X_CHAN_SINLE_SHOT_SET	BIT(4)
+#define PAC193X_CHAN_ALERT_MASK		BIT(3)
+#define PAC193X_CHAN_ALERT_EN		BIT(3)
+#define PAC193X_CHAN_ALERT_CC_MASK	BIT(2)
+#define PAC193X_CHAN_ALERT_CC_EN	BIT(2)
+#define PAC193X_CHAN_OVF_ALERT_MASK	BIT(1)
+#define PAC193X_CHAN_OVF_ALERT_EN	BIT(1)
+#define PAC193X_CHAN_OVF_MASK		BIT(0)
+
+#define PAC193X_CHAN_DIS_CH1_OFF_MASK	BIT(7)
+#define PAC193X_CHAN_DIS_CH1_OFF(x)	((u8)FIELD_PREP(PAC193X_CHAN_DIS_CH1_OFF_MASK, !(x)))
+#define PAC193X_CHAN_DIS_CH2_OFF_MASK	BIT(6)
+#define PAC193X_CHAN_DIS_CH2_OFF(x)	((u8)FIELD_PREP(PAC193X_CHAN_DIS_CH2_OFF_MASK, !(x)))
+#define PAC193X_CHAN_DIS_CH3_OFF_MASK	BIT(5)
+#define PAC193X_CHAN_DIS_CH3_OFF(x)	((u8)FIELD_PREP(PAC193X_CHAN_DIS_CH3_OFF_MASK, !(x)))
+#define PAC193X_CHAN_DIS_CH4_OFF_MASK	BIT(4)
+#define PAC193X_CHAN_DIS_CH4_OFF(x)	((u8)FIELD_PREP(PAC193X_CHAN_DIS_CH4_OFF_MASK, !(x)))
+#define PAC193X_SMBUS_TIMEOUT_MASK	BIT(3)
+#define PAC193X_SMBUS_TIMEOUT_EN(x)	FIELD_PREP(PAC193X_SMBUS_TIMEOUT_MASK, x)
+#define PAC193X_SMBUS_BYTECOUNT_MASK	BIT(2)
+#define PAC193X_SMBUS_BYTECOUNT_EN(x)	FIELD_PREP(PAC193X_SMBUS_BYTECOUNT_MASK, x)
+#define PAC193X_SMBUS_NO_SKIP_MASK	BIT(1)
+#define PAC193X_SMBUS_NO_SKIP_EN(x)	FIELD_PREP(PAC193X_SMBUS_NO_SKIP_MASK, x)
+
+#define PAC193X_NEG_PWR_CH1_BIDI(x)	((x) ? BIT(7) : 0)
+#define PAC193X_NEG_PWR_CH2_BIDI(x)	((x) ? BIT(6) : 0)
+#define PAC193X_NEG_PWR_CH3_BIDI(x)	((x) ? BIT(5) : 0)
+#define PAC193X_NEG_PWR_CH4_BIDI(x)	((x) ? BIT(4) : 0)
+#define PAC193X_NEG_PWR_CH1_BIDV(x)	((x) ? BIT(3) : 0)
+#define PAC193X_NEG_PWR_CH2_BIDV(x)	((x) ? BIT(2) : 0)
+#define PAC193X_NEG_PWR_CH3_BIDV(x)	((x) ? BIT(1) : 0)
+#define PAC193X_NEG_PWR_CH4_BIDV(x)	((x) ? BIT(0) : 0)
+
+/*
+ * Universal Unique Identifier (UUID),
+ * 033771E0-1705-47B4-9535-D1BBE14D9A09, is
+ * reserved to Microchip for the PAC193X and must not be changed
+ */
+#define PAC193X_DSM_UUID		"033771E0-1705-47B4-9535-D1BBE14D9A09"
+
+enum pac193x_ids {
+	pac1934,
+	pac1933,
+	pac1932,
+	pac1931
+};
+
+enum pac193x_samps {
+	pac193X_samp_1024sps,
+	pac193X_samp_256sps,
+	pac193X_samp_64sps,
+	pac193X_samp_8sps
+};
+
+/**
+ * struct pac193x_features - features of a pac193x instance
+ * @phys_channels: number of physical channels supported by the chip
+ * @prod_id: product ID
+ */
+struct pac193x_features {
+	u8 phys_channels;
+	u8 prod_id;
+};
+
+struct samp_rate_mapping {
+	u16 samp_rate;
+	u8 shift2value;
+};
+
+static const unsigned int samp_rate_map_tbl[] = {
+	[pac193X_samp_1024sps] = 1024,
+	[pac193X_samp_256sps] = 256,
+	[pac193X_samp_64sps] = 64,
+	[pac193X_samp_8sps] = 8,
+};
+
+static const struct pac193x_features pac193x_chip_config[] = {
+	[pac1934] = {
+	    .phys_channels = PAC193X_MAX_NUM_CHANNELS,
+	    .prod_id = PAC1934_PID,
+	},
+	[pac1933] = {
+	    .phys_channels = PAC193X_MAX_NUM_CHANNELS - 1,
+	    .prod_id = PAC1933_PID,
+	},
+	[pac1932] = {
+	    .phys_channels = PAC193X_MAX_NUM_CHANNELS - 2,
+	    .prod_id = PAC1932_PID,
+	},
+	[pac1931] = {
+	    .phys_channels = PAC193X_MAX_NUM_CHANNELS - 3,
+	    .prod_id = PAC1931_PID,
+	},
+};
+
+/**
+ * struct reg_data - data from the registers
+ * @meas_regs: snapshot of raw measurements registers
+ * @ctrl_regs: snapshot of control registers
+ * @energy_sec_acc: snapshot of energy values
+ * @vpower_acc: accumulated vpower values
+ * @vpower: snapshot of vpower registers
+ * @vbus: snapshot of vbus registers
+ * @vbus_avg: averages of vbus registers
+ * @vsense: snapshot of vsense registers
+ * @vsense_avg: averages of vsense registers
+ * @num_enabled_channels: count of how many chip channels are currently enabled
+ */
+struct reg_data {
+	u8	meas_regs[PAC193X_MEAS_REG_LEN];
+	u8	ctrl_regs[PAC193X_CTRL_REG_LEN];
+	s64	energy_sec_acc[PAC193X_MAX_NUM_CHANNELS];
+	s64	vpower_acc[PAC193X_MAX_NUM_CHANNELS];
+	s32	vpower[PAC193X_MAX_NUM_CHANNELS];
+	s32	vbus[PAC193X_MAX_NUM_CHANNELS];
+	s32	vbus_avg[PAC193X_MAX_NUM_CHANNELS];
+	s32	vsense[PAC193X_MAX_NUM_CHANNELS];
+	s32	vsense_avg[PAC193X_MAX_NUM_CHANNELS];
+	u8	num_enabled_channels;
+};
+
+/**
+ * struct pac193x_chip_info - information about the chip
+ * @client: the i2c-client attached to the device
+ * @lock: lock used by the chip's mutex
+ * @tmr_forced_update: timers used
+ * @wq_chip: queued work used for refresh commands
+ * @work_chip_rfsh: work used for refresh commands
+ * @phys_channels: phys channels count
+ * @active_channels: array of values, true means that channel is active
+ * @bi_dir: array of bools, true means that channel is bidirectional
+ * @chip_variant: chip variant
+ * @chip_revision: chip revision
+ * @shunts: shunts
+ * @chip_reg_data: chip reg data
+ * @sample_rate_value: sampling frequency
+ * @channel_names: channel names
+ * @pac193x_info: pac193x_info
+ * @jiffies_tstamp: chip's uptime
+ * @crt_samp_spd_bitfield:the current sampling speed
+ */
+struct pac193x_chip_info {
+	struct i2c_client	*client;
+	struct mutex		lock; /*lock to prevent concurent reads/writes */
+	struct timer_list	tmr_forced_update;
+	struct workqueue_struct *wq_chip;
+	struct work_struct	work_chip_rfsh;
+	u8			phys_channels;
+	bool			active_channels[PAC193X_MAX_NUM_CHANNELS];
+	bool			bi_dir[PAC193X_MAX_NUM_CHANNELS];
+	u8			chip_variant;
+	u8			chip_revision;
+	u32			shunts[PAC193X_MAX_NUM_CHANNELS];
+	struct reg_data		chip_reg_data;
+	s32			sample_rate_value;
+	char			*channel_names[PAC193X_MAX_NUM_CHANNELS];
+	u8			crt_samp_spd_bitfield;
+	unsigned long		jiffies_tstamp;
+	struct iio_info		pac193x_info;
+};
+
+struct __packed pac193x_uuid_format {
+	u32	data1;
+	u16	data2;
+	u16	data3;
+	u8	data4[8];
+};
+
+#define to_pac193x_chip_info(d) container_of(d, struct pac193x_chip_info, work_chip_rfsh)
+
+/* macros to extract the parameters */
+#define MVACCS(x)		sign_extend64((u64)(x), 47)
+#define MVPOWERS(x)		sign_extend32((u32)(x), 27)
+#define MVBUS_SENSES(x)		sign_extend32((u32)(x), 15)
+
+#define PAC193X_VPOWER_ACC_CHANNEL(_index, _si, _address) {			\
+	.type = IIO_ENERGY,							\
+	.address = (_address),							\
+	.indexed = 1,								\
+	.channel = (_index),							\
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW)	|			\
+				BIT(IIO_CHAN_INFO_SCALE),			\
+	.info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ),		\
+	.info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),	\
+	.scan_index = (_si),							\
+	.scan_type = {								\
+		.sign = 'u',							\
+		.realbits = PAC193X_ENERGY_U_RES,				\
+		.storagebits = PAC193X_ENERGY_U_RES,				\
+		.shift = 0,							\
+		.endianness = IIO_CPU,						\
+	}									\
+}
+
+#define PAC193X_VBUS_CHANNEL(_index, _si, _address) {				\
+	.type = IIO_VOLTAGE,							\
+	.address = (_address),							\
+	.indexed = 1,								\
+	.channel = (_index),							\
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),				\
+	.info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ),		\
+	.info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),	\
+	.scan_index = (_si),							\
+	.scan_type = {								\
+		.sign = 'u',							\
+		.realbits = PAC193X_VOLTAGE_U_RES,				\
+		.storagebits = PAC193X_VOLTAGE_U_RES,				\
+		.shift = 0,							\
+		.endianness = IIO_CPU,						\
+	}									\
+}
+
+#define PAC193X_VBUS_AVG_CHANNEL(_index, _si, _address) {			\
+	.type = IIO_VOLTAGE,							\
+	.address = (_address),							\
+	.indexed = 1,								\
+	.channel = (_index),							\
+	.info_mask_separate = BIT(IIO_CHAN_INFO_AVERAGE_RAW)			\
+				| BIT(IIO_CHAN_INFO_SCALE),			\
+	.info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ),		\
+	.info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),	\
+	.scan_index = (_si),							\
+	.scan_type = {								\
+		.sign = 'u',							\
+		.realbits = PAC193X_VOLTAGE_U_RES,				\
+		.storagebits = PAC193X_VOLTAGE_U_RES,				\
+		.shift = 0,							\
+		.endianness = IIO_CPU,						\
+	}									\
+}
+
+#define PAC193X_VSENSE_CHANNEL(_index, _si, _address) {				\
+	.type = IIO_CURRENT,							\
+	.address = (_address),							\
+	.indexed = 1,								\
+	.channel = (_index),							\
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),				\
+	.info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ),		\
+	.info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),	\
+	.scan_index = (_si),							\
+	.scan_type = {								\
+		.sign = 'u',							\
+		.realbits = PAC193X_CURRENT_U_RES,				\
+		.storagebits = PAC193X_CURRENT_U_RES,				\
+		.shift = 0,							\
+		.endianness = IIO_CPU,						\
+	}									\
+}
+
+#define PAC193X_VSENSE_AVG_CHANNEL(_index, _si, _address) {			\
+	.type = IIO_CURRENT,							\
+	.address = (_address),							\
+	.indexed = 1,								\
+	.channel = (_index),							\
+	.info_mask_separate = BIT(IIO_CHAN_INFO_AVERAGE_RAW) |			\
+			BIT(IIO_CHAN_INFO_SCALE),				\
+	.info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ),		\
+	.info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),	\
+	.scan_index = (_si),							\
+	.scan_type = {								\
+		.sign = 'u',							\
+		.realbits = PAC193X_CURRENT_U_RES,				\
+		.storagebits = PAC193X_CURRENT_U_RES,				\
+		.shift = 0,							\
+		.endianness = IIO_CPU,						\
+	}									\
+}
+
+#define PAC193X_VPOWER_CHANNEL(_index, _si, _address) {				\
+	.type = IIO_POWER,							\
+	.address = (_address),							\
+	.indexed = 1,								\
+	.channel = (_index),							\
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |				\
+			BIT(IIO_CHAN_INFO_SCALE),				\
+	.info_mask_shared_by_dir = BIT(IIO_CHAN_INFO_SAMP_FREQ),		\
+	.info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),	\
+	.scan_index = (_si),							\
+	.scan_type = {								\
+		.sign = 'u',							\
+		.realbits = PAC193X_POWER_U_RES,				\
+		.storagebits = 32,						\
+		.shift = 4,							\
+		.endianness = IIO_CPU,						\
+	}									\
+}
+
+#define PAC193X_SOFT_TIMESTAMP(_index) {					\
+	.type = IIO_TIMESTAMP,							\
+	.channel = -1,								\
+	.scan_index = (_index),							\
+}
+
+static const struct iio_chan_spec pac193x_single_channel[] = {
+	PAC193X_VPOWER_ACC_CHANNEL(0, 0, PAC193X_VPOWER_ACC_1_ADDR),
+	PAC193X_VPOWER_CHANNEL(0, 0, PAC193X_VPOWER_1_ADDR),
+	PAC193X_VBUS_CHANNEL(0, 0, PAC193X_VBUS_1_ADDR),
+	PAC193X_VSENSE_CHANNEL(0, 0, PAC193X_VSENSE_1_ADDR),
+	PAC193X_VBUS_AVG_CHANNEL(0, 0, PAC193X_VBUS_AVG_1_ADDR),
+	PAC193X_VSENSE_AVG_CHANNEL(0, 0, PAC193X_VSENSE_AVG_1_ADDR),
+};
+
+static const struct iio_chan_spec pac193x_ts[] = {
+	PAC193X_SOFT_TIMESTAMP(0),
+};
+
+static inline u64 pac193x_get_mvaccu(u8 *addr)
+{
+	return (((u64)(*(u8 *)((addr) + 0)) << 40) |
+		((u64)(*(u8 *)((addr) + 1)) << 32) |
+		((u64)(*(u8 *)((addr) + 2)) << 24) |
+		((u64)(*(u8 *)((addr) + 3)) << 16) |
+		((u64)(*(u8 *)((addr) + 4)) << 8)  |
+		((u64)(*(u8 *)((addr) + 5)) << 0));
+}
+
+static inline u32 pac193x_get_vpoweru(u8 *addr)
+{
+	return (((u32)(*(u8 *)((addr) + 0)) << 20) |
+		((u32)(*(u8 *)((addr) + 1)) << 12) |
+		((u32)(*(u8 *)((addr) + 2)) << 4) |
+		((u32)(*(u8 *)((addr) + 3)) >> 4));
+}
+
+static inline u16 pac193x_get_mvbus_senseu(u8 *addr)
+{
+	u16 tmp;
+
+	tmp = (u16)(*(u16 *)(addr));
+	be16_to_cpus(&tmp);
+
+	return tmp;
+}
+
+/* Low-level I2c functions */
+static int pac193x_i2c_read(struct i2c_client *client, u8 reg_addr, void *databuf, u8 len)
+{
+	int ret;
+	struct i2c_msg msgs[2] = {
+		{ .addr = client->addr,
+		 .len = 1,
+		 .buf = (u8 *)&reg_addr,
+		 .flags = 0
+		},
+		{ .addr = client->addr,
+		 .len = len,
+		 .buf = databuf,
+		 .flags = I2C_M_RD
+		}
+	};
+
+	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int pac193x_i2c_write_byte(struct i2c_client *client, u8 reg_addr, u8 val)
+{
+	int ret;
+	u8 buf[2];
+	struct i2c_msg msg = {
+		.addr = client->addr,
+		.len = sizeof(buf),
+		.buf = (u8 *)&buf,
+		.flags = 0
+	};
+
+	buf[0] = reg_addr;
+	buf[1] = val;
+
+	ret = i2c_transfer(client->adapter, &msg, 1);
+	if (ret < 0) {
+		dev_err(&client->dev,
+			"failed writing register 0x%02X\n", reg_addr);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int pac193x_i2c_send_byte(struct i2c_client *client, u8 reg_addr)
+{
+	int ret;
+	u8 buf;
+	struct i2c_msg msg = {
+		.addr = client->addr,
+		.len = sizeof(buf),
+		.buf = (u8 *)&buf,
+		.flags = 0
+	};
+
+	buf = reg_addr;
+	ret = i2c_transfer(client->adapter, &msg, 1);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int pac193x_i2c_write(struct i2c_client *client, u8 reg_addr, u8 *data, int len)
+{
+	int ret;
+	u8 send[PAC193X_MAX_REGISTER_LENGTH + 1];
+	struct i2c_msg msg = {
+		.addr = client->addr,
+		.len = len + 1,
+		.flags = 0
+	};
+
+	send[0] = reg_addr;
+	memcpy(&send[1], data, len * sizeof(u8));
+	msg.buf = send;
+
+	ret = i2c_transfer(client->adapter, &msg, 1);
+	if (ret < 0) {
+		dev_err(&client->dev,
+			"failed writing data from register 0x%02X\n", reg_addr);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int pac193x_match_samp_rate(struct pac193x_chip_info *chip_info, u32 new_samp_rate)
+{
+	int cnt;
+
+	for (cnt = 0; cnt < ARRAY_SIZE(samp_rate_map_tbl); cnt++) {
+		if (new_samp_rate == samp_rate_map_tbl[cnt]) {
+			chip_info->crt_samp_spd_bitfield = cnt;
+			break;
+		}
+	}
+
+	if (cnt == ARRAY_SIZE(samp_rate_map_tbl))
+		/* not a valid sample rate value */
+		return -EINVAL;
+
+	return 0;
+}
+
+static ssize_t shunt_value_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct pac193x_chip_info *chip_info = iio_priv(indio_dev);
+	int len = 0;
+	int target = (int)(attr->attr.name[strlen(attr->attr.name) - 1] - '0') - 1;
+
+	len += sprintf(buf, "%u\n", chip_info->shunts[target]);
+
+	return len;
+}
+
+static ssize_t channel_name_show(struct device *dev,
+				 struct device_attribute *attr,
+				 char *buf)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct pac193x_chip_info *chip_info = iio_priv(indio_dev);
+	int len = 0;
+	int target = (int)(attr->attr.name[strlen(attr->attr.name) - 1] - '0') - 1;
+
+	len += sprintf(buf, "%s\n", chip_info->channel_names[target]);
+
+	return len;
+}
+
+static ssize_t shunt_value_store(struct device *dev,
+				 struct device_attribute *attr,
+				 const char *buf, size_t count)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct pac193x_chip_info *chip_info = iio_priv(indio_dev);
+	int sh_val, target;
+
+	target = (int)(attr->attr.name[strlen(attr->attr.name) - 1] - '0') - 1;
+	if (kstrtouint(buf, 10, &sh_val)) {
+		dev_err(dev, "Shunt value is not valid\n");
+		return -EINVAL;
+	}
+
+	mutex_lock(&chip_info->lock);
+	chip_info->shunts[target] = sh_val;
+	mutex_unlock(&chip_info->lock);
+
+	return count;
+}
+
+static int pac193x_read_avail(struct iio_dev *indio_dev,
+			      struct iio_chan_spec const *channel,
+			      const int **vals, int *type, int *length, long mask)
+{
+	switch (mask) {
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		*type = IIO_VAL_INT;
+		*vals = samp_rate_map_tbl;
+		*length = ARRAY_SIZE(samp_rate_map_tbl);
+		return IIO_AVAIL_LIST;
+	}
+
+	return -EINVAL;
+}
+
+static int pac193x_send_refresh(struct pac193x_chip_info *chip_info,
+				bool refresh_v, u32 wait_time)
+{
+	/* this function only sends REFRESH or REFRESH_V */
+	struct i2c_client *client = chip_info->client;
+	int ret;
+	u8 refresh_cmd, bidir_reg;
+	bool revision_bug = false;
+
+	if (chip_info->chip_revision == 2 || chip_info->chip_revision == 3) {
+		/*
+		 *chip rev 2 and 3 bug workaround
+		 * see: PAC193X Family Data Sheet Errata DS80000836A.pdf
+		 */
+		revision_bug = true;
+
+		bidir_reg = PAC193X_NEG_PWR_CH1_BIDI(chip_info->bi_dir[PAC193X_CH_1]) |
+			PAC193X_NEG_PWR_CH2_BIDI(chip_info->bi_dir[PAC193X_CH_2]) |
+			PAC193X_NEG_PWR_CH3_BIDI(chip_info->bi_dir[PAC193X_CH_3]) |
+			PAC193X_NEG_PWR_CH4_BIDI(chip_info->bi_dir[PAC193X_CH_4]) |
+			PAC193X_NEG_PWR_CH1_BIDV(chip_info->bi_dir[PAC193X_CH_1]) |
+			PAC193X_NEG_PWR_CH2_BIDV(chip_info->bi_dir[PAC193X_CH_2]) |
+			PAC193X_NEG_PWR_CH3_BIDV(chip_info->bi_dir[PAC193X_CH_3]) |
+			PAC193X_NEG_PWR_CH4_BIDV(chip_info->bi_dir[PAC193X_CH_4]);
+
+		/* write the updated registers back */
+		ret = pac193x_i2c_write_byte(client,
+					     PAC193X_CTRL_STAT_REGS_ADDR +
+					     PAC193X_NEG_PWR_REG_OFF,
+					     bidir_reg);
+		if (ret)
+			return ret;
+	}
+
+	/*
+	 * if refresh_v is not false, send REFRESH_V instead of REFRESH
+	 * (doesn't reset the accumulators)
+	 */
+	if (refresh_v)
+		refresh_cmd = PAC193X_REFRESH_V_REG;
+	else
+		refresh_cmd = PAC193X_REFRESH_REG;
+
+	ret = pac193x_i2c_send_byte(client, refresh_cmd);
+	if (ret) {
+		dev_err(&client->dev, "%s - cannot send 0x%02X\n",
+			__func__, refresh_cmd);
+		return ret;
+	}
+
+	if (revision_bug) {
+		/*
+		 * chip rev 2 and 3 bug workaround - write again the same register
+		 * write the updated registers back
+		 */
+		ret = pac193x_i2c_write_byte(client,
+					     PAC193X_CTRL_STAT_REGS_ADDR +
+					     PAC193X_NEG_PWR_REG_OFF, bidir_reg);
+		if (ret)
+			return ret;
+	}
+
+	/* register data retrieval timestamp */
+	chip_info->jiffies_tstamp = jiffies;
+
+	/* wait till the data is available */
+	usleep_range(wait_time, wait_time + 100);
+
+	return ret;
+}
+
+static int pac193x_reg_snapshot(struct pac193x_chip_info *chip_info,
+				bool do_refresh, bool refresh_v, u32 wait_time)
+{
+	int ret;
+	struct i2c_client *client = chip_info->client;
+	u8 samp_shift, ctrl_regs_tmp;
+	u8 *offset_reg_data_p;
+	u32 samp_rate, cnt;
+	s64 curr_energy, inc;
+	struct reg_data *reg_data;
+
+	mutex_lock(&chip_info->lock);
+
+	if (do_refresh) {
+		ret = pac193x_send_refresh(chip_info, refresh_v, wait_time);
+		if (ret < 0) {
+			dev_err(&client->dev,
+				"%s - cannot send refresh\n",
+				__func__);
+			goto reg_snapshot_err;
+		}
+	}
+
+	/* read the ctrl/status registers for this snapshot */
+	ret = pac193x_i2c_read(client, PAC193X_CTRL_STAT_REGS_ADDR,
+			       (u8 *)chip_info->chip_reg_data.ctrl_regs,
+			       PAC193X_CTRL_REG_LEN);
+	if (ret) {
+		dev_err(&client->dev,
+			"%s - cannot read ctrl/status registers\n",
+			__func__);
+		goto reg_snapshot_err;
+	}
+
+	reg_data = &chip_info->chip_reg_data;
+
+	/* read the data registers */
+	ret = pac193x_i2c_read(client, PAC193X_ACC_COUNT_REG,
+			       (u8 *)reg_data->meas_regs, PAC193X_MEAS_REG_LEN);
+	if (ret) {
+		dev_err(&client->dev,
+			"%s - cannot read ACC_COUNT register\n", __func__);
+		goto reg_snapshot_err;
+	}
+
+	/* see how much shift is required by the sample rate */
+	samp_rate = samp_rate_map_tbl[((reg_data->ctrl_regs[PAC193X_CTRL_LAT_REG_OFF]) >> 6)];
+	samp_shift = get_count_order(samp_rate);
+
+	ctrl_regs_tmp = reg_data->ctrl_regs[PAC193X_CHANNEL_DIS_LAT_REG_OFF];
+	offset_reg_data_p = &reg_data->meas_regs[PAC193X_ACC_REG_LEN];
+
+	/* start with VPOWER_ACC */
+	for (cnt = 0; cnt < chip_info->phys_channels; cnt++) {
+		/* check if the channel is active, skip all fields if disabled */
+		if (((ctrl_regs_tmp << cnt) & 0x80) == 0) {
+			curr_energy = chip_info->chip_reg_data.energy_sec_acc[cnt];
+
+			reg_data->vpower_acc[cnt] = pac193x_get_mvaccu(offset_reg_data_p);
+
+			if (chip_info->bi_dir[cnt])
+				reg_data->vpower_acc[cnt] = MVACCS(reg_data->vpower_acc[cnt]);
+
+			/*
+			 * compute the scaled to 1 second accumulated energy value;
+			 * energy accumulator scaled to 1sec = VPOWER_ACC/2^samp_shift
+			 * the chip's sampling rate is 2^samp_shift samples/sec
+			 */
+			inc = (reg_data->vpower_acc[cnt] >> samp_shift);
+
+			/* add the power_acc field */
+			curr_energy += inc;
+
+			/* check if we have reached the upper/lower limit */
+			if (curr_energy > (s64)PAC_193X_MAX_POWER_ACC)
+				curr_energy = PAC_193X_MAX_POWER_ACC;
+			else if (curr_energy < ((s64)PAC_193X_MIN_POWER_ACC))
+				curr_energy = PAC_193X_MIN_POWER_ACC;
+
+			reg_data->energy_sec_acc[cnt] = curr_energy;
+
+			offset_reg_data_p += PAC193X_VPOWER_ACC_REG_LEN;
+		}
+	}
+
+	/* continue with VBUS */
+	for (cnt = 0; cnt < chip_info->phys_channels; cnt++) {
+		if (((ctrl_regs_tmp << cnt) & 0x80) == 0) {
+			reg_data->vbus[cnt] = pac193x_get_mvbus_senseu(offset_reg_data_p);
+
+			if (chip_info->bi_dir[cnt])
+				reg_data->vbus[cnt] = MVBUS_SENSES(reg_data->vbus[cnt]);
+
+			offset_reg_data_p += PAC193X_VBUS_SENSE_REG_LEN;
+		}
+	}
+
+	/* VSENSE */
+	for (cnt = 0; cnt < chip_info->phys_channels; cnt++) {
+		if (((ctrl_regs_tmp << cnt) & 0x80) == 0) {
+			reg_data->vsense[cnt] = pac193x_get_mvbus_senseu(offset_reg_data_p);
+
+			if (chip_info->bi_dir[cnt])
+				reg_data->vsense[cnt] = MVBUS_SENSES(reg_data->vsense[cnt]);
+
+			offset_reg_data_p += PAC193X_VBUS_SENSE_REG_LEN;
+		}
+	}
+
+	/* VBUS_AVG */
+	for (cnt = 0; cnt < chip_info->phys_channels; cnt++) {
+		if (((ctrl_regs_tmp << cnt) & 0x80) == 0) {
+			reg_data->vbus_avg[cnt] = pac193x_get_mvbus_senseu(offset_reg_data_p);
+
+			if (chip_info->bi_dir[cnt])
+				reg_data->vbus_avg[cnt] = MVBUS_SENSES(reg_data->vbus_avg[cnt]);
+
+			offset_reg_data_p += PAC193X_VBUS_SENSE_REG_LEN;
+		}
+	}
+
+	/* VSENSE_AVG */
+	for (cnt = 0; cnt < chip_info->phys_channels; cnt++) {
+		if (((ctrl_regs_tmp << cnt) & 0x80) == 0) {
+			reg_data->vsense_avg[cnt] = pac193x_get_mvbus_senseu(offset_reg_data_p);
+
+			if (chip_info->bi_dir[cnt])
+				reg_data->vsense_avg[cnt] = MVBUS_SENSES(reg_data->vsense_avg[cnt]);
+
+			offset_reg_data_p += PAC193X_VBUS_SENSE_REG_LEN;
+		}
+	}
+
+	/* VPOWER */
+	for (cnt = 0; cnt < chip_info->phys_channels; cnt++) {
+		if (((ctrl_regs_tmp << cnt) & 0x80) == 0) {
+			reg_data->vpower[cnt] = pac193x_get_vpoweru(offset_reg_data_p);
+
+			if (chip_info->bi_dir[cnt])
+				reg_data->vpower[cnt] = MVPOWERS(reg_data->vpower[cnt]);
+
+			offset_reg_data_p += PAC193X_VPOWER_REG_LEN;
+		}
+	}
+
+reg_snapshot_err:
+	mutex_unlock(&chip_info->lock);
+
+	return ret;
+}
+
+static int pac193x_retrieve_data(struct pac193x_chip_info *chip_info,
+				 u32 wait_time)
+{
+	int ret = 0;
+
+	/*
+	 * check if the minimal elapsed time has passed and if so,
+	 * re-read the chip, otherwise the cached info is just fine
+	 */
+	if (time_after(jiffies, chip_info->jiffies_tstamp +
+					 msecs_to_jiffies(PAC193X_MIN_POLLING_TIME_MS))) {
+		ret = pac193x_reg_snapshot(chip_info, true, false, wait_time);
+
+		/*
+		 * re-schedule the work for the read registers timeout
+		 * (to prevent chip regs saturation)
+		 */
+		mod_timer(&chip_info->tmr_forced_update,
+			  chip_info->jiffies_tstamp +
+			  msecs_to_jiffies(PAC193X_MAX_RFSH_LIMIT_MS));
+	}
+
+	return ret;
+}
+
+/**
+ * pac193x_read_raw() - data read function.
+ * @indio_dev: the struct iio_dev associated with this device instance
+ * @chan: the channel whose data is to be read
+ * @val: first element of returned value (typically INT)
+ * @val2: second element of returned value (typically MICRO)
+ * @mask: what we actually want to read as per the info_mask_* in iio_chan_spec.
+ */
+static int pac193x_read_raw(struct iio_dev *indio_dev,
+			    struct iio_chan_spec const *chan, int *val,
+			    int *val2, long mask)
+{
+	struct pac193x_chip_info *chip_info = iio_priv(indio_dev);
+	s64 curr_energy, int_part;
+	int rem, ret, channel = chan->channel - 1;
+
+	ret = pac193x_retrieve_data(chip_info, PAC193X_MIN_UPDATE_WAIT_TIME_MS);
+	if (ret < 0)
+		return ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		switch (chan->type) {
+		case IIO_VOLTAGE:
+			switch (chan->address) {
+			case PAC193X_VBUS_1_ADDR:
+			case PAC193X_VBUS_2_ADDR:
+			case PAC193X_VBUS_3_ADDR:
+			case PAC193X_VBUS_4_ADDR:
+				*val = chip_info->chip_reg_data.vbus[channel];
+				return IIO_VAL_INT;
+			default:
+				return -EINVAL;
+			}
+			break;
+		case IIO_CURRENT:
+			switch (chan->address) {
+			case PAC193X_VSENSE_1_ADDR:
+			case PAC193X_VSENSE_2_ADDR:
+			case PAC193X_VSENSE_3_ADDR:
+			case PAC193X_VSENSE_4_ADDR:
+				*val = chip_info->chip_reg_data.vsense[channel];
+				return IIO_VAL_INT;
+			default:
+				return -EINVAL;
+			}
+			break;
+		case IIO_POWER:
+			switch (chan->address) {
+			case PAC193X_VPOWER_1_ADDR:
+			case PAC193X_VPOWER_2_ADDR:
+			case PAC193X_VPOWER_3_ADDR:
+			case PAC193X_VPOWER_4_ADDR:
+				*val = chip_info->chip_reg_data.vpower[channel];
+				return IIO_VAL_INT;
+			default:
+				return -EINVAL;
+			}
+			break;
+		case IIO_ENERGY:
+			switch (chan->address) {
+			case PAC193X_VPOWER_ACC_1_ADDR:
+			case PAC193X_VPOWER_ACC_2_ADDR:
+			case PAC193X_VPOWER_ACC_3_ADDR:
+			case PAC193X_VPOWER_ACC_4_ADDR:
+				/*
+				 * expresses the 64 bit energy value as a 32 bit integer part and
+				 * 32 bits (representing 8 digits) fractional value
+				 */
+				curr_energy = chip_info->chip_reg_data.energy_sec_acc[channel];
+				int_part = div_s64_rem(curr_energy, 100000000, &rem);
+
+				/* rescale reminder to be printed as "nano" value */
+				rem = rem * 10;
+
+				*val = int_part;
+				*val2 = rem;
+				return IIO_VAL_INT_PLUS_NANO;
+			default:
+				return -EINVAL;
+			}
+			break;
+		default:
+			return -EINVAL;
+		}
+		break;
+	case IIO_CHAN_INFO_AVERAGE_RAW:
+		switch (chan->type) {
+		case IIO_VOLTAGE:
+			switch (chan->address) {
+			case PAC193X_VBUS_AVG_1_ADDR:
+			case PAC193X_VBUS_AVG_2_ADDR:
+			case PAC193X_VBUS_AVG_3_ADDR:
+			case PAC193X_VBUS_AVG_4_ADDR:
+				*val = chip_info->chip_reg_data.vbus_avg[channel];
+				return IIO_VAL_INT;
+
+			default:
+				return -EINVAL;
+			}
+			break;
+		case IIO_CURRENT:
+			switch (chan->address) {
+			case PAC193X_VSENSE_AVG_1_ADDR:
+			case PAC193X_VSENSE_AVG_2_ADDR:
+			case PAC193X_VSENSE_AVG_3_ADDR:
+			case PAC193X_VSENSE_AVG_4_ADDR:
+				*val = chip_info->chip_reg_data.vsense_avg[channel];
+				return IIO_VAL_INT;
+
+			default:
+				return -EINVAL;
+			}
+			break;
+		default:
+			return -EINVAL;
+		}
+		break;
+	case IIO_CHAN_INFO_SCALE:
+		switch (chan->address) {
+		/* Voltages - scale for millivolts */
+		case PAC193X_VBUS_1_ADDR:
+		case PAC193X_VBUS_2_ADDR:
+		case PAC193X_VBUS_3_ADDR:
+		case PAC193X_VBUS_4_ADDR:
+		case PAC193X_VBUS_AVG_1_ADDR:
+		case PAC193X_VBUS_AVG_2_ADDR:
+		case PAC193X_VBUS_AVG_3_ADDR:
+		case PAC193X_VBUS_AVG_4_ADDR:
+			*val = PAC193X_VOLTAGE_MILLIVOLTS_MAX;
+			if (chan->scan_type.sign == 'u')
+				*val2 = PAC193X_VOLTAGE_U_RES;
+			else
+				*val2 = PAC193X_VOLTAGE_S_RES;
+			return IIO_VAL_FRACTIONAL_LOG2;
+
+		/*
+		 * Currents - scale for mA - depends on the
+		 * channel's shunt value
+		 * (100mV * 1000000) / (2^16 * shunt(uohm))
+		 */
+		case PAC193X_VSENSE_1_ADDR:
+		case PAC193X_VSENSE_2_ADDR:
+		case PAC193X_VSENSE_3_ADDR:
+		case PAC193X_VSENSE_4_ADDR:
+		case PAC193X_VSENSE_AVG_1_ADDR:
+		case PAC193X_VSENSE_AVG_2_ADDR:
+		case PAC193X_VSENSE_AVG_3_ADDR:
+		case PAC193X_VSENSE_AVG_4_ADDR:
+			*val = PAC193X_MAX_VSENSE_RSHIFTED_BY_16B;
+			if (chan->scan_type.sign == 'u')
+				*val2 = chip_info->shunts[channel];
+			else
+				*val2 = chip_info->shunts[channel] >> 1;
+			return IIO_VAL_FRACTIONAL;
+
+		/*
+		 * Power - mW - it will use the combined scale
+		 * for current and voltage
+		 * current(mA) * voltage(mV) = power (uW)
+		 */
+		case PAC193X_VPOWER_1_ADDR:
+		case PAC193X_VPOWER_2_ADDR:
+		case PAC193X_VPOWER_3_ADDR:
+		case PAC193X_VPOWER_4_ADDR:
+			*val = PAC193X_MAX_VPOWER_RSHIFTED_BY_28B;
+			if (chan->scan_type.sign == 'u')
+				*val2 = chip_info->shunts[channel];
+			else
+				*val2 = chip_info->shunts[channel] >> 1;
+			return IIO_VAL_FRACTIONAL;
+		case PAC193X_VPOWER_ACC_1_ADDR:
+		case PAC193X_VPOWER_ACC_2_ADDR:
+		case PAC193X_VPOWER_ACC_3_ADDR:
+		case PAC193X_VPOWER_ACC_4_ADDR:
+
+			/*
+			 * expresses the 32 bit scale value
+			 * here compute the scale for energy (Watt-second or Joule)
+			 */
+			*val = PAC193X_SCALE_CONSTANT;
+
+			if (chan->scan_type.sign == 'u')
+				*val2 = chip_info->shunts[channel];
+			else
+				*val2 = chip_info->shunts[channel] >> 1;
+			return IIO_VAL_FRACTIONAL;
+		default:
+			return -EINVAL;
+		}
+		break;
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		*val = chip_info->sample_rate_value;
+		return IIO_VAL_INT;
+	default:
+		return -EINVAL;
+	}
+
+	return -EINVAL;
+}
+
+/*
+ * pac193x_write_raw() - data write function.
+ * @indio_dev:	the struct iio_dev associated with this device instance
+ * @chan:	the channel whose data is to be written
+ * @val:	first element of value to set (typically INT)
+ * @val2:	second element of value to set (typically MICRO)
+ * @mask:	what we actually want to write as per the info_mask_* in iio_chan_spec.
+ *
+ * Note that all raw writes are assumed IIO_VAL_INT and info mask elements
+ * are assumed to be IIO_INT_PLUS_MICRO unless the callback write_raw_get_fmt
+ * in struct iio_info is provided by the driver.
+ */
+static int pac193x_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan,
+			     int val, int val2, long mask)
+{
+	struct pac193x_chip_info *chip_info = iio_priv(indio_dev);
+	struct i2c_client *client = chip_info->client;
+	int ret = -EINVAL;
+	s32 old_samp_rate;
+	u8 ctrl_reg;
+
+	if (iio_buffer_enabled(indio_dev))
+		return -EBUSY;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		ret = pac193x_match_samp_rate(chip_info, (u16)val);
+		if (ret)
+			return ret;
+
+		/* store the old sampling rate */
+		old_samp_rate = chip_info->sample_rate_value;
+
+		/* we have a valid sample rate */
+		chip_info->sample_rate_value = val;
+
+		/*
+		 * now lock the access to the chip, write the new
+		 * sampling value and trigger a snapshot(incl refresh)
+		 */
+		mutex_lock(&chip_info->lock);
+
+		/* enable ALERT pin */
+		ctrl_reg = PAC193X_CRTL_SAMPLE_RATE_SET(chip_info->crt_samp_spd_bitfield) |
+			PAC193X_CHAN_ALERT_EN;
+		ret = pac193x_i2c_write_byte(client, PAC193X_CTRL_REG, ctrl_reg);
+		if (ret) {
+			dev_err(&client->dev, "%s - can't update sample rate\n", __func__);
+			chip_info->sample_rate_value = old_samp_rate;
+			mutex_unlock(&chip_info->lock);
+			return ret;
+		}
+
+		/*
+		 * unlock the access towards the chip - register
+		 * snapshot includes its own access lock
+		 */
+		mutex_unlock(&chip_info->lock);
+
+		/*
+		 * now, force a snapshot with refresh - call retrieve
+		 * data in order to update the refresh timer
+		 * alter the timestamp in order to force trigger a
+		 * register snapshot and a timestamp update
+		 */
+		chip_info->jiffies_tstamp -=
+			msecs_to_jiffies(PAC193X_MIN_POLLING_TIME_MS);
+		ret = pac193x_retrieve_data(chip_info, (1024 / old_samp_rate) * 1000);
+		if (ret < 0) {
+			dev_err(&client->dev,
+				"%s - cannot snapshot ctrl and measurement regs\n",
+				__func__);
+			return ret;
+		}
+
+		ret = 0;
+		break;
+	default:
+		break;
+	}
+
+	return ret;
+}
+
+static void pac193x_work_periodic_rfsh(struct work_struct *work)
+{
+	struct pac193x_chip_info *chip_info = to_pac193x_chip_info(work);
+
+	/* do a REFRESH, then read */
+	pac193x_reg_snapshot(chip_info, true, false, PAC193X_MIN_UPDATE_WAIT_TIME_MS);
+}
+
+static void pac193x_read_reg_timeout(struct timer_list *t)
+{
+	struct pac193x_chip_info *chip_info = from_timer(chip_info, t, tmr_forced_update);
+
+	mod_timer(&chip_info->tmr_forced_update,
+		  jiffies + msecs_to_jiffies(PAC193X_MAX_RFSH_LIMIT_MS));
+
+	/* schedule the periodic reading from the chip */
+	queue_work(chip_info->wq_chip, &chip_info->work_chip_rfsh);
+}
+
+static int pac193x_read_revision(struct pac193x_chip_info *chip_info, u8 *buf)
+{
+	int ret;
+	struct i2c_client *client = chip_info->client;
+
+	/*
+	 * try to identify the chip variant
+	 * read the chip ID values
+	 */
+	ret = pac193x_i2c_read(client, PAC193X_PID_REG_ADDR, buf, PAC193X_ID_REG_LEN);
+	if (ret) {
+		dev_err(&client->dev, "cannot read revision\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int pac193x_chip_identify(struct pac193x_chip_info *chip_info)
+{
+	int ret;
+	struct i2c_client *client = chip_info->client;
+	u8 rev_info[PAC193X_ID_REG_LEN];
+
+	ret = pac193x_read_revision(chip_info, (u8 *)rev_info);
+
+	if (ret) {
+		dev_err(&client->dev, "cannot read revision\n");
+		return ret;
+	}
+
+	if (rev_info[PAC193X_PID_IDX] != pac193x_chip_config[chip_info->chip_variant].prod_id) {
+		dev_err(&client->dev,
+			"chip's product ID doesn't match the exact one for this part\n");
+		return -EINVAL;
+	}
+
+	dev_dbg(&client->dev, "Chip revision: 0x%02X\n", rev_info[PAC193X_RID_IDX]);
+	chip_info->chip_revision = rev_info[PAC193X_RID_IDX];
+
+	return 0;
+}
+
+static int pac193x_get_variant(struct pac193x_chip_info *chip_info)
+{
+	u8 rev_info[PAC193X_ID_REG_LEN];
+	int ret;
+
+	ret = pac193x_read_revision(chip_info, (u8 *)rev_info);
+
+	if (!ret) {
+		chip_info->chip_variant = rev_info[PAC193X_PID_IDX];
+		chip_info->chip_revision = rev_info[PAC193X_RID_IDX];
+		switch (chip_info->chip_variant) {
+		case PAC1934_PID:
+			chip_info->phys_channels = PAC1934_NUM_CHANNELS;
+			break;
+		case PAC1933_PID:
+			chip_info->phys_channels = PAC1933_NUM_CHANNELS;
+			break;
+		case PAC1932_PID:
+			chip_info->phys_channels = PAC1932_NUM_CHANNELS;
+			break;
+		case PAC1931_PID:
+			chip_info->phys_channels = PAC1931_NUM_CHANNELS;
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+
+	return 0;
+}
+
+static int pac193x_setup_periodic_refresh(struct pac193x_chip_info *chip_info)
+{
+	chip_info->wq_chip = create_workqueue("wq_pac193x");
+	INIT_WORK(&chip_info->work_chip_rfsh, pac193x_work_periodic_rfsh);
+
+	/* setup the latest moment for reading the regs before saturation */
+	timer_setup(&chip_info->tmr_forced_update, pac193x_read_reg_timeout, 0);
+
+	/* register the timer */
+	mod_timer(&chip_info->tmr_forced_update,
+		  jiffies + msecs_to_jiffies(PAC193X_MAX_RFSH_LIMIT_MS));
+
+	return 0;
+}
+
+static union acpi_object *pac193x_acpi_eval_function(acpi_handle handle,
+						     int revision,
+						     int function)
+{
+	acpi_status status;
+	struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL};
+	union acpi_object args[PAC193X_ACPI_ARG_COUNT];
+	struct acpi_object_list args_list;
+	uuid_t uuid;
+
+	uuid_parse(PAC193X_DSM_UUID, &uuid);
+
+	args[0].type = ACPI_TYPE_BUFFER;
+	args[0].buffer.length = sizeof(struct pac193x_uuid_format);
+	args[0].buffer.pointer = (u8 *)&uuid;
+
+	args[1].type = ACPI_TYPE_INTEGER;
+	args[1].integer.value = revision;
+
+	args[2].type = ACPI_TYPE_INTEGER;
+	args[2].integer.value = function;
+
+	args[3].type = ACPI_TYPE_PACKAGE;
+	args[3].package.count = 0;
+
+	args_list.count = PAC193X_ACPI_ARG_COUNT;
+	args_list.pointer = &args[0];
+
+	status = acpi_evaluate_object(handle, "_DSM", &args_list, &buffer);
+
+	if (ACPI_FAILURE(status)) {
+		kfree(buffer.pointer);
+		return NULL;
+	}
+
+	return buffer.pointer;
+}
+
+static char *pac193x_acpi_get_acpi_match_entry(acpi_handle handle)
+{
+	acpi_status status;
+	union acpi_object *name_object;
+	struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL};
+
+	status = acpi_evaluate_object(handle, "_HID", NULL, &buffer);
+	name_object = buffer.pointer;
+
+	return name_object->string.pointer;
+}
+
+static const char *pac193x_match_acpi_device(struct i2c_client *client,
+					     struct pac193x_chip_info *chip_info)
+{
+	char *name;
+	acpi_handle handle;
+	union acpi_object *rez;
+	unsigned short bi_dir_mask;
+	int idx, i;
+
+	handle = ACPI_HANDLE(&client->dev);
+	name = pac193x_acpi_get_acpi_match_entry(handle);
+	if (!name)
+		return NULL;
+
+	rez = pac193x_acpi_eval_function(handle, 0, PAC193X_ACPI_GET_NAMES_AND_MOHMS_VALS);
+
+	if (!rez)
+		return NULL;
+
+	for (i = 0; i < rez->package.count; i += 2) {
+		idx = i / 2;
+		chip_info->channel_names[idx] =
+			devm_kmemdup(&client->dev, rez->package.elements[i].string.pointer,
+				     (size_t)rez->package.elements[i].string.length + 1,
+				     GFP_KERNEL);
+		chip_info->channel_names[idx][rez->package.elements[i].string.length] = '\0';
+		chip_info->shunts[idx] =
+			rez->package.elements[i + 1].integer.value * 1000;
+		chip_info->active_channels[idx] = (chip_info->shunts[idx] != 0);
+	}
+
+	kfree(rez);
+
+	rez = pac193x_acpi_eval_function(handle, 1, PAC193X_ACPI_GET_UOHMS_VALS);
+	if (!rez) {
+		/*
+		 * initialising with default values
+		 * we assume all channels are unidectional(the mask is zero)
+		 * and assign the default sampling rate
+		 */
+		chip_info->sample_rate_value = PAC193X_DEFAULT_CHIP_SAMP_SPEED;
+		return name;
+	}
+
+	for (i = 0; i < rez->package.count; i++) {
+		idx = i;
+		chip_info->shunts[idx] = rez->package.elements[i].integer.value;
+		chip_info->active_channels[idx] = (chip_info->shunts[idx] != 0);
+	}
+
+	kfree(rez);
+
+	rez = pac193x_acpi_eval_function(handle, 1, PAC193X_ACPI_GET_BIPOLAR_SETTINGS);
+	if (!rez)
+		return NULL;
+	bi_dir_mask = rez->package.elements[0].integer.value;
+	chip_info->bi_dir[0] = (bi_dir_mask & (1 << 0)) << 0;
+	chip_info->bi_dir[0] = (bi_dir_mask & (1 << 1)) << 1;
+	chip_info->bi_dir[0] = (bi_dir_mask & (1 << 2)) << 2;
+	chip_info->bi_dir[0] = (bi_dir_mask & (1 << 3)) << 3;
+	kfree(rez);
+
+	rez = pac193x_acpi_eval_function(handle, 1, PAC193X_ACPI_GET_SAMP);
+	if (!rez)
+		return NULL;
+
+	chip_info->sample_rate_value = rez->package.elements[0].integer.value;
+	kfree(rez);
+
+	return name;
+}
+
+static const struct of_device_id pac193x_of_match[] = {
+	{ .compatible = "microchip,pac1934",
+	  .data = (void *)&pac193x_chip_config[pac1934]
+	},
+	{ .compatible = "microchip,pac1933",
+	  .data = (void *)&pac193x_chip_config[pac1933]
+	},
+	{ .compatible = "microchip,pac1932",
+	  .data = (void *)&pac193x_chip_config[pac1932]
+	},
+	{ .compatible = "microchip,pac1931",
+	  .data = (void *)&pac193x_chip_config[pac1931]
+	},
+	{}
+};
+MODULE_DEVICE_TABLE(of, pac193x_of_match);
+
+static int pac193x_match_of_device(struct i2c_client *client,
+				   struct pac193x_chip_info *chip_info)
+{
+	struct device_node *node;
+	unsigned int current_channel;
+	int idx, ret;
+
+	ret = of_property_read_u32(client->dev.of_node, "microchip,samp-rate",
+				   &chip_info->sample_rate_value);
+	if (ret) {
+		dev_err(&client->dev,
+			"Cannot read sample rate value ...%d\n", ret);
+		goto err_of_node_put;
+	}
+
+	ret = pac193x_match_samp_rate(chip_info, chip_info->sample_rate_value);
+	if (ret) {
+		dev_err(&client->dev,
+			"The given sample rate value is not supported: %d\n",
+			chip_info->sample_rate_value);
+		goto err_of_node_put;
+	}
+
+	current_channel = 1;
+	for_each_child_of_node(client->dev.of_node, node) {
+		ret = of_property_read_u32(node, "reg", &idx);
+		if (ret) {
+			dev_err(&client->dev,
+				"invalid channel_index, error: %d\n",
+				ret);
+			goto err_of_node_put;
+		}
+		/* adjust idx to match channel index (1 to 4) from the datasheet */
+		idx--;
+		if (current_channel >= (chip_info->phys_channels + 1) ||
+		    idx >= chip_info->phys_channels || idx < 0) {
+			dev_err(&client->dev,
+				"invalid channel_index %d value on %s\n",
+				(idx + 1), node->full_name);
+			goto err_of_node_put;
+		}
+
+		/* enable channel */
+		chip_info->active_channels[idx] = true;
+
+		ret = of_property_read_u32(node, "microchip,uohms-shunt-res",
+					   &chip_info->shunts[idx]);
+		if (ret) {
+			dev_err(&client->dev,
+				"%s: invalid shunt-resistor value: %d\n",
+				node->full_name, ret);
+			goto err_of_node_put;
+		}
+
+		ret = of_property_read_string(node, "microchip,rail-name",
+					      (const char **)&chip_info->channel_names[idx]);
+		if (ret) {
+			dev_err(&client->dev,
+				"%s: invalid rail-name value: %d\n",
+				node->full_name, ret);
+			goto err_of_node_put;
+		}
+
+		chip_info->bi_dir[idx] =
+			of_property_read_bool(node, "microchip,bi-directional");
+
+		current_channel++;
+	}
+
+	return 0;
+
+err_of_node_put:
+	of_node_put(node);
+	return ret;
+}
+
+static int pac193x_chip_configure(struct pac193x_chip_info *chip_info)
+{
+	int cnt, ret;
+	struct i2c_client *client = chip_info->client;
+	u8 regs[PAC193X_CTRL_STATUS_INFO_LEN], idx, ctrl_reg;
+	u32 wait_time;
+
+	/*
+	 * count how many channels are enabled and store
+	 * this information within the driver data
+	 */
+	cnt = 0;
+	chip_info->chip_reg_data.num_enabled_channels = 0;
+	while (cnt < chip_info->phys_channels) {
+		if (chip_info->active_channels[cnt])
+			chip_info->chip_reg_data.num_enabled_channels++;
+		cnt++;
+	}
+
+	/*
+	 * read whatever information was gathered before the driver was loaded
+	 * establish which channels are enabled/disabled and then establish the
+	 * information retrieval mode (using SKIP or no).
+	 * Read the chip ID values
+	 */
+	ret = pac193x_i2c_read(client, PAC193X_CTRL_STAT_REGS_ADDR,
+			       (u8 *)regs, PAC193X_CTRL_STATUS_INFO_LEN);
+	if (ret) {
+		dev_err(&client->dev,
+			"%s - cannot read regs from 0x%02X\n",
+			__func__, PAC193X_CTRL_STAT_REGS_ADDR);
+		return ret;
+	}
+
+	/* write the CHANNEL_DIS and the NEG_PWR registers */
+	regs[PAC193X_CHANNEL_DIS_REG_OFF] =
+		PAC193X_CHAN_DIS_CH1_OFF(chip_info->active_channels[PAC193X_CH_1]) |
+		PAC193X_CHAN_DIS_CH2_OFF(chip_info->active_channels[PAC193X_CH_2]) |
+		PAC193X_CHAN_DIS_CH3_OFF(chip_info->active_channels[PAC193X_CH_3]) |
+		PAC193X_CHAN_DIS_CH4_OFF(chip_info->active_channels[PAC193X_CH_4]) |
+		PAC193X_SMBUS_TIMEOUT_EN(0) |
+		PAC193X_SMBUS_BYTECOUNT_EN(0) |
+		PAC193X_SMBUS_NO_SKIP_EN(0);
+
+	regs[PAC193X_NEG_PWR_REG_OFF] =
+		PAC193X_NEG_PWR_CH1_BIDI(chip_info->bi_dir[PAC193X_CH_1]) |
+		PAC193X_NEG_PWR_CH2_BIDI(chip_info->bi_dir[PAC193X_CH_2]) |
+		PAC193X_NEG_PWR_CH3_BIDI(chip_info->bi_dir[PAC193X_CH_3]) |
+		PAC193X_NEG_PWR_CH4_BIDI(chip_info->bi_dir[PAC193X_CH_4]) |
+		PAC193X_NEG_PWR_CH1_BIDV(chip_info->bi_dir[PAC193X_CH_1]) |
+		PAC193X_NEG_PWR_CH2_BIDV(chip_info->bi_dir[PAC193X_CH_2]) |
+		PAC193X_NEG_PWR_CH3_BIDV(chip_info->bi_dir[PAC193X_CH_3]) |
+		PAC193X_NEG_PWR_CH4_BIDV(chip_info->bi_dir[PAC193X_CH_4]);
+
+	/*
+	 * the current can be measured uni or bi-dir, but voltages are set only
+	 * for uni-directional operation
+	 * no SLOW triggered REFRESH, clear POR
+	 */
+	regs[PAC193X_SLOW_REG_OFF] = 0;
+
+	/* write the updated registers back */
+	ret = pac193x_i2c_write(client, PAC193X_CTRL_STAT_REGS_ADDR, (u8 *)regs, 3);
+	if (ret)
+		return ret;
+
+	/* enable the ALERT pin functionality */
+	ctrl_reg = PAC193X_CRTL_SAMPLE_RATE_SET(chip_info->crt_samp_spd_bitfield) |
+		PAC193X_CHAN_ALERT_EN;
+	ret = pac193x_i2c_write_byte(client, PAC193X_CTRL_REG, ctrl_reg);
+	if (ret)
+		return ret;
+
+	/*
+	 * send a REFRESH to the chip, so the new settings take place
+	 * as well as resetting the accumulators
+	 */
+	ret = pac193x_i2c_send_byte(client, PAC193X_REFRESH_REG);
+	if (ret) {
+		dev_err(&client->dev,
+			"%s - cannot send 0x%02X\n",
+			__func__, PAC193X_REFRESH_REG);
+		return ret;
+	}
+
+	/*
+	 * get the current(in the chip) sampling speed and compute the
+	 * required timeout based on its value
+	 * the timeout is 1/sampling_speed
+	 */
+	idx = regs[PAC193X_CTRL_ACT_REG_OFF] >> PAC193X_SAMPLE_RATE_SHIFT;
+	wait_time = (1024 / samp_rate_map_tbl[idx]) * 1000;
+
+	/*
+	 * wait the maximum amount of time to be on the safe side - the
+	 * maximum wait time is for 8sps
+	 */
+	usleep_range(wait_time, wait_time + 100);
+
+	/* setup the refresh timeout */
+	ret = pac193x_setup_periodic_refresh(chip_info);
+
+	return ret;
+}
+
+static int pac193x_prep_iio_channels(struct pac193x_chip_info *chip_info, struct iio_dev *indio_dev)
+{
+	struct i2c_client *client;
+	struct iio_chan_spec *ch_sp;
+	int channel_size, channel_attribute_count, attribute_count, cnt;
+	void *dyn_ch_struct, *tmp_data;
+
+	client = chip_info->client;
+
+	/* find out dynamically how many IIO channels we need */
+	channel_attribute_count = 0;
+	channel_size = 0;
+	for (cnt = 0; cnt < chip_info->phys_channels; cnt++) {
+		if (chip_info->active_channels[cnt]) {
+			/* add the size of the properties of one chip physical channel */
+			channel_size += sizeof(pac193x_single_channel);
+			/* count how many enabled channels we have */
+			channel_attribute_count += ARRAY_SIZE(pac193x_single_channel);
+			dev_info(&client->dev, ":%s: Channel %d active\n", __func__, cnt + 1);
+		}
+	}
+
+	/* now add the timestamp channel size */
+	channel_size += sizeof(pac193x_ts);
+
+	/* add one more channel which is the timestamp */
+	attribute_count = channel_attribute_count + 1;
+
+	dyn_ch_struct = kzalloc(channel_size, GFP_KERNEL);
+	if (!dyn_ch_struct)
+		return -EINVAL;
+
+	tmp_data = dyn_ch_struct;
+
+	/* populate the dynamic channels and make all the adjustments */
+	for (cnt = 0; cnt < chip_info->phys_channels; cnt++) {
+		if (chip_info->active_channels[cnt]) {
+			memcpy(tmp_data, pac193x_single_channel, sizeof(pac193x_single_channel));
+			ch_sp = (struct iio_chan_spec *)tmp_data;
+			ch_sp[IIO_EN].channel = cnt  + 1;
+			ch_sp[IIO_EN].scan_index = cnt;
+			ch_sp[IIO_EN].address = cnt + PAC193X_VPOWER_ACC_1_ADDR;
+			ch_sp[IIO_POW].channel = cnt  + 1;
+			ch_sp[IIO_POW].scan_index = cnt;
+			ch_sp[IIO_POW].address = cnt + PAC193X_VPOWER_1_ADDR;
+			ch_sp[IIO_VOLT].channel = cnt  + 1;
+			ch_sp[IIO_VOLT].scan_index = cnt;
+			ch_sp[IIO_VOLT].address = cnt + PAC193X_VBUS_1_ADDR;
+			ch_sp[IIO_CRT].channel = cnt  + 1;
+			ch_sp[IIO_CRT].scan_index = cnt;
+			ch_sp[IIO_CRT].address = cnt + PAC193X_VSENSE_1_ADDR;
+			ch_sp[IIO_VOLTAVG].channel = cnt  + 1;
+			ch_sp[IIO_VOLTAVG].scan_index = cnt;
+			ch_sp[IIO_VOLTAVG].address = cnt + PAC193X_VBUS_AVG_1_ADDR;
+			ch_sp[IIO_CRTAVG].channel = cnt  + 1;
+			ch_sp[IIO_CRTAVG].scan_index = cnt;
+			ch_sp[IIO_CRTAVG].address = cnt + PAC193X_VSENSE_AVG_1_ADDR;
+
+			/*
+			 * now modify the parameters in all channels if the
+			 * whole chip rail(channel) is bi-directional
+			 */
+			if (chip_info->bi_dir[cnt]) {
+				ch_sp[IIO_EN].scan_type.sign = 's';
+				ch_sp[IIO_EN].scan_type.realbits = PAC193X_ENERGY_S_RES;
+				ch_sp[IIO_POW].scan_type.sign = 's';
+				ch_sp[IIO_POW].scan_type.realbits = PAC193X_POWER_S_RES;
+				ch_sp[IIO_VOLT].scan_type.sign = 's';
+				ch_sp[IIO_VOLT].scan_type.realbits = PAC193X_VOLTAGE_S_RES;
+				ch_sp[IIO_CRT].scan_type.sign = 's';
+				ch_sp[IIO_CRT].scan_type.realbits = PAC193X_CURRENT_S_RES;
+				ch_sp[IIO_VOLTAVG].scan_type.sign = 's';
+				ch_sp[IIO_VOLTAVG].scan_type.realbits = PAC193X_VOLTAGE_S_RES;
+				ch_sp[IIO_CRTAVG].scan_type.sign = 's';
+				ch_sp[IIO_CRTAVG].scan_type.realbits = PAC193X_CURRENT_S_RES;
+			}
+			tmp_data += sizeof(pac193x_single_channel);
+		}
+	}
+
+	/* now copy the timestamp channel */
+	memcpy(tmp_data, pac193x_ts, sizeof(pac193x_ts));
+	ch_sp = (struct iio_chan_spec *)tmp_data;
+	ch_sp[0].scan_index = attribute_count - 1;
+
+	/*
+	 * send the updated dynamic channel structure information towards IIO
+	 * prepare the required field for IIO class registration
+	 */
+	indio_dev->num_channels = attribute_count;
+	indio_dev->channels = devm_kmemdup(&client->dev,
+					   (const struct iio_chan_spec *)dyn_ch_struct,
+					   channel_size, GFP_KERNEL);
+
+	kfree(dyn_ch_struct);
+
+	if (!indio_dev->channels)
+		return -EINVAL;
+
+	return 0;
+}
+
+static ssize_t reset_accumulators_store(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t count)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct pac193x_chip_info *chip_info = iio_priv(indio_dev);
+	int ret, i;
+	u8 refresh_cmd = PAC193X_REFRESH_REG;
+
+	ret = pac193x_i2c_send_byte(chip_info->client, refresh_cmd);
+	if (ret) {
+		dev_err(&indio_dev->dev,
+			"%s - cannot send 0x%02X\n",
+			__func__, refresh_cmd);
+	}
+
+	for (i = 0 ; i < chip_info->phys_channels; i++)
+		chip_info->chip_reg_data.energy_sec_acc[i] = 0;
+
+	return count;
+}
+
+static IIO_DEVICE_ATTR(shunt_value_1, 0644, shunt_value_show, shunt_value_store, 0);
+static IIO_DEVICE_ATTR(shunt_value_2, 0644, shunt_value_show, shunt_value_store, 0);
+static IIO_DEVICE_ATTR(shunt_value_3, 0644, shunt_value_show, shunt_value_store, 0);
+static IIO_DEVICE_ATTR(shunt_value_4, 0644, shunt_value_show, shunt_value_store, 0);
+
+static IIO_DEVICE_ATTR(channel_name_1, 0444, channel_name_show, NULL, 0);
+static IIO_DEVICE_ATTR(channel_name_2, 0444, channel_name_show, NULL, 0);
+static IIO_DEVICE_ATTR(channel_name_3, 0444, channel_name_show, NULL, 0);
+static IIO_DEVICE_ATTR(channel_name_4, 0444, channel_name_show, NULL, 0);
+
+static IIO_DEVICE_ATTR(reset_accumulators, 0200, NULL, reset_accumulators_store, 0);
+
+static struct attribute *pac193x_all_attributes[] = {
+	PAC193X_DEV_ATTR(shunt_value_1),
+	PAC193X_DEV_ATTR(channel_name_1),
+	PAC193X_DEV_ATTR(shunt_value_2),
+	PAC193X_DEV_ATTR(channel_name_2),
+	PAC193X_DEV_ATTR(shunt_value_3),
+	PAC193X_DEV_ATTR(channel_name_3),
+	PAC193X_DEV_ATTR(shunt_value_4),
+	PAC193X_DEV_ATTR(channel_name_4),
+	PAC193X_DEV_ATTR(reset_accumulators),
+	NULL
+};
+
+static int pac193x_prep_custom_attributes(struct pac193x_chip_info *chip_info,
+					  struct iio_dev *indio_dev)
+{
+	int i, j, active_channels_count = 0;
+	struct attribute **pac193x_custom_attributes;
+	struct attribute_group *pac193x_group;
+	struct i2c_client *client = chip_info->client;
+
+	for (i = 0 ; i < chip_info->phys_channels; i++)
+		if (chip_info->active_channels[i])
+			active_channels_count++;
+
+	pac193x_group = devm_kzalloc(&client->dev, sizeof(*pac193x_group), GFP_KERNEL);
+
+	pac193x_custom_attributes = devm_kzalloc(&client->dev,
+						 (PAC193X_CUSTOM_ATTR_FOR_CHANNEL *
+						 active_channels_count +
+						 PAC193X_SHARED_DEVATTRS_COUNT)
+						 * sizeof(*pac193x_group) + 1,
+						 GFP_KERNEL);
+	j = 0;
+
+	for (i = 0 ; i < chip_info->phys_channels; i++) {
+		if (chip_info->active_channels[i]) {
+			pac193x_custom_attributes[PAC193X_CUSTOM_ATTR_FOR_CHANNEL * j] =
+			pac193x_all_attributes[PAC193X_CUSTOM_ATTR_FOR_CHANNEL * i];
+			pac193x_custom_attributes[PAC193X_CUSTOM_ATTR_FOR_CHANNEL * j + 1] =
+			pac193x_all_attributes[PAC193X_CUSTOM_ATTR_FOR_CHANNEL * i + 1];
+			j++;
+		}
+	}
+
+	for (i = 0; i < PAC193X_SHARED_DEVATTRS_COUNT; i++)
+		pac193x_custom_attributes[PAC193X_CUSTOM_ATTR_FOR_CHANNEL *
+			active_channels_count + i] =
+			pac193x_all_attributes[PAC193X_CUSTOM_ATTR_FOR_CHANNEL *
+			chip_info->phys_channels + i];
+
+	pac193x_group->attrs = pac193x_custom_attributes;
+	chip_info->pac193x_info.attrs = pac193x_group;
+
+	return 0;
+}
+
+static void pac193x_remove(struct i2c_client *client)
+{
+	int ret;
+	struct iio_dev *indio_dev = dev_get_drvdata(&client->dev);
+	struct pac193x_chip_info *chip_info = iio_priv(indio_dev);
+
+	ret = try_to_del_timer_sync(&chip_info->tmr_forced_update);
+	if (ret < 0)
+		dev_err(&client->dev,
+			"%s - cannot delete the forced readout timer\n",
+			__func__);
+
+	if (chip_info->wq_chip) {
+		cancel_work_sync(&chip_info->work_chip_rfsh);
+		flush_workqueue(chip_info->wq_chip);
+		destroy_workqueue(chip_info->wq_chip);
+	}
+}
+
+static int pac193x_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+	struct pac193x_chip_info *chip_info;
+	struct iio_dev *indio_dev;
+	const char *name = NULL;
+	const struct of_device_id *match;
+	int cnt, ret, dev_id = 0;
+
+	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip_info));
+	if (!indio_dev) {
+		dev_err_probe(&indio_dev->dev, PTR_ERR(indio_dev),
+			      "Can't allocate iio device\n");
+		return -ENOMEM;
+	}
+
+	chip_info = iio_priv(indio_dev);
+
+	i2c_set_clientdata(client, indio_dev);
+	chip_info->client = client;
+
+	memset(&chip_info->chip_reg_data, 0, sizeof(chip_info->chip_reg_data));
+
+	if (ACPI_HANDLE(&client->dev)) {
+		pac193x_get_variant(chip_info);
+	} else {
+		dev_id = id->driver_data;
+
+		/* store the type of chip */
+		chip_info->chip_variant = dev_id;
+
+		/* get the maximum number of channels for the given chip id */
+		chip_info->phys_channels = pac193x_chip_config[dev_id].phys_channels;
+	}
+
+	/*
+	 * load default settings - all channels disabled,
+	 * uni directional flow, default shunt values
+	 */
+	for (cnt = 0; cnt < chip_info->phys_channels; cnt++) {
+		chip_info->active_channels[cnt] = false;
+		chip_info->bi_dir[cnt] = false;
+		chip_info->shunts[cnt] = SHUNT_UOHMS_DEFAULT;
+	}
+
+	chip_info->crt_samp_spd_bitfield = pac193X_samp_1024sps;
+
+	if (ACPI_HANDLE(&client->dev)) {
+		switch (chip_info->chip_variant) {
+		case PAC1934_PID:
+			client->dev.init_name = "pac1934";
+			break;
+		case PAC1933_PID:
+			client->dev.init_name = "pac1933";
+			break;
+		case PAC1932_PID:
+			client->dev.init_name = "pac1932";
+			break;
+		case PAC1931_PID:
+			client->dev.init_name = "pac1931";
+			break;
+		default:
+			return -EINVAL;
+		}
+		name = pac193x_match_acpi_device(client, chip_info);
+	} else {
+		/* identify the chip we have to deal with */
+		ret = pac193x_chip_identify(chip_info);
+		if (ret)
+			return -EINVAL;
+
+		/* check if we find the device within DT */
+		if (!client->dev.of_node || (!of_get_next_child(client->dev.of_node, NULL)))
+			return -EINVAL;
+
+		match = of_match_node(pac193x_of_match, client->dev.of_node);
+		if (match) {
+			ret = pac193x_match_of_device(client, chip_info);
+			if (!ret)
+				name = match->compatible;
+		}
+	}
+
+	if (!name) {
+		dev_err_probe(&indio_dev->dev, PTR_ERR(indio_dev),
+			      "parameter parsing returned an error\n");
+		return -EINVAL;
+	}
+
+	mutex_init(&chip_info->lock);
+
+	/*
+	 * do now any chip specific initialization (e.g. read/write
+	 * some registers), enable/disable certain channels, change the sampling
+	 * rate to the requested value
+	 */
+	ret = pac193x_chip_configure(chip_info);
+	if (ret < 0)
+		goto free_chan_attr_mem;
+
+	/* prepare the channel information */
+	ret = pac193x_prep_iio_channels(chip_info, indio_dev);
+	if (ret < 0)
+		goto free_chan_attr_mem;
+
+	ret = pac193x_prep_custom_attributes(chip_info, indio_dev);
+	if (ret < 0) {
+		dev_err_probe(&indio_dev->dev, ret,
+			      "Can't configure custom attributes for PAC193X device\n");
+		goto free_chan_attr_mem;
+	}
+
+	chip_info->pac193x_info.read_raw = pac193x_read_raw;
+	chip_info->pac193x_info.read_avail = pac193x_read_avail;
+	chip_info->pac193x_info.write_raw = pac193x_write_raw;
+
+	indio_dev->info = &chip_info->pac193x_info;
+	indio_dev->name = name;
+	indio_dev->dev.parent = &client->dev;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	/*
+	 * read whatever it has been accumulated in the chip so far
+	 * and reset the accumulators
+	 */
+	ret = pac193x_reg_snapshot(chip_info, true, false, PAC193X_MIN_UPDATE_WAIT_TIME_MS);
+	if (ret < 0)
+		goto free_chan_attr_mem;
+
+	/* register with IIO */
+	ret = devm_iio_device_register(&client->dev, indio_dev);
+	if (ret < 0) {
+		dev_err_probe(&indio_dev->dev, ret,
+			      "Can't register IIO device\n");
+		goto free_chan_attr_mem;
+	}
+
+	return 0;
+
+free_chan_attr_mem:
+	pac193x_remove(client);
+
+	return ret;
+}
+
+static const struct i2c_device_id pac193x_id[] = {
+	{ "pac1934", pac1934 },
+	{ "pac1933", pac1933 },
+	{ "pac1932", pac1932 },
+	{ "pac1931", pac1931 },
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, pac193x_id);
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id pac193x_acpi_match[] = {
+	{"MCHP1930", 0},
+	{ },
+};
+MODULE_DEVICE_TABLE(acpi, pac193x_acpi_match);
+#endif
+
+static struct i2c_driver pac193x_driver = {
+	.driver	 = {
+		.name = "pac193x",
+		.of_match_table = pac193x_of_match,
+		.acpi_match_table = ACPI_PTR(pac193x_acpi_match)
+		},
+	.probe = pac193x_probe,
+	.remove = pac193x_remove,
+	.id_table = pac193x_id,
+};
+
+module_i2c_driver(pac193x_driver);
+
+MODULE_AUTHOR("Bogdan Bolocan <bogdan.bolocan@microchip.com>");
+MODULE_AUTHOR("Victor Tudose");
+MODULE_AUTHOR("Marius Cristea <marius.cristea@microchip.com>");
+MODULE_DESCRIPTION("IIO driver for PAC193X Multi-Channel DC Power/Energy Monitor");
+MODULE_LICENSE("GPL");