From 2d9ab11c262e39f3bc829d0679257ab942d5878f Mon Sep 17 00:00:00 2001 From: Tao Zhang Date: Sun, 4 Feb 2024 13:30:37 +0800 Subject: coresight-tpdm: Add support to configure CMB TPDM CMB subunits support two forms of CMB data set element creation: continuous and trace-on-change collection mode. Continuous change creates CMB data set elements on every CMBCLK edge. Trace-on-change creates CMB data set elements only when a new data set element differs in value from the previous element in a CMB data set. Set CMB_CR.MODE to 0 for continuous CMB collection mode. Set CMB_CR.MODE to 1 for trace-on-change CMB collection mode. Reviewed-by: James Clark Signed-off-by: Tao Zhang Signed-off-by: Jinlong Mao Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/1707024641-22460-7-git-send-email-quic_taozha@quicinc.com --- Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm index 4dd49b159543..6dfb18d6d64a 100644 --- a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm +++ b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm @@ -170,3 +170,17 @@ Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) /cmb_mode +Date: January 2024 +KernelVersion 6.9 +Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Description: (Write) Set the data collection mode of CMB tpdm. Continuous + change creates CMB data set elements on every CMBCLK edge. + Trace-on-change creates CMB data set elements only when a new + data set element differs in value from the previous element + in a CMB data set. + + Accepts only one of the 2 values - 0 or 1. + 0 : Continuous CMB collection mode. + 1 : Trace-on-change CMB collection mode. -- cgit v1.2.3 From 53d4a017a52458d835a1f9524df09c65c2208400 Mon Sep 17 00:00:00 2001 From: Tao Zhang Date: Sun, 4 Feb 2024 13:30:38 +0800 Subject: coresight-tpdm: Add pattern registers support for CMB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Timestamps are requested if the monitor’s CMB data set unit input data matches the value in the Monitor CMB timestamp pattern and mask registers (M_CMB_TPR and M_CMB_TPMR) when CMB timestamp enabled via the timestamp insertion enable register bit(CMB_TIER.PATT_TSENAB). The pattern match trigger output is achieved via setting values into the CMB trigger pattern and mask registers (CMB_XPR and CMB_XPMR). After configuring a pattern through these registers, the TPDM subunit will assert an output trigger every time it receives new input data that matches the configured pattern value. Values in a given bit number of the mask register correspond to the same bit number in the corresponding pattern register. Reviewed-by: James Clark Signed-off-by: Tao Zhang Signed-off-by: Jinlong Mao Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/1707024641-22460-8-git-send-email-quic_taozha@quicinc.com --- .../ABI/testing/sysfs-bus-coresight-devices-tpdm | 30 +++++++ drivers/hwtracing/coresight/coresight-tpdm.c | 96 +++++++++++++++++++++- drivers/hwtracing/coresight/coresight-tpdm.h | 39 +++++++++ 3 files changed, 164 insertions(+), 1 deletion(-) (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm index 6dfb18d6d64a..b6cf050861ed 100644 --- a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm +++ b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm @@ -184,3 +184,33 @@ Description: (Write) Set the data collection mode of CMB tpdm. Continuous Accepts only one of the 2 values - 0 or 1. 0 : Continuous CMB collection mode. 1 : Trace-on-change CMB collection mode. + +What: /sys/bus/coresight/devices//cmb_trig_patt/xpr[0:1] +Date: January 2024 +KernelVersion 6.9 +Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Description: + (RW) Set/Get the value of the trigger pattern for the CMB + subunit TPDM. + +What: /sys/bus/coresight/devices//cmb_trig_patt/xpmr[0:1] +Date: January 2024 +KernelVersion 6.9 +Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Description: + (RW) Set/Get the mask of the trigger pattern for the CMB + subunit TPDM. + +What: /sys/bus/coresight/devices//dsb_patt/tpr[0:1] +Date: January 2024 +KernelVersion 6.9 +Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Description: + (RW) Set/Get the value of the pattern for the CMB subunit TPDM. + +What: /sys/bus/coresight/devices//dsb_patt/tpmr[0:1] +Date: January 2024 +KernelVersion 6.9 +Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Description: + (RW) Set/Get the mask of the pattern for the CMB subunit TPDM. diff --git a/drivers/hwtracing/coresight/coresight-tpdm.c b/drivers/hwtracing/coresight/coresight-tpdm.c index b20071460375..07587287d9fa 100644 --- a/drivers/hwtracing/coresight/coresight-tpdm.c +++ b/drivers/hwtracing/coresight/coresight-tpdm.c @@ -66,6 +66,26 @@ static ssize_t tpdm_simple_dataset_show(struct device *dev, return -EINVAL; return sysfs_emit(buf, "0x%x\n", drvdata->dsb->msr[tpdm_attr->idx]); + case CMB_TRIG_PATT: + if (tpdm_attr->idx >= TPDM_CMB_MAX_PATT) + return -EINVAL; + return sysfs_emit(buf, "0x%x\n", + drvdata->cmb->trig_patt[tpdm_attr->idx]); + case CMB_TRIG_PATT_MASK: + if (tpdm_attr->idx >= TPDM_CMB_MAX_PATT) + return -EINVAL; + return sysfs_emit(buf, "0x%x\n", + drvdata->cmb->trig_patt_mask[tpdm_attr->idx]); + case CMB_PATT: + if (tpdm_attr->idx >= TPDM_CMB_MAX_PATT) + return -EINVAL; + return sysfs_emit(buf, "0x%x\n", + drvdata->cmb->patt_val[tpdm_attr->idx]); + case CMB_PATT_MASK: + if (tpdm_attr->idx >= TPDM_CMB_MAX_PATT) + return -EINVAL; + return sysfs_emit(buf, "0x%x\n", + drvdata->cmb->patt_mask[tpdm_attr->idx]); } return -EINVAL; } @@ -118,6 +138,30 @@ static ssize_t tpdm_simple_dataset_store(struct device *dev, ret = size; } break; + case CMB_TRIG_PATT: + if (tpdm_attr->idx < TPDM_CMB_MAX_PATT) { + drvdata->cmb->trig_patt[tpdm_attr->idx] = val; + ret = size; + } + break; + case CMB_TRIG_PATT_MASK: + if (tpdm_attr->idx < TPDM_CMB_MAX_PATT) { + drvdata->cmb->trig_patt_mask[tpdm_attr->idx] = val; + ret = size; + } + break; + case CMB_PATT: + if (tpdm_attr->idx < TPDM_CMB_MAX_PATT) { + drvdata->cmb->patt_val[tpdm_attr->idx] = val; + ret = size; + } + break; + case CMB_PATT_MASK: + if (tpdm_attr->idx < TPDM_CMB_MAX_PATT) { + drvdata->cmb->patt_mask[tpdm_attr->idx] = val; + ret = size; + } + break; default: break; } @@ -280,12 +324,32 @@ static void tpdm_enable_dsb(struct tpdm_drvdata *drvdata) static void tpdm_enable_cmb(struct tpdm_drvdata *drvdata) { - u32 val; + u32 val, i; if (!tpdm_has_cmb_dataset(drvdata)) return; + /* Configure pattern registers */ + for (i = 0; i < TPDM_CMB_MAX_PATT; i++) { + writel_relaxed(drvdata->cmb->patt_val[i], + drvdata->base + TPDM_CMB_TPR(i)); + writel_relaxed(drvdata->cmb->patt_mask[i], + drvdata->base + TPDM_CMB_TPMR(i)); + writel_relaxed(drvdata->cmb->trig_patt[i], + drvdata->base + TPDM_CMB_XPR(i)); + writel_relaxed(drvdata->cmb->trig_patt_mask[i], + drvdata->base + TPDM_CMB_XPMR(i)); + } + val = readl_relaxed(drvdata->base + TPDM_CMB_CR); + /* + * Set to 0 for continuous CMB collection mode, + * 1 for trace-on-change CMB collection mode. + */ + if (drvdata->cmb->trace_mode) + val |= TPDM_CMB_CR_MODE; + else + val &= ~TPDM_CMB_CR_MODE; /* Set the enable bit of CMB control register to 1 */ val |= TPDM_CMB_CR_ENA; writel_relaxed(val, drvdata->base + TPDM_CMB_CR); @@ -887,6 +951,22 @@ static struct attribute *tpdm_dsb_msr_attrs[] = { NULL, }; +static struct attribute *tpdm_cmb_trig_patt_attrs[] = { + CMB_TRIG_PATT_ATTR(0), + CMB_TRIG_PATT_ATTR(1), + CMB_TRIG_PATT_MASK_ATTR(0), + CMB_TRIG_PATT_MASK_ATTR(1), + NULL, +}; + +static struct attribute *tpdm_cmb_patt_attrs[] = { + CMB_PATT_ATTR(0), + CMB_PATT_ATTR(1), + CMB_PATT_MASK_ATTR(0), + CMB_PATT_MASK_ATTR(1), + NULL, +}; + static struct attribute *tpdm_dsb_attrs[] = { &dev_attr_dsb_mode.attr, &dev_attr_dsb_trig_ts.attr, @@ -933,6 +1013,18 @@ static struct attribute_group tpdm_cmb_attr_grp = { .is_visible = tpdm_cmb_is_visible, }; +static struct attribute_group tpdm_cmb_trig_patt_grp = { + .attrs = tpdm_cmb_trig_patt_attrs, + .is_visible = tpdm_cmb_is_visible, + .name = "cmb_trig_patt", +}; + +static struct attribute_group tpdm_cmb_patt_grp = { + .attrs = tpdm_cmb_patt_attrs, + .is_visible = tpdm_cmb_is_visible, + .name = "cmb_patt", +}; + static const struct attribute_group *tpdm_attr_grps[] = { &tpdm_attr_grp, &tpdm_dsb_attr_grp, @@ -941,6 +1033,8 @@ static const struct attribute_group *tpdm_attr_grps[] = { &tpdm_dsb_patt_grp, &tpdm_dsb_msr_grp, &tpdm_cmb_attr_grp, + &tpdm_cmb_trig_patt_grp, + &tpdm_cmb_patt_grp, NULL, }; diff --git a/drivers/hwtracing/coresight/coresight-tpdm.h b/drivers/hwtracing/coresight/coresight-tpdm.h index 2af92c270ed1..e5d4c22cd38c 100644 --- a/drivers/hwtracing/coresight/coresight-tpdm.h +++ b/drivers/hwtracing/coresight/coresight-tpdm.h @@ -11,12 +11,23 @@ /* CMB Subunit Registers */ #define TPDM_CMB_CR (0xA00) +/* CMB subunit timestamp pattern registers */ +#define TPDM_CMB_TPR(n) (0xA08 + (n * 4)) +/* CMB subunit timestamp pattern mask registers */ +#define TPDM_CMB_TPMR(n) (0xA10 + (n * 4)) +/* CMB subunit trigger pattern registers */ +#define TPDM_CMB_XPR(n) (0xA18 + (n * 4)) +/* CMB subunit trigger pattern mask registers */ +#define TPDM_CMB_XPMR(n) (0xA20 + (n * 4)) /* Enable bit for CMB subunit */ #define TPDM_CMB_CR_ENA BIT(0) /* Trace collection mode for CMB subunit */ #define TPDM_CMB_CR_MODE BIT(1) +/* Patten register number */ +#define TPDM_CMB_MAX_PATT 2 + /* DSB Subunit Registers */ #define TPDM_DSB_CR (0x780) #define TPDM_DSB_TIER (0x784) @@ -151,6 +162,22 @@ tpdm_simple_dataset_rw(msr##nr, \ DSB_MSR, nr) +#define CMB_TRIG_PATT_ATTR(nr) \ + tpdm_simple_dataset_rw(xpr##nr, \ + CMB_TRIG_PATT, nr) + +#define CMB_TRIG_PATT_MASK_ATTR(nr) \ + tpdm_simple_dataset_rw(xpmr##nr, \ + CMB_TRIG_PATT_MASK, nr) + +#define CMB_PATT_ATTR(nr) \ + tpdm_simple_dataset_rw(tpr##nr, \ + CMB_PATT, nr) + +#define CMB_PATT_MASK_ATTR(nr) \ + tpdm_simple_dataset_rw(tpmr##nr, \ + CMB_PATT_MASK, nr) + /** * struct dsb_dataset - specifics associated to dsb dataset * @mode: DSB programming mode @@ -186,9 +213,17 @@ struct dsb_dataset { /** * struct cmb_dataset * @trace_mode: Dataset collection mode + * @patt_val: Save value for pattern + * @patt_mask: Save value for pattern mask + * @trig_patt: Save value for trigger pattern + * @trig_patt_mask: Save value for trigger pattern mask */ struct cmb_dataset { u32 trace_mode; + u32 patt_val[TPDM_CMB_MAX_PATT]; + u32 patt_mask[TPDM_CMB_MAX_PATT]; + u32 trig_patt[TPDM_CMB_MAX_PATT]; + u32 trig_patt_mask[TPDM_CMB_MAX_PATT]; }; /** @@ -225,6 +260,10 @@ enum dataset_mem { DSB_PATT, DSB_PATT_MASK, DSB_MSR, + CMB_TRIG_PATT, + CMB_TRIG_PATT_MASK, + CMB_PATT, + CMB_PATT_MASK }; /** -- cgit v1.2.3 From dc6ce57e2aa0b10b0b78517245ea0ba47eed75b1 Mon Sep 17 00:00:00 2001 From: Tao Zhang Date: Sun, 4 Feb 2024 13:30:39 +0800 Subject: coresight-tpdm: Add timestamp control register support for the CMB CMB_TIER register is CMB subunit timestamp insertion enable register. Bit 0 is PATT_TSENAB bit. Set this bit to 1 to request a timestamp following a CMB interface pattern match. Bit 1 is XTRIG_TSENAB bit. Set this bit to 1 to request a timestamp following a CMB CTI timestamp request. Bit 2 is TS_ALL bit. Set this bit to 1 to request timestamp for all packets. Reviewed-by: James Clark Signed-off-by: Tao Zhang Signed-off-by: Jinlong Mao Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/1707024641-22460-9-git-send-email-quic_taozha@quicinc.com --- .../ABI/testing/sysfs-bus-coresight-devices-tpdm | 35 ++++++ drivers/hwtracing/coresight/coresight-tpdm.c | 121 +++++++++++++++++++-- drivers/hwtracing/coresight/coresight-tpdm.h | 31 ++++++ 3 files changed, 180 insertions(+), 7 deletions(-) (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm index b6cf050861ed..e8c76ce4dd15 100644 --- a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm +++ b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm @@ -214,3 +214,38 @@ KernelVersion 6.9 Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) Description: (RW) Set/Get the mask of the pattern for the CMB subunit TPDM. + +What: /sys/bus/coresight/devices//cmb_patt/enable_ts +Date: January 2024 +KernelVersion 6.9 +Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Description: + (Write) Set the pattern timestamp of CMB tpdm. Read + the pattern timestamp of CMB tpdm. + + Accepts only one of the 2 values - 0 or 1. + 0 : Disable CMB pattern timestamp. + 1 : Enable CMB pattern timestamp. + +What: /sys/bus/coresight/devices//cmb_trig_ts +Date: January 2024 +KernelVersion 6.9 +Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Description: + (RW) Set/Get the trigger timestamp of the CMB for tpdm. + + Accepts only one of the 2 values - 0 or 1. + 0 : Set the CMB trigger type to false + 1 : Set the CMB trigger type to true + +What: /sys/bus/coresight/devices//cmb_ts_all +Date: January 2024 +KernelVersion 6.9 +Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Description: + (RW) Read or write the status of timestamp upon all interface. + Only value 0 and 1 can be written to this node. Set this node to 1 to requeset + timestamp to all trace packet. + Accepts only one of the 2 values - 0 or 1. + 0 : Disable the timestamp of all trace packets. + 1 : Enable the timestamp of all trace packets. diff --git a/drivers/hwtracing/coresight/coresight-tpdm.c b/drivers/hwtracing/coresight/coresight-tpdm.c index 07587287d9fa..22966d541230 100644 --- a/drivers/hwtracing/coresight/coresight-tpdm.c +++ b/drivers/hwtracing/coresight/coresight-tpdm.c @@ -322,6 +322,31 @@ static void tpdm_enable_dsb(struct tpdm_drvdata *drvdata) writel_relaxed(val, drvdata->base + TPDM_DSB_CR); } +static void set_cmb_tier(struct tpdm_drvdata *drvdata) +{ + u32 val; + + val = readl_relaxed(drvdata->base + TPDM_CMB_TIER); + + /* Clear all relevant fields */ + val &= ~(TPDM_CMB_TIER_PATT_TSENAB | TPDM_CMB_TIER_TS_ALL | + TPDM_CMB_TIER_XTRIG_TSENAB); + + /* Set pattern timestamp type and enablement */ + if (drvdata->cmb->patt_ts) + val |= TPDM_CMB_TIER_PATT_TSENAB; + + /* Set trigger timestamp */ + if (drvdata->cmb->trig_ts) + val |= TPDM_CMB_TIER_XTRIG_TSENAB; + + /* Set all timestamp enablement*/ + if (drvdata->cmb->ts_all) + val |= TPDM_CMB_TIER_TS_ALL; + + writel_relaxed(val, drvdata->base + TPDM_CMB_TIER); +} + static void tpdm_enable_cmb(struct tpdm_drvdata *drvdata) { u32 val, i; @@ -341,6 +366,8 @@ static void tpdm_enable_cmb(struct tpdm_drvdata *drvdata) drvdata->base + TPDM_CMB_XPMR(i)); } + set_cmb_tier(drvdata); + val = readl_relaxed(drvdata->base + TPDM_CMB_CR); /* * Set to 0 for continuous CMB collection mode, @@ -687,9 +714,18 @@ static ssize_t enable_ts_show(struct device *dev, char *buf) { struct tpdm_drvdata *drvdata = dev_get_drvdata(dev->parent); + struct tpdm_dataset_attribute *tpdm_attr = + container_of(attr, struct tpdm_dataset_attribute, attr); + ssize_t size = -EINVAL; - return sysfs_emit(buf, "%u\n", - (unsigned int)drvdata->dsb->patt_ts); + if (tpdm_attr->mem == DSB_PATT) + size = sysfs_emit(buf, "%u\n", + (unsigned int)drvdata->dsb->patt_ts); + else if (tpdm_attr->mem == CMB_PATT) + size = sysfs_emit(buf, "%u\n", + (unsigned int)drvdata->cmb->patt_ts); + + return size; } /* @@ -701,17 +737,23 @@ static ssize_t enable_ts_store(struct device *dev, size_t size) { struct tpdm_drvdata *drvdata = dev_get_drvdata(dev->parent); + struct tpdm_dataset_attribute *tpdm_attr = + container_of(attr, struct tpdm_dataset_attribute, attr); unsigned long val; if ((kstrtoul(buf, 0, &val)) || (val & ~1UL)) return -EINVAL; - spin_lock(&drvdata->spinlock); - drvdata->dsb->patt_ts = !!val; - spin_unlock(&drvdata->spinlock); + guard(spinlock)(&drvdata->spinlock); + if (tpdm_attr->mem == DSB_PATT) + drvdata->dsb->patt_ts = !!val; + else if (tpdm_attr->mem == CMB_PATT) + drvdata->cmb->patt_ts = !!val; + else + return -EINVAL; + return size; } -static DEVICE_ATTR_RW(enable_ts); static ssize_t set_type_show(struct device *dev, struct device_attribute *attr, @@ -842,6 +884,68 @@ static ssize_t cmb_mode_store(struct device *dev, } static DEVICE_ATTR_RW(cmb_mode); +static ssize_t cmb_ts_all_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct tpdm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + return sysfs_emit(buf, "%u\n", + (unsigned int)drvdata->cmb->ts_all); +} + +static ssize_t cmb_ts_all_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t size) +{ + struct tpdm_drvdata *drvdata = dev_get_drvdata(dev->parent); + unsigned long val; + + if ((kstrtoul(buf, 0, &val)) || (val & ~1UL)) + return -EINVAL; + + guard(spinlock)(&drvdata->spinlock); + if (val) + drvdata->cmb->ts_all = true; + else + drvdata->cmb->ts_all = false; + + return size; +} +static DEVICE_ATTR_RW(cmb_ts_all); + +static ssize_t cmb_trig_ts_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct tpdm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + return sysfs_emit(buf, "%u\n", + (unsigned int)drvdata->cmb->trig_ts); +} + +static ssize_t cmb_trig_ts_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t size) +{ + struct tpdm_drvdata *drvdata = dev_get_drvdata(dev->parent); + unsigned long val; + + if ((kstrtoul(buf, 0, &val)) || (val & ~1UL)) + return -EINVAL; + + guard(spinlock)(&drvdata->spinlock); + if (val) + drvdata->cmb->trig_ts = true; + else + drvdata->cmb->trig_ts = false; + + return size; +} +static DEVICE_ATTR_RW(cmb_trig_ts); + static struct attribute *tpdm_dsb_edge_attrs[] = { &dev_attr_ctrl_idx.attr, &dev_attr_ctrl_val.attr, @@ -910,7 +1014,7 @@ static struct attribute *tpdm_dsb_patt_attrs[] = { DSB_PATT_MASK_ATTR(5), DSB_PATT_MASK_ATTR(6), DSB_PATT_MASK_ATTR(7), - &dev_attr_enable_ts.attr, + DSB_PATT_ENABLE_TS, &dev_attr_set_type.attr, NULL, }; @@ -964,6 +1068,7 @@ static struct attribute *tpdm_cmb_patt_attrs[] = { CMB_PATT_ATTR(1), CMB_PATT_MASK_ATTR(0), CMB_PATT_MASK_ATTR(1), + CMB_PATT_ENABLE_TS, NULL, }; @@ -976,6 +1081,8 @@ static struct attribute *tpdm_dsb_attrs[] = { static struct attribute *tpdm_cmb_attrs[] = { &dev_attr_cmb_mode.attr, + &dev_attr_cmb_ts_all.attr, + &dev_attr_cmb_trig_ts.attr, NULL, }; diff --git a/drivers/hwtracing/coresight/coresight-tpdm.h b/drivers/hwtracing/coresight/coresight-tpdm.h index e5d4c22cd38c..6d1fe4aed7d0 100644 --- a/drivers/hwtracing/coresight/coresight-tpdm.h +++ b/drivers/hwtracing/coresight/coresight-tpdm.h @@ -11,6 +11,8 @@ /* CMB Subunit Registers */ #define TPDM_CMB_CR (0xA00) +/* CMB subunit timestamp insertion enable register */ +#define TPDM_CMB_TIER (0xA04) /* CMB subunit timestamp pattern registers */ #define TPDM_CMB_TPR(n) (0xA08 + (n * 4)) /* CMB subunit timestamp pattern mask registers */ @@ -24,6 +26,12 @@ #define TPDM_CMB_CR_ENA BIT(0) /* Trace collection mode for CMB subunit */ #define TPDM_CMB_CR_MODE BIT(1) +/* Timestamp control for pattern match */ +#define TPDM_CMB_TIER_PATT_TSENAB BIT(0) +/* CMB CTI timestamp request */ +#define TPDM_CMB_TIER_XTRIG_TSENAB BIT(1) +/* For timestamp fo all trace */ +#define TPDM_CMB_TIER_TS_ALL BIT(2) /* Patten register number */ #define TPDM_CMB_MAX_PATT 2 @@ -134,6 +142,15 @@ } \ })[0].attr.attr) +#define tpdm_patt_enable_ts(name, mem) \ + (&((struct tpdm_dataset_attribute[]) { \ + { \ + __ATTR(name, 0644, enable_ts_show, \ + enable_ts_store), \ + mem, \ + } \ + })[0].attr.attr) + #define DSB_EDGE_CTRL_ATTR(nr) \ tpdm_simple_dataset_ro(edcr##nr, \ DSB_EDGE_CTRL, nr) @@ -158,6 +175,10 @@ tpdm_simple_dataset_rw(tpmr##nr, \ DSB_PATT_MASK, nr) +#define DSB_PATT_ENABLE_TS \ + tpdm_patt_enable_ts(enable_ts, \ + DSB_PATT) + #define DSB_MSR_ATTR(nr) \ tpdm_simple_dataset_rw(msr##nr, \ DSB_MSR, nr) @@ -178,6 +199,10 @@ tpdm_simple_dataset_rw(tpmr##nr, \ CMB_PATT_MASK, nr) +#define CMB_PATT_ENABLE_TS \ + tpdm_patt_enable_ts(enable_ts, \ + CMB_PATT) + /** * struct dsb_dataset - specifics associated to dsb dataset * @mode: DSB programming mode @@ -217,6 +242,9 @@ struct dsb_dataset { * @patt_mask: Save value for pattern mask * @trig_patt: Save value for trigger pattern * @trig_patt_mask: Save value for trigger pattern mask + * @patt_ts: Indicates if pattern match for timestamp is enabled. + * @trig_ts: Indicates if CTI trigger for timestamp is enabled. + * @ts_all: Indicates if timestamp is enabled for all packets. */ struct cmb_dataset { u32 trace_mode; @@ -224,6 +252,9 @@ struct cmb_dataset { u32 patt_mask[TPDM_CMB_MAX_PATT]; u32 trig_patt[TPDM_CMB_MAX_PATT]; u32 trig_patt_mask[TPDM_CMB_MAX_PATT]; + bool patt_ts; + bool trig_ts; + bool ts_all; }; /** -- cgit v1.2.3 From 8e8804145a46e092c5207bb1258d29236a2cbcaa Mon Sep 17 00:00:00 2001 From: Tao Zhang Date: Sun, 4 Feb 2024 13:30:41 +0800 Subject: coresight-tpdm: Add msr register support for CMB Add the nodes for CMB subunit MSR(mux select register) support. CMB MSRs(mux select registers) is to separate mux, arbitration, interleaving,data packing control from stream filtering control. Reviewed-by: James Clark Signed-off-by: Tao Zhang Signed-off-by: Mao Jinlong Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/1707024641-22460-11-git-send-email-quic_taozha@quicinc.com --- .../ABI/testing/sysfs-bus-coresight-devices-tpdm | 8 ++ drivers/hwtracing/coresight/coresight-tpdm.c | 85 ++++++++++++++++++++++ drivers/hwtracing/coresight/coresight-tpdm.h | 16 +++- 3 files changed, 108 insertions(+), 1 deletion(-) (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm index e8c76ce4dd15..b4d0fc8d319d 100644 --- a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm +++ b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm @@ -249,3 +249,11 @@ Description: Accepts only one of the 2 values - 0 or 1. 0 : Disable the timestamp of all trace packets. 1 : Enable the timestamp of all trace packets. + +What: /sys/bus/coresight/devices//cmb_msr/msr[0:31] +Date: January 2024 +KernelVersion 6.9 +Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Description: + (RW) Set/Get the MSR(mux select register) for the CMB subunit + TPDM. diff --git a/drivers/hwtracing/coresight/coresight-tpdm.c b/drivers/hwtracing/coresight/coresight-tpdm.c index 22966d541230..a9708ab0d488 100644 --- a/drivers/hwtracing/coresight/coresight-tpdm.c +++ b/drivers/hwtracing/coresight/coresight-tpdm.c @@ -86,6 +86,11 @@ static ssize_t tpdm_simple_dataset_show(struct device *dev, return -EINVAL; return sysfs_emit(buf, "0x%x\n", drvdata->cmb->patt_mask[tpdm_attr->idx]); + case CMB_MSR: + if (tpdm_attr->idx >= drvdata->cmb_msr_num) + return -EINVAL; + return sysfs_emit(buf, "0x%x\n", + drvdata->cmb->msr[tpdm_attr->idx]); } return -EINVAL; } @@ -162,6 +167,12 @@ static ssize_t tpdm_simple_dataset_store(struct device *dev, ret = size; } break; + case CMB_MSR: + if (tpdm_attr->idx < drvdata->cmb_msr_num) { + drvdata->cmb->msr[tpdm_attr->idx] = val; + ret = size; + } + break; default: break; } @@ -209,6 +220,23 @@ static umode_t tpdm_dsb_msr_is_visible(struct kobject *kobj, return 0; } +static umode_t tpdm_cmb_msr_is_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + struct device *dev = kobj_to_dev(kobj); + struct tpdm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + struct device_attribute *dev_attr = + container_of(attr, struct device_attribute, attr); + struct tpdm_dataset_attribute *tpdm_attr = + container_of(dev_attr, struct tpdm_dataset_attribute, attr); + + if (tpdm_attr->idx < drvdata->cmb_msr_num) + return attr->mode; + + return 0; +} + static void tpdm_reset_datasets(struct tpdm_drvdata *drvdata) { if (tpdm_has_dsb_dataset(drvdata)) { @@ -347,6 +375,15 @@ static void set_cmb_tier(struct tpdm_drvdata *drvdata) writel_relaxed(val, drvdata->base + TPDM_CMB_TIER); } +static void set_cmb_msr(struct tpdm_drvdata *drvdata) +{ + int i; + + for (i = 0; i < drvdata->cmb_msr_num; i++) + writel_relaxed(drvdata->cmb->msr[i], + drvdata->base + TPDM_CMB_MSR(i)); +} + static void tpdm_enable_cmb(struct tpdm_drvdata *drvdata) { u32 val, i; @@ -367,6 +404,7 @@ static void tpdm_enable_cmb(struct tpdm_drvdata *drvdata) } set_cmb_tier(drvdata); + set_cmb_msr(drvdata); val = readl_relaxed(drvdata->base + TPDM_CMB_CR); /* @@ -1072,6 +1110,42 @@ static struct attribute *tpdm_cmb_patt_attrs[] = { NULL, }; +static struct attribute *tpdm_cmb_msr_attrs[] = { + CMB_MSR_ATTR(0), + CMB_MSR_ATTR(1), + CMB_MSR_ATTR(2), + CMB_MSR_ATTR(3), + CMB_MSR_ATTR(4), + CMB_MSR_ATTR(5), + CMB_MSR_ATTR(6), + CMB_MSR_ATTR(7), + CMB_MSR_ATTR(8), + CMB_MSR_ATTR(9), + CMB_MSR_ATTR(10), + CMB_MSR_ATTR(11), + CMB_MSR_ATTR(12), + CMB_MSR_ATTR(13), + CMB_MSR_ATTR(14), + CMB_MSR_ATTR(15), + CMB_MSR_ATTR(16), + CMB_MSR_ATTR(17), + CMB_MSR_ATTR(18), + CMB_MSR_ATTR(19), + CMB_MSR_ATTR(20), + CMB_MSR_ATTR(21), + CMB_MSR_ATTR(22), + CMB_MSR_ATTR(23), + CMB_MSR_ATTR(24), + CMB_MSR_ATTR(25), + CMB_MSR_ATTR(26), + CMB_MSR_ATTR(27), + CMB_MSR_ATTR(28), + CMB_MSR_ATTR(29), + CMB_MSR_ATTR(30), + CMB_MSR_ATTR(31), + NULL, +}; + static struct attribute *tpdm_dsb_attrs[] = { &dev_attr_dsb_mode.attr, &dev_attr_dsb_trig_ts.attr, @@ -1132,6 +1206,12 @@ static struct attribute_group tpdm_cmb_patt_grp = { .name = "cmb_patt", }; +static struct attribute_group tpdm_cmb_msr_grp = { + .attrs = tpdm_cmb_msr_attrs, + .is_visible = tpdm_cmb_msr_is_visible, + .name = "cmb_msr", +}; + static const struct attribute_group *tpdm_attr_grps[] = { &tpdm_attr_grp, &tpdm_dsb_attr_grp, @@ -1142,6 +1222,7 @@ static const struct attribute_group *tpdm_attr_grps[] = { &tpdm_cmb_attr_grp, &tpdm_cmb_trig_patt_grp, &tpdm_cmb_patt_grp, + &tpdm_cmb_msr_grp, NULL, }; @@ -1180,6 +1261,10 @@ static int tpdm_probe(struct amba_device *adev, const struct amba_id *id) of_property_read_u32(drvdata->dev->of_node, "qcom,dsb-msrs-num", &drvdata->dsb_msr_num); + if (drvdata && tpdm_has_cmb_dataset(drvdata)) + of_property_read_u32(drvdata->dev->of_node, + "qcom,cmb-msrs-num", &drvdata->cmb_msr_num); + /* Set up coresight component description */ desc.name = coresight_alloc_device_name(&tpdm_devs, dev); if (!desc.name) diff --git a/drivers/hwtracing/coresight/coresight-tpdm.h b/drivers/hwtracing/coresight/coresight-tpdm.h index 6d1fe4aed7d0..f3a8f56d0fe7 100644 --- a/drivers/hwtracing/coresight/coresight-tpdm.h +++ b/drivers/hwtracing/coresight/coresight-tpdm.h @@ -21,6 +21,8 @@ #define TPDM_CMB_XPR(n) (0xA18 + (n * 4)) /* CMB subunit trigger pattern mask registers */ #define TPDM_CMB_XPMR(n) (0xA20 + (n * 4)) +/* CMB MSR register */ +#define TPDM_CMB_MSR(n) (0xA80 + (n * 4)) /* Enable bit for CMB subunit */ #define TPDM_CMB_CR_ENA BIT(0) @@ -36,6 +38,9 @@ /* Patten register number */ #define TPDM_CMB_MAX_PATT 2 +/* MAX number of DSB MSR */ +#define TPDM_CMB_MAX_MSR 32 + /* DSB Subunit Registers */ #define TPDM_DSB_CR (0x780) #define TPDM_DSB_TIER (0x784) @@ -203,6 +208,10 @@ tpdm_patt_enable_ts(enable_ts, \ CMB_PATT) +#define CMB_MSR_ATTR(nr) \ + tpdm_simple_dataset_rw(msr##nr, \ + CMB_MSR, nr) + /** * struct dsb_dataset - specifics associated to dsb dataset * @mode: DSB programming mode @@ -242,6 +251,7 @@ struct dsb_dataset { * @patt_mask: Save value for pattern mask * @trig_patt: Save value for trigger pattern * @trig_patt_mask: Save value for trigger pattern mask + * @msr Save value for MSR * @patt_ts: Indicates if pattern match for timestamp is enabled. * @trig_ts: Indicates if CTI trigger for timestamp is enabled. * @ts_all: Indicates if timestamp is enabled for all packets. @@ -252,6 +262,7 @@ struct cmb_dataset { u32 patt_mask[TPDM_CMB_MAX_PATT]; u32 trig_patt[TPDM_CMB_MAX_PATT]; u32 trig_patt_mask[TPDM_CMB_MAX_PATT]; + u32 msr[TPDM_CMB_MAX_MSR]; bool patt_ts; bool trig_ts; bool ts_all; @@ -268,6 +279,7 @@ struct cmb_dataset { * @dsb Specifics associated to TPDM DSB. * @cmb Specifics associated to TPDM CMB. * @dsb_msr_num Number of MSR supported by DSB TPDM + * @cmb_msr_num Number of MSR supported by CMB TPDM */ struct tpdm_drvdata { @@ -280,6 +292,7 @@ struct tpdm_drvdata { struct dsb_dataset *dsb; struct cmb_dataset *cmb; u32 dsb_msr_num; + u32 cmb_msr_num; }; /* Enumerate members of various datasets */ @@ -294,7 +307,8 @@ enum dataset_mem { CMB_TRIG_PATT, CMB_TRIG_PATT_MASK, CMB_PATT, - CMB_PATT_MASK + CMB_PATT_MASK, + CMB_MSR }; /** -- cgit v1.2.3 From 0fb528c8255bd2de6a2fba26ed28d75a7f0cb630 Mon Sep 17 00:00:00 2001 From: Marius Cristea Date: Thu, 22 Feb 2024 18:42:06 +0200 Subject: iio: adc: adding support for PAC193x This is the iio driver for Microchip PAC193X series of Power Monitor with Accumulator chip family. Signed-off-by: Marius Cristea Link: https://lore.kernel.org/r/20240222164206.65700-3-marius.cristea@microchip.com Signed-off-by: Jonathan Cameron --- .../ABI/testing/sysfs-bus-iio-adc-pac1934 | 9 + MAINTAINERS | 7 + drivers/iio/adc/Kconfig | 11 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/pac1934.c | 1636 ++++++++++++++++++++ 5 files changed, 1664 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-adc-pac1934 create mode 100644 drivers/iio/adc/pac1934.c (limited to 'Documentation/ABI/testing') diff --git a/Documentation/ABI/testing/sysfs-bus-iio-adc-pac1934 b/Documentation/ABI/testing/sysfs-bus-iio-adc-pac1934 new file mode 100644 index 000000000000..625b7f867847 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-iio-adc-pac1934 @@ -0,0 +1,9 @@ +What: /sys/bus/iio/devices/iio:deviceX/in_shunt_resistorY +KernelVersion: 6.7 +Contact: linux-iio@vger.kernel.org +Description: + The value of the shunt resistor may be known only at runtime + and set by a client application. This attribute allows to + set its value in micro-ohms. X is the IIO index of the device. + Y is the channel number. The value is used to calculate + current, power and accumulated energy. diff --git a/MAINTAINERS b/MAINTAINERS index d59d52ec8b06..031600dd6c2a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14426,6 +14426,13 @@ F: Documentation/devicetree/bindings/nvmem/microchip,sama7g5-otpc.yaml F: drivers/nvmem/microchip-otpc.c F: include/dt-bindings/nvmem/microchip,sama7g5-otpc.h +MICROCHIP PAC1934 POWER/ENERGY MONITOR DRIVER +M: Marius Cristea +L: linux-iio@vger.kernel.org +S: Supported +F: Documentation/devicetree/bindings/iio/adc/microchip,pac1934.yaml +F: drivers/iio/adc/pac1934.c + MICROCHIP PCI1XXXX GP DRIVER M: Vaibhaav Ram T.L M: Kumaravel Thiagarajan diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index d4462c202784..0d9282fa67f5 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -930,6 +930,17 @@ config NPCM_ADC This driver can also be built as a module. If so, the module will be called npcm_adc. +config PAC1934 + tristate "Microchip Technology PAC1934 driver" + depends on I2C + help + Say yes here to build support for Microchip Technology's PAC1931, + PAC1932, PAC1933, PAC1934 Single/Multi-Channel Power Monitor with + Accumulator. + + This driver can also be built as a module. If so, the module + will be called pac1934. + 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 a64326f40fcb..b3c434722364 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -86,6 +86,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_PAC1934) += pac1934.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/pac1934.c b/drivers/iio/adc/pac1934.c new file mode 100644 index 000000000000..e0c2742da523 --- /dev/null +++ b/drivers/iio/adc/pac1934.c @@ -0,0 +1,1636 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * IIO driver for PAC1934 Multi-Channel DC Power/Energy Monitor + * + * Copyright (C) 2017-2024 Microchip Technology Inc. and its subsidiaries + * + * Author: Bogdan Bolocan + * Author: Victor Tudose + * Author: Marius Cristea + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +/* + * maximum accumulation time should be (17 * 60 * 1000) around 17 minutes@1024 sps + * till PAC1934 accumulation registers starts to saturate + */ +#define PAC1934_MAX_RFSH_LIMIT_MS 60000 +/* 50msec is the timeout for validity of the cached registers */ +#define PAC1934_MIN_POLLING_TIME_MS 50 +/* + * 1000usec is the minimum wait time for normal conversions when sample + * rate doesn't change + */ +#define PAC1934_MIN_UPDATE_WAIT_TIME_US 1000 + +/* 32000mV */ +#define PAC1934_VOLTAGE_MILLIVOLTS_MAX 32000 +/* voltage bits resolution when set for unsigned values */ +#define PAC1934_VOLTAGE_U_RES 16 +/* voltage bits resolution when set for signed values */ +#define PAC1934_VOLTAGE_S_RES 15 + +/* + * 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 + +#define PAC1934_MAX_NUM_CHANNELS 4 + +#define PAC1934_MEAS_REG_LEN 76 +#define PAC1934_CTRL_REG_LEN 12 + +#define PAC1934_DEFAULT_CHIP_SAMP_SPEED_HZ 1024 + +/* I2C address map */ +#define PAC1934_REFRESH_REG_ADDR 0x00 +#define PAC1934_CTRL_REG_ADDR 0x01 +#define PAC1934_ACC_COUNT_REG_ADDR 0x02 +#define PAC1934_VPOWER_ACC_1_ADDR 0x03 +#define PAC1934_VPOWER_ACC_2_ADDR 0x04 +#define PAC1934_VPOWER_ACC_3_ADDR 0x05 +#define PAC1934_VPOWER_ACC_4_ADDR 0x06 +#define PAC1934_VBUS_1_ADDR 0x07 +#define PAC1934_VBUS_2_ADDR 0x08 +#define PAC1934_VBUS_3_ADDR 0x09 +#define PAC1934_VBUS_4_ADDR 0x0A +#define PAC1934_VSENSE_1_ADDR 0x0B +#define PAC1934_VSENSE_2_ADDR 0x0C +#define PAC1934_VSENSE_3_ADDR 0x0D +#define PAC1934_VSENSE_4_ADDR 0x0E +#define PAC1934_VBUS_AVG_1_ADDR 0x0F +#define PAC1934_VBUS_AVG_2_ADDR 0x10 +#define PAC1934_VBUS_AVG_3_ADDR 0x11 +#define PAC1934_VBUS_AVG_4_ADDR 0x12 +#define PAC1934_VSENSE_AVG_1_ADDR 0x13 +#define PAC1934_VSENSE_AVG_2_ADDR 0x14 +#define PAC1934_VSENSE_AVG_3_ADDR 0x15 +#define PAC1934_VSENSE_AVG_4_ADDR 0x16 +#define PAC1934_VPOWER_1_ADDR 0x17 +#define PAC1934_VPOWER_2_ADDR 0x18 +#define PAC1934_VPOWER_3_ADDR 0x19 +#define PAC1934_VPOWER_4_ADDR 0x1A +#define PAC1934_REFRESH_V_REG_ADDR 0x1F +#define PAC1934_CTRL_STAT_REGS_ADDR 0x1C +#define PAC1934_PID_REG_ADDR 0xFD +#define PAC1934_MID_REG_ADDR 0xFE +#define PAC1934_RID_REG_ADDR 0xFF + +/* PRODUCT ID REGISTER + MANUFACTURER ID REGISTER + REVISION ID REGISTER */ +#define PAC1934_ID_REG_LEN 3 +#define PAC1934_PID_IDX 0 +#define PAC1934_MID_IDX 1 +#define PAC1934_RID_IDX 2 + +#define PAC1934_ACPI_GET_NAMES_AND_MOHMS_VALS 1 +#define PAC1934_ACPI_GET_UOHMS_VALS 2 +#define PAC1934_ACPI_GET_BIPOLAR_SETTINGS 4 +#define PAC1934_ACPI_GET_SAMP 5 + +#define PAC1934_SAMPLE_RATE_SHIFT 6 + +#define PAC1934_VBUS_SENSE_REG_LEN 2 +#define PAC1934_ACC_REG_LEN 3 +#define PAC1934_VPOWER_REG_LEN 4 +#define PAC1934_VPOWER_ACC_REG_LEN 6 +#define PAC1934_MAX_REGISTER_LENGTH 6 + +#define PAC1934_CUSTOM_ATTR_FOR_CHANNEL 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 PAC1934_CHANNEL_DIS_REG_OFF 0 +#define PAC1934_NEG_PWR_REG_OFF 1 + +/* + * when reading/writing multiple bytes from offset PAC1934_CHANNEL_DIS_REG_OFF, + * the chip jumps over the 0x1E (REFRESH_G) and 0x1F (REFRESH_V) offsets + */ +#define PAC1934_SLOW_REG_OFF 2 +#define PAC1934_CTRL_ACT_REG_OFF 3 +#define PAC1934_CHANNEL_DIS_ACT_REG_OFF 4 +#define PAC1934_NEG_PWR_ACT_REG_OFF 5 +#define PAC1934_CTRL_LAT_REG_OFF 6 +#define PAC1934_CHANNEL_DIS_LAT_REG_OFF 7 +#define PAC1934_NEG_PWR_LAT_REG_OFF 8 +#define PAC1934_PID_REG_OFF 9 +#define PAC1934_MID_REG_OFF 10 +#define PAC1934_REV_REG_OFF 11 +#define PAC1934_CTRL_STATUS_INFO_LEN 12 + +#define PAC1934_MID 0x5D +#define PAC1931_PID 0x58 +#define PAC1932_PID 0x59 +#define PAC1933_PID 0x5A +#define PAC1934_PID 0x5B + +/* Scale constant = (10^3 * 3.2 * 10^9 / 2^28) for mili Watt-second */ +#define PAC1934_SCALE_CONSTANT 11921 + +#define PAC1934_MAX_VPOWER_RSHIFTED_BY_28B 11921 +#define PAC1934_MAX_VSENSE_RSHIFTED_BY_16B 1525 + +#define PAC1934_DEV_ATTR(name) (&iio_dev_attr_##name.dev_attr.attr) + +#define PAC1934_CRTL_SAMPLE_RATE_MASK GENMASK(7, 6) +#define PAC1934_CHAN_SLEEP_MASK BIT(5) +#define PAC1934_CHAN_SLEEP_SET BIT(5) +#define PAC1934_CHAN_SINGLE_MASK BIT(4) +#define PAC1934_CHAN_SINGLE_SHOT_SET BIT(4) +#define PAC1934_CHAN_ALERT_MASK BIT(3) +#define PAC1934_CHAN_ALERT_EN BIT(3) +#define PAC1934_CHAN_ALERT_CC_MASK BIT(2) +#define PAC1934_CHAN_ALERT_CC_EN BIT(2) +#define PAC1934_CHAN_OVF_ALERT_MASK BIT(1) +#define PAC1934_CHAN_OVF_ALERT_EN BIT(1) +#define PAC1934_CHAN_OVF_MASK BIT(0) + +#define PAC1934_CHAN_DIS_CH1_OFF_MASK BIT(7) +#define PAC1934_CHAN_DIS_CH2_OFF_MASK BIT(6) +#define PAC1934_CHAN_DIS_CH3_OFF_MASK BIT(5) +#define PAC1934_CHAN_DIS_CH4_OFF_MASK BIT(4) +#define PAC1934_SMBUS_TIMEOUT_MASK BIT(3) +#define PAC1934_SMBUS_BYTECOUNT_MASK BIT(2) +#define PAC1934_SMBUS_NO_SKIP_MASK BIT(1) + +#define PAC1934_NEG_PWR_CH1_BIDI_MASK BIT(7) +#define PAC1934_NEG_PWR_CH2_BIDI_MASK BIT(6) +#define PAC1934_NEG_PWR_CH3_BIDI_MASK BIT(5) +#define PAC1934_NEG_PWR_CH4_BIDI_MASK BIT(4) +#define PAC1934_NEG_PWR_CH1_BIDV_MASK BIT(3) +#define PAC1934_NEG_PWR_CH2_BIDV_MASK BIT(2) +#define PAC1934_NEG_PWR_CH3_BIDV_MASK BIT(1) +#define PAC1934_NEG_PWR_CH4_BIDV_MASK BIT(0) + +/* + * Universal Unique Identifier (UUID), + * 033771E0-1705-47B4-9535-D1BBE14D9A09, + * is reserved to Microchip for the PAC1934. + */ +#define PAC1934_DSM_UUID "033771E0-1705-47B4-9535-D1BBE14D9A09" + +enum pac1934_ids { + PAC1931, + PAC1932, + PAC1933, + PAC1934 +}; + +enum pac1934_samps { + PAC1934_SAMP_1024SPS, + PAC1934_SAMP_256SPS, + PAC1934_SAMP_64SPS, + PAC1934_SAMP_8SPS +}; + +/* + * these indexes are exactly describing the element order within a single + * PAC1934 phys channel IIO channel descriptor; see the static const struct + * iio_chan_spec pac1934_single_channel[] declaration + */ +enum pac1934_ch_idx { + PAC1934_CH_ENERGY, + PAC1934_CH_POWER, + PAC1934_CH_VOLTAGE, + PAC1934_CH_CURRENT, + PAC1934_CH_VOLTAGE_AVERAGE, + PAC1934_CH_CURRENT_AVERAGE +}; + +/** + * struct pac1934_features - features of a pac1934 instance + * @phys_channels: number of physical channels supported by the chip + * @name: chip's name + */ +struct pac1934_features { + u8 phys_channels; + const char *name; +}; + +struct samp_rate_mapping { + u16 samp_rate; + u8 shift2value; +}; + +static const unsigned int samp_rate_map_tbl[] = { + [PAC1934_SAMP_1024SPS] = 1024, + [PAC1934_SAMP_256SPS] = 256, + [PAC1934_SAMP_64SPS] = 64, + [PAC1934_SAMP_8SPS] = 8, +}; + +static const struct pac1934_features pac1934_chip_config[] = { + [PAC1931] = { + .phys_channels = 1, + .name = "pac1931", + }, + [PAC1932] = { + .phys_channels = 2, + .name = "pac1932", + }, + [PAC1933] = { + .phys_channels = 3, + .name = "pac1933", + }, + [PAC1934] = { + .phys_channels = 4, + .name = "pac1934", + }, +}; + +/** + * 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[PAC1934_MEAS_REG_LEN]; + u8 ctrl_regs[PAC1934_CTRL_REG_LEN]; + s64 energy_sec_acc[PAC1934_MAX_NUM_CHANNELS]; + s64 vpower_acc[PAC1934_MAX_NUM_CHANNELS]; + s32 vpower[PAC1934_MAX_NUM_CHANNELS]; + s32 vbus[PAC1934_MAX_NUM_CHANNELS]; + s32 vbus_avg[PAC1934_MAX_NUM_CHANNELS]; + s32 vsense[PAC1934_MAX_NUM_CHANNELS]; + s32 vsense_avg[PAC1934_MAX_NUM_CHANNELS]; + u8 num_enabled_channels; +}; + +/** + * struct pac1934_chip_info - information about the chip + * @client: the i2c-client attached to the device + * @lock: synchronize access to driver's state members + * @work_chip_rfsh: work queue used for refresh commands + * @phys_channels: phys channels count + * @active_channels: array of values, true means that channel is active + * @enable_energy: array of values, true means that channel energy is measured + * @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 + * @labels: table with channels labels + * @iio_info: iio_info + * @tstamp: chip's uptime + */ +struct pac1934_chip_info { + struct i2c_client *client; + struct mutex lock; /* synchronize access to driver's state members */ + struct delayed_work work_chip_rfsh; + u8 phys_channels; + bool active_channels[PAC1934_MAX_NUM_CHANNELS]; + bool enable_energy[PAC1934_MAX_NUM_CHANNELS]; + bool bi_dir[PAC1934_MAX_NUM_CHANNELS]; + u8 chip_variant; + u8 chip_revision; + u32 shunts[PAC1934_MAX_NUM_CHANNELS]; + struct reg_data chip_reg_data; + s32 sample_rate_value; + char *labels[PAC1934_MAX_NUM_CHANNELS]; + struct iio_info iio_info; + unsigned long tstamp; +}; + +#define TO_PAC1934_CHIP_INFO(d) container_of(d, struct pac1934_chip_info, work_chip_rfsh) + +#define PAC1934_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) | \ + BIT(IIO_CHAN_INFO_ENABLE), \ + .info_mask_shared_by_all = 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 = 48, \ + .storagebits = 64, \ + .endianness = IIO_CPU, \ + } \ +} + +#define PAC1934_VBUS_CHANNEL(_index, _si, _address) { \ + .type = IIO_VOLTAGE, \ + .address = (_address), \ + .indexed = 1, \ + .channel = (_index), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = 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 = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + } \ +} + +#define PAC1934_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_all = 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 = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + } \ +} + +#define PAC1934_VSENSE_CHANNEL(_index, _si, _address) { \ + .type = IIO_CURRENT, \ + .address = (_address), \ + .indexed = 1, \ + .channel = (_index), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = 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 = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + } \ +} + +#define PAC1934_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_all = 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 = 16, \ + .storagebits = 16, \ + .endianness = IIO_CPU, \ + } \ +} + +#define PAC1934_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_all = 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 = 28, \ + .storagebits = 32, \ + .shift = 4, \ + .endianness = IIO_CPU, \ + } \ +} + +static const struct iio_chan_spec pac1934_single_channel[] = { + PAC1934_VPOWER_ACC_CHANNEL(0, 0, PAC1934_VPOWER_ACC_1_ADDR), + PAC1934_VPOWER_CHANNEL(0, 0, PAC1934_VPOWER_1_ADDR), + PAC1934_VBUS_CHANNEL(0, 0, PAC1934_VBUS_1_ADDR), + PAC1934_VSENSE_CHANNEL(0, 0, PAC1934_VSENSE_1_ADDR), + PAC1934_VBUS_AVG_CHANNEL(0, 0, PAC1934_VBUS_AVG_1_ADDR), + PAC1934_VSENSE_AVG_CHANNEL(0, 0, PAC1934_VSENSE_AVG_1_ADDR), +}; + +/* Low-level I2c functions used to transfer up to 76 bytes at once */ +static int pac1934_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 *)®_addr, + }, + { + .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 pac1934_get_samp_rate_idx(struct pac1934_chip_info *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]) + return cnt; + + /* not a valid sample rate value */ + return -EINVAL; +} + +static ssize_t pac1934_shunt_value_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct pac1934_chip_info *info = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + + return sysfs_emit(buf, "%u\n", info->shunts[this_attr->address]); +} + +static ssize_t pac1934_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 pac1934_chip_info *info = iio_priv(indio_dev); + struct iio_dev_attr *this_attr = to_iio_dev_attr(attr); + int sh_val; + + if (kstrtouint(buf, 10, &sh_val)) { + dev_err(dev, "Shunt value is not valid\n"); + return -EINVAL; + } + + scoped_guard(mutex, &info->lock) + info->shunts[this_attr->address] = sh_val; + + return count; +} + +static int pac1934_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 pac1934_send_refresh(struct pac1934_chip_info *info, + u8 refresh_cmd, u32 wait_time) +{ + /* this function only sends REFRESH or REFRESH_V */ + struct i2c_client *client = info->client; + int ret; + u8 bidir_reg; + bool revision_bug = false; + + if (info->chip_revision == 2 || info->chip_revision == 3) { + /* + * chip rev 2 and 3 bug workaround + * see: PAC1934 Family Data Sheet Errata DS80000836A.pdf + */ + revision_bug = true; + + bidir_reg = + FIELD_PREP(PAC1934_NEG_PWR_CH1_BIDI_MASK, info->bi_dir[0]) | + FIELD_PREP(PAC1934_NEG_PWR_CH2_BIDI_MASK, info->bi_dir[1]) | + FIELD_PREP(PAC1934_NEG_PWR_CH3_BIDI_MASK, info->bi_dir[2]) | + FIELD_PREP(PAC1934_NEG_PWR_CH4_BIDI_MASK, info->bi_dir[3]) | + FIELD_PREP(PAC1934_NEG_PWR_CH1_BIDV_MASK, info->bi_dir[0]) | + FIELD_PREP(PAC1934_NEG_PWR_CH2_BIDV_MASK, info->bi_dir[1]) | + FIELD_PREP(PAC1934_NEG_PWR_CH3_BIDV_MASK, info->bi_dir[2]) | + FIELD_PREP(PAC1934_NEG_PWR_CH4_BIDV_MASK, info->bi_dir[3]); + + ret = i2c_smbus_write_byte_data(client, + PAC1934_CTRL_STAT_REGS_ADDR + + PAC1934_NEG_PWR_REG_OFF, + bidir_reg); + if (ret) + return ret; + } + + ret = i2c_smbus_write_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 = i2c_smbus_write_byte_data(client, + PAC1934_CTRL_STAT_REGS_ADDR + + PAC1934_NEG_PWR_REG_OFF, bidir_reg); + if (ret) + return ret; + } + + /* register data retrieval timestamp */ + info->tstamp = jiffies; + + /* wait till the data is available */ + usleep_range(wait_time, wait_time + 100); + + return ret; +} + +static int pac1934_reg_snapshot(struct pac1934_chip_info *info, + bool do_refresh, u8 refresh_cmd, u32 wait_time) +{ + int ret; + struct i2c_client *client = info->client; + u8 samp_shift, ctrl_regs_tmp; + u8 *offset_reg_data_p; + u16 tmp_value; + u32 samp_rate, cnt, tmp; + s64 curr_energy, inc; + u64 tmp_energy; + struct reg_data *reg_data; + + guard(mutex)(&info->lock); + + if (do_refresh) { + ret = pac1934_send_refresh(info, refresh_cmd, wait_time); + if (ret < 0) { + dev_err(&client->dev, + "%s - cannot send refresh\n", + __func__); + return ret; + } + } + + ret = i2c_smbus_read_i2c_block_data(client, PAC1934_CTRL_STAT_REGS_ADDR, + PAC1934_CTRL_REG_LEN, + (u8 *)info->chip_reg_data.ctrl_regs); + if (ret < 0) { + dev_err(&client->dev, + "%s - cannot read ctrl/status registers\n", + __func__); + return ret; + } + + reg_data = &info->chip_reg_data; + + /* read the data registers */ + ret = pac1934_i2c_read(client, PAC1934_ACC_COUNT_REG_ADDR, + (u8 *)reg_data->meas_regs, PAC1934_MEAS_REG_LEN); + if (ret) { + dev_err(&client->dev, + "%s - cannot read ACC_COUNT register: %d:%d\n", + __func__, ret, PAC1934_MEAS_REG_LEN); + return ret; + } + + /* see how much shift is required by the sample rate */ + samp_rate = samp_rate_map_tbl[((reg_data->ctrl_regs[PAC1934_CTRL_LAT_REG_OFF]) >> 6)]; + samp_shift = get_count_order(samp_rate); + + ctrl_regs_tmp = reg_data->ctrl_regs[PAC1934_CHANNEL_DIS_LAT_REG_OFF]; + offset_reg_data_p = ®_data->meas_regs[PAC1934_ACC_REG_LEN]; + + /* start with VPOWER_ACC */ + for (cnt = 0; cnt < info->phys_channels; cnt++) { + /* check if the channel is active, skip all fields if disabled */ + if ((ctrl_regs_tmp << cnt) & 0x80) + continue; + + /* skip if the energy accumulation is disabled */ + if (info->enable_energy[cnt]) { + curr_energy = info->chip_reg_data.energy_sec_acc[cnt]; + + tmp_energy = get_unaligned_be48(offset_reg_data_p); + + if (info->bi_dir[cnt]) + reg_data->vpower_acc[cnt] = sign_extend64(tmp_energy, 47); + else + reg_data->vpower_acc[cnt] = tmp_energy; + + /* + * 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; + + clamp(curr_energy, PAC_193X_MIN_POWER_ACC, PAC_193X_MAX_POWER_ACC); + + reg_data->energy_sec_acc[cnt] = curr_energy; + } + + offset_reg_data_p += PAC1934_VPOWER_ACC_REG_LEN; + } + + /* continue with VBUS */ + for (cnt = 0; cnt < info->phys_channels; cnt++) { + if ((ctrl_regs_tmp << cnt) & 0x80) + continue; + + tmp_value = get_unaligned_be16(offset_reg_data_p); + + if (info->bi_dir[cnt]) + reg_data->vbus[cnt] = sign_extend32((u32)(tmp_value), 15); + else + reg_data->vbus[cnt] = tmp_value; + + offset_reg_data_p += PAC1934_VBUS_SENSE_REG_LEN; + } + + /* VSENSE */ + for (cnt = 0; cnt < info->phys_channels; cnt++) { + if ((ctrl_regs_tmp << cnt) & 0x80) + continue; + + tmp_value = get_unaligned_be16(offset_reg_data_p); + + if (info->bi_dir[cnt]) + reg_data->vsense[cnt] = sign_extend32((u32)(tmp_value), 15); + else + reg_data->vsense[cnt] = tmp_value; + + offset_reg_data_p += PAC1934_VBUS_SENSE_REG_LEN; + } + + /* VBUS_AVG */ + for (cnt = 0; cnt < info->phys_channels; cnt++) { + if ((ctrl_regs_tmp << cnt) & 0x80) + continue; + + tmp_value = get_unaligned_be16(offset_reg_data_p); + + if (info->bi_dir[cnt]) + reg_data->vbus_avg[cnt] = sign_extend32((u32)(tmp_value), 15); + else + reg_data->vbus_avg[cnt] = tmp_value; + + offset_reg_data_p += PAC1934_VBUS_SENSE_REG_LEN; + } + + /* VSENSE_AVG */ + for (cnt = 0; cnt < info->phys_channels; cnt++) { + if ((ctrl_regs_tmp << cnt) & 0x80) + continue; + + tmp_value = get_unaligned_be16(offset_reg_data_p); + + if (info->bi_dir[cnt]) + reg_data->vsense_avg[cnt] = sign_extend32((u32)(tmp_value), 15); + else + reg_data->vsense_avg[cnt] = tmp_value; + + offset_reg_data_p += PAC1934_VBUS_SENSE_REG_LEN; + } + + /* VPOWER */ + for (cnt = 0; cnt < info->phys_channels; cnt++) { + if ((ctrl_regs_tmp << cnt) & 0x80) + continue; + + tmp = get_unaligned_be32(offset_reg_data_p) >> 4; + + if (info->bi_dir[cnt]) + reg_data->vpower[cnt] = sign_extend32(tmp, 27); + else + reg_data->vpower[cnt] = tmp; + + offset_reg_data_p += PAC1934_VPOWER_REG_LEN; + } + + return 0; +} + +static int pac1934_retrieve_data(struct pac1934_chip_info *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, info->tstamp + msecs_to_jiffies(PAC1934_MIN_POLLING_TIME_MS))) { + ret = pac1934_reg_snapshot(info, true, PAC1934_REFRESH_REG_ADDR, + wait_time); + + /* + * Re-schedule the work for the read registers on timeout + * (to prevent chip registers saturation) + */ + mod_delayed_work(system_wq, &info->work_chip_rfsh, + msecs_to_jiffies(PAC1934_MAX_RFSH_LIMIT_MS)); + } + + return ret; +} + +static int pac1934_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct pac1934_chip_info *info = iio_priv(indio_dev); + s64 curr_energy; + int ret, channel = chan->channel - 1; + + ret = pac1934_retrieve_data(info, PAC1934_MIN_UPDATE_WAIT_TIME_US); + if (ret < 0) + return ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_VOLTAGE: + *val = info->chip_reg_data.vbus[channel]; + return IIO_VAL_INT; + case IIO_CURRENT: + *val = info->chip_reg_data.vsense[channel]; + return IIO_VAL_INT; + case IIO_POWER: + *val = info->chip_reg_data.vpower[channel]; + return IIO_VAL_INT; + case IIO_ENERGY: + curr_energy = info->chip_reg_data.energy_sec_acc[channel]; + *val = (u32)curr_energy; + *val2 = (u32)(curr_energy >> 32); + return IIO_VAL_INT_64; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_AVERAGE_RAW: + switch (chan->type) { + case IIO_VOLTAGE: + *val = info->chip_reg_data.vbus_avg[channel]; + return IIO_VAL_INT; + case IIO_CURRENT: + *val = info->chip_reg_data.vsense_avg[channel]; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->address) { + /* Voltages - scale for millivolts */ + case PAC1934_VBUS_1_ADDR: + case PAC1934_VBUS_2_ADDR: + case PAC1934_VBUS_3_ADDR: + case PAC1934_VBUS_4_ADDR: + case PAC1934_VBUS_AVG_1_ADDR: + case PAC1934_VBUS_AVG_2_ADDR: + case PAC1934_VBUS_AVG_3_ADDR: + case PAC1934_VBUS_AVG_4_ADDR: + *val = PAC1934_VOLTAGE_MILLIVOLTS_MAX; + if (chan->scan_type.sign == 'u') + *val2 = PAC1934_VOLTAGE_U_RES; + else + *val2 = PAC1934_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 PAC1934_VSENSE_1_ADDR: + case PAC1934_VSENSE_2_ADDR: + case PAC1934_VSENSE_3_ADDR: + case PAC1934_VSENSE_4_ADDR: + case PAC1934_VSENSE_AVG_1_ADDR: + case PAC1934_VSENSE_AVG_2_ADDR: + case PAC1934_VSENSE_AVG_3_ADDR: + case PAC1934_VSENSE_AVG_4_ADDR: + *val = PAC1934_MAX_VSENSE_RSHIFTED_BY_16B; + if (chan->scan_type.sign == 'u') + *val2 = info->shunts[channel]; + else + *val2 = info->shunts[channel] >> 1; + return IIO_VAL_FRACTIONAL; + /* + * Power - uW - it will use the combined scale + * for current and voltage + * current(mA) * voltage(mV) = power (uW) + */ + case PAC1934_VPOWER_1_ADDR: + case PAC1934_VPOWER_2_ADDR: + case PAC1934_VPOWER_3_ADDR: + case PAC1934_VPOWER_4_ADDR: + *val = PAC1934_MAX_VPOWER_RSHIFTED_BY_28B; + if (chan->scan_type.sign == 'u') + *val2 = info->shunts[channel]; + else + *val2 = info->shunts[channel] >> 1; + return IIO_VAL_FRACTIONAL; + case PAC1934_VPOWER_ACC_1_ADDR: + case PAC1934_VPOWER_ACC_2_ADDR: + case PAC1934_VPOWER_ACC_3_ADDR: + case PAC1934_VPOWER_ACC_4_ADDR: + /* + * expresses the 32 bit scale value here compute + * the scale for energy (miliWatt-second or miliJoule) + */ + *val = PAC1934_SCALE_CONSTANT; + + if (chan->scan_type.sign == 'u') + *val2 = info->shunts[channel]; + else + *val2 = info->shunts[channel] >> 1; + return IIO_VAL_FRACTIONAL; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + *val = info->sample_rate_value; + return IIO_VAL_INT; + case IIO_CHAN_INFO_ENABLE: + *val = info->enable_energy[channel]; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int pac1934_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct pac1934_chip_info *info = iio_priv(indio_dev); + struct i2c_client *client = info->client; + int ret = -EINVAL; + s32 old_samp_rate; + u8 ctrl_reg; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + ret = pac1934_get_samp_rate_idx(info, val); + if (ret < 0) + return ret; + + /* write the new sampling value and trigger a snapshot(incl refresh) */ + scoped_guard(mutex, &info->lock) { + ctrl_reg = FIELD_PREP(PAC1934_CRTL_SAMPLE_RATE_MASK, ret); + ret = i2c_smbus_write_byte_data(client, PAC1934_CTRL_REG_ADDR, ctrl_reg); + if (ret) { + dev_err(&client->dev, + "%s - can't update sample rate\n", + __func__); + return ret; + } + } + + old_samp_rate = info->sample_rate_value; + info->sample_rate_value = val; + + /* + * 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 + */ + info->tstamp -= msecs_to_jiffies(PAC1934_MIN_POLLING_TIME_MS); + ret = pac1934_retrieve_data(info, (1024 / old_samp_rate) * 1000); + if (ret < 0) { + dev_err(&client->dev, + "%s - cannot snapshot ctrl and measurement regs\n", + __func__); + return ret; + } + + return 0; + case IIO_CHAN_INFO_ENABLE: + scoped_guard(mutex, &info->lock) { + info->enable_energy[chan->channel - 1] = val ? true : false; + if (!val) + info->chip_reg_data.energy_sec_acc[chan->channel - 1] = 0; + } + + return 0; + default: + return -EINVAL; + } +} + +static int pac1934_read_label(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, char *label) +{ + struct pac1934_chip_info *info = iio_priv(indio_dev); + + switch (chan->address) { + case PAC1934_VBUS_1_ADDR: + case PAC1934_VBUS_2_ADDR: + case PAC1934_VBUS_3_ADDR: + case PAC1934_VBUS_4_ADDR: + return sysfs_emit(label, "%s_VBUS_%d\n", + info->labels[chan->scan_index], + chan->scan_index + 1); + case PAC1934_VBUS_AVG_1_ADDR: + case PAC1934_VBUS_AVG_2_ADDR: + case PAC1934_VBUS_AVG_3_ADDR: + case PAC1934_VBUS_AVG_4_ADDR: + return sysfs_emit(label, "%s_VBUS_AVG_%d\n", + info->labels[chan->scan_index], + chan->scan_index + 1); + case PAC1934_VSENSE_1_ADDR: + case PAC1934_VSENSE_2_ADDR: + case PAC1934_VSENSE_3_ADDR: + case PAC1934_VSENSE_4_ADDR: + return sysfs_emit(label, "%s_IBUS_%d\n", + info->labels[chan->scan_index], + chan->scan_index + 1); + case PAC1934_VSENSE_AVG_1_ADDR: + case PAC1934_VSENSE_AVG_2_ADDR: + case PAC1934_VSENSE_AVG_3_ADDR: + case PAC1934_VSENSE_AVG_4_ADDR: + return sysfs_emit(label, "%s_IBUS_AVG_%d\n", + info->labels[chan->scan_index], + chan->scan_index + 1); + case PAC1934_VPOWER_1_ADDR: + case PAC1934_VPOWER_2_ADDR: + case PAC1934_VPOWER_3_ADDR: + case PAC1934_VPOWER_4_ADDR: + return sysfs_emit(label, "%s_POWER_%d\n", + info->labels[chan->scan_index], + chan->scan_index + 1); + case PAC1934_VPOWER_ACC_1_ADDR: + case PAC1934_VPOWER_ACC_2_ADDR: + case PAC1934_VPOWER_ACC_3_ADDR: + case PAC1934_VPOWER_ACC_4_ADDR: + return sysfs_emit(label, "%s_ENERGY_%d\n", + info->labels[chan->scan_index], + chan->scan_index + 1); + } + + return 0; +} + +static void pac1934_work_periodic_rfsh(struct work_struct *work) +{ + struct pac1934_chip_info *info = TO_PAC1934_CHIP_INFO((struct delayed_work *)work); + struct device *dev = &info->client->dev; + + dev_dbg(dev, "%s - Periodic refresh\n", __func__); + + /* do a REFRESH, then read */ + pac1934_reg_snapshot(info, true, PAC1934_REFRESH_REG_ADDR, + PAC1934_MIN_UPDATE_WAIT_TIME_US); + + schedule_delayed_work(&info->work_chip_rfsh, + msecs_to_jiffies(PAC1934_MAX_RFSH_LIMIT_MS)); +} + +static int pac1934_read_revision(struct pac1934_chip_info *info, u8 *buf) +{ + int ret; + struct i2c_client *client = info->client; + + ret = i2c_smbus_read_i2c_block_data(client, PAC1934_PID_REG_ADDR, + PAC1934_ID_REG_LEN, + buf); + if (ret < 0) { + dev_err(&client->dev, "cannot read revision\n"); + return ret; + } + + return 0; +} + +static int pac1934_chip_identify(struct pac1934_chip_info *info) +{ + u8 rev_info[PAC1934_ID_REG_LEN]; + struct device *dev = &info->client->dev; + int ret = 0; + + ret = pac1934_read_revision(info, (u8 *)rev_info); + if (ret) + return ret; + + info->chip_variant = rev_info[PAC1934_PID_IDX]; + info->chip_revision = rev_info[PAC1934_RID_IDX]; + + dev_dbg(dev, "Chip variant: 0x%02X\n", info->chip_variant); + dev_dbg(dev, "Chip revision: 0x%02X\n", info->chip_revision); + + switch (info->chip_variant) { + case PAC1934_PID: + return PAC1934; + case PAC1933_PID: + return PAC1933; + case PAC1932_PID: + return PAC1932; + case PAC1931_PID: + return PAC1931; + default: + return -EINVAL; + } +} + +/* + * documentation related to the ACPI device definition + * https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ApplicationNotes/ApplicationNotes/PAC1934-Integration-Notes-for-Microsoft-Windows-10-and-Windows-11-Driver-Support-DS00002534.pdf + */ +static bool pac1934_acpi_parse_channel_config(struct i2c_client *client, + struct pac1934_chip_info *info) +{ + acpi_handle handle; + union acpi_object *rez; + struct device *dev = &client->dev; + unsigned short bi_dir_mask; + int idx, i; + guid_t guid; + + handle = ACPI_HANDLE(dev); + + guid_parse(PAC1934_DSM_UUID, &guid); + + rez = acpi_evaluate_dsm(handle, &guid, 0, PAC1934_ACPI_GET_NAMES_AND_MOHMS_VALS, NULL); + if (!rez) + return false; + + for (i = 0; i < rez->package.count; i += 2) { + idx = i / 2; + info->labels[idx] = + devm_kmemdup(dev, rez->package.elements[i].string.pointer, + (size_t)rez->package.elements[i].string.length + 1, + GFP_KERNEL); + info->labels[idx][rez->package.elements[i].string.length] = '\0'; + info->shunts[idx] = rez->package.elements[i + 1].integer.value * 1000; + info->active_channels[idx] = (info->shunts[idx] != 0); + } + + ACPI_FREE(rez); + + rez = acpi_evaluate_dsm(handle, &guid, 1, PAC1934_ACPI_GET_UOHMS_VALS, NULL); + if (!rez) { + /* + * initializing with default values + * we assume all channels are unidirectional(the mask is zero) + * and assign the default sampling rate + */ + info->sample_rate_value = PAC1934_DEFAULT_CHIP_SAMP_SPEED_HZ; + return true; + } + + for (i = 0; i < rez->package.count; i++) { + idx = i; + info->shunts[idx] = rez->package.elements[i].integer.value; + info->active_channels[idx] = (info->shunts[idx] != 0); + } + + ACPI_FREE(rez); + + rez = acpi_evaluate_dsm(handle, &guid, 1, PAC1934_ACPI_GET_BIPOLAR_SETTINGS, NULL); + if (!rez) + return false; + + bi_dir_mask = rez->package.elements[0].integer.value; + info->bi_dir[0] = ((bi_dir_mask & (1 << 3)) | (bi_dir_mask & (1 << 7))) != 0; + info->bi_dir[1] = ((bi_dir_mask & (1 << 2)) | (bi_dir_mask & (1 << 6))) != 0; + info->bi_dir[2] = ((bi_dir_mask & (1 << 1)) | (bi_dir_mask & (1 << 5))) != 0; + info->bi_dir[3] = ((bi_dir_mask & (1 << 0)) | (bi_dir_mask & (1 << 4))) != 0; + + ACPI_FREE(rez); + + rez = acpi_evaluate_dsm(handle, &guid, 1, PAC1934_ACPI_GET_SAMP, NULL); + if (!rez) + return false; + + info->sample_rate_value = rez->package.elements[0].integer.value; + + ACPI_FREE(rez); + + return true; +} + +static bool pac1934_of_parse_channel_config(struct i2c_client *client, + struct pac1934_chip_info *info) +{ + struct fwnode_handle *node, *fwnode; + struct device *dev = &client->dev; + unsigned int current_channel; + int idx, ret; + + info->sample_rate_value = 1024; + current_channel = 1; + + fwnode = dev_fwnode(dev); + fwnode_for_each_available_child_node(fwnode, node) { + ret = fwnode_property_read_u32(node, "reg", &idx); + if (ret) { + dev_err_probe(dev, ret, + "reading invalid channel index\n"); + goto err_fwnode; + } + /* adjust idx to match channel index (1 to 4) from the datasheet */ + idx--; + + if (current_channel >= (info->phys_channels + 1) || + idx >= info->phys_channels || idx < 0) { + dev_err_probe(dev, -EINVAL, + "%s: invalid channel_index %d value\n", + fwnode_get_name(node), idx); + goto err_fwnode; + } + + /* enable channel */ + info->active_channels[idx] = true; + + ret = fwnode_property_read_u32(node, "shunt-resistor-micro-ohms", + &info->shunts[idx]); + if (ret) { + dev_err_probe(dev, ret, + "%s: invalid shunt-resistor value: %d\n", + fwnode_get_name(node), info->shunts[idx]); + goto err_fwnode; + } + + if (fwnode_property_present(node, "label")) { + ret = fwnode_property_read_string(node, "label", + (const char **)&info->labels[idx]); + if (ret) { + dev_err_probe(dev, ret, + "%s: invalid rail-name value\n", + fwnode_get_name(node)); + goto err_fwnode; + } + } + + info->bi_dir[idx] = fwnode_property_read_bool(node, "bipolar"); + + current_channel++; + } + + return true; + +err_fwnode: + fwnode_handle_put(node); + + return false; +} + +static void pac1934_cancel_delayed_work(void *dwork) +{ + cancel_delayed_work_sync(dwork); +} + +static int pac1934_chip_configure(struct pac1934_chip_info *info) +{ + int cnt, ret; + struct i2c_client *client = info->client; + u8 regs[PAC1934_CTRL_STATUS_INFO_LEN], idx, ctrl_reg; + u32 wait_time; + + info->chip_reg_data.num_enabled_channels = 0; + for (cnt = 0; cnt < info->phys_channels; cnt++) { + if (info->active_channels[cnt]) + info->chip_reg_data.num_enabled_channels++; + } + + /* + * 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 = i2c_smbus_read_i2c_block_data(client, PAC1934_CTRL_STAT_REGS_ADDR, + ARRAY_SIZE(regs), + (u8 *)regs); + if (ret < 0) { + dev_err_probe(&client->dev, ret, + "%s - cannot read regs from 0x%02X\n", + __func__, PAC1934_CTRL_STAT_REGS_ADDR); + return ret; + } + + /* write the CHANNEL_DIS and the NEG_PWR registers */ + regs[PAC1934_CHANNEL_DIS_REG_OFF] = + FIELD_PREP(PAC1934_CHAN_DIS_CH1_OFF_MASK, info->active_channels[0] ? 0 : 1) | + FIELD_PREP(PAC1934_CHAN_DIS_CH2_OFF_MASK, info->active_channels[1] ? 0 : 1) | + FIELD_PREP(PAC1934_CHAN_DIS_CH3_OFF_MASK, info->active_channels[2] ? 0 : 1) | + FIELD_PREP(PAC1934_CHAN_DIS_CH4_OFF_MASK, info->active_channels[3] ? 0 : 1) | + FIELD_PREP(PAC1934_SMBUS_TIMEOUT_MASK, 0) | + FIELD_PREP(PAC1934_SMBUS_BYTECOUNT_MASK, 0) | + FIELD_PREP(PAC1934_SMBUS_NO_SKIP_MASK, 0); + + regs[PAC1934_NEG_PWR_REG_OFF] = + FIELD_PREP(PAC1934_NEG_PWR_CH1_BIDI_MASK, info->bi_dir[0]) | + FIELD_PREP(PAC1934_NEG_PWR_CH2_BIDI_MASK, info->bi_dir[1]) | + FIELD_PREP(PAC1934_NEG_PWR_CH3_BIDI_MASK, info->bi_dir[2]) | + FIELD_PREP(PAC1934_NEG_PWR_CH4_BIDI_MASK, info->bi_dir[3]) | + FIELD_PREP(PAC1934_NEG_PWR_CH1_BIDV_MASK, info->bi_dir[0]) | + FIELD_PREP(PAC1934_NEG_PWR_CH2_BIDV_MASK, info->bi_dir[1]) | + FIELD_PREP(PAC1934_NEG_PWR_CH3_BIDV_MASK, info->bi_dir[2]) | + FIELD_PREP(PAC1934_NEG_PWR_CH4_BIDV_MASK, info->bi_dir[3]); + + /* no SLOW triggered REFRESH, clear POR */ + regs[PAC1934_SLOW_REG_OFF] = 0; + + ret = i2c_smbus_write_block_data(client, PAC1934_CTRL_STAT_REGS_ADDR, + ARRAY_SIZE(regs), (u8 *)regs); + if (ret) + return ret; + + /* Default sampling rate */ + ctrl_reg = FIELD_PREP(PAC1934_CRTL_SAMPLE_RATE_MASK, PAC1934_SAMP_1024SPS); + + ret = i2c_smbus_write_byte_data(client, PAC1934_CTRL_REG_ADDR, 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 = i2c_smbus_write_byte(client, PAC1934_REFRESH_REG_ADDR); + if (ret) { + dev_err(&client->dev, + "%s - cannot send 0x%02X\n", + __func__, PAC1934_REFRESH_REG_ADDR); + 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[PAC1934_CTRL_ACT_REG_OFF] >> PAC1934_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); + + INIT_DELAYED_WORK(&info->work_chip_rfsh, pac1934_work_periodic_rfsh); + /* Setup the latest moment for reading the regs before saturation */ + schedule_delayed_work(&info->work_chip_rfsh, + msecs_to_jiffies(PAC1934_MAX_RFSH_LIMIT_MS)); + + return devm_add_action_or_reset(&client->dev, pac1934_cancel_delayed_work, + &info->work_chip_rfsh); +} + +static int pac1934_prep_iio_channels(struct pac1934_chip_info *info, struct iio_dev *indio_dev) +{ + struct iio_chan_spec *ch_sp; + int channel_size, attribute_count, cnt; + void *dyn_ch_struct, *tmp_data; + struct device *dev = &info->client->dev; + + /* find out dynamically how many IIO channels we need */ + attribute_count = 0; + channel_size = 0; + for (cnt = 0; cnt < info->phys_channels; cnt++) { + if (!info->active_channels[cnt]) + continue; + + /* add the size of the properties of one chip physical channel */ + channel_size += sizeof(pac1934_single_channel); + /* count how many enabled channels we have */ + attribute_count += ARRAY_SIZE(pac1934_single_channel); + dev_dbg(dev, ":%s: Channel %d active\n", __func__, cnt + 1); + } + + dyn_ch_struct = devm_kzalloc(dev, 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 < info->phys_channels; cnt++) { + if (!info->active_channels[cnt]) + continue; + + memcpy(tmp_data, pac1934_single_channel, sizeof(pac1934_single_channel)); + ch_sp = (struct iio_chan_spec *)tmp_data; + ch_sp[PAC1934_CH_ENERGY].channel = cnt + 1; + ch_sp[PAC1934_CH_ENERGY].scan_index = cnt; + ch_sp[PAC1934_CH_ENERGY].address = cnt + PAC1934_VPOWER_ACC_1_ADDR; + ch_sp[PAC1934_CH_POWER].channel = cnt + 1; + ch_sp[PAC1934_CH_POWER].scan_index = cnt; + ch_sp[PAC1934_CH_POWER].address = cnt + PAC1934_VPOWER_1_ADDR; + ch_sp[PAC1934_CH_VOLTAGE].channel = cnt + 1; + ch_sp[PAC1934_CH_VOLTAGE].scan_index = cnt; + ch_sp[PAC1934_CH_VOLTAGE].address = cnt + PAC1934_VBUS_1_ADDR; + ch_sp[PAC1934_CH_CURRENT].channel = cnt + 1; + ch_sp[PAC1934_CH_CURRENT].scan_index = cnt; + ch_sp[PAC1934_CH_CURRENT].address = cnt + PAC1934_VSENSE_1_ADDR; + + /* + * In order to be able to use labels for PAC1934_CH_VOLTAGE, and + * PAC1934_CH_VOLTAGE_AVERAGE,respectively PAC1934_CH_CURRENT + * and PAC1934_CH_CURRENT_AVERAGE we need to use different + * channel numbers. We will add +5 (+1 to maximum PAC channels). + */ + ch_sp[PAC1934_CH_VOLTAGE_AVERAGE].channel = cnt + 5; + ch_sp[PAC1934_CH_VOLTAGE_AVERAGE].scan_index = cnt; + ch_sp[PAC1934_CH_VOLTAGE_AVERAGE].address = cnt + PAC1934_VBUS_AVG_1_ADDR; + ch_sp[PAC1934_CH_CURRENT_AVERAGE].channel = cnt + 5; + ch_sp[PAC1934_CH_CURRENT_AVERAGE].scan_index = cnt; + ch_sp[PAC1934_CH_CURRENT_AVERAGE].address = cnt + PAC1934_VSENSE_AVG_1_ADDR; + + /* + * now modify the parameters in all channels if the + * whole chip rail(channel) is bi-directional + */ + if (info->bi_dir[cnt]) { + ch_sp[PAC1934_CH_ENERGY].scan_type.sign = 's'; + ch_sp[PAC1934_CH_ENERGY].scan_type.realbits = 47; + ch_sp[PAC1934_CH_POWER].scan_type.sign = 's'; + ch_sp[PAC1934_CH_POWER].scan_type.realbits = 27; + ch_sp[PAC1934_CH_VOLTAGE].scan_type.sign = 's'; + ch_sp[PAC1934_CH_VOLTAGE].scan_type.realbits = 15; + ch_sp[PAC1934_CH_CURRENT].scan_type.sign = 's'; + ch_sp[PAC1934_CH_CURRENT].scan_type.realbits = 15; + ch_sp[PAC1934_CH_VOLTAGE_AVERAGE].scan_type.sign = 's'; + ch_sp[PAC1934_CH_VOLTAGE_AVERAGE].scan_type.realbits = 15; + ch_sp[PAC1934_CH_CURRENT_AVERAGE].scan_type.sign = 's'; + ch_sp[PAC1934_CH_CURRENT_AVERAGE].scan_type.realbits = 15; + } + tmp_data += sizeof(pac1934_single_channel); + } + + /* + * 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 = (const struct iio_chan_spec *)dyn_ch_struct; + + return 0; +} + +static IIO_DEVICE_ATTR(in_shunt_resistor1, 0644, + pac1934_shunt_value_show, pac1934_shunt_value_store, 0); +static IIO_DEVICE_ATTR(in_shunt_resistor2, 0644, + pac1934_shunt_value_show, pac1934_shunt_value_store, 1); +static IIO_DEVICE_ATTR(in_shunt_resistor3, 0644, + pac1934_shunt_value_show, pac1934_shunt_value_store, 2); +static IIO_DEVICE_ATTR(in_shunt_resistor4, 0644, + pac1934_shunt_value_show, pac1934_shunt_value_store, 3); + +static int pac1934_prep_custom_attributes(struct pac1934_chip_info *info, + struct iio_dev *indio_dev) +{ + int i, active_channels_count = 0; + struct attribute **pac1934_custom_attr; + struct attribute_group *pac1934_group; + struct device *dev = &info->client->dev; + + for (i = 0 ; i < info->phys_channels; i++) + if (info->active_channels[i]) + active_channels_count++; + + pac1934_group = devm_kzalloc(dev, sizeof(*pac1934_group), GFP_KERNEL); + if (!pac1934_group) + return -ENOMEM; + + pac1934_custom_attr = devm_kzalloc(dev, + (PAC1934_CUSTOM_ATTR_FOR_CHANNEL * + active_channels_count) + * sizeof(*pac1934_group) + 1, + GFP_KERNEL); + if (!pac1934_custom_attr) + return -ENOMEM; + + i = 0; + if (info->active_channels[0]) + pac1934_custom_attr[i++] = PAC1934_DEV_ATTR(in_shunt_resistor1); + + if (info->active_channels[1]) + pac1934_custom_attr[i++] = PAC1934_DEV_ATTR(in_shunt_resistor2); + + if (info->active_channels[2]) + pac1934_custom_attr[i++] = PAC1934_DEV_ATTR(in_shunt_resistor3); + + if (info->active_channels[3]) + pac1934_custom_attr[i] = PAC1934_DEV_ATTR(in_shunt_resistor4); + + pac1934_group->attrs = pac1934_custom_attr; + info->iio_info.attrs = pac1934_group; + + return 0; +} + +static void pac1934_mutex_destroy(void *data) +{ + struct mutex *lock = data; + + mutex_destroy(lock); +} + +static const struct iio_info pac1934_info = { + .read_raw = pac1934_read_raw, + .write_raw = pac1934_write_raw, + .read_avail = pac1934_read_avail, + .read_label = pac1934_read_label, +}; + +static int pac1934_probe(struct i2c_client *client) +{ + struct pac1934_chip_info *info; + const struct pac1934_features *chip; + struct iio_dev *indio_dev; + int cnt, ret; + bool match = false; + struct device *dev = &client->dev; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*info)); + if (!indio_dev) + return -ENOMEM; + + info = iio_priv(indio_dev); + + info->client = client; + + /* always start with energy accumulation enabled */ + for (cnt = 0; cnt < PAC1934_MAX_NUM_CHANNELS; cnt++) + info->enable_energy[cnt] = true; + + ret = pac1934_chip_identify(info); + if (ret < 0) { + /* + * If failed to identify the hardware based on internal + * registers, try using fallback compatible in device tree + * to deal with some newer part number. + */ + chip = i2c_get_match_data(client); + if (!chip) + return -EINVAL; + + info->phys_channels = chip->phys_channels; + indio_dev->name = chip->name; + } else { + info->phys_channels = pac1934_chip_config[ret].phys_channels; + indio_dev->name = pac1934_chip_config[ret].name; + } + + if (acpi_match_device(dev->driver->acpi_match_table, dev)) + match = pac1934_acpi_parse_channel_config(client, info); + else + /* + * This makes it possible to use also ACPI PRP0001 for + * registering the device using device tree properties. + */ + match = pac1934_of_parse_channel_config(client, info); + + if (!match) + return dev_err_probe(dev, -EINVAL, + "parameter parsing returned an error\n"); + + mutex_init(&info->lock); + ret = devm_add_action_or_reset(dev, pac1934_mutex_destroy, + &info->lock); + if (ret < 0) + return ret; + + /* + * 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 = pac1934_chip_configure(info); + if (ret < 0) + return ret; + + /* prepare the channel information */ + ret = pac1934_prep_iio_channels(info, indio_dev); + if (ret < 0) + return ret; + + info->iio_info = pac1934_info; + indio_dev->info = &info->iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = pac1934_prep_custom_attributes(info, indio_dev); + if (ret < 0) + return dev_err_probe(dev, ret, + "Can't configure custom attributes for PAC1934 device\n"); + + /* + * read whatever has been accumulated in the chip so far + * and reset the accumulators + */ + ret = pac1934_reg_snapshot(info, true, PAC1934_REFRESH_REG_ADDR, + PAC1934_MIN_UPDATE_WAIT_TIME_US); + if (ret < 0) + return ret; + + ret = devm_iio_device_register(dev, indio_dev); + if (ret < 0) + return dev_err_probe(dev, ret, + "Can't register IIO device\n"); + + return 0; +} + +static const struct i2c_device_id pac1934_id[] = { + { .name = "pac1931", .driver_data = (kernel_ulong_t)&pac1934_chip_config[PAC1931] }, + { .name = "pac1932", .driver_data = (kernel_ulong_t)&pac1934_chip_config[PAC1932] }, + { .name = "pac1933", .driver_data = (kernel_ulong_t)&pac1934_chip_config[PAC1933] }, + { .name = "pac1934", .driver_data = (kernel_ulong_t)&pac1934_chip_config[PAC1934] }, + {} +}; +MODULE_DEVICE_TABLE(i2c, pac1934_id); + +static const struct of_device_id pac1934_of_match[] = { + { + .compatible = "microchip,pac1931", + .data = &pac1934_chip_config[PAC1931] + }, + { + .compatible = "microchip,pac1932", + .data = &pac1934_chip_config[PAC1932] + }, + { + .compatible = "microchip,pac1933", + .data = &pac1934_chip_config[PAC1933] + }, + { + .compatible = "microchip,pac1934", + .data = &pac1934_chip_config[PAC1934] + }, + {} +}; +MODULE_DEVICE_TABLE(of, pac1934_of_match); + +/* + * using MCHP1930 to be compatible with BIOS ACPI. See example: + * https://ww1.microchip.com/downloads/aemDocuments/documents/OTH/ApplicationNotes/ApplicationNotes/PAC1934-Integration-Notes-for-Microsoft-Windows-10-and-Windows-11-Driver-Support-DS00002534.pdf + */ +static const struct acpi_device_id pac1934_acpi_match[] = { + { "MCHP1930", .driver_data = (kernel_ulong_t)&pac1934_chip_config[PAC1934] }, + {} +}; +MODULE_DEVICE_TABLE(acpi, pac1934_acpi_match); + +static struct i2c_driver pac1934_driver = { + .driver = { + .name = "pac1934", + .of_match_table = pac1934_of_match, + .acpi_match_table = pac1934_acpi_match + }, + .probe = pac1934_probe, + .id_table = pac1934_id, +}; + +module_i2c_driver(pac1934_driver); + +MODULE_AUTHOR("Bogdan Bolocan "); +MODULE_AUTHOR("Victor Tudose"); +MODULE_AUTHOR("Marius Cristea "); +MODULE_DESCRIPTION("IIO driver for PAC1934 Multi-Channel DC Power/Energy Monitor"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3