From 2373699a3505061cd21625c3f3b70dc3d03a3d8c Mon Sep 17 00:00:00 2001 From: Anshuman Khandual Date: Fri, 18 Aug 2023 13:51:12 +0530 Subject: coresight: tmc: Make etr buffer mode user configurable from sysfs Currently TMC-ETR automatically selects the buffer mode from all available methods in the following sequentially fallback manner - also in that order. 1. FLAT mode with or without IOMMU 2. TMC-ETR-SG (scatter gather) mode when available 3. CATU mode when available But this order might not be ideal for all situations. For example if there is a CATU connected to ETR, it may be better to use TMC-ETR scatter gather method, rather than CATU. But hard coding such order changes will prevent us from testing or using a particular mode. This change provides following new sysfs tunables for the user to control TMC-ETR buffer mode explicitly, if required. This adds following new sysfs files for buffer mode selection purpose explicitly in the user space. /sys/bus/coresight/devices/tmc_etr/buf_modes_available /sys/bus/coresight/devices/tmc_etr/buf_mode_preferred $ cat buf_modes_available auto flat tmc-sg catu ------------------> Supported TMC-ETR buffer modes $ echo catu > buf_mode_preferred -------> Explicit buffer mode request But explicit user request has to be within supported ETR buffer modes only. These sysfs interface files are exclussive to ETR, and hence these are not available for other TMC devices such as ETB or ETF etc. A new auto' mode (i.e ETR_MODE_AUTO) has been added to help fallback to the existing default behaviour, when user provided preferred buffer mode fails. ETR_MODE_FLAT and ETR_MODE_AUTO are always available as preferred modes. Cc: Suzuki K Poulose Cc: Mike Leach Cc: James Clark Cc: Leo Yan Cc: Alexander Shishkin Cc: coresight@lists.linaro.org Cc: linux-arm-kernel@lists.infradead.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Anshuman Khandual [Fixup year in sysfs ABI documentation] Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/20230818082112.554638-1-anshuman.khandual@arm.com --- .../ABI/testing/sysfs-bus-coresight-devices-tmc | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'Documentation/ABI') diff --git a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tmc b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tmc index 6aa527296c71..96aafa66b4a5 100644 --- a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tmc +++ b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tmc @@ -91,3 +91,19 @@ Contact: Mathieu Poirier Description: (RW) Size of the trace buffer for TMC-ETR when used in SYSFS mode. Writable only for TMC-ETR configurations. The value should be aligned to the kernel pagesize. + +What: /sys/bus/coresight/devices/.tmc/buf_modes_available +Date: August 2023 +KernelVersion: 6.7 +Contact: Anshuman Khandual +Description: (Read) Shows all supported Coresight TMC-ETR buffer modes available + for the users to configure explicitly. This file is avaialble only + for TMC ETR devices. + +What: /sys/bus/coresight/devices/.tmc/buf_mode_preferred +Date: August 2023 +KernelVersion: 6.7 +Contact: Anshuman Khandual +Description: (RW) Current Coresight TMC-ETR buffer mode selected. But user could + only provide a mode which is supported for a given ETR device. This + file is available only for TMC ETR devices. -- cgit v1.2.3 From 8fbbce11a90f345a1ff39e2a08e312ee763a1139 Mon Sep 17 00:00:00 2001 From: Tao Zhang Date: Thu, 28 Sep 2023 14:29:39 +0800 Subject: coresight-tpdm: Add reset node to TPDM node TPDM device need a node to reset the configurations and status of it. This change provides a node to reset the configurations and disable the TPDM if it has been enabled. Signed-off-by: Tao Zhang Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/1695882586-10306-7-git-send-email-quic_taozha@quicinc.com --- .../ABI/testing/sysfs-bus-coresight-devices-tpdm | 10 ++++++++++ drivers/hwtracing/coresight/coresight-tpdm.c | 22 ++++++++++++++++++++++ 2 files changed, 32 insertions(+) (limited to 'Documentation/ABI') diff --git a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm index 4a58e649550d..ef8b5a6bd4ac 100644 --- a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm +++ b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm @@ -11,3 +11,13 @@ Description: Accepts only one of the 2 values - 1 or 2. 1 : Generate 64 bits data 2 : Generate 32 bits data + +What: /sys/bus/coresight/devices//reset_dataset +Date: March 2023 +KernelVersion 6.7 +Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Description: + (Write) Reset the dataset of the tpdm. + + Accepts only one value - 1. + 1 : Reset the dataset of the tpdm diff --git a/drivers/hwtracing/coresight/coresight-tpdm.c b/drivers/hwtracing/coresight/coresight-tpdm.c index 951ad4d9b76f..9c65e4c01128 100644 --- a/drivers/hwtracing/coresight/coresight-tpdm.c +++ b/drivers/hwtracing/coresight/coresight-tpdm.c @@ -162,6 +162,27 @@ static int tpdm_datasets_setup(struct tpdm_drvdata *drvdata) return 0; } +static ssize_t reset_dataset_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t size) +{ + int ret = 0; + unsigned long val; + struct tpdm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + ret = kstrtoul(buf, 0, &val); + if (ret || val != 1) + return -EINVAL; + + spin_lock(&drvdata->spinlock); + tpdm_reset_datasets(drvdata); + spin_unlock(&drvdata->spinlock); + + return size; +} +static DEVICE_ATTR_WO(reset_dataset); + /* * value 1: 64 bits test data * value 2: 32 bits test data @@ -202,6 +223,7 @@ static ssize_t integration_test_store(struct device *dev, static DEVICE_ATTR_WO(integration_test); static struct attribute *tpdm_attrs[] = { + &dev_attr_reset_dataset.attr, &dev_attr_integration_test.attr, NULL, }; -- cgit v1.2.3 From 851b3f9c9c0838060158e288c1387d44652c54b5 Mon Sep 17 00:00:00 2001 From: Tao Zhang Date: Thu, 28 Sep 2023 14:29:40 +0800 Subject: coresight-tpdm: Add nodes to set trigger timestamp and type The nodes are needed to set or show the trigger timestamp and trigger type. This change is to add these nodes to achieve these function. Signed-off-by: Tao Zhang Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/1695882586-10306-8-git-send-email-quic_taozha@quicinc.com --- .../ABI/testing/sysfs-bus-coresight-devices-tpdm | 22 +++++ drivers/hwtracing/coresight/coresight-tpdm.c | 95 ++++++++++++++++++++++ 2 files changed, 117 insertions(+) (limited to 'Documentation/ABI') diff --git a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm index ef8b5a6bd4ac..b15bf012a050 100644 --- a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm +++ b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm @@ -21,3 +21,25 @@ Description: Accepts only one value - 1. 1 : Reset the dataset of the tpdm + +What: /sys/bus/coresight/devices//dsb_trig_type +Date: March 2023 +KernelVersion 6.7 +Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Description: + (RW) Set/Get the trigger type of the DSB for tpdm. + + Accepts only one of the 2 values - 0 or 1. + 0 : Set the DSB trigger type to false + 1 : Set the DSB trigger type to true + +What: /sys/bus/coresight/devices//dsb_trig_ts +Date: March 2023 +KernelVersion 6.7 +Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Description: + (RW) Set/Get the trigger timestamp of the DSB for tpdm. + + Accepts only one of the 2 values - 0 or 1. + 0 : Set the DSB trigger type to false + 1 : Set the DSB trigger type to true diff --git a/drivers/hwtracing/coresight/coresight-tpdm.c b/drivers/hwtracing/coresight/coresight-tpdm.c index 9c65e4c01128..e9fc3482d480 100644 --- a/drivers/hwtracing/coresight/coresight-tpdm.c +++ b/drivers/hwtracing/coresight/coresight-tpdm.c @@ -25,6 +25,18 @@ static bool tpdm_has_dsb_dataset(struct tpdm_drvdata *drvdata) return (drvdata->datasets & TPDM_PIDR0_DS_DSB); } +static umode_t tpdm_dsb_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); + + if (drvdata && tpdm_has_dsb_dataset(drvdata)) + return attr->mode; + + return 0; +} + static void tpdm_reset_datasets(struct tpdm_drvdata *drvdata) { if (tpdm_has_dsb_dataset(drvdata)) { @@ -232,8 +244,91 @@ static struct attribute_group tpdm_attr_grp = { .attrs = tpdm_attrs, }; +static ssize_t dsb_trig_type_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->dsb->trig_type); +} + +/* + * Trigger type (boolean): + * false - Disable trigger type. + * true - Enable trigger type. + */ +static ssize_t dsb_trig_type_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; + + spin_lock(&drvdata->spinlock); + if (val) + drvdata->dsb->trig_type = true; + else + drvdata->dsb->trig_type = false; + spin_unlock(&drvdata->spinlock); + return size; +} +static DEVICE_ATTR_RW(dsb_trig_type); + +static ssize_t dsb_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->dsb->trig_ts); +} + +/* + * Trigger timestamp (boolean): + * false - Disable trigger timestamp. + * true - Enable trigger timestamp. + */ +static ssize_t dsb_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; + + spin_lock(&drvdata->spinlock); + if (val) + drvdata->dsb->trig_ts = true; + else + drvdata->dsb->trig_ts = false; + spin_unlock(&drvdata->spinlock); + return size; +} +static DEVICE_ATTR_RW(dsb_trig_ts); + +static struct attribute *tpdm_dsb_attrs[] = { + &dev_attr_dsb_trig_ts.attr, + &dev_attr_dsb_trig_type.attr, + NULL, +}; + +static struct attribute_group tpdm_dsb_attr_grp = { + .attrs = tpdm_dsb_attrs, + .is_visible = tpdm_dsb_is_visible, +}; + static const struct attribute_group *tpdm_attr_grps[] = { &tpdm_attr_grp, + &tpdm_dsb_attr_grp, NULL, }; -- cgit v1.2.3 From 018e43ad1eeefbb8797e4c933953c50c09e3f4f6 Mon Sep 17 00:00:00 2001 From: Tao Zhang Date: Thu, 28 Sep 2023 14:29:41 +0800 Subject: coresight-tpdm: Add node to set dsb programming mode Add node to set and show programming mode for TPDM DSB subunit. Once the DSB programming mode is set, it will be written to the register DSB_CR. Signed-off-by: Tao Zhang Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/1695882586-10306-9-git-send-email-quic_taozha@quicinc.com --- .../ABI/testing/sysfs-bus-coresight-devices-tpdm | 14 ++++++ drivers/hwtracing/coresight/coresight-tpdm.c | 53 ++++++++++++++++++++++ drivers/hwtracing/coresight/coresight-tpdm.h | 19 ++++++++ 3 files changed, 86 insertions(+) (limited to 'Documentation/ABI') diff --git a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm index b15bf012a050..8ec7548070b7 100644 --- a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm +++ b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm @@ -43,3 +43,17 @@ Description: Accepts only one of the 2 values - 0 or 1. 0 : Set the DSB trigger type to false 1 : Set the DSB trigger type to true + +What: /sys/bus/coresight/devices//dsb_mode +Date: March 2023 +KernelVersion 6.7 +Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Description: + (RW) Set/Get the programming mode of the DSB for tpdm. + + Accepts the value needs to be greater than 0. What data + bits do is listed below. + Bit[0:1] : Test mode control bit for choosing the inputs. + Bit[3] : Set to 0 for low performance mode. + Set to 1 for high performance mode. + Bit[4:8] : Select byte lane for high performance mode. diff --git a/drivers/hwtracing/coresight/coresight-tpdm.c b/drivers/hwtracing/coresight/coresight-tpdm.c index e9fc3482d480..6201f12718ca 100644 --- a/drivers/hwtracing/coresight/coresight-tpdm.c +++ b/drivers/hwtracing/coresight/coresight-tpdm.c @@ -4,6 +4,7 @@ */ #include +#include #include #include #include @@ -47,6 +48,27 @@ static void tpdm_reset_datasets(struct tpdm_drvdata *drvdata) } } +static void set_dsb_mode(struct tpdm_drvdata *drvdata, u32 *val) +{ + u32 mode; + + /* Set the test accurate mode */ + mode = TPDM_DSB_MODE_TEST(drvdata->dsb->mode); + *val &= ~TPDM_DSB_CR_TEST_MODE; + *val |= FIELD_PREP(TPDM_DSB_CR_TEST_MODE, mode); + + /* Set the byte lane for high-performance mode */ + mode = TPDM_DSB_MODE_HPBYTESEL(drvdata->dsb->mode); + *val &= ~TPDM_DSB_CR_HPSEL; + *val |= FIELD_PREP(TPDM_DSB_CR_HPSEL, mode); + + /* Set the performance mode */ + if (drvdata->dsb->mode & TPDM_DSB_MODE_PERF) + *val |= TPDM_DSB_CR_MODE; + else + *val &= ~TPDM_DSB_CR_MODE; +} + static void tpdm_enable_dsb(struct tpdm_drvdata *drvdata) { u32 val; @@ -60,6 +82,8 @@ static void tpdm_enable_dsb(struct tpdm_drvdata *drvdata) writel_relaxed(val, drvdata->base + TPDM_DSB_TIER); val = readl_relaxed(drvdata->base + TPDM_DSB_CR); + /* Set the mode of DSB dataset */ + set_dsb_mode(drvdata, &val); /* Set trigger type */ if (drvdata->dsb->trig_type) val |= TPDM_DSB_CR_TRIG_TYPE; @@ -244,6 +268,34 @@ static struct attribute_group tpdm_attr_grp = { .attrs = tpdm_attrs, }; +static ssize_t dsb_mode_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct tpdm_drvdata *drvdata = dev_get_drvdata(dev->parent); + + return sysfs_emit(buf, "%x\n", drvdata->dsb->mode); +} + +static ssize_t dsb_mode_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 < 0) || + (val & ~TPDM_DSB_MODE_MASK)) + return -EINVAL; + + spin_lock(&drvdata->spinlock); + drvdata->dsb->mode = val & TPDM_DSB_MODE_MASK; + spin_unlock(&drvdata->spinlock); + return size; +} +static DEVICE_ATTR_RW(dsb_mode); + static ssize_t dsb_trig_type_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -316,6 +368,7 @@ static ssize_t dsb_trig_ts_store(struct device *dev, static DEVICE_ATTR_RW(dsb_trig_ts); static struct attribute *tpdm_dsb_attrs[] = { + &dev_attr_dsb_mode.attr, &dev_attr_dsb_trig_ts.attr, &dev_attr_dsb_trig_type.attr, NULL, diff --git a/drivers/hwtracing/coresight/coresight-tpdm.h b/drivers/hwtracing/coresight/coresight-tpdm.h index f59e751d3581..b55d6f5ce852 100644 --- a/drivers/hwtracing/coresight/coresight-tpdm.h +++ b/drivers/hwtracing/coresight/coresight-tpdm.h @@ -15,11 +15,28 @@ /* Enable bit for DSB subunit */ #define TPDM_DSB_CR_ENA BIT(0) +/* Enable bit for DSB subunit perfmance mode */ +#define TPDM_DSB_CR_MODE BIT(1) /* Enable bit for DSB subunit trigger type */ #define TPDM_DSB_CR_TRIG_TYPE BIT(12) +/* Data bits for DSB high performace mode */ +#define TPDM_DSB_CR_HPSEL GENMASK(6, 2) +/* Data bits for DSB test mode */ +#define TPDM_DSB_CR_TEST_MODE GENMASK(10, 9) + /* Enable bit for DSB subunit trigger timestamp */ #define TPDM_DSB_TIER_XTRIG_TSENAB BIT(1) +/* DSB programming modes */ +/* DSB mode bits mask */ +#define TPDM_DSB_MODE_MASK GENMASK(8, 0) +/* Test mode control bit*/ +#define TPDM_DSB_MODE_TEST(val) (val & GENMASK(1, 0)) +/* Performance mode */ +#define TPDM_DSB_MODE_PERF BIT(3) +/* High performance mode */ +#define TPDM_DSB_MODE_HPBYTESEL(val) (val & GENMASK(8, 4)) + /* TPDM integration test registers */ #define TPDM_ITATBCNTRL (0xEF0) #define TPDM_ITCNTRL (0xF00) @@ -48,10 +65,12 @@ /** * struct dsb_dataset - specifics associated to dsb dataset + * @mode: DSB programming mode * @trig_ts: Enable/Disable trigger timestamp. * @trig_type: Enable/Disable trigger type. */ struct dsb_dataset { + u32 mode; bool trig_ts; bool trig_type; }; -- cgit v1.2.3 From f376caf25f79965ab140b7a297cb4a5bb0c89523 Mon Sep 17 00:00:00 2001 From: Tao Zhang Date: Thu, 28 Sep 2023 14:29:42 +0800 Subject: coresight-tpdm: Add nodes for dsb edge control Add the nodes to set value for DSB edge control and DSB edge control mask. Each DSB subunit TPDM has maximum of n(n<16) EDCR resgisters to configure edge control. DSB edge detection control 00: Rising edge detection 01: Falling edge detection 10: Rising and falling edge detection (toggle detection) And each DSB subunit TPDM has maximum of m(m<8) ECDMR registers to configure mask. Eight 32 bit registers providing DSB interface edge detection mask control. Add the nodes to configure DSB edge control and DSB edge control mask. Each DSB subunit TPDM maximum of 256 edge detections can be configured. The index and value sysfs files need to be paired and written to order. The index sysfs file is to set the index number of the edge detection which needs to be configured. And the value sysfs file is to set the control or mask for the edge detection. DSB edge detection control should be set as the following values. 00: Rising edge detection 01: Falling edge detection 10: Rising and falling edge detection (toggle detection) And DSB edge mask should be set as 0 or 1. Each DSB subunit TPDM has maximum of n(n<16) EDCR resgisters to configure edge control. And each DSB subunit TPDM has maximum of m(m<8) ECDMR registers to configure mask. Add the nodes to read a set of the edge control value and mask of the DSB in TPDM. Signed-off-by: Tao Zhang Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/1695882586-10306-10-git-send-email-quic_taozha@quicinc.com --- .../ABI/testing/sysfs-bus-coresight-devices-tpdm | 51 ++++++ drivers/hwtracing/coresight/coresight-tpdm.c | 174 ++++++++++++++++++++- drivers/hwtracing/coresight/coresight-tpdm.h | 60 +++++++ 3 files changed, 284 insertions(+), 1 deletion(-) (limited to 'Documentation/ABI') diff --git a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm index 8ec7548070b7..6853bb1295e3 100644 --- a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm +++ b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm @@ -57,3 +57,54 @@ Description: Bit[3] : Set to 0 for low performance mode. Set to 1 for high performance mode. Bit[4:8] : Select byte lane for high performance mode. + +What: /sys/bus/coresight/devices//dsb_edge/ctrl_idx +Date: March 2023 +KernelVersion 6.7 +Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Description: + (RW) Set/Get the index number of the edge detection for the DSB + subunit TPDM. Since there are at most 256 edge detections, this + value ranges from 0 to 255. + +What: /sys/bus/coresight/devices//dsb_edge/ctrl_val +Date: March 2023 +KernelVersion 6.7 +Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Description: + Write a data to control the edge detection corresponding to + the index number. Before writing data to this sysfs file, + "ctrl_idx" should be written first to configure the index + number of the edge detection which needs to be controlled. + + Accepts only one of the following values. + 0 - Rising edge detection + 1 - Falling edge detection + 2 - Rising and falling edge detection (toggle detection) + + +What: /sys/bus/coresight/devices//dsb_edge/ctrl_mask +Date: March 2023 +KernelVersion 6.7 +Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Description: + Write a data to mask the edge detection corresponding to the index + number. Before writing data to this sysfs file, "ctrl_idx" should + be written first to configure the index number of the edge detection + which needs to be masked. + + Accepts only one of the 2 values - 0 or 1. + +What: /sys/bus/coresight/devices//dsb_edge/edcr[0:15] +Date: March 2023 +KernelVersion 6.7 +Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Description: + Read a set of the edge control value of the DSB in TPDM. + +What: /sys/bus/coresight/devices//dsb_edge/edcmr[0:7] +Date: March 2023 +KernelVersion 6.7 +Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Description: + Read a set of the edge control mask of the DSB in TPDM. \ No newline at end of file diff --git a/drivers/hwtracing/coresight/coresight-tpdm.c b/drivers/hwtracing/coresight/coresight-tpdm.c index 6201f12718ca..7175e70c2c4e 100644 --- a/drivers/hwtracing/coresight/coresight-tpdm.c +++ b/drivers/hwtracing/coresight/coresight-tpdm.c @@ -21,6 +21,30 @@ DEFINE_CORESIGHT_DEVLIST(tpdm_devs, "tpdm"); +/* Read dataset array member with the index number */ +static ssize_t tpdm_simple_dataset_show(struct device *dev, + struct device_attribute *attr, + 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); + + switch (tpdm_attr->mem) { + case DSB_EDGE_CTRL: + if (tpdm_attr->idx >= TPDM_DSB_MAX_EDCR) + return -EINVAL; + return sysfs_emit(buf, "0x%x\n", + drvdata->dsb->edge_ctrl[tpdm_attr->idx]); + case DSB_EDGE_CTRL_MASK: + if (tpdm_attr->idx >= TPDM_DSB_MAX_EDCMR) + return -EINVAL; + return sysfs_emit(buf, "0x%x\n", + drvdata->dsb->edge_ctrl_mask[tpdm_attr->idx]); + } + return -EINVAL; +} + static bool tpdm_has_dsb_dataset(struct tpdm_drvdata *drvdata) { return (drvdata->datasets & TPDM_PIDR0_DS_DSB); @@ -71,7 +95,14 @@ static void set_dsb_mode(struct tpdm_drvdata *drvdata, u32 *val) static void tpdm_enable_dsb(struct tpdm_drvdata *drvdata) { - u32 val; + u32 val, i; + + for (i = 0; i < TPDM_DSB_MAX_EDCR; i++) + writel_relaxed(drvdata->dsb->edge_ctrl[i], + drvdata->base + TPDM_DSB_EDCR(i)); + for (i = 0; i < TPDM_DSB_MAX_EDCMR; i++) + writel_relaxed(drvdata->dsb->edge_ctrl_mask[i], + drvdata->base + TPDM_DSB_EDCMR(i)); val = readl_relaxed(drvdata->base + TPDM_DSB_TIER); /* Set trigger timestamp */ @@ -296,6 +327,109 @@ static ssize_t dsb_mode_store(struct device *dev, } static DEVICE_ATTR_RW(dsb_mode); +static ssize_t ctrl_idx_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->dsb->edge_ctrl_idx); +} + +/* + * The EDCR registers can include up to 16 32-bit registers, and each + * one can be configured to control up to 16 edge detections(2 bits + * control one edge detection). So a total 256 edge detections can be + * configured. This function provides a way to set the index number of + * the edge detection which needs to be configured. + */ +static ssize_t ctrl_idx_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 >= TPDM_DSB_MAX_LINES)) + return -EINVAL; + + spin_lock(&drvdata->spinlock); + drvdata->dsb->edge_ctrl_idx = val; + spin_unlock(&drvdata->spinlock); + + return size; +} +static DEVICE_ATTR_RW(ctrl_idx); + +/* + * This function is used to control the edge detection according + * to the index number that has been set. + * "edge_ctrl" should be one of the following values. + * 0 - Rising edge detection + * 1 - Falling edge detection + * 2 - Rising and falling edge detection (toggle detection) + */ +static ssize_t ctrl_val_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, edge_ctrl; + int reg; + + if ((kstrtoul(buf, 0, &edge_ctrl)) || (edge_ctrl > 0x2)) + return -EINVAL; + + spin_lock(&drvdata->spinlock); + /* + * There are 2 bit per DSB Edge Control line. + * Thus we have 16 lines in a 32bit word. + */ + reg = EDCR_TO_WORD_IDX(drvdata->dsb->edge_ctrl_idx); + val = drvdata->dsb->edge_ctrl[reg]; + val &= ~EDCR_TO_WORD_MASK(drvdata->dsb->edge_ctrl_idx); + val |= EDCR_TO_WORD_VAL(edge_ctrl, drvdata->dsb->edge_ctrl_idx); + drvdata->dsb->edge_ctrl[reg] = val; + spin_unlock(&drvdata->spinlock); + + return size; +} +static DEVICE_ATTR_WO(ctrl_val); + +static ssize_t ctrl_mask_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; + u32 set; + int reg; + + if ((kstrtoul(buf, 0, &val)) || (val & ~1UL)) + return -EINVAL; + + spin_lock(&drvdata->spinlock); + /* + * There is 1 bit per DSB Edge Control Mark line. + * Thus we have 32 lines in a 32bit word. + */ + reg = EDCMR_TO_WORD_IDX(drvdata->dsb->edge_ctrl_idx); + set = drvdata->dsb->edge_ctrl_mask[reg]; + if (val) + set |= BIT(EDCMR_TO_WORD_SHIFT(drvdata->dsb->edge_ctrl_idx)); + else + set &= ~BIT(EDCMR_TO_WORD_SHIFT(drvdata->dsb->edge_ctrl_idx)); + drvdata->dsb->edge_ctrl_mask[reg] = set; + spin_unlock(&drvdata->spinlock); + + return size; +} +static DEVICE_ATTR_WO(ctrl_mask); + static ssize_t dsb_trig_type_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -367,6 +501,37 @@ static ssize_t dsb_trig_ts_store(struct device *dev, } static DEVICE_ATTR_RW(dsb_trig_ts); +static struct attribute *tpdm_dsb_edge_attrs[] = { + &dev_attr_ctrl_idx.attr, + &dev_attr_ctrl_val.attr, + &dev_attr_ctrl_mask.attr, + DSB_EDGE_CTRL_ATTR(0), + DSB_EDGE_CTRL_ATTR(1), + DSB_EDGE_CTRL_ATTR(2), + DSB_EDGE_CTRL_ATTR(3), + DSB_EDGE_CTRL_ATTR(4), + DSB_EDGE_CTRL_ATTR(5), + DSB_EDGE_CTRL_ATTR(6), + DSB_EDGE_CTRL_ATTR(7), + DSB_EDGE_CTRL_ATTR(8), + DSB_EDGE_CTRL_ATTR(9), + DSB_EDGE_CTRL_ATTR(10), + DSB_EDGE_CTRL_ATTR(11), + DSB_EDGE_CTRL_ATTR(12), + DSB_EDGE_CTRL_ATTR(13), + DSB_EDGE_CTRL_ATTR(14), + DSB_EDGE_CTRL_ATTR(15), + DSB_EDGE_CTRL_MASK_ATTR(0), + DSB_EDGE_CTRL_MASK_ATTR(1), + DSB_EDGE_CTRL_MASK_ATTR(2), + DSB_EDGE_CTRL_MASK_ATTR(3), + DSB_EDGE_CTRL_MASK_ATTR(4), + DSB_EDGE_CTRL_MASK_ATTR(5), + DSB_EDGE_CTRL_MASK_ATTR(6), + DSB_EDGE_CTRL_MASK_ATTR(7), + NULL, +}; + static struct attribute *tpdm_dsb_attrs[] = { &dev_attr_dsb_mode.attr, &dev_attr_dsb_trig_ts.attr, @@ -379,9 +544,16 @@ static struct attribute_group tpdm_dsb_attr_grp = { .is_visible = tpdm_dsb_is_visible, }; +static struct attribute_group tpdm_dsb_edge_grp = { + .attrs = tpdm_dsb_edge_attrs, + .is_visible = tpdm_dsb_is_visible, + .name = "dsb_edge", +}; + static const struct attribute_group *tpdm_attr_grps[] = { &tpdm_attr_grp, &tpdm_dsb_attr_grp, + &tpdm_dsb_edge_grp, NULL, }; diff --git a/drivers/hwtracing/coresight/coresight-tpdm.h b/drivers/hwtracing/coresight/coresight-tpdm.h index b55d6f5ce852..a9c65d96316a 100644 --- a/drivers/hwtracing/coresight/coresight-tpdm.h +++ b/drivers/hwtracing/coresight/coresight-tpdm.h @@ -12,6 +12,8 @@ /* DSB Subunit Registers */ #define TPDM_DSB_CR (0x780) #define TPDM_DSB_TIER (0x784) +#define TPDM_DSB_EDCR(n) (0x808 + (n * 4)) +#define TPDM_DSB_EDCMR(n) (0x848 + (n * 4)) /* Enable bit for DSB subunit */ #define TPDM_DSB_CR_ENA BIT(0) @@ -37,6 +39,16 @@ /* High performance mode */ #define TPDM_DSB_MODE_HPBYTESEL(val) (val & GENMASK(8, 4)) +#define EDCRS_PER_WORD 16 +#define EDCR_TO_WORD_IDX(r) ((r) / EDCRS_PER_WORD) +#define EDCR_TO_WORD_SHIFT(r) ((r % EDCRS_PER_WORD) * 2) +#define EDCR_TO_WORD_VAL(val, r) (val << EDCR_TO_WORD_SHIFT(r)) +#define EDCR_TO_WORD_MASK(r) EDCR_TO_WORD_VAL(0x3, r) + +#define EDCMRS_PER_WORD 32 +#define EDCMR_TO_WORD_IDX(r) ((r) / EDCMRS_PER_WORD) +#define EDCMR_TO_WORD_SHIFT(r) ((r) % EDCMRS_PER_WORD) + /* TPDM integration test registers */ #define TPDM_ITATBCNTRL (0xEF0) #define TPDM_ITCNTRL (0xF00) @@ -63,14 +75,43 @@ #define TPDM_PIDR0_DS_IMPDEF BIT(0) #define TPDM_PIDR0_DS_DSB BIT(1) +#define TPDM_DSB_MAX_LINES 256 +/* MAX number of EDCR registers */ +#define TPDM_DSB_MAX_EDCR 16 +/* MAX number of EDCMR registers */ +#define TPDM_DSB_MAX_EDCMR 8 + +#define tpdm_simple_dataset_ro(name, mem, idx) \ + (&((struct tpdm_dataset_attribute[]) { \ + { \ + __ATTR(name, 0444, tpdm_simple_dataset_show, NULL), \ + mem, \ + idx, \ + } \ + })[0].attr.attr) + +#define DSB_EDGE_CTRL_ATTR(nr) \ + tpdm_simple_dataset_ro(edcr##nr, \ + DSB_EDGE_CTRL, nr) + +#define DSB_EDGE_CTRL_MASK_ATTR(nr) \ + tpdm_simple_dataset_ro(edcmr##nr, \ + DSB_EDGE_CTRL_MASK, nr) + /** * struct dsb_dataset - specifics associated to dsb dataset * @mode: DSB programming mode + * @edge_ctrl_idx Index number of the edge control + * @edge_ctrl: Save value for edge control + * @edge_ctrl_mask: Save value for edge control mask * @trig_ts: Enable/Disable trigger timestamp. * @trig_type: Enable/Disable trigger type. */ struct dsb_dataset { u32 mode; + u32 edge_ctrl_idx; + u32 edge_ctrl[TPDM_DSB_MAX_EDCR]; + u32 edge_ctrl_mask[TPDM_DSB_MAX_EDCMR]; bool trig_ts; bool trig_type; }; @@ -96,4 +137,23 @@ struct tpdm_drvdata { struct dsb_dataset *dsb; }; +/* Enumerate members of various datasets */ +enum dataset_mem { + DSB_EDGE_CTRL, + DSB_EDGE_CTRL_MASK, +}; + +/** + * struct tpdm_dataset_attribute - Record the member variables and + * index number of datasets that need to be operated by sysfs file + * @attr: The device attribute + * @mem: The member in the dataset data structure + * @idx: The index number of the array data + */ +struct tpdm_dataset_attribute { + struct device_attribute attr; + enum dataset_mem mem; + u32 idx; +}; + #endif /* _CORESIGHT_CORESIGHT_TPDM_H */ -- cgit v1.2.3 From a8138a9445e6d159138b7e574dc5ee7cbcc2f06a Mon Sep 17 00:00:00 2001 From: Tao Zhang Date: Thu, 28 Sep 2023 14:29:43 +0800 Subject: coresight-tpdm: Add nodes to configure pattern match output Add nodes to configure trigger pattern and trigger pattern mask. Each DSB subunit TPDM has maximum of n(n<7) XPR registers to configure trigger pattern match output. Eight 32 bit registers providing DSB interface trigger output pattern match comparison. And each DSB subunit TPDM has maximum of m(m<7) XPMR registers to configure trigger pattern mask match output. Eight 32 bit registers providing DSB interface trigger output pattern match mask. Signed-off-by: Tao Zhang Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/1695882586-10306-11-git-send-email-quic_taozha@quicinc.com --- .../ABI/testing/sysfs-bus-coresight-devices-tpdm | 18 ++++- drivers/hwtracing/coresight/coresight-tpdm.c | 82 +++++++++++++++++++++- drivers/hwtracing/coresight/coresight-tpdm.h | 28 ++++++++ 3 files changed, 126 insertions(+), 2 deletions(-) (limited to 'Documentation/ABI') diff --git a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm index 6853bb1295e3..2252e4706a90 100644 --- a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm +++ b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm @@ -107,4 +107,20 @@ Date: March 2023 KernelVersion 6.7 Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) Description: - Read a set of the edge control mask of the DSB in TPDM. \ No newline at end of file + Read a set of the edge control mask of the DSB in TPDM. + +What: /sys/bus/coresight/devices//dsb_trig_patt/xpr[0:7] +Date: March 2023 +KernelVersion 6.7 +Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Description: + (RW) Set/Get the value of the trigger pattern for the DSB + subunit TPDM. + +What: /sys/bus/coresight/devices//dsb_trig_patt/xpmr[0:7] +Date: March 2023 +KernelVersion 6.7 +Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Description: + (RW) Set/Get the mask of the trigger pattern for the DSB + subunit TPDM. \ No newline at end of file diff --git a/drivers/hwtracing/coresight/coresight-tpdm.c b/drivers/hwtracing/coresight/coresight-tpdm.c index 7175e70c2c4e..e04c41f83265 100644 --- a/drivers/hwtracing/coresight/coresight-tpdm.c +++ b/drivers/hwtracing/coresight/coresight-tpdm.c @@ -41,10 +41,58 @@ static ssize_t tpdm_simple_dataset_show(struct device *dev, return -EINVAL; return sysfs_emit(buf, "0x%x\n", drvdata->dsb->edge_ctrl_mask[tpdm_attr->idx]); + case DSB_TRIG_PATT: + if (tpdm_attr->idx >= TPDM_DSB_MAX_PATT) + return -EINVAL; + return sysfs_emit(buf, "0x%x\n", + drvdata->dsb->trig_patt[tpdm_attr->idx]); + case DSB_TRIG_PATT_MASK: + if (tpdm_attr->idx >= TPDM_DSB_MAX_PATT) + return -EINVAL; + return sysfs_emit(buf, "0x%x\n", + drvdata->dsb->trig_patt_mask[tpdm_attr->idx]); } return -EINVAL; } +/* Write dataset array member with the index number */ +static ssize_t tpdm_simple_dataset_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t size) +{ + unsigned long val; + ssize_t ret = size; + + struct tpdm_drvdata *drvdata = dev_get_drvdata(dev->parent); + struct tpdm_dataset_attribute *tpdm_attr = + container_of(attr, struct tpdm_dataset_attribute, attr); + + if (kstrtoul(buf, 0, &val)) + return -EINVAL; + + spin_lock(&drvdata->spinlock); + switch (tpdm_attr->mem) { + case DSB_TRIG_PATT: + if (tpdm_attr->idx < TPDM_DSB_MAX_PATT) + drvdata->dsb->trig_patt[tpdm_attr->idx] = val; + else + ret = -EINVAL; + break; + case DSB_TRIG_PATT_MASK: + if (tpdm_attr->idx < TPDM_DSB_MAX_PATT) + drvdata->dsb->trig_patt_mask[tpdm_attr->idx] = val; + else + ret = -EINVAL; + break; + default: + ret = -EINVAL; + } + spin_unlock(&drvdata->spinlock); + + return ret; +} + static bool tpdm_has_dsb_dataset(struct tpdm_drvdata *drvdata) { return (drvdata->datasets & TPDM_PIDR0_DS_DSB); @@ -103,7 +151,12 @@ static void tpdm_enable_dsb(struct tpdm_drvdata *drvdata) for (i = 0; i < TPDM_DSB_MAX_EDCMR; i++) writel_relaxed(drvdata->dsb->edge_ctrl_mask[i], drvdata->base + TPDM_DSB_EDCMR(i)); - + for (i = 0; i < TPDM_DSB_MAX_PATT; i++) { + writel_relaxed(drvdata->dsb->trig_patt[i], + drvdata->base + TPDM_DSB_XPR(i)); + writel_relaxed(drvdata->dsb->trig_patt_mask[i], + drvdata->base + TPDM_DSB_XPMR(i)); + } val = readl_relaxed(drvdata->base + TPDM_DSB_TIER); /* Set trigger timestamp */ if (drvdata->dsb->trig_ts) @@ -532,6 +585,26 @@ static struct attribute *tpdm_dsb_edge_attrs[] = { NULL, }; +static struct attribute *tpdm_dsb_trig_patt_attrs[] = { + DSB_TRIG_PATT_ATTR(0), + DSB_TRIG_PATT_ATTR(1), + DSB_TRIG_PATT_ATTR(2), + DSB_TRIG_PATT_ATTR(3), + DSB_TRIG_PATT_ATTR(4), + DSB_TRIG_PATT_ATTR(5), + DSB_TRIG_PATT_ATTR(6), + DSB_TRIG_PATT_ATTR(7), + DSB_TRIG_PATT_MASK_ATTR(0), + DSB_TRIG_PATT_MASK_ATTR(1), + DSB_TRIG_PATT_MASK_ATTR(2), + DSB_TRIG_PATT_MASK_ATTR(3), + DSB_TRIG_PATT_MASK_ATTR(4), + DSB_TRIG_PATT_MASK_ATTR(5), + DSB_TRIG_PATT_MASK_ATTR(6), + DSB_TRIG_PATT_MASK_ATTR(7), + NULL, +}; + static struct attribute *tpdm_dsb_attrs[] = { &dev_attr_dsb_mode.attr, &dev_attr_dsb_trig_ts.attr, @@ -550,10 +623,17 @@ static struct attribute_group tpdm_dsb_edge_grp = { .name = "dsb_edge", }; +static struct attribute_group tpdm_dsb_trig_patt_grp = { + .attrs = tpdm_dsb_trig_patt_attrs, + .is_visible = tpdm_dsb_is_visible, + .name = "dsb_trig_patt", +}; + static const struct attribute_group *tpdm_attr_grps[] = { &tpdm_attr_grp, &tpdm_dsb_attr_grp, &tpdm_dsb_edge_grp, + &tpdm_dsb_trig_patt_grp, NULL, }; diff --git a/drivers/hwtracing/coresight/coresight-tpdm.h b/drivers/hwtracing/coresight/coresight-tpdm.h index a9c65d96316a..2cf7bdbdbb15 100644 --- a/drivers/hwtracing/coresight/coresight-tpdm.h +++ b/drivers/hwtracing/coresight/coresight-tpdm.h @@ -12,6 +12,8 @@ /* DSB Subunit Registers */ #define TPDM_DSB_CR (0x780) #define TPDM_DSB_TIER (0x784) +#define TPDM_DSB_XPR(n) (0x7C8 + (n * 4)) +#define TPDM_DSB_XPMR(n) (0x7E8 + (n * 4)) #define TPDM_DSB_EDCR(n) (0x808 + (n * 4)) #define TPDM_DSB_EDCMR(n) (0x848 + (n * 4)) @@ -80,6 +82,8 @@ #define TPDM_DSB_MAX_EDCR 16 /* MAX number of EDCMR registers */ #define TPDM_DSB_MAX_EDCMR 8 +/* MAX number of DSB pattern */ +#define TPDM_DSB_MAX_PATT 8 #define tpdm_simple_dataset_ro(name, mem, idx) \ (&((struct tpdm_dataset_attribute[]) { \ @@ -90,6 +94,16 @@ } \ })[0].attr.attr) +#define tpdm_simple_dataset_rw(name, mem, idx) \ + (&((struct tpdm_dataset_attribute[]) { \ + { \ + __ATTR(name, 0644, tpdm_simple_dataset_show, \ + tpdm_simple_dataset_store), \ + mem, \ + idx, \ + } \ + })[0].attr.attr) + #define DSB_EDGE_CTRL_ATTR(nr) \ tpdm_simple_dataset_ro(edcr##nr, \ DSB_EDGE_CTRL, nr) @@ -98,12 +112,22 @@ tpdm_simple_dataset_ro(edcmr##nr, \ DSB_EDGE_CTRL_MASK, nr) +#define DSB_TRIG_PATT_ATTR(nr) \ + tpdm_simple_dataset_rw(xpr##nr, \ + DSB_TRIG_PATT, nr) + +#define DSB_TRIG_PATT_MASK_ATTR(nr) \ + tpdm_simple_dataset_rw(xpmr##nr, \ + DSB_TRIG_PATT_MASK, nr) + /** * struct dsb_dataset - specifics associated to dsb dataset * @mode: DSB programming mode * @edge_ctrl_idx Index number of the edge control * @edge_ctrl: Save value for edge control * @edge_ctrl_mask: Save value for edge control mask + * @trig_patt: Save value for trigger pattern + * @trig_patt_mask: Save value for trigger pattern mask * @trig_ts: Enable/Disable trigger timestamp. * @trig_type: Enable/Disable trigger type. */ @@ -112,6 +136,8 @@ struct dsb_dataset { u32 edge_ctrl_idx; u32 edge_ctrl[TPDM_DSB_MAX_EDCR]; u32 edge_ctrl_mask[TPDM_DSB_MAX_EDCMR]; + u32 trig_patt[TPDM_DSB_MAX_PATT]; + u32 trig_patt_mask[TPDM_DSB_MAX_PATT]; bool trig_ts; bool trig_type; }; @@ -141,6 +167,8 @@ struct tpdm_drvdata { enum dataset_mem { DSB_EDGE_CTRL, DSB_EDGE_CTRL_MASK, + DSB_TRIG_PATT, + DSB_TRIG_PATT_MASK, }; /** -- cgit v1.2.3 From 4c983382a29eaddd8746af23702f657258bb91cc Mon Sep 17 00:00:00 2001 From: Tao Zhang Date: Thu, 28 Sep 2023 14:29:44 +0800 Subject: coresight-tpdm: Add nodes for timestamp request Add nodes to configure the timestamp request based on input pattern match. Each TPDM that support DSB subunit has maximum of n(n<7) TPR registers to configure value for timestamp request based on input pattern match. Eight 32 bit registers providing DSB interface timestamp request pattern match comparison. And each TPDM that support DSB subunit has maximum of m(m<7) TPMR registers to configure pattern mask for timestamp request. Eight 32 bit registers providing DSB interface timestamp request pattern match mask generation. Add nodes to enable/disable pattern timestamp and set pattern timestamp type. Signed-off-by: Tao Zhang Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/1695882586-10306-12-git-send-email-quic_taozha@quicinc.com --- .../ABI/testing/sysfs-bus-coresight-devices-tpdm | 40 +++++- drivers/hwtracing/coresight/coresight-tpdm.c | 155 ++++++++++++++++++++- drivers/hwtracing/coresight/coresight-tpdm.h | 24 ++++ 3 files changed, 211 insertions(+), 8 deletions(-) (limited to 'Documentation/ABI') diff --git a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm index 2252e4706a90..1f20a3f7df7d 100644 --- a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm +++ b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm @@ -123,4 +123,42 @@ KernelVersion 6.7 Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) Description: (RW) Set/Get the mask of the trigger pattern for the DSB - subunit TPDM. \ No newline at end of file + subunit TPDM. + +What: /sys/bus/coresight/devices//dsb_patt/tpr[0:7] +Date: March 2023 +KernelVersion 6.7 +Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Description: + (RW) Set/Get the value of the pattern for the DSB subunit TPDM. + +What: /sys/bus/coresight/devices//dsb_patt/tpmr[0:7] +Date: March 2023 +KernelVersion 6.7 +Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Description: + (RW) Set/Get the mask of the pattern for the DSB subunit TPDM. + +What: /sys/bus/coresight/devices//dsb_patt/enable_ts +Date: March 2023 +KernelVersion 6.7 +Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Description: + (Write) Set the pattern timestamp of DSB tpdm. Read + the pattern timestamp of DSB tpdm. + + Accepts only one of the 2 values - 0 or 1. + 0 : Disable DSB pattern timestamp. + 1 : Enable DSB pattern timestamp. + +What: /sys/bus/coresight/devices//dsb_patt/set_type +Date: March 2023 +KernelVersion 6.7 +Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Description: + (Write) Set the pattern type of DSB tpdm. Read + the pattern type of DSB tpdm. + + Accepts only one of the 2 values - 0 or 1. + 0 : Set the DSB pattern type to value. + 1 : Set the DSB pattern type to toggle. diff --git a/drivers/hwtracing/coresight/coresight-tpdm.c b/drivers/hwtracing/coresight/coresight-tpdm.c index e04c41f83265..693b90c82f3a 100644 --- a/drivers/hwtracing/coresight/coresight-tpdm.c +++ b/drivers/hwtracing/coresight/coresight-tpdm.c @@ -51,6 +51,16 @@ static ssize_t tpdm_simple_dataset_show(struct device *dev, return -EINVAL; return sysfs_emit(buf, "0x%x\n", drvdata->dsb->trig_patt_mask[tpdm_attr->idx]); + case DSB_PATT: + if (tpdm_attr->idx >= TPDM_DSB_MAX_PATT) + return -EINVAL; + return sysfs_emit(buf, "0x%x\n", + drvdata->dsb->patt_val[tpdm_attr->idx]); + case DSB_PATT_MASK: + if (tpdm_attr->idx >= TPDM_DSB_MAX_PATT) + return -EINVAL; + return sysfs_emit(buf, "0x%x\n", + drvdata->dsb->patt_mask[tpdm_attr->idx]); } return -EINVAL; } @@ -85,6 +95,18 @@ static ssize_t tpdm_simple_dataset_store(struct device *dev, else ret = -EINVAL; break; + case DSB_PATT: + if (tpdm_attr->idx < TPDM_DSB_MAX_PATT) + drvdata->dsb->patt_val[tpdm_attr->idx] = val; + else + ret = -EINVAL; + break; + case DSB_PATT_MASK: + if (tpdm_attr->idx < TPDM_DSB_MAX_PATT) + drvdata->dsb->patt_mask[tpdm_attr->idx] = val; + else + ret = -EINVAL; + break; default: ret = -EINVAL; } @@ -141,6 +163,36 @@ static void set_dsb_mode(struct tpdm_drvdata *drvdata, u32 *val) *val &= ~TPDM_DSB_CR_MODE; } +static void set_dsb_tier(struct tpdm_drvdata *drvdata) +{ + u32 val; + + val = readl_relaxed(drvdata->base + TPDM_DSB_TIER); + + /* Clear all relevant fields */ + val &= ~(TPDM_DSB_TIER_PATT_TSENAB | TPDM_DSB_TIER_PATT_TYPE | + TPDM_DSB_TIER_XTRIG_TSENAB); + + /* Set pattern timestamp type and enablement */ + if (drvdata->dsb->patt_ts) { + val |= TPDM_DSB_TIER_PATT_TSENAB; + if (drvdata->dsb->patt_type) + val |= TPDM_DSB_TIER_PATT_TYPE; + else + val &= ~TPDM_DSB_TIER_PATT_TYPE; + } else { + val &= ~TPDM_DSB_TIER_PATT_TSENAB; + } + + /* Set trigger timestamp */ + if (drvdata->dsb->trig_ts) + val |= TPDM_DSB_TIER_XTRIG_TSENAB; + else + val &= ~TPDM_DSB_TIER_XTRIG_TSENAB; + + writel_relaxed(val, drvdata->base + TPDM_DSB_TIER); +} + static void tpdm_enable_dsb(struct tpdm_drvdata *drvdata) { u32 val, i; @@ -152,18 +204,17 @@ static void tpdm_enable_dsb(struct tpdm_drvdata *drvdata) writel_relaxed(drvdata->dsb->edge_ctrl_mask[i], drvdata->base + TPDM_DSB_EDCMR(i)); for (i = 0; i < TPDM_DSB_MAX_PATT; i++) { + writel_relaxed(drvdata->dsb->patt_val[i], + drvdata->base + TPDM_DSB_TPR(i)); + writel_relaxed(drvdata->dsb->patt_mask[i], + drvdata->base + TPDM_DSB_TPMR(i)); writel_relaxed(drvdata->dsb->trig_patt[i], drvdata->base + TPDM_DSB_XPR(i)); writel_relaxed(drvdata->dsb->trig_patt_mask[i], drvdata->base + TPDM_DSB_XPMR(i)); } - val = readl_relaxed(drvdata->base + TPDM_DSB_TIER); - /* Set trigger timestamp */ - if (drvdata->dsb->trig_ts) - val |= TPDM_DSB_TIER_XTRIG_TSENAB; - else - val &= ~TPDM_DSB_TIER_XTRIG_TSENAB; - writel_relaxed(val, drvdata->base + TPDM_DSB_TIER); + + set_dsb_tier(drvdata); val = readl_relaxed(drvdata->base + TPDM_DSB_CR); /* Set the mode of DSB dataset */ @@ -483,6 +534,67 @@ static ssize_t ctrl_mask_store(struct device *dev, } static DEVICE_ATTR_WO(ctrl_mask); +static ssize_t enable_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->dsb->patt_ts); +} + +/* + * value 1: Enable/Disable DSB pattern timestamp + */ +static ssize_t enable_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; + + spin_lock(&drvdata->spinlock); + drvdata->dsb->patt_ts = !!val; + spin_unlock(&drvdata->spinlock); + return size; +} +static DEVICE_ATTR_RW(enable_ts); + +static ssize_t set_type_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->dsb->patt_type); +} + +/* + * value 1: Set DSB pattern type + */ +static ssize_t set_type_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; + + spin_lock(&drvdata->spinlock); + drvdata->dsb->patt_type = val; + spin_unlock(&drvdata->spinlock); + return size; +} +static DEVICE_ATTR_RW(set_type); + static ssize_t dsb_trig_type_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -605,6 +717,28 @@ static struct attribute *tpdm_dsb_trig_patt_attrs[] = { NULL, }; +static struct attribute *tpdm_dsb_patt_attrs[] = { + DSB_PATT_ATTR(0), + DSB_PATT_ATTR(1), + DSB_PATT_ATTR(2), + DSB_PATT_ATTR(3), + DSB_PATT_ATTR(4), + DSB_PATT_ATTR(5), + DSB_PATT_ATTR(6), + DSB_PATT_ATTR(7), + DSB_PATT_MASK_ATTR(0), + DSB_PATT_MASK_ATTR(1), + DSB_PATT_MASK_ATTR(2), + DSB_PATT_MASK_ATTR(3), + DSB_PATT_MASK_ATTR(4), + DSB_PATT_MASK_ATTR(5), + DSB_PATT_MASK_ATTR(6), + DSB_PATT_MASK_ATTR(7), + &dev_attr_enable_ts.attr, + &dev_attr_set_type.attr, + NULL, +}; + static struct attribute *tpdm_dsb_attrs[] = { &dev_attr_dsb_mode.attr, &dev_attr_dsb_trig_ts.attr, @@ -629,11 +763,18 @@ static struct attribute_group tpdm_dsb_trig_patt_grp = { .name = "dsb_trig_patt", }; +static struct attribute_group tpdm_dsb_patt_grp = { + .attrs = tpdm_dsb_patt_attrs, + .is_visible = tpdm_dsb_is_visible, + .name = "dsb_patt", +}; + static const struct attribute_group *tpdm_attr_grps[] = { &tpdm_attr_grp, &tpdm_dsb_attr_grp, &tpdm_dsb_edge_grp, &tpdm_dsb_trig_patt_grp, + &tpdm_dsb_patt_grp, NULL, }; diff --git a/drivers/hwtracing/coresight/coresight-tpdm.h b/drivers/hwtracing/coresight/coresight-tpdm.h index 2cf7bdbdbb15..891979db111a 100644 --- a/drivers/hwtracing/coresight/coresight-tpdm.h +++ b/drivers/hwtracing/coresight/coresight-tpdm.h @@ -12,6 +12,8 @@ /* DSB Subunit Registers */ #define TPDM_DSB_CR (0x780) #define TPDM_DSB_TIER (0x784) +#define TPDM_DSB_TPR(n) (0x788 + (n * 4)) +#define TPDM_DSB_TPMR(n) (0x7A8 + (n * 4)) #define TPDM_DSB_XPR(n) (0x7C8 + (n * 4)) #define TPDM_DSB_XPMR(n) (0x7E8 + (n * 4)) #define TPDM_DSB_EDCR(n) (0x808 + (n * 4)) @@ -28,8 +30,12 @@ /* Data bits for DSB test mode */ #define TPDM_DSB_CR_TEST_MODE GENMASK(10, 9) +/* Enable bit for DSB subunit pattern timestamp */ +#define TPDM_DSB_TIER_PATT_TSENAB BIT(0) /* Enable bit for DSB subunit trigger timestamp */ #define TPDM_DSB_TIER_XTRIG_TSENAB BIT(1) +/* Bit for DSB subunit pattern type */ +#define TPDM_DSB_TIER_PATT_TYPE BIT(2) /* DSB programming modes */ /* DSB mode bits mask */ @@ -120,14 +126,26 @@ tpdm_simple_dataset_rw(xpmr##nr, \ DSB_TRIG_PATT_MASK, nr) +#define DSB_PATT_ATTR(nr) \ + tpdm_simple_dataset_rw(tpr##nr, \ + DSB_PATT, nr) + +#define DSB_PATT_MASK_ATTR(nr) \ + tpdm_simple_dataset_rw(tpmr##nr, \ + DSB_PATT_MASK, nr) + /** * struct dsb_dataset - specifics associated to dsb dataset * @mode: DSB programming mode * @edge_ctrl_idx Index number of the edge control * @edge_ctrl: Save value for edge control * @edge_ctrl_mask: Save value for edge control mask + * @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 + * @patt_ts: Enable/Disable pattern timestamp + * @patt_type: Set pattern type * @trig_ts: Enable/Disable trigger timestamp. * @trig_type: Enable/Disable trigger type. */ @@ -136,8 +154,12 @@ struct dsb_dataset { u32 edge_ctrl_idx; u32 edge_ctrl[TPDM_DSB_MAX_EDCR]; u32 edge_ctrl_mask[TPDM_DSB_MAX_EDCMR]; + u32 patt_val[TPDM_DSB_MAX_PATT]; + u32 patt_mask[TPDM_DSB_MAX_PATT]; u32 trig_patt[TPDM_DSB_MAX_PATT]; u32 trig_patt_mask[TPDM_DSB_MAX_PATT]; + bool patt_ts; + bool patt_type; bool trig_ts; bool trig_type; }; @@ -169,6 +191,8 @@ enum dataset_mem { DSB_EDGE_CTRL_MASK, DSB_TRIG_PATT, DSB_TRIG_PATT_MASK, + DSB_PATT, + DSB_PATT_MASK, }; /** -- cgit v1.2.3 From 350ba15ae187c118979566f1288adb5f69f24230 Mon Sep 17 00:00:00 2001 From: Tao Zhang Date: Thu, 28 Sep 2023 14:29:46 +0800 Subject: coresight-tpdm: Add nodes for dsb msr support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the nodes for DSB subunit MSR(mux select register) support. The TPDM MSR (mux select register) interface is an optional interface and associated bank of registers per TPDM subunit. The intent of mux select registers is to control muxing structures driving the TPDM’s’ various subunit interfaces. Signed-off-by: Tao Zhang Signed-off-by: Suzuki K Poulose Link: https://lore.kernel.org/r/1695882586-10306-14-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 | 12 +++ 3 files changed, 105 insertions(+) (limited to 'Documentation/ABI') diff --git a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm index 1f20a3f7df7d..f07218e78843 100644 --- a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm +++ b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm @@ -162,3 +162,11 @@ Description: Accepts only one of the 2 values - 0 or 1. 0 : Set the DSB pattern type to value. 1 : Set the DSB pattern type to toggle. + +What: /sys/bus/coresight/devices//dsb_msr/msr[0:31] +Date: March 2023 +KernelVersion 6.7 +Contact: Jinlong Mao (QUIC) , Tao Zhang (QUIC) +Description: + (RW) Set/Get the MSR(mux select register) for the DSB subunit + TPDM. diff --git a/drivers/hwtracing/coresight/coresight-tpdm.c b/drivers/hwtracing/coresight/coresight-tpdm.c index 693b90c82f3a..b25284e06395 100644 --- a/drivers/hwtracing/coresight/coresight-tpdm.c +++ b/drivers/hwtracing/coresight/coresight-tpdm.c @@ -61,6 +61,11 @@ static ssize_t tpdm_simple_dataset_show(struct device *dev, return -EINVAL; return sysfs_emit(buf, "0x%x\n", drvdata->dsb->patt_mask[tpdm_attr->idx]); + case DSB_MSR: + if (tpdm_attr->idx >= drvdata->dsb_msr_num) + return -EINVAL; + return sysfs_emit(buf, "0x%x\n", + drvdata->dsb->msr[tpdm_attr->idx]); } return -EINVAL; } @@ -107,6 +112,12 @@ static ssize_t tpdm_simple_dataset_store(struct device *dev, else ret = -EINVAL; break; + case DSB_MSR: + if (tpdm_attr->idx < drvdata->dsb_msr_num) + drvdata->dsb->msr[tpdm_attr->idx] = val; + else + ret = -EINVAL; + break; default: ret = -EINVAL; } @@ -132,6 +143,22 @@ static umode_t tpdm_dsb_is_visible(struct kobject *kobj, return 0; } +static umode_t tpdm_dsb_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->dsb_msr_num) + return attr->mode; + + return 0; +} + static void tpdm_reset_datasets(struct tpdm_drvdata *drvdata) { if (tpdm_has_dsb_dataset(drvdata)) { @@ -193,6 +220,15 @@ static void set_dsb_tier(struct tpdm_drvdata *drvdata) writel_relaxed(val, drvdata->base + TPDM_DSB_TIER); } +static void set_dsb_msr(struct tpdm_drvdata *drvdata) +{ + int i; + + for (i = 0; i < drvdata->dsb_msr_num; i++) + writel_relaxed(drvdata->dsb->msr[i], + drvdata->base + TPDM_DSB_MSR(i)); +} + static void tpdm_enable_dsb(struct tpdm_drvdata *drvdata) { u32 val, i; @@ -216,6 +252,8 @@ static void tpdm_enable_dsb(struct tpdm_drvdata *drvdata) set_dsb_tier(drvdata); + set_dsb_msr(drvdata); + val = readl_relaxed(drvdata->base + TPDM_DSB_CR); /* Set the mode of DSB dataset */ set_dsb_mode(drvdata, &val); @@ -739,6 +777,42 @@ static struct attribute *tpdm_dsb_patt_attrs[] = { NULL, }; +static struct attribute *tpdm_dsb_msr_attrs[] = { + DSB_MSR_ATTR(0), + DSB_MSR_ATTR(1), + DSB_MSR_ATTR(2), + DSB_MSR_ATTR(3), + DSB_MSR_ATTR(4), + DSB_MSR_ATTR(5), + DSB_MSR_ATTR(6), + DSB_MSR_ATTR(7), + DSB_MSR_ATTR(8), + DSB_MSR_ATTR(9), + DSB_MSR_ATTR(10), + DSB_MSR_ATTR(11), + DSB_MSR_ATTR(12), + DSB_MSR_ATTR(13), + DSB_MSR_ATTR(14), + DSB_MSR_ATTR(15), + DSB_MSR_ATTR(16), + DSB_MSR_ATTR(17), + DSB_MSR_ATTR(18), + DSB_MSR_ATTR(19), + DSB_MSR_ATTR(20), + DSB_MSR_ATTR(21), + DSB_MSR_ATTR(22), + DSB_MSR_ATTR(23), + DSB_MSR_ATTR(24), + DSB_MSR_ATTR(25), + DSB_MSR_ATTR(26), + DSB_MSR_ATTR(27), + DSB_MSR_ATTR(28), + DSB_MSR_ATTR(29), + DSB_MSR_ATTR(30), + DSB_MSR_ATTR(31), + NULL, +}; + static struct attribute *tpdm_dsb_attrs[] = { &dev_attr_dsb_mode.attr, &dev_attr_dsb_trig_ts.attr, @@ -769,12 +843,19 @@ static struct attribute_group tpdm_dsb_patt_grp = { .name = "dsb_patt", }; +static struct attribute_group tpdm_dsb_msr_grp = { + .attrs = tpdm_dsb_msr_attrs, + .is_visible = tpdm_dsb_msr_is_visible, + .name = "dsb_msr", +}; + static const struct attribute_group *tpdm_attr_grps[] = { &tpdm_attr_grp, &tpdm_dsb_attr_grp, &tpdm_dsb_edge_grp, &tpdm_dsb_trig_patt_grp, &tpdm_dsb_patt_grp, + &tpdm_dsb_msr_grp, NULL, }; @@ -809,6 +890,10 @@ static int tpdm_probe(struct amba_device *adev, const struct amba_id *id) if (ret) return ret; + if (drvdata && tpdm_has_dsb_dataset(drvdata)) + of_property_read_u32(drvdata->dev->of_node, + "qcom,dsb_msr_num", &drvdata->dsb_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 891979db111a..4115b2a17b8d 100644 --- a/drivers/hwtracing/coresight/coresight-tpdm.h +++ b/drivers/hwtracing/coresight/coresight-tpdm.h @@ -18,6 +18,7 @@ #define TPDM_DSB_XPMR(n) (0x7E8 + (n * 4)) #define TPDM_DSB_EDCR(n) (0x808 + (n * 4)) #define TPDM_DSB_EDCMR(n) (0x848 + (n * 4)) +#define TPDM_DSB_MSR(n) (0x980 + (n * 4)) /* Enable bit for DSB subunit */ #define TPDM_DSB_CR_ENA BIT(0) @@ -90,6 +91,8 @@ #define TPDM_DSB_MAX_EDCMR 8 /* MAX number of DSB pattern */ #define TPDM_DSB_MAX_PATT 8 +/* MAX number of DSB MSR */ +#define TPDM_DSB_MAX_MSR 32 #define tpdm_simple_dataset_ro(name, mem, idx) \ (&((struct tpdm_dataset_attribute[]) { \ @@ -134,6 +137,10 @@ tpdm_simple_dataset_rw(tpmr##nr, \ DSB_PATT_MASK, nr) +#define DSB_MSR_ATTR(nr) \ + tpdm_simple_dataset_rw(msr##nr, \ + DSB_MSR, nr) + /** * struct dsb_dataset - specifics associated to dsb dataset * @mode: DSB programming mode @@ -144,6 +151,7 @@ * @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: Enable/Disable pattern timestamp * @patt_type: Set pattern type * @trig_ts: Enable/Disable trigger timestamp. @@ -158,6 +166,7 @@ struct dsb_dataset { u32 patt_mask[TPDM_DSB_MAX_PATT]; u32 trig_patt[TPDM_DSB_MAX_PATT]; u32 trig_patt_mask[TPDM_DSB_MAX_PATT]; + u32 msr[TPDM_DSB_MAX_MSR]; bool patt_ts; bool patt_type; bool trig_ts; @@ -173,6 +182,7 @@ struct dsb_dataset { * @enable: enable status of the component. * @datasets: The datasets types present of the TPDM. * @dsb Specifics associated to TPDM DSB. + * @dsb_msr_num Number of MSR supported by DSB TPDM */ struct tpdm_drvdata { @@ -183,6 +193,7 @@ struct tpdm_drvdata { bool enable; unsigned long datasets; struct dsb_dataset *dsb; + u32 dsb_msr_num; }; /* Enumerate members of various datasets */ @@ -193,6 +204,7 @@ enum dataset_mem { DSB_TRIG_PATT_MASK, DSB_PATT, DSB_PATT_MASK, + DSB_MSR, }; /** -- cgit v1.2.3 From 9d4408feff89f8d86b8f34339b08ceb5f8400190 Mon Sep 17 00:00:00 2001 From: Bagas Sanjaya Date: Tue, 17 Oct 2023 16:56:08 +0700 Subject: Documentation: ABI: coresight-tpdm: Fix Bit[3] description indentation Stephen Rothwell reported htmldocs warnings when merging coresight tree: Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm:48: ERROR: Unexpected indentation. Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm:48: WARNING: Block quote ends without a blank line; unexpected unindent. Fix indentation alignment for Bit[3] list entry in dsb_mode description to silence above warnings. Fixes: 018e43ad1eee ("coresight-tpdm: Add node to set dsb programming mode") Reported-by: Stephen Rothwell Closes: https://lore.kernel.org/linux-next/20231017143324.75387a21@canb.auug.org.au/ Signed-off-by: Bagas Sanjaya Link: https://lore.kernel.org/r/20231017095608.136277-1-bagasdotme@gmail.com Signed-off-by: Suzuki K Poulose --- Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'Documentation/ABI') diff --git a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm index f07218e78843..4dd49b159543 100644 --- a/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm +++ b/Documentation/ABI/testing/sysfs-bus-coresight-devices-tpdm @@ -54,8 +54,8 @@ Description: Accepts the value needs to be greater than 0. What data bits do is listed below. Bit[0:1] : Test mode control bit for choosing the inputs. - Bit[3] : Set to 0 for low performance mode. - Set to 1 for high performance mode. + Bit[3] : Set to 0 for low performance mode. Set to 1 for high + performance mode. Bit[4:8] : Select byte lane for high performance mode. What: /sys/bus/coresight/devices//dsb_edge/ctrl_idx -- cgit v1.2.3 From 8a636db3aa57ed468b88804ecf27798df6c9c553 Mon Sep 17 00:00:00 2001 From: Jagath Jog J Date: Fri, 13 Oct 2023 09:18:08 +0530 Subject: iio: imu: Add driver for BMI323 IMU The Bosch BMI323 is a 6-axis low-power IMU that provide measurements for acceleration, angular rate, and temperature. This sensor includes motion-triggered interrupt features, such as a step counter, tap detection, and activity/inactivity interrupt capabilities. The driver supports various functionalities, including data ready, FIFO data handling, and events such as tap detection, step counting, and activity interrupts. Signed-off-by: Jagath Jog J Link: https://lore.kernel.org/r/20231013034808.8948-3-jagathjog1996@gmail.com Signed-off-by: Jonathan Cameron --- Documentation/ABI/testing/sysfs-bus-iio | 18 + MAINTAINERS | 7 + drivers/iio/imu/Kconfig | 1 + drivers/iio/imu/Makefile | 1 + drivers/iio/imu/bmi323/Kconfig | 33 + drivers/iio/imu/bmi323/Makefile | 7 + drivers/iio/imu/bmi323/bmi323.h | 209 +++ drivers/iio/imu/bmi323/bmi323_core.c | 2139 +++++++++++++++++++++++++++++++ drivers/iio/imu/bmi323/bmi323_i2c.c | 121 ++ drivers/iio/imu/bmi323/bmi323_spi.c | 92 ++ 10 files changed, 2628 insertions(+) create mode 100644 drivers/iio/imu/bmi323/Kconfig create mode 100644 drivers/iio/imu/bmi323/Makefile create mode 100644 drivers/iio/imu/bmi323/bmi323.h create mode 100644 drivers/iio/imu/bmi323/bmi323_core.c create mode 100644 drivers/iio/imu/bmi323/bmi323_i2c.c create mode 100644 drivers/iio/imu/bmi323/bmi323_spi.c (limited to 'Documentation/ABI') diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio index 19cde14f3869..0eadc08c3a13 100644 --- a/Documentation/ABI/testing/sysfs-bus-iio +++ b/Documentation/ABI/testing/sysfs-bus-iio @@ -2254,3 +2254,21 @@ Description: If a label is defined for this event add that to the event specific attributes. This is useful for userspace to be able to better identify an individual event. + +What: /sys/.../events/in_accel_gesture_tap_wait_timeout +KernelVersion: 6.7 +Contact: linux-iio@vger.kernel.org +Description: + Enable tap gesture confirmation with timeout. + +What: /sys/.../events/in_accel_gesture_tap_wait_dur +KernelVersion: 6.7 +Contact: linux-iio@vger.kernel.org +Description: + Timeout value in seconds for tap gesture confirmation. + +What: /sys/.../events/in_accel_gesture_tap_wait_dur_available +KernelVersion: 6.7 +Contact: linux-iio@vger.kernel.org +Description: + List of available timeout value for tap gesture confirmation. diff --git a/MAINTAINERS b/MAINTAINERS index 97f51d5ec1cf..8b74fad87d76 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3647,6 +3647,13 @@ S: Maintained F: Documentation/devicetree/bindings/iio/accel/bosch,bma400.yaml F: drivers/iio/accel/bma400* +BOSCH SENSORTEC BMI323 IMU IIO DRIVER +M: Jagath Jog J +L: linux-iio@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/iio/imu/bosch,bma400.yaml +F: drivers/iio/imu/bmi323/ + BPF JIT for ARM M: Russell King M: Puranjay Mohan diff --git a/drivers/iio/imu/Kconfig b/drivers/iio/imu/Kconfig index c2f97629e9cd..52a155ff3250 100644 --- a/drivers/iio/imu/Kconfig +++ b/drivers/iio/imu/Kconfig @@ -53,6 +53,7 @@ config ADIS16480 ADIS16485, ADIS16488 inertial sensors. source "drivers/iio/imu/bmi160/Kconfig" +source "drivers/iio/imu/bmi323/Kconfig" source "drivers/iio/imu/bno055/Kconfig" config FXOS8700 diff --git a/drivers/iio/imu/Makefile b/drivers/iio/imu/Makefile index 6eb612034722..7e2d7d5c3b7b 100644 --- a/drivers/iio/imu/Makefile +++ b/drivers/iio/imu/Makefile @@ -15,6 +15,7 @@ adis_lib-$(CONFIG_IIO_ADIS_LIB_BUFFER) += adis_buffer.o obj-$(CONFIG_IIO_ADIS_LIB) += adis_lib.o obj-y += bmi160/ +obj-y += bmi323/ obj-y += bno055/ obj-$(CONFIG_FXOS8700) += fxos8700_core.o diff --git a/drivers/iio/imu/bmi323/Kconfig b/drivers/iio/imu/bmi323/Kconfig new file mode 100644 index 000000000000..ab37b285393c --- /dev/null +++ b/drivers/iio/imu/bmi323/Kconfig @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# BMI323 IMU driver +# + +config BMI323 + tristate + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + +config BMI323_I2C + tristate "Bosch BMI323 I2C driver" + depends on I2C + select BMI323 + select REGMAP_I2C + help + Enable support for the Bosch BMI323 6-Axis IMU connected to I2C + interface. + + This driver can also be built as a module. If so, the module will be + called bmi323_i2c. + +config BMI323_SPI + tristate "Bosch BMI323 SPI driver" + depends on SPI + select BMI323 + select REGMAP_SPI + help + Enable support for the Bosch BMI323 6-Axis IMU connected to SPI + interface. + + This driver can also be built as a module. If so, the module will be + called bmi323_spi. diff --git a/drivers/iio/imu/bmi323/Makefile b/drivers/iio/imu/bmi323/Makefile new file mode 100644 index 000000000000..a6a6dc0207c9 --- /dev/null +++ b/drivers/iio/imu/bmi323/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for Bosch BMI323 IMU +# +obj-$(CONFIG_BMI323) += bmi323_core.o +obj-$(CONFIG_BMI323_I2C) += bmi323_i2c.o +obj-$(CONFIG_BMI323_SPI) += bmi323_spi.o diff --git a/drivers/iio/imu/bmi323/bmi323.h b/drivers/iio/imu/bmi323/bmi323.h new file mode 100644 index 000000000000..dff126d41658 --- /dev/null +++ b/drivers/iio/imu/bmi323/bmi323.h @@ -0,0 +1,209 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * IIO driver for Bosch BMI323 6-Axis IMU + * + * Copyright (C) 2023, Jagath Jog J + */ + +#ifndef _BMI323_H_ +#define _BMI323_H_ + +#include +#include +#include + +#define BMI323_I2C_DUMMY 2 +#define BMI323_SPI_DUMMY 1 + +/* Register map */ + +#define BMI323_CHIP_ID_REG 0x00 +#define BMI323_CHIP_ID_VAL 0x0043 +#define BMI323_CHIP_ID_MSK GENMASK(7, 0) +#define BMI323_ERR_REG 0x01 +#define BMI323_STATUS_REG 0x02 +#define BMI323_STATUS_POR_MSK BIT(0) + +/* Accelero/Gyro/Temp data registers */ +#define BMI323_ACCEL_X_REG 0x03 +#define BMI323_GYRO_X_REG 0x06 +#define BMI323_TEMP_REG 0x09 +#define BMI323_ALL_CHAN_MSK GENMASK(5, 0) + +/* Status registers */ +#define BMI323_STATUS_INT1_REG 0x0D +#define BMI323_STATUS_INT2_REG 0x0E +#define BMI323_STATUS_NOMOTION_MSK BIT(0) +#define BMI323_STATUS_MOTION_MSK BIT(1) +#define BMI323_STATUS_STP_WTR_MSK BIT(5) +#define BMI323_STATUS_TAP_MSK BIT(8) +#define BMI323_STATUS_ERROR_MSK BIT(10) +#define BMI323_STATUS_TMP_DRDY_MSK BIT(11) +#define BMI323_STATUS_GYR_DRDY_MSK BIT(12) +#define BMI323_STATUS_ACC_DRDY_MSK BIT(13) +#define BMI323_STATUS_ACC_GYR_DRDY_MSK GENMASK(13, 12) +#define BMI323_STATUS_FIFO_WTRMRK_MSK BIT(14) +#define BMI323_STATUS_FIFO_FULL_MSK BIT(15) + +/* Feature registers */ +#define BMI323_FEAT_IO0_REG 0x10 +#define BMI323_FEAT_IO0_XYZ_NOMOTION_MSK GENMASK(2, 0) +#define BMI323_FEAT_IO0_XYZ_MOTION_MSK GENMASK(5, 3) +#define BMI323_FEAT_XYZ_MSK GENMASK(2, 0) +#define BMI323_FEAT_IO0_STP_CNT_MSK BIT(9) +#define BMI323_FEAT_IO0_S_TAP_MSK BIT(12) +#define BMI323_FEAT_IO0_D_TAP_MSK BIT(13) +#define BMI323_FEAT_IO1_REG 0x11 +#define BMI323_FEAT_IO1_ERR_MSK GENMASK(3, 0) +#define BMI323_FEAT_IO2_REG 0x12 +#define BMI323_FEAT_IO_STATUS_REG 0x14 +#define BMI323_FEAT_IO_STATUS_MSK BIT(0) +#define BMI323_FEAT_ENG_POLL 2000 +#define BMI323_FEAT_ENG_TIMEOUT 10000 + +/* FIFO registers */ +#define BMI323_FIFO_FILL_LEVEL_REG 0x15 +#define BMI323_FIFO_DATA_REG 0x16 + +/* Accelero/Gyro config registers */ +#define BMI323_ACC_CONF_REG 0x20 +#define BMI323_GYRO_CONF_REG 0x21 +#define BMI323_ACC_GYRO_CONF_MODE_MSK GENMASK(14, 12) +#define BMI323_ACC_GYRO_CONF_ODR_MSK GENMASK(3, 0) +#define BMI323_ACC_GYRO_CONF_SCL_MSK GENMASK(6, 4) +#define BMI323_ACC_GYRO_CONF_BW_MSK BIT(7) +#define BMI323_ACC_GYRO_CONF_AVG_MSK GENMASK(10, 8) + +/* FIFO registers */ +#define BMI323_FIFO_WTRMRK_REG 0x35 +#define BMI323_FIFO_CONF_REG 0x36 +#define BMI323_FIFO_CONF_STP_FUL_MSK BIT(0) +#define BMI323_FIFO_CONF_ACC_GYR_EN_MSK GENMASK(10, 9) +#define BMI323_FIFO_ACC_GYR_MSK GENMASK(1, 0) +#define BMI323_FIFO_CTRL_REG 0x37 +#define BMI323_FIFO_FLUSH_MSK BIT(0) + +/* Interrupt pin config registers */ +#define BMI323_IO_INT_CTR_REG 0x38 +#define BMI323_IO_INT1_LVL_MSK BIT(0) +#define BMI323_IO_INT1_OD_MSK BIT(1) +#define BMI323_IO_INT1_OP_EN_MSK BIT(2) +#define BMI323_IO_INT1_LVL_OD_OP_MSK GENMASK(2, 0) +#define BMI323_IO_INT2_LVL_MSK BIT(8) +#define BMI323_IO_INT2_OD_MSK BIT(9) +#define BMI323_IO_INT2_OP_EN_MSK BIT(10) +#define BMI323_IO_INT2_LVL_OD_OP_MSK GENMASK(10, 8) +#define BMI323_IO_INT_CONF_REG 0x39 +#define BMI323_IO_INT_LTCH_MSK BIT(0) +#define BMI323_INT_MAP1_REG 0x3A +#define BMI323_INT_MAP2_REG 0x3B +#define BMI323_NOMOTION_MSK GENMASK(1, 0) +#define BMI323_MOTION_MSK GENMASK(3, 2) +#define BMI323_STEP_CNT_MSK GENMASK(11, 10) +#define BMI323_TAP_MSK GENMASK(1, 0) +#define BMI323_TMP_DRDY_MSK GENMASK(7, 6) +#define BMI323_GYR_DRDY_MSK GENMASK(9, 8) +#define BMI323_ACC_DRDY_MSK GENMASK(11, 10) +#define BMI323_FIFO_WTRMRK_MSK GENMASK(13, 12) +#define BMI323_FIFO_FULL_MSK GENMASK(15, 14) + +/* Feature registers */ +#define BMI323_FEAT_CTRL_REG 0x40 +#define BMI323_FEAT_ENG_EN_MSK BIT(0) +#define BMI323_FEAT_DATA_ADDR 0x41 +#define BMI323_FEAT_DATA_TX 0x42 +#define BMI323_FEAT_DATA_STATUS 0x43 +#define BMI323_FEAT_DATA_TX_RDY_MSK BIT(1) +#define BMI323_FEAT_EVNT_EXT_REG 0x47 +#define BMI323_FEAT_EVNT_EXT_S_MSK BIT(3) +#define BMI323_FEAT_EVNT_EXT_D_MSK BIT(4) + +#define BMI323_CMD_REG 0x7E +#define BMI323_RST_VAL 0xDEAF +#define BMI323_CFG_RES_REG 0x7F + +/* Extended registers */ +#define BMI323_GEN_SET1_REG 0x02 +#define BMI323_GEN_SET1_MODE_MSK BIT(0) +#define BMI323_GEN_HOLD_DUR_MSK GENMASK(4, 1) + +/* Any Motion/No Motion config registers */ +#define BMI323_ANYMO1_REG 0x05 +#define BMI323_NOMO1_REG 0x08 +#define BMI323_MO2_OFFSET 0x01 +#define BMI323_MO3_OFFSET 0x02 +#define BMI323_MO1_REF_UP_MSK BIT(12) +#define BMI323_MO1_SLOPE_TH_MSK GENMASK(11, 0) +#define BMI323_MO2_HYSTR_MSK GENMASK(9, 0) +#define BMI323_MO3_DURA_MSK GENMASK(12, 0) + +/* Step counter config registers */ +#define BMI323_STEP_SC1_REG 0x10 +#define BMI323_STEP_SC1_WTRMRK_MSK GENMASK(9, 0) +#define BMI323_STEP_SC1_RST_CNT_MSK BIT(10) +#define BMI323_STEP_SC1_REG 0x10 +#define BMI323_STEP_LEN 2 + +/* Tap gesture config registers */ +#define BMI323_TAP1_REG 0x1E +#define BMI323_TAP1_AXIS_SEL_MSK GENMASK(1, 0) +#define BMI323_AXIS_XYZ_MSK GENMASK(1, 0) +#define BMI323_TAP1_TIMOUT_MSK BIT(2) +#define BMI323_TAP1_MAX_PEAKS_MSK GENMASK(5, 3) +#define BMI323_TAP1_MODE_MSK GENMASK(7, 6) +#define BMI323_TAP2_REG 0x1F +#define BMI323_TAP2_THRES_MSK GENMASK(9, 0) +#define BMI323_TAP2_MAX_DUR_MSK GENMASK(15, 10) +#define BMI323_TAP3_REG 0x20 +#define BMI323_TAP3_QUIET_TIM_MSK GENMASK(15, 12) +#define BMI323_TAP3_QT_BW_TAP_MSK GENMASK(11, 8) +#define BMI323_TAP3_QT_AFT_GES_MSK GENMASK(15, 12) + +#define BMI323_MOTION_THRES_SCALE 512 +#define BMI323_MOTION_HYSTR_SCALE 512 +#define BMI323_MOTION_DURAT_SCALE 50 +#define BMI323_TAP_THRES_SCALE 512 +#define BMI323_DUR_BW_TAP_SCALE 200 +#define BMI323_QUITE_TIM_GES_SCALE 25 +#define BMI323_MAX_GES_DUR_SCALE 25 + +/* + * The formula to calculate temperature in C. + * See datasheet section 6.1.1, Register Map Overview + * + * T_C = (temp_raw / 512) + 23 + */ +#define BMI323_TEMP_OFFSET 11776 +#define BMI323_TEMP_SCALE 1953125 + +/* + * The BMI323 features a FIFO with a capacity of 2048 bytes. Each frame + * consists of accelerometer (X, Y, Z) data and gyroscope (X, Y, Z) data, + * totaling 6 words or 12 bytes. The FIFO buffer can hold a total of + * 170 frames. + * + * If a watermark interrupt is configured for 170 frames, the interrupt will + * trigger when the FIFO reaches 169 frames, so limit the maximum watermark + * level to 169 frames. In terms of data, 169 frames would equal 1014 bytes, + * which is approximately 2 frames before the FIFO reaches its full capacity. + * See datasheet section 5.7.3 FIFO Buffer Interrupts + */ +#define BMI323_BYTES_PER_SAMPLE 2 +#define BMI323_FIFO_LENGTH_IN_BYTES 2048 +#define BMI323_FIFO_FRAME_LENGTH 6 +#define BMI323_FIFO_FULL_IN_FRAMES \ + ((BMI323_FIFO_LENGTH_IN_BYTES / \ + (BMI323_BYTES_PER_SAMPLE * BMI323_FIFO_FRAME_LENGTH)) - 1) +#define BMI323_FIFO_FULL_IN_WORDS \ + (BMI323_FIFO_FULL_IN_FRAMES * BMI323_FIFO_FRAME_LENGTH) + +#define BMI323_INT_MICRO_TO_RAW(val, val2, scale) ((val) * (scale) + \ + ((val2) * (scale)) / MEGA) + +#define BMI323_RAW_TO_MICRO(raw, scale) ((((raw) % (scale)) * MEGA) / scale) + +struct device; +int bmi323_core_probe(struct device *dev); +extern const struct regmap_config bmi323_regmap_config; + +#endif diff --git a/drivers/iio/imu/bmi323/bmi323_core.c b/drivers/iio/imu/bmi323/bmi323_core.c new file mode 100644 index 000000000000..0bd5dedd9a63 --- /dev/null +++ b/drivers/iio/imu/bmi323/bmi323_core.c @@ -0,0 +1,2139 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * IIO core driver for Bosch BMI323 6-Axis IMU. + * + * Copyright (C) 2023, Jagath Jog J + * + * Datasheet: https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmi323-ds000.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "bmi323.h" + +enum bmi323_sensor_type { + BMI323_ACCEL, + BMI323_GYRO, + BMI323_SENSORS_CNT, +}; + +enum bmi323_opr_mode { + ACC_GYRO_MODE_DISABLE = 0x00, + GYRO_DRIVE_MODE_ENABLED = 0x01, + ACC_GYRO_MODE_DUTYCYCLE = 0x03, + ACC_GYRO_MODE_CONTINOUS = 0x04, + ACC_GYRO_MODE_HIGH_PERF = 0x07, +}; + +enum bmi323_state { + BMI323_IDLE, + BMI323_BUFFER_DRDY_TRIGGERED, + BMI323_BUFFER_FIFO, +}; + +enum bmi323_irq_pin { + BMI323_IRQ_DISABLED, + BMI323_IRQ_INT1, + BMI323_IRQ_INT2, +}; + +enum bmi323_3db_bw { + BMI323_BW_ODR_BY_2, + BMI323_BW_ODR_BY_4, +}; + +enum bmi323_scan { + BMI323_ACCEL_X, + BMI323_ACCEL_Y, + BMI323_ACCEL_Z, + BMI323_GYRO_X, + BMI323_GYRO_Y, + BMI323_GYRO_Z, + BMI323_CHAN_MAX +}; + +struct bmi323_hw { + u8 data; + u8 config; + const int (*scale_table)[2]; + int scale_table_len; +}; + +/* + * The accelerometer supports +-2G/4G/8G/16G ranges, and the resolution of + * each sample is 16 bits, signed. + * At +-8G the scale can calculated by + * ((8 + 8) * 9.80665 / (2^16 - 1)) * 10^6 = 2394.23819 scale in micro + * + */ +static const int bmi323_accel_scale[][2] = { + { 0, 598 }, + { 0, 1197 }, + { 0, 2394 }, + { 0, 4788 }, +}; + +static const int bmi323_gyro_scale[][2] = { + { 0, 66 }, + { 0, 133 }, + { 0, 266 }, + { 0, 532 }, + { 0, 1065 }, +}; + +static const int bmi323_accel_gyro_avrg[] = {0, 2, 4, 8, 16, 32, 64}; + +static const struct bmi323_hw bmi323_hw[2] = { + [BMI323_ACCEL] = { + .data = BMI323_ACCEL_X_REG, + .config = BMI323_ACC_CONF_REG, + .scale_table = bmi323_accel_scale, + .scale_table_len = ARRAY_SIZE(bmi323_accel_scale), + }, + [BMI323_GYRO] = { + .data = BMI323_GYRO_X_REG, + .config = BMI323_GYRO_CONF_REG, + .scale_table = bmi323_gyro_scale, + .scale_table_len = ARRAY_SIZE(bmi323_gyro_scale), + }, +}; + +struct bmi323_data { + struct device *dev; + struct regmap *regmap; + struct iio_mount_matrix orientation; + enum bmi323_irq_pin irq_pin; + struct iio_trigger *trig; + bool drdy_trigger_enabled; + enum bmi323_state state; + s64 fifo_tstamp, old_fifo_tstamp; + u32 odrns[BMI323_SENSORS_CNT]; + u32 odrhz[BMI323_SENSORS_CNT]; + unsigned int feature_events; + + /* + * Lock to protect the members of device's private data from concurrent + * access and also to serialize the access of extended registers. + * See bmi323_write_ext_reg(..) for more info. + */ + struct mutex mutex; + int watermark; + __le16 fifo_buff[BMI323_FIFO_FULL_IN_WORDS] __aligned(IIO_DMA_MINALIGN); + struct { + __le16 channels[BMI323_CHAN_MAX]; + s64 ts __aligned(8); + } buffer; + __le16 steps_count[BMI323_STEP_LEN]; +}; + +static const struct iio_mount_matrix * +bmi323_get_mount_matrix(const struct iio_dev *idev, + const struct iio_chan_spec *chan) +{ + struct bmi323_data *data = iio_priv(idev); + + return &data->orientation; +} + +static const struct iio_chan_spec_ext_info bmi323_ext_info[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_TYPE, bmi323_get_mount_matrix), + { } +}; + +static const struct iio_event_spec bmi323_step_wtrmrk_event = { + .type = IIO_EV_TYPE_CHANGE, + .dir = IIO_EV_DIR_NONE, + .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_VALUE), +}; + +static const struct iio_event_spec bmi323_accel_event[] = { + { + .type = IIO_EV_TYPE_MAG, + .dir = IIO_EV_DIR_FALLING, + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_PERIOD) | + BIT(IIO_EV_INFO_HYSTERESIS) | + BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_MAG, + .dir = IIO_EV_DIR_RISING, + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_PERIOD) | + BIT(IIO_EV_INFO_HYSTERESIS) | + BIT(IIO_EV_INFO_ENABLE), + }, + { + .type = IIO_EV_TYPE_GESTURE, + .dir = IIO_EV_DIR_SINGLETAP, + .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_RESET_TIMEOUT), + }, + { + .type = IIO_EV_TYPE_GESTURE, + .dir = IIO_EV_DIR_DOUBLETAP, + .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_RESET_TIMEOUT) | + BIT(IIO_EV_INFO_TAP2_MIN_DELAY), + }, +}; + +#define BMI323_ACCEL_CHANNEL(_type, _axis, _index) { \ + .type = _type, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ + .ext_info = bmi323_ext_info, \ + .event_spec = bmi323_accel_event, \ + .num_event_specs = ARRAY_SIZE(bmi323_accel_event), \ +} + +#define BMI323_GYRO_CHANNEL(_type, _axis, _index) { \ + .type = _type, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ + .ext_info = bmi323_ext_info, \ +} + +static const struct iio_chan_spec bmi323_channels[] = { + BMI323_ACCEL_CHANNEL(IIO_ACCEL, X, BMI323_ACCEL_X), + BMI323_ACCEL_CHANNEL(IIO_ACCEL, Y, BMI323_ACCEL_Y), + BMI323_ACCEL_CHANNEL(IIO_ACCEL, Z, BMI323_ACCEL_Z), + BMI323_GYRO_CHANNEL(IIO_ANGL_VEL, X, BMI323_GYRO_X), + BMI323_GYRO_CHANNEL(IIO_ANGL_VEL, Y, BMI323_GYRO_Y), + BMI323_GYRO_CHANNEL(IIO_ANGL_VEL, Z, BMI323_GYRO_Z), + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_SCALE), + .scan_index = -1, + }, + { + .type = IIO_STEPS, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | + BIT(IIO_CHAN_INFO_ENABLE), + .scan_index = -1, + .event_spec = &bmi323_step_wtrmrk_event, + .num_event_specs = 1, + + }, + IIO_CHAN_SOFT_TIMESTAMP(BMI323_CHAN_MAX), +}; + +static const int bmi323_acc_gyro_odr[][2] = { + { 0, 781250 }, + { 1, 562500 }, + { 3, 125000 }, + { 6, 250000 }, + { 12, 500000 }, + { 25, 0 }, + { 50, 0 }, + { 100, 0 }, + { 200, 0 }, + { 400, 0 }, + { 800, 0 }, +}; + +static const int bmi323_acc_gyro_odrns[] = { + 1280 * MEGA, + 640 * MEGA, + 320 * MEGA, + 160 * MEGA, + 80 * MEGA, + 40 * MEGA, + 20 * MEGA, + 10 * MEGA, + 5 * MEGA, + 2500 * KILO, + 1250 * KILO, +}; + +static enum bmi323_sensor_type bmi323_iio_to_sensor(enum iio_chan_type iio_type) +{ + switch (iio_type) { + case IIO_ACCEL: + return BMI323_ACCEL; + case IIO_ANGL_VEL: + return BMI323_GYRO; + default: + return -EINVAL; + } +} + +static int bmi323_set_mode(struct bmi323_data *data, + enum bmi323_sensor_type sensor, + enum bmi323_opr_mode mode) +{ + guard(mutex)(&data->mutex); + return regmap_update_bits(data->regmap, bmi323_hw[sensor].config, + BMI323_ACC_GYRO_CONF_MODE_MSK, + FIELD_PREP(BMI323_ACC_GYRO_CONF_MODE_MSK, + mode)); +} + +/* + * When writing data to extended register there must be no communication to + * any other register before write transaction is complete. + * See datasheet section 6.2 Extended Register Map Description. + */ +static int bmi323_write_ext_reg(struct bmi323_data *data, unsigned int ext_addr, + unsigned int ext_data) +{ + int ret, feature_status; + + ret = regmap_read(data->regmap, BMI323_FEAT_DATA_STATUS, + &feature_status); + if (ret) + return ret; + + if (!FIELD_GET(BMI323_FEAT_DATA_TX_RDY_MSK, feature_status)) + return -EBUSY; + + ret = regmap_write(data->regmap, BMI323_FEAT_DATA_ADDR, ext_addr); + if (ret) + return ret; + + return regmap_write(data->regmap, BMI323_FEAT_DATA_TX, ext_data); +} + +/* + * When reading data from extended register there must be no communication to + * any other register before read transaction is complete. + * See datasheet section 6.2 Extended Register Map Description. + */ +static int bmi323_read_ext_reg(struct bmi323_data *data, unsigned int ext_addr, + unsigned int *ext_data) +{ + int ret, feature_status; + + ret = regmap_read(data->regmap, BMI323_FEAT_DATA_STATUS, + &feature_status); + if (ret) + return ret; + + if (!FIELD_GET(BMI323_FEAT_DATA_TX_RDY_MSK, feature_status)) + return -EBUSY; + + ret = regmap_write(data->regmap, BMI323_FEAT_DATA_ADDR, ext_addr); + if (ret) + return ret; + + return regmap_read(data->regmap, BMI323_FEAT_DATA_TX, ext_data); +} + +static int bmi323_update_ext_reg(struct bmi323_data *data, + unsigned int ext_addr, + unsigned int mask, unsigned int ext_data) +{ + unsigned int value; + int ret; + + ret = bmi323_read_ext_reg(data, ext_addr, &value); + if (ret) + return ret; + + set_mask_bits(&value, mask, ext_data); + + return bmi323_write_ext_reg(data, ext_addr, value); +} + +static int bmi323_get_error_status(struct bmi323_data *data) +{ + int error, ret; + + guard(mutex)(&data->mutex); + ret = regmap_read(data->regmap, BMI323_ERR_REG, &error); + if (ret) + return ret; + + if (error) + dev_err(data->dev, "Sensor error 0x%x\n", error); + + return error; +} + +static int bmi323_feature_engine_events(struct bmi323_data *data, + const unsigned int event_mask, + bool state) +{ + unsigned int value; + int ret; + + ret = regmap_read(data->regmap, BMI323_FEAT_IO0_REG, &value); + if (ret) + return ret; + + /* Register must be cleared before changing an active config */ + ret = regmap_write(data->regmap, BMI323_FEAT_IO0_REG, 0); + if (ret) + return ret; + + if (state) + value |= event_mask; + else + value &= ~event_mask; + + ret = regmap_write(data->regmap, BMI323_FEAT_IO0_REG, value); + if (ret) + return ret; + + return regmap_write(data->regmap, BMI323_FEAT_IO_STATUS_REG, + BMI323_FEAT_IO_STATUS_MSK); +} + +static int bmi323_step_wtrmrk_en(struct bmi323_data *data, int state) +{ + enum bmi323_irq_pin step_irq; + int ret; + + guard(mutex)(&data->mutex); + if (!FIELD_GET(BMI323_FEAT_IO0_STP_CNT_MSK, data->feature_events)) + return -EINVAL; + + if (state) + step_irq = data->irq_pin; + else + step_irq = BMI323_IRQ_DISABLED; + + ret = bmi323_update_ext_reg(data, BMI323_STEP_SC1_REG, + BMI323_STEP_SC1_WTRMRK_MSK, + FIELD_PREP(BMI323_STEP_SC1_WTRMRK_MSK, + state ? 1 : 0)); + if (ret) + return ret; + + return regmap_update_bits(data->regmap, BMI323_INT_MAP1_REG, + BMI323_STEP_CNT_MSK, + FIELD_PREP(BMI323_STEP_CNT_MSK, step_irq)); +} + +static int bmi323_motion_config_reg(enum iio_event_direction dir) +{ + switch (dir) { + case IIO_EV_DIR_RISING: + return BMI323_ANYMO1_REG; + case IIO_EV_DIR_FALLING: + return BMI323_NOMO1_REG; + default: + return -EINVAL; + } +} + +static int bmi323_motion_event_en(struct bmi323_data *data, + enum iio_event_direction dir, int state) +{ + unsigned int state_value = state ? BMI323_FEAT_XYZ_MSK : 0; + int config, ret, msk, raw, field_value; + enum bmi323_irq_pin motion_irq; + int irq_msk, irq_field_val; + + if (state) + motion_irq = data->irq_pin; + else + motion_irq = BMI323_IRQ_DISABLED; + + switch (dir) { + case IIO_EV_DIR_RISING: + msk = BMI323_FEAT_IO0_XYZ_MOTION_MSK; + raw = 512; + config = BMI323_ANYMO1_REG; + irq_msk = BMI323_MOTION_MSK; + irq_field_val = FIELD_PREP(BMI323_MOTION_MSK, motion_irq); + field_value = FIELD_PREP(BMI323_FEAT_IO0_XYZ_MOTION_MSK, + state_value); + break; + case IIO_EV_DIR_FALLING: + msk = BMI323_FEAT_IO0_XYZ_NOMOTION_MSK; + raw = 0; + config = BMI323_NOMO1_REG; + irq_msk = BMI323_NOMOTION_MSK; + irq_field_val = FIELD_PREP(BMI323_NOMOTION_MSK, motion_irq); + field_value = FIELD_PREP(BMI323_FEAT_IO0_XYZ_NOMOTION_MSK, + state_value); + break; + default: + return -EINVAL; + } + + guard(mutex)(&data->mutex); + ret = bmi323_feature_engine_events(data, msk, state); + if (ret) + return ret; + + ret = bmi323_update_ext_reg(data, config, + BMI323_MO1_REF_UP_MSK, + FIELD_PREP(BMI323_MO1_REF_UP_MSK, 0)); + if (ret) + return ret; + + /* Set initial value to avoid interrupts while enabling*/ + ret = bmi323_update_ext_reg(data, config, + BMI323_MO1_SLOPE_TH_MSK, + FIELD_PREP(BMI323_MO1_SLOPE_TH_MSK, raw)); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, BMI323_INT_MAP1_REG, irq_msk, + irq_field_val); + if (ret) + return ret; + + set_mask_bits(&data->feature_events, msk, field_value); + + return 0; +} + +static int bmi323_tap_event_en(struct bmi323_data *data, + enum iio_event_direction dir, int state) +{ + enum bmi323_irq_pin tap_irq; + int ret, tap_enabled; + + guard(mutex)(&data->mutex); + + if (data->odrhz[BMI323_ACCEL] < 200) { + dev_err(data->dev, "Invalid accelrometer parameter\n"); + return -EINVAL; + } + + switch (dir) { + case IIO_EV_DIR_SINGLETAP: + ret = bmi323_feature_engine_events(data, + BMI323_FEAT_IO0_S_TAP_MSK, + state); + if (ret) + return ret; + + set_mask_bits(&data->feature_events, BMI323_FEAT_IO0_S_TAP_MSK, + FIELD_PREP(BMI323_FEAT_IO0_S_TAP_MSK, state)); + break; + case IIO_EV_DIR_DOUBLETAP: + ret = bmi323_feature_engine_events(data, + BMI323_FEAT_IO0_D_TAP_MSK, + state); + if (ret) + return ret; + + set_mask_bits(&data->feature_events, BMI323_FEAT_IO0_D_TAP_MSK, + FIELD_PREP(BMI323_FEAT_IO0_D_TAP_MSK, state)); + break; + default: + return -EINVAL; + } + + tap_enabled = FIELD_GET(BMI323_FEAT_IO0_S_TAP_MSK | + BMI323_FEAT_IO0_D_TAP_MSK, + data->feature_events); + + if (tap_enabled) + tap_irq = data->irq_pin; + else + tap_irq = BMI323_IRQ_DISABLED; + + ret = regmap_update_bits(data->regmap, BMI323_INT_MAP2_REG, + BMI323_TAP_MSK, + FIELD_PREP(BMI323_TAP_MSK, tap_irq)); + if (ret) + return ret; + + if (!state) + return 0; + + ret = bmi323_update_ext_reg(data, BMI323_TAP1_REG, + BMI323_TAP1_MAX_PEAKS_MSK, + FIELD_PREP(BMI323_TAP1_MAX_PEAKS_MSK, + 0x04)); + if (ret) + return ret; + + ret = bmi323_update_ext_reg(data, BMI323_TAP1_REG, + BMI323_TAP1_AXIS_SEL_MSK, + FIELD_PREP(BMI323_TAP1_AXIS_SEL_MSK, + BMI323_AXIS_XYZ_MSK)); + if (ret) + return ret; + + return bmi323_update_ext_reg(data, BMI323_TAP1_REG, + BMI323_TAP1_TIMOUT_MSK, + FIELD_PREP(BMI323_TAP1_TIMOUT_MSK, + 0)); +} + +static ssize_t in_accel_gesture_tap_wait_dur_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct bmi323_data *data = iio_priv(indio_dev); + unsigned int reg_value, raw; + int ret, val[2]; + + scoped_guard(mutex, &data->mutex) { + ret = bmi323_read_ext_reg(data, BMI323_TAP2_REG, ®_value); + if (ret) + return ret; + } + + raw = FIELD_GET(BMI323_TAP2_MAX_DUR_MSK, reg_value); + val[0] = raw / BMI323_MAX_GES_DUR_SCALE; + val[1] = BMI323_RAW_TO_MICRO(raw, BMI323_MAX_GES_DUR_SCALE); + + return iio_format_value(buf, IIO_VAL_INT_PLUS_MICRO, ARRAY_SIZE(val), + val); +} + +static ssize_t in_accel_gesture_tap_wait_dur_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct bmi323_data *data = iio_priv(indio_dev); + int ret, val_int, val_fract, raw; + + ret = iio_str_to_fixpoint(buf, 100000, &val_int, &val_fract); + if (ret) + return ret; + + raw = BMI323_INT_MICRO_TO_RAW(val_int, val_fract, + BMI323_MAX_GES_DUR_SCALE); + if (!in_range(raw, 0, 64)) + return -EINVAL; + + guard(mutex)(&data->mutex); + ret = bmi323_update_ext_reg(data, BMI323_TAP2_REG, + BMI323_TAP2_MAX_DUR_MSK, + FIELD_PREP(BMI323_TAP2_MAX_DUR_MSK, raw)); + if (ret) + return ret; + + return len; +} + +/* + * Maximum duration from first tap within the second tap is expected to happen. + * This timeout is applicable only if gesture_tap_wait_timeout is enabled. + */ +static IIO_DEVICE_ATTR_RW(in_accel_gesture_tap_wait_dur, 0); + +static ssize_t in_accel_gesture_tap_wait_timeout_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct bmi323_data *data = iio_priv(indio_dev); + unsigned int reg_value, raw; + int ret; + + scoped_guard(mutex, &data->mutex) { + ret = bmi323_read_ext_reg(data, BMI323_TAP1_REG, ®_value); + if (ret) + return ret; + } + + raw = FIELD_GET(BMI323_TAP1_TIMOUT_MSK, reg_value); + + return iio_format_value(buf, IIO_VAL_INT, 1, &raw); +} + +static ssize_t in_accel_gesture_tap_wait_timeout_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct bmi323_data *data = iio_priv(indio_dev); + bool val; + int ret; + + ret = kstrtobool(buf, &val); + if (ret) + return ret; + + guard(mutex)(&data->mutex); + ret = bmi323_update_ext_reg(data, BMI323_TAP1_REG, + BMI323_TAP1_TIMOUT_MSK, + FIELD_PREP(BMI323_TAP1_TIMOUT_MSK, val)); + if (ret) + return ret; + + return len; +} + +/* Enable/disable gesture confirmation with wait time */ +static IIO_DEVICE_ATTR_RW(in_accel_gesture_tap_wait_timeout, 0); + +static IIO_CONST_ATTR(in_accel_gesture_tap_wait_dur_available, + "[0.0 0.04 2.52]"); + +static IIO_CONST_ATTR(in_accel_gesture_doubletap_tap2_min_delay_available, + "[0.005 0.005 0.075]"); + +static IIO_CONST_ATTR(in_accel_gesture_tap_reset_timeout_available, + "[0.04 0.04 0.6]"); + +static IIO_CONST_ATTR(in_accel_gesture_tap_value_available, "[0.0 0.002 1.99]"); + +static IIO_CONST_ATTR(in_accel_mag_value_available, "[0.0 0.002 7.99]"); + +static IIO_CONST_ATTR(in_accel_mag_period_available, "[0.0 0.02 162.0]"); + +static IIO_CONST_ATTR(in_accel_mag_hysteresis_available, "[0.0 0.002 1.99]"); + +static struct attribute *bmi323_event_attributes[] = { + &iio_const_attr_in_accel_gesture_tap_value_available.dev_attr.attr, + &iio_const_attr_in_accel_gesture_tap_reset_timeout_available.dev_attr.attr, + &iio_const_attr_in_accel_gesture_doubletap_tap2_min_delay_available.dev_attr.attr, + &iio_const_attr_in_accel_gesture_tap_wait_dur_available.dev_attr.attr, + &iio_dev_attr_in_accel_gesture_tap_wait_timeout.dev_attr.attr, + &iio_dev_attr_in_accel_gesture_tap_wait_dur.dev_attr.attr, + &iio_const_attr_in_accel_mag_value_available.dev_attr.attr, + &iio_const_attr_in_accel_mag_period_available.dev_attr.attr, + &iio_const_attr_in_accel_mag_hysteresis_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group bmi323_event_attribute_group = { + .attrs = bmi323_event_attributes, +}; + +static int bmi323_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, int state) +{ + struct bmi323_data *data = iio_priv(indio_dev); + + switch (type) { + case IIO_EV_TYPE_MAG: + return bmi323_motion_event_en(data, dir, state); + case IIO_EV_TYPE_GESTURE: + return bmi323_tap_event_en(data, dir, state); + case IIO_EV_TYPE_CHANGE: + return bmi323_step_wtrmrk_en(data, state); + default: + return -EINVAL; + } +} + +static int bmi323_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct bmi323_data *data = iio_priv(indio_dev); + int ret, value, reg_val; + + guard(mutex)(&data->mutex); + + switch (chan->type) { + case IIO_ACCEL: + switch (dir) { + case IIO_EV_DIR_SINGLETAP: + ret = FIELD_GET(BMI323_FEAT_IO0_S_TAP_MSK, + data->feature_events); + break; + case IIO_EV_DIR_DOUBLETAP: + ret = FIELD_GET(BMI323_FEAT_IO0_D_TAP_MSK, + data->feature_events); + break; + case IIO_EV_DIR_RISING: + value = FIELD_GET(BMI323_FEAT_IO0_XYZ_MOTION_MSK, + data->feature_events); + ret = value ? 1 : 0; + break; + case IIO_EV_DIR_FALLING: + value = FIELD_GET(BMI323_FEAT_IO0_XYZ_NOMOTION_MSK, + data->feature_events); + ret = value ? 1 : 0; + break; + default: + ret = -EINVAL; + break; + } + return ret; + case IIO_STEPS: + ret = regmap_read(data->regmap, BMI323_INT_MAP1_REG, ®_val); + if (ret) + return ret; + + return FIELD_GET(BMI323_STEP_CNT_MSK, reg_val) ? 1 : 0; + default: + return -EINVAL; + } +} + +static int bmi323_write_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct bmi323_data *data = iio_priv(indio_dev); + unsigned int raw; + int reg; + + guard(mutex)(&data->mutex); + + switch (type) { + case IIO_EV_TYPE_GESTURE: + switch (info) { + case IIO_EV_INFO_VALUE: + if (!in_range(val, 0, 2)) + return -EINVAL; + + raw = BMI323_INT_MICRO_TO_RAW(val, val2, + BMI323_TAP_THRES_SCALE); + + return bmi323_update_ext_reg(data, BMI323_TAP2_REG, + BMI323_TAP2_THRES_MSK, + FIELD_PREP(BMI323_TAP2_THRES_MSK, + raw)); + case IIO_EV_INFO_RESET_TIMEOUT: + if (val || !in_range(val2, 40000, 560001)) + return -EINVAL; + + raw = BMI323_INT_MICRO_TO_RAW(val, val2, + BMI323_QUITE_TIM_GES_SCALE); + + return bmi323_update_ext_reg(data, BMI323_TAP3_REG, + BMI323_TAP3_QT_AFT_GES_MSK, + FIELD_PREP(BMI323_TAP3_QT_AFT_GES_MSK, + raw)); + case IIO_EV_INFO_TAP2_MIN_DELAY: + if (val || !in_range(val2, 5000, 70001)) + return -EINVAL; + + raw = BMI323_INT_MICRO_TO_RAW(val, val2, + BMI323_DUR_BW_TAP_SCALE); + + return bmi323_update_ext_reg(data, BMI323_TAP3_REG, + BMI323_TAP3_QT_BW_TAP_MSK, + FIELD_PREP(BMI323_TAP3_QT_BW_TAP_MSK, + raw)); + default: + return -EINVAL; + } + case IIO_EV_TYPE_MAG: + reg = bmi323_motion_config_reg(dir); + if (reg < 0) + return -EINVAL; + + switch (info) { + case IIO_EV_INFO_VALUE: + if (!in_range(val, 0, 8)) + return -EINVAL; + + raw = BMI323_INT_MICRO_TO_RAW(val, val2, + BMI323_MOTION_THRES_SCALE); + + return bmi323_update_ext_reg(data, reg, + BMI323_MO1_SLOPE_TH_MSK, + FIELD_PREP(BMI323_MO1_SLOPE_TH_MSK, + raw)); + case IIO_EV_INFO_PERIOD: + if (!in_range(val, 0, 163)) + return -EINVAL; + + raw = BMI323_INT_MICRO_TO_RAW(val, val2, + BMI323_MOTION_DURAT_SCALE); + + return bmi323_update_ext_reg(data, + reg + BMI323_MO3_OFFSET, + BMI323_MO3_DURA_MSK, + FIELD_PREP(BMI323_MO3_DURA_MSK, + raw)); + case IIO_EV_INFO_HYSTERESIS: + if (!in_range(val, 0, 2)) + return -EINVAL; + + raw = BMI323_INT_MICRO_TO_RAW(val, val2, + BMI323_MOTION_HYSTR_SCALE); + + return bmi323_update_ext_reg(data, + reg + BMI323_MO2_OFFSET, + BMI323_MO2_HYSTR_MSK, + FIELD_PREP(BMI323_MO2_HYSTR_MSK, + raw)); + default: + return -EINVAL; + } + case IIO_EV_TYPE_CHANGE: + if (!in_range(val, 0, 20461)) + return -EINVAL; + + raw = val / 20; + return bmi323_update_ext_reg(data, BMI323_STEP_SC1_REG, + BMI323_STEP_SC1_WTRMRK_MSK, + FIELD_PREP(BMI323_STEP_SC1_WTRMRK_MSK, + raw)); + default: + return -EINVAL; + } +} + +static int bmi323_read_event_value(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct bmi323_data *data = iio_priv(indio_dev); + unsigned int raw, reg_value; + int ret, reg; + + guard(mutex)(&data->mutex); + + switch (type) { + case IIO_EV_TYPE_GESTURE: + switch (info) { + case IIO_EV_INFO_VALUE: + ret = bmi323_read_ext_reg(data, BMI323_TAP2_REG, + ®_value); + if (ret) + return ret; + + raw = FIELD_GET(BMI323_TAP2_THRES_MSK, reg_value); + *val = raw / BMI323_TAP_THRES_SCALE; + *val2 = BMI323_RAW_TO_MICRO(raw, BMI323_TAP_THRES_SCALE); + return IIO_VAL_INT_PLUS_MICRO; + case IIO_EV_INFO_RESET_TIMEOUT: + ret = bmi323_read_ext_reg(data, BMI323_TAP3_REG, + ®_value); + if (ret) + return ret; + + raw = FIELD_GET(BMI323_TAP3_QT_AFT_GES_MSK, reg_value); + *val = 0; + *val2 = BMI323_RAW_TO_MICRO(raw, + BMI323_QUITE_TIM_GES_SCALE); + return IIO_VAL_INT_PLUS_MICRO; + case IIO_EV_INFO_TAP2_MIN_DELAY: + ret = bmi323_read_ext_reg(data, BMI323_TAP3_REG, + ®_value); + if (ret) + return ret; + + raw = FIELD_GET(BMI323_TAP3_QT_BW_TAP_MSK, reg_value); + *val = 0; + *val2 = BMI323_RAW_TO_MICRO(raw, + BMI323_DUR_BW_TAP_SCALE); + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_EV_TYPE_MAG: + reg = bmi323_motion_config_reg(dir); + if (reg < 0) + return -EINVAL; + + switch (info) { + case IIO_EV_INFO_VALUE: + ret = bmi323_read_ext_reg(data, reg, ®_value); + if (ret) + return ret; + + raw = FIELD_GET(BMI323_MO1_SLOPE_TH_MSK, reg_value); + *val = raw / BMI323_MOTION_THRES_SCALE; + *val2 = BMI323_RAW_TO_MICRO(raw, + BMI323_MOTION_THRES_SCALE); + return IIO_VAL_INT_PLUS_MICRO; + case IIO_EV_INFO_PERIOD: + ret = bmi323_read_ext_reg(data, + reg + BMI323_MO3_OFFSET, + ®_value); + if (ret) + return ret; + + raw = FIELD_GET(BMI323_MO3_DURA_MSK, reg_value); + *val = raw / BMI323_MOTION_DURAT_SCALE; + *val2 = BMI323_RAW_TO_MICRO(raw, + BMI323_MOTION_DURAT_SCALE); + return IIO_VAL_INT_PLUS_MICRO; + case IIO_EV_INFO_HYSTERESIS: + ret = bmi323_read_ext_reg(data, + reg + BMI323_MO2_OFFSET, + ®_value); + if (ret) + return ret; + + raw = FIELD_GET(BMI323_MO2_HYSTR_MSK, reg_value); + *val = raw / BMI323_MOTION_HYSTR_SCALE; + *val2 = BMI323_RAW_TO_MICRO(raw, + BMI323_MOTION_HYSTR_SCALE); + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_EV_TYPE_CHANGE: + ret = bmi323_read_ext_reg(data, BMI323_STEP_SC1_REG, + ®_value); + if (ret) + return ret; + + raw = FIELD_GET(BMI323_STEP_SC1_WTRMRK_MSK, reg_value); + *val = raw * 20; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int __bmi323_fifo_flush(struct iio_dev *indio_dev) +{ + struct bmi323_data *data = iio_priv(indio_dev); + int i, ret, fifo_lvl, frame_count, bit, index; + __le16 *frame, *pchannels; + u64 sample_period; + s64 tstamp; + + guard(mutex)(&data->mutex); + ret = regmap_read(data->regmap, BMI323_FIFO_FILL_LEVEL_REG, &fifo_lvl); + if (ret) + return ret; + + fifo_lvl = min(fifo_lvl, BMI323_FIFO_FULL_IN_WORDS); + + frame_count = fifo_lvl / BMI323_FIFO_FRAME_LENGTH; + if (!frame_count) + return -EINVAL; + + if (fifo_lvl % BMI323_FIFO_FRAME_LENGTH) + dev_warn(data->dev, "Bad FIFO alignment\n"); + + /* + * Approximate timestamps for each of the sample based on the sampling + * frequency, timestamp for last sample and number of samples. + */ + if (data->old_fifo_tstamp) { + sample_period = data->fifo_tstamp - data->old_fifo_tstamp; + do_div(sample_period, frame_count); + } else { + sample_period = data->odrns[BMI323_ACCEL]; + } + + tstamp = data->fifo_tstamp - (frame_count - 1) * sample_period; + + ret = regmap_noinc_read(data->regmap, BMI323_FIFO_DATA_REG, + &data->fifo_buff[0], + fifo_lvl * BMI323_BYTES_PER_SAMPLE); + if (ret) + return ret; + + for (i = 0; i < frame_count; i++) { + frame = &data->fifo_buff[i * BMI323_FIFO_FRAME_LENGTH]; + pchannels = &data->buffer.channels[0]; + + index = 0; + for_each_set_bit(bit, indio_dev->active_scan_mask, + BMI323_CHAN_MAX) + pchannels[index++] = frame[bit]; + + iio_push_to_buffers_with_timestamp(indio_dev, &data->buffer, + tstamp); + + tstamp += sample_period; + } + + return frame_count; +} + +static int bmi323_set_watermark(struct iio_dev *indio_dev, unsigned int val) +{ + struct bmi323_data *data = iio_priv(indio_dev); + + val = min(val, (u32)BMI323_FIFO_FULL_IN_FRAMES); + + guard(mutex)(&data->mutex); + data->watermark = val; + + return 0; +} + +static int bmi323_fifo_disable(struct bmi323_data *data) +{ + int ret; + + guard(mutex)(&data->mutex); + ret = regmap_write(data->regmap, BMI323_FIFO_CONF_REG, 0); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, BMI323_INT_MAP2_REG, + BMI323_FIFO_WTRMRK_MSK, + FIELD_PREP(BMI323_FIFO_WTRMRK_MSK, 0)); + if (ret) + return ret; + + data->fifo_tstamp = 0; + data->state = BMI323_IDLE; + + return 0; +} + +static int bmi323_buffer_predisable(struct iio_dev *indio_dev) +{ + struct bmi323_data *data = iio_priv(indio_dev); + + if (iio_device_get_current_mode(indio_dev) == INDIO_BUFFER_TRIGGERED) + return 0; + + return bmi323_fifo_disable(data); +} + +static int bmi323_update_watermark(struct bmi323_data *data) +{ + int wtrmrk; + + wtrmrk = data->watermark * BMI323_FIFO_FRAME_LENGTH; + + return regmap_write(data->regmap, BMI323_FIFO_WTRMRK_REG, wtrmrk); +} + +static int bmi323_fifo_enable(struct bmi323_data *data) +{ + int ret; + + guard(mutex)(&data->mutex); + ret = regmap_update_bits(data->regmap, BMI323_FIFO_CONF_REG, + BMI323_FIFO_CONF_ACC_GYR_EN_MSK, + FIELD_PREP(BMI323_FIFO_CONF_ACC_GYR_EN_MSK, + BMI323_FIFO_ACC_GYR_MSK)); + if (ret) + return ret; + + ret = regmap_update_bits(data->regmap, BMI323_INT_MAP2_REG, + BMI323_FIFO_WTRMRK_MSK, + FIELD_PREP(BMI323_FIFO_WTRMRK_MSK, + data->irq_pin)); + if (ret) + return ret; + + ret = bmi323_update_watermark(data); + if (ret) + return ret; + + ret = regmap_write(data->regmap, BMI323_FIFO_CTRL_REG, + BMI323_FIFO_FLUSH_MSK); + if (ret) + return ret; + + data->state = BMI323_BUFFER_FIFO; + + return 0; +} + +static int bmi323_buffer_preenable(struct iio_dev *indio_dev) +{ + struct bmi323_data *data = iio_priv(indio_dev); + + guard(mutex)(&data->mutex); + /* + * When the ODR of the accelerometer and gyroscope do not match, the + * maximum ODR value between the accelerometer and gyroscope is used + * for FIFO and the signal with lower ODR will insert dummy frame. + * So allow buffer read only when ODR's of accelero and gyro are equal. + * See datasheet section 5.7 "FIFO Data Buffering". + */ + if (data->odrns[BMI323_ACCEL] != data->odrns[BMI323_GYRO]) { + dev_err(data->dev, "Accelero and Gyro ODR doesn't match\n"); + return -EINVAL; + } + + return 0; +} + +static int bmi323_buffer_postenable(struct iio_dev *indio_dev) +{ + struct bmi323_data *data = iio_priv(indio_dev); + + if (iio_device_get_current_mode(indio_dev) == INDIO_BUFFER_TRIGGERED) + return 0; + + return bmi323_fifo_enable(data); +} + +static ssize_t hwfifo_watermark_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct bmi323_data *data = iio_priv(indio_dev); + int wm; + + scoped_guard(mutex, &data->mutex) + wm = data->watermark; + + return sysfs_emit(buf, "%d\n", wm); +} +static IIO_DEVICE_ATTR_RO(hwfifo_watermark, 0); + +static ssize_t hwfifo_enabled_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct bmi323_data *data = iio_priv(indio_dev); + bool state; + + scoped_guard(mutex, &data->mutex) + state = data->state == BMI323_BUFFER_FIFO; + + return sysfs_emit(buf, "%d\n", state); +} +static IIO_DEVICE_ATTR_RO(hwfifo_enabled, 0); + +static const struct iio_dev_attr *bmi323_fifo_attributes[] = { + &iio_dev_attr_hwfifo_watermark, + &iio_dev_attr_hwfifo_enabled, + NULL +}; + +static const struct iio_buffer_setup_ops bmi323_buffer_ops = { + .preenable = bmi323_buffer_preenable, + .postenable = bmi323_buffer_postenable, + .predisable = bmi323_buffer_predisable, +}; + +static irqreturn_t bmi323_irq_thread_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct bmi323_data *data = iio_priv(indio_dev); + unsigned int status_addr, status, feature_event; + s64 timestamp = iio_get_time_ns(indio_dev); + int ret; + + if (data->irq_pin == BMI323_IRQ_INT1) + status_addr = BMI323_STATUS_INT1_REG; + else + status_addr = BMI323_STATUS_INT2_REG; + + scoped_guard(mutex, &data->mutex) { + ret = regmap_read(data->regmap, status_addr, &status); + if (ret) + return IRQ_NONE; + } + + if (!status || FIELD_GET(BMI323_STATUS_ERROR_MSK, status)) + return IRQ_NONE; + + if (FIELD_GET(BMI323_STATUS_FIFO_WTRMRK_MSK, status)) { + data->old_fifo_tstamp = data->fifo_tstamp; + data->fifo_tstamp = iio_get_time_ns(indio_dev); + ret = __bmi323_fifo_flush(indio_dev); + if (ret < 0) + return IRQ_NONE; + } + + if (FIELD_GET(BMI323_STATUS_ACC_GYR_DRDY_MSK, status)) + iio_trigger_poll_nested(data->trig); + + if (FIELD_GET(BMI323_STATUS_MOTION_MSK, status)) + iio_push_event(indio_dev, IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, + IIO_MOD_X_OR_Y_OR_Z, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_RISING), + timestamp); + + if (FIELD_GET(BMI323_STATUS_NOMOTION_MSK, status)) + iio_push_event(indio_dev, IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, + IIO_MOD_X_OR_Y_OR_Z, + IIO_EV_TYPE_MAG, + IIO_EV_DIR_FALLING), + timestamp); + + if (FIELD_GET(BMI323_STATUS_STP_WTR_MSK, status)) + iio_push_event(indio_dev, IIO_MOD_EVENT_CODE(IIO_STEPS, 0, + IIO_NO_MOD, + IIO_EV_TYPE_CHANGE, + IIO_EV_DIR_NONE), + timestamp); + + if (FIELD_GET(BMI323_STATUS_TAP_MSK, status)) { + scoped_guard(mutex, &data->mutex) { + ret = regmap_read(data->regmap, + BMI323_FEAT_EVNT_EXT_REG, + &feature_event); + if (ret) + return IRQ_NONE; + } + + if (FIELD_GET(BMI323_FEAT_EVNT_EXT_S_MSK, feature_event)) { + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, + IIO_MOD_X_OR_Y_OR_Z, + IIO_EV_TYPE_GESTURE, + IIO_EV_DIR_SINGLETAP), + timestamp); + } + + if (FIELD_GET(BMI323_FEAT_EVNT_EXT_D_MSK, feature_event)) + iio_push_event(indio_dev, + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, + IIO_MOD_X_OR_Y_OR_Z, + IIO_EV_TYPE_GESTURE, + IIO_EV_DIR_DOUBLETAP), + timestamp); + } + + return IRQ_HANDLED; +} + +static int bmi323_set_drdy_irq(struct bmi323_data *data, + enum bmi323_irq_pin irq_pin) +{ + int ret; + + ret = regmap_update_bits(data->regmap, BMI323_INT_MAP2_REG, + BMI323_GYR_DRDY_MSK, + FIELD_PREP(BMI323_GYR_DRDY_MSK, irq_pin)); + if (ret) + return ret; + + return regmap_update_bits(data->regmap, BMI323_INT_MAP2_REG, + BMI323_ACC_DRDY_MSK, + FIELD_PREP(BMI323_ACC_DRDY_MSK, irq_pin)); +} + +static int bmi323_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct bmi323_data *data = iio_trigger_get_drvdata(trig); + enum bmi323_irq_pin irq_pin; + + guard(mutex)(&data->mutex); + + if (data->state == BMI323_BUFFER_FIFO) { + dev_warn(data->dev, "Can't set trigger when FIFO enabled\n"); + return -EBUSY; + } + + if (state) { + data->state = BMI323_BUFFER_DRDY_TRIGGERED; + irq_pin = data->irq_pin; + } else { + data->state = BMI323_IDLE; + irq_pin = BMI323_IRQ_DISABLED; + } + + return bmi323_set_drdy_irq(data, irq_pin); +} + +static const struct iio_trigger_ops bmi323_trigger_ops = { + .set_trigger_state = &bmi323_data_rdy_trigger_set_state, +}; + +static irqreturn_t bmi323_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct bmi323_data *data = iio_priv(indio_dev); + int ret, bit, index = 0; + + /* Lock to protect the data->buffer */ + guard(mutex)(&data->mutex); + + if (*indio_dev->active_scan_mask == BMI323_ALL_CHAN_MSK) { + ret = regmap_bulk_read(data->regmap, BMI323_ACCEL_X_REG, + &data->buffer.channels, + ARRAY_SIZE(data->buffer.channels)); + if (ret) + return IRQ_NONE; + } else { + for_each_set_bit(bit, indio_dev->active_scan_mask, + BMI323_CHAN_MAX) { + ret = regmap_raw_read(data->regmap, + BMI323_ACCEL_X_REG + bit, + &data->buffer.channels[index++], + BMI323_BYTES_PER_SAMPLE); + if (ret) + return IRQ_NONE; + } + } + + iio_push_to_buffers_with_timestamp(indio_dev, &data->buffer, + iio_get_time_ns(indio_dev)); + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int bmi323_set_average(struct bmi323_data *data, + enum bmi323_sensor_type sensor, int avg) +{ + int raw = ARRAY_SIZE(bmi323_accel_gyro_avrg); + + while (raw--) + if (avg == bmi323_accel_gyro_avrg[raw]) + break; + if (raw < 0) + return -EINVAL; + + guard(mutex)(&data->mutex); + return regmap_update_bits(data->regmap, bmi323_hw[sensor].config, + BMI323_ACC_GYRO_CONF_AVG_MSK, + FIELD_PREP(BMI323_ACC_GYRO_CONF_AVG_MSK, + raw)); +} + +static int bmi323_get_average(struct bmi323_data *data, + enum bmi323_sensor_type sensor, int *avg) +{ + int ret, value, raw; + + scoped_guard(mutex, &data->mutex) { + ret = regmap_read(data->regmap, bmi323_hw[sensor].config, &value); + if (ret) + return ret; + } + + raw = FIELD_GET(BMI323_ACC_GYRO_CONF_AVG_MSK, value); + *avg = bmi323_accel_gyro_avrg[raw]; + + return IIO_VAL_INT; +} + +static int bmi323_enable_steps(struct bmi323_data *data, int val) +{ + int ret; + + guard(mutex)(&data->mutex); + if (data->odrhz[BMI323_ACCEL] < 200) { + dev_err(data->dev, "Invalid accelrometer parameter\n"); + return -EINVAL; + } + + ret = bmi323_feature_engine_events(data, BMI323_FEAT_IO0_STP_CNT_MSK, + val ? 1 : 0); + if (ret) + return ret; + + set_mask_bits(&data->feature_events, BMI323_FEAT_IO0_STP_CNT_MSK, + FIELD_PREP(BMI323_FEAT_IO0_STP_CNT_MSK, val ? 1 : 0)); + + return 0; +} + +static int bmi323_read_steps(struct bmi323_data *data, int *val) +{ + int ret; + + guard(mutex)(&data->mutex); + if (!FIELD_GET(BMI323_FEAT_IO0_STP_CNT_MSK, data->feature_events)) + return -EINVAL; + + ret = regmap_bulk_read(data->regmap, BMI323_FEAT_IO2_REG, + data->steps_count, + ARRAY_SIZE(data->steps_count)); + if (ret) + return ret; + + *val = get_unaligned_le32(data->steps_count); + + return IIO_VAL_INT; +} + +static int bmi323_read_axis(struct bmi323_data *data, + struct iio_chan_spec const *chan, int *val) +{ + enum bmi323_sensor_type sensor; + unsigned int value; + u8 addr; + int ret; + + ret = bmi323_get_error_status(data); + if (ret) + return -EINVAL; + + sensor = bmi323_iio_to_sensor(chan->type); + addr = bmi323_hw[sensor].data + (chan->channel2 - IIO_MOD_X); + + scoped_guard(mutex, &data->mutex) { + ret = regmap_read(data->regmap, addr, &value); + if (ret) + return ret; + } + + *val = sign_extend32(value, chan->scan_type.realbits - 1); + + return IIO_VAL_INT; +} + +static int bmi323_get_temp_data(struct bmi323_data *data, int *val) +{ + unsigned int value; + int ret; + + ret = bmi323_get_error_status(data); + if (ret) + return -EINVAL; + + scoped_guard(mutex, &data->mutex) { + ret = regmap_read(data->regmap, BMI323_TEMP_REG, &value); + if (ret) + return ret; + } + + *val = sign_extend32(value, 15); + + return IIO_VAL_INT; +} + +static int bmi323_get_odr(struct bmi323_data *data, + enum bmi323_sensor_type sensor, int *odr, int *uodr) +{ + int ret, value, odr_raw; + + scoped_guard(mutex, &data->mutex) { + ret = regmap_read(data->regmap, bmi323_hw[sensor].config, &value); + if (ret) + return ret; + } + + odr_raw = FIELD_GET(BMI323_ACC_GYRO_CONF_ODR_MSK, value); + *odr = bmi323_acc_gyro_odr[odr_raw - 1][0]; + *uodr = bmi323_acc_gyro_odr[odr_raw - 1][1]; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int bmi323_configure_power_mode(struct bmi323_data *data, + enum bmi323_sensor_type sensor, + int odr_index) +{ + enum bmi323_opr_mode mode; + + if (bmi323_acc_gyro_odr[odr_index][0] > 25) + mode = ACC_GYRO_MODE_CONTINOUS; + else + mode = ACC_GYRO_MODE_DUTYCYCLE; + + return bmi323_set_mode(data, sensor, mode); +} + +static int bmi323_set_odr(struct bmi323_data *data, + enum bmi323_sensor_type sensor, int odr, int uodr) +{ + int odr_raw, ret; + + odr_raw = ARRAY_SIZE(bmi323_acc_gyro_odr); + + while (odr_raw--) + if (odr == bmi323_acc_gyro_odr[odr_raw][0] && + uodr == bmi323_acc_gyro_odr[odr_raw][1]) + break; + if (odr_raw < 0) + return -EINVAL; + + ret = bmi323_configure_power_mode(data, sensor, odr_raw); + if (ret) + return -EINVAL; + + guard(mutex)(&data->mutex); + data->odrhz[sensor] = bmi323_acc_gyro_odr[odr_raw][0]; + data->odrns[sensor] = bmi323_acc_gyro_odrns[odr_raw]; + + odr_raw++; + + return regmap_update_bits(data->regmap, bmi323_hw[sensor].config, + BMI323_ACC_GYRO_CONF_ODR_MSK, + FIELD_PREP(BMI323_ACC_GYRO_CONF_ODR_MSK, + odr_raw)); +} + +static int bmi323_get_scale(struct bmi323_data *data, + enum bmi323_sensor_type sensor, int *val2) +{ + int ret, value, scale_raw; + + scoped_guard(mutex, &data->mutex) { + ret = regmap_read(data->regmap, bmi323_hw[sensor].config, + &value); + if (ret) + return ret; + } + + scale_raw = FIELD_GET(BMI323_ACC_GYRO_CONF_SCL_MSK, value); + *val2 = bmi323_hw[sensor].scale_table[scale_raw][1]; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int bmi323_set_scale(struct bmi323_data *data, + enum bmi323_sensor_type sensor, int val, int val2) +{ + int scale_raw; + + scale_raw = bmi323_hw[sensor].scale_table_len; + + while (scale_raw--) + if (val == bmi323_hw[sensor].scale_table[scale_raw][0] && + val2 == bmi323_hw[sensor].scale_table[scale_raw][1]) + break; + if (scale_raw < 0) + return -EINVAL; + + guard(mutex)(&data->mutex); + return regmap_update_bits(data->regmap, bmi323_hw[sensor].config, + BMI323_ACC_GYRO_CONF_SCL_MSK, + FIELD_PREP(BMI323_ACC_GYRO_CONF_SCL_MSK, + scale_raw)); +} + +static int bmi323_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + enum bmi323_sensor_type sensor; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *type = IIO_VAL_INT_PLUS_MICRO; + *vals = (const int *)bmi323_acc_gyro_odr; + *length = ARRAY_SIZE(bmi323_acc_gyro_odr) * 2; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SCALE: + sensor = bmi323_iio_to_sensor(chan->type); + *type = IIO_VAL_INT_PLUS_MICRO; + *vals = (const int *)bmi323_hw[sensor].scale_table; + *length = bmi323_hw[sensor].scale_table_len * 2; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *type = IIO_VAL_INT; + *vals = (const int *)bmi323_accel_gyro_avrg; + *length = ARRAY_SIZE(bmi323_accel_gyro_avrg); + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int bmi323_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct bmi323_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; + + ret = bmi323_set_odr(data, bmi323_iio_to_sensor(chan->type), + val, val2); + iio_device_release_direct_mode(indio_dev); + return ret; + case IIO_CHAN_INFO_SCALE: + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; + + ret = bmi323_set_scale(data, bmi323_iio_to_sensor(chan->type), + val, val2); + iio_device_release_direct_mode(indio_dev); + return ret; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; + + ret = bmi323_set_average(data, bmi323_iio_to_sensor(chan->type), + val); + + iio_device_release_direct_mode(indio_dev); + return ret; + case IIO_CHAN_INFO_ENABLE: + return bmi323_enable_steps(data, val); + case IIO_CHAN_INFO_PROCESSED: + scoped_guard(mutex, &data->mutex) { + if (val || !FIELD_GET(BMI323_FEAT_IO0_STP_CNT_MSK, + data->feature_events)) + return -EINVAL; + + /* Clear step counter value */ + ret = bmi323_update_ext_reg(data, BMI323_STEP_SC1_REG, + BMI323_STEP_SC1_RST_CNT_MSK, + FIELD_PREP(BMI323_STEP_SC1_RST_CNT_MSK, + 1)); + } + return ret; + default: + return -EINVAL; + } +} + +static int bmi323_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct bmi323_data *data = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + return bmi323_read_steps(data, val); + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_ACCEL: + case IIO_ANGL_VEL: + ret = iio_device_claim_direct_mode(indio_dev); + if (ret) + return ret; + + ret = bmi323_read_axis(data, chan, val); + + iio_device_release_direct_mode(indio_dev); + return ret; + case IIO_TEMP: + return bmi323_get_temp_data(data, val); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + return bmi323_get_odr(data, bmi323_iio_to_sensor(chan->type), + val, val2); + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_ACCEL: + case IIO_ANGL_VEL: + *val = 0; + return bmi323_get_scale(data, + bmi323_iio_to_sensor(chan->type), + val2); + case IIO_TEMP: + *val = BMI323_TEMP_SCALE / MEGA; + *val2 = BMI323_TEMP_SCALE % MEGA; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + return bmi323_get_average(data, + bmi323_iio_to_sensor(chan->type), + val); + case IIO_CHAN_INFO_OFFSET: + switch (chan->type) { + case IIO_TEMP: + *val = BMI323_TEMP_OFFSET; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_ENABLE: + scoped_guard(mutex, &data->mutex) + *val = FIELD_GET(BMI323_FEAT_IO0_STP_CNT_MSK, + data->feature_events); + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static const struct iio_info bmi323_info = { + .read_raw = bmi323_read_raw, + .write_raw = bmi323_write_raw, + .read_avail = bmi323_read_avail, + .hwfifo_set_watermark = bmi323_set_watermark, + .write_event_config = bmi323_write_event_config, + .read_event_config = bmi323_read_event_config, + .write_event_value = bmi323_write_event_value, + .read_event_value = bmi323_read_event_value, + .event_attrs = &bmi323_event_attribute_group, +}; + +#define BMI323_SCAN_MASK_ACCEL_3AXIS \ + (BIT(BMI323_ACCEL_X) | BIT(BMI323_ACCEL_Y) | BIT(BMI323_ACCEL_Z)) + +#define BMI323_SCAN_MASK_GYRO_3AXIS \ + (BIT(BMI323_GYRO_X) | BIT(BMI323_GYRO_Y) | BIT(BMI323_GYRO_Z)) + +static const unsigned long bmi323_avail_scan_masks[] = { + /* 3-axis accel */ + BMI323_SCAN_MASK_ACCEL_3AXIS, + /* 3-axis gyro */ + BMI323_SCAN_MASK_GYRO_3AXIS, + /* 3-axis accel + 3-axis gyro */ + BMI323_SCAN_MASK_ACCEL_3AXIS | BMI323_SCAN_MASK_GYRO_3AXIS, + 0 +}; + +static int bmi323_int_pin_config(struct bmi323_data *data, + enum bmi323_irq_pin irq_pin, + bool active_high, bool open_drain, bool latch) +{ + unsigned int mask, field_value; + int ret; + + ret = regmap_update_bits(data->regmap, BMI323_IO_INT_CONF_REG, + BMI323_IO_INT_LTCH_MSK, + FIELD_PREP(BMI323_IO_INT_LTCH_MSK, latch)); + if (ret) + return ret; + + ret = bmi323_update_ext_reg(data, BMI323_GEN_SET1_REG, + BMI323_GEN_HOLD_DUR_MSK, + FIELD_PREP(BMI323_GEN_HOLD_DUR_MSK, 0)); + if (ret) + return ret; + + switch (irq_pin) { + case BMI323_IRQ_INT1: + mask = BMI323_IO_INT1_LVL_OD_OP_MSK; + + field_value = FIELD_PREP(BMI323_IO_INT1_LVL_MSK, active_high) | + FIELD_PREP(BMI323_IO_INT1_OD_MSK, open_drain) | + FIELD_PREP(BMI323_IO_INT1_OP_EN_MSK, 1); + break; + case BMI323_IRQ_INT2: + mask = BMI323_IO_INT2_LVL_OD_OP_MSK; + + field_value = FIELD_PREP(BMI323_IO_INT2_LVL_MSK, active_high) | + FIELD_PREP(BMI323_IO_INT2_OD_MSK, open_drain) | + FIELD_PREP(BMI323_IO_INT2_OP_EN_MSK, 1); + break; + default: + return -EINVAL; + } + + return regmap_update_bits(data->regmap, BMI323_IO_INT_CTR_REG, mask, + field_value); +} + +static int bmi323_trigger_probe(struct bmi323_data *data, + struct iio_dev *indio_dev) +{ + bool open_drain, active_high, latch; + struct fwnode_handle *fwnode; + enum bmi323_irq_pin irq_pin; + int ret, irq, irq_type; + struct irq_data *desc; + + fwnode = dev_fwnode(data->dev); + if (!fwnode) + return -ENODEV; + + irq = fwnode_irq_get_byname(fwnode, "INT1"); + if (irq > 0) { + irq_pin = BMI323_IRQ_INT1; + } else { + irq = fwnode_irq_get_byname(fwnode, "INT2"); + if (irq < 0) + return 0; + + irq_pin = BMI323_IRQ_INT2; + } + + desc = irq_get_irq_data(irq); + if (!desc) + return dev_err_probe(data->dev, -EINVAL, + "Could not find IRQ %d\n", irq); + + irq_type = irqd_get_trigger_type(desc); + switch (irq_type) { + case IRQF_TRIGGER_RISING: + latch = false; + active_high = true; + break; + case IRQF_TRIGGER_HIGH: + latch = true; + active_high = true; + break; + case IRQF_TRIGGER_FALLING: + latch = false; + active_high = false; + break; + case IRQF_TRIGGER_LOW: + latch = true; + active_high = false; + break; + default: + return dev_err_probe(data->dev, -EINVAL, + "Invalid interrupt type 0x%x specified\n", + irq_type); + } + + open_drain = fwnode_property_read_bool(fwnode, "drive-open-drain"); + + ret = bmi323_int_pin_config(data, irq_pin, active_high, open_drain, + latch); + if (ret) + return dev_err_probe(data->dev, ret, + "Failed to configure irq line\n"); + + data->trig = devm_iio_trigger_alloc(data->dev, "%s-trig-%d", + indio_dev->name, irq_pin); + if (!data->trig) + return -ENOMEM; + + data->trig->ops = &bmi323_trigger_ops; + iio_trigger_set_drvdata(data->trig, data); + + ret = devm_request_threaded_irq(data->dev, irq, NULL, + bmi323_irq_thread_handler, + IRQF_ONESHOT, "bmi323-int", indio_dev); + if (ret) + return dev_err_probe(data->dev, ret, "Failed to request IRQ\n"); + + ret = devm_iio_trigger_register(data->dev, data->trig); + if (ret) + return dev_err_probe(data->dev, ret, + "Trigger registration failed\n"); + + data->irq_pin = irq_pin; + + return 0; +} + +static int bmi323_feature_engine_enable(struct bmi323_data *data, bool en) +{ + unsigned int feature_status; + int ret; + + if (!en) + return regmap_write(data->regmap, BMI323_FEAT_CTRL_REG, 0); + + ret = regmap_write(data->regmap, BMI323_FEAT_IO2_REG, 0x012c); + if (ret) + return ret; + + ret = regmap_write(data->regmap, BMI323_FEAT_IO_STATUS_REG, + BMI323_FEAT_IO_STATUS_MSK); + if (ret) + return ret; + + ret = regmap_write(data->regmap, BMI323_FEAT_CTRL_REG, + BMI323_FEAT_ENG_EN_MSK); + if (ret) + return ret; + + /* + * It takes around 4 msec to enable the Feature engine, so check + * the status of the feature engine every 2 msec for a maximum + * of 5 trials. + */ + ret = regmap_read_poll_timeout(data->regmap, BMI323_FEAT_IO1_REG, + feature_status, + FIELD_GET(BMI323_FEAT_IO1_ERR_MSK, + feature_status) == 1, + BMI323_FEAT_ENG_POLL, + BMI323_FEAT_ENG_TIMEOUT); + if (ret) + return dev_err_probe(data->dev, -EINVAL, + "Failed to enable feature engine\n"); + + return 0; +} + +static void bmi323_disable(void *data_ptr) +{ + struct bmi323_data *data = data_ptr; + + bmi323_set_mode(data, BMI323_ACCEL, ACC_GYRO_MODE_DISABLE); + bmi323_set_mode(data, BMI323_GYRO, ACC_GYRO_MODE_DISABLE); +} + +static int bmi323_set_bw(struct bmi323_data *data, + enum bmi323_sensor_type sensor, enum bmi323_3db_bw bw) +{ + return regmap_update_bits(data->regmap, bmi323_hw[sensor].config, + BMI323_ACC_GYRO_CONF_BW_MSK, + FIELD_PREP(BMI323_ACC_GYRO_CONF_BW_MSK, bw)); +} + +static int bmi323_init(struct bmi323_data *data) +{ + int ret, val; + + /* + * Perform soft reset to make sure the device is in a known state after + * start up. A delay of 1.5 ms is required after reset. + * See datasheet section 5.17 "Soft Reset". + */ + ret = regmap_write(data->regmap, BMI323_CMD_REG, BMI323_RST_VAL); + if (ret) + return ret; + + usleep_range(1500, 2000); + + /* + * Dummy read is required to enable SPI interface after reset. + * See datasheet section 7.2.1 "Protocol Selection". + */ + regmap_read(data->regmap, BMI323_CHIP_ID_REG, &val); + + ret = regmap_read(data->regmap, BMI323_STATUS_REG, &val); + if (ret) + return ret; + + if (!FIELD_GET(BMI323_STATUS_POR_MSK, val)) + return dev_err_probe(data->dev, -EINVAL, + "Sensor initialization error\n"); + + ret = regmap_read(data->regmap, BMI323_CHIP_ID_REG, &val); + if (ret) + return ret; + + if (FIELD_GET(BMI323_CHIP_ID_MSK, val) != BMI323_CHIP_ID_VAL) + return dev_err_probe(data->dev, -EINVAL, "Chip ID mismatch\n"); + + ret = bmi323_feature_engine_enable(data, true); + if (ret) + return ret; + + ret = regmap_read(data->regmap, BMI323_ERR_REG, &val); + if (ret) + return ret; + + if (val) + return dev_err_probe(data->dev, -EINVAL, + "Sensor power error = 0x%x\n", val); + + /* + * Set the Bandwidth coefficient which defines the 3 dB cutoff + * frequency in relation to the ODR. + */ + ret = bmi323_set_bw(data, BMI323_ACCEL, BMI323_BW_ODR_BY_2); + if (ret) + return ret; + + ret = bmi323_set_bw(data, BMI323_GYRO, BMI323_BW_ODR_BY_2); + if (ret) + return ret; + + ret = bmi323_set_odr(data, BMI323_ACCEL, 25, 0); + if (ret) + return ret; + + ret = bmi323_set_odr(data, BMI323_GYRO, 25, 0); + if (ret) + return ret; + + return devm_add_action_or_reset(data->dev, bmi323_disable, data); +} + +int bmi323_core_probe(struct device *dev) +{ + static const char * const regulator_names[] = { "vdd", "vddio" }; + struct iio_dev *indio_dev; + struct bmi323_data *data; + struct regmap *regmap; + int ret; + + regmap = dev_get_regmap(dev, NULL); + if (!regmap) + return dev_err_probe(dev, -ENODEV, "Failed to get regmap\n"); + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return dev_err_probe(dev, -ENOMEM, + "Failed to allocate device\n"); + + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(regulator_names), + regulator_names); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable regulators\n"); + + data = iio_priv(indio_dev); + data->dev = dev; + data->regmap = regmap; + mutex_init(&data->mutex); + + ret = bmi323_init(data); + if (ret) + return -EINVAL; + + ret = iio_read_mount_matrix(dev, &data->orientation); + if (ret) + return ret; + + indio_dev->name = "bmi323-imu"; + indio_dev->info = &bmi323_info; + indio_dev->channels = bmi323_channels; + indio_dev->num_channels = ARRAY_SIZE(bmi323_channels); + indio_dev->available_scan_masks = bmi323_avail_scan_masks; + indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE; + dev_set_drvdata(data->dev, indio_dev); + + ret = bmi323_trigger_probe(data, indio_dev); + if (ret) + return -EINVAL; + + ret = devm_iio_triggered_buffer_setup_ext(data->dev, indio_dev, + &iio_pollfunc_store_time, + bmi323_trigger_handler, + IIO_BUFFER_DIRECTION_IN, + &bmi323_buffer_ops, + bmi323_fifo_attributes); + if (ret) + return dev_err_probe(data->dev, ret, + "Failed to setup trigger buffer\n"); + + ret = devm_iio_device_register(data->dev, indio_dev); + if (ret) + return dev_err_probe(data->dev, ret, + "Unable to register iio device\n"); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(bmi323_core_probe, IIO_BMI323); + +MODULE_DESCRIPTION("Bosch BMI323 IMU driver"); +MODULE_AUTHOR("Jagath Jog J "); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/imu/bmi323/bmi323_i2c.c b/drivers/iio/imu/bmi323/bmi323_i2c.c new file mode 100644 index 000000000000..0008e186367d --- /dev/null +++ b/drivers/iio/imu/bmi323/bmi323_i2c.c @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * I2C driver for Bosch BMI323 6-Axis IMU. + * + * Copyright (C) 2023, Jagath Jog J + */ + +#include +#include +#include +#include + +#include "bmi323.h" + +struct bmi323_i2c_priv { + struct i2c_client *i2c; + u8 i2c_rx_buffer[BMI323_FIFO_LENGTH_IN_BYTES + BMI323_I2C_DUMMY]; +}; + +/* + * From BMI323 datasheet section 4: Notes on the Serial Interface Support. + * Each I2C register read operation requires to read two dummy bytes before + * the actual payload. + */ +static int bmi323_regmap_i2c_read(void *context, const void *reg_buf, + size_t reg_size, void *val_buf, + size_t val_size) +{ + struct bmi323_i2c_priv *priv = context; + struct i2c_msg msgs[2]; + int ret; + + msgs[0].addr = priv->i2c->addr; + msgs[0].flags = priv->i2c->flags; + msgs[0].len = reg_size; + msgs[0].buf = (u8 *)reg_buf; + + msgs[1].addr = priv->i2c->addr; + msgs[1].len = val_size + BMI323_I2C_DUMMY; + msgs[1].buf = priv->i2c_rx_buffer; + msgs[1].flags = priv->i2c->flags | I2C_M_RD; + + ret = i2c_transfer(priv->i2c->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) + return -EIO; + + memcpy(val_buf, priv->i2c_rx_buffer + BMI323_I2C_DUMMY, val_size); + + return 0; +} + +static int bmi323_regmap_i2c_write(void *context, const void *data, + size_t count) +{ + struct bmi323_i2c_priv *priv = context; + u8 reg; + + reg = *(u8 *)data; + return i2c_smbus_write_i2c_block_data(priv->i2c, reg, + count - sizeof(u8), + data + sizeof(u8)); +} + +static struct regmap_bus bmi323_regmap_bus = { + .read = bmi323_regmap_i2c_read, + .write = bmi323_regmap_i2c_write, +}; + +const struct regmap_config bmi323_i2c_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .max_register = BMI323_CFG_RES_REG, + .val_format_endian = REGMAP_ENDIAN_LITTLE, +}; + +static int bmi323_i2c_probe(struct i2c_client *i2c) +{ + struct device *dev = &i2c->dev; + struct bmi323_i2c_priv *priv; + struct regmap *regmap; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->i2c = i2c; + regmap = devm_regmap_init(dev, &bmi323_regmap_bus, priv, + &bmi323_i2c_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), + "Failed to initialize I2C Regmap\n"); + + return bmi323_core_probe(dev); +} + +static const struct i2c_device_id bmi323_i2c_ids[] = { + { "bmi323" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, bmi323_i2c_ids); + +static const struct of_device_id bmi323_of_i2c_match[] = { + { .compatible = "bosch,bmi323" }, + { } +}; +MODULE_DEVICE_TABLE(of, bmi323_of_i2c_match); + +static struct i2c_driver bmi323_i2c_driver = { + .driver = { + .name = "bmi323", + .of_match_table = bmi323_of_i2c_match, + }, + .probe = bmi323_i2c_probe, + .id_table = bmi323_i2c_ids, +}; +module_i2c_driver(bmi323_i2c_driver); + +MODULE_DESCRIPTION("Bosch BMI323 IMU driver"); +MODULE_AUTHOR("Jagath Jog J "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(IIO_BMI323); diff --git a/drivers/iio/imu/bmi323/bmi323_spi.c b/drivers/iio/imu/bmi323/bmi323_spi.c new file mode 100644 index 000000000000..6dc3352dd714 --- /dev/null +++ b/drivers/iio/imu/bmi323/bmi323_spi.c @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * SPI driver for Bosch BMI323 6-Axis IMU. + * + * Copyright (C) 2023, Jagath Jog J + */ + +#include +#include +#include +#include + +#include "bmi323.h" + +/* + * From BMI323 datasheet section 4: Notes on the Serial Interface Support. + * Each SPI register read operation requires to read one dummy byte before + * the actual payload. + */ +static int bmi323_regmap_spi_read(void *context, const void *reg_buf, + size_t reg_size, void *val_buf, + size_t val_size) +{ + struct spi_device *spi = context; + + return spi_write_then_read(spi, reg_buf, reg_size, val_buf, val_size); +} + +static int bmi323_regmap_spi_write(void *context, const void *data, + size_t count) +{ + struct spi_device *spi = context; + u8 *data_buff = (u8 *)data; + + data_buff[1] = data_buff[0]; + return spi_write(spi, data_buff + 1, count - 1); +} + +static struct regmap_bus bmi323_regmap_bus = { + .read = bmi323_regmap_spi_read, + .write = bmi323_regmap_spi_write, +}; + +const struct regmap_config bmi323_spi_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .pad_bits = 8, + .read_flag_mask = BIT(7), + .max_register = BMI323_CFG_RES_REG, + .val_format_endian = REGMAP_ENDIAN_LITTLE, +}; + +static int bmi323_spi_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct regmap *regmap; + + regmap = devm_regmap_init(dev, &bmi323_regmap_bus, dev, + &bmi323_spi_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), + "Failed to initialize SPI Regmap\n"); + + return bmi323_core_probe(dev); +} + +static const struct spi_device_id bmi323_spi_ids[] = { + { "bmi323" }, + { } +}; +MODULE_DEVICE_TABLE(spi, bmi323_spi_ids); + +static const struct of_device_id bmi323_of_spi_match[] = { + { .compatible = "bosch,bmi323" }, + { } +}; +MODULE_DEVICE_TABLE(of, bmi323_of_spi_match); + +static struct spi_driver bmi323_spi_driver = { + .driver = { + .name = "bmi323", + .of_match_table = bmi323_of_spi_match, + }, + .probe = bmi323_spi_probe, + .id_table = bmi323_spi_ids, +}; +module_spi_driver(bmi323_spi_driver); + +MODULE_DESCRIPTION("Bosch BMI323 IMU driver"); +MODULE_AUTHOR("Jagath Jog J "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(IIO_BMI323); -- cgit v1.2.3 From b89710bd215e650f0aaf8ffe7104413d46d44392 Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Mon, 27 Nov 2023 18:34:28 +0100 Subject: iio: add modifiers for A and B ultraviolet light Currently there are only two modifiers for ultraviolet light: a generic one for any ultraviolet light (IIO_MOD_LIGHT_UV) and one for deep ultraviolet (IIO_MOD_LIGHT_DUV), which is also referred as ultraviolet C (UV-C) band and covers short-wave ultraviolet. There are still no modifiers for the long-wave and medium-wave ultraviolet bands. These two bands are the main components used to obtain the UV index on the Earth's surface. Add modifiers for the ultraviolet A (UV-A) and ultraviolet B (UV-B) bands. Signed-off-by: Javier Carrasco Link: https://lore.kernel.org/r/20231110-veml6075-v3-1-6ee46775b422@gmail.com Signed-off-by: Jonathan Cameron --- Documentation/ABI/testing/sysfs-bus-iio | 7 +++++-- drivers/iio/industrialio-core.c | 2 ++ include/uapi/linux/iio/types.h | 2 ++ tools/iio/iio_event_monitor.c | 2 ++ 4 files changed, 11 insertions(+), 2 deletions(-) (limited to 'Documentation/ABI') diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio index 0eadc08c3a13..0d3ec5fc45f2 100644 --- a/Documentation/ABI/testing/sysfs-bus-iio +++ b/Documentation/ABI/testing/sysfs-bus-iio @@ -1574,6 +1574,8 @@ What: /sys/.../iio:deviceX/in_intensityY_raw What: /sys/.../iio:deviceX/in_intensityY_ir_raw What: /sys/.../iio:deviceX/in_intensityY_both_raw What: /sys/.../iio:deviceX/in_intensityY_uv_raw +What: /sys/.../iio:deviceX/in_intensityY_uva_raw +What: /sys/.../iio:deviceX/in_intensityY_uvb_raw What: /sys/.../iio:deviceX/in_intensityY_duv_raw KernelVersion: 3.4 Contact: linux-iio@vger.kernel.org @@ -1582,8 +1584,9 @@ Description: that measurements contain visible and infrared light components or just infrared light, respectively. Modifier uv indicates that measurements contain ultraviolet light - components. Modifier duv indicates that measurements - contain deep ultraviolet light components. + components. Modifiers uva, uvb and duv indicate that + measurements contain A, B or deep (C) ultraviolet light + components respectively. What: /sys/.../iio:deviceX/in_uvindex_input KernelVersion: 4.6 diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index 34e1f8d0071c..f6a123d397db 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -117,6 +117,8 @@ static const char * const iio_modifier_names[] = { [IIO_MOD_LIGHT_GREEN] = "green", [IIO_MOD_LIGHT_BLUE] = "blue", [IIO_MOD_LIGHT_UV] = "uv", + [IIO_MOD_LIGHT_UVA] = "uva", + [IIO_MOD_LIGHT_UVB] = "uvb", [IIO_MOD_LIGHT_DUV] = "duv", [IIO_MOD_QUATERNION] = "quaternion", [IIO_MOD_TEMP_AMBIENT] = "ambient", diff --git a/include/uapi/linux/iio/types.h b/include/uapi/linux/iio/types.h index 9c2ffdcd6623..5060963707b1 100644 --- a/include/uapi/linux/iio/types.h +++ b/include/uapi/linux/iio/types.h @@ -91,6 +91,8 @@ enum iio_modifier { IIO_MOD_CO2, IIO_MOD_VOC, IIO_MOD_LIGHT_UV, + IIO_MOD_LIGHT_UVA, + IIO_MOD_LIGHT_UVB, IIO_MOD_LIGHT_DUV, IIO_MOD_PM1, IIO_MOD_PM2P5, diff --git a/tools/iio/iio_event_monitor.c b/tools/iio/iio_event_monitor.c index 2eaaa7123b04..8073c9e4fe46 100644 --- a/tools/iio/iio_event_monitor.c +++ b/tools/iio/iio_event_monitor.c @@ -105,6 +105,8 @@ static const char * const iio_modifier_names[] = { [IIO_MOD_LIGHT_GREEN] = "green", [IIO_MOD_LIGHT_BLUE] = "blue", [IIO_MOD_LIGHT_UV] = "uv", + [IIO_MOD_LIGHT_UVA] = "uva", + [IIO_MOD_LIGHT_UVB] = "uvb", [IIO_MOD_LIGHT_DUV] = "duv", [IIO_MOD_QUATERNION] = "quaternion", [IIO_MOD_TEMP_AMBIENT] = "ambient", -- cgit v1.2.3 From 608531bd8615766fda6f423c746d89ac9db5c0d0 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Thu, 7 Dec 2023 14:41:50 +0100 Subject: doc: iio: Document intensity scale as poorly defined Add comment about intensity scale being poorly defined and having no proper units. Signed-off-by: Marek Vasut Link: https://lore.kernel.org/r/20231207134200.329174-1-marex@denx.de Signed-off-by: Jonathan Cameron --- Documentation/ABI/testing/sysfs-bus-iio | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'Documentation/ABI') diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio index 0d3ec5fc45f2..94b8d8461b7c 100644 --- a/Documentation/ABI/testing/sysfs-bus-iio +++ b/Documentation/ABI/testing/sysfs-bus-iio @@ -618,7 +618,9 @@ KernelVersion: 2.6.35 Contact: linux-iio@vger.kernel.org Description: If a discrete set of scale values is available, they - are listed in this attribute. + are listed in this attribute. Unlike illumination, + multiplying intensity by intensity_scale does not + yield value with any standardized unit. What: /sys/bus/iio/devices/iio:deviceX/out_voltageY_hardwaregain What: /sys/bus/iio/devices/iio:deviceX/in_intensity_hardwaregain -- cgit v1.2.3 From a4887e9782959e3e8f756412b53808157803de60 Mon Sep 17 00:00:00 2001 From: Javier Carrasco Date: Mon, 11 Dec 2023 20:28:40 +0800 Subject: iio: ABI: document temperature and humidity peak/trough raw attributes The in_temp_peak_raw attribute is already in use, but its documentation is still missing. The in_humidityrelative_raw must be documented for a new iio user that supports this attribute. Add temp and humidityrelative use cases. When at it, remove an extra blank space in the description. For users that support minimum values, a new in__trough_raw attribute is required. Add this attribute and document the first uses of it for temp and humidityrelative types. Signed-off-by: Javier Carrasco Link: https://lore.kernel.org/r/20231211122840.9760-1-579lpy@gmail.com Signed-off-by: Jonathan Cameron --- Documentation/ABI/testing/sysfs-bus-iio | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'Documentation/ABI') diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio index 94b8d8461b7c..2e6d5ebfd3c7 100644 --- a/Documentation/ABI/testing/sysfs-bus-iio +++ b/Documentation/ABI/testing/sysfs-bus-iio @@ -362,10 +362,21 @@ Description: What: /sys/bus/iio/devices/iio:deviceX/in_accel_x_peak_raw What: /sys/bus/iio/devices/iio:deviceX/in_accel_y_peak_raw What: /sys/bus/iio/devices/iio:deviceX/in_accel_z_peak_raw +What: /sys/bus/iio/devices/iio:deviceX/in_humidityrelative_peak_raw +What: /sys/bus/iio/devices/iio:deviceX/in_temp_peak_raw KernelVersion: 2.6.36 Contact: linux-iio@vger.kernel.org Description: - Highest value since some reset condition. These + Highest value since some reset condition. These + attributes allow access to this and are otherwise + the direct equivalent of the Y[_name]_raw attributes. + +What: /sys/bus/iio/devices/iio:deviceX/in_humidityrelative_trough_raw +What: /sys/bus/iio/devices/iio:deviceX/in_temp_trough_raw +KernelVersion: 6.7 +Contact: linux-iio@vger.kernel.org +Description: + Lowest value since some reset condition. These attributes allow access to this and are otherwise the direct equivalent of the Y[_name]_raw attributes. -- cgit v1.2.3 From 192048e5a5b6660079ba4fce679e82adc05cfece Mon Sep 17 00:00:00 2001 From: Miquel Raynal Date: Fri, 15 Dec 2023 11:15:33 +0000 Subject: ABI: sysfs-nvmem-cells: Expose cells through sysfs The binary content of nvmem devices is available to the user so in the easiest cases, finding the content of a cell is rather easy as it is just a matter of looking at a known and fixed offset. However, nvmem layouts have been recently introduced to cope with more advanced situations, where the offset and size of the cells is not known in advance or is dynamic. When using layouts, more advanced parsers are used by the kernel in order to give direct access to the content of each cell regardless of their position/size in the underlying device, but these information were not accessible to the user. By exposing the nvmem cells to the user through a dedicated cell/ folder containing one file per cell, we provide a straightforward access to useful user information without the need for re-writing a userland parser. Content of nvmem cells is usually: product names, manufacturing date, MAC addresses, etc, Signed-off-by: Miquel Raynal Reviewed-by: Greg Kroah-Hartman Signed-off-by: Srinivas Kandagatla Link: https://lore.kernel.org/r/20231215111536.316972-8-srinivas.kandagatla@linaro.org Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-nvmem-cells | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-nvmem-cells (limited to 'Documentation/ABI') diff --git a/Documentation/ABI/testing/sysfs-nvmem-cells b/Documentation/ABI/testing/sysfs-nvmem-cells new file mode 100644 index 000000000000..7af70adf3690 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-nvmem-cells @@ -0,0 +1,21 @@ +What: /sys/bus/nvmem/devices/.../cells/ +Date: May 2023 +KernelVersion: 6.5 +Contact: Miquel Raynal +Description: + The "cells" folder contains one file per cell exposed by the + NVMEM device. The name of the file is: @, with + being the cell name and its location in the NVMEM + device, in hexadecimal (without the '0x' prefix, to mimic device + tree node names). The length of the file is the size of the cell + (when known). The content of the file is the binary content of + the cell (may sometimes be ASCII, likely without trailing + character). + Note: This file is only present if CONFIG_NVMEM_SYSFS + is enabled. + + Example:: + + hexdump -C /sys/bus/nvmem/devices/1-00563/cells/product-name@d + 00000000 54 4e 34 38 4d 2d 50 2d 44 4e |TN48M-P-DN| + 0000000a -- cgit v1.2.3 From aeda33ab8160c7a2e24ba4f44492ad1e974ddc7d Mon Sep 17 00:00:00 2001 From: Abhijit Gangurde Date: Fri, 22 Dec 2023 12:16:26 +0530 Subject: cdx: create sysfs bin files for cdx resources Resource binary file contains the content of the memory regions. These resources devices can be used to mmap the MMIO regions in the user-space. Co-developed-by: Puneet Gupta Signed-off-by: Puneet Gupta Signed-off-by: Abhijit Gangurde Link: https://lore.kernel.org/r/20231222064627.2828960-1-abhijit.gangurde@amd.com Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-bus-cdx | 7 ++ drivers/cdx/cdx.c | 118 +++++++++++++++++++++++++++++++- include/linux/cdx/cdx_bus.h | 10 +++ 3 files changed, 134 insertions(+), 1 deletion(-) (limited to 'Documentation/ABI') diff --git a/Documentation/ABI/testing/sysfs-bus-cdx b/Documentation/ABI/testing/sysfs-bus-cdx index 8c067ff99e54..e84277531414 100644 --- a/Documentation/ABI/testing/sysfs-bus-cdx +++ b/Documentation/ABI/testing/sysfs-bus-cdx @@ -98,6 +98,13 @@ Description: # echo 1 > /sys/bus/cdx/devices/.../remove +What: /sys/bus/cdx/devices/.../resource +Date: July 2023 +Contact: puneet.gupta@amd.com +Description: + The resource binary file contains the content of the memory + regions. These files can be m'maped from userspace. + What: /sys/bus/cdx/devices/.../modalias Date: July 2023 Contact: nipun.gupta@amd.com diff --git a/drivers/cdx/cdx.c b/drivers/cdx/cdx.c index d84d153078d7..2ec1846ff063 100644 --- a/drivers/cdx/cdx.c +++ b/drivers/cdx/cdx.c @@ -80,6 +80,8 @@ static DEFINE_MUTEX(cdx_controller_lock); static char *compat_node_name = "xlnx,versal-net-cdx"; +static void cdx_destroy_res_attr(struct cdx_device *cdx_dev, int num); + /** * cdx_dev_reset - Reset a CDX device * @dev: CDX device @@ -148,6 +150,7 @@ static int cdx_unregister_device(struct device *dev, if (cdx_dev->enabled && cdx->ops->bus_disable) cdx->ops->bus_disable(cdx, cdx_dev->bus_num); } else { + cdx_destroy_res_attr(cdx_dev, MAX_CDX_DEV_RESOURCES); kfree(cdx_dev->driver_override); cdx_dev->driver_override = NULL; } @@ -643,11 +646,105 @@ static void cdx_device_release(struct device *dev) kfree(cdx_dev); } +static const struct vm_operations_struct cdx_phys_vm_ops = { +#ifdef CONFIG_HAVE_IOREMAP_PROT + .access = generic_access_phys, +#endif +}; + +/** + * cdx_mmap_resource - map a CDX resource into user memory space + * @fp: File pointer. Not used in this function, but required where + * this API is registered as a callback. + * @kobj: kobject for mapping + * @attr: struct bin_attribute for the file being mapped + * @vma: struct vm_area_struct passed into the mmap + * + * Use the regular CDX mapping routines to map a CDX resource into userspace. + * + * Return: true on success, false otherwise. + */ +static int cdx_mmap_resource(struct file *fp, struct kobject *kobj, + struct bin_attribute *attr, + struct vm_area_struct *vma) +{ + struct cdx_device *cdx_dev = to_cdx_device(kobj_to_dev(kobj)); + int num = (unsigned long)attr->private; + struct resource *res; + unsigned long size; + + res = &cdx_dev->res[num]; + if (iomem_is_exclusive(res->start)) + return -EINVAL; + + /* Make sure the caller is mapping a valid resource for this device */ + size = ((cdx_resource_len(cdx_dev, num) - 1) >> PAGE_SHIFT) + 1; + if (vma->vm_pgoff + vma_pages(vma) > size) + return -EINVAL; + + /* + * Map memory region and vm->vm_pgoff is expected to be an + * offset within that region. + */ + vma->vm_page_prot = pgprot_device(vma->vm_page_prot); + vma->vm_pgoff += (cdx_resource_start(cdx_dev, num) >> PAGE_SHIFT); + vma->vm_ops = &cdx_phys_vm_ops; + return io_remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, + vma->vm_end - vma->vm_start, + vma->vm_page_prot); +} + +static void cdx_destroy_res_attr(struct cdx_device *cdx_dev, int num) +{ + int i; + + /* removing the bin attributes */ + for (i = 0; i < num; i++) { + struct bin_attribute *res_attr; + + res_attr = cdx_dev->res_attr[i]; + if (res_attr) { + sysfs_remove_bin_file(&cdx_dev->dev.kobj, res_attr); + kfree(res_attr); + } + } +} + +#define CDX_RES_ATTR_NAME_LEN 10 +static int cdx_create_res_attr(struct cdx_device *cdx_dev, int num) +{ + struct bin_attribute *res_attr; + char *res_attr_name; + int ret; + + res_attr = kzalloc(sizeof(*res_attr) + CDX_RES_ATTR_NAME_LEN, GFP_ATOMIC); + if (!res_attr) + return -ENOMEM; + + res_attr_name = (char *)(res_attr + 1); + + sysfs_bin_attr_init(res_attr); + + cdx_dev->res_attr[num] = res_attr; + sprintf(res_attr_name, "resource%d", num); + + res_attr->mmap = cdx_mmap_resource; + res_attr->attr.name = res_attr_name; + res_attr->attr.mode = 0600; + res_attr->size = cdx_resource_len(cdx_dev, num); + res_attr->private = (void *)(unsigned long)num; + ret = sysfs_create_bin_file(&cdx_dev->dev.kobj, res_attr); + if (ret) + kfree(res_attr); + + return ret; +} + int cdx_device_add(struct cdx_dev_params *dev_params) { struct cdx_controller *cdx = dev_params->cdx; struct cdx_device *cdx_dev; - int ret; + int ret, i; cdx_dev = kzalloc(sizeof(*cdx_dev), GFP_KERNEL); if (!cdx_dev) @@ -690,7 +787,26 @@ int cdx_device_add(struct cdx_dev_params *dev_params) goto fail; } + /* Create resource attributes */ + for (i = 0; i < MAX_CDX_DEV_RESOURCES; i++) { + if (cdx_resource_flags(cdx_dev, i) & IORESOURCE_MEM) { + /* skip empty resources */ + if (!cdx_resource_len(cdx_dev, i)) + continue; + + ret = cdx_create_res_attr(cdx_dev, i); + if (ret != 0) { + dev_err(&cdx_dev->dev, + "cdx device resource<%d> file creation failed: %d", i, ret); + goto resource_create_fail; + } + } + } + return 0; +resource_create_fail: + cdx_destroy_res_attr(cdx_dev, i); + device_del(&cdx_dev->dev); fail: /* * Do not free cdx_dev here as it would be freed in diff --git a/include/linux/cdx/cdx_bus.h b/include/linux/cdx/cdx_bus.h index 94ad2c9017c9..fab9e62c7e7d 100644 --- a/include/linux/cdx/cdx_bus.h +++ b/include/linux/cdx/cdx_bus.h @@ -135,6 +135,7 @@ struct cdx_device { u8 bus_num; u8 dev_num; struct resource res[MAX_CDX_DEV_RESOURCES]; + struct bin_attribute *res_attr[MAX_CDX_DEV_RESOURCES]; u8 res_count; u64 dma_mask; u16 flags; @@ -147,6 +148,15 @@ struct cdx_device { #define to_cdx_device(_dev) \ container_of(_dev, struct cdx_device, dev) +#define cdx_resource_start(dev, num) ((dev)->res[(num)].start) +#define cdx_resource_end(dev, num) ((dev)->res[(num)].end) +#define cdx_resource_flags(dev, num) ((dev)->res[(num)].flags) +#define cdx_resource_len(dev, num) \ + ((cdx_resource_start((dev), (num)) == 0 && \ + cdx_resource_end((dev), (num)) == \ + cdx_resource_start((dev), (num))) ? 0 : \ + (cdx_resource_end((dev), (num)) - \ + cdx_resource_start((dev), (num)) + 1)) /** * struct cdx_driver - CDX device driver * @driver: Generic device driver -- cgit v1.2.3