Message ID | 1475700868-75737-16-git-send-email-christopher.lee.bostic@gmail.com |
---|---|
State | Superseded, archived |
Headers | show |
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
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
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
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
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 --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 */