diff options
Diffstat (limited to 'drivers/fsi')
-rw-r--r-- | drivers/fsi/Kconfig | 32 | ||||
-rw-r--r-- | drivers/fsi/Makefile | 2 | ||||
-rw-r--r-- | drivers/fsi/cf-fsi-fw.h | 157 | ||||
-rw-r--r-- | drivers/fsi/fsi-core.c | 590 | ||||
-rw-r--r-- | drivers/fsi/fsi-master-ast-cf.c | 1440 | ||||
-rw-r--r-- | drivers/fsi/fsi-master-gpio.c | 471 | ||||
-rw-r--r-- | drivers/fsi/fsi-master-hub.c | 5 | ||||
-rw-r--r-- | drivers/fsi/fsi-master.h | 37 | ||||
-rw-r--r-- | drivers/fsi/fsi-sbefifo.c | 1066 | ||||
-rw-r--r-- | drivers/fsi/fsi-scom.c | 558 |
10 files changed, 4036 insertions, 322 deletions
diff --git a/drivers/fsi/Kconfig b/drivers/fsi/Kconfig index a326ed663d3c..af3a20dd5aa4 100644 --- a/drivers/fsi/Kconfig +++ b/drivers/fsi/Kconfig @@ -12,6 +12,21 @@ menuconfig FSI if FSI +config FSI_NEW_DEV_NODE + bool "Create '/dev/fsi' directory for char devices" + default n + ---help--- + This option causes char devices created for FSI devices to be + located under a common /dev/fsi/ directory. Set to N unless your + userspace has been updated to handle the new location. + + Additionally, it also causes the char device names to be offset + by one so that chip 0 will have /dev/scom1 and chip1 /dev/scom2 + to match old userspace expectations. + + New userspace will use udev rules to generate predictable access + symlinks in /dev/fsi/by-path when this option is enabled. + config FSI_MASTER_GPIO tristate "GPIO-based FSI master" depends on GPIOLIB @@ -27,9 +42,26 @@ config FSI_MASTER_HUB allow chaining of FSI links to an arbitrary depth. This allows for a high target device fanout. +config FSI_MASTER_AST_CF + tristate "FSI master based on Aspeed ColdFire coprocessor" + depends on GPIOLIB + depends on GPIO_ASPEED + ---help--- + This option enables a FSI master using the AST2400 and AST2500 GPIO + lines driven by the internal ColdFire coprocessor. This requires + the corresponding machine specific ColdFire firmware to be available. + config FSI_SCOM tristate "SCOM FSI client device driver" ---help--- This option enables an FSI based SCOM device driver. +config FSI_SBEFIFO + tristate "SBEFIFO FSI client device driver" + depends on OF_ADDRESS + ---help--- + This option enables an FSI based SBEFIFO device driver. The SBEFIFO is + a pipe-like FSI device for communicating with the self boot engine + (SBE) on POWER processors. + endif diff --git a/drivers/fsi/Makefile b/drivers/fsi/Makefile index 65eb99dfafdb..a50d6ce22fb3 100644 --- a/drivers/fsi/Makefile +++ b/drivers/fsi/Makefile @@ -2,4 +2,6 @@ obj-$(CONFIG_FSI) += fsi-core.o obj-$(CONFIG_FSI_MASTER_HUB) += fsi-master-hub.o obj-$(CONFIG_FSI_MASTER_GPIO) += fsi-master-gpio.o +obj-$(CONFIG_FSI_MASTER_AST_CF) += fsi-master-ast-cf.o obj-$(CONFIG_FSI_SCOM) += fsi-scom.o +obj-$(CONFIG_FSI_SBEFIFO) += fsi-sbefifo.o diff --git a/drivers/fsi/cf-fsi-fw.h b/drivers/fsi/cf-fsi-fw.h new file mode 100644 index 000000000000..712df0461911 --- /dev/null +++ b/drivers/fsi/cf-fsi-fw.h @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: GPL-2.0+ +#ifndef __CF_FSI_FW_H +#define __CF_FSI_FW_H + +/* + * uCode file layout + * + * 0000...03ff : m68k exception vectors + * 0400...04ff : Header info & boot config block + * 0500....... : Code & stack + */ + +/* + * Header info & boot config area + * + * The Header info is built into the ucode and provide version and + * platform information. + * + * the Boot config needs to be adjusted by the ARM prior to starting + * the ucode if the Command/Status area isn't at 0x320000 in CF space + * (ie. beginning of SRAM). + */ + +#define HDR_OFFSET 0x400 + +/* Info: Signature & version */ +#define HDR_SYS_SIG 0x00 /* 2 bytes system signature */ +#define SYS_SIG_SHARED 0x5348 +#define SYS_SIG_SPLIT 0x5350 +#define HDR_FW_VERS 0x02 /* 2 bytes Major.Minor */ +#define HDR_API_VERS 0x04 /* 2 bytes Major.Minor */ +#define API_VERSION_MAJ 2 /* Current version */ +#define API_VERSION_MIN 1 +#define HDR_FW_OPTIONS 0x08 /* 4 bytes option flags */ +#define FW_OPTION_TRACE_EN 0x00000001 /* FW tracing enabled */ +#define FW_OPTION_CONT_CLOCK 0x00000002 /* Continuous clocking supported */ +#define HDR_FW_SIZE 0x10 /* 4 bytes size for combo image */ + +/* Boot Config: Address of Command/Status area */ +#define HDR_CMD_STAT_AREA 0x80 /* 4 bytes CF address */ +#define HDR_FW_CONTROL 0x84 /* 4 bytes control flags */ +#define FW_CONTROL_CONT_CLOCK 0x00000002 /* Continuous clocking enabled */ +#define FW_CONTROL_DUMMY_RD 0x00000004 /* Extra dummy read (AST2400) */ +#define FW_CONTROL_USE_STOP 0x00000008 /* Use STOP instructions */ +#define HDR_CLOCK_GPIO_VADDR 0x90 /* 2 bytes offset from GPIO base */ +#define HDR_CLOCK_GPIO_DADDR 0x92 /* 2 bytes offset from GPIO base */ +#define HDR_DATA_GPIO_VADDR 0x94 /* 2 bytes offset from GPIO base */ +#define HDR_DATA_GPIO_DADDR 0x96 /* 2 bytes offset from GPIO base */ +#define HDR_TRANS_GPIO_VADDR 0x98 /* 2 bytes offset from GPIO base */ +#define HDR_TRANS_GPIO_DADDR 0x9a /* 2 bytes offset from GPIO base */ +#define HDR_CLOCK_GPIO_BIT 0x9c /* 1 byte bit number */ +#define HDR_DATA_GPIO_BIT 0x9d /* 1 byte bit number */ +#define HDR_TRANS_GPIO_BIT 0x9e /* 1 byte bit number */ + +/* + * Command/Status area layout: Main part + */ + +/* Command/Status register: + * + * +---------------------------+ + * | STAT | RLEN | CLEN | CMD | + * | 8 | 8 | 8 | 8 | + * +---------------------------+ + * | | | | + * status | | | + * Response len | | + * (in bits) | | + * | | + * Command len | + * (in bits) | + * | + * Command code + * + * Due to the big endian layout, that means that a byte read will + * return the status byte + */ +#define CMD_STAT_REG 0x00 +#define CMD_REG_CMD_MASK 0x000000ff +#define CMD_REG_CMD_SHIFT 0 +#define CMD_NONE 0x00 +#define CMD_COMMAND 0x01 +#define CMD_BREAK 0x02 +#define CMD_IDLE_CLOCKS 0x03 /* clen = #clocks */ +#define CMD_INVALID 0xff +#define CMD_REG_CLEN_MASK 0x0000ff00 +#define CMD_REG_CLEN_SHIFT 8 +#define CMD_REG_RLEN_MASK 0x00ff0000 +#define CMD_REG_RLEN_SHIFT 16 +#define CMD_REG_STAT_MASK 0xff000000 +#define CMD_REG_STAT_SHIFT 24 +#define STAT_WORKING 0x00 +#define STAT_COMPLETE 0x01 +#define STAT_ERR_INVAL_CMD 0x80 +#define STAT_ERR_INVAL_IRQ 0x81 +#define STAT_ERR_MTOE 0x82 + +/* Response tag & CRC */ +#define STAT_RTAG 0x04 + +/* Response CRC */ +#define STAT_RCRC 0x05 + +/* Echo and Send delay */ +#define ECHO_DLY_REG 0x08 +#define SEND_DLY_REG 0x09 + +/* Command data area + * + * Last byte of message must be left aligned + */ +#define CMD_DATA 0x10 /* 64 bit of data */ + +/* Response data area, right aligned, unused top bits are 1 */ +#define RSP_DATA 0x20 /* 32 bit of data */ + +/* Misc */ +#define INT_CNT 0x30 /* 32-bit interrupt count */ +#define BAD_INT_VEC 0x34 /* 32-bit bad interrupt vector # */ +#define CF_STARTED 0x38 /* byte, set to -1 when copro started */ +#define CLK_CNT 0x3c /* 32-bit, clock count (debug only) */ + +/* + * SRAM layout: GPIO arbitration part + */ +#define ARB_REG 0x40 +#define ARB_ARM_REQ 0x01 +#define ARB_ARM_ACK 0x02 + +/* Misc2 */ +#define CF_RESET_D0 0x50 +#define CF_RESET_D1 0x54 +#define BAD_INT_S0 0x58 +#define BAD_INT_S1 0x5c +#define STOP_CNT 0x60 + +/* Internal */ + +/* + * SRAM layout: Trace buffer (debug builds only) + */ +#define TRACEBUF 0x100 +#define TR_CLKOBIT0 0xc0 +#define TR_CLKOBIT1 0xc1 +#define TR_CLKOSTART 0x82 +#define TR_OLEN 0x83 /* + len */ +#define TR_CLKZ 0x84 /* + count */ +#define TR_CLKWSTART 0x85 +#define TR_CLKTAG 0x86 /* + tag */ +#define TR_CLKDATA 0x87 /* + len */ +#define TR_CLKCRC 0x88 /* + raw crc */ +#define TR_CLKIBIT0 0x90 +#define TR_CLKIBIT1 0x91 +#define TR_END 0xff + +#endif /* __CF_FSI_FW_H */ + diff --git a/drivers/fsi/fsi-core.c b/drivers/fsi/fsi-core.c index 4c03d6933646..2c31563fdcae 100644 --- a/drivers/fsi/fsi-core.c +++ b/drivers/fsi/fsi-core.c @@ -11,6 +11,11 @@ * 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. + * + * TODO: + * - Rework topology + * - s/chip_id/chip_loc + * - s/cfam/chip (cfam_id -> chip_id etc...) */ #include <linux/crc4.h> @@ -21,6 +26,9 @@ #include <linux/of.h> #include <linux/slab.h> #include <linux/bitops.h> +#include <linux/cdev.h> +#include <linux/fs.h> +#include <linux/uaccess.h> #include "fsi-master.h" @@ -78,9 +86,15 @@ static DEFINE_IDA(master_ida); struct fsi_slave { struct device dev; struct fsi_master *master; - int id; - int link; + struct cdev cdev; + int cdev_idx; + int id; /* FSI address */ + int link; /* FSI link# */ + u32 cfam_id; + int chip_id; uint32_t size; /* size of slave address space */ + u8 t_send_delay; + u8 t_echo_delay; }; #define to_fsi_master(d) container_of(d, struct fsi_master, dev) @@ -89,6 +103,13 @@ struct fsi_slave { static const int slave_retries = 2; static int discard_errors; +static dev_t fsi_base_dev; +static DEFINE_IDA(fsi_minor_ida); +#define FSI_CHAR_MAX_DEVICES 0x1000 + +/* Legacy /dev numbering: 4 devices per chip, 16 chips */ +#define FSI_CHAR_LEGACY_TOP 64 + static int fsi_master_read(struct fsi_master *master, int link, uint8_t slave_id, uint32_t addr, void *val, size_t size); static int fsi_master_write(struct fsi_master *master, int link, @@ -190,7 +211,7 @@ static int fsi_slave_calc_addr(struct fsi_slave *slave, uint32_t *addrp, static int fsi_slave_report_and_clear_errors(struct fsi_slave *slave) { struct fsi_master *master = slave->master; - uint32_t irq, stat; + __be32 irq, stat; int rc, link; uint8_t id; @@ -215,7 +236,53 @@ static int fsi_slave_report_and_clear_errors(struct fsi_slave *slave) &irq, sizeof(irq)); } -static int fsi_slave_set_smode(struct fsi_master *master, int link, int id); +/* 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; +} + +static uint32_t fsi_slave_smode(int id, u8 t_senddly, u8 t_echodly) +{ + return FSI_SMODE_WSC | FSI_SMODE_ECRC + | fsi_smode_sid(id) + | fsi_smode_echodly(t_echodly - 1) | fsi_smode_senddly(t_senddly - 1) + | fsi_smode_lbcrr(0x8); +} + +static int fsi_slave_set_smode(struct fsi_slave *slave) +{ + uint32_t smode; + __be32 data; + + /* set our smode register with the slave ID field to 0; this enables + * extended slave addressing + */ + smode = fsi_slave_smode(slave->id, slave->t_send_delay, slave->t_echo_delay); + data = cpu_to_be32(smode); + + return fsi_master_write(slave->master, slave->link, slave->id, + FSI_SLAVE_BASE + FSI_SMODE, + &data, sizeof(data)); +} static int fsi_slave_handle_error(struct fsi_slave *slave, bool write, uint32_t addr, size_t size) @@ -223,7 +290,7 @@ static int fsi_slave_handle_error(struct fsi_slave *slave, bool write, struct fsi_master *master = slave->master; int rc, link; uint32_t reg; - uint8_t id; + uint8_t id, send_delay, echo_delay; if (discard_errors) return -1; @@ -254,15 +321,26 @@ static int fsi_slave_handle_error(struct fsi_slave *slave, bool write, } } + send_delay = slave->t_send_delay; + echo_delay = slave->t_echo_delay; + /* getting serious, reset the slave via BREAK */ rc = fsi_master_break(master, link); if (rc) return rc; - rc = fsi_slave_set_smode(master, link, id); + slave->t_send_delay = send_delay; + slave->t_echo_delay = echo_delay; + + rc = fsi_slave_set_smode(slave); if (rc) return rc; + if (master->link_config) + master->link_config(master, link, + slave->t_send_delay, + slave->t_echo_delay); + return fsi_slave_report_and_clear_errors(slave); } @@ -390,7 +468,6 @@ static struct device_node *fsi_device_find_of_node(struct fsi_device *dev) static int fsi_slave_scan(struct fsi_slave *slave) { uint32_t engine_addr; - uint32_t conf; int rc, i; /* @@ -404,15 +481,17 @@ static int fsi_slave_scan(struct fsi_slave *slave) for (i = 2; i < engine_page_size / sizeof(uint32_t); i++) { uint8_t slots, version, type, crc; struct fsi_device *dev; + uint32_t conf; + __be32 data; - rc = fsi_slave_read(slave, (i + 1) * sizeof(conf), - &conf, sizeof(conf)); + rc = fsi_slave_read(slave, (i + 1) * sizeof(data), + &data, sizeof(data)); if (rc) { dev_warn(&slave->dev, "error reading slave registers\n"); return -1; } - conf = be32_to_cpu(conf); + conf = be32_to_cpu(data); crc = crc4(0, conf, 32); if (crc) { @@ -539,79 +618,11 @@ static const struct bin_attribute fsi_slave_raw_attr = { .write = fsi_slave_sysfs_raw_write, }; -static ssize_t fsi_slave_sysfs_term_write(struct file *file, - struct kobject *kobj, struct bin_attribute *attr, - char *buf, loff_t off, size_t count) -{ - struct fsi_slave *slave = to_fsi_slave(kobj_to_dev(kobj)); - struct fsi_master *master = slave->master; - - if (!master->term) - return -ENODEV; - - master->term(master, slave->link, slave->id); - return count; -} - -static const struct bin_attribute fsi_slave_term_attr = { - .attr = { - .name = "term", - .mode = 0200, - }, - .size = 0, - .write = fsi_slave_sysfs_term_write, -}; - -/* 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; -} - -static uint32_t fsi_slave_smode(int id) -{ - return FSI_SMODE_WSC | FSI_SMODE_ECRC - | fsi_smode_sid(id) - | fsi_smode_echodly(0xf) | fsi_smode_senddly(0xf) - | fsi_smode_lbcrr(0x8); -} - -static int fsi_slave_set_smode(struct fsi_master *master, int link, int id) -{ - uint32_t smode; - - /* set our smode register with the slave ID field to 0; this enables - * extended slave addressing - */ - smode = fsi_slave_smode(id); - smode = cpu_to_be32(smode); - - return fsi_master_write(master, link, id, FSI_SLAVE_BASE + FSI_SMODE, - &smode, sizeof(smode)); -} - static void fsi_slave_release(struct device *dev) { struct fsi_slave *slave = to_fsi_slave(dev); + fsi_free_minor(slave->dev.devt); of_node_put(dev->of_node); kfree(slave); } @@ -659,11 +670,303 @@ static struct device_node *fsi_slave_find_of_node(struct fsi_master *master, return NULL; } +static ssize_t cfam_read(struct file *filep, char __user *buf, size_t count, + loff_t *offset) +{ + struct fsi_slave *slave = filep->private_data; + size_t total_len, read_len; + loff_t off = *offset; + ssize_t rc; + + if (off < 0) + return -EINVAL; + + if (off > 0xffffffff || count > 0xffffffff || off + count > 0xffffffff) + return -EINVAL; + + for (total_len = 0; total_len < count; total_len += read_len) { + __be32 data; + + read_len = min_t(size_t, count, 4); + read_len -= off & 0x3; + + rc = fsi_slave_read(slave, off, &data, read_len); + if (rc) + goto fail; + rc = copy_to_user(buf + total_len, &data, read_len); + if (rc) { + rc = -EFAULT; + goto fail; + } + off += read_len; + } + rc = count; + fail: + *offset = off; + return count; +} + +static ssize_t cfam_write(struct file *filep, const char __user *buf, + size_t count, loff_t *offset) +{ + struct fsi_slave *slave = filep->private_data; + size_t total_len, write_len; + loff_t off = *offset; + ssize_t rc; + + + if (off < 0) + return -EINVAL; + + if (off > 0xffffffff || count > 0xffffffff || off + count > 0xffffffff) + return -EINVAL; + + for (total_len = 0; total_len < count; total_len += write_len) { + __be32 data; + + write_len = min_t(size_t, count, 4); + write_len -= off & 0x3; + + rc = copy_from_user(&data, buf + total_len, write_len); + if (rc) { + rc = -EFAULT; + goto fail; + } + rc = fsi_slave_write(slave, off, &data, write_len); + if (rc) + goto fail; + off += write_len; + } + rc = count; + fail: + *offset = off; + return count; +} + +static loff_t cfam_llseek(struct file *file, loff_t offset, int whence) +{ + switch (whence) { + case SEEK_CUR: + break; + case SEEK_SET: + file->f_pos = offset; + break; + default: + return -EINVAL; + } + + return offset; +} + +static int cfam_open(struct inode *inode, struct file *file) +{ + struct fsi_slave *slave = container_of(inode->i_cdev, struct fsi_slave, cdev); + + file->private_data = slave; + + return 0; +} + +static const struct file_operations cfam_fops = { + .owner = THIS_MODULE, + .open = cfam_open, + .llseek = cfam_llseek, + .read = cfam_read, + .write = cfam_write, +}; + +static ssize_t send_term_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fsi_slave *slave = to_fsi_slave(dev); + struct fsi_master *master = slave->master; + + if (!master->term) + return -ENODEV; + + master->term(master, slave->link, slave->id); + return count; +} + +static DEVICE_ATTR_WO(send_term); + +static ssize_t slave_send_echo_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct fsi_slave *slave = to_fsi_slave(dev); + + return sprintf(buf, "%u\n", slave->t_send_delay); +} + +static ssize_t slave_send_echo_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct fsi_slave *slave = to_fsi_slave(dev); + struct fsi_master *master = slave->master; + unsigned long val; + int rc; + + if (kstrtoul(buf, 0, &val) < 0) + return -EINVAL; + + if (val < 1 || val > 16) + return -EINVAL; + + if (!master->link_config) + return -ENXIO; + + /* Current HW mandates that send and echo delay are identical */ + slave->t_send_delay = val; + slave->t_echo_delay = val; + + rc = fsi_slave_set_smode(slave); + if (rc < 0) + return rc; + if (master->link_config) + master->link_config(master, slave->link, + slave->t_send_delay, + slave->t_echo_delay); + + return count; +} + +static DEVICE_ATTR(send_echo_delays, 0600, + slave_send_echo_show, slave_send_echo_store); + +static ssize_t chip_id_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct fsi_slave *slave = to_fsi_slave(dev); + + return sprintf(buf, "%d\n", slave->chip_id); +} + +static DEVICE_ATTR_RO(chip_id); + +static ssize_t cfam_id_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct fsi_slave *slave = to_fsi_slave(dev); + + return sprintf(buf, "0x%x\n", slave->cfam_id); +} + +static DEVICE_ATTR_RO(cfam_id); + +static struct attribute *cfam_attr[] = { + &dev_attr_send_echo_delays.attr, + &dev_attr_chip_id.attr, + &dev_attr_cfam_id.attr, + &dev_attr_send_term.attr, + NULL, +}; + +static const struct attribute_group cfam_attr_group = { + .attrs = cfam_attr, +}; + +static const struct attribute_group *cfam_attr_groups[] = { + &cfam_attr_group, + NULL, +}; + +static char *cfam_devnode(struct device *dev, umode_t *mode, + kuid_t *uid, kgid_t *gid) +{ + struct fsi_slave *slave = to_fsi_slave(dev); + +#ifdef CONFIG_FSI_NEW_DEV_NODE + return kasprintf(GFP_KERNEL, "fsi/cfam%d", slave->cdev_idx); +#else + return kasprintf(GFP_KERNEL, "cfam%d", slave->cdev_idx); +#endif +} + +static const struct device_type cfam_type = { + .name = "cfam", + .devnode = cfam_devnode, + .groups = cfam_attr_groups +}; + +static char *fsi_cdev_devnode(struct device *dev, umode_t *mode, + kuid_t *uid, kgid_t *gid) +{ +#ifdef CONFIG_FSI_NEW_DEV_NODE + return kasprintf(GFP_KERNEL, "fsi/%s", dev_name(dev)); +#else + return kasprintf(GFP_KERNEL, "%s", dev_name(dev)); +#endif +} + +const struct device_type fsi_cdev_type = { + .name = "fsi-cdev", + .devnode = fsi_cdev_devnode, +}; +EXPORT_SYMBOL_GPL(fsi_cdev_type); + +/* Backward compatible /dev/ numbering in "old style" mode */ +static int fsi_adjust_index(int index) +{ +#ifdef CONFIG_FSI_NEW_DEV_NODE + return index; +#else + return index + 1; +#endif +} + +static int __fsi_get_new_minor(struct fsi_slave *slave, enum fsi_dev_type type, + dev_t *out_dev, int *out_index) +{ + int cid = slave->chip_id; + int id; + + /* Check if we qualify for legacy numbering */ + if (cid >= 0 && cid < 16 && type < 4) { + /* Try reserving the legacy number */ + id = (cid << 4) | type; + id = ida_simple_get(&fsi_minor_ida, id, id + 1, GFP_KERNEL); + if (id >= 0) { + *out_index = fsi_adjust_index(cid); + *out_dev = fsi_base_dev + id; + return 0; + } + /* Other failure */ + if (id != -ENOSPC) + return id; + /* Fallback to non-legacy allocation */ + } + id = ida_simple_get(&fsi_minor_ida, FSI_CHAR_LEGACY_TOP, + FSI_CHAR_MAX_DEVICES, GFP_KERNEL); + if (id < 0) + return id; + *out_index = fsi_adjust_index(id); + *out_dev = fsi_base_dev + id; + return 0; +} + +int fsi_get_new_minor(struct fsi_device *fdev, enum fsi_dev_type type, + dev_t *out_dev, int *out_index) +{ + return __fsi_get_new_minor(fdev->slave, type, out_dev, out_index); +} +EXPORT_SYMBOL_GPL(fsi_get_new_minor); + +void fsi_free_minor(dev_t dev) +{ + ida_simple_remove(&fsi_minor_ida, MINOR(dev)); +} +EXPORT_SYMBOL_GPL(fsi_free_minor); + static int fsi_slave_init(struct fsi_master *master, int link, uint8_t id) { - uint32_t chip_id, llmode; + uint32_t cfam_id; struct fsi_slave *slave; uint8_t crc; + __be32 data, llmode; int rc; /* Currently, we only support single slaves on a link, and use the @@ -672,31 +975,23 @@ static int fsi_slave_init(struct fsi_master *master, int link, uint8_t id) if (id != 0) return -EINVAL; - rc = fsi_master_read(master, link, id, 0, &chip_id, sizeof(chip_id)); + rc = fsi_master_read(master, link, id, 0, &data, sizeof(data)); if (rc) { dev_dbg(&master->dev, "can't read slave %02x:%02x %d\n", link, id, rc); return -ENODEV; } - chip_id = be32_to_cpu(chip_id); + cfam_id = be32_to_cpu(data); - crc = crc4(0, chip_id, 32); + crc = crc4(0, cfam_id, 32); if (crc) { - dev_warn(&master->dev, "slave %02x:%02x invalid chip id CRC!\n", + dev_warn(&master->dev, "slave %02x:%02x invalid cfam id CRC!\n", link, id); return -EIO; } dev_dbg(&master->dev, "fsi: found chip %08x at %02x:%02x:%02x\n", - chip_id, master->idx, link, id); - - rc = fsi_slave_set_smode(master, link, id); - if (rc) { - dev_warn(&master->dev, - "can't set smode on slave:%02x:%02x %d\n", - link, id, rc); - return -ENODEV; - } + cfam_id, master->idx, link, id); /* If we're behind a master that doesn't provide a self-running bus * clock, put the slave into async mode @@ -719,30 +1014,61 @@ static int fsi_slave_init(struct fsi_master *master, int link, uint8_t id) if (!slave) return -ENOMEM; - slave->master = master; + dev_set_name(&slave->dev, "slave@%02x:%02x", link, id); + slave->dev.type = &cfam_type; slave->dev.parent = &master->dev; slave->dev.of_node = fsi_slave_find_of_node(master, link, id); slave->dev.release = fsi_slave_release; + device_initialize(&slave->dev); + slave->cfam_id = cfam_id; + slave->master = master; slave->link = link; slave->id = id; slave->size = FSI_SLAVE_SIZE_23b; + slave->t_send_delay = 16; + slave->t_echo_delay = 16; + + /* Get chip ID if any */ + slave->chip_id = -1; + if (slave->dev.of_node) { + uint32_t prop; + if (!of_property_read_u32(slave->dev.of_node, "chip-id", &prop)) + slave->chip_id = prop; - dev_set_name(&slave->dev, "slave@%02x:%02x", link, id); - rc = device_register(&slave->dev); - if (rc < 0) { - dev_warn(&master->dev, "failed to create slave device: %d\n", - rc); - put_device(&slave->dev); - return rc; } + /* Allocate a minor in the FSI space */ + rc = __fsi_get_new_minor(slave, fsi_dev_cfam, &slave->dev.devt, + &slave->cdev_idx); + if (rc) + goto err_free; + + /* Create chardev for userspace access */ + cdev_init(&slave->cdev, &cfam_fops); + rc = cdev_device_add(&slave->cdev, &slave->dev); + if (rc) { + dev_err(&slave->dev, "Error %d creating slave device\n", rc); + goto err_free; + } + + rc = fsi_slave_set_smode(slave); + if (rc) { + dev_warn(&master->dev, + "can't set smode on slave:%02x:%02x %d\n", + link, id, rc); + kfree(slave); + return -ENODEV; + } + if (master->link_config) + master->link_config(master, link, + slave->t_send_delay, + slave->t_echo_delay); + + /* Legacy raw file -> to be removed */ rc = device_create_bin_file(&slave->dev, &fsi_slave_raw_attr); if (rc) dev_warn(&slave->dev, "failed to create raw attr: %d\n", rc); - rc = device_create_bin_file(&slave->dev, &fsi_slave_term_attr); - if (rc) - dev_warn(&slave->dev, "failed to create term attr: %d\n", rc); rc = fsi_slave_scan(slave); if (rc) @@ -750,6 +1076,10 @@ static int fsi_slave_init(struct fsi_master *master, int link, uint8_t id) rc); return rc; + + err_free: + put_device(&slave->dev); + return rc; } /* FSI master support */ @@ -814,12 +1144,16 @@ static int fsi_master_link_enable(struct fsi_master *master, int link) */ static int fsi_master_break(struct fsi_master *master, int link) { + int rc = 0; + trace_fsi_master_break(master, link); if (master->send_break) - return master->send_break(master, link); + rc = master->send_break(master, link); + if (master->link_config) + master->link_config(master, link, 16, 16); - return 0; + return rc; } static int fsi_master_scan(struct fsi_master *master) @@ -854,8 +1188,11 @@ static int fsi_slave_remove_device(struct device *dev, void *arg) static int fsi_master_remove_slave(struct device *dev, void *arg) { + struct fsi_slave *slave = to_fsi_slave(dev); + device_for_each_child(dev, NULL, fsi_slave_remove_device); - device_unregister(dev); + cdev_device_del(&slave->cdev, &slave->dev); + put_device(dev); return 0; } @@ -866,8 +1203,14 @@ static void fsi_master_unscan(struct fsi_master *master) int fsi_master_rescan(struct fsi_master *master) { + int rc; + + mutex_lock(&master->scan_lock); fsi_master_unscan(master); - return fsi_master_scan(master); + rc = fsi_master_scan(master); + mutex_unlock(&master->scan_lock); + + return rc; } EXPORT_SYMBOL_GPL(fsi_master_rescan); @@ -903,9 +1246,7 @@ int fsi_master_register(struct fsi_master *master) int rc; struct device_node *np; - if (!master) - return -EINVAL; - + mutex_init(&master->scan_lock); master->idx = ida_simple_get(&master_ida, 0, INT_MAX, GFP_KERNEL); dev_set_name(&master->dev, "fsi%d", master->idx); @@ -917,21 +1258,24 @@ int fsi_master_register(struct fsi_master *master) rc = device_create_file(&master->dev, &dev_attr_rescan); if (rc) { - device_unregister(&master->dev); + device_del(&master->dev); ida_simple_remove(&master_ida, master->idx); return rc; } rc = device_create_file(&master->dev, &dev_attr_break); if (rc) { - device_unregister(&master->dev); + device_del(&master->dev); ida_simple_remove(&master_ida, master->idx); return rc; } np = dev_of_node(&master->dev); - if (!of_property_read_bool(np, "no-scan-on-init")) + if (!of_property_read_bool(np, "no-scan-on-init")) { + mutex_lock(&master->scan_lock); fsi_master_scan(master); + mutex_unlock(&master->scan_lock); + } return 0; } @@ -944,7 +1288,9 @@ void fsi_master_unregister(struct fsi_master *master) master->idx = -1; } + mutex_lock(&master->scan_lock); fsi_master_unscan(master); + mutex_unlock(&master->scan_lock); device_unregister(&master->dev); } EXPORT_SYMBOL_GPL(fsi_master_unregister); @@ -996,13 +1342,27 @@ EXPORT_SYMBOL_GPL(fsi_bus_type); static int __init fsi_init(void) { - return bus_register(&fsi_bus_type); + int rc; + + rc = alloc_chrdev_region(&fsi_base_dev, 0, FSI_CHAR_MAX_DEVICES, "fsi"); + if (rc) + return rc; + rc = bus_register(&fsi_bus_type); + if (rc) + goto fail_bus; + return 0; + + fail_bus: + unregister_chrdev_region(fsi_base_dev, FSI_CHAR_MAX_DEVICES); + return rc; } postcore_initcall(fsi_init); static void fsi_exit(void) { bus_unregister(&fsi_bus_type); + unregister_chrdev_region(fsi_base_dev, FSI_CHAR_MAX_DEVICES); + ida_destroy(&fsi_minor_ida); } module_exit(fsi_exit); module_param(discard_errors, int, 0664); diff --git a/drivers/fsi/fsi-master-ast-cf.c b/drivers/fsi/fsi-master-ast-cf.c new file mode 100644 index 000000000000..04d10ea8d343 --- /dev/null +++ b/drivers/fsi/fsi-master-ast-cf.c @@ -0,0 +1,1440 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2018 IBM Corp +/* + * A FSI master controller, using a simple GPIO bit-banging interface + */ + +#include <linux/crc4.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/fsi.h> +#include <linux/gpio/consumer.h> +#include <linux/io.h> +#include <linux/irqflags.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/regmap.h> +#include <linux/firmware.h> +#include <linux/gpio/aspeed.h> +#include <linux/mfd/syscon.h> +#include <linux/of_address.h> +#include <linux/genalloc.h> + +#include "fsi-master.h" +#include "cf-fsi-fw.h" + +#define FW_FILE_NAME "cf-fsi-fw.bin" + +/* Common SCU based coprocessor control registers */ +#define SCU_COPRO_CTRL 0x100 +#define SCU_COPRO_RESET 0x00000002 +#define SCU_COPRO_CLK_EN 0x00000001 + +/* AST2500 specific ones */ +#define SCU_2500_COPRO_SEG0 0x104 +#define SCU_2500_COPRO_SEG1 0x108 +#define SCU_2500_COPRO_SEG2 0x10c +#define SCU_2500_COPRO_SEG3 0x110 +#define SCU_2500_COPRO_SEG4 0x114 +#define SCU_2500_COPRO_SEG5 0x118 +#define SCU_2500_COPRO_SEG6 0x11c +#define SCU_2500_COPRO_SEG7 0x120 +#define SCU_2500_COPRO_SEG8 0x124 +#define SCU_2500_COPRO_SEG_SWAP 0x00000001 +#define SCU_2500_COPRO_CACHE_CTL 0x128 +#define SCU_2500_COPRO_CACHE_EN 0x00000001 +#define SCU_2500_COPRO_SEG0_CACHE_EN 0x00000002 +#define SCU_2500_COPRO_SEG1_CACHE_EN 0x00000004 +#define SCU_2500_COPRO_SEG2_CACHE_EN 0x00000008 +#define SCU_2500_COPRO_SEG3_CACHE_EN 0x00000010 +#define SCU_2500_COPRO_SEG4_CACHE_EN 0x00000020 +#define SCU_2500_COPRO_SEG5_CACHE_EN 0x00000040 +#define SCU_2500_COPRO_SEG6_CACHE_EN 0x00000080 +#define SCU_2500_COPRO_SEG7_CACHE_EN 0x00000100 +#define SCU_2500_COPRO_SEG8_CACHE_EN 0x00000200 + +#define SCU_2400_COPRO_SEG0 0x104 +#define SCU_2400_COPRO_SEG2 0x108 +#define SCU_2400_COPRO_SEG4 0x10c +#define SCU_2400_COPRO_SEG6 0x110 +#define SCU_2400_COPRO_SEG8 0x114 +#define SCU_2400_COPRO_SEG_SWAP 0x80000000 +#define SCU_2400_COPRO_CACHE_CTL 0x118 +#define SCU_2400_COPRO_CACHE_EN 0x00000001 +#define SCU_2400_COPRO_SEG0_CACHE_EN 0x00000002 +#define SCU_2400_COPRO_SEG2_CACHE_EN 0x00000004 +#define SCU_2400_COPRO_SEG4_CACHE_EN 0x00000008 +#define SCU_2400_COPRO_SEG6_CACHE_EN 0x00000010 +#define SCU_2400_COPRO_SEG8_CACHE_EN 0x00000020 + +/* CVIC registers */ +#define CVIC_EN_REG 0x10 +#define CVIC_TRIG_REG 0x18 + +/* + * System register base address (needed for configuring the + * coldfire maps) + */ +#define SYSREG_BASE 0x1e600000 + +/* Amount of SRAM required */ +#define SRAM_SIZE 0x1000 + +#define LAST_ADDR_INVALID 0x1 + +struct fsi_master_acf { + struct fsi_master master; + struct device *dev; + struct regmap *scu; + struct mutex lock; /* mutex for command ordering */ + struct gpio_desc *gpio_clk; + struct gpio_desc *gpio_data; + struct gpio_desc *gpio_trans; /* Voltage translator */ + struct gpio_desc *gpio_enable; /* FSI enable */ + struct gpio_desc *gpio_mux; /* Mux control */ + uint16_t gpio_clk_vreg; + uint16_t gpio_clk_dreg; + uint16_t gpio_dat_vreg; + uint16_t gpio_dat_dreg; + uint16_t gpio_tra_vreg; + uint16_t gpio_tra_dreg; + uint8_t gpio_clk_bit; + uint8_t gpio_dat_bit; + uint8_t gpio_tra_bit; + uint32_t cf_mem_addr; + size_t cf_mem_size; + void __iomem *cf_mem; + void __iomem *cvic; + struct gen_pool *sram_pool; + void __iomem *sram; + bool is_ast2500; + bool external_mode; + bool trace_enabled; + uint32_t last_addr; + uint8_t t_send_delay; + uint8_t t_echo_delay; + uint32_t cvic_sw_irq; +}; +#define to_fsi_master_acf(m) container_of(m, struct fsi_master_acf, master) + +struct fsi_msg { + uint64_t msg; + uint8_t bits; +}; + +#define CREATE_TRACE_POINTS +#include <trace/events/fsi_master_ast_cf.h> + +static void msg_push_bits(struct fsi_msg *msg, uint64_t data, int bits) +{ + msg->msg <<= bits; + msg->msg |= data & ((1ull << bits) - 1); + msg->bits += bits; +} + +static void msg_push_crc(struct fsi_msg *msg) +{ + uint8_t crc; + int top; + + top = msg->bits & 0x3; + + /* start bit, and any non-aligned top bits */ + crc = crc4(0, 1 << top | msg->msg >> (msg->bits - top), top + 1); + + /* aligned bits */ + crc = crc4(crc, msg->msg, msg->bits - top); + + msg_push_bits(msg, crc, 4); +} + +static void msg_finish_cmd(struct fsi_msg *cmd) +{ + /* Left align message */ + cmd->msg <<= (64 - cmd->bits); +} + +static bool check_same_address(struct fsi_master_acf *master, int id, + uint32_t addr) +{ + /* this will also handle LAST_ADDR_INVALID */ + return master->last_addr == (((id & 0x3) << 21) | (addr & ~0x3)); +} + +static bool check_relative_address(struct fsi_master_acf *master, int id, + uint32_t addr, uint32_t *rel_addrp) +{ + uint32_t last_addr = master->last_addr; + int32_t rel_addr; + + if (last_addr == LAST_ADDR_INVALID) + return false; + + /* We may be in 23-bit addressing mode, which uses the id as the + * top two address bits. So, if we're referencing a different ID, + * use absolute addresses. + */ + if (((last_addr >> 21) & 0x3) != id) + return false; + + /* remove the top two bits from any 23-bit addressing */ + last_addr &= (1 << 21) - 1; + + /* We know that the addresses are limited to 21 bits, so this won't + * overflow the signed rel_addr */ + rel_addr = addr - last_addr; + if (rel_addr > 255 || rel_addr < -256) + return false; + + *rel_addrp = (uint32_t)rel_addr; + + return true; +} + +static void last_address_update(struct fsi_master_acf *master, + int id, bool valid, uint32_t addr) +{ + if (!valid) + master->last_addr = LAST_ADDR_INVALID; + else + master->last_addr = ((id & 0x3) << 21) | (addr & ~0x3); +} + +/* + * Encode an Absolute/Relative/Same Address command + */ +static void build_ar_command(struct fsi_master_acf *master, + struct fsi_msg *cmd, uint8_t id, + uint32_t addr, size_t size, + const void *data) +{ + int i, addr_bits, opcode_bits; + bool write = !!data; + uint8_t ds, opcode; + uint32_t rel_addr; + + cmd->bits = 0; + cmd->msg = 0; + + /* we have 21 bits of address max */ + addr &= ((1 << 21) - 1); + + /* cmd opcodes are variable length - SAME_AR is only two bits */ + opcode_bits = 3; + + if (check_same_address(master, id, addr)) { + /* we still address the byte offset within the word */ + addr_bits = 2; + opcode_bits = 2; + opcode = FSI_CMD_SAME_AR; + trace_fsi_master_acf_cmd_same_addr(master); + + } else if (check_relative_address(master, id, addr, &rel_addr)) { + /* 8 bits plus sign */ + addr_bits = 9; + addr = rel_addr; + opcode = FSI_CMD_REL_AR; + trace_fsi_master_acf_cmd_rel_addr(master, rel_addr); + + } else { + addr_bits = 21; + opcode = FSI_CMD_ABS_AR; + trace_fsi_master_acf_cmd_abs_addr(master, addr); + } + + /* + * The read/write size is encoded in the lower bits of the address + * (as it must be naturally-aligned), and the following ds bit. + * + * size addr:1 addr:0 ds + * 1 x x 0 + * 2 x 0 1 + * 4 0 1 1 + * + */ + ds = size > 1 ? 1 : 0; + addr &= ~(size - 1); + if (size == 4) + addr |= 1; + + msg_push_bits(cmd, id, 2); + msg_push_bits(cmd, opcode, opcode_bits); + msg_push_bits(cmd, write ? 0 : 1, 1); + msg_push_bits(cmd, addr, addr_bits); + msg_push_bits(cmd, ds, 1); + for (i = 0; write && i < size; i++) + msg_push_bits(cmd, ((uint8_t *)data)[i], 8); + + msg_push_crc(cmd); + msg_finish_cmd(cmd); +} + +static void build_dpoll_command(struct fsi_msg *cmd, uint8_t slave_id) +{ + cmd->bits = 0; + cmd->msg = 0; + + msg_push_bits(cmd, slave_id, 2); + msg_push_bits(cmd, FSI_CMD_DPOLL, 3); + msg_push_crc(cmd); + msg_finish_cmd(cmd); +} + +static void build_epoll_command(struct fsi_msg *cmd, uint8_t slave_id) +{ + cmd->bits = 0; + cmd->msg = 0; + + msg_push_bits(cmd, slave_id, 2); + msg_push_bits(cmd, FSI_CMD_EPOLL, 3); + msg_push_crc(cmd); + msg_finish_cmd(cmd); +} + +static void build_term_command(struct fsi_msg *cmd, uint8_t slave_id) +{ + cmd->bits = 0; + cmd->msg = 0; + + msg_push_bits(cmd, slave_id, 2); + msg_push_bits(cmd, FSI_CMD_TERM, 6); + msg_push_crc(cmd); + msg_finish_cmd(cmd); +} + +static int do_copro_command(struct fsi_master_acf *master, uint32_t op) +{ + uint32_t timeout = 10000000; + uint8_t stat; + + trace_fsi_master_acf_copro_command(master, op); + + /* Send command */ + iowrite32be(op, master->sram + CMD_STAT_REG); + + /* Ring doorbell if any */ + if (master->cvic) + iowrite32(0x2, master->cvic + CVIC_TRIG_REG); + + /* Wait for status to indicate completion (or error) */ + do { + if (timeout-- == 0) { + dev_warn(master->dev, + "Timeout waiting for coprocessor completion\n"); + return -ETIMEDOUT; + } + stat = ioread8(master->sram + CMD_STAT_REG); + } while(stat < STAT_COMPLETE || stat == 0xff); + + if (stat == STAT_COMPLETE) + return 0; + switch(stat) { + case STAT_ERR_INVAL_CMD: + return -EINVAL; + case STAT_ERR_INVAL_IRQ: + return -EIO; + case STAT_ERR_MTOE: + return -ESHUTDOWN; + } + return -ENXIO; +} + +static int clock_zeros(struct fsi_master_acf *master, int count) +{ + while (count) { + int rc, lcnt = min(count, 255); + + rc = do_copro_command(master, + CMD_IDLE_CLOCKS | (lcnt << CMD_REG_CLEN_SHIFT)); + if (rc) + return rc; + count -= lcnt; + } + return 0; +} + +static int send_request(struct fsi_master_acf *master, struct fsi_msg *cmd, + unsigned int resp_bits) +{ + uint32_t op; + + trace_fsi_master_acf_send_request(master, cmd, resp_bits); + + /* Store message into SRAM */ + iowrite32be((cmd->msg >> 32), master->sram + CMD_DATA); + iowrite32be((cmd->msg & 0xffffffff), master->sram + CMD_DATA + 4); + + op = CMD_COMMAND; + op |= cmd->bits << CMD_REG_CLEN_SHIFT; + if (resp_bits) + op |= resp_bits << CMD_REG_RLEN_SHIFT; + + return do_copro_command(master, op); +} + +static int read_copro_response(struct fsi_master_acf *master, uint8_t size, + uint32_t *response, u8 *tag) +{ + uint8_t rtag = ioread8(master->sram + STAT_RTAG) & 0xf; + uint8_t rcrc = ioread8(master->sram + STAT_RCRC) & 0xf; + uint32_t rdata = 0; + uint32_t crc; + uint8_t ack; + + *tag = ack = rtag & 3; + + /* we have a whole message now; check CRC */ + crc = crc4(0, 1, 1); + crc = crc4(crc, rtag, 4); + if (ack == FSI_RESP_ACK && size) { + rdata = ioread32be(master->sram + RSP_DATA); + crc = crc4(crc, rdata, size); + if (response) + *response = rdata; + } + crc = crc4(crc, rcrc, 4); + + trace_fsi_master_acf_copro_response(master, rtag, rcrc, rdata, crc == 0); + + if (crc) { + /* + * Check if it's all 1's or all 0's, that probably means + * the host is off + */ + if ((rtag == 0xf && rcrc == 0xf) || (rtag == 0 && rcrc == 0)) + return -ENODEV; + dev_dbg(master->dev, "Bad response CRC !\n"); + return -EAGAIN; + } + return 0; +} + +static int send_term(struct fsi_master_acf *master, uint8_t slave) +{ + struct fsi_msg cmd; + uint8_t tag; + int rc; + + build_term_command(&cmd, slave); + + rc = send_request(master, &cmd, 0); + if (rc) { + dev_warn(master->dev, "Error %d sending term\n", rc); + return rc; + } + + rc = read_copro_response(master, 0, NULL, &tag); + if (rc < 0) { + dev_err(master->dev, + "TERM failed; lost communication with slave\n"); + return -EIO; + } else if (tag != FSI_RESP_ACK) { + dev_err(master->dev, "TERM failed; response %d\n", tag); + return -EIO; + } + return 0; +} + +static void dump_ucode_trace(struct fsi_master_acf *master) +{ + char trbuf[52]; + char *p; + int i; + + dev_dbg(master->dev, + "CMDSTAT:%08x RTAG=%02x RCRC=%02x RDATA=%02x #INT=%08x\n", + ioread32be(master->sram + CMD_STAT_REG), + ioread8(master->sram + STAT_RTAG), + ioread8(master->sram + STAT_RCRC), + ioread32be(master->sram + RSP_DATA), + ioread32be(master->sram + INT_CNT)); + + for (i = 0; i < 512; i++) { + uint8_t v; + if ((i % 16) == 0) + p = trbuf; + v = ioread8(master->sram + TRACEBUF + i); + p += sprintf(p, "%02x ", v); + if (((i % 16) == 15) || v == TR_END) + dev_dbg(master->dev, "%s\n", trbuf); + if (v == TR_END) + break; + } +} + +static int handle_response(struct fsi_master_acf *master, + uint8_t slave, uint8_t size, void *data) +{ + int busy_count = 0, rc; + int crc_err_retries = 0; + struct fsi_msg cmd; + uint32_t response; + uint8_t tag; +retry: + rc = read_copro_response(master, size, &response, &tag); + + /* Handle retries on CRC errors */ + if (rc == -EAGAIN) { + /* Too many retries ? */ + if (crc_err_retries++ > FSI_CRC_ERR_RETRIES) { + /* + * Pass it up as a -EIO otherwise upper level will retry + * the whole command which isn't what we want here. + */ + rc = -EIO; + goto bail; + } + trace_fsi_master_acf_crc_rsp_error(master, crc_err_retries); + if (master->trace_enabled) + dump_ucode_trace(master); + rc = clock_zeros(master, FSI_MASTER_EPOLL_CLOCKS); + if (rc) { + dev_warn(master->dev, + "Error %d clocking zeros for E_POLL\n", rc); + return rc; + } + build_epoll_command(&cmd, slave); + rc = send_request(master, &cmd, size); + if (rc) { + dev_warn(master->dev, "Error %d sending E_POLL\n", rc); + return -EIO; + } + goto retry; + } + if (rc) + return rc; + + switch (tag) { + case FSI_RESP_ACK: + if (size && data) { + if (size == 32) + *(__be32 *)data = cpu_to_be32(response); + else if (size == 16) + *(__be16 *)data = cpu_to_be16(response); + else + *(u8 *)data = response; + } + break; + case FSI_RESP_BUSY: + /* + * Its necessary to clock slave before issuing + * d-poll, not indicated in the hardware protocol + * spec. < 20 clocks causes slave to hang, 21 ok. + */ + dev_dbg(master->dev, "Busy, retrying...\n"); + if (master->trace_enabled) + dump_ucode_trace(master); + rc = clock_zeros(master, FSI_MASTER_DPOLL_CLOCKS); + if (rc) { + dev_warn(master->dev, + "Error %d clocking zeros for D_POLL\n", rc); + break; + } + if (busy_count++ < FSI_MASTER_MAX_BUSY) { + build_dpoll_command(&cmd, slave); + rc = send_request(master, &cmd, size); + if (rc) { + dev_warn(master->dev, "Error %d sending D_POLL\n", rc); + break; + } + goto retry; + } + dev_dbg(master->dev, + "ERR slave is stuck in busy state, issuing TERM\n"); + send_term(master, slave); + rc = -EIO; + break; + + case FSI_RESP_ERRA: + dev_dbg(master->dev, "ERRA received\n"); + if (master->trace_enabled) + dump_ucode_trace(master); + rc = -EIO; + break; + case FSI_RESP_ERRC: + dev_dbg(master->dev, "ERRC received\n"); + if (master->trace_enabled) + dump_ucode_trace(master); + rc = -EAGAIN; + break; + } + bail: + if (busy_count > 0) { + trace_fsi_master_acf_poll_response_busy(master, busy_count); + } + + return rc; +} + +static int fsi_master_acf_xfer(struct fsi_master_acf *master, uint8_t slave, + struct fsi_msg *cmd, size_t resp_len, void *resp) +{ + int rc = -EAGAIN, retries = 0; + + resp_len <<= 3; + while ((retries++) < FSI_CRC_ERR_RETRIES) { + rc = send_request(master, cmd, resp_len); + if (rc) { + if (rc != -ESHUTDOWN) + dev_warn(master->dev, "Error %d sending command\n", rc); + break; + } + rc = handle_response(master, slave, resp_len, resp); + if (rc != -EAGAIN) + break; + rc = -EIO; + dev_dbg(master->dev, "ECRC retry %d\n", retries); + + /* Pace it a bit before retry */ + msleep(1); + } + + return rc; +} + +static int fsi_master_acf_read(struct fsi_master *_master, int link, + uint8_t id, uint32_t addr, void *val, + size_t size) +{ + struct fsi_master_acf *master = to_fsi_master_acf(_master); + struct fsi_msg cmd; + int rc; + + if (link != 0) + return -ENODEV; + + mutex_lock(&master->lock); + dev_dbg(master->dev, "read id %d addr %x size %zd\n", id, addr, size); + build_ar_command(master, &cmd, id, addr, size, NULL); + rc = fsi_master_acf_xfer(master, id, &cmd, size, val); + last_address_update(master, id, rc == 0, addr); + if (rc) + dev_dbg(master->dev, "read id %d addr 0x%08x err: %d\n", + id, addr, rc); + mutex_unlock(&master->lock); + + return rc; +} + +static int fsi_master_acf_write(struct fsi_master *_master, int link, + uint8_t id, uint32_t addr, const void *val, + size_t size) +{ + struct fsi_master_acf *master = to_fsi_master_acf(_master); + struct fsi_msg cmd; + int rc; + + if (link != 0) + return -ENODEV; + + mutex_lock(&master->lock); + build_ar_command(master, &cmd, id, addr, size, val); + dev_dbg(master->dev, "write id %d addr %x size %zd raw_data: %08x\n", + id, addr, size, *(uint32_t *)val); + rc = fsi_master_acf_xfer(master, id, &cmd, 0, NULL); + last_address_update(master, id, rc == 0, addr); + if (rc) + dev_dbg(master->dev, "write id %d addr 0x%08x err: %d\n", + id, addr, rc); + mutex_unlock(&master->lock); + + return rc; +} + +static int fsi_master_acf_term(struct fsi_master *_master, + int link, uint8_t id) +{ + struct fsi_master_acf *master = to_fsi_master_acf(_master); + struct fsi_msg cmd; + int rc; + + if (link != 0) + return -ENODEV; + + mutex_lock(&master->lock); + build_term_command(&cmd, id); + dev_dbg(master->dev, "term id %d\n", id); + rc = fsi_master_acf_xfer(master, id, &cmd, 0, NULL); + last_address_update(master, id, false, 0); + mutex_unlock(&master->lock); + + return rc; +} + +static int fsi_master_acf_break(struct fsi_master *_master, int link) +{ + struct fsi_master_acf *master = to_fsi_master_acf(_master); + int rc; + + if (link != 0) + return -ENODEV; + + mutex_lock(&master->lock); + if (master->external_mode) { + mutex_unlock(&master->lock); + return -EBUSY; + } + dev_dbg(master->dev, "sending BREAK\n"); + rc = do_copro_command(master, CMD_BREAK); + last_address_update(master, 0, false, 0); + mutex_unlock(&master->lock); + + /* Wait for logic reset to take effect */ + udelay(200); + + return rc; +} + +static void reset_cf(struct fsi_master_acf *master) +{ + regmap_write(master->scu, SCU_COPRO_CTRL, SCU_COPRO_RESET); + usleep_range(20,20); + regmap_write(master->scu, SCU_COPRO_CTRL, 0); + usleep_range(20,20); +} + +static void start_cf(struct fsi_master_acf *master) +{ + regmap_write(master->scu, SCU_COPRO_CTRL, SCU_COPRO_CLK_EN); +} + +static void setup_ast2500_cf_maps(struct fsi_master_acf *master) +{ + /* + * Note about byteswap setting: the bus is wired backwards, + * so setting the byteswap bit actually makes the ColdFire + * work "normally" for a BE processor, ie, put the MSB in + * the lowest address byte. + * + * We thus need to set the bit for our main memory which + * contains our program code. We create two mappings for + * the register, one with each setting. + * + * Segments 2 and 3 has a "swapped" mapping (BE) + * and 6 and 7 have a non-swapped mapping (LE) which allows + * us to avoid byteswapping register accesses since the + * registers are all LE. + */ + + /* Setup segment 0 to our memory region */ + regmap_write(master->scu, SCU_2500_COPRO_SEG0, master->cf_mem_addr | + SCU_2500_COPRO_SEG_SWAP); + + /* Segments 2 and 3 to sysregs with byteswap (for SRAM) */ + regmap_write(master->scu, SCU_2500_COPRO_SEG2, SYSREG_BASE | + SCU_2500_COPRO_SEG_SWAP); + regmap_write(master->scu, SCU_2500_COPRO_SEG3, SYSREG_BASE | 0x100000 | + SCU_2500_COPRO_SEG_SWAP); + + /* And segment 6 and 7 to sysregs no byteswap */ + regmap_write(master->scu, SCU_2500_COPRO_SEG6, SYSREG_BASE); + regmap_write(master->scu, SCU_2500_COPRO_SEG7, SYSREG_BASE | 0x100000); + + /* Memory cachable, regs and SRAM not cachable */ + regmap_write(master->scu, SCU_2500_COPRO_CACHE_CTL, + SCU_2500_COPRO_SEG0_CACHE_EN | SCU_2500_COPRO_CACHE_EN); +} + +static void setup_ast2400_cf_maps(struct fsi_master_acf *master) +{ + /* Setup segment 0 to our memory region */ + regmap_write(master->scu, SCU_2400_COPRO_SEG0, master->cf_mem_addr | + SCU_2400_COPRO_SEG_SWAP); + + /* Segments 2 to sysregs with byteswap (for SRAM) */ + regmap_write(master->scu, SCU_2400_COPRO_SEG2, SYSREG_BASE | + SCU_2400_COPRO_SEG_SWAP); + + /* And segment 6 to sysregs no byteswap */ + regmap_write(master->scu, SCU_2400_COPRO_SEG6, SYSREG_BASE); + + /* Memory cachable, regs and SRAM not cachable */ + regmap_write(master->scu, SCU_2400_COPRO_CACHE_CTL, + SCU_2400_COPRO_SEG0_CACHE_EN | SCU_2400_COPRO_CACHE_EN); +} + +static void setup_common_fw_config(struct fsi_master_acf *master, + void __iomem *base) +{ + iowrite16be(master->gpio_clk_vreg, base + HDR_CLOCK_GPIO_VADDR); + iowrite16be(master->gpio_clk_dreg, base + HDR_CLOCK_GPIO_DADDR); + iowrite16be(master->gpio_dat_vreg, base + HDR_DATA_GPIO_VADDR); + iowrite16be(master->gpio_dat_dreg, base + HDR_DATA_GPIO_DADDR); + iowrite16be(master->gpio_tra_vreg, base + HDR_TRANS_GPIO_VADDR); + iowrite16be(master->gpio_tra_dreg, base + HDR_TRANS_GPIO_DADDR); + iowrite8(master->gpio_clk_bit, base + HDR_CLOCK_GPIO_BIT); + iowrite8(master->gpio_dat_bit, base + HDR_DATA_GPIO_BIT); + iowrite8(master->gpio_tra_bit, base + HDR_TRANS_GPIO_BIT); +} + +static void setup_ast2500_fw_config(struct fsi_master_acf *master) +{ + void __iomem *base = master->cf_mem + HDR_OFFSET; + + setup_common_fw_config(master, base); + iowrite32be(FW_CONTROL_USE_STOP, base + HDR_FW_CONTROL); +} + +static void setup_ast2400_fw_config(struct fsi_master_acf *master) +{ + void __iomem *base = master->cf_mem + HDR_OFFSET; + + setup_common_fw_config(master, base); + iowrite32be(FW_CONTROL_CONT_CLOCK|FW_CONTROL_DUMMY_RD, base + HDR_FW_CONTROL); +} + +static int setup_gpios_for_copro(struct fsi_master_acf *master) +{ + + int rc; + + /* This aren't under ColdFire control, just set them up appropriately */ + gpiod_direction_output(master->gpio_mux, 1); + gpiod_direction_output(master->gpio_enable, 1); + + /* Those are under ColdFire control, let it configure them */ + rc = aspeed_gpio_copro_grab_gpio(master->gpio_clk, &master->gpio_clk_vreg, + &master->gpio_clk_dreg, &master->gpio_clk_bit); + if (rc) { + dev_err(master->dev, "failed to assign clock gpio to coprocessor\n"); + return rc; + } + rc = aspeed_gpio_copro_grab_gpio(master->gpio_data, &master->gpio_dat_vreg, + &master->gpio_dat_dreg, &master->gpio_dat_bit); + if (rc) { + dev_err(master->dev, "failed to assign data gpio to coprocessor\n"); + aspeed_gpio_copro_release_gpio(master->gpio_clk); + return rc; + } + rc = aspeed_gpio_copro_grab_gpio(master->gpio_trans, &master->gpio_tra_vreg, + &master->gpio_tra_dreg, &master->gpio_tra_bit); + if (rc) { + dev_err(master->dev, "failed to assign trans gpio to coprocessor\n"); + aspeed_gpio_copro_release_gpio(master->gpio_clk); + aspeed_gpio_copro_release_gpio(master->gpio_data); + return rc; + } + return 0; +} + +static void release_copro_gpios(struct fsi_master_acf *master) +{ + aspeed_gpio_copro_release_gpio(master->gpio_clk); + aspeed_gpio_copro_release_gpio(master->gpio_data); + aspeed_gpio_copro_release_gpio(master->gpio_trans); +} + +static int load_copro_firmware(struct fsi_master_acf *master) +{ + const struct firmware *fw; + uint16_t sig = 0, wanted_sig; + const u8 *data; + size_t size = 0; + int rc; + + /* Get the binary */ + rc = request_firmware(&fw, FW_FILE_NAME, master->dev); + if (rc) { + dev_err( + master->dev, "Error %d to load firwmare '%s' !\n", + rc, FW_FILE_NAME); + return rc; + } + + /* Which image do we want ? (shared vs. split clock/data GPIOs) */ + if (master->gpio_clk_vreg == master->gpio_dat_vreg) + wanted_sig = SYS_SIG_SHARED; + else + wanted_sig = SYS_SIG_SPLIT; + dev_dbg(master->dev, "Looking for image sig %04x\n", wanted_sig); + + /* Try to find it */ + for (data = fw->data; data < (fw->data + fw->size);) { + sig = be16_to_cpup((__be16 *)(data + HDR_OFFSET + HDR_SYS_SIG)); + size = be32_to_cpup((__be32 *)(data + HDR_OFFSET + HDR_FW_SIZE)); + if (sig == wanted_sig) + break; + data += size; + } + if (sig != wanted_sig) { + dev_err(master->dev, "Failed to locate image sig %04x in FW blob\n", + wanted_sig); + rc = -ENODEV; + goto release_fw; + } + if (size > master->cf_mem_size) { + dev_err(master->dev, "FW size (%zd) bigger than memory reserve (%zd)\n", + fw->size, master->cf_mem_size); + rc = -ENOMEM; + } else { + memcpy_toio(master->cf_mem, data, size); + } + +release_fw: + release_firmware(fw); + return rc; +} + +static int check_firmware_image(struct fsi_master_acf *master) +{ + uint32_t fw_vers, fw_api, fw_options; + + fw_vers = ioread16be(master->cf_mem + HDR_OFFSET + HDR_FW_VERS); + fw_api = ioread16be(master->cf_mem + HDR_OFFSET + HDR_API_VERS); + fw_options = ioread32be(master->cf_mem + HDR_OFFSET + HDR_FW_OPTIONS); + master->trace_enabled = !!(fw_options & FW_OPTION_TRACE_EN); + + /* Check version and signature */ + dev_info(master->dev, "ColdFire initialized, firmware v%d API v%d.%d (trace %s)\n", + fw_vers, fw_api >> 8, fw_api & 0xff, + master->trace_enabled ? "enabled" : "disabled"); + + if ((fw_api >> 8) != API_VERSION_MAJ) { + dev_err(master->dev, "Unsupported coprocessor API version !\n"); + return -ENODEV; + } + + return 0; +} + +static int copro_enable_sw_irq(struct fsi_master_acf *master) +{ + int timeout; + uint32_t val; + + /* + * Enable coprocessor interrupt input. I've had problems getting the + * value to stick, so try in a loop + */ + for (timeout = 0; timeout < 10; timeout++) { + iowrite32(0x2, master->cvic + CVIC_EN_REG); + val = ioread32(master->cvic + CVIC_EN_REG); + if (val & 2) + break; + msleep(1); + } + if (!(val & 2)) { + dev_err(master->dev, "Failed to enable coprocessor interrupt !\n"); + return -ENODEV; + } + return 0; +} + +static int fsi_master_acf_setup(struct fsi_master_acf *master) +{ + int timeout, rc; + uint32_t val; + + /* Make sure the ColdFire is stopped */ + reset_cf(master); + + /* + * Clear SRAM. This needs to happen before we setup the GPIOs + * as we might start trying to arbitrate as soon as that happens. + */ + memset_io(master->sram, 0, SRAM_SIZE); + + /* Configure GPIOs */ + rc = setup_gpios_for_copro(master); + if (rc) + return rc; + + /* Load the firmware into the reserved memory */ + rc = load_copro_firmware(master); + if (rc) + return rc; + + /* Read signature and check versions */ + rc = check_firmware_image(master); + if (rc) + return rc; + + /* Setup coldfire memory map */ + if (master->is_ast2500) { + setup_ast2500_cf_maps(master); + setup_ast2500_fw_config(master); + } else { + setup_ast2400_cf_maps(master); + setup_ast2400_fw_config(master); + } + + /* Start the ColdFire */ + start_cf(master); + + /* Wait for status register to indicate command completion + * which signals the initialization is complete + */ + for (timeout = 0; timeout < 10; timeout++) { + val = ioread8(master->sram + CF_STARTED); + if (val) + break; + msleep(1); + } + if (!val) { + dev_err(master->dev, "Coprocessor startup timeout !\n"); + rc = -ENODEV; + goto err; + } + + /* Configure echo & send delay */ + iowrite8(master->t_send_delay, master->sram + SEND_DLY_REG); + iowrite8(master->t_echo_delay, master->sram + ECHO_DLY_REG); + + /* Enable SW interrupt to copro if any */ + if (master->cvic) { + rc = copro_enable_sw_irq(master); + if (rc) + goto err; + } + return 0; + err: + /* An error occurred, don't leave the coprocessor running */ + reset_cf(master); + + /* Release the GPIOs */ + release_copro_gpios(master); + + return rc; +} + + +static void fsi_master_acf_terminate(struct fsi_master_acf *master) +{ + unsigned long flags; + + /* + * A GPIO arbitration requestion could come in while this is + * happening. To avoid problems, we disable interrupts so it + * cannot preempt us on this CPU + */ + + local_irq_save(flags); + + /* Stop the coprocessor */ + reset_cf(master); + + /* We mark the copro not-started */ + iowrite32(0, master->sram + CF_STARTED); + + /* We mark the ARB register as having given up arbitration to + * deal with a potential race with the arbitration request + */ + iowrite8(ARB_ARM_ACK, master->sram + ARB_REG); + + local_irq_restore(flags); + + /* Return the GPIOs to the ARM */ + release_copro_gpios(master); +} + +static void fsi_master_acf_setup_external(struct fsi_master_acf *master) +{ + /* Setup GPIOs for external FSI master (FSP box) */ + gpiod_direction_output(master->gpio_mux, 0); + gpiod_direction_output(master->gpio_trans, 0); + gpiod_direction_output(master->gpio_enable, 1); + gpiod_direction_input(master->gpio_clk); + gpiod_direction_input(master->gpio_data); +} + +static int fsi_master_acf_link_enable(struct fsi_master *_master, int link) +{ + struct fsi_master_acf *master = to_fsi_master_acf(_master); + int rc = -EBUSY; + + if (link != 0) + return -ENODEV; + + mutex_lock(&master->lock); + if (!master->external_mode) { + gpiod_set_value(master->gpio_enable, 1); + rc = 0; + } + mutex_unlock(&master->lock); + + return rc; +} + +static int fsi_master_acf_link_config(struct fsi_master *_master, int link, + u8 t_send_delay, u8 t_echo_delay) +{ + struct fsi_master_acf *master = to_fsi_master_acf(_master); + + if (link != 0) + return -ENODEV; + + mutex_lock(&master->lock); + master->t_send_delay = t_send_delay; + master->t_echo_delay = t_echo_delay; + dev_dbg(master->dev, "Changing delays: send=%d echo=%d\n", + t_send_delay, t_echo_delay); + iowrite8(master->t_send_delay, master->sram + SEND_DLY_REG); + iowrite8(master->t_echo_delay, master->sram + ECHO_DLY_REG); + mutex_unlock(&master->lock); + + return 0; +} + +static ssize_t external_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fsi_master_acf *master = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE - 1, "%u\n", + master->external_mode ? 1 : 0); +} + +static ssize_t external_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct fsi_master_acf *master = dev_get_drvdata(dev); + unsigned long val; + bool external_mode; + int err; + + err = kstrtoul(buf, 0, &val); + if (err) + return err; + + external_mode = !!val; + + mutex_lock(&master->lock); + + if (external_mode == master->external_mode) { + mutex_unlock(&master->lock); + return count; + } + + master->external_mode = external_mode; + if (master->external_mode) { + fsi_master_acf_terminate(master); + fsi_master_acf_setup_external(master); + } else + fsi_master_acf_setup(master); + + mutex_unlock(&master->lock); + + fsi_master_rescan(&master->master); + + return count; +} + +static DEVICE_ATTR(external_mode, 0664, + external_mode_show, external_mode_store); + +static int fsi_master_acf_gpio_request(void *data) +{ + struct fsi_master_acf *master = data; + int timeout; + u8 val; + + /* Note: This doesn't require holding out mutex */ + + /* Write reqest */ + iowrite8(ARB_ARM_REQ, master->sram + ARB_REG); + + /* + * There is a race (which does happen at boot time) when we get an + * arbitration request as we are either about to or just starting + * the coprocessor. + * + * To handle it, we first check if we are running. If not yet we + * check whether the copro is started in the SCU. + * + * If it's not started, we can basically just assume we have arbitration + * and return. Otherwise, we wait normally expecting for the arbitration + * to eventually complete. + */ + if (ioread32(master->sram + CF_STARTED) == 0) { + unsigned int reg = 0; + + regmap_read(master->scu, SCU_COPRO_CTRL, ®); + if (!(reg & SCU_COPRO_CLK_EN)) + return 0; + } + + /* Ring doorbell if any */ + if (master->cvic) + iowrite32(0x2, master->cvic + CVIC_TRIG_REG); + + for (timeout = 0; timeout < 10000; timeout++) { + val = ioread8(master->sram + ARB_REG); + if (val != ARB_ARM_REQ) + break; + udelay(1); + } + + /* If it failed, override anyway */ + if (val != ARB_ARM_ACK) + dev_warn(master->dev, "GPIO request arbitration timeout\n"); + + return 0; +} + +static int fsi_master_acf_gpio_release(void *data) +{ + struct fsi_master_acf *master = data; + + /* Write release */ + iowrite8(0, master->sram + ARB_REG); + + /* Ring doorbell if any */ + if (master->cvic) + iowrite32(0x2, master->cvic + CVIC_TRIG_REG); + + return 0; +} + +static void fsi_master_acf_release(struct device *dev) +{ + struct fsi_master_acf *master = to_fsi_master_acf(dev_to_fsi_master(dev)); + + /* Cleanup, stop coprocessor */ + mutex_lock(&master->lock); + fsi_master_acf_terminate(master); + aspeed_gpio_copro_set_ops(NULL, NULL); + mutex_unlock(&master->lock); + + /* Free resources */ + gen_pool_free(master->sram_pool, (unsigned long)master->sram, SRAM_SIZE); + of_node_put(dev_of_node(master->dev)); + + kfree(master); +} + +static const struct aspeed_gpio_copro_ops fsi_master_acf_gpio_ops = { + .request_access = fsi_master_acf_gpio_request, + .release_access = fsi_master_acf_gpio_release, +}; + +static int fsi_master_acf_probe(struct platform_device *pdev) +{ + struct device_node *np, *mnode = dev_of_node(&pdev->dev); + struct genpool_data_fixed gpdf; + struct fsi_master_acf *master; + struct gpio_desc *gpio; + struct resource res; + uint32_t cf_mem_align; + int rc; + + master = kzalloc(sizeof(*master), GFP_KERNEL); + if (!master) + return -ENOMEM; + + master->dev = &pdev->dev; + master->master.dev.parent = master->dev; + master->last_addr = LAST_ADDR_INVALID; + + /* AST2400 vs. AST2500 */ + master->is_ast2500 = of_device_is_compatible(mnode, "aspeed,ast2500-cf-fsi-master"); + + /* Grab the SCU, we'll need to access it to configure the coprocessor */ + if (master->is_ast2500) + master->scu = syscon_regmap_lookup_by_compatible("aspeed,ast2500-scu"); + else + master->scu = syscon_regmap_lookup_by_compatible("aspeed,ast2400-scu"); + if (IS_ERR(master->scu)) { + dev_err(&pdev->dev, "failed to find SCU regmap\n"); + rc = PTR_ERR(master->scu); + goto err_free; + } + + /* Grab all the GPIOs we need */ + gpio = devm_gpiod_get(&pdev->dev, "clock", 0); + if (IS_ERR(gpio)) { + dev_err(&pdev->dev, "failed to get clock gpio\n"); + rc = PTR_ERR(gpio); + goto err_free; + } + master->gpio_clk = gpio; + + gpio = devm_gpiod_get(&pdev->dev, "data", 0); + if (IS_ERR(gpio)) { + dev_err(&pdev->dev, "failed to get data gpio\n"); + rc = PTR_ERR(gpio); + goto err_free; + } + master->gpio_data = gpio; + + /* Optional GPIOs */ + gpio = devm_gpiod_get_optional(&pdev->dev, "trans", 0); + if (IS_ERR(gpio)) { + dev_err(&pdev->dev, "failed to get trans gpio\n"); + rc = PTR_ERR(gpio); + goto err_free; + } + master->gpio_trans = gpio; + + gpio = devm_gpiod_get_optional(&pdev->dev, "enable", 0); + if (IS_ERR(gpio)) { + dev_err(&pdev->dev, "failed to get enable gpio\n"); + rc = PTR_ERR(gpio); + goto err_free; + } + master->gpio_enable = gpio; + + gpio = devm_gpiod_get_optional(&pdev->dev, "mux", 0); + if (IS_ERR(gpio)) { + dev_err(&pdev->dev, "failed to get mux gpio\n"); + rc = PTR_ERR(gpio); + goto err_free; + } + master->gpio_mux = gpio; + + /* Grab the reserved memory region (use DMA API instead ?) */ + np = of_parse_phandle(mnode, "memory-region", 0); + if (!np) { + dev_err(&pdev->dev, "Didn't find reserved memory\n"); + rc = -EINVAL; + goto err_free; + } + rc = of_address_to_resource(np, 0, &res); + of_node_put(np); + if (rc) { + dev_err(&pdev->dev, "Couldn't address to resource for reserved memory\n"); + rc = -ENOMEM; + goto err_free; + } + master->cf_mem_size = resource_size(&res); + master->cf_mem_addr = (uint32_t)res.start; + cf_mem_align = master->is_ast2500 ? 0x00100000 : 0x00200000; + if (master->cf_mem_addr & (cf_mem_align - 1)) { + dev_err(&pdev->dev, "Reserved memory has insufficient alignment\n"); + rc = -ENOMEM; + goto err_free; + } + master->cf_mem = devm_ioremap_resource(&pdev->dev, &res); + if (IS_ERR(master->cf_mem)) { + rc = PTR_ERR(master->cf_mem); + dev_err(&pdev->dev, "Error %d mapping coldfire memory\n", rc); + goto err_free; + } + dev_dbg(&pdev->dev, "DRAM allocation @%x\n", master->cf_mem_addr); + + /* AST2500 has a SW interrupt to the coprocessor */ + if (master->is_ast2500) { + /* Grab the CVIC (ColdFire interrupts controller) */ + np = of_parse_phandle(mnode, "aspeed,cvic", 0); + if (!np) { + dev_err(&pdev->dev, "Didn't find CVIC\n"); + rc = -EINVAL; + goto err_free; + } + master->cvic = devm_of_iomap(&pdev->dev, np, 0, NULL); + if (IS_ERR(master->cvic)) { + rc = PTR_ERR(master->cvic); + dev_err(&pdev->dev, "Error %d mapping CVIC\n", rc); + goto err_free; + } + rc = of_property_read_u32(np, "copro-sw-interrupts", + &master->cvic_sw_irq); + if (rc) { + dev_err(&pdev->dev, "Can't find coprocessor SW interrupt\n"); + goto err_free; + } + } + + /* Grab the SRAM */ + master->sram_pool = of_gen_pool_get(dev_of_node(&pdev->dev), "aspeed,sram", 0); + if (!master->sram_pool) { + rc = -ENODEV; + dev_err(&pdev->dev, "Can't find sram pool\n"); + goto err_free; + } + + /* Current microcode only deals with fixed location in SRAM */ + gpdf.offset = 0; + master->sram = (void __iomem *)gen_pool_alloc_algo(master->sram_pool, SRAM_SIZE, + gen_pool_fixed_alloc, &gpdf); + if (!master->sram) { + rc = -ENOMEM; + dev_err(&pdev->dev, "Failed to allocate sram from pool\n"); + goto err_free; + } + dev_dbg(&pdev->dev, "SRAM allocation @%lx\n", + (unsigned long)gen_pool_virt_to_phys(master->sram_pool, + (unsigned long)master->sram)); + + /* + * Hookup with the GPIO driver for arbitration of GPIO banks + * ownership. + */ + aspeed_gpio_copro_set_ops(&fsi_master_acf_gpio_ops, master); + + /* Default FSI command delays */ + master->t_send_delay = FSI_SEND_DELAY_CLOCKS; + master->t_echo_delay = FSI_ECHO_DELAY_CLOCKS; + master->master.n_links = 1; + if (master->is_ast2500) + master->master.flags = FSI_MASTER_FLAG_SWCLOCK; + master->master.read = fsi_master_acf_read; + master->master.write = fsi_master_acf_write; + master->master.term = fsi_master_acf_term; + master->master.send_break = fsi_master_acf_break; + master->master.link_enable = fsi_master_acf_link_enable; + master->master.link_config = fsi_master_acf_link_config; + master->master.dev.of_node = of_node_get(dev_of_node(master->dev)); + master->master.dev.release = fsi_master_acf_release; + platform_set_drvdata(pdev, master); + mutex_init(&master->lock); + + mutex_lock(&master->lock); + rc = fsi_master_acf_setup(master); + mutex_unlock(&master->lock); + if (rc) + goto release_of_dev; + + rc = device_create_file(&pdev->dev, &dev_attr_external_mode); + if (rc) + goto stop_copro; + + rc = fsi_master_register(&master->master); + if (!rc) + return 0; + + device_remove_file(master->dev, &dev_attr_external_mode); + put_device(&master->master.dev); + return rc; + + stop_copro: + fsi_master_acf_terminate(master); + release_of_dev: + aspeed_gpio_copro_set_ops(NULL, NULL); + gen_pool_free(master->sram_pool, (unsigned long)master->sram, SRAM_SIZE); + of_node_put(dev_of_node(master->dev)); + err_free: + kfree(master); + return rc; +} + + +static int fsi_master_acf_remove(struct platform_device *pdev) +{ + struct fsi_master_acf *master = platform_get_drvdata(pdev); + + device_remove_file(master->dev, &dev_attr_external_mode); + + fsi_master_unregister(&master->master); + + return 0; +} + +static const struct of_device_id fsi_master_acf_match[] = { + { .compatible = "aspeed,ast2400-cf-fsi-master" }, + { .compatible = "aspeed,ast2500-cf-fsi-master" }, + { }, +}; + +static struct platform_driver fsi_master_acf = { + .driver = { + .name = "fsi-master-acf", + .of_match_table = fsi_master_acf_match, + }, + .probe = fsi_master_acf_probe, + .remove = fsi_master_acf_remove, +}; + +module_platform_driver(fsi_master_acf); +MODULE_LICENSE("GPL"); diff --git a/drivers/fsi/fsi-master-gpio.c b/drivers/fsi/fsi-master-gpio.c index 3f487449a277..4eb3a766fd4a 100644 --- a/drivers/fsi/fsi-master-gpio.c +++ b/drivers/fsi/fsi-master-gpio.c @@ -8,59 +8,31 @@ #include <linux/fsi.h> #include <linux/gpio/consumer.h> #include <linux/io.h> +#include <linux/irqflags.h> #include <linux/module.h> #include <linux/of.h> #include <linux/platform_device.h> #include <linux/slab.h> -#include <linux/spinlock.h> #include "fsi-master.h" #define FSI_GPIO_STD_DLY 1 /* Standard pin delay in nS */ -#define FSI_ECHO_DELAY_CLOCKS 16 /* Number clocks for echo delay */ -#define FSI_PRE_BREAK_CLOCKS 50 /* Number clocks to prep for break */ -#define FSI_BREAK_CLOCKS 256 /* Number of clocks to issue break */ -#define FSI_POST_BREAK_CLOCKS 16000 /* Number clocks to set up cfam */ -#define FSI_INIT_CLOCKS 5000 /* Clock out any old data */ -#define FSI_GPIO_STD_DELAY 10 /* Standard GPIO delay in nS */ - /* todo: adjust down as low as */ - /* possible or eliminate */ -#define FSI_GPIO_CMD_DPOLL 0x2 -#define FSI_GPIO_CMD_TERM 0x3f -#define FSI_GPIO_CMD_ABS_AR 0x4 - -#define FSI_GPIO_DPOLL_CLOCKS 100 /* < 21 will cause slave to hang */ - -/* Bus errors */ -#define FSI_GPIO_ERR_BUSY 1 /* Slave stuck in busy state */ -#define FSI_GPIO_RESP_ERRA 2 /* Any (misc) Error */ -#define FSI_GPIO_RESP_ERRC 3 /* Slave reports master CRC error */ -#define FSI_GPIO_MTOE 4 /* Master time out error */ -#define FSI_GPIO_CRC_INVAL 5 /* Master reports slave CRC error */ - -/* Normal slave responses */ -#define FSI_GPIO_RESP_BUSY 1 -#define FSI_GPIO_RESP_ACK 0 -#define FSI_GPIO_RESP_ACKD 4 - -#define FSI_GPIO_MAX_BUSY 100 -#define FSI_GPIO_MTOE_COUNT 1000 -#define FSI_GPIO_DRAIN_BITS 20 -#define FSI_GPIO_CRC_SIZE 4 -#define FSI_GPIO_MSG_ID_SIZE 2 -#define FSI_GPIO_MSG_RESPID_SIZE 2 -#define FSI_GPIO_PRIME_SLAVE_CLOCKS 100 +#define LAST_ADDR_INVALID 0x1 struct fsi_master_gpio { struct fsi_master master; struct device *dev; - spinlock_t cmd_lock; /* Lock for commands */ + struct mutex cmd_lock; /* mutex for command ordering */ struct gpio_desc *gpio_clk; struct gpio_desc *gpio_data; struct gpio_desc *gpio_trans; /* Voltage translator */ struct gpio_desc *gpio_enable; /* FSI enable */ struct gpio_desc *gpio_mux; /* Mux control */ bool external_mode; + bool no_delays; + uint32_t last_addr; + uint8_t t_send_delay; + uint8_t t_echo_delay; }; #define CREATE_TRACE_POINTS @@ -78,19 +50,31 @@ static void clock_toggle(struct fsi_master_gpio *master, int count) int i; for (i = 0; i < count; i++) { - ndelay(FSI_GPIO_STD_DLY); + if (!master->no_delays) + ndelay(FSI_GPIO_STD_DLY); gpiod_set_value(master->gpio_clk, 0); - ndelay(FSI_GPIO_STD_DLY); + if (!master->no_delays) + ndelay(FSI_GPIO_STD_DLY); gpiod_set_value(master->gpio_clk, 1); } } -static int sda_in(struct fsi_master_gpio *master) +static int sda_clock_in(struct fsi_master_gpio *master) { int in; - ndelay(FSI_GPIO_STD_DLY); + if (!master->no_delays) + ndelay(FSI_GPIO_STD_DLY); + gpiod_set_value(master->gpio_clk, 0); + + /* Dummy read to feed the synchronizers */ + gpiod_get_value(master->gpio_data); + + /* Actual data read */ in = gpiod_get_value(master->gpio_data); + if (!master->no_delays) + ndelay(FSI_GPIO_STD_DLY); + gpiod_set_value(master->gpio_clk, 1); return in ? 1 : 0; } @@ -113,10 +97,17 @@ static void set_sda_output(struct fsi_master_gpio *master, int value) static void clock_zeros(struct fsi_master_gpio *master, int count) { + trace_fsi_master_gpio_clock_zeros(master, count); set_sda_output(master, 1); clock_toggle(master, count); } +static void echo_delay(struct fsi_master_gpio *master) +{ + clock_zeros(master, master->t_echo_delay); +} + + static void serial_in(struct fsi_master_gpio *master, struct fsi_gpio_msg *msg, uint8_t num_bits) { @@ -125,8 +116,7 @@ static void serial_in(struct fsi_master_gpio *master, struct fsi_gpio_msg *msg, set_sda_input(master); for (bit = 0; bit < num_bits; bit++) { - clock_toggle(master, 1); - in_bit = sda_in(master); + in_bit = sda_clock_in(master); msg->msg <<= 1; msg->msg |= ~in_bit & 0x1; /* Data is active low */ } @@ -191,22 +181,92 @@ static void msg_push_crc(struct fsi_gpio_msg *msg) msg_push_bits(msg, crc, 4); } +static bool check_same_address(struct fsi_master_gpio *master, int id, + uint32_t addr) +{ + /* this will also handle LAST_ADDR_INVALID */ + return master->last_addr == (((id & 0x3) << 21) | (addr & ~0x3)); +} + +static bool check_relative_address(struct fsi_master_gpio *master, int id, + uint32_t addr, uint32_t *rel_addrp) +{ + uint32_t last_addr = master->last_addr; + int32_t rel_addr; + + if (last_addr == LAST_ADDR_INVALID) + return false; + + /* We may be in 23-bit addressing mode, which uses the id as the + * top two address bits. So, if we're referencing a different ID, + * use absolute addresses. + */ + if (((last_addr >> 21) & 0x3) != id) + return false; + + /* remove the top two bits from any 23-bit addressing */ + last_addr &= (1 << 21) - 1; + + /* We know that the addresses are limited to 21 bits, so this won't + * overflow the signed rel_addr */ + rel_addr = addr - last_addr; + if (rel_addr > 255 || rel_addr < -256) + return false; + + *rel_addrp = (uint32_t)rel_addr; + + return true; +} + +static void last_address_update(struct fsi_master_gpio *master, + int id, bool valid, uint32_t addr) +{ + if (!valid) + master->last_addr = LAST_ADDR_INVALID; + else + master->last_addr = ((id & 0x3) << 21) | (addr & ~0x3); +} + /* - * Encode an Absolute Address command + * Encode an Absolute/Relative/Same Address command */ -static void build_abs_ar_command(struct fsi_gpio_msg *cmd, - uint8_t id, uint32_t addr, size_t size, const void *data) +static void build_ar_command(struct fsi_master_gpio *master, + struct fsi_gpio_msg *cmd, uint8_t id, + uint32_t addr, size_t size, const void *data) { + int i, addr_bits, opcode_bits; bool write = !!data; - uint8_t ds; - int i; + uint8_t ds, opcode; + uint32_t rel_addr; cmd->bits = 0; cmd->msg = 0; - msg_push_bits(cmd, id, 2); - msg_push_bits(cmd, FSI_GPIO_CMD_ABS_AR, 3); - msg_push_bits(cmd, write ? 0 : 1, 1); + /* we have 21 bits of address max */ + addr &= ((1 << 21) - 1); + + /* cmd opcodes are variable length - SAME_AR is only two bits */ + opcode_bits = 3; + + if (check_same_address(master, id, addr)) { + /* we still address the byte offset within the word */ + addr_bits = 2; + opcode_bits = 2; + opcode = FSI_CMD_SAME_AR; + trace_fsi_master_gpio_cmd_same_addr(master); + + } else if (check_relative_address(master, id, addr, &rel_addr)) { + /* 8 bits plus sign */ + addr_bits = 9; + addr = rel_addr; + opcode = FSI_CMD_REL_AR; + trace_fsi_master_gpio_cmd_rel_addr(master, rel_addr); + + } else { + addr_bits = 21; + opcode = FSI_CMD_ABS_AR; + trace_fsi_master_gpio_cmd_abs_addr(master, addr); + } /* * The read/write size is encoded in the lower bits of the address @@ -223,7 +283,10 @@ static void build_abs_ar_command(struct fsi_gpio_msg *cmd, if (size == 4) addr |= 1; - msg_push_bits(cmd, addr & ((1 << 21) - 1), 21); + msg_push_bits(cmd, id, 2); + msg_push_bits(cmd, opcode, opcode_bits); + msg_push_bits(cmd, write ? 0 : 1, 1); + msg_push_bits(cmd, addr, addr_bits); msg_push_bits(cmd, ds, 1); for (i = 0; write && i < size; i++) msg_push_bits(cmd, ((uint8_t *)data)[i], 8); @@ -237,14 +300,18 @@ static void build_dpoll_command(struct fsi_gpio_msg *cmd, uint8_t slave_id) cmd->msg = 0; msg_push_bits(cmd, slave_id, 2); - msg_push_bits(cmd, FSI_GPIO_CMD_DPOLL, 3); + msg_push_bits(cmd, FSI_CMD_DPOLL, 3); msg_push_crc(cmd); } -static void echo_delay(struct fsi_master_gpio *master) +static void build_epoll_command(struct fsi_gpio_msg *cmd, uint8_t slave_id) { - set_sda_output(master, 1); - clock_toggle(master, FSI_ECHO_DELAY_CLOCKS); + cmd->bits = 0; + cmd->msg = 0; + + msg_push_bits(cmd, slave_id, 2); + msg_push_bits(cmd, FSI_CMD_EPOLL, 3); + msg_push_crc(cmd); } static void build_term_command(struct fsi_gpio_msg *cmd, uint8_t slave_id) @@ -253,40 +320,40 @@ static void build_term_command(struct fsi_gpio_msg *cmd, uint8_t slave_id) cmd->msg = 0; msg_push_bits(cmd, slave_id, 2); - msg_push_bits(cmd, FSI_GPIO_CMD_TERM, 6); + msg_push_bits(cmd, FSI_CMD_TERM, 6); msg_push_crc(cmd); } /* - * Store information on master errors so handler can detect and clean - * up the bus + * Note: callers rely specifically on this returning -EAGAIN for + * a CRC error detected in the response. Use other error code + * for other situations. It will be converted to something else + * higher up the stack before it reaches userspace. */ -static void fsi_master_gpio_error(struct fsi_master_gpio *master, int error) -{ - -} - static int read_one_response(struct fsi_master_gpio *master, uint8_t data_size, struct fsi_gpio_msg *msgp, uint8_t *tagp) { struct fsi_gpio_msg msg; - uint8_t id, tag; + unsigned long flags; uint32_t crc; + uint8_t tag; int i; + local_irq_save(flags); + /* wait for the start bit */ - for (i = 0; i < FSI_GPIO_MTOE_COUNT; i++) { + for (i = 0; i < FSI_MASTER_MTOE_COUNT; i++) { msg.bits = 0; msg.msg = 0; serial_in(master, &msg, 1); if (msg.msg) break; } - if (i == FSI_GPIO_MTOE_COUNT) { + if (i == FSI_MASTER_MTOE_COUNT) { dev_dbg(master->dev, "Master time out waiting for response\n"); - fsi_master_gpio_error(master, FSI_GPIO_MTOE); - return -EIO; + local_irq_restore(flags); + return -ETIMEDOUT; } msg.bits = 0; @@ -295,23 +362,27 @@ static int read_one_response(struct fsi_master_gpio *master, /* Read slave ID & response tag */ serial_in(master, &msg, 4); - id = (msg.msg >> FSI_GPIO_MSG_RESPID_SIZE) & 0x3; tag = msg.msg & 0x3; /* If we have an ACK and we're expecting data, clock the data in too */ - if (tag == FSI_GPIO_RESP_ACK && data_size) + if (tag == FSI_RESP_ACK && data_size) serial_in(master, &msg, data_size * 8); /* read CRC */ - serial_in(master, &msg, FSI_GPIO_CRC_SIZE); + serial_in(master, &msg, FSI_CRC_SIZE); + + local_irq_restore(flags); /* we have a whole message now; check CRC */ crc = crc4(0, 1, 1); crc = crc4(crc, msg.msg, msg.bits); if (crc) { - dev_dbg(master->dev, "ERR response CRC\n"); - fsi_master_gpio_error(master, FSI_GPIO_CRC_INVAL); - return -EIO; + /* Check if it's all 1's, that probably means the host is off */ + if (((~msg.msg) & ((1ull << msg.bits) - 1)) == 0) + return -ENODEV; + dev_dbg(master->dev, "ERR response CRC msg: 0x%016llx (%d bits)\n", + msg.msg, msg.bits); + return -EAGAIN; } if (msgp) @@ -325,19 +396,23 @@ static int read_one_response(struct fsi_master_gpio *master, static int issue_term(struct fsi_master_gpio *master, uint8_t slave) { struct fsi_gpio_msg cmd; + unsigned long flags; uint8_t tag; int rc; build_term_command(&cmd, slave); + + local_irq_save(flags); serial_out(master, &cmd); echo_delay(master); + local_irq_restore(flags); rc = read_one_response(master, 0, NULL, &tag); if (rc < 0) { dev_err(master->dev, "TERM failed; lost communication with slave\n"); return -EIO; - } else if (tag != FSI_GPIO_RESP_ACK) { + } else if (tag != FSI_RESP_ACK) { dev_err(master->dev, "TERM failed; response %d\n", tag); return -EIO; } @@ -350,16 +425,39 @@ static int poll_for_response(struct fsi_master_gpio *master, { struct fsi_gpio_msg response, cmd; int busy_count = 0, rc, i; + unsigned long flags; uint8_t tag; uint8_t *data_byte = data; - + int crc_err_retries = 0; retry: rc = read_one_response(master, size, &response, &tag); - if (rc) - return rc; + + /* Handle retries on CRC errors */ + if (rc == -EAGAIN) { + /* Too many retries ? */ + if (crc_err_retries++ > FSI_CRC_ERR_RETRIES) { + /* + * Pass it up as a -EIO otherwise upper level will retry + * the whole command which isn't what we want here. + */ + rc = -EIO; + goto fail; + } + dev_dbg(master->dev, + "CRC error retry %d\n", crc_err_retries); + trace_fsi_master_gpio_crc_rsp_error(master); + build_epoll_command(&cmd, slave); + local_irq_save(flags); + clock_zeros(master, FSI_MASTER_EPOLL_CLOCKS); + serial_out(master, &cmd); + echo_delay(master); + local_irq_restore(flags); + goto retry; + } else if (rc) + goto fail; switch (tag) { - case FSI_GPIO_RESP_ACK: + case FSI_RESP_ACK: if (size && data) { uint64_t val = response.msg; /* clear crc & mask */ @@ -372,57 +470,89 @@ retry: } } break; - case FSI_GPIO_RESP_BUSY: + case FSI_RESP_BUSY: /* * Its necessary to clock slave before issuing * d-poll, not indicated in the hardware protocol * spec. < 20 clocks causes slave to hang, 21 ok. */ - clock_zeros(master, FSI_GPIO_DPOLL_CLOCKS); - if (busy_count++ < FSI_GPIO_MAX_BUSY) { + if (busy_count++ < FSI_MASTER_MAX_BUSY) { build_dpoll_command(&cmd, slave); + local_irq_save(flags); + clock_zeros(master, FSI_MASTER_DPOLL_CLOCKS); serial_out(master, &cmd); echo_delay(master); + local_irq_restore(flags); goto retry; } dev_warn(master->dev, "ERR slave is stuck in busy state, issuing TERM\n"); + local_irq_save(flags); + clock_zeros(master, FSI_MASTER_DPOLL_CLOCKS); + local_irq_restore(flags); issue_term(master, slave); rc = -EIO; break; - case FSI_GPIO_RESP_ERRA: - case FSI_GPIO_RESP_ERRC: - dev_dbg(master->dev, "ERR%c received: 0x%x\n", - tag == FSI_GPIO_RESP_ERRA ? 'A' : 'C', - (int)response.msg); - fsi_master_gpio_error(master, response.msg); + case FSI_RESP_ERRA: + dev_dbg(master->dev, "ERRA received: 0x%x\n", (int)response.msg); rc = -EIO; break; + case FSI_RESP_ERRC: + dev_dbg(master->dev, "ERRC received: 0x%x\n", (int)response.msg); + trace_fsi_master_gpio_crc_cmd_error(master); + rc = -EAGAIN; + break; } - /* Clock the slave enough to be ready for next operation */ - clock_zeros(master, FSI_GPIO_PRIME_SLAVE_CLOCKS); + if (busy_count > 0) + trace_fsi_master_gpio_poll_response_busy(master, busy_count); + fail: + /* + * tSendDelay clocks, avoids signal reflections when switching + * from receive of response back to send of data. + */ + local_irq_save(flags); + clock_zeros(master, master->t_send_delay); + local_irq_restore(flags); + return rc; } -static int fsi_master_gpio_xfer(struct fsi_master_gpio *master, uint8_t slave, - struct fsi_gpio_msg *cmd, size_t resp_len, void *resp) +static int send_request(struct fsi_master_gpio *master, + struct fsi_gpio_msg *cmd) { unsigned long flags; - int rc; - - spin_lock_irqsave(&master->cmd_lock, flags); - if (master->external_mode) { - spin_unlock_irqrestore(&master->cmd_lock, flags); + if (master->external_mode) return -EBUSY; - } + local_irq_save(flags); serial_out(master, cmd); echo_delay(master); - rc = poll_for_response(master, slave, resp_len, resp); - spin_unlock_irqrestore(&master->cmd_lock, flags); + local_irq_restore(flags); + + return 0; +} + +static int fsi_master_gpio_xfer(struct fsi_master_gpio *master, uint8_t slave, + struct fsi_gpio_msg *cmd, size_t resp_len, void *resp) +{ + int rc = -EAGAIN, retries = 0; + + while ((retries++) < FSI_CRC_ERR_RETRIES) { + rc = send_request(master, cmd); + if (rc) + break; + rc = poll_for_response(master, slave, resp_len, resp); + if (rc != -EAGAIN) + break; + rc = -EIO; + dev_warn(master->dev, "ECRC retry %d\n", retries); + + /* Pace it a bit before retry */ + msleep(1); + } return rc; } @@ -432,12 +562,18 @@ static int fsi_master_gpio_read(struct fsi_master *_master, int link, { struct fsi_master_gpio *master = to_fsi_master_gpio(_master); struct fsi_gpio_msg cmd; + int rc; if (link != 0) return -ENODEV; - build_abs_ar_command(&cmd, id, addr, size, NULL); - return fsi_master_gpio_xfer(master, id, &cmd, size, val); + mutex_lock(&master->cmd_lock); + build_ar_command(master, &cmd, id, addr, size, NULL); + rc = fsi_master_gpio_xfer(master, id, &cmd, size, val); + last_address_update(master, id, rc == 0, addr); + mutex_unlock(&master->cmd_lock); + + return rc; } static int fsi_master_gpio_write(struct fsi_master *_master, int link, @@ -445,12 +581,18 @@ static int fsi_master_gpio_write(struct fsi_master *_master, int link, { struct fsi_master_gpio *master = to_fsi_master_gpio(_master); struct fsi_gpio_msg cmd; + int rc; if (link != 0) return -ENODEV; - build_abs_ar_command(&cmd, id, addr, size, val); - return fsi_master_gpio_xfer(master, id, &cmd, 0, NULL); + mutex_lock(&master->cmd_lock); + build_ar_command(master, &cmd, id, addr, size, val); + rc = fsi_master_gpio_xfer(master, id, &cmd, 0, NULL); + last_address_update(master, id, rc == 0, addr); + mutex_unlock(&master->cmd_lock); + + return rc; } static int fsi_master_gpio_term(struct fsi_master *_master, @@ -458,12 +600,18 @@ static int fsi_master_gpio_term(struct fsi_master *_master, { struct fsi_master_gpio *master = to_fsi_master_gpio(_master); struct fsi_gpio_msg cmd; + int rc; if (link != 0) return -ENODEV; + mutex_lock(&master->cmd_lock); build_term_command(&cmd, id); - return fsi_master_gpio_xfer(master, id, &cmd, 0, NULL); + rc = fsi_master_gpio_xfer(master, id, &cmd, 0, NULL); + last_address_update(master, id, false, 0); + mutex_unlock(&master->cmd_lock); + + return rc; } static int fsi_master_gpio_break(struct fsi_master *_master, int link) @@ -476,11 +624,14 @@ static int fsi_master_gpio_break(struct fsi_master *_master, int link) trace_fsi_master_gpio_break(master); - spin_lock_irqsave(&master->cmd_lock, flags); + mutex_lock(&master->cmd_lock); if (master->external_mode) { - spin_unlock_irqrestore(&master->cmd_lock, flags); + mutex_unlock(&master->cmd_lock); return -EBUSY; } + + local_irq_save(flags); + set_sda_output(master, 1); sda_out(master, 1); clock_toggle(master, FSI_PRE_BREAK_CLOCKS); @@ -489,7 +640,11 @@ static int fsi_master_gpio_break(struct fsi_master *_master, int link) echo_delay(master); sda_out(master, 1); clock_toggle(master, FSI_POST_BREAK_CLOCKS); - spin_unlock_irqrestore(&master->cmd_lock, flags); + + local_irq_restore(flags); + + last_address_update(master, 0, false, 0); + mutex_unlock(&master->cmd_lock); /* Wait for logic reset to take effect */ udelay(200); @@ -499,6 +654,8 @@ static int fsi_master_gpio_break(struct fsi_master *_master, int link) static void fsi_master_gpio_init(struct fsi_master_gpio *master) { + unsigned long flags; + gpiod_direction_output(master->gpio_mux, 1); gpiod_direction_output(master->gpio_trans, 1); gpiod_direction_output(master->gpio_enable, 1); @@ -506,7 +663,9 @@ static void fsi_master_gpio_init(struct fsi_master_gpio *master) gpiod_direction_output(master->gpio_data, 1); /* todo: evaluate if clocks can be reduced */ + local_irq_save(flags); clock_zeros(master, FSI_INIT_CLOCKS); + local_irq_restore(flags); } static void fsi_master_gpio_init_external(struct fsi_master_gpio *master) @@ -521,22 +680,37 @@ static void fsi_master_gpio_init_external(struct fsi_master_gpio *master) static int fsi_master_gpio_link_enable(struct fsi_master *_master, int link) { struct fsi_master_gpio *master = to_fsi_master_gpio(_master); - unsigned long flags; int rc = -EBUSY; if (link != 0) return -ENODEV; - spin_lock_irqsave(&master->cmd_lock, flags); + mutex_lock(&master->cmd_lock); if (!master->external_mode) { gpiod_set_value(master->gpio_enable, 1); rc = 0; } - spin_unlock_irqrestore(&master->cmd_lock, flags); + mutex_unlock(&master->cmd_lock); return rc; } +static int fsi_master_gpio_link_config(struct fsi_master *_master, int link, + u8 t_send_delay, u8 t_echo_delay) +{ + struct fsi_master_gpio *master = to_fsi_master_gpio(_master); + + if (link != 0) + return -ENODEV; + + mutex_lock(&master->cmd_lock); + master->t_send_delay = t_send_delay; + master->t_echo_delay = t_echo_delay; + mutex_unlock(&master->cmd_lock); + + return 0; +} + static ssize_t external_mode_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -550,7 +724,7 @@ static ssize_t external_mode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct fsi_master_gpio *master = dev_get_drvdata(dev); - unsigned long flags, val; + unsigned long val; bool external_mode; int err; @@ -560,10 +734,10 @@ static ssize_t external_mode_store(struct device *dev, external_mode = !!val; - spin_lock_irqsave(&master->cmd_lock, flags); + mutex_lock(&master->cmd_lock); if (external_mode == master->external_mode) { - spin_unlock_irqrestore(&master->cmd_lock, flags); + mutex_unlock(&master->cmd_lock); return count; } @@ -572,7 +746,8 @@ static ssize_t external_mode_store(struct device *dev, fsi_master_gpio_init_external(master); else fsi_master_gpio_init(master); - spin_unlock_irqrestore(&master->cmd_lock, flags); + + mutex_unlock(&master->cmd_lock); fsi_master_rescan(&master->master); @@ -582,31 +757,44 @@ static ssize_t external_mode_store(struct device *dev, static DEVICE_ATTR(external_mode, 0664, external_mode_show, external_mode_store); +static void fsi_master_gpio_release(struct device *dev) +{ + struct fsi_master_gpio *master = to_fsi_master_gpio(dev_to_fsi_master(dev)); + + of_node_put(dev_of_node(master->dev)); + + kfree(master); +} + static int fsi_master_gpio_probe(struct platform_device *pdev) { struct fsi_master_gpio *master; struct gpio_desc *gpio; int rc; - master = devm_kzalloc(&pdev->dev, sizeof(*master), GFP_KERNEL); + master = kzalloc(sizeof(*master), GFP_KERNEL); if (!master) return -ENOMEM; master->dev = &pdev->dev; master->master.dev.parent = master->dev; master->master.dev.of_node = of_node_get(dev_of_node(master->dev)); + master->master.dev.release = fsi_master_gpio_release; + master->last_addr = LAST_ADDR_INVALID; gpio = devm_gpiod_get(&pdev->dev, "clock", 0); if (IS_ERR(gpio)) { dev_err(&pdev->dev, "failed to get clock gpio\n"); - return PTR_ERR(gpio); + rc = PTR_ERR(gpio); + goto err_free; } master->gpio_clk = gpio; gpio = devm_gpiod_get(&pdev->dev, "data", 0); if (IS_ERR(gpio)) { dev_err(&pdev->dev, "failed to get data gpio\n"); - return PTR_ERR(gpio); + rc = PTR_ERR(gpio); + goto err_free; } master->gpio_data = gpio; @@ -614,24 +802,38 @@ static int fsi_master_gpio_probe(struct platform_device *pdev) gpio = devm_gpiod_get_optional(&pdev->dev, "trans", 0); if (IS_ERR(gpio)) { dev_err(&pdev->dev, "failed to get trans gpio\n"); - return PTR_ERR(gpio); + rc = PTR_ERR(gpio); + goto err_free; } master->gpio_trans = gpio; gpio = devm_gpiod_get_optional(&pdev->dev, "enable", 0); if (IS_ERR(gpio)) { dev_err(&pdev->dev, "failed to get enable gpio\n"); - return PTR_ERR(gpio); + rc = PTR_ERR(gpio); + goto err_free; } master->gpio_enable = gpio; gpio = devm_gpiod_get_optional(&pdev->dev, "mux", 0); if (IS_ERR(gpio)) { dev_err(&pdev->dev, "failed to get mux gpio\n"); - return PTR_ERR(gpio); + rc = PTR_ERR(gpio); + goto err_free; } master->gpio_mux = gpio; + /* + * Check if GPIO block is slow enought that no extra delays + * are necessary. This improves performance on ast2500 by + * an order of magnitude. + */ + master->no_delays = device_property_present(&pdev->dev, "no-gpio-delays"); + + /* Default FSI command delays */ + master->t_send_delay = FSI_SEND_DELAY_CLOCKS; + master->t_echo_delay = FSI_ECHO_DELAY_CLOCKS; + master->master.n_links = 1; master->master.flags = FSI_MASTER_FLAG_SWCLOCK; master->master.read = fsi_master_gpio_read; @@ -639,34 +841,37 @@ static int fsi_master_gpio_probe(struct platform_device *pdev) master->master.term = fsi_master_gpio_term; master->master.send_break = fsi_master_gpio_break; master->master.link_enable = fsi_master_gpio_link_enable; + master->master.link_config = fsi_master_gpio_link_config; platform_set_drvdata(pdev, master); - spin_lock_init(&master->cmd_lock); + mutex_init(&master->cmd_lock); fsi_master_gpio_init(master); rc = device_create_file(&pdev->dev, &dev_attr_external_mode); if (rc) - return rc; + goto err_free; - return fsi_master_register(&master->master); + rc = fsi_master_register(&master->master); + if (rc) { + device_remove_file(&pdev->dev, &dev_attr_external_mode); + put_device(&master->master.dev); + return rc; + } + return 0; + err_free: + kfree(master); + return rc; } + static int fsi_master_gpio_remove(struct platform_device *pdev) { struct fsi_master_gpio *master = platform_get_drvdata(pdev); - devm_gpiod_put(&pdev->dev, master->gpio_clk); - devm_gpiod_put(&pdev->dev, master->gpio_data); - if (master->gpio_trans) - devm_gpiod_put(&pdev->dev, master->gpio_trans); - if (master->gpio_enable) - devm_gpiod_put(&pdev->dev, master->gpio_enable); - if (master->gpio_mux) - devm_gpiod_put(&pdev->dev, master->gpio_mux); - fsi_master_unregister(&master->master); + device_remove_file(&pdev->dev, &dev_attr_external_mode); - of_node_put(master->master.dev.of_node); + fsi_master_unregister(&master->master); return 0; } diff --git a/drivers/fsi/fsi-master-hub.c b/drivers/fsi/fsi-master-hub.c index 5885fc4a1ef0..b3c1e9debcf2 100644 --- a/drivers/fsi/fsi-master-hub.c +++ b/drivers/fsi/fsi-master-hub.c @@ -122,7 +122,8 @@ static int hub_master_write(struct fsi_master *master, int link, static int hub_master_break(struct fsi_master *master, int link) { - uint32_t addr, cmd; + uint32_t addr; + __be32 cmd; addr = 0x4; cmd = cpu_to_be32(0xc0de0000); @@ -205,7 +206,7 @@ static int hub_master_init(struct fsi_master_hub *hub) if (rc) return rc; - reg = ~0; + reg = cpu_to_be32(~0); rc = fsi_device_write(dev, FSI_MSENP0, ®, sizeof(reg)); if (rc) return rc; diff --git a/drivers/fsi/fsi-master.h b/drivers/fsi/fsi-master.h index ee0b46086026..040a7d4cf717 100644 --- a/drivers/fsi/fsi-master.h +++ b/drivers/fsi/fsi-master.h @@ -18,7 +18,41 @@ #define DRIVERS_FSI_MASTER_H #include <linux/device.h> +#include <linux/mutex.h> +/* Various protocol delays */ +#define FSI_ECHO_DELAY_CLOCKS 16 /* Number clocks for echo delay */ +#define FSI_SEND_DELAY_CLOCKS 16 /* Number clocks for send delay */ +#define FSI_PRE_BREAK_CLOCKS 50 /* Number clocks to prep for break */ +#define FSI_BREAK_CLOCKS 256 /* Number of clocks to issue break */ +#define FSI_POST_BREAK_CLOCKS 16000 /* Number clocks to set up cfam */ +#define FSI_INIT_CLOCKS 5000 /* Clock out any old data */ +#define FSI_MASTER_DPOLL_CLOCKS 50 /* < 21 will cause slave to hang */ +#define FSI_MASTER_EPOLL_CLOCKS 50 /* Number of clocks for E_POLL retry */ + +/* Various retry maximums */ +#define FSI_CRC_ERR_RETRIES 10 +#define FSI_MASTER_MAX_BUSY 200 +#define FSI_MASTER_MTOE_COUNT 1000 + +/* Command encodings */ +#define FSI_CMD_DPOLL 0x2 +#define FSI_CMD_EPOLL 0x3 +#define FSI_CMD_TERM 0x3f +#define FSI_CMD_ABS_AR 0x4 +#define FSI_CMD_REL_AR 0x5 +#define FSI_CMD_SAME_AR 0x3 /* but only a 2-bit opcode... */ + +/* Slave responses */ +#define FSI_RESP_ACK 0 /* Success */ +#define FSI_RESP_BUSY 1 /* Slave busy */ +#define FSI_RESP_ERRA 2 /* Any (misc) Error */ +#define FSI_RESP_ERRC 3 /* Slave reports master CRC error */ + +/* Misc */ +#define FSI_CRC_SIZE 4 + +/* fsi-master definition and flags */ #define FSI_MASTER_FLAG_SWCLOCK 0x1 struct fsi_master { @@ -26,6 +60,7 @@ struct fsi_master { int idx; int n_links; int flags; + struct mutex scan_lock; int (*read)(struct fsi_master *, int link, uint8_t id, uint32_t addr, void *val, size_t size); int (*write)(struct fsi_master *, int link, uint8_t id, @@ -33,6 +68,8 @@ struct fsi_master { int (*term)(struct fsi_master *, int link, uint8_t id); int (*send_break)(struct fsi_master *, int link); int (*link_enable)(struct fsi_master *, int link); + int (*link_config)(struct fsi_master *, int link, + u8 t_send_delay, u8 t_echo_delay); }; #define dev_to_fsi_master(d) container_of(d, struct fsi_master, dev) diff --git a/drivers/fsi/fsi-sbefifo.c b/drivers/fsi/fsi-sbefifo.c new file mode 100644 index 000000000000..ae861342626e --- /dev/null +++ b/drivers/fsi/fsi-sbefifo.c @@ -0,0 +1,1066 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) IBM Corporation 2017 + * + * 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 + * MERGCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/fsi.h> +#include <linux/fsi-sbefifo.h> +#include <linux/kernel.h> +#include <linux/cdev.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_platform.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/delay.h> +#include <linux/uio.h> +#include <linux/vmalloc.h> +#include <linux/mm.h> + +/* + * The SBEFIFO is a pipe-like FSI device for communicating with + * the self boot engine on POWER processors. + */ + +#define DEVICE_NAME "sbefifo" +#define FSI_ENGID_SBE 0x22 + +/* + * Register layout + */ + +/* Register banks */ +#define SBEFIFO_UP 0x00 /* FSI -> Host */ +#define SBEFIFO_DOWN 0x40 /* Host -> FSI */ + +/* Per-bank registers */ +#define SBEFIFO_FIFO 0x00 /* The FIFO itself */ +#define SBEFIFO_STS 0x04 /* Status register */ +#define SBEFIFO_STS_PARITY_ERR 0x20000000 +#define SBEFIFO_STS_RESET_REQ 0x02000000 +#define SBEFIFO_STS_GOT_EOT 0x00800000 +#define SBEFIFO_STS_MAX_XFER_LIMIT 0x00400000 +#define SBEFIFO_STS_FULL 0x00200000 +#define SBEFIFO_STS_EMPTY 0x00100000 +#define SBEFIFO_STS_ECNT_MASK 0x000f0000 +#define SBEFIFO_STS_ECNT_SHIFT 16 +#define SBEFIFO_STS_VALID_MASK 0x0000ff00 +#define SBEFIFO_STS_VALID_SHIFT 8 +#define SBEFIFO_STS_EOT_MASK 0x000000ff +#define SBEFIFO_STS_EOT_SHIFT 0 +#define SBEFIFO_EOT_RAISE 0x08 /* (Up only) Set End Of Transfer */ +#define SBEFIFO_REQ_RESET 0x0C /* (Up only) Reset Request */ +#define SBEFIFO_PERFORM_RESET 0x10 /* (Down only) Perform Reset */ +#define SBEFIFO_EOT_ACK 0x14 /* (Down only) Acknowledge EOT */ +#define SBEFIFO_DOWN_MAX 0x18 /* (Down only) Max transfer */ + +/* CFAM GP Mailbox SelfBoot Message register */ +#define CFAM_GP_MBOX_SBM_ADDR 0x2824 /* Converted 0x2809 */ + +#define CFAM_SBM_SBE_BOOTED 0x80000000 +#define CFAM_SBM_SBE_ASYNC_FFDC 0x40000000 +#define CFAM_SBM_SBE_STATE_MASK 0x00f00000 +#define CFAM_SBM_SBE_STATE_SHIFT 20 + +enum sbe_state +{ + SBE_STATE_UNKNOWN = 0x0, // Unkown, initial state + SBE_STATE_IPLING = 0x1, // IPL'ing - autonomous mode (transient) + SBE_STATE_ISTEP = 0x2, // ISTEP - Running IPL by steps (transient) + SBE_STATE_MPIPL = 0x3, // MPIPL + SBE_STATE_RUNTIME = 0x4, // SBE Runtime + SBE_STATE_DMT = 0x5, // Dead Man Timer State (transient) + SBE_STATE_DUMP = 0x6, // Dumping + SBE_STATE_FAILURE = 0x7, // Internal SBE failure + SBE_STATE_QUIESCE = 0x8, // Final state - needs SBE reset to get out +}; + +/* FIFO depth */ +#define SBEFIFO_FIFO_DEPTH 8 + +/* Helpers */ +#define sbefifo_empty(sts) ((sts) & SBEFIFO_STS_EMPTY) +#define sbefifo_full(sts) ((sts) & SBEFIFO_STS_FULL) +#define sbefifo_parity_err(sts) ((sts) & SBEFIFO_STS_PARITY_ERR) +#define sbefifo_populated(sts) (((sts) & SBEFIFO_STS_ECNT_MASK) >> SBEFIFO_STS_ECNT_SHIFT) +#define sbefifo_vacant(sts) (SBEFIFO_FIFO_DEPTH - sbefifo_populated(sts)) +#define sbefifo_eot_set(sts) (((sts) & SBEFIFO_STS_EOT_MASK) >> SBEFIFO_STS_EOT_SHIFT) + +/* Reset request timeout in ms */ +#define SBEFIFO_RESET_TIMEOUT 10000 + +/* Timeouts for commands in ms */ +#define SBEFIFO_TIMEOUT_START_CMD 10000 +#define SBEFIFO_TIMEOUT_IN_CMD 1000 +#define SBEFIFO_TIMEOUT_START_RSP 10000 +#define SBEFIFO_TIMEOUT_IN_RSP 1000 + +/* Other constants */ +#define SBEFIFO_MAX_USER_CMD_LEN (0x100000 + PAGE_SIZE) +#define SBEFIFO_RESET_MAGIC 0x52534554 /* "RSET" */ + +struct sbefifo { + uint32_t magic; +#define SBEFIFO_MAGIC 0x53424546 /* "SBEF" */ + struct fsi_device *fsi_dev; + struct device dev; + struct cdev cdev; + struct mutex lock; + bool broken; + bool dead; + bool async_ffdc; +}; + +struct sbefifo_user { + struct sbefifo *sbefifo; + struct mutex file_lock; + void *cmd_page; + void *pending_cmd; + size_t pending_len; +}; + +static DEFINE_MUTEX(sbefifo_ffdc_mutex); + + +static void __sbefifo_dump_ffdc(struct device *dev, const __be32 *ffdc, + size_t ffdc_sz, bool internal) +{ + int pack = 0; +#define FFDC_LSIZE 60 + static char ffdc_line[FFDC_LSIZE]; + char *p = ffdc_line; + + while (ffdc_sz) { + u32 w0, w1, w2, i; + if (ffdc_sz < 3) { + dev_err(dev, "SBE invalid FFDC package size %zd\n", ffdc_sz); + return; + } + w0 = be32_to_cpu(*(ffdc++)); + w1 = be32_to_cpu(*(ffdc++)); + w2 = be32_to_cpu(*(ffdc++)); + ffdc_sz -= 3; + if ((w0 >> 16) != 0xFFDC) { + dev_err(dev, "SBE invalid FFDC package signature %08x %08x %08x\n", + w0, w1, w2); + break; + } + w0 &= 0xffff; + if (w0 > ffdc_sz) { + dev_err(dev, "SBE FFDC package len %d words but only %zd remaining\n", + w0, ffdc_sz); + w0 = ffdc_sz; + break; + } + if (internal) { + dev_warn(dev, "+---- SBE FFDC package %d for async err -----+\n", + pack++); + } else { + dev_warn(dev, "+---- SBE FFDC package %d for cmd %02x:%02x -----+\n", + pack++, (w1 >> 8) & 0xff, w1 & 0xff); + } + dev_warn(dev, "| Response code: %08x |\n", w2); + dev_warn(dev, "|-------------------------------------------|\n"); + for (i = 0; i < w0; i++) { + if ((i & 3) == 0) { + p = ffdc_line; + p += sprintf(p, "| %04x:", i << 4); + } + p += sprintf(p, " %08x", be32_to_cpu(*(ffdc++))); + ffdc_sz--; + if ((i & 3) == 3 || i == (w0 - 1)) { + while ((i & 3) < 3) { + p += sprintf(p, " "); + i++; + } + dev_warn(dev, "%s |\n", ffdc_line); + } + } + dev_warn(dev, "+-------------------------------------------+\n"); + } +} + +static void sbefifo_dump_ffdc(struct device *dev, const __be32 *ffdc, + size_t ffdc_sz, bool internal) +{ + mutex_lock(&sbefifo_ffdc_mutex); + __sbefifo_dump_ffdc(dev, ffdc, ffdc_sz, internal); + mutex_unlock(&sbefifo_ffdc_mutex); +} + +int sbefifo_parse_status(struct device *dev, u16 cmd, __be32 *response, + size_t resp_len, size_t *data_len) +{ + u32 dh, s0, s1; + size_t ffdc_sz; + + if (resp_len < 3) { + pr_debug("sbefifo: cmd %04x, response too small: %zd\n", + cmd, resp_len); + return -ENXIO; + } + dh = be32_to_cpu(response[resp_len - 1]); + if (dh > resp_len || dh < 3) { + dev_err(dev, "SBE cmd %02x:%02x status offset out of range: %d/%zd\n", + cmd >> 8, cmd & 0xff, dh, resp_len); + return -ENXIO; + } + s0 = be32_to_cpu(response[resp_len - dh]); + s1 = be32_to_cpu(response[resp_len - dh + 1]); + if (((s0 >> 16) != 0xC0DE) || ((s0 & 0xffff) != cmd)) { + dev_err(dev, "SBE cmd %02x:%02x, status signature invalid: 0x%08x 0x%08x\n", + cmd >> 8, cmd & 0xff, s0, s1); + return -ENXIO; + } + if (s1 != 0) { + ffdc_sz = dh - 3; + dev_warn(dev, "SBE error cmd %02x:%02x status=%04x:%04x\n", + cmd >> 8, cmd & 0xff, s1 >> 16, s1 & 0xffff); + if (ffdc_sz) + sbefifo_dump_ffdc(dev, &response[resp_len - dh + 2], + ffdc_sz, false); + } + if (data_len) + *data_len = resp_len - dh; + + /* + * Primary status don't have the top bit set, so can't be confused with + * Linux negative error codes, so return the status word whole. + */ + return s1; +} +EXPORT_SYMBOL_GPL(sbefifo_parse_status); + +static int sbefifo_regr(struct sbefifo *sbefifo, int reg, u32 *word) +{ + __be32 raw_word; + int rc; + + rc = fsi_device_read(sbefifo->fsi_dev, reg, &raw_word, + sizeof(raw_word)); + if (rc) + return rc; + + *word = be32_to_cpu(raw_word); + + return 0; +} + +static int sbefifo_regw(struct sbefifo *sbefifo, int reg, u32 word) +{ + __be32 raw_word = cpu_to_be32(word); + + return fsi_device_write(sbefifo->fsi_dev, reg, &raw_word, + sizeof(raw_word)); +} + +static int sbefifo_check_sbe_state(struct sbefifo *sbefifo) +{ + __be32 raw_word; + u32 sbm; + int rc; + + rc = fsi_slave_read(sbefifo->fsi_dev->slave, CFAM_GP_MBOX_SBM_ADDR, + &raw_word, sizeof(raw_word)); + if (rc) + return rc; + sbm = be32_to_cpu(raw_word); + + /* SBE booted at all ? */ + if (!(sbm & CFAM_SBM_SBE_BOOTED)) + return -ESHUTDOWN; + + /* Check its state */ + switch ((sbm & CFAM_SBM_SBE_STATE_MASK) >> CFAM_SBM_SBE_STATE_SHIFT) { + case SBE_STATE_UNKNOWN: + return -ESHUTDOWN; + case SBE_STATE_IPLING: + case SBE_STATE_ISTEP: + case SBE_STATE_MPIPL: + case SBE_STATE_DMT: + return -EBUSY; + case SBE_STATE_RUNTIME: + case SBE_STATE_DUMP: /* Not sure about that one */ + break; + case SBE_STATE_FAILURE: + case SBE_STATE_QUIESCE: + return -ESHUTDOWN; + } + + /* Is there async FFDC available ? Remember it */ + if (sbm & CFAM_SBM_SBE_ASYNC_FFDC) + sbefifo->async_ffdc = true; + + return 0; +} + +/* Don't flip endianness of data to/from FIFO, just pass through. */ +static int sbefifo_down_read(struct sbefifo *sbefifo, __be32 *word) +{ + return fsi_device_read(sbefifo->fsi_dev, SBEFIFO_DOWN, word, + sizeof(*word)); +} + +static int sbefifo_up_write(struct sbefifo *sbefifo, __be32 word) +{ + return fsi_device_write(sbefifo->fsi_dev, SBEFIFO_UP, &word, + sizeof(word)); +} + +static int sbefifo_request_reset(struct sbefifo *sbefifo) +{ + struct device *dev = &sbefifo->fsi_dev->dev; + u32 status, timeout; + int rc; + + dev_dbg(dev, "Requesting FIFO reset\n"); + + /* Mark broken first, will be cleared if reset succeeds */ + sbefifo->broken = true; + + /* Send reset request */ + rc = sbefifo_regw(sbefifo, SBEFIFO_UP | SBEFIFO_REQ_RESET, 1); + if (rc) { + dev_err(dev, "Sending reset request failed, rc=%d\n", rc); + return rc; + } + + /* Wait for it to complete */ + for (timeout = 0; timeout < SBEFIFO_RESET_TIMEOUT; timeout++) { + rc = sbefifo_regr(sbefifo, SBEFIFO_UP | SBEFIFO_STS, &status); + if (rc) { + dev_err(dev, "Failed to read UP fifo status during reset" + " , rc=%d\n", rc); + return rc; + } + + if (!(status & SBEFIFO_STS_RESET_REQ)) { + dev_dbg(dev, "FIFO reset done\n"); + sbefifo->broken = false; + return 0; + } + + msleep(1); + } + dev_err(dev, "FIFO reset timed out\n"); + + return -ETIMEDOUT; +} + +static int sbefifo_cleanup_hw(struct sbefifo *sbefifo) +{ + struct device *dev = &sbefifo->fsi_dev->dev; + u32 up_status, down_status; + bool need_reset = false; + int rc; + + rc = sbefifo_check_sbe_state(sbefifo); + if (rc) { + dev_dbg(dev, "SBE state=%d\n", rc); + return rc; + } + + /* If broken, we don't need to look at status, go straight to reset */ + if (sbefifo->broken) + goto do_reset; + + rc = sbefifo_regr(sbefifo, SBEFIFO_UP | SBEFIFO_STS, &up_status); + if (rc) { + dev_err(dev, "Cleanup: Reading UP status failed, rc=%d\n", rc); + + /* Will try reset again on next attempt at using it */ + sbefifo->broken = true; + return rc; + } + + rc = sbefifo_regr(sbefifo, SBEFIFO_DOWN | SBEFIFO_STS, &down_status); + if (rc) { + dev_err(dev, "Cleanup: Reading DOWN status failed, rc=%d\n", rc); + + /* Will try reset again on next attempt at using it */ + sbefifo->broken = true; + return rc; + } + + /* The FIFO already contains a reset request from the SBE ? */ + if (down_status & SBEFIFO_STS_RESET_REQ) { + dev_info(dev, "Cleanup: FIFO reset request set, resetting\n"); + rc = sbefifo_regw(sbefifo, SBEFIFO_UP, SBEFIFO_PERFORM_RESET); + if (rc) { + sbefifo->broken = true; + dev_err(dev, "Cleanup: Reset reg write failed, rc=%d\n", rc); + return rc; + } + sbefifo->broken = false; + return 0; + } + + /* Parity error on either FIFO ? */ + if ((up_status | down_status) & SBEFIFO_STS_PARITY_ERR) + need_reset = true; + + /* Either FIFO not empty ? */ + if (!((up_status & down_status) & SBEFIFO_STS_EMPTY)) + need_reset = true; + + if (!need_reset) + return 0; + + dev_info(dev, "Cleanup: FIFO not clean (up=0x%08x down=0x%08x)\n", + up_status, down_status); + + do_reset: + + /* Mark broken, will be cleared if/when reset succeeds */ + return sbefifo_request_reset(sbefifo); +} + +static int sbefifo_wait(struct sbefifo *sbefifo, bool up, + u32 *status, unsigned long timeout) +{ + struct device *dev = &sbefifo->fsi_dev->dev; + unsigned long end_time; + bool ready = false; + u32 addr, sts = 0; + int rc; + + dev_vdbg(dev, "Wait on %s fifo...\n", up ? "up" : "down"); + + addr = (up ? SBEFIFO_UP : SBEFIFO_DOWN) | SBEFIFO_STS; + + end_time = jiffies + timeout; + while (!time_after(jiffies, end_time)) { + cond_resched(); + rc = sbefifo_regr(sbefifo, addr, &sts); + if (rc < 0) { + dev_err(dev, "FSI error %d reading status register\n", rc); + return rc; + } + if (!up && sbefifo_parity_err(sts)) { + dev_err(dev, "Parity error in DOWN FIFO\n"); + return -ENXIO; + } + ready = !(up ? sbefifo_full(sts) : sbefifo_empty(sts)); + if (ready) + break; + } + if (!ready) { + dev_err(dev, "%s FIFO Timeout ! status=%08x\n", up ? "UP" : "DOWN", sts); + return -ETIMEDOUT; + } + dev_vdbg(dev, "End of wait status: %08x\n", sts); + + *status = sts; + + return 0; +} + +static int sbefifo_send_command(struct sbefifo *sbefifo, + const __be32 *command, size_t cmd_len) +{ + struct device *dev = &sbefifo->fsi_dev->dev; + size_t len, chunk, vacant = 0, remaining = cmd_len; + unsigned long timeout; + u32 status; + int rc; + + dev_vdbg(dev, "sending command (%zd words, cmd=%04x)\n", + cmd_len, be32_to_cpu(command[1])); + + /* As long as there's something to send */ + timeout = msecs_to_jiffies(SBEFIFO_TIMEOUT_START_CMD); + while (remaining) { + /* Wait for room in the FIFO */ + rc = sbefifo_wait(sbefifo, true, &status, timeout); + if (rc < 0) + return rc; + timeout = msecs_to_jiffies(SBEFIFO_TIMEOUT_IN_CMD); + + vacant = sbefifo_vacant(status); + len = chunk = min(vacant, remaining); + + dev_vdbg(dev, " status=%08x vacant=%zd chunk=%zd\n", + status, vacant, chunk); + + /* Write as much as we can */ + while (len--) { + rc = sbefifo_up_write(sbefifo, *(command++)); + if (rc) { + dev_err(dev, "FSI error %d writing UP FIFO\n", rc); + return rc; + } + } + remaining -= chunk; + vacant -= chunk; + } + + /* If there's no room left, wait for some to write EOT */ + if (!vacant) { + rc = sbefifo_wait(sbefifo, true, &status, timeout); + if (rc) + return rc; + } + + /* Send an EOT */ + rc = sbefifo_regw(sbefifo, SBEFIFO_UP | SBEFIFO_EOT_RAISE, 0); + if (rc) + dev_err(dev, "FSI error %d writing EOT\n", rc); + return rc; +} + +static int sbefifo_read_response(struct sbefifo *sbefifo, struct iov_iter *response) +{ + struct device *dev = &sbefifo->fsi_dev->dev; + u32 status, eot_set; + unsigned long timeout; + bool overflow = false; + __be32 data; + size_t len; + int rc; + + dev_vdbg(dev, "reading response, buflen = %zd\n", iov_iter_count(response)); + + timeout = msecs_to_jiffies(SBEFIFO_TIMEOUT_START_RSP); + for (;;) { + /* Grab FIFO status (this will handle parity errors) */ + rc = sbefifo_wait(sbefifo, false, &status, timeout); + if (rc < 0) + return rc; + timeout = msecs_to_jiffies(SBEFIFO_TIMEOUT_IN_RSP); + + /* Decode status */ + len = sbefifo_populated(status); + eot_set = sbefifo_eot_set(status); + + dev_vdbg(dev, " chunk size %zd eot_set=0x%x\n", len, eot_set); + + /* Go through the chunk */ + while(len--) { + /* Read the data */ + rc = sbefifo_down_read(sbefifo, &data); + if (rc < 0) + return rc; + + /* Was it an EOT ? */ + if (eot_set & 0x80) { + /* + * There should be nothing else in the FIFO, + * if there is, mark broken, this will force + * a reset on next use, but don't fail the + * command. + */ + if (len) { + dev_warn(dev, "FIFO read hit" + " EOT with still %zd data\n", + len); + sbefifo->broken = true; + } + + /* We are done */ + rc = sbefifo_regw(sbefifo, + SBEFIFO_DOWN | SBEFIFO_EOT_ACK, 0); + + /* + * If that write fail, still complete the request but mark + * the fifo as broken for subsequent reset (not much else + * we can do here). + */ + if (rc) { + dev_err(dev, "FSI error %d ack'ing EOT\n", rc); + sbefifo->broken = true; + } + + /* Tell whether we overflowed */ + return overflow ? -EOVERFLOW : 0; + } + + /* Store it if there is room */ + if (iov_iter_count(response) >= sizeof(__be32)) { + if (copy_to_iter(&data, sizeof(__be32), response) < sizeof(__be32)) + return -EFAULT; + } else { + dev_vdbg(dev, "Response overflowed !\n"); + + overflow = true; + } + + /* Next EOT bit */ + eot_set <<= 1; + } + } + /* Shouldn't happen */ + return -EIO; +} + +static int sbefifo_do_command(struct sbefifo *sbefifo, + const __be32 *command, size_t cmd_len, + struct iov_iter *response) +{ + /* Try sending the command */ + int rc = sbefifo_send_command(sbefifo, command, cmd_len); + if (rc) + return rc; + + /* Now, get the response */ + return sbefifo_read_response(sbefifo, response); +} + +static void sbefifo_collect_async_ffdc(struct sbefifo *sbefifo) +{ + struct device *dev = &sbefifo->fsi_dev->dev; + struct iov_iter ffdc_iter; + struct kvec ffdc_iov; + __be32 *ffdc; + size_t ffdc_sz; + __be32 cmd[2]; + int rc; + + sbefifo->async_ffdc = false; + ffdc = vmalloc(SBEFIFO_MAX_FFDC_SIZE); + if (!ffdc) { + dev_err(dev, "Failed to allocate SBE FFDC buffer\n"); + return; + } + ffdc_iov.iov_base = ffdc; + ffdc_iov.iov_len = SBEFIFO_MAX_FFDC_SIZE; + iov_iter_kvec(&ffdc_iter, WRITE | ITER_KVEC, &ffdc_iov, 1, SBEFIFO_MAX_FFDC_SIZE); + cmd[0] = cpu_to_be32(2); + cmd[1] = cpu_to_be32(SBEFIFO_CMD_GET_SBE_FFDC); + rc = sbefifo_do_command(sbefifo, cmd, 2, &ffdc_iter); + if (rc != 0) { + dev_err(dev, "Error %d retrieving SBE FFDC\n", rc); + goto bail; + } + ffdc_sz = SBEFIFO_MAX_FFDC_SIZE - iov_iter_count(&ffdc_iter); + ffdc_sz /= sizeof(__be32); + rc = sbefifo_parse_status(dev, SBEFIFO_CMD_GET_SBE_FFDC, ffdc, + ffdc_sz, &ffdc_sz); + if (rc != 0) { + dev_err(dev, "Error %d decoding SBE FFDC\n", rc); + goto bail; + } + if (ffdc_sz > 0) + sbefifo_dump_ffdc(dev, ffdc, ffdc_sz, true); + bail: + vfree(ffdc); + +} + +static int __sbefifo_submit(struct sbefifo *sbefifo, + const __be32 *command, size_t cmd_len, + struct iov_iter *response) +{ + struct device *dev = &sbefifo->fsi_dev->dev; + int rc; + + if (sbefifo->dead) + return -ENODEV; + + if (cmd_len < 2 || be32_to_cpu(command[0]) != cmd_len) { + dev_vdbg(dev, "Invalid command len %zd (header: %d)\n", + cmd_len, be32_to_cpu(command[0])); + return -EINVAL; + } + + /* First ensure the HW is in a clean state */ + rc = sbefifo_cleanup_hw(sbefifo); + if (rc) + return rc; + + /* Look for async FFDC first if any */ + if (sbefifo->async_ffdc) + sbefifo_collect_async_ffdc(sbefifo); + + rc = sbefifo_do_command(sbefifo, command, cmd_len, response); + if (rc != 0 && rc != -EOVERFLOW) + goto fail; + return rc; + fail: + /* + * On failure, attempt a reset. Ignore the result, it will mark + * the fifo broken if the reset fails + */ + sbefifo_request_reset(sbefifo); + + /* Return original error */ + return rc; +} + +/** + * sbefifo_submit() - Submit and SBE fifo command and receive response + * @dev: The sbefifo device + * @command: The raw command data + * @cmd_len: The command size (in 32-bit words) + * @response: The output response buffer + * @resp_len: In: Response buffer size, Out: Response size + * + * This will perform the entire operation. If the reponse buffer + * overflows, returns -EOVERFLOW + */ +int sbefifo_submit(struct device *dev, const __be32 *command, size_t cmd_len, + __be32 *response, size_t *resp_len) +{ + struct sbefifo *sbefifo; + struct iov_iter resp_iter; + struct kvec resp_iov; + size_t rbytes; + int rc; + + if (!dev) + return -ENODEV; + sbefifo = dev_get_drvdata(dev); + if (!sbefifo) + return -ENODEV; + if (WARN_ON_ONCE(sbefifo->magic != SBEFIFO_MAGIC)) + return -ENODEV; + if (!resp_len || !command || !response) + return -EINVAL; + + /* Prepare iov iterator */ + rbytes = (*resp_len) * sizeof(__be32); + resp_iov.iov_base = response; + resp_iov.iov_len = rbytes; + iov_iter_kvec(&resp_iter, WRITE | ITER_KVEC, &resp_iov, 1, rbytes); + + /* Perform the command */ + mutex_lock(&sbefifo->lock); + rc = __sbefifo_submit(sbefifo, command, cmd_len, &resp_iter); + mutex_unlock(&sbefifo->lock); + + /* Extract the response length */ + rbytes -= iov_iter_count(&resp_iter); + *resp_len = rbytes / sizeof(__be32); + + return rc; +} +EXPORT_SYMBOL_GPL(sbefifo_submit); + +/* + * Char device interface + */ + +static void sbefifo_release_command(struct sbefifo_user *user) +{ + if (is_vmalloc_addr(user->pending_cmd)) + vfree(user->pending_cmd); + user->pending_cmd = NULL; + user->pending_len = 0; +} + +static int sbefifo_user_open(struct inode *inode, struct file *file) +{ + struct sbefifo *sbefifo = container_of(inode->i_cdev, struct sbefifo, cdev); + struct sbefifo_user *user; + + user = kzalloc(sizeof(struct sbefifo_user), GFP_KERNEL); + if (!user) + return -ENOMEM; + + file->private_data = user; + user->sbefifo = sbefifo; + user->cmd_page = (void *)__get_free_page(GFP_KERNEL); + if (!user->cmd_page) { + kfree(user); + return -ENOMEM; + } + mutex_init(&user->file_lock); + + return 0; +} + +static ssize_t sbefifo_user_read(struct file *file, char __user *buf, + size_t len, loff_t *offset) +{ + struct sbefifo_user *user = file->private_data; + struct sbefifo *sbefifo; + struct iov_iter resp_iter; + struct iovec resp_iov; + size_t cmd_len; + int rc; + + if (!user) + return -EINVAL; + sbefifo = user->sbefifo; + if (len & 3) + return -EINVAL; + + mutex_lock(&user->file_lock); + + /* Cronus relies on -EAGAIN after a short read */ + if (user->pending_len == 0) { + rc = -EAGAIN; + goto bail; + } + if (user->pending_len < 8) { + rc = -EINVAL; + goto bail; + } + cmd_len = user->pending_len >> 2; + + /* Prepare iov iterator */ + resp_iov.iov_base = buf; + resp_iov.iov_len = len; + iov_iter_init(&resp_iter, WRITE, &resp_iov, 1, len); + + /* Perform the command */ + mutex_lock(&sbefifo->lock); + rc = __sbefifo_submit(sbefifo, user->pending_cmd, cmd_len, &resp_iter); + mutex_unlock(&sbefifo->lock); + if (rc < 0) + goto bail; + + /* Extract the response length */ + rc = len - iov_iter_count(&resp_iter); + bail: + sbefifo_release_command(user); + mutex_unlock(&user->file_lock); + return rc; +} + +static ssize_t sbefifo_user_write(struct file *file, const char __user *buf, + size_t len, loff_t *offset) +{ + struct sbefifo_user *user = file->private_data; + struct sbefifo *sbefifo; + int rc = len; + + if (!user) + return -EINVAL; + sbefifo = user->sbefifo; + if (len > SBEFIFO_MAX_USER_CMD_LEN) + return -EINVAL; + if (len & 3) + return -EINVAL; + + mutex_lock(&user->file_lock); + + /* Can we use the pre-allocate buffer ? If not, allocate */ + if (len <= PAGE_SIZE) + user->pending_cmd = user->cmd_page; + else + user->pending_cmd = vmalloc(len); + if (!user->pending_cmd) { + rc = -ENOMEM; + goto bail; + } + + /* Copy the command into the staging buffer */ + if (copy_from_user(user->pending_cmd, buf, len)) { + rc = -EFAULT; + goto bail; + } + + /* Check for the magic reset command */ + if (len == 4 && be32_to_cpu(*(__be32 *)user->pending_cmd) == + SBEFIFO_RESET_MAGIC) { + + /* Clear out any pending command */ + user->pending_len = 0; + + /* Trigger reset request */ + mutex_lock(&sbefifo->lock); + rc = sbefifo_request_reset(user->sbefifo); + mutex_unlock(&sbefifo->lock); + if (rc == 0) + rc = 4; + goto bail; + } + + /* Update the staging buffer size */ + user->pending_len = len; + bail: + if (!user->pending_len) + sbefifo_release_command(user); + + mutex_unlock(&user->file_lock); + + /* And that's it, we'll issue the command on a read */ + return rc; +} + +static int sbefifo_user_release(struct inode *inode, struct file *file) +{ + struct sbefifo_user *user = file->private_data; + + if (!user) + return -EINVAL; + + sbefifo_release_command(user); + free_page((unsigned long)user->cmd_page); + kfree(user); + + return 0; +} + +static const struct file_operations sbefifo_fops = { + .owner = THIS_MODULE, + .open = sbefifo_user_open, + .read = sbefifo_user_read, + .write = sbefifo_user_write, + .release = sbefifo_user_release, +}; + +static void sbefifo_free(struct device *dev) +{ + struct sbefifo *sbefifo = container_of(dev, struct sbefifo, dev); + + put_device(&sbefifo->fsi_dev->dev); + kfree(sbefifo); +} + +/* + * Probe/remove + */ + +static int sbefifo_probe(struct device *dev) +{ + struct fsi_device *fsi_dev = to_fsi_dev(dev); + struct sbefifo *sbefifo; + struct device_node *np; + struct platform_device *child; + char child_name[32]; + int rc, didx, child_idx = 0; + + dev_dbg(dev, "Found sbefifo device\n"); + + sbefifo = kzalloc(sizeof(*sbefifo), GFP_KERNEL); + if (!sbefifo) + return -ENOMEM; + + /* Grab a reference to the device (parent of our cdev), we'll drop it later */ + if (!get_device(dev)) { + kfree(sbefifo); + return -ENODEV; + } + + sbefifo->magic = SBEFIFO_MAGIC; + sbefifo->fsi_dev = fsi_dev; + dev_set_drvdata(dev, sbefifo); + mutex_init(&sbefifo->lock); + + /* + * Try cleaning up the FIFO. If this fails, we still register the + * driver and will try cleaning things up again on the next access. + */ + rc = sbefifo_cleanup_hw(sbefifo); + if (rc && rc != -ESHUTDOWN) + dev_err(dev, "Initial HW cleanup failed, will retry later\n"); + + /* Create chardev for userspace access */ + sbefifo->dev.type = &fsi_cdev_type; + sbefifo->dev.parent = dev; + sbefifo->dev.release = sbefifo_free; + device_initialize(&sbefifo->dev); + + /* Allocate a minor in the FSI space */ + rc = fsi_get_new_minor(fsi_dev, fsi_dev_sbefifo, &sbefifo->dev.devt, &didx); + if (rc) + goto err; + + dev_set_name(&sbefifo->dev, "sbefifo%d", didx); + cdev_init(&sbefifo->cdev, &sbefifo_fops); + rc = cdev_device_add(&sbefifo->cdev, &sbefifo->dev); + if (rc) { + dev_err(dev, "Error %d creating char device %s\n", + rc, dev_name(&sbefifo->dev)); + goto err_free_minor; + } + + /* Create platform devs for dts child nodes (occ, etc) */ + for_each_available_child_of_node(dev->of_node, np) { + snprintf(child_name, sizeof(child_name), "%s-dev%d", + dev_name(&sbefifo->dev), child_idx++); + child = of_platform_device_create(np, child_name, dev); + if (!child) + dev_warn(dev, "failed to create child %s dev\n", + child_name); + } + + return 0; + err_free_minor: + fsi_free_minor(sbefifo->dev.devt); + err: + put_device(&sbefifo->dev); + return rc; +} + +static int sbefifo_unregister_child(struct device *dev, void *data) +{ + struct platform_device *child = to_platform_device(dev); + + of_device_unregister(child); + if (dev->of_node) + of_node_clear_flag(dev->of_node, OF_POPULATED); + + return 0; +} + +static int sbefifo_remove(struct device *dev) +{ + struct sbefifo *sbefifo = dev_get_drvdata(dev); + + dev_dbg(dev, "Removing sbefifo device...\n"); + + mutex_lock(&sbefifo->lock); + sbefifo->dead = true; + mutex_unlock(&sbefifo->lock); + + cdev_device_del(&sbefifo->cdev, &sbefifo->dev); + fsi_free_minor(sbefifo->dev.devt); + device_for_each_child(dev, NULL, sbefifo_unregister_child); + put_device(&sbefifo->dev); + + return 0; +} + +static struct fsi_device_id sbefifo_ids[] = { + { + .engine_type = FSI_ENGID_SBE, + .version = FSI_VERSION_ANY, + }, + { 0 } +}; + +static struct fsi_driver sbefifo_drv = { + .id_table = sbefifo_ids, + .drv = { + .name = DEVICE_NAME, + .bus = &fsi_bus_type, + .probe = sbefifo_probe, + .remove = sbefifo_remove, + } +}; + +static int sbefifo_init(void) +{ + return fsi_driver_register(&sbefifo_drv); +} + +static void sbefifo_exit(void) +{ + fsi_driver_unregister(&sbefifo_drv); +} + +module_init(sbefifo_init); +module_exit(sbefifo_exit); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Brad Bishop <bradleyb@fuzziesquirrel.com>"); +MODULE_AUTHOR("Eddie James <eajames@linux.vnet.ibm.com>"); +MODULE_AUTHOR("Andrew Jeffery <andrew@aj.id.au>"); +MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); +MODULE_DESCRIPTION("Linux device interface to the POWER Self Boot Engine"); diff --git a/drivers/fsi/fsi-scom.c b/drivers/fsi/fsi-scom.c index e13353a2fd7c..df94021dd9d1 100644 --- a/drivers/fsi/fsi-scom.c +++ b/drivers/fsi/fsi-scom.c @@ -20,42 +20,73 @@ #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/slab.h> -#include <linux/miscdevice.h> +#include <linux/cdev.h> #include <linux/list.h> -#include <linux/idr.h> -#define FSI_ENGID_SCOM 0x5 +#include <uapi/linux/fsi.h> -#define SCOM_FSI2PIB_DELAY 50 +#define FSI_ENGID_SCOM 0x5 /* SCOM engine register set */ #define SCOM_DATA0_REG 0x00 #define SCOM_DATA1_REG 0x04 #define SCOM_CMD_REG 0x08 -#define SCOM_RESET_REG 0x1C +#define SCOM_FSI2PIB_RESET_REG 0x18 +#define SCOM_STATUS_REG 0x1C /* Read */ +#define SCOM_PIB_RESET_REG 0x1C /* Write */ -#define SCOM_RESET_CMD 0x80000000 +/* Command register */ #define SCOM_WRITE_CMD 0x80000000 +#define SCOM_READ_CMD 0x00000000 + +/* Status register bits */ +#define SCOM_STATUS_ERR_SUMMARY 0x80000000 +#define SCOM_STATUS_PROTECTION 0x01000000 +#define SCOM_STATUS_PARITY 0x04000000 +#define SCOM_STATUS_PIB_ABORT 0x00100000 +#define SCOM_STATUS_PIB_RESP_MASK 0x00007000 +#define SCOM_STATUS_PIB_RESP_SHIFT 12 + +#define SCOM_STATUS_ANY_ERR (SCOM_STATUS_ERR_SUMMARY | \ + SCOM_STATUS_PROTECTION | \ + SCOM_STATUS_PARITY | \ + SCOM_STATUS_PIB_ABORT | \ + SCOM_STATUS_PIB_RESP_MASK) +/* SCOM address encodings */ +#define XSCOM_ADDR_IND_FLAG BIT_ULL(63) +#define XSCOM_ADDR_INF_FORM1 BIT_ULL(60) + +/* SCOM indirect stuff */ +#define XSCOM_ADDR_DIRECT_PART 0x7fffffffull +#define XSCOM_ADDR_INDIRECT_PART 0x000fffff00000000ull +#define XSCOM_DATA_IND_READ BIT_ULL(63) +#define XSCOM_DATA_IND_COMPLETE BIT_ULL(31) +#define XSCOM_DATA_IND_ERR_MASK 0x70000000ull +#define XSCOM_DATA_IND_ERR_SHIFT 28 +#define XSCOM_DATA_IND_DATA 0x0000ffffull +#define XSCOM_DATA_IND_FORM1_DATA 0x000fffffffffffffull +#define XSCOM_ADDR_FORM1_LOW 0x000ffffffffull +#define XSCOM_ADDR_FORM1_HI 0xfff00000000ull +#define XSCOM_ADDR_FORM1_HI_SHIFT 20 + +/* Retries */ +#define SCOM_MAX_RETRIES 100 /* Retries on busy */ +#define SCOM_MAX_IND_RETRIES 10 /* Retries indirect not ready */ struct scom_device { struct list_head link; struct fsi_device *fsi_dev; - struct miscdevice mdev; - char name[32]; - int idx; + struct device dev; + struct cdev cdev; + struct mutex lock; + bool dead; }; -#define to_scom_dev(x) container_of((x), struct scom_device, mdev) - -static struct list_head scom_devices; - -static DEFINE_IDA(scom_ida); - -static int put_scom(struct scom_device *scom_dev, uint64_t value, - uint32_t addr) +static int __put_scom(struct scom_device *scom_dev, uint64_t value, + uint32_t addr, uint32_t *status) { + __be32 data, raw_status; int rc; - uint32_t data; data = cpu_to_be32((value >> 32) & 0xffffffff); rc = fsi_device_write(scom_dev->fsi_dev, SCOM_DATA0_REG, &data, @@ -70,53 +101,286 @@ static int put_scom(struct scom_device *scom_dev, uint64_t value, return rc; data = cpu_to_be32(SCOM_WRITE_CMD | addr); - return fsi_device_write(scom_dev->fsi_dev, SCOM_CMD_REG, &data, + rc = fsi_device_write(scom_dev->fsi_dev, SCOM_CMD_REG, &data, sizeof(uint32_t)); + if (rc) + return rc; + rc = fsi_device_read(scom_dev->fsi_dev, SCOM_STATUS_REG, &raw_status, + sizeof(uint32_t)); + if (rc) + return rc; + *status = be32_to_cpu(raw_status); + + return 0; } -static int get_scom(struct scom_device *scom_dev, uint64_t *value, - uint32_t addr) +static int __get_scom(struct scom_device *scom_dev, uint64_t *value, + uint32_t addr, uint32_t *status) { - uint32_t result, data; + __be32 data, raw_status; int rc; + *value = 0ULL; - data = cpu_to_be32(addr); + data = cpu_to_be32(SCOM_READ_CMD | addr); rc = fsi_device_write(scom_dev->fsi_dev, SCOM_CMD_REG, &data, sizeof(uint32_t)); if (rc) return rc; + rc = fsi_device_read(scom_dev->fsi_dev, SCOM_STATUS_REG, &raw_status, + sizeof(uint32_t)); + if (rc) + return rc; - rc = fsi_device_read(scom_dev->fsi_dev, SCOM_DATA0_REG, &result, + /* + * Read the data registers even on error, so we don't have + * to interpret the status register here. + */ + rc = fsi_device_read(scom_dev->fsi_dev, SCOM_DATA0_REG, &data, sizeof(uint32_t)); if (rc) return rc; - - *value |= (uint64_t)cpu_to_be32(result) << 32; - rc = fsi_device_read(scom_dev->fsi_dev, SCOM_DATA1_REG, &result, + *value |= (uint64_t)be32_to_cpu(data) << 32; + rc = fsi_device_read(scom_dev->fsi_dev, SCOM_DATA1_REG, &data, sizeof(uint32_t)); if (rc) return rc; + *value |= be32_to_cpu(data); + *status = be32_to_cpu(raw_status); - *value |= cpu_to_be32(result); + return rc; +} + +static int put_indirect_scom_form0(struct scom_device *scom, uint64_t value, + uint64_t addr, uint32_t *status) +{ + uint64_t ind_data, ind_addr; + int rc, retries, err = 0; + + if (value & ~XSCOM_DATA_IND_DATA) + return -EINVAL; + + ind_addr = addr & XSCOM_ADDR_DIRECT_PART; + ind_data = (addr & XSCOM_ADDR_INDIRECT_PART) | value; + rc = __put_scom(scom, ind_data, ind_addr, status); + if (rc || (*status & SCOM_STATUS_ANY_ERR)) + return rc; + + for (retries = 0; retries < SCOM_MAX_IND_RETRIES; retries++) { + rc = __get_scom(scom, &ind_data, addr, status); + if (rc || (*status & SCOM_STATUS_ANY_ERR)) + return rc; + + err = (ind_data & XSCOM_DATA_IND_ERR_MASK) >> XSCOM_DATA_IND_ERR_SHIFT; + *status = err << SCOM_STATUS_PIB_RESP_SHIFT; + if ((ind_data & XSCOM_DATA_IND_COMPLETE) || (err != SCOM_PIB_BLOCKED)) + return 0; + + msleep(1); + } + return rc; +} + +static int put_indirect_scom_form1(struct scom_device *scom, uint64_t value, + uint64_t addr, uint32_t *status) +{ + uint64_t ind_data, ind_addr; + + if (value & ~XSCOM_DATA_IND_FORM1_DATA) + return -EINVAL; + ind_addr = addr & XSCOM_ADDR_FORM1_LOW; + ind_data = value | (addr & XSCOM_ADDR_FORM1_HI) << XSCOM_ADDR_FORM1_HI_SHIFT; + return __put_scom(scom, ind_data, ind_addr, status); +} + +static int get_indirect_scom_form0(struct scom_device *scom, uint64_t *value, + uint64_t addr, uint32_t *status) +{ + uint64_t ind_data, ind_addr; + int rc, retries, err = 0; + + ind_addr = addr & XSCOM_ADDR_DIRECT_PART; + ind_data = (addr & XSCOM_ADDR_INDIRECT_PART) | XSCOM_DATA_IND_READ; + rc = __put_scom(scom, ind_data, ind_addr, status); + if (rc || (*status & SCOM_STATUS_ANY_ERR)) + return rc; + + for (retries = 0; retries < SCOM_MAX_IND_RETRIES; retries++) { + rc = __get_scom(scom, &ind_data, addr, status); + if (rc || (*status & SCOM_STATUS_ANY_ERR)) + return rc; + + err = (ind_data & XSCOM_DATA_IND_ERR_MASK) >> XSCOM_DATA_IND_ERR_SHIFT; + *status = err << SCOM_STATUS_PIB_RESP_SHIFT; + *value = ind_data & XSCOM_DATA_IND_DATA; + + if ((ind_data & XSCOM_DATA_IND_COMPLETE) || (err != SCOM_PIB_BLOCKED)) + return 0; + + msleep(1); + } + return rc; +} + +static int raw_put_scom(struct scom_device *scom, uint64_t value, + uint64_t addr, uint32_t *status) +{ + if (addr & XSCOM_ADDR_IND_FLAG) { + if (addr & XSCOM_ADDR_INF_FORM1) + return put_indirect_scom_form1(scom, value, addr, status); + else + return put_indirect_scom_form0(scom, value, addr, status); + } else + return __put_scom(scom, value, addr, status); +} + +static int raw_get_scom(struct scom_device *scom, uint64_t *value, + uint64_t addr, uint32_t *status) +{ + if (addr & XSCOM_ADDR_IND_FLAG) { + if (addr & XSCOM_ADDR_INF_FORM1) + return -ENXIO; + return get_indirect_scom_form0(scom, value, addr, status); + } else + return __get_scom(scom, value, addr, status); +} + +static int handle_fsi2pib_status(struct scom_device *scom, uint32_t status) +{ + uint32_t dummy = -1; + + if (status & SCOM_STATUS_PROTECTION) + return -EPERM; + if (status & SCOM_STATUS_PARITY) { + fsi_device_write(scom->fsi_dev, SCOM_FSI2PIB_RESET_REG, &dummy, + sizeof(uint32_t)); + return -EIO; + } + /* Return -EBUSY on PIB abort to force a retry */ + if (status & SCOM_STATUS_PIB_ABORT) + return -EBUSY; + if (status & SCOM_STATUS_ERR_SUMMARY) { + fsi_device_write(scom->fsi_dev, SCOM_FSI2PIB_RESET_REG, &dummy, + sizeof(uint32_t)); + return -EIO; + } return 0; } +static int handle_pib_status(struct scom_device *scom, uint8_t status) +{ + uint32_t dummy = -1; + + if (status == SCOM_PIB_SUCCESS) + return 0; + if (status == SCOM_PIB_BLOCKED) + return -EBUSY; + + /* Reset the bridge */ + fsi_device_write(scom->fsi_dev, SCOM_FSI2PIB_RESET_REG, &dummy, + sizeof(uint32_t)); + + switch(status) { + case SCOM_PIB_OFFLINE: + return -ENODEV; + case SCOM_PIB_BAD_ADDR: + return -ENXIO; + case SCOM_PIB_TIMEOUT: + return -ETIMEDOUT; + case SCOM_PIB_PARTIAL: + case SCOM_PIB_CLK_ERR: + case SCOM_PIB_PARITY_ERR: + default: + return -EIO; + } +} + +static int put_scom(struct scom_device *scom, uint64_t value, + uint64_t addr) +{ + uint32_t status, dummy = -1; + int rc, retries; + + for (retries = 0; retries < SCOM_MAX_RETRIES; retries++) { + rc = raw_put_scom(scom, value, addr, &status); + if (rc) { + /* Try resetting the bridge if FSI fails */ + if (rc != -ENODEV && retries == 0) { + fsi_device_write(scom->fsi_dev, SCOM_FSI2PIB_RESET_REG, + &dummy, sizeof(uint32_t)); + rc = -EBUSY; + } else + return rc; + } else + rc = handle_fsi2pib_status(scom, status); + if (rc && rc != -EBUSY) + break; + if (rc == 0) { + rc = handle_pib_status(scom, + (status & SCOM_STATUS_PIB_RESP_MASK) + >> SCOM_STATUS_PIB_RESP_SHIFT); + if (rc && rc != -EBUSY) + break; + } + if (rc == 0) + break; + msleep(1); + } + return rc; +} + +static int get_scom(struct scom_device *scom, uint64_t *value, + uint64_t addr) +{ + uint32_t status, dummy = -1; + int rc, retries; + + for (retries = 0; retries < SCOM_MAX_RETRIES; retries++) { + rc = raw_get_scom(scom, value, addr, &status); + if (rc) { + /* Try resetting the bridge if FSI fails */ + if (rc != -ENODEV && retries == 0) { + fsi_device_write(scom->fsi_dev, SCOM_FSI2PIB_RESET_REG, + &dummy, sizeof(uint32_t)); + rc = -EBUSY; + } else + return rc; + } else + rc = handle_fsi2pib_status(scom, status); + if (rc && rc != -EBUSY) + break; + if (rc == 0) { + rc = handle_pib_status(scom, + (status & SCOM_STATUS_PIB_RESP_MASK) + >> SCOM_STATUS_PIB_RESP_SHIFT); + if (rc && rc != -EBUSY) + break; + } + if (rc == 0) + break; + msleep(1); + } + return rc; +} + static ssize_t scom_read(struct file *filep, char __user *buf, size_t len, - loff_t *offset) + loff_t *offset) { - int rc; - struct miscdevice *mdev = - (struct miscdevice *)filep->private_data; - struct scom_device *scom = to_scom_dev(mdev); + struct scom_device *scom = filep->private_data; struct device *dev = &scom->fsi_dev->dev; uint64_t val; + int rc; if (len != sizeof(uint64_t)) return -EINVAL; - rc = get_scom(scom, &val, *offset); + mutex_lock(&scom->lock); + if (scom->dead) + rc = -ENODEV; + else + rc = get_scom(scom, &val, *offset); + mutex_unlock(&scom->lock); if (rc) { dev_dbg(dev, "get_scom fail:%d\n", rc); return rc; @@ -130,11 +394,10 @@ static ssize_t scom_read(struct file *filep, char __user *buf, size_t len, } static ssize_t scom_write(struct file *filep, const char __user *buf, - size_t len, loff_t *offset) + size_t len, loff_t *offset) { int rc; - struct miscdevice *mdev = filep->private_data; - struct scom_device *scom = to_scom_dev(mdev); + struct scom_device *scom = filep->private_data; struct device *dev = &scom->fsi_dev->dev; uint64_t val; @@ -147,7 +410,12 @@ static ssize_t scom_write(struct file *filep, const char __user *buf, return -EINVAL; } - rc = put_scom(scom, val, *offset); + mutex_lock(&scom->lock); + if (scom->dead) + rc = -ENODEV; + else + rc = put_scom(scom, val, *offset); + mutex_unlock(&scom->lock); if (rc) { dev_dbg(dev, "put_scom failed with:%d\n", rc); return rc; @@ -171,50 +439,205 @@ static loff_t scom_llseek(struct file *file, loff_t offset, int whence) return offset; } +static void raw_convert_status(struct scom_access *acc, uint32_t status) +{ + acc->pib_status = (status & SCOM_STATUS_PIB_RESP_MASK) >> + SCOM_STATUS_PIB_RESP_SHIFT; + acc->intf_errors = 0; + + if (status & SCOM_STATUS_PROTECTION) + acc->intf_errors |= SCOM_INTF_ERR_PROTECTION; + else if (status & SCOM_STATUS_PARITY) + acc->intf_errors |= SCOM_INTF_ERR_PARITY; + else if (status & SCOM_STATUS_PIB_ABORT) + acc->intf_errors |= SCOM_INTF_ERR_ABORT; + else if (status & SCOM_STATUS_ERR_SUMMARY) + acc->intf_errors |= SCOM_INTF_ERR_UNKNOWN; +} + +static int scom_raw_read(struct scom_device *scom, void __user *argp) +{ + struct scom_access acc; + uint32_t status; + int rc; + + if (copy_from_user(&acc, argp, sizeof(struct scom_access))) + return -EFAULT; + + rc = raw_get_scom(scom, &acc.data, acc.addr, &status); + if (rc) + return rc; + raw_convert_status(&acc, status); + if (copy_to_user(argp, &acc, sizeof(struct scom_access))) + return -EFAULT; + return 0; +} + +static int scom_raw_write(struct scom_device *scom, void __user *argp) +{ + u64 prev_data, mask, data; + struct scom_access acc; + uint32_t status; + int rc; + + if (copy_from_user(&acc, argp, sizeof(struct scom_access))) + return -EFAULT; + + if (acc.mask) { + rc = raw_get_scom(scom, &prev_data, acc.addr, &status); + if (rc) + return rc; + if (status & SCOM_STATUS_ANY_ERR) + goto fail; + mask = acc.mask; + } else { + prev_data = mask = -1ull; + } + data = (prev_data & ~mask) | (acc.data & mask); + rc = raw_put_scom(scom, data, acc.addr, &status); + if (rc) + return rc; + fail: + raw_convert_status(&acc, status); + if (copy_to_user(argp, &acc, sizeof(struct scom_access))) + return -EFAULT; + return 0; +} + +static int scom_reset(struct scom_device *scom, void __user *argp) +{ + uint32_t flags, dummy = -1; + int rc = 0; + + if (get_user(flags, (__u32 __user *)argp)) + return -EFAULT; + if (flags & SCOM_RESET_PIB) + rc = fsi_device_write(scom->fsi_dev, SCOM_PIB_RESET_REG, &dummy, + sizeof(uint32_t)); + if (!rc && (flags & (SCOM_RESET_PIB | SCOM_RESET_INTF))) + rc = fsi_device_write(scom->fsi_dev, SCOM_FSI2PIB_RESET_REG, &dummy, + sizeof(uint32_t)); + return rc; +} + +static int scom_check(struct scom_device *scom, void __user *argp) +{ + /* Still need to find out how to get "protected" */ + return put_user(SCOM_CHECK_SUPPORTED, (__u32 __user *)argp); +} + +static long scom_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct scom_device *scom = file->private_data; + void __user *argp = (void __user *)arg; + int rc = -ENOTTY; + + mutex_lock(&scom->lock); + if (scom->dead) { + mutex_unlock(&scom->lock); + return -ENODEV; + } + switch(cmd) { + case FSI_SCOM_CHECK: + rc = scom_check(scom, argp); + break; + case FSI_SCOM_READ: + rc = scom_raw_read(scom, argp); + break; + case FSI_SCOM_WRITE: + rc = scom_raw_write(scom, argp); + break; + case FSI_SCOM_RESET: + rc = scom_reset(scom, argp); + break; + } + mutex_unlock(&scom->lock); + return rc; +} + +static int scom_open(struct inode *inode, struct file *file) +{ + struct scom_device *scom = container_of(inode->i_cdev, struct scom_device, cdev); + + file->private_data = scom; + + return 0; +} + static const struct file_operations scom_fops = { - .owner = THIS_MODULE, - .llseek = scom_llseek, - .read = scom_read, - .write = scom_write, + .owner = THIS_MODULE, + .open = scom_open, + .llseek = scom_llseek, + .read = scom_read, + .write = scom_write, + .unlocked_ioctl = scom_ioctl, }; +static void scom_free(struct device *dev) +{ + struct scom_device *scom = container_of(dev, struct scom_device, dev); + + put_device(&scom->fsi_dev->dev); + kfree(scom); +} + static int scom_probe(struct device *dev) { - uint32_t data; struct fsi_device *fsi_dev = to_fsi_dev(dev); struct scom_device *scom; + int rc, didx; - scom = devm_kzalloc(dev, sizeof(*scom), GFP_KERNEL); + scom = kzalloc(sizeof(*scom), GFP_KERNEL); if (!scom) return -ENOMEM; + dev_set_drvdata(dev, scom); + mutex_init(&scom->lock); - scom->idx = ida_simple_get(&scom_ida, 1, INT_MAX, GFP_KERNEL); - snprintf(scom->name, sizeof(scom->name), "scom%d", scom->idx); + /* Grab a reference to the device (parent of our cdev), we'll drop it later */ + if (!get_device(dev)) { + kfree(scom); + return -ENODEV; + } scom->fsi_dev = fsi_dev; - scom->mdev.minor = MISC_DYNAMIC_MINOR; - scom->mdev.fops = &scom_fops; - scom->mdev.name = scom->name; - scom->mdev.parent = dev; - list_add(&scom->link, &scom_devices); - data = cpu_to_be32(SCOM_RESET_CMD); - fsi_device_write(fsi_dev, SCOM_RESET_REG, &data, sizeof(uint32_t)); + /* Create chardev for userspace access */ + scom->dev.type = &fsi_cdev_type; + scom->dev.parent = dev; + scom->dev.release = scom_free; + device_initialize(&scom->dev); + + /* Allocate a minor in the FSI space */ + rc = fsi_get_new_minor(fsi_dev, fsi_dev_scom, &scom->dev.devt, &didx); + if (rc) + goto err; + + dev_set_name(&scom->dev, "scom%d", didx); + cdev_init(&scom->cdev, &scom_fops); + rc = cdev_device_add(&scom->cdev, &scom->dev); + if (rc) { + dev_err(dev, "Error %d creating char device %s\n", + rc, dev_name(&scom->dev)); + goto err_free_minor; + } - return misc_register(&scom->mdev); + return 0; + err_free_minor: + fsi_free_minor(scom->dev.devt); + err: + put_device(&scom->dev); + return rc; } static int scom_remove(struct device *dev) { - struct scom_device *scom, *scom_tmp; - struct fsi_device *fsi_dev = to_fsi_dev(dev); + struct scom_device *scom = dev_get_drvdata(dev); - list_for_each_entry_safe(scom, scom_tmp, &scom_devices, link) { - if (scom->fsi_dev == fsi_dev) { - list_del(&scom->link); - ida_simple_remove(&scom_ida, scom->idx); - misc_deregister(&scom->mdev); - } - } + mutex_lock(&scom->lock); + scom->dead = true; + mutex_unlock(&scom->lock); + cdev_device_del(&scom->cdev, &scom->dev); + fsi_free_minor(scom->dev.devt); + put_device(&scom->dev); return 0; } @@ -239,20 +662,11 @@ static struct fsi_driver scom_drv = { static int scom_init(void) { - INIT_LIST_HEAD(&scom_devices); return fsi_driver_register(&scom_drv); } static void scom_exit(void) { - struct list_head *pos; - struct scom_device *scom; - - list_for_each(pos, &scom_devices) { - scom = list_entry(pos, struct scom_device, link); - misc_deregister(&scom->mdev); - devm_kfree(&scom->fsi_dev->dev, scom); - } fsi_driver_unregister(&scom_drv); } |