public inbox for linux-i2c@vger.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH] i2c:mux: Driver for PCA9641 I2C Master Arbiter
@ 2017-02-10 10:43 Vidya Sagar Ravipati
  2017-02-10 13:17 ` Peter Rosin
  2017-02-14  8:39 ` Guenter Roeck
  0 siblings, 2 replies; 3+ messages in thread
From: Vidya Sagar Ravipati @ 2017-02-10 10:43 UTC (permalink / raw)
  To: jdelvare, linux, wsa, linux-i2c; +Cc: roopa, vidya.chowdary

From: Vidya Sagar Ravipati <vidya@cumulusnetworks.com>

This patch adds support for PCA9641, an I2C Bus Master Arbiter.
NXP PCA9641 arbiter is modeled as single channel I2C Multiplexer
to be able to utilize the I2C multiplexer framework similar to
PCA9541.

Enhancing PCA9541 driver to support PCA9641 i2c master arbiter
http://www.nxp.com/documents/data_sheet/PCA9641.pdf

Signed-off-by: Vidya Sagar Ravipati <vidya@cumulusnetworks.com>
Signed-off-by: Luffy<Luffy.Cheng@quantatw.com>
---
 drivers/i2c/muxes/i2c-mux-pca9541.c | 157 ++++++++++++++++++++++++++++++++++--
 1 file changed, 152 insertions(+), 5 deletions(-)

diff --git a/drivers/i2c/muxes/i2c-mux-pca9541.c b/drivers/i2c/muxes/i2c-mux-pca9541.c
index 77840f7..89d8518 100644
--- a/drivers/i2c/muxes/i2c-mux-pca9541.c
+++ b/drivers/i2c/muxes/i2c-mux-pca9541.c
@@ -59,6 +59,36 @@
 #define PCA9541_ISTAT_MYTEST	(1 << 6)
 #define PCA9541_ISTAT_NMYTEST	(1 << 7)
 
+#define PCA9641_ID		0
+#define PCA9641_ID_MAGIC	0x38
+#define PCA9641_CONTROL		0x01
+#define PCA9641_STATUS		0x02
+
+#define PCA9641_CONTROL		0x01
+#define PCA9641_CTL_LOCK_REQ		(1 << 0)
+#define PCA9641_CTL_LOCK_GRANT		(1 << 1)
+#define PCA9641_CTL_BUS_CONNECT		(1 << 2)
+#define PCA9641_CTL_BUS_INIT		(1 << 3)
+#define PCA9641_CTL_SMBUS_SWRST		(1 << 4)
+#define PCA9641_CTL_IDLE_TIMER_DIS	(1 << 5)
+#define PCA9641_CTL_SMBUS_DIS		(1 << 6)
+#define PCA9641_CTL_PRIORITY		(1 << 7)
+
+#define PCA9641_STATUS		0x02
+#define PCA9641_STS_OTHER_LOCK		(1 << 0)
+#define PCA9641_STS_BUS_INIT_FAIL	(1 << 1)
+#define PCA9641_STS_BUS_HUNG		(1 << 2)
+#define PCA9641_STS_MBOX_EMPTY		(1 << 3)
+#define PCA9641_STS_MBOX_FULL		(1 << 4)
+#define PCA9641_STS_TEST_INT		(1 << 5)
+#define PCA9641_STS_SCL_IO		(1 << 6)
+#define PCA9641_STS_SDA_IO		(1 << 7)
+
+#define PCA9641_RES_TIME       0x03
+
+#define other_lock(x)  ((x) & PCA9641_STS_OTHER_LOCK)
+#define lock_grant(x)  ((x) & PCA9641_CTL_LOCK_GRANT)
+
 #define BUSON		(PCA9541_CTL_BUSON | PCA9541_CTL_NBUSON)
 #define MYBUS		(PCA9541_CTL_MYBUS | PCA9541_CTL_NMYBUS)
 #define mybus(x)	(!((x) & MYBUS) || ((x) & MYBUS) == MYBUS)
@@ -73,13 +103,20 @@
 #define SELECT_DELAY_LONG	1000
 
 struct pca9541 {
+	int device_type;
 	struct i2c_client *client;
 	unsigned long select_timeout;
 	unsigned long arb_timeout;
 };
 
+enum pca9x41 {
+	PCA9541 = 0,
+	PCA9641
+};
+
 static const struct i2c_device_id pca9541_id[] = {
-	{"pca9541", 0},
+	{"pca9541", PCA9541},
+	{"pca9641", PCA9641},
 	{}
 };
 
@@ -178,12 +215,17 @@ static int pca9541_reg_read(struct i2c_client *client, u8 command)
 /* Release bus. Also reset NTESTON and BUSINIT if it was set. */
 static void pca9541_release_bus(struct i2c_client *client)
 {
+	struct pca9541 *data = i2c_get_clientdata(client);
 	int reg;
 
-	reg = pca9541_reg_read(client, PCA9541_CONTROL);
-	if (reg >= 0 && !busoff(reg) && mybus(reg))
-		pca9541_reg_write(client, PCA9541_CONTROL,
-				  (reg & PCA9541_CTL_NBUSON) >> 1);
+	if (data->device_type == PCA9541) {
+		reg = pca9541_reg_read(client, PCA9541_CONTROL);
+		if (reg >= 0 && !busoff(reg) && mybus(reg))
+			pca9541_reg_write(client, PCA9541_CONTROL,
+				(reg & PCA9541_CTL_NBUSON) >> 1);
+	} else
+		pca9541_reg_write(client, PCA9641_CONTROL, 0);
+
 }
 
 /*
@@ -294,6 +336,90 @@ static int pca9541_arbitrate(struct i2c_client *client)
 	return 0;
 }
 
+/*
+ * Channel arbitration
+ *
+ * Return values:
+ *  <0: error
+ *  0 : bus not acquired
+ *  1 : bus acquired
+ */
+static int pca9641_arbitrate(struct i2c_client *client)
+{
+	struct pca9541 *data = i2c_get_clientdata(client);
+	int reg_ctl, reg_sts;
+
+	reg_ctl = pca9541_reg_read(client, PCA9641_CONTROL);
+	if (reg_ctl < 0)
+		return reg_ctl;
+
+	reg_sts = pca9541_reg_read(client, PCA9641_STATUS);
+	if (!other_lock(reg_sts) && !lock_grant(reg_ctl)) {
+		/*
+		 * Bus is off. Request ownership or turn it on unless
+		 * other master requested ownership.
+		 */
+		/* FIXME: bus reserve time (1-255) for without interrupt */
+		/*  Lock forever
+		 * pca9541_reg_write(client, PCA9641_RES_TIME, 0);
+		 */
+		reg_ctl |= PCA9641_CTL_LOCK_REQ;
+		pca9541_reg_write(client, PCA9641_CONTROL, reg_ctl);
+
+		/* FIXME: If need Wait for lock grant */
+		udelay(100);
+
+		reg_ctl = pca9541_reg_read(client, PCA9641_CONTROL);
+
+		if (lock_grant(reg_ctl)) {
+			/*
+			 * Other master did not request ownership,
+			 * or arbitration timeout expired. Take the bus.
+			 */
+			reg_ctl |= PCA9641_CTL_BUS_CONNECT;
+			pca9541_reg_write(client, PCA9641_CONTROL, reg_ctl);
+			data->select_timeout = SELECT_DELAY_SHORT;
+
+			return 1;
+		} else {
+			/*
+			 * Other master requested ownership.
+			 * Set extra long timeout to give it time to acquire it.
+			 */
+			data->select_timeout = SELECT_DELAY_LONG * 2;
+		}
+	} else if (lock_grant(reg_ctl)) {
+		/*
+		 * Bus is on, and we own it. We are done with acquisition.
+		 */
+		reg_ctl |= PCA9641_CTL_BUS_CONNECT;
+		pca9541_reg_write(client, PCA9641_CONTROL, reg_ctl);
+
+		return 1;
+	} else if (other_lock(reg_sts)) {
+		/*
+		 * Other master owns the bus.
+		 * If arbitration timeout has expired, force ownership.
+		 * Otherwise request it.
+		 */
+		data->select_timeout = SELECT_DELAY_LONG;
+		reg_ctl |= PCA9641_CTL_LOCK_REQ;
+		pca9541_reg_write(client, PCA9641_CONTROL, reg_ctl);
+
+		/*
+		 * if (time_is_before_eq_jiffies(data->arb_timeout)) {
+		 * TODO:Time is up, take the bus and reset it.
+		 *
+		 *} else {
+		 * TODO: Request bus ownership if needed
+		 *
+		 *}
+		 */
+	}
+
+	return 0;
+}
+
 static int pca9541_select_chan(struct i2c_mux_core *muxc, u32 chan)
 {
 	struct pca9541 *data = i2c_mux_priv(muxc);
@@ -306,6 +432,11 @@ static int pca9541_select_chan(struct i2c_mux_core *muxc, u32 chan)
 		/* force bus ownership after this time */
 
 	do {
+		if (data->device_type == PCA9541)
+			ret = pca9541_arbitrate(client);
+		else
+			ret = pca9641_arbitrate(client);
+
 		ret = pca9541_arbitrate(client);
 		if (ret)
 			return ret < 0 ? ret : 0;
@@ -328,6 +459,17 @@ static int pca9541_release_chan(struct i2c_mux_core *muxc, u32 chan)
 	return 0;
 }
 
+static int pca9641_detect_id(struct i2c_client *client)
+{
+	int reg;
+
+	reg = pca9541_reg_read(client, PCA9641_ID);
+	if (reg == PCA9641_ID_MAGIC)
+		return 1;
+	else
+		return 0;
+}
+
 /*
  * I2C init/probing/exit functions
  */
@@ -335,6 +477,7 @@ static int pca9541_probe(struct i2c_client *client,
 			 const struct i2c_device_id *id)
 {
 	struct i2c_adapter *adap = client->adapter;
+	kernel_ulong_t device_id = id->driver_data;
 	struct pca954x_platform_data *pdata = dev_get_platdata(&client->dev);
 	struct i2c_mux_core *muxc;
 	struct pca9541 *data;
@@ -344,6 +487,9 @@ static int pca9541_probe(struct i2c_client *client,
 	if (!i2c_check_functionality(adap, I2C_FUNC_SMBUS_BYTE_DATA))
 		return -ENODEV;
 
+	if (device_id == PCA9641 && !pca9641_detect_id(client))
+		return ret;
+
 	/*
 	 * I2C accesses are unprotected here.
 	 * We have to lock the adapter before releasing the bus.
@@ -366,6 +512,7 @@ static int pca9541_probe(struct i2c_client *client,
 	data = i2c_mux_priv(muxc);
 	data->client = client;
 
+	data->device_type = device_id;
 	i2c_set_clientdata(client, muxc);
 
 	ret = i2c_mux_add_adapter(muxc, force, 0, 0);
-- 
2.1.4

^ permalink raw reply related	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2017-02-14  8:39 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2017-02-10 10:43 [RFC PATCH] i2c:mux: Driver for PCA9641 I2C Master Arbiter Vidya Sagar Ravipati
2017-02-10 13:17 ` Peter Rosin
2017-02-14  8:39 ` Guenter Roeck

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox