All of lore.kernel.org
 help / color / mirror / Atom feed
From: Lorenzo Bianconi <lorenzo.bianconi@redhat.com>
To: jic23@kernel.org
Cc: linux-iio@vger.kernel.org, devicetree@vger.kernel.org
Subject: [PATCH v2 6/9] iio: imu: st_lsm6dsx: add i2c embedded controller support
Date: Sun, 11 Nov 2018 15:15:33 +0100	[thread overview]
Message-ID: <c9a134df6858ee2f290c844be18f8c2cef54c559.1541945612.git.lorenzo.bianconi@redhat.com> (raw)
In-Reply-To: <cover.1541945612.git.lorenzo.bianconi@redhat.com>

i2c controller embedded in lsm6dx series can connect up to four
slave devices using accelerometer sensor as trigger for i2c
read/write operations.
Introduce sensor hub support for lsm6dso sensor. Add register map
for lis2mdl magnetometer sensor.
In order to perform single read/write operations st_lsm6dsx driver
relies on SLV0 channel (hw FIFO is not supported yet)

Signed-off-by: Lorenzo Bianconi <lorenzo.bianconi@redhat.com>
---
 drivers/iio/imu/st_lsm6dsx/Makefile           |   3 +-
 drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h       | 112 +++
 drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c  | 135 ++--
 drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c  | 702 ++++++++++++++++++
 .../linux/platform_data/st_sensors_pdata.h    |   2 +
 5 files changed, 911 insertions(+), 43 deletions(-)
 create mode 100644 drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c

diff --git a/drivers/iio/imu/st_lsm6dsx/Makefile b/drivers/iio/imu/st_lsm6dsx/Makefile
index 35919febea2a..e5f733ce6e11 100644
--- a/drivers/iio/imu/st_lsm6dsx/Makefile
+++ b/drivers/iio/imu/st_lsm6dsx/Makefile
@@ -1,4 +1,5 @@
-st_lsm6dsx-y := st_lsm6dsx_core.o st_lsm6dsx_buffer.o
+st_lsm6dsx-y := st_lsm6dsx_core.o st_lsm6dsx_buffer.o \
+		st_lsm6dsx_shub.o
 
 obj-$(CONFIG_IIO_ST_LSM6DSX) += st_lsm6dsx.o
 obj-$(CONFIG_IIO_ST_LSM6DSX_I2C) += st_lsm6dsx_i2c.o
diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h
index 2beb4f563892..d20746eb3d2d 100644
--- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h
+++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h
@@ -43,6 +43,24 @@ enum st_lsm6dsx_hw_id {
 					 * ST_LSM6DSX_TAGGED_SAMPLE_SIZE)
 #define ST_LSM6DSX_SHIFT_VAL(val, mask)	(((val) << __ffs(mask)) & (mask))
 
+#define ST_LSM6DSX_CHANNEL(chan_type, addr, mod, scan_idx)		\
+{									\
+	.type = chan_type,						\
+	.address = addr,						\
+	.modified = 1,							\
+	.channel2 = mod,						\
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |			\
+			      BIT(IIO_CHAN_INFO_SCALE),			\
+	.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),	\
+	.scan_index = scan_idx,						\
+	.scan_type = {							\
+		.sign = 's',						\
+		.realbits = 16,						\
+		.storagebits = 16,					\
+		.endianness = IIO_LE,					\
+	},								\
+}
+
 struct st_lsm6dsx_reg {
 	u8 addr;
 	u8 mask;
@@ -50,6 +68,28 @@ struct st_lsm6dsx_reg {
 
 struct st_lsm6dsx_hw;
 
+struct st_lsm6dsx_odr {
+	u16 hz;
+	u8 val;
+};
+
+#define ST_LSM6DSX_ODR_LIST_SIZE	6
+struct st_lsm6dsx_odr_table_entry {
+	struct st_lsm6dsx_reg reg;
+	struct st_lsm6dsx_odr odr_avl[ST_LSM6DSX_ODR_LIST_SIZE];
+};
+
+struct st_lsm6dsx_fs {
+	u32 gain;
+	u8 val;
+};
+
+#define ST_LSM6DSX_FS_LIST_SIZE		4
+struct st_lsm6dsx_fs_table_entry {
+	struct st_lsm6dsx_reg reg;
+	struct st_lsm6dsx_fs fs_avl[ST_LSM6DSX_FS_LIST_SIZE];
+};
+
 /**
  * struct st_lsm6dsx_fifo_ops - ST IMU FIFO settings
  * @read_fifo: Read FIFO callback.
@@ -84,6 +124,66 @@ struct st_lsm6dsx_hw_ts_settings {
 	struct st_lsm6dsx_reg decimator;
 };
 
+/**
+ * struct st_lsm6dsx_shub_settings - ST IMU hw i2c controller settings
+ * @page_mux: register page mux info (addr + mask).
+ * @master_en: master config register info (addr + mask).
+ * @pullup_en: i2c controller pull-up register info (addr + mask).
+ * @aux_sens: aux sensor register info (addr + mask).
+ * @shub_out: sensor hub first output register info.
+ * @slv0_addr: slave0 address in secondary page.
+ * @dw_slv0_addr: slave0 write register address in secondary page.
+ */
+struct st_lsm6dsx_shub_settings {
+	struct st_lsm6dsx_reg page_mux;
+	struct st_lsm6dsx_reg master_en;
+	struct st_lsm6dsx_reg pullup_en;
+	struct st_lsm6dsx_reg aux_sens;
+	u8 shub_out;
+	u8 slv0_addr;
+	u8 dw_slv0_addr;
+};
+
+enum st_lsm6dsx_ext_sensor_id {
+	ST_LSM6DSX_ID_MAGN,
+};
+
+/**
+ * struct st_lsm6dsx_ext_dev_settings - i2c controller slave settings
+ * @i2c_addr: I2c slave address list.
+ * @wai: Wai address info.
+ * @id: external sensor id.
+ * @odr: Output data rate of the sensor [Hz].
+ * @gain: Configured sensor sensitivity.
+ * @temp_comp: Temperature compensation register info (addr + mask).
+ * @pwr_table: Power on register info (addr + mask).
+ * @off_canc: Offset cancellation register info (addr + mask).
+ * @bdu: Block data update register info (addr + mask).
+ * @out: Output register info.
+ */
+struct st_lsm6dsx_ext_dev_settings {
+	u8 i2c_addr[2];
+	struct {
+		u8 addr;
+		u8 val;
+	} wai;
+	enum st_lsm6dsx_ext_sensor_id id;
+	struct st_lsm6dsx_odr_table_entry odr_table;
+	struct st_lsm6dsx_fs_table_entry fs_table;
+	struct st_lsm6dsx_reg temp_comp;
+	struct {
+		struct st_lsm6dsx_reg reg;
+		u8 off_val;
+		u8 on_val;
+	} pwr_table;
+	struct st_lsm6dsx_reg off_canc;
+	struct st_lsm6dsx_reg bdu;
+	struct {
+		u8 addr;
+		u8 len;
+	} out;
+};
+
 /**
  * struct st_lsm6dsx_settings - ST IMU sensor settings
  * @wai: Sensor WhoAmI default value.
@@ -93,6 +193,7 @@ struct st_lsm6dsx_hw_ts_settings {
  * @batch: List of FIFO batching register info (addr + mask).
  * @fifo_ops: Sensor hw FIFO parameters.
  * @ts_settings: Hw timer related settings.
+ * @shub_settings: i2c controller related settings.
  */
 struct st_lsm6dsx_settings {
 	u8 wai;
@@ -102,6 +203,7 @@ struct st_lsm6dsx_settings {
 	struct st_lsm6dsx_reg batch[ST_LSM6DSX_MAX_ID];
 	struct st_lsm6dsx_fifo_ops fifo_ops;
 	struct st_lsm6dsx_hw_ts_settings ts_settings;
+	struct st_lsm6dsx_shub_settings shub_settings;
 };
 
 enum st_lsm6dsx_sensor_id {
@@ -129,6 +231,7 @@ enum st_lsm6dsx_fifo_mode {
  * @sip: Number of samples in a given pattern.
  * @decimator: FIFO decimation factor.
  * @ts_ref: Sensor timestamp reference for hw one.
+ * @ext_info: Sensor settings if it is connected to i2c controller
  */
 struct st_lsm6dsx_sensor {
 	char name[32];
@@ -142,6 +245,11 @@ struct st_lsm6dsx_sensor {
 	u8 sip;
 	u8 decimator;
 	s64 ts_ref;
+
+	struct {
+		const struct st_lsm6dsx_ext_dev_settings *settings;
+		u8 addr;
+	} ext_info;
 };
 
 /**
@@ -181,6 +289,7 @@ struct st_lsm6dsx_hw {
 	const struct st_lsm6dsx_settings *settings;
 };
 
+static const unsigned long st_lsm6dsx_available_scan_masks[] = {0x7, 0x0};
 extern const struct dev_pm_ops st_lsm6dsx_pm_ops;
 
 int st_lsm6dsx_probe(struct device *dev, int irq, int hw_id, const char *name,
@@ -197,6 +306,9 @@ int st_lsm6dsx_set_fifo_mode(struct st_lsm6dsx_hw *hw,
 int st_lsm6dsx_read_fifo(struct st_lsm6dsx_hw *hw);
 int st_lsm6dsx_read_tagged_fifo(struct st_lsm6dsx_hw *hw);
 int st_lsm6dsx_check_odr(struct st_lsm6dsx_sensor *sensor, u16 odr, u8 *val);
+int st_lsm6dsx_shub_probe(struct st_lsm6dsx_hw *hw, const char *name);
+int st_lsm6dsx_shub_set_enable(struct st_lsm6dsx_sensor *sensor, bool enable);
+int st_lsm6dsx_set_page(struct st_lsm6dsx_hw *hw, bool enable);
 
 static inline int
 st_lsm6dsx_update_bits_locked(struct st_lsm6dsx_hw *hw, unsigned int addr,
diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c
index 28ddedbd1304..149080acd859 100644
--- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c
+++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c
@@ -88,17 +88,6 @@
 #define ST_LSM6DSX_GYRO_FS_1000_GAIN		IIO_DEGREE_TO_RAD(35000)
 #define ST_LSM6DSX_GYRO_FS_2000_GAIN		IIO_DEGREE_TO_RAD(70000)
 
-struct st_lsm6dsx_odr {
-	u16 hz;
-	u8 val;
-};
-
-#define ST_LSM6DSX_ODR_LIST_SIZE	6
-struct st_lsm6dsx_odr_table_entry {
-	struct st_lsm6dsx_reg reg;
-	struct st_lsm6dsx_odr odr_avl[ST_LSM6DSX_ODR_LIST_SIZE];
-};
-
 static const struct st_lsm6dsx_odr_table_entry st_lsm6dsx_odr_table[] = {
 	[ST_LSM6DSX_ID_ACC] = {
 		.reg = {
@@ -126,17 +115,6 @@ static const struct st_lsm6dsx_odr_table_entry st_lsm6dsx_odr_table[] = {
 	}
 };
 
-struct st_lsm6dsx_fs {
-	u32 gain;
-	u8 val;
-};
-
-#define ST_LSM6DSX_FS_LIST_SIZE		4
-struct st_lsm6dsx_fs_table_entry {
-	struct st_lsm6dsx_reg reg;
-	struct st_lsm6dsx_fs fs_avl[ST_LSM6DSX_FS_LIST_SIZE];
-};
-
 static const struct st_lsm6dsx_fs_table_entry st_lsm6dsx_fs_table[] = {
 	[ST_LSM6DSX_ID_ACC] = {
 		.reg = {
@@ -342,27 +320,30 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = {
 				.mask = GENMASK(7, 6),
 			},
 		},
+		.shub_settings = {
+			.page_mux = {
+				.addr = 0x01,
+				.mask = BIT(6),
+			},
+			.master_en = {
+				.addr = 0x14,
+				.mask = BIT(2),
+			},
+			.pullup_en = {
+				.addr = 0x14,
+				.mask = BIT(3),
+			},
+			.aux_sens = {
+				.addr = 0x14,
+				.mask = GENMASK(1, 0),
+			},
+			.shub_out = 0x02,
+			.slv0_addr = 0x15,
+			.dw_slv0_addr = 0x21,
+		}
 	},
 };
 
-#define ST_LSM6DSX_CHANNEL(chan_type, addr, mod, scan_idx)		\
-{									\
-	.type = chan_type,						\
-	.address = addr,						\
-	.modified = 1,							\
-	.channel2 = mod,						\
-	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |			\
-			      BIT(IIO_CHAN_INFO_SCALE),			\
-	.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),	\
-	.scan_index = scan_idx,						\
-	.scan_type = {							\
-		.sign = 's',						\
-		.realbits = 16,						\
-		.storagebits = 16,					\
-		.endianness = IIO_LE,					\
-	},								\
-}
-
 static const struct iio_chan_spec st_lsm6dsx_acc_channels[] = {
 	ST_LSM6DSX_CHANNEL(IIO_ACCEL, ST_LSM6DSX_REG_ACC_OUT_X_L_ADDR,
 			   IIO_MOD_X, 0),
@@ -383,6 +364,21 @@ static const struct iio_chan_spec st_lsm6dsx_gyro_channels[] = {
 	IIO_CHAN_SOFT_TIMESTAMP(3),
 };
 
+int st_lsm6dsx_set_page(struct st_lsm6dsx_hw *hw, bool enable)
+{
+	const struct st_lsm6dsx_shub_settings *hub_settings;
+	unsigned int data;
+	int err;
+
+	hub_settings = &hw->settings->shub_settings;
+	data = ST_LSM6DSX_SHIFT_VAL(enable, hub_settings->page_mux.mask);
+	err = regmap_update_bits(hw->regmap, hub_settings->page_mux.addr,
+				 hub_settings->page_mux.mask, data);
+	usleep_range(100, 150);
+
+	return err;
+}
+
 static int st_lsm6dsx_check_whoami(struct st_lsm6dsx_hw *hw, int id)
 {
 	int err, i, j, data;
@@ -736,8 +732,6 @@ static const struct iio_info st_lsm6dsx_gyro_info = {
 	.hwfifo_set_watermark = st_lsm6dsx_set_watermark,
 };
 
-static const unsigned long st_lsm6dsx_available_scan_masks[] = {0x7, 0x0};
-
 static int st_lsm6dsx_of_get_drdy_pin(struct st_lsm6dsx_hw *hw, int *drdy_pin)
 {
 	struct device_node *np = hw->dev->of_node;
@@ -776,6 +770,51 @@ static int st_lsm6dsx_get_drdy_reg(struct st_lsm6dsx_hw *hw, u8 *drdy_reg)
 	return err;
 }
 
+static int st_lsm6dsx_init_shub(struct st_lsm6dsx_hw *hw)
+{
+	const struct st_lsm6dsx_shub_settings *hub_settings;
+	struct device_node *np = hw->dev->of_node;
+	struct st_sensors_platform_data *pdata;
+	unsigned int data;
+	int err = 0;
+
+	hub_settings = &hw->settings->shub_settings;
+
+	pdata = (struct st_sensors_platform_data *)hw->dev->platform_data;
+	if ((np && of_property_read_bool(np, "st,pullups")) ||
+	    (pdata && pdata->pullups)) {
+		err = st_lsm6dsx_set_page(hw, true);
+		if (err < 0)
+			return err;
+
+		data = ST_LSM6DSX_SHIFT_VAL(1, hub_settings->pullup_en.mask);
+		err = regmap_update_bits(hw->regmap,
+					 hub_settings->pullup_en.addr,
+					 hub_settings->pullup_en.mask, data);
+
+		st_lsm6dsx_set_page(hw, false);
+
+		if (err < 0)
+			return err;
+	}
+
+	if (hub_settings->aux_sens.addr) {
+		/* configure aux sensors */
+		err = st_lsm6dsx_set_page(hw, true);
+		if (err < 0)
+			return err;
+
+		data = ST_LSM6DSX_SHIFT_VAL(3, hub_settings->aux_sens.mask);
+		err = regmap_update_bits(hw->regmap,
+					 hub_settings->aux_sens.addr,
+					 hub_settings->aux_sens.mask, data);
+
+		st_lsm6dsx_set_page(hw, false);
+	}
+
+	return err;
+}
+
 static int st_lsm6dsx_init_hw_timer(struct st_lsm6dsx_hw *hw)
 {
 	const struct st_lsm6dsx_hw_ts_settings *ts_settings;
@@ -856,6 +895,10 @@ static int st_lsm6dsx_init_device(struct st_lsm6dsx_hw *hw)
 	if (err < 0)
 		return err;
 
+	err = st_lsm6dsx_init_shub(hw);
+	if (err < 0)
+		return err;
+
 	return st_lsm6dsx_init_hw_timer(hw);
 }
 
@@ -909,6 +952,7 @@ static struct iio_dev *st_lsm6dsx_alloc_iiodev(struct st_lsm6dsx_hw *hw,
 int st_lsm6dsx_probe(struct device *dev, int irq, int hw_id, const char *name,
 		     struct regmap *regmap)
 {
+	const struct st_lsm6dsx_shub_settings *hub_settings;
 	struct st_lsm6dsx_hw *hw;
 	int i, err;
 
@@ -944,6 +988,13 @@ int st_lsm6dsx_probe(struct device *dev, int irq, int hw_id, const char *name,
 	if (err < 0)
 		return err;
 
+	hub_settings = &hw->settings->shub_settings;
+	if (hub_settings->master_en.addr) {
+		err = st_lsm6dsx_shub_probe(hw, name);
+		if (err < 0)
+			return err;
+	}
+
 	if (hw->irq > 0) {
 		err = st_lsm6dsx_fifo_setup(hw);
 		if (err < 0)
diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c
new file mode 100644
index 000000000000..d6c5ffe9b556
--- /dev/null
+++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c
@@ -0,0 +1,702 @@
+/*
+ * STMicroelectronics st_lsm6dsx i2c controller driver
+ *
+ * i2c controller embedded in lsm6dx series can connect up to four
+ * slave devices using accelerometer sensor as trigger for i2c
+ * read/write operations. Current implementation relies on SLV0 channel
+ * for slave configuration and SLV{1,2,3} to read data and push them into
+ * the hw FIFO
+ *
+ * Copyright (C) 2018 Lorenzo Bianconi <lorenzo.bianconi83@gmail.com>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ */
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/bitfield.h>
+
+#include "st_lsm6dsx.h"
+
+#define ST_LSM6DSX_MAX_SLV_NUM			3
+#define ST_LSM6DSX_SLV_ADDR(n, base)		((base) + (n) * 3)
+#define ST_LSM6DSX_SLV_SUB_ADDR(n, base)	((base) + 1 + (n) * 3)
+#define ST_LSM6DSX_SLV_CONFIG(n, base)		((base) + 2 + (n) * 3)
+
+#define ST_LS6DSX_READ_OP_MASK			GENMASK(2, 0)
+
+static const struct st_lsm6dsx_ext_dev_settings st_lsm6dsx_ext_dev_table[] = {
+	/* LIS2MDL */
+	{
+		.i2c_addr = { 0x1e },
+		.wai = {
+			.addr = 0x4f,
+			.val = 0x40,
+		},
+		.id = ST_LSM6DSX_ID_MAGN,
+		.odr_table = {
+			.reg = {
+				.addr = 0x60,
+				.mask = GENMASK(3, 2),
+			},
+			.odr_avl[0] = {  10, 0x0 },
+			.odr_avl[1] = {  20, 0x1 },
+			.odr_avl[2] = {  50, 0x2 },
+			.odr_avl[3] = { 100, 0x3 },
+		},
+		.fs_table = {
+			.fs_avl[0] = {
+				.gain = 1500,
+				.val = 0x0,
+			}, /* 1500 uG/LSB */
+		},
+		.temp_comp = {
+			.addr = 0x60,
+			.mask = BIT(7),
+		},
+		.pwr_table = {
+			.reg = {
+				.addr = 0x60,
+				.mask = GENMASK(1, 0),
+			},
+			.off_val = 0x2,
+			.on_val = 0x0,
+		},
+		.off_canc = {
+			.addr = 0x61,
+			.mask = BIT(1),
+		},
+		.bdu = {
+			.addr = 0x62,
+			.mask = BIT(4),
+		},
+		.out = {
+			.addr = 0x68,
+			.len = 6,
+		},
+	},
+};
+
+static void st_lsm6dsx_shub_wait_complete(struct st_lsm6dsx_hw *hw)
+{
+	struct st_lsm6dsx_sensor *sensor;
+
+	sensor = iio_priv(hw->iio_devs[ST_LSM6DSX_ID_ACC]);
+	msleep((2000U / sensor->odr) + 1);
+}
+
+/**
+ * st_lsm6dsx_shub_read_reg - read i2c controller register
+ *
+ * Read st_lsm6dsx i2c controller register
+ */
+static int st_lsm6dsx_shub_read_reg(struct st_lsm6dsx_hw *hw, u8 addr,
+				    u8 *data, int len)
+{
+	const struct st_lsm6dsx_shub_settings *hub_settings;
+	int err;
+
+	mutex_lock(&hw->page_lock);
+
+	hub_settings = &hw->settings->shub_settings;
+	err = st_lsm6dsx_set_page(hw, true);
+	if (err < 0)
+		goto out;
+
+	err = regmap_bulk_read(hw->regmap, addr, data, len);
+
+	st_lsm6dsx_set_page(hw, false);
+out:
+	mutex_unlock(&hw->page_lock);
+
+	return err;
+}
+
+/**
+ * st_lsm6dsx_shub_write_reg - write i2c controller register
+ *
+ * Write st_lsm6dsx i2c controller register
+ */
+static int st_lsm6dsx_shub_write_reg(struct st_lsm6dsx_hw *hw, u8 addr,
+				     u8 *data, int len)
+{
+	int err;
+
+	mutex_lock(&hw->page_lock);
+	err = st_lsm6dsx_set_page(hw, true);
+	if (err < 0)
+		goto out;
+
+	err = regmap_bulk_write(hw->regmap, addr, data, len);
+
+	st_lsm6dsx_set_page(hw, false);
+out:
+	mutex_unlock(&hw->page_lock);
+
+	return err;
+}
+
+static int st_lsm6dsx_shub_master_enable(struct st_lsm6dsx_sensor *sensor,
+					 bool enable)
+{
+	const struct st_lsm6dsx_shub_settings *hub_settings;
+	struct st_lsm6dsx_hw *hw = sensor->hw;
+	unsigned int data;
+	int err;
+
+	/* enable acc sensor as trigger */
+	err = st_lsm6dsx_sensor_set_enable(sensor, enable);
+	if (err < 0)
+		return err;
+
+	mutex_lock(&hw->page_lock);
+
+	hub_settings = &hw->settings->shub_settings;
+	err = st_lsm6dsx_set_page(hw, true);
+	if (err < 0)
+		goto out;
+
+	data = ST_LSM6DSX_SHIFT_VAL(enable, hub_settings->master_en.mask);
+	err = regmap_update_bits(hw->regmap, hub_settings->master_en.addr,
+				 hub_settings->master_en.mask, data);
+
+	st_lsm6dsx_set_page(hw, false);
+out:
+	mutex_unlock(&hw->page_lock);
+
+	return err;
+}
+
+/**
+ * st_lsm6dsx_shub_read - read data from slave device register
+ *
+ * Read data from slave device register. SLV0 is used for
+ * one-shot read operation
+ */
+static int
+st_lsm6dsx_shub_read(struct st_lsm6dsx_sensor *sensor, u8 addr,
+		     u8 *data, int len)
+{
+	const struct st_lsm6dsx_shub_settings *hub_settings;
+	struct st_lsm6dsx_hw *hw = sensor->hw;
+	u8 config[3], slv_addr;
+	int err;
+
+	hub_settings = &hw->settings->shub_settings;
+	slv_addr = ST_LSM6DSX_SLV_ADDR(0, hub_settings->slv0_addr);
+
+	config[0] = (sensor->ext_info.addr << 1) | 1;
+	config[1] = addr;
+	config[2] = len & ST_LS6DSX_READ_OP_MASK;
+
+	err = st_lsm6dsx_shub_write_reg(hw, slv_addr, config,
+					sizeof(config));
+	if (err < 0)
+		return err;
+
+	err = st_lsm6dsx_shub_master_enable(sensor, true);
+	if (err < 0)
+		return err;
+
+	st_lsm6dsx_shub_wait_complete(hw);
+
+	err = st_lsm6dsx_shub_read_reg(hw, hub_settings->shub_out, data,
+				       len & ST_LS6DSX_READ_OP_MASK);
+
+	st_lsm6dsx_shub_master_enable(sensor, false);
+
+	memset(config, 0, sizeof(config));
+	return st_lsm6dsx_shub_write_reg(hw, slv_addr, config,
+					 sizeof(config));
+}
+
+/**
+ * st_lsm6dsx_shub_write - write data to slave device register
+ *
+ * Write data from slave device register. SLV0 is used for
+ * one-shot write operation
+ */
+static int
+st_lsm6dsx_shub_write(struct st_lsm6dsx_sensor *sensor, u8 addr,
+		      u8 *data, int len)
+{
+	const struct st_lsm6dsx_shub_settings *hub_settings;
+	struct st_lsm6dsx_hw *hw = sensor->hw;
+	u8 config[2], slv_addr;
+	int err, i;
+
+	hub_settings = &hw->settings->shub_settings;
+	slv_addr = ST_LSM6DSX_SLV_ADDR(0, hub_settings->slv0_addr);
+	config[0] = sensor->ext_info.addr << 1;
+	for (i = 0 ; i < len; i++) {
+		config[1] = addr + i;
+
+		err = st_lsm6dsx_shub_write_reg(hw, slv_addr, config,
+						sizeof(config));
+		if (err < 0)
+			return err;
+
+		err = st_lsm6dsx_shub_write_reg(hw, hub_settings->dw_slv0_addr,
+						&data[i], 1);
+		if (err < 0)
+			return err;
+
+		err = st_lsm6dsx_shub_master_enable(sensor, true);
+		if (err < 0)
+			return err;
+
+		st_lsm6dsx_shub_wait_complete(hw);
+
+		st_lsm6dsx_shub_master_enable(sensor, false);
+	}
+
+	memset(config, 0, sizeof(config));
+	return st_lsm6dsx_shub_write_reg(hw, slv_addr, config, sizeof(config));
+}
+
+static int
+st_lsm6dsx_shub_write_with_mask(struct st_lsm6dsx_sensor *sensor,
+				u8 addr, u8 mask, u8 val)
+{
+	int err;
+	u8 data;
+
+	err = st_lsm6dsx_shub_read(sensor, addr, &data, sizeof(data));
+	if (err < 0)
+		return err;
+
+	data = ((data & ~mask) | (val << __ffs(mask) & mask));
+
+	return st_lsm6dsx_shub_write(sensor, addr, &data, sizeof(data));
+}
+
+static int
+st_lsm6dsx_shub_get_odr_val(struct st_lsm6dsx_sensor *sensor,
+			    u16 odr, u16 *val)
+{
+	const struct st_lsm6dsx_ext_dev_settings *settings;
+	int i;
+
+	settings = sensor->ext_info.settings;
+	for (i = 0; i < ST_LSM6DSX_ODR_LIST_SIZE; i++)
+		if (settings->odr_table.odr_avl[i].hz == odr)
+			break;
+
+	if (i == ST_LSM6DSX_ODR_LIST_SIZE)
+		return -EINVAL;
+
+	*val = settings->odr_table.odr_avl[i].val;
+	return 0;
+}
+
+static int
+st_lsm6dsx_shub_set_odr(struct st_lsm6dsx_sensor *sensor, u16 odr)
+{
+	const struct st_lsm6dsx_ext_dev_settings *settings;
+	u16 val;
+	int err;
+
+	err = st_lsm6dsx_shub_get_odr_val(sensor, odr, &val);
+	if (err < 0)
+		return err;
+
+	settings = sensor->ext_info.settings;
+	return st_lsm6dsx_shub_write_with_mask(sensor,
+					       settings->odr_table.reg.addr,
+					       settings->odr_table.reg.mask,
+					       val);
+}
+
+int st_lsm6dsx_shub_set_enable(struct st_lsm6dsx_sensor *sensor, bool enable)
+{
+	const struct st_lsm6dsx_ext_dev_settings *settings;
+	int err;
+
+	settings = sensor->ext_info.settings;
+	if (enable) {
+		err = st_lsm6dsx_shub_set_odr(sensor, sensor->odr);
+		if (err < 0)
+			return err;
+	} else {
+		err = st_lsm6dsx_shub_write_with_mask(sensor,
+					settings->odr_table.reg.addr,
+					settings->odr_table.reg.mask, 0);
+		if (err < 0)
+			return err;
+	}
+
+	if (settings->pwr_table.reg.addr) {
+		u8 val;
+
+		val = enable ? settings->pwr_table.on_val
+			     : settings->pwr_table.off_val;
+		err = st_lsm6dsx_shub_write_with_mask(sensor,
+					settings->pwr_table.reg.addr,
+					settings->pwr_table.reg.mask, val);
+		if (err < 0)
+			return err;
+	}
+
+	return st_lsm6dsx_shub_master_enable(sensor, enable);
+}
+
+static int
+st_lsm6dsx_shub_read_oneshot(struct st_lsm6dsx_sensor *sensor,
+			     struct iio_chan_spec const *ch,
+			     int *val)
+{
+	int err, delay, len = ch->scan_type.realbits >> 3;
+	__le32 data;
+
+	err = st_lsm6dsx_shub_set_enable(sensor, true);
+	if (err < 0)
+		return err;
+
+	delay = 1000000 / sensor->odr;
+	usleep_range(delay, 2 * delay);
+
+	err = st_lsm6dsx_shub_read(sensor, ch->address, (u8 *)&data, len);
+	if (err < 0)
+		return err;
+
+	st_lsm6dsx_shub_set_enable(sensor, false);
+
+	switch (len) {
+	case 2:
+		*val = (s16)le16_to_cpu(data);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return IIO_VAL_INT;
+}
+
+static int
+st_lsm6dsx_shub_read_raw(struct iio_dev *iio_dev,
+			 struct iio_chan_spec const *ch,
+			 int *val, int *val2, long mask)
+{
+	struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev);
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		ret = iio_device_claim_direct_mode(iio_dev);
+		if (ret)
+			break;
+
+		ret = st_lsm6dsx_shub_read_oneshot(sensor, ch, val);
+		iio_device_release_direct_mode(iio_dev);
+		break;
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		*val = sensor->odr;
+		ret = IIO_VAL_INT;
+		break;
+	case IIO_CHAN_INFO_SCALE:
+		*val = 0;
+		*val2 = sensor->gain;
+		ret = IIO_VAL_INT_PLUS_MICRO;
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static int
+st_lsm6dsx_shub_write_raw(struct iio_dev *iio_dev,
+			  struct iio_chan_spec const *chan,
+			  int val, int val2, long mask)
+{
+	struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev);
+	int err;
+
+	err = iio_device_claim_direct_mode(iio_dev);
+	if (err)
+		return err;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_SAMP_FREQ: {
+		u16 data;
+
+		err = st_lsm6dsx_shub_get_odr_val(sensor, val, &data);
+		if (!err)
+			sensor->odr = val;
+		break;
+	}
+	default:
+		err = -EINVAL;
+		break;
+	}
+
+	iio_device_release_direct_mode(iio_dev);
+
+	return err;
+}
+
+static ssize_t
+st_lsm6dsx_shub_sampling_freq_avail(struct device *dev,
+				    struct device_attribute *attr,
+				    char *buf)
+{
+	struct st_lsm6dsx_sensor *sensor = iio_priv(dev_get_drvdata(dev));
+	const struct st_lsm6dsx_ext_dev_settings *settings;
+	int i, len = 0;
+
+	settings = sensor->ext_info.settings;
+	for (i = 0; i < ST_LSM6DSX_ODR_LIST_SIZE; i++) {
+		u16 val = settings->odr_table.odr_avl[i].hz;
+
+		if (val > 0)
+			len += scnprintf(buf + len, PAGE_SIZE - len, "%d ",
+					 val);
+	}
+	buf[len - 1] = '\n';
+
+	return len;
+}
+
+static ssize_t st_lsm6dsx_shub_scale_avail(struct device *dev,
+					   struct device_attribute *attr,
+					   char *buf)
+{
+	struct st_lsm6dsx_sensor *sensor = iio_priv(dev_get_drvdata(dev));
+	const struct st_lsm6dsx_ext_dev_settings *settings;
+	int i, len = 0;
+
+	settings = sensor->ext_info.settings;
+	for (i = 0; i < ST_LSM6DSX_FS_LIST_SIZE; i++) {
+		u16 val = settings->fs_table.fs_avl[i].gain;
+
+		if (val > 0)
+			len += scnprintf(buf + len, PAGE_SIZE - len, "0.%06u ",
+					 val);
+	}
+	buf[len - 1] = '\n';
+
+	return len;
+}
+
+static IIO_DEV_ATTR_SAMP_FREQ_AVAIL(st_lsm6dsx_shub_sampling_freq_avail);
+static IIO_DEVICE_ATTR(in_scale_available, 0444,
+		       st_lsm6dsx_shub_scale_avail, NULL, 0);
+static struct attribute *st_lsm6dsx_ext_attributes[] = {
+	&iio_dev_attr_sampling_frequency_available.dev_attr.attr,
+	&iio_dev_attr_in_scale_available.dev_attr.attr,
+	NULL,
+};
+
+static const struct attribute_group st_lsm6dsx_ext_attribute_group = {
+	.attrs = st_lsm6dsx_ext_attributes,
+};
+
+static const struct iio_info st_lsm6dsx_ext_info = {
+	.attrs = &st_lsm6dsx_ext_attribute_group,
+	.read_raw = st_lsm6dsx_shub_read_raw,
+	.write_raw = st_lsm6dsx_shub_write_raw,
+	.hwfifo_set_watermark = st_lsm6dsx_set_watermark,
+};
+
+static struct iio_dev *
+st_lsm6dsx_shub_alloc_iiodev(struct st_lsm6dsx_hw *hw,
+			     enum st_lsm6dsx_sensor_id id,
+			     const struct st_lsm6dsx_ext_dev_settings *info,
+			     u8 i2c_addr, const char *name)
+{
+	struct iio_chan_spec *ext_channels;
+	struct st_lsm6dsx_sensor *sensor;
+	struct iio_dev *iio_dev;
+
+	iio_dev = devm_iio_device_alloc(hw->dev, sizeof(*sensor));
+	if (!iio_dev)
+		return NULL;
+
+	iio_dev->modes = INDIO_DIRECT_MODE;
+	iio_dev->dev.parent = hw->dev;
+	iio_dev->info = &st_lsm6dsx_ext_info;
+
+	sensor = iio_priv(iio_dev);
+	sensor->id = id;
+	sensor->hw = hw;
+	sensor->odr = info->odr_table.odr_avl[0].hz;
+	sensor->gain = info->fs_table.fs_avl[0].gain;
+	sensor->ext_info.settings = info;
+	sensor->ext_info.addr = i2c_addr;
+	sensor->watermark = 1;
+
+	switch (info->id) {
+	case ST_LSM6DSX_ID_MAGN: {
+		const struct iio_chan_spec magn_channels[] = {
+			ST_LSM6DSX_CHANNEL(IIO_MAGN, info->out.addr,
+					   IIO_MOD_X, 0),
+			ST_LSM6DSX_CHANNEL(IIO_MAGN, info->out.addr + 2,
+					   IIO_MOD_Y, 1),
+			ST_LSM6DSX_CHANNEL(IIO_MAGN, info->out.addr + 4,
+					   IIO_MOD_Z, 2),
+			IIO_CHAN_SOFT_TIMESTAMP(3),
+		};
+
+		ext_channels = devm_kzalloc(hw->dev, sizeof(magn_channels),
+					    GFP_KERNEL);
+		if (!ext_channels)
+			return NULL;
+
+		memcpy(ext_channels, magn_channels, sizeof(magn_channels));
+		iio_dev->available_scan_masks = st_lsm6dsx_available_scan_masks;
+		iio_dev->channels = ext_channels;
+		iio_dev->num_channels = ARRAY_SIZE(magn_channels);
+
+		scnprintf(sensor->name, sizeof(sensor->name), "%s_magn",
+			  name);
+		break;
+	}
+	default:
+		return NULL;
+	}
+	iio_dev->name = sensor->name;
+
+	return iio_dev;
+}
+
+static int st_lsm6dsx_shub_init_device(struct st_lsm6dsx_sensor *sensor)
+{
+	const struct st_lsm6dsx_ext_dev_settings *settings;
+	int err;
+
+	settings = sensor->ext_info.settings;
+	if (settings->bdu.addr) {
+		err = st_lsm6dsx_shub_write_with_mask(sensor,
+						      settings->bdu.addr,
+						      settings->bdu.mask, 1);
+		if (err < 0)
+			return err;
+	}
+
+	if (settings->temp_comp.addr) {
+		err = st_lsm6dsx_shub_write_with_mask(sensor,
+					settings->temp_comp.addr,
+					settings->temp_comp.mask, 1);
+		if (err < 0)
+			return err;
+	}
+
+	if (settings->off_canc.addr) {
+		err = st_lsm6dsx_shub_write_with_mask(sensor,
+					settings->off_canc.addr,
+					settings->off_canc.mask, 1);
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+static int
+st_lsm6dsx_shub_check_wai(struct st_lsm6dsx_hw *hw, u8 *i2c_addr,
+			  const struct st_lsm6dsx_ext_dev_settings *settings)
+{
+	const struct st_lsm6dsx_shub_settings *hub_settings;
+	struct st_lsm6dsx_sensor *sensor;
+	u8 config[3], data, slv_addr;
+	bool found = false;
+	int i, err;
+
+	hub_settings = &hw->settings->shub_settings;
+	slv_addr = ST_LSM6DSX_SLV_ADDR(0, hub_settings->slv0_addr);
+	sensor = iio_priv(hw->iio_devs[ST_LSM6DSX_ID_ACC]);
+
+	for (i = 0; i < ARRAY_SIZE(settings->i2c_addr); i++) {
+		if (!settings->i2c_addr[i])
+			continue;
+
+		/* read wai slave register */
+		config[0] = (settings->i2c_addr[i] << 1) | 0x1;
+		config[1] = settings->wai.addr;
+		config[2] = 0x1;
+
+		err = st_lsm6dsx_shub_write_reg(hw, slv_addr, config,
+						sizeof(config));
+		if (err < 0)
+			return err;
+
+		err = st_lsm6dsx_shub_master_enable(sensor, true);
+		if (err < 0)
+			return err;
+
+		st_lsm6dsx_shub_wait_complete(hw);
+
+		err = st_lsm6dsx_shub_read_reg(hw,
+					       hub_settings->shub_out,
+					       &data, sizeof(data));
+
+		st_lsm6dsx_shub_master_enable(sensor, false);
+
+		if (err < 0)
+			return err;
+
+		if (data != settings->wai.val)
+			continue;
+
+		*i2c_addr = settings->i2c_addr[i];
+		found = true;
+		break;
+	}
+
+	/* reset SLV0 channel */
+	memset(config, 0, sizeof(config));
+	err = st_lsm6dsx_shub_write_reg(hw, slv_addr, config,
+					sizeof(config));
+	if (err < 0)
+		return err;
+
+	return found ? 0 : -ENODEV;
+}
+
+int st_lsm6dsx_shub_probe(struct st_lsm6dsx_hw *hw, const char *name)
+{
+	enum st_lsm6dsx_sensor_id id = ST_LSM6DSX_ID_EXT0;
+	struct st_lsm6dsx_sensor *sensor;
+	int err, i, num_ext_dev = 0;
+	u8 i2c_addr = 0;
+
+	for (i = 0; i < ARRAY_SIZE(st_lsm6dsx_ext_dev_table); i++) {
+		err = st_lsm6dsx_shub_check_wai(hw, &i2c_addr,
+					&st_lsm6dsx_ext_dev_table[i]);
+		if (err == -ENODEV)
+			continue;
+		else if (err < 0)
+			return err;
+
+		hw->iio_devs[id] = st_lsm6dsx_shub_alloc_iiodev(hw, id,
+						&st_lsm6dsx_ext_dev_table[i],
+						i2c_addr, name);
+		if (!hw->iio_devs[id])
+			return -ENOMEM;
+
+		sensor = iio_priv(hw->iio_devs[id]);
+		err = st_lsm6dsx_shub_init_device(sensor);
+		if (err < 0)
+			return err;
+
+		if (++num_ext_dev >= ST_LSM6DSX_MAX_SLV_NUM)
+			break;
+		id++;
+	}
+
+	return 0;
+}
diff --git a/include/linux/platform_data/st_sensors_pdata.h b/include/linux/platform_data/st_sensors_pdata.h
index f8274b0c6888..728193111c2f 100644
--- a/include/linux/platform_data/st_sensors_pdata.h
+++ b/include/linux/platform_data/st_sensors_pdata.h
@@ -18,11 +18,13 @@
  *	Accelerometer DRDY on LSM330 available only on pin 1 (see datasheet).
  * @open_drain: set the interrupt line to be open drain if possible.
  * @spi_3wire: enable spi-3wire mode.
+ * @pullups: enable/disable i2c controller pullup resistors.
  */
 struct st_sensors_platform_data {
 	u8 drdy_int_pin;
 	bool open_drain;
 	bool spi_3wire;
+	bool pullups;
 };
 
 #endif /* ST_SENSORS_PDATA_H */
-- 
2.19.1

  parent reply	other threads:[~2018-11-12  0:04 UTC|newest]

Thread overview: 26+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-11-11 14:15 [PATCH v2 0/9] add i2c controller support to st_lsm6dsx driver Lorenzo Bianconi
2018-11-11 14:15 ` [PATCH v2 1/9] iio: imu: st_lsm6dsx: introduce locked read/write utility routines Lorenzo Bianconi
2018-11-11 16:12   ` Jonathan Cameron
2018-11-11 14:15 ` [PATCH v2 2/9] iio: imu: st_lsm6dsx: reload trimming parameter at bootstrap Lorenzo Bianconi
2018-11-11 16:13   ` Jonathan Cameron
2018-11-11 14:15 ` [PATCH v2 3/9] iio: imu: st_lsm6dsx: remove static from st_lsm6dsx_set_watermark Lorenzo Bianconi
2018-11-11 16:14   ` Jonathan Cameron
2018-11-11 14:15 ` [PATCH v2 4/9] iio: imu: st_lsm6dsx: introduce ST_LSM6DSX_ID_EXT sensor ids Lorenzo Bianconi
2018-11-11 16:15   ` Jonathan Cameron
2018-11-11 14:15 ` [PATCH v2 5/9] iio: imu: st_lsm6dsx: introduce st_lsm6dsx_sensor_set_enable routine Lorenzo Bianconi
2018-11-11 16:31   ` Jonathan Cameron
2018-11-11 14:15 ` Lorenzo Bianconi [this message]
2018-11-11 16:40   ` [PATCH v2 6/9] iio: imu: st_lsm6dsx: add i2c embedded controller support Jonathan Cameron
2018-11-11 14:15 ` [PATCH v2 7/9] iio: imu: st_lsm6dsx: add st_lsm6dsx_push_tagged_data routine Lorenzo Bianconi
2018-11-11 16:43   ` Jonathan Cameron
2018-11-11 14:15 ` [PATCH v2 8/9] iio: imu: st_lsm6dsx: add hw FIFO support to i2c controller Lorenzo Bianconi
2018-11-11 16:43   ` Jonathan Cameron
2018-11-11 14:15 ` [PATCH v2 9/9] dt-bindings: iio: imu: st_lsm6dsx: add support to i2c pullup resistors Lorenzo Bianconi
2018-11-11 16:45   ` Jonathan Cameron
2018-11-17 15:37   ` Rob Herring
2018-11-17 16:26     ` Jonathan Cameron
2018-11-17 17:24       ` Lorenzo Bianconi
2018-11-21 18:57         ` Jonathan Cameron
2018-11-25  9:13           ` Lorenzo Bianconi
2018-11-11 16:46 ` [PATCH v2 0/9] add i2c controller support to st_lsm6dsx driver Jonathan Cameron
2018-11-11 17:35   ` Lorenzo Bianconi

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=c9a134df6858ee2f290c844be18f8c2cef54c559.1541945612.git.lorenzo.bianconi@redhat.com \
    --to=lorenzo.bianconi@redhat.com \
    --cc=devicetree@vger.kernel.org \
    --cc=jic23@kernel.org \
    --cc=linux-iio@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.