diff mbox

[linux,15/15] drivers/fsi: Initialize CFAMs for read/write access

Message ID 1475700868-75737-16-git-send-email-christopher.lee.bostic@gmail.com
State Superseded, archived
Headers show

Commit Message

christopher.lee.bostic@gmail.com Oct. 5, 2016, 8:54 p.m. UTC
From: Chris Bostic <cbostic@us.ibm.com>

Send break command to CFAMs to reset their logic.  If break
fails this serves as an indication there is no CFAM present.
Set various slave engine mode register defaults.

Add new fields to struct fsi_master to manage hardware
workarounds for break ids, target addresses and cfam IDs.

Limit each link scan to one CFAM.  Due to limitations in
hardware break commands only one can be recognized per link.

Signed-off-by: Chris Bostic <cbostic@us.ibm.com>
---
 drivers/fsi/fsi-cfam.h        |  1 +
 drivers/fsi/fsi-core.c        | 58 +++++++++++++++++++++++++++++++++++++++-
 drivers/fsi/fsi-master-fake.c |  3 +++
 drivers/fsi/fsi-master-gpio.c |  3 +++
 drivers/fsi/fsi-master.h      |  5 ++++
 drivers/fsi/fsi-slave.h       | 62 +++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 131 insertions(+), 1 deletion(-)
 create mode 100644 drivers/fsi/fsi-slave.h

Comments

Jeremy Kerr Oct. 6, 2016, 6:53 a.m. UTC | #1
Hi Chris,

> Send break command to CFAMs to reset their logic.  If break
> fails this serves as an indication there is no CFAM present.
> Set various slave engine mode register defaults.
> 
> Add new fields to struct fsi_master to manage hardware
> workarounds for break ids, target addresses and cfam IDs.
> 
> Limit each link scan to one CFAM.  Due to limitations in
> hardware break commands only one can be recognized per link.

I'm not sure what the restriction is there. We can only send a break
over one link? Or something else?

> +/*
> + * Issue a break command to this link
> + */
> +static int fsi_master_break(struct fsi_master *master, int link)
> +{
> +	int rc;
> +	uint32_t cmd = FSI_CMD_BREAK;
> +
> +	rc = master->write(master, link, master->break_id, master->break_offset,
> +			   &cmd, sizeof(cmd));
> +	if (rc) {
> +		dev_warn(master->dev, "break to %02x:%02x failed\n",
> +			 link, master->break_id);
> +		return -ENODEV;
> +	}
> +	udelay(200);		/* wait for logic reset to take effect */
> +
> +	rc = master->read(master, link, master->break_id,
> +			  FSI_SLAVE_BASE + FSI_SMODE, &cmd, sizeof(cmd));
> +	dev_info(master->dev, "smode after break:%08x rc:%d\n", cmd, rc);
> +	if (rc) {
> +		dev_warn(master->dev, "read smode after break failed\n");
> +		return -ENODEV;
> +	}
> +
> +	return rc;
> +}

This seems very specific to one particular master implementation. What
is a GPIO-based FSI master supposed to do here? Decode the write to
specific master addresses and then perform the GPIO sequences for a
proper break?

Instead, the break should be added as an operation on a struct
fsi_master:

  struct fsi_master {
  	struct device	*dev;
  	int		idx;
  	int		n_links;
  	int		(*read)(struct fsi_master *, int link,
  				uint8_t slave, uint32_t addr,
  				void *val, size_t size);
  	int		(*write)(struct fsi_master *, int link,
  				uint8_t slave, uint32_t addr,
  				const void *val, size_t size);
+ 	int		(*break)(struct fsi_master *, int link);
  };

- then the FSI master implementations can do what's required for
sending a break. Then, the core's defintion of fsi_master_break()
becomes:

static int fsi_master_break(struct fsi_master *master, int link)
{
	int rc = 0;

	if (master->break)
		master->break(master, link)

	return rc;
}

Cheers,


Jeremy
Jeremy Kerr Oct. 6, 2016, 7:02 a.m. UTC | #2
Hi Chris,

> static int fsi_master_break(struct fsi_master *master, int link)
> {
> 	int rc = 0;
> 
> 	if (master->break)
> 		master->break(master, link)

Sorry, that should be:
 		rc = master->break(master, link);

Cheers,


Jeremy
christopher.lee.bostic@gmail.com Oct. 6, 2016, 1:29 p.m. UTC | #3
On Thu, Oct 6, 2016 at 1:53 AM, Jeremy Kerr <jk@ozlabs.org> wrote:
> Hi Chris,
>
>> Send break command to CFAMs to reset their logic.  If break
>> fails this serves as an indication there is no CFAM present.
>> Set various slave engine mode register defaults.
>>
>> Add new fields to struct fsi_master to manage hardware
>> workarounds for break ids, target addresses and cfam IDs.
>>
>> Limit each link scan to one CFAM.  Due to limitations in
>> hardware break commands only one can be recognized per link.
>
> I'm not sure what the restriction is there. We can only send a break
> over one link? Or something else?
>
A break can be issued on any available link.

Due to hardware bugs that vary depending on the combination of
master type and cfam type the break command must be issued to
different ID's and addresses accordingly.  This was the reason
for adding the cfam_id, break_id, etc.. to the fsi_master struct.

For example, an FSP master must issue a break to the base
address of the 3rd cfam slot on the link.  Offset 0x00600000
on a link that covers 0x00800000 of address space.
There are different requirements for cascaded masters and their
downstream cfams.   Due to this break command limitation
there cannot be more than one cfam per link.  There are also
similar issues with 'terminate' commands that are issued to
reset a cfam out of its locked bus error state.

Additionally, some cfams such as the P8/P9 have a much
larger address window that takes up nearly all of the 0x00800000
address space for a given link.  As such there can only
ever be one cfam on that particular link.

Once the hardware issues are resolved related to break and
terminate commands then some links would be able to support
multiple cfams.

Since the first fsi code delivery will deal with basic one cfam per
link configuration platforms to start with I don't think this is
something to gate release of a first pass fsi device driver.

>> +/*
>> + * Issue a break command to this link
>> + */
>> +static int fsi_master_break(struct fsi_master *master, int link)
>> +{
>> +     int rc;
>> +     uint32_t cmd = FSI_CMD_BREAK;
>> +
>> +     rc = master->write(master, link, master->break_id, master->break_offset,
>> +                        &cmd, sizeof(cmd));
>> +     if (rc) {
>> +             dev_warn(master->dev, "break to %02x:%02x failed\n",
>> +                      link, master->break_id);
>> +             return -ENODEV;
>> +     }
>> +     udelay(200);            /* wait for logic reset to take effect */
>> +
>> +     rc = master->read(master, link, master->break_id,
>> +                       FSI_SLAVE_BASE + FSI_SMODE, &cmd, sizeof(cmd));
>> +     dev_info(master->dev, "smode after break:%08x rc:%d\n", cmd, rc);
>> +     if (rc) {
>> +             dev_warn(master->dev, "read smode after break failed\n");
>> +             return -ENODEV;
>> +     }
>> +
>> +     return rc;
>> +}
>
> This seems very specific to one particular master implementation. What
> is a GPIO-based FSI master supposed to do here? Decode the write to
> specific master addresses and then perform the GPIO sequences for a
> proper break?
>
> Instead, the break should be added as an operation on a struct
> fsi_master:
>
>   struct fsi_master {
>         struct device   *dev;
>         int             idx;
>         int             n_links;
>         int             (*read)(struct fsi_master *, int link,
>                                 uint8_t slave, uint32_t addr,
>                                 void *val, size_t size);
>         int             (*write)(struct fsi_master *, int link,
>                                 uint8_t slave, uint32_t addr,
>                                 const void *val, size_t size);
> +       int             (*break)(struct fsi_master *, int link);
>   };
>
> - then the FSI master implementations can do what's required for
> sending a break. Then, the core's defintion of fsi_master_break()
> becomes:
>
> static int fsi_master_break(struct fsi_master *master, int link)
> {
>         int rc = 0;
>
>         if (master->break)
>                 master->break(master, link)
>
>         return rc;
> }

OK, I'll make that change.

>
> Cheers,
>
>
> Jeremy
Jeremy Kerr Oct. 7, 2016, 1:21 a.m. UTC | #4
Hi Chris,

> A break can be issued on any available link.

OK.

> Due to hardware bugs that vary depending on the combination of
> master type and cfam type the break command must be issued to
> different ID's and addresses accordingly.  This was the reason for
> adding the cfam_id, break_id, etc.. to the fsi_master struct.

But (according to the docs I have) a break is just a logical 1 for 256
clocks - it contains no addressing ID / information. What do the
cfam_ids and break_ids represent "on the wire"?

Cheers,


Jeremy
christopher.lee.bostic@gmail.com Oct. 7, 2016, 2:57 p.m. UTC | #5
On Thu, Oct 6, 2016 at 8:21 PM, Jeremy Kerr <jk@ozlabs.org> wrote:
> Hi Chris,
>
>> A break can be issued on any available link.
>
> OK.
>
>> Due to hardware bugs that vary depending on the combination of
>> master type and cfam type the break command must be issued to
>> different ID's and addresses accordingly.  This was the reason for
>> adding the cfam_id, break_id, etc.. to the fsi_master struct.
>
> But (according to the docs I have) a break is just a logical 1 for 256
> clocks - it contains no addressing ID / information. What do the
> cfam_ids and break_ids represent "on the wire"?

Right, at the protocol level a break is simply 256 1's.   One of the
cascaded masters on P8,P9 though has quirks where you must
adjust the target address and ID when writing to the actual hardware.
I don't know the hardware specifics as to what the bug is but
the logic responsible for the translation of a firmware initiated MMIO
onto a link that results in the 256 1's has a bug that corrupts that
message in some way.  Note that this is even an issue for the gpio
master since the bug I just described is in the P8,P9 cascaded master
hardware.

Sounds like we need  to schedule a meeting to probably
go over all the hardware quirks that need to be dealt with.  Exchanging
emails probably isn't the quickest way.

Long story short we'll still need a master->break( ) operation defined
for each master type.

-Chris

>
> Cheers,
>
>
> Jeremy
diff mbox

Patch

diff --git a/drivers/fsi/fsi-cfam.h b/drivers/fsi/fsi-cfam.h
index a542f60..628c5de 100644
--- a/drivers/fsi/fsi-cfam.h
+++ b/drivers/fsi/fsi-cfam.h
@@ -25,6 +25,7 @@ 
 #define FSI_MAX_CFAMS_PER_LINK	4
 #define FSI_CFAM_SIZE		(FSI_LINK_SIZE / FSI_MAX_CFAMS_PER_LINK)
 #define FSI_PEEK_BASE		0x410
+#define FSI_SLAVE_BASE		0x800
 
 /* Config space decoding */
 #define FSI_CFG_NEXT_MASK	0x80000000
diff --git a/drivers/fsi/fsi-core.c b/drivers/fsi/fsi-core.c
index a7f23a5..41c5a78 100644
--- a/drivers/fsi/fsi-core.c
+++ b/drivers/fsi/fsi-core.c
@@ -19,9 +19,11 @@ 
 #include <linux/fsi.h>
 #include <linux/module.h>
 #include <linux/slab.h>
+#include <linux/delay.h>
 
 #include "fsi-master.h"
 #include "fsi-cfam.h"
+#include "fsi-slave.h"
 
 #define FSI_PEEK_BASE			0x410
 
@@ -212,13 +214,67 @@  static void fsi_cfam_release(struct device *dev)
 	kfree(cfam);
 }
 
+/*
+ * Issue a break command to this link
+ */
+static int fsi_master_break(struct fsi_master *master, int link)
+{
+	int rc;
+	uint32_t cmd = FSI_CMD_BREAK;
+
+	rc = master->write(master, link, master->break_id, master->break_offset,
+			   &cmd, sizeof(cmd));
+	if (rc) {
+		dev_warn(master->dev, "break to %02x:%02x failed\n",
+			 link, master->break_id);
+		return -ENODEV;
+	}
+	udelay(200);		/* wait for logic reset to take effect */
+
+	rc = master->read(master, link, master->break_id,
+			  FSI_SLAVE_BASE + FSI_SMODE, &cmd, sizeof(cmd));
+	dev_info(master->dev, "smode after break:%08x rc:%d\n", cmd, rc);
+	if (rc) {
+		dev_warn(master->dev, "read smode after break failed\n");
+		return -ENODEV;
+	}
+
+	return rc;
+}
+
+static void set_smode_defaults(struct fsi_master *master, uint32_t *smode)
+{
+	*smode = FSI_SMODE_WSC | FSI_SMODE_ECRC
+		| fsi_smode_echodly(0xf) | fsi_smode_senddly(0xf)
+		| fsi_smode_lbcrr(1) | fsi_smode_sid(master->cfam_id);
+}
+
 static int fsi_cfam_init(struct fsi_master *master,
 		int link, uint8_t cfam_id)
 {
 	struct fsi_cfam *cfam = NULL;
-	uint32_t chip_id;
+	uint32_t chip_id, smode;
 	int rc;
 
+	/*
+	 * todo: Due to CFAM hardware issues related to BREAK commands we're
+	 * limited to only one CFAM per link.  This can be removed once
+	 * issue is resolved.
+	 */
+	if (cfam_id > 0)
+		return 0;
+
+	rc = fsi_master_break(master, link);
+	if (rc) {
+		dev_warn(master->dev, "no cfam detected at %02x:%02x\n",
+				link, cfam_id);
+		return -ENODEV;
+	}
+
+	set_smode_defaults(master, &smode);
+	rc = master->write(master, link, master->break_id,
+			   FSI_SLAVE_BASE + FSI_SMODE, &smode, sizeof(smode));
+
 	rc = master->read(master, link, cfam_id, 0, &chip_id, sizeof(chip_id));
 	if (rc) {
 		dev_warn(master->dev, "can't read cfam %02x:%02x: %d\n",
diff --git a/drivers/fsi/fsi-master-fake.c b/drivers/fsi/fsi-master-fake.c
index 658a5f6..3663a0a 100644
--- a/drivers/fsi/fsi-master-fake.c
+++ b/drivers/fsi/fsi-master-fake.c
@@ -63,6 +63,9 @@  static int fsi_master_fake_probe(struct platform_device *pdev)
 	master->n_links = 1;
 	master->read = fsi_master_fake_read;
 	master->write = fsi_master_fake_write;
+	master->cfam_id = 0;
+	master->break_id = 0;
+	master->break_offset = 0;
 
 	return fsi_master_register(master);
 }
diff --git a/drivers/fsi/fsi-master-gpio.c b/drivers/fsi/fsi-master-gpio.c
index 3e1aeb6..9769960 100644
--- a/drivers/fsi/fsi-master-gpio.c
+++ b/drivers/fsi/fsi-master-gpio.c
@@ -71,6 +71,9 @@  static int fsi_master_gpio_probe(struct platform_device *pdev)
 
 	master->master.read = fsi_master_gpio_read;
 	master->master.write = fsi_master_gpio_write;
+	master->master.cfam_id = 0;
+	master->master.break_id = 0;
+	master->master.break_offset = 0;
 
 	return fsi_master_register(&master->master);
 }
diff --git a/drivers/fsi/fsi-master.h b/drivers/fsi/fsi-master.h
index 4cdc4b4..41a5dc5 100644
--- a/drivers/fsi/fsi-master.h
+++ b/drivers/fsi/fsi-master.h
@@ -19,10 +19,15 @@ 
 
 #include <linux/device.h>
 
+#define	FSI_CMD_BREAK		0xc0de0000	/* Break command */
+
 struct fsi_master {
 	struct device	*dev;
 	int		idx;
 	int		n_links;
+	int		cfam_id;	/* 1 CFAM per link hw workaround */
+	int		break_id;	/* Break to this CFAM ID */
+	uint32_t	break_offset;	/* Where to write break */
 	int		(*read)(struct fsi_master *, int link,
 				uint8_t cfam, uint32_t addr,
 				void *val, size_t size);
diff --git a/drivers/fsi/fsi-slave.h b/drivers/fsi/fsi-slave.h
new file mode 100644
index 0000000..ea7a760
--- /dev/null
+++ b/drivers/fsi/fsi-slave.h
@@ -0,0 +1,62 @@ 
+/*
+ * FSI slave engine definitions.
+ *
+ * Copyright (C) IBM Corporation 2016
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef DRIVERS_FSI_SLAVE_H
+#define DRIVERS_FSI_SLAVE_H
+
+/*
+ * FSI slave engine control register offsets
+ */
+#define	FSI_SMODE		0x0	/* R/W: Mode register */
+
+/*
+ * SMODE fields
+ */
+#define	FSI_SMODE_WSC		0x80000000	/* Warm start completed */
+#define	FSI_SMODE_ECRC		0x20000000	/* Enable hardware CRC check */
+#define	FSI_SMODE_SID_SHIFT	24		/* ID shift */
+#define	FSI_SMODE_SID_MASK	3		/* ID mask */
+#define	FSI_SMODE_ED_SHIFT	20		/* Echo delay shift */
+#define	FSI_SMODE_ED_MASK	0xf		/* Echo delay mask */
+#define	FSI_SMODE_SD_SHIFT	16		/* Send delay shift */
+#define	FSI_SMODE_SD_MASK	0xf		/* Send delay mask */
+#define	FSI_SMODE_LBCRR_SHIFT	8		/* Clock rate ratio shift */
+#define	FSI_SMODE_LBCRR_MASK	0xf		/* Clock rate ratio mask */
+
+/* Encode slave local bus echo delay */
+static inline uint32_t fsi_smode_echodly(int x)
+{
+	return (x & FSI_SMODE_ED_MASK) << FSI_SMODE_ED_SHIFT;
+}
+
+/* Encode slave local bus send delay */
+static inline uint32_t fsi_smode_senddly(int x)
+{
+	return (x & FSI_SMODE_SD_MASK) << FSI_SMODE_SD_SHIFT;
+}
+
+/* Encode slave local bus clock rate ratio */
+static inline uint32_t fsi_smode_lbcrr(int x)
+{
+	return (x & FSI_SMODE_LBCRR_MASK) << FSI_SMODE_LBCRR_SHIFT;
+}
+
+/* Encode slave ID */
+static inline uint32_t fsi_smode_sid(int x)
+{
+	return (x & FSI_SMODE_SID_MASK) << FSI_SMODE_SID_SHIFT;
+}
+
+#endif /* DRIVERS_FSI_SLAVE_H */