Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+105
View File
@@ -0,0 +1,105 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# FSI subsystem
#
menuconfig FSI
tristate "FSI support"
depends on OF
select CRC4
help
FSI - the FRU Support Interface - is a simple bus for low-level
access to POWER-based hardware.
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
select CRC4
help
This option enables a FSI master driver using GPIO lines.
config FSI_MASTER_HUB
tristate "FSI hub master"
help
This option enables a FSI hub master driver. Hub is a type of FSI
master that is connected to the upstream master via a slave. Hubs
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
select GENERIC_ALLOCATOR
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_MASTER_ASPEED
tristate "FSI ASPEED master"
depends on HAS_IOMEM
help
This option enables a FSI master that is present behind an OPB bridge
in the AST2600.
Enable it for your BMC kernel in an OpenPower or IBM Power system.
config FSI_MASTER_I2CR
tristate "IBM I2C Responder virtual FSI master"
depends on I2C
help
This option enables a virtual FSI master in order to access a CFAM
behind an IBM I2C Responder (I2CR) chip. The I2CR is an I2C device
that translates I2C commands to CFAM or SCOM operations, effectively
implementing an FSI master and bus.
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.
config FSI_OCC
tristate "OCC SBEFIFO client device driver"
depends on FSI_SBEFIFO
help
This option enables an SBEFIFO based On-Chip Controller (OCC) device
driver. The OCC is a device embedded on a POWER processor that collects
and aggregates sensor data from the processor and system. The OCC can
provide the raw sensor data as well as perform thermal and power
management on the system.
config I2CR_SCOM
tristate "IBM I2C Responder SCOM driver"
depends on FSI_MASTER_I2CR
help
This option enables an I2C Responder based SCOM device driver. The
I2CR has the capability to directly perform SCOM operations instead
of using the FSI2PIB engine.
endif
+12
View File
@@ -0,0 +1,12 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_FSI) += fsi-core.o
obj-$(CONFIG_FSI_MASTER_HUB) += fsi-master-hub.o
obj-$(CONFIG_FSI_MASTER_ASPEED) += fsi-master-aspeed.o
obj-$(CONFIG_FSI_MASTER_GPIO) += fsi-master-gpio.o
obj-$(CONFIG_FSI_MASTER_I2CR) += fsi-master-i2cr.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
obj-$(CONFIG_FSI_OCC) += fsi-occ.o
obj-$(CONFIG_I2CR_SCOM) += i2cr-scom.o
+157
View File
@@ -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 */
File diff suppressed because it is too large Load Diff
+674
View File
@@ -0,0 +1,674 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (C) IBM Corporation 2018
// FSI master driver for AST2600
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/fsi.h>
#include <linux/io.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/iopoll.h>
#include <linux/gpio/consumer.h>
#include "fsi-master.h"
struct fsi_master_aspeed {
struct fsi_master master;
struct mutex lock; /* protect HW access */
struct device *dev;
void __iomem *base;
struct clk *clk;
struct gpio_desc *cfam_reset_gpio;
};
#define to_fsi_master_aspeed(m) \
container_of(m, struct fsi_master_aspeed, master)
/* Control register (size 0x400) */
static const u32 ctrl_base = 0x80000000;
static const u32 fsi_base = 0xa0000000;
#define OPB_FSI_VER 0x00
#define OPB_TRIGGER 0x04
#define OPB_CTRL_BASE 0x08
#define OPB_FSI_BASE 0x0c
#define OPB_CLK_SYNC 0x3c
#define OPB_IRQ_CLEAR 0x40
#define OPB_IRQ_MASK 0x44
#define OPB_IRQ_STATUS 0x48
#define OPB0_SELECT 0x10
#define OPB0_RW 0x14
#define OPB0_XFER_SIZE 0x18
#define OPB0_FSI_ADDR 0x1c
#define OPB0_FSI_DATA_W 0x20
#define OPB0_STATUS 0x80
#define OPB0_FSI_DATA_R 0x84
#define OPB0_WRITE_ORDER1 0x4c
#define OPB0_WRITE_ORDER2 0x50
#define OPB1_WRITE_ORDER1 0x54
#define OPB1_WRITE_ORDER2 0x58
#define OPB0_READ_ORDER1 0x5c
#define OPB1_READ_ORDER2 0x60
#define OPB_RETRY_COUNTER 0x64
/* OPBn_STATUS */
#define STATUS_HALFWORD_ACK BIT(0)
#define STATUS_FULLWORD_ACK BIT(1)
#define STATUS_ERR_ACK BIT(2)
#define STATUS_RETRY BIT(3)
#define STATUS_TIMEOUT BIT(4)
/* OPB_IRQ_MASK */
#define OPB1_XFER_ACK_EN BIT(17)
#define OPB0_XFER_ACK_EN BIT(16)
/* OPB_RW */
#define CMD_READ BIT(0)
#define CMD_WRITE 0
/* OPBx_XFER_SIZE */
#define XFER_FULLWORD (BIT(1) | BIT(0))
#define XFER_HALFWORD (BIT(0))
#define XFER_BYTE (0)
#define CREATE_TRACE_POINTS
#include <trace/events/fsi_master_aspeed.h>
#define FSI_LINK_ENABLE_SETUP_TIME 10 /* in mS */
/* Run the bus at maximum speed by default */
#define FSI_DIVISOR_DEFAULT 1
#define FSI_DIVISOR_CABLED 2
static u16 aspeed_fsi_divisor = FSI_DIVISOR_DEFAULT;
module_param_named(bus_div,aspeed_fsi_divisor, ushort, 0);
#define OPB_POLL_TIMEOUT 500
static int __opb_write(struct fsi_master_aspeed *aspeed, u32 addr,
u32 val, u32 transfer_size)
{
void __iomem *base = aspeed->base;
u32 reg, status;
int ret;
/*
* The ordering of these writes up until the trigger
* write does not matter, so use writel_relaxed.
*/
writel_relaxed(CMD_WRITE, base + OPB0_RW);
writel_relaxed(transfer_size, base + OPB0_XFER_SIZE);
writel_relaxed(addr, base + OPB0_FSI_ADDR);
writel_relaxed(val, base + OPB0_FSI_DATA_W);
writel_relaxed(0x1, base + OPB_IRQ_CLEAR);
writel(0x1, base + OPB_TRIGGER);
ret = readl_poll_timeout(base + OPB_IRQ_STATUS, reg,
(reg & OPB0_XFER_ACK_EN) != 0,
0, OPB_POLL_TIMEOUT);
status = readl(base + OPB0_STATUS);
trace_fsi_master_aspeed_opb_write(addr, val, transfer_size, status, reg);
/* Return error when poll timed out */
if (ret)
return ret;
/* Command failed, master will reset */
if (status & STATUS_ERR_ACK)
return -EIO;
return 0;
}
static int opb_writeb(struct fsi_master_aspeed *aspeed, u32 addr, u8 val)
{
return __opb_write(aspeed, addr, val, XFER_BYTE);
}
static int opb_writew(struct fsi_master_aspeed *aspeed, u32 addr, __be16 val)
{
return __opb_write(aspeed, addr, (__force u16)val, XFER_HALFWORD);
}
static int opb_writel(struct fsi_master_aspeed *aspeed, u32 addr, __be32 val)
{
return __opb_write(aspeed, addr, (__force u32)val, XFER_FULLWORD);
}
static int __opb_read(struct fsi_master_aspeed *aspeed, uint32_t addr,
u32 transfer_size, void *out)
{
void __iomem *base = aspeed->base;
u32 result, reg;
int status, ret;
/*
* The ordering of these writes up until the trigger
* write does not matter, so use writel_relaxed.
*/
writel_relaxed(CMD_READ, base + OPB0_RW);
writel_relaxed(transfer_size, base + OPB0_XFER_SIZE);
writel_relaxed(addr, base + OPB0_FSI_ADDR);
writel_relaxed(0x1, base + OPB_IRQ_CLEAR);
writel(0x1, base + OPB_TRIGGER);
ret = readl_poll_timeout(base + OPB_IRQ_STATUS, reg,
(reg & OPB0_XFER_ACK_EN) != 0,
0, OPB_POLL_TIMEOUT);
status = readl(base + OPB0_STATUS);
result = readl(base + OPB0_FSI_DATA_R);
trace_fsi_master_aspeed_opb_read(addr, transfer_size, result,
readl(base + OPB0_STATUS),
reg);
/* Return error when poll timed out */
if (ret)
return ret;
/* Command failed, master will reset */
if (status & STATUS_ERR_ACK)
return -EIO;
if (out) {
switch (transfer_size) {
case XFER_BYTE:
*(u8 *)out = result;
break;
case XFER_HALFWORD:
*(u16 *)out = result;
break;
case XFER_FULLWORD:
*(u32 *)out = result;
break;
default:
return -EINVAL;
}
}
return 0;
}
static int opb_readl(struct fsi_master_aspeed *aspeed, uint32_t addr, __be32 *out)
{
return __opb_read(aspeed, addr, XFER_FULLWORD, out);
}
static int opb_readw(struct fsi_master_aspeed *aspeed, uint32_t addr, __be16 *out)
{
return __opb_read(aspeed, addr, XFER_HALFWORD, (void *)out);
}
static int opb_readb(struct fsi_master_aspeed *aspeed, uint32_t addr, u8 *out)
{
return __opb_read(aspeed, addr, XFER_BYTE, (void *)out);
}
static int check_errors(struct fsi_master_aspeed *aspeed, int err)
{
int ret;
if (trace_fsi_master_aspeed_opb_error_enabled()) {
__be32 mresp0, mstap0, mesrb0;
opb_readl(aspeed, ctrl_base + FSI_MRESP0, &mresp0);
opb_readl(aspeed, ctrl_base + FSI_MSTAP0, &mstap0);
opb_readl(aspeed, ctrl_base + FSI_MESRB0, &mesrb0);
trace_fsi_master_aspeed_opb_error(
be32_to_cpu(mresp0),
be32_to_cpu(mstap0),
be32_to_cpu(mesrb0));
}
if (err == -EIO) {
/* Check MAEB (0x70) ? */
/* Then clear errors in master */
ret = opb_writel(aspeed, ctrl_base + FSI_MRESP0,
cpu_to_be32(FSI_MRESP_RST_ALL_MASTER));
if (ret) {
/* TODO: log? return different code? */
return ret;
}
/* TODO: confirm that 0x70 was okay */
}
/* This will pass through timeout errors */
return err;
}
static int aspeed_master_read(struct fsi_master *master, int link,
uint8_t id, uint32_t addr, void *val, size_t size)
{
struct fsi_master_aspeed *aspeed = to_fsi_master_aspeed(master);
int ret;
if (id > 0x3)
return -EINVAL;
addr |= id << 21;
addr += link * FSI_HUB_LINK_SIZE;
mutex_lock(&aspeed->lock);
switch (size) {
case 1:
ret = opb_readb(aspeed, fsi_base + addr, val);
break;
case 2:
ret = opb_readw(aspeed, fsi_base + addr, val);
break;
case 4:
ret = opb_readl(aspeed, fsi_base + addr, val);
break;
default:
ret = -EINVAL;
goto done;
}
ret = check_errors(aspeed, ret);
done:
mutex_unlock(&aspeed->lock);
return ret;
}
static int aspeed_master_write(struct fsi_master *master, int link,
uint8_t id, uint32_t addr, const void *val, size_t size)
{
struct fsi_master_aspeed *aspeed = to_fsi_master_aspeed(master);
int ret;
if (id > 0x3)
return -EINVAL;
addr |= id << 21;
addr += link * FSI_HUB_LINK_SIZE;
mutex_lock(&aspeed->lock);
switch (size) {
case 1:
ret = opb_writeb(aspeed, fsi_base + addr, *(u8 *)val);
break;
case 2:
ret = opb_writew(aspeed, fsi_base + addr, *(__be16 *)val);
break;
case 4:
ret = opb_writel(aspeed, fsi_base + addr, *(__be32 *)val);
break;
default:
ret = -EINVAL;
goto done;
}
ret = check_errors(aspeed, ret);
done:
mutex_unlock(&aspeed->lock);
return ret;
}
static int aspeed_master_link_enable(struct fsi_master *master, int link,
bool enable)
{
struct fsi_master_aspeed *aspeed = to_fsi_master_aspeed(master);
int idx, bit, ret;
__be32 reg;
idx = link / 32;
bit = link % 32;
reg = cpu_to_be32(0x80000000 >> bit);
mutex_lock(&aspeed->lock);
if (!enable) {
ret = opb_writel(aspeed, ctrl_base + FSI_MCENP0 + (4 * idx), reg);
goto done;
}
ret = opb_writel(aspeed, ctrl_base + FSI_MSENP0 + (4 * idx), reg);
if (ret)
goto done;
mdelay(FSI_LINK_ENABLE_SETUP_TIME);
done:
mutex_unlock(&aspeed->lock);
return ret;
}
static int aspeed_master_term(struct fsi_master *master, int link, uint8_t id)
{
uint32_t addr;
__be32 cmd;
addr = 0x4;
cmd = cpu_to_be32(0xecc00000);
return aspeed_master_write(master, link, id, addr, &cmd, 4);
}
static int aspeed_master_break(struct fsi_master *master, int link)
{
uint32_t addr;
__be32 cmd;
addr = 0x0;
cmd = cpu_to_be32(0xc0de0000);
return aspeed_master_write(master, link, 0, addr, &cmd, 4);
}
static void aspeed_master_release(struct device *dev)
{
struct fsi_master_aspeed *aspeed =
to_fsi_master_aspeed(to_fsi_master(dev));
kfree(aspeed);
}
/* mmode encoders */
static inline u32 fsi_mmode_crs0(u32 x)
{
return (x & FSI_MMODE_CRS0MASK) << FSI_MMODE_CRS0SHFT;
}
static inline u32 fsi_mmode_crs1(u32 x)
{
return (x & FSI_MMODE_CRS1MASK) << FSI_MMODE_CRS1SHFT;
}
static int aspeed_master_init(struct fsi_master_aspeed *aspeed)
{
__be32 reg;
reg = cpu_to_be32(FSI_MRESP_RST_ALL_MASTER | FSI_MRESP_RST_ALL_LINK
| FSI_MRESP_RST_MCR | FSI_MRESP_RST_PYE);
opb_writel(aspeed, ctrl_base + FSI_MRESP0, reg);
/* Initialize the MFSI (hub master) engine */
reg = cpu_to_be32(FSI_MRESP_RST_ALL_MASTER | FSI_MRESP_RST_ALL_LINK
| FSI_MRESP_RST_MCR | FSI_MRESP_RST_PYE);
opb_writel(aspeed, ctrl_base + FSI_MRESP0, reg);
reg = cpu_to_be32(FSI_MECTRL_EOAE | FSI_MECTRL_P8_AUTO_TERM);
opb_writel(aspeed, ctrl_base + FSI_MECTRL, reg);
reg = cpu_to_be32(FSI_MMODE_ECRC | FSI_MMODE_EPC | FSI_MMODE_RELA
| fsi_mmode_crs0(aspeed_fsi_divisor)
| fsi_mmode_crs1(aspeed_fsi_divisor)
| FSI_MMODE_P8_TO_LSB);
dev_info(aspeed->dev, "mmode set to %08x (divisor %d)\n",
be32_to_cpu(reg), aspeed_fsi_divisor);
opb_writel(aspeed, ctrl_base + FSI_MMODE, reg);
reg = cpu_to_be32(0xffff0000);
opb_writel(aspeed, ctrl_base + FSI_MDLYR, reg);
reg = cpu_to_be32(~0);
opb_writel(aspeed, ctrl_base + FSI_MSENP0, reg);
/* Leave enabled long enough for master logic to set up */
mdelay(FSI_LINK_ENABLE_SETUP_TIME);
opb_writel(aspeed, ctrl_base + FSI_MCENP0, reg);
opb_readl(aspeed, ctrl_base + FSI_MAEB, NULL);
reg = cpu_to_be32(FSI_MRESP_RST_ALL_MASTER | FSI_MRESP_RST_ALL_LINK);
opb_writel(aspeed, ctrl_base + FSI_MRESP0, reg);
opb_readl(aspeed, ctrl_base + FSI_MLEVP0, NULL);
/* Reset the master bridge */
reg = cpu_to_be32(FSI_MRESB_RST_GEN);
opb_writel(aspeed, ctrl_base + FSI_MRESB0, reg);
reg = cpu_to_be32(FSI_MRESB_RST_ERR);
opb_writel(aspeed, ctrl_base + FSI_MRESB0, reg);
return 0;
}
static ssize_t cfam_reset_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct fsi_master_aspeed *aspeed = dev_get_drvdata(dev);
trace_fsi_master_aspeed_cfam_reset(true);
mutex_lock(&aspeed->lock);
gpiod_set_value(aspeed->cfam_reset_gpio, 1);
usleep_range(900, 1000);
gpiod_set_value(aspeed->cfam_reset_gpio, 0);
usleep_range(900, 1000);
opb_writel(aspeed, ctrl_base + FSI_MRESP0, cpu_to_be32(FSI_MRESP_RST_ALL_MASTER));
mutex_unlock(&aspeed->lock);
trace_fsi_master_aspeed_cfam_reset(false);
return count;
}
static DEVICE_ATTR(cfam_reset, 0200, NULL, cfam_reset_store);
static int setup_cfam_reset(struct fsi_master_aspeed *aspeed)
{
struct device *dev = aspeed->dev;
struct gpio_desc *gpio;
int rc;
gpio = devm_gpiod_get_optional(dev, "cfam-reset", GPIOD_OUT_LOW);
if (IS_ERR(gpio))
return PTR_ERR(gpio);
if (!gpio)
return 0;
aspeed->cfam_reset_gpio = gpio;
rc = device_create_file(dev, &dev_attr_cfam_reset);
if (rc) {
devm_gpiod_put(dev, gpio);
return rc;
}
return 0;
}
static int tacoma_cabled_fsi_fixup(struct device *dev)
{
struct gpio_desc *routing_gpio, *mux_gpio;
int gpio;
/*
* The routing GPIO is a jumper indicating we should mux for the
* externally connected FSI cable.
*/
routing_gpio = devm_gpiod_get_optional(dev, "fsi-routing",
GPIOD_IN | GPIOD_FLAGS_BIT_NONEXCLUSIVE);
if (IS_ERR(routing_gpio))
return PTR_ERR(routing_gpio);
if (!routing_gpio)
return 0;
mux_gpio = devm_gpiod_get_optional(dev, "fsi-mux", GPIOD_ASIS);
if (IS_ERR(mux_gpio))
return PTR_ERR(mux_gpio);
if (!mux_gpio)
return 0;
gpio = gpiod_get_value(routing_gpio);
if (gpio < 0)
return gpio;
/* If the routing GPIO is high we should set the mux to low. */
if (gpio) {
/*
* Cable signal integrity means we should run the bus
* slightly slower. Do not override if a kernel param
* has already overridden.
*/
if (aspeed_fsi_divisor == FSI_DIVISOR_DEFAULT)
aspeed_fsi_divisor = FSI_DIVISOR_CABLED;
gpiod_direction_output(mux_gpio, 0);
dev_info(dev, "FSI configured for external cable\n");
} else {
gpiod_direction_output(mux_gpio, 1);
}
devm_gpiod_put(dev, routing_gpio);
return 0;
}
static int fsi_master_aspeed_probe(struct platform_device *pdev)
{
struct fsi_master_aspeed *aspeed;
int rc, links, reg;
__be32 raw;
rc = tacoma_cabled_fsi_fixup(&pdev->dev);
if (rc) {
dev_err(&pdev->dev, "Tacoma FSI cable fixup failed\n");
return rc;
}
aspeed = kzalloc_obj(*aspeed);
if (!aspeed)
return -ENOMEM;
aspeed->dev = &pdev->dev;
aspeed->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(aspeed->base)) {
rc = PTR_ERR(aspeed->base);
goto err_free_aspeed;
}
aspeed->clk = devm_clk_get(aspeed->dev, NULL);
if (IS_ERR(aspeed->clk)) {
dev_err(aspeed->dev, "couldn't get clock\n");
rc = PTR_ERR(aspeed->clk);
goto err_free_aspeed;
}
rc = clk_prepare_enable(aspeed->clk);
if (rc) {
dev_err(aspeed->dev, "couldn't enable clock\n");
goto err_free_aspeed;
}
rc = setup_cfam_reset(aspeed);
if (rc) {
dev_err(&pdev->dev, "CFAM reset GPIO setup failed\n");
}
writel(0x1, aspeed->base + OPB_CLK_SYNC);
writel(OPB1_XFER_ACK_EN | OPB0_XFER_ACK_EN,
aspeed->base + OPB_IRQ_MASK);
/* TODO: determine an appropriate value */
writel(0x10, aspeed->base + OPB_RETRY_COUNTER);
writel(ctrl_base, aspeed->base + OPB_CTRL_BASE);
writel(fsi_base, aspeed->base + OPB_FSI_BASE);
/* Set read data order */
writel(0x00030b1b, aspeed->base + OPB0_READ_ORDER1);
/* Set write data order */
writel(0x0011101b, aspeed->base + OPB0_WRITE_ORDER1);
writel(0x0c330f3f, aspeed->base + OPB0_WRITE_ORDER2);
/*
* Select OPB0 for all operations.
* Will need to be reworked when enabling DMA or anything that uses
* OPB1.
*/
writel(0x1, aspeed->base + OPB0_SELECT);
rc = opb_readl(aspeed, ctrl_base + FSI_MVER, &raw);
if (rc) {
dev_err(&pdev->dev, "failed to read hub version\n");
goto err_release;
}
reg = be32_to_cpu(raw);
links = (reg >> 8) & 0xff;
dev_info(&pdev->dev, "hub version %08x (%d links)\n", reg, links);
aspeed->master.dev.parent = &pdev->dev;
aspeed->master.dev.release = aspeed_master_release;
aspeed->master.dev.of_node = of_node_get(dev_of_node(&pdev->dev));
aspeed->master.n_links = links;
aspeed->master.read = aspeed_master_read;
aspeed->master.write = aspeed_master_write;
aspeed->master.send_break = aspeed_master_break;
aspeed->master.term = aspeed_master_term;
aspeed->master.link_enable = aspeed_master_link_enable;
dev_set_drvdata(&pdev->dev, aspeed);
mutex_init(&aspeed->lock);
aspeed_master_init(aspeed);
rc = fsi_master_register(&aspeed->master);
if (rc)
goto err_release;
/* At this point, fsi_master_register performs the device_initialize(),
* and holds the sole reference on master.dev. This means the device
* will be freed (via ->release) during any subsequent call to
* fsi_master_unregister. We add our own reference to it here, so we
* can perform cleanup (in _remove()) without it being freed before
* we're ready.
*/
get_device(&aspeed->master.dev);
return 0;
err_release:
clk_disable_unprepare(aspeed->clk);
err_free_aspeed:
kfree(aspeed);
return rc;
}
static void fsi_master_aspeed_remove(struct platform_device *pdev)
{
struct fsi_master_aspeed *aspeed = platform_get_drvdata(pdev);
fsi_master_unregister(&aspeed->master);
clk_disable_unprepare(aspeed->clk);
}
static const struct of_device_id fsi_master_aspeed_match[] = {
{ .compatible = "aspeed,ast2600-fsi-master" },
{ },
};
MODULE_DEVICE_TABLE(of, fsi_master_aspeed_match);
static struct platform_driver fsi_master_aspeed_driver = {
.driver = {
.name = "fsi-master-aspeed",
.of_match_table = fsi_master_aspeed_match,
},
.probe = fsi_master_aspeed_probe,
.remove = fsi_master_aspeed_remove,
};
module_platform_driver(fsi_master_aspeed_driver);
MODULE_DESCRIPTION("FSI master driver for AST2600");
MODULE_LICENSE("GPL");
File diff suppressed because it is too large Load Diff
+896
View File
@@ -0,0 +1,896 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* 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 "fsi-master.h"
#define FSI_GPIO_STD_DLY 1 /* Standard pin delay in nS */
#define LAST_ADDR_INVALID 0x1
struct fsi_master_gpio {
struct fsi_master master;
struct device *dev;
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
#include <trace/events/fsi_master_gpio.h>
#define to_fsi_master_gpio(m) container_of(m, struct fsi_master_gpio, master)
struct fsi_gpio_msg {
uint64_t msg;
uint8_t bits;
};
static void clock_toggle(struct fsi_master_gpio *master, int count)
{
int i;
for (i = 0; i < count; i++) {
if (!master->no_delays)
ndelay(FSI_GPIO_STD_DLY);
gpiod_set_value(master->gpio_clk, 0);
if (!master->no_delays)
ndelay(FSI_GPIO_STD_DLY);
gpiod_set_value(master->gpio_clk, 1);
}
}
static int sda_clock_in(struct fsi_master_gpio *master)
{
int in;
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;
}
static void sda_out(struct fsi_master_gpio *master, int value)
{
gpiod_set_value(master->gpio_data, value);
}
static void set_sda_input(struct fsi_master_gpio *master)
{
gpiod_direction_input(master->gpio_data);
gpiod_set_value(master->gpio_trans, 0);
}
static void set_sda_output(struct fsi_master_gpio *master, int value)
{
gpiod_set_value(master->gpio_trans, 1);
gpiod_direction_output(master->gpio_data, 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)
{
uint8_t bit, in_bit;
set_sda_input(master);
for (bit = 0; bit < num_bits; bit++) {
in_bit = sda_clock_in(master);
msg->msg <<= 1;
msg->msg |= ~in_bit & 0x1; /* Data is active low */
}
msg->bits += num_bits;
trace_fsi_master_gpio_in(master, num_bits, msg->msg);
}
static void serial_out(struct fsi_master_gpio *master,
const struct fsi_gpio_msg *cmd)
{
uint8_t bit;
uint64_t msg = ~cmd->msg; /* Data is active low */
uint64_t sda_mask = 0x1ULL << (cmd->bits - 1);
uint64_t last_bit = ~0;
int next_bit;
trace_fsi_master_gpio_out(master, cmd->bits, cmd->msg);
if (!cmd->bits) {
dev_warn(master->dev, "trying to output 0 bits\n");
return;
}
set_sda_output(master, 0);
/* Send the start bit */
sda_out(master, 0);
clock_toggle(master, 1);
/* Send the message */
for (bit = 0; bit < cmd->bits; bit++) {
next_bit = (msg & sda_mask) >> (cmd->bits - 1);
if (last_bit ^ next_bit) {
sda_out(master, next_bit);
last_bit = next_bit;
}
clock_toggle(master, 1);
msg <<= 1;
}
}
static void msg_push_bits(struct fsi_gpio_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_gpio_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 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/Relative/Same Address command
*/
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, 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_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
* (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);
}
static void build_dpoll_command(struct fsi_gpio_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);
}
static void build_epoll_command(struct fsi_gpio_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);
}
static void build_term_command(struct fsi_gpio_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);
}
/*
* 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 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;
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_MASTER_MTOE_COUNT; i++) {
msg.bits = 0;
msg.msg = 0;
serial_in(master, &msg, 1);
if (msg.msg)
break;
}
if (i == FSI_MASTER_MTOE_COUNT) {
dev_dbg(master->dev,
"Master time out waiting for response\n");
local_irq_restore(flags);
return -ETIMEDOUT;
}
msg.bits = 0;
msg.msg = 0;
/* Read slave ID & response tag */
serial_in(master, &msg, 4);
tag = msg.msg & 0x3;
/* If we have an ACK and we're expecting data, clock the data in too */
if (tag == FSI_RESP_ACK && data_size)
serial_in(master, &msg, data_size * 8);
/* read CRC */
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) {
/* 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)
*msgp = msg;
if (tagp)
*tagp = tag;
return 0;
}
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_RESP_ACK) {
dev_err(master->dev, "TERM failed; response %d\n", tag);
return -EIO;
}
return 0;
}
static int poll_for_response(struct fsi_master_gpio *master,
uint8_t slave, uint8_t size, void *data)
{
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);
/* 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_RESP_ACK:
if (size && data) {
uint64_t val = response.msg;
/* clear crc & mask */
val >>= 4;
val &= (1ull << (size * 8)) - 1;
for (i = 0; i < size; i++) {
data_byte[size-i-1] = val;
val >>= 8;
}
}
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.
*/
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_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;
}
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 send_request(struct fsi_master_gpio *master,
struct fsi_gpio_msg *cmd)
{
unsigned long flags;
if (master->external_mode)
return -EBUSY;
local_irq_save(flags);
serial_out(master, cmd);
echo_delay(master);
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;
}
static int fsi_master_gpio_read(struct fsi_master *_master, int link,
uint8_t id, uint32_t addr, void *val, size_t size)
{
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_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,
uint8_t id, uint32_t addr, const void *val, size_t size)
{
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_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,
int link, uint8_t id)
{
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);
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)
{
struct fsi_master_gpio *master = to_fsi_master_gpio(_master);
unsigned long flags;
if (link != 0)
return -ENODEV;
trace_fsi_master_gpio_break(master);
mutex_lock(&master->cmd_lock);
if (master->external_mode) {
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);
sda_out(master, 0);
clock_toggle(master, FSI_BREAK_CLOCKS);
echo_delay(master);
sda_out(master, 1);
clock_toggle(master, FSI_POST_BREAK_CLOCKS);
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);
return 0;
}
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);
gpiod_direction_output(master->gpio_clk, 1);
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)
{
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_gpio_link_enable(struct fsi_master *_master, int link,
bool enable)
{
struct fsi_master_gpio *master = to_fsi_master_gpio(_master);
int rc = -EBUSY;
if (link != 0)
return -ENODEV;
mutex_lock(&master->cmd_lock);
if (!master->external_mode) {
gpiod_set_value(master->gpio_enable, enable ? 1 : 0);
rc = 0;
}
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)
{
struct fsi_master_gpio *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_gpio *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->cmd_lock);
if (external_mode == master->external_mode) {
mutex_unlock(&master->cmd_lock);
return count;
}
master->external_mode = external_mode;
if (master->external_mode)
fsi_master_gpio_init_external(master);
else
fsi_master_gpio_init(master);
mutex_unlock(&master->cmd_lock);
fsi_master_rescan(&master->master);
return count;
}
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(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 = kzalloc_obj(*master);
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");
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;
/*
* 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;
master->master.write = fsi_master_gpio_write;
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);
mutex_init(&master->cmd_lock);
fsi_master_gpio_init(master);
rc = device_create_file(&pdev->dev, &dev_attr_external_mode);
if (rc)
goto err_free;
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 void fsi_master_gpio_remove(struct platform_device *pdev)
{
struct fsi_master_gpio *master = platform_get_drvdata(pdev);
device_remove_file(&pdev->dev, &dev_attr_external_mode);
fsi_master_unregister(&master->master);
}
static const struct of_device_id fsi_master_gpio_match[] = {
{ .compatible = "fsi-master-gpio" },
{ },
};
MODULE_DEVICE_TABLE(of, fsi_master_gpio_match);
static struct platform_driver fsi_master_gpio_driver = {
.driver = {
.name = "fsi-master-gpio",
.of_match_table = fsi_master_gpio_match,
},
.probe = fsi_master_gpio_probe,
.remove = fsi_master_gpio_remove,
};
module_platform_driver(fsi_master_gpio_driver);
MODULE_DESCRIPTION("A FSI master controller, using a simple GPIO bit-banging interface");
MODULE_LICENSE("GPL");
+296
View File
@@ -0,0 +1,296 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* FSI hub master driver
*
* Copyright (C) IBM Corporation 2016
*/
#include <linux/delay.h>
#include <linux/fsi.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/slab.h>
#include "fsi-master.h"
#define FSI_ENGID_HUB_MASTER 0x1c
#define FSI_LINK_ENABLE_SETUP_TIME 10 /* in mS */
/*
* FSI hub master support
*
* A hub master increases the number of potential target devices that the
* primary FSI master can access. For each link a primary master supports,
* each of those links can in turn be chained to a hub master with multiple
* links of its own.
*
* The hub is controlled by a set of control registers exposed as a regular fsi
* device (the hub->upstream device), and provides access to the downstream FSI
* bus as through an address range on the slave itself (->addr and ->size).
*
* [This differs from "cascaded" masters, which expose the entire downstream
* bus entirely through the fsi device address range, and so have a smaller
* accessible address space.]
*/
struct fsi_master_hub {
struct fsi_master master;
struct fsi_device *upstream;
uint32_t addr, size; /* slave-relative addr of */
/* master address space */
};
#define to_fsi_master_hub(m) container_of(m, struct fsi_master_hub, master)
static int hub_master_read(struct fsi_master *master, int link,
uint8_t id, uint32_t addr, void *val, size_t size)
{
struct fsi_master_hub *hub = to_fsi_master_hub(master);
if (id != 0)
return -EINVAL;
addr += hub->addr + (link * FSI_HUB_LINK_SIZE);
return fsi_slave_read(hub->upstream->slave, addr, val, size);
}
static int hub_master_write(struct fsi_master *master, int link,
uint8_t id, uint32_t addr, const void *val, size_t size)
{
struct fsi_master_hub *hub = to_fsi_master_hub(master);
if (id != 0)
return -EINVAL;
addr += hub->addr + (link * FSI_HUB_LINK_SIZE);
return fsi_slave_write(hub->upstream->slave, addr, val, size);
}
static int hub_master_break(struct fsi_master *master, int link)
{
uint32_t addr;
__be32 cmd;
addr = 0x4;
cmd = cpu_to_be32(0xc0de0000);
return hub_master_write(master, link, 0, addr, &cmd, sizeof(cmd));
}
static int hub_master_link_enable(struct fsi_master *master, int link,
bool enable)
{
struct fsi_master_hub *hub = to_fsi_master_hub(master);
int idx, bit;
__be32 reg;
int rc;
idx = link / 32;
bit = link % 32;
reg = cpu_to_be32(0x80000000 >> bit);
if (!enable)
return fsi_device_write(hub->upstream, FSI_MCENP0 + (4 * idx),
&reg, 4);
rc = fsi_device_write(hub->upstream, FSI_MSENP0 + (4 * idx), &reg, 4);
if (rc)
return rc;
mdelay(FSI_LINK_ENABLE_SETUP_TIME);
return 0;
}
static void hub_master_release(struct device *dev)
{
struct fsi_master_hub *hub = to_fsi_master_hub(to_fsi_master(dev));
kfree(hub);
}
/* mmode encoders */
static inline u32 fsi_mmode_crs0(u32 x)
{
return (x & FSI_MMODE_CRS0MASK) << FSI_MMODE_CRS0SHFT;
}
static inline u32 fsi_mmode_crs1(u32 x)
{
return (x & FSI_MMODE_CRS1MASK) << FSI_MMODE_CRS1SHFT;
}
static int hub_master_init(struct fsi_master_hub *hub)
{
struct fsi_device *dev = hub->upstream;
__be32 reg;
int rc;
reg = cpu_to_be32(FSI_MRESP_RST_ALL_MASTER | FSI_MRESP_RST_ALL_LINK
| FSI_MRESP_RST_MCR | FSI_MRESP_RST_PYE);
rc = fsi_device_write(dev, FSI_MRESP0, &reg, sizeof(reg));
if (rc)
return rc;
/* Initialize the MFSI (hub master) engine */
reg = cpu_to_be32(FSI_MRESP_RST_ALL_MASTER | FSI_MRESP_RST_ALL_LINK
| FSI_MRESP_RST_MCR | FSI_MRESP_RST_PYE);
rc = fsi_device_write(dev, FSI_MRESP0, &reg, sizeof(reg));
if (rc)
return rc;
reg = cpu_to_be32(FSI_MECTRL_EOAE | FSI_MECTRL_P8_AUTO_TERM);
rc = fsi_device_write(dev, FSI_MECTRL, &reg, sizeof(reg));
if (rc)
return rc;
reg = cpu_to_be32(FSI_MMODE_EIP | FSI_MMODE_ECRC | FSI_MMODE_EPC
| fsi_mmode_crs0(1) | fsi_mmode_crs1(1)
| FSI_MMODE_P8_TO_LSB);
rc = fsi_device_write(dev, FSI_MMODE, &reg, sizeof(reg));
if (rc)
return rc;
reg = cpu_to_be32(0xffff0000);
rc = fsi_device_write(dev, FSI_MDLYR, &reg, sizeof(reg));
if (rc)
return rc;
reg = cpu_to_be32(~0);
rc = fsi_device_write(dev, FSI_MSENP0, &reg, sizeof(reg));
if (rc)
return rc;
/* Leave enabled long enough for master logic to set up */
mdelay(FSI_LINK_ENABLE_SETUP_TIME);
rc = fsi_device_write(dev, FSI_MCENP0, &reg, sizeof(reg));
if (rc)
return rc;
rc = fsi_device_read(dev, FSI_MAEB, &reg, sizeof(reg));
if (rc)
return rc;
reg = cpu_to_be32(FSI_MRESP_RST_ALL_MASTER | FSI_MRESP_RST_ALL_LINK);
rc = fsi_device_write(dev, FSI_MRESP0, &reg, sizeof(reg));
if (rc)
return rc;
rc = fsi_device_read(dev, FSI_MLEVP0, &reg, sizeof(reg));
if (rc)
return rc;
/* Reset the master bridge */
reg = cpu_to_be32(FSI_MRESB_RST_GEN);
rc = fsi_device_write(dev, FSI_MRESB0, &reg, sizeof(reg));
if (rc)
return rc;
reg = cpu_to_be32(FSI_MRESB_RST_ERR);
return fsi_device_write(dev, FSI_MRESB0, &reg, sizeof(reg));
}
static int hub_master_probe(struct fsi_device *fsi_dev)
{
struct device *dev = &fsi_dev->dev;
struct fsi_master_hub *hub;
uint32_t reg, links;
__be32 __reg;
int rc;
rc = fsi_device_read(fsi_dev, FSI_MVER, &__reg, sizeof(__reg));
if (rc)
return rc;
reg = be32_to_cpu(__reg);
links = (reg >> 8) & 0xff;
dev_dbg(dev, "hub version %08x (%d links)\n", reg, links);
rc = fsi_slave_claim_range(fsi_dev->slave, FSI_HUB_LINK_OFFSET,
FSI_HUB_LINK_SIZE * links);
if (rc) {
dev_err(dev, "can't claim slave address range for links");
return rc;
}
hub = kzalloc_obj(*hub);
if (!hub) {
rc = -ENOMEM;
goto err_release;
}
hub->addr = FSI_HUB_LINK_OFFSET;
hub->size = FSI_HUB_LINK_SIZE * links;
hub->upstream = fsi_dev;
hub->master.dev.parent = dev;
hub->master.dev.release = hub_master_release;
hub->master.dev.of_node = of_node_get(dev_of_node(dev));
hub->master.n_links = links;
hub->master.read = hub_master_read;
hub->master.write = hub_master_write;
hub->master.send_break = hub_master_break;
hub->master.link_enable = hub_master_link_enable;
fsi_set_drvdata(fsi_dev, hub);
hub_master_init(hub);
rc = fsi_master_register(&hub->master);
if (rc)
goto err_release;
/* At this point, fsi_master_register performs the device_initialize(),
* and holds the sole reference on master.dev. This means the device
* will be freed (via ->release) during any subsequent call to
* fsi_master_unregister. We add our own reference to it here, so we
* can perform cleanup (in _remove()) without it being freed before
* we're ready.
*/
get_device(&hub->master.dev);
return 0;
err_release:
fsi_slave_release_range(fsi_dev->slave, FSI_HUB_LINK_OFFSET,
FSI_HUB_LINK_SIZE * links);
return rc;
}
static void hub_master_remove(struct fsi_device *fsi_dev)
{
struct fsi_master_hub *hub = fsi_get_drvdata(fsi_dev);
fsi_master_unregister(&hub->master);
fsi_slave_release_range(hub->upstream->slave, hub->addr, hub->size);
of_node_put(hub->master.dev.of_node);
/*
* master.dev will likely be ->release()ed after this, which free()s
* the hub
*/
put_device(&hub->master.dev);
}
static const struct fsi_device_id hub_master_ids[] = {
{
.engine_type = FSI_ENGID_HUB_MASTER,
.version = FSI_VERSION_ANY,
},
{ 0 }
};
static struct fsi_driver hub_master_driver = {
.id_table = hub_master_ids,
.probe = hub_master_probe,
.remove = hub_master_remove,
.drv = {
.name = "fsi-master-hub",
}
};
module_fsi_driver(hub_master_driver);
MODULE_DESCRIPTION("FSI hub master driver");
MODULE_LICENSE("GPL");
+315
View File
@@ -0,0 +1,315 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (C) IBM Corporation 2023 */
#include <linux/device.h>
#include <linux/fsi.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include "fsi-master-i2cr.h"
#define CREATE_TRACE_POINTS
#include <trace/events/fsi_master_i2cr.h>
#define I2CR_ADDRESS_CFAM(a) ((a) >> 2)
#define I2CR_INITIAL_PARITY true
#define I2CR_STATUS_CMD 0x60002
#define I2CR_STATUS_ERR BIT_ULL(61)
#define I2CR_ERROR_CMD 0x60004
#define I2CR_LOG_CMD 0x60008
static const u8 i2cr_cfam[] = {
0xc0, 0x02, 0x0d, 0xa6,
0x80, 0x01, 0x10, 0x02,
0x80, 0x01, 0x10, 0x02,
0x80, 0x01, 0x10, 0x02,
0x80, 0x01, 0x80, 0x52,
0x80, 0x01, 0x10, 0x02,
0x80, 0x01, 0x10, 0x02,
0x80, 0x01, 0x10, 0x02,
0x80, 0x01, 0x10, 0x02,
0x80, 0x01, 0x22, 0x2d,
0x00, 0x00, 0x00, 0x00,
0xde, 0xad, 0xc0, 0xde
};
static bool i2cr_check_parity32(u32 v, bool parity)
{
u32 i;
for (i = 0; i < 32; ++i) {
if (v & (1u << i))
parity = !parity;
}
return parity;
}
static bool i2cr_check_parity64(u64 v)
{
u32 i;
bool parity = I2CR_INITIAL_PARITY;
for (i = 0; i < 64; ++i) {
if (v & (1llu << i))
parity = !parity;
}
return parity;
}
static u32 i2cr_get_command(u32 address, bool parity)
{
address <<= 1;
if (i2cr_check_parity32(address, parity))
address |= 1;
return address;
}
static int i2cr_transfer(struct i2c_client *client, u32 command, u64 *data)
{
struct i2c_msg msgs[2];
int ret;
msgs[0].addr = client->addr;
msgs[0].flags = 0;
msgs[0].len = sizeof(command);
msgs[0].buf = (__u8 *)&command;
msgs[1].addr = client->addr;
msgs[1].flags = I2C_M_RD;
msgs[1].len = sizeof(*data);
msgs[1].buf = (__u8 *)data;
ret = i2c_transfer(client->adapter, msgs, 2);
if (ret == 2)
return 0;
trace_i2cr_i2c_error(client, command, ret);
if (ret < 0)
return ret;
return -EIO;
}
static int i2cr_check_status(struct i2c_client *client)
{
u64 status;
int ret;
ret = i2cr_transfer(client, I2CR_STATUS_CMD, &status);
if (ret)
return ret;
if (status & I2CR_STATUS_ERR) {
u32 buf[3] = { 0, 0, 0 };
u64 error;
u64 log;
i2cr_transfer(client, I2CR_ERROR_CMD, &error);
i2cr_transfer(client, I2CR_LOG_CMD, &log);
trace_i2cr_status_error(client, status, error, log);
buf[0] = I2CR_STATUS_CMD;
i2c_master_send(client, (const char *)buf, sizeof(buf));
buf[0] = I2CR_ERROR_CMD;
i2c_master_send(client, (const char *)buf, sizeof(buf));
buf[0] = I2CR_LOG_CMD;
i2c_master_send(client, (const char *)buf, sizeof(buf));
dev_err(&client->dev, "status:%016llx error:%016llx log:%016llx\n", status, error,
log);
return -EREMOTEIO;
}
trace_i2cr_status(client, status);
return 0;
}
int fsi_master_i2cr_read(struct fsi_master_i2cr *i2cr, u32 addr, u64 *data)
{
u32 command = i2cr_get_command(addr, I2CR_INITIAL_PARITY);
int ret;
mutex_lock(&i2cr->lock);
ret = i2cr_transfer(i2cr->client, command, data);
if (ret)
goto unlock;
ret = i2cr_check_status(i2cr->client);
if (ret)
goto unlock;
trace_i2cr_read(i2cr->client, command, data);
unlock:
mutex_unlock(&i2cr->lock);
return ret;
}
EXPORT_SYMBOL_GPL(fsi_master_i2cr_read);
int fsi_master_i2cr_write(struct fsi_master_i2cr *i2cr, u32 addr, u64 data)
{
u32 buf[3] = { 0 };
int ret;
buf[0] = i2cr_get_command(addr, i2cr_check_parity64(data));
memcpy(&buf[1], &data, sizeof(data));
mutex_lock(&i2cr->lock);
ret = i2c_master_send(i2cr->client, (const char *)buf, sizeof(buf));
if (ret == sizeof(buf)) {
ret = i2cr_check_status(i2cr->client);
if (!ret)
trace_i2cr_write(i2cr->client, buf[0], data);
} else {
trace_i2cr_i2c_error(i2cr->client, buf[0], ret);
if (ret >= 0)
ret = -EIO;
}
mutex_unlock(&i2cr->lock);
return ret;
}
EXPORT_SYMBOL_GPL(fsi_master_i2cr_write);
static int i2cr_read(struct fsi_master *master, int link, uint8_t id, uint32_t addr, void *val,
size_t size)
{
struct fsi_master_i2cr *i2cr = container_of(master, struct fsi_master_i2cr, master);
u64 data;
size_t i;
int ret;
if (link || id || (addr & 0xffff0000) || !(size == 1 || size == 2 || size == 4))
return -EINVAL;
/*
* The I2CR doesn't have CFAM or FSI slave address space - only the
* engines. In order for this to work with the FSI core, we need to
* emulate at minimum the CFAM config table so that the appropriate
* engines are discovered.
*/
if (addr < 0xc00) {
if (addr > sizeof(i2cr_cfam) - 4)
addr = (addr & 0x3) + (sizeof(i2cr_cfam) - 4);
memcpy(val, &i2cr_cfam[addr], size);
return 0;
}
ret = fsi_master_i2cr_read(i2cr, I2CR_ADDRESS_CFAM(addr), &data);
if (ret)
return ret;
/*
* FSI core expects up to 4 bytes BE back, while I2CR replied with LE
* bytes on the wire.
*/
for (i = 0; i < size; ++i)
((u8 *)val)[i] = ((u8 *)&data)[7 - i];
return 0;
}
static int i2cr_write(struct fsi_master *master, int link, uint8_t id, uint32_t addr,
const void *val, size_t size)
{
struct fsi_master_i2cr *i2cr = container_of(master, struct fsi_master_i2cr, master);
u64 data = 0;
size_t i;
if (link || id || (addr & 0xffff0000) || !(size == 1 || size == 2 || size == 4))
return -EINVAL;
/* I2CR writes to CFAM or FSI slave address are a successful no-op. */
if (addr < 0xc00)
return 0;
/*
* FSI core passes up to 4 bytes BE, while the I2CR expects LE bytes on
* the wire.
*/
for (i = 0; i < size; ++i)
((u8 *)&data)[7 - i] = ((u8 *)val)[i];
return fsi_master_i2cr_write(i2cr, I2CR_ADDRESS_CFAM(addr), data);
}
static void i2cr_release(struct device *dev)
{
struct fsi_master_i2cr *i2cr = to_fsi_master_i2cr(to_fsi_master(dev));
of_node_put(dev->of_node);
kfree(i2cr);
}
static int i2cr_probe(struct i2c_client *client)
{
struct fsi_master_i2cr *i2cr;
int ret;
i2cr = kzalloc_obj(*i2cr);
if (!i2cr)
return -ENOMEM;
/* Only one I2CR on any given I2C bus (fixed I2C device address) */
i2cr->master.idx = client->adapter->nr;
dev_set_name(&i2cr->master.dev, "i2cr%d", i2cr->master.idx);
i2cr->master.dev.parent = &client->dev;
i2cr->master.dev.of_node = of_node_get(dev_of_node(&client->dev));
i2cr->master.dev.release = i2cr_release;
i2cr->master.n_links = 1;
i2cr->master.read = i2cr_read;
i2cr->master.write = i2cr_write;
mutex_init(&i2cr->lock);
i2cr->client = client;
ret = fsi_master_register(&i2cr->master);
if (ret)
return ret;
i2c_set_clientdata(client, i2cr);
return 0;
}
static void i2cr_remove(struct i2c_client *client)
{
struct fsi_master_i2cr *i2cr = i2c_get_clientdata(client);
fsi_master_unregister(&i2cr->master);
}
static const struct of_device_id i2cr_ids[] = {
{ .compatible = "ibm,i2cr-fsi-master" },
{ }
};
MODULE_DEVICE_TABLE(of, i2cr_ids);
static struct i2c_driver i2cr_driver = {
.probe = i2cr_probe,
.remove = i2cr_remove,
.driver = {
.name = "fsi-master-i2cr",
.of_match_table = i2cr_ids,
},
};
module_i2c_driver(i2cr_driver)
MODULE_AUTHOR("Eddie James <eajames@linux.ibm.com>");
MODULE_DESCRIPTION("IBM I2C Responder virtual FSI master driver");
MODULE_LICENSE("GPL");
+33
View File
@@ -0,0 +1,33 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) IBM Corporation 2023 */
#ifndef DRIVERS_FSI_MASTER_I2CR_H
#define DRIVERS_FSI_MASTER_I2CR_H
#include <linux/i2c.h>
#include <linux/mutex.h>
#include "fsi-master.h"
struct i2c_client;
struct fsi_master_i2cr {
struct fsi_master master;
struct mutex lock; /* protect HW access */
struct i2c_client *client;
};
#define to_fsi_master_i2cr(m) container_of(m, struct fsi_master_i2cr, master)
int fsi_master_i2cr_read(struct fsi_master_i2cr *i2cr, u32 addr, u64 *data);
int fsi_master_i2cr_write(struct fsi_master_i2cr *i2cr, u32 addr, u64 data);
static inline bool is_fsi_master_i2cr(struct fsi_master *master)
{
if (master->dev.parent && master->dev.parent->type == &i2c_client_type)
return true;
return false;
}
#endif /* DRIVERS_FSI_MASTER_I2CR_H */
+161
View File
@@ -0,0 +1,161 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* FSI master definitions. These comprise the core <--> master interface,
* to allow the core to interact with the (hardware-specific) masters.
*
* Copyright (C) IBM Corporation 2016
*/
#ifndef DRIVERS_FSI_MASTER_H
#define DRIVERS_FSI_MASTER_H
#include <linux/device.h>
#include <linux/mutex.h>
/*
* Master registers
*
* These are used by hardware masters, such as the one in the FSP2, AST2600 and
* the hub master in POWER processors.
*/
/* Control Registers */
#define FSI_MMODE 0x0 /* R/W: mode */
#define FSI_MDLYR 0x4 /* R/W: delay */
#define FSI_MCRSP 0x8 /* R/W: clock rate */
#define FSI_MENP0 0x10 /* R/W: enable */
#define FSI_MLEVP0 0x18 /* R: plug detect */
#define FSI_MSENP0 0x18 /* S: Set enable */
#define FSI_MCENP0 0x20 /* C: Clear enable */
#define FSI_MAEB 0x70 /* R: Error address */
#define FSI_MVER 0x74 /* R: master version/type */
#define FSI_MSTAP0 0xd0 /* R: Port status */
#define FSI_MRESP0 0xd0 /* W: Port reset */
#define FSI_MESRB0 0x1d0 /* R: Master error status */
#define FSI_MRESB0 0x1d0 /* W: Reset bridge */
#define FSI_MSCSB0 0x1d4 /* R: Master sub command stack */
#define FSI_MATRB0 0x1d8 /* R: Master address trace */
#define FSI_MDTRB0 0x1dc /* R: Master data trace */
#define FSI_MECTRL 0x2e0 /* W: Error control */
/* MMODE: Mode control */
#define FSI_MMODE_EIP 0x80000000 /* Enable interrupt polling */
#define FSI_MMODE_ECRC 0x40000000 /* Enable error recovery */
#define FSI_MMODE_RELA 0x20000000 /* Enable relative address commands */
#define FSI_MMODE_EPC 0x10000000 /* Enable parity checking */
#define FSI_MMODE_P8_TO_LSB 0x00000010 /* Timeout value LSB */
/* MSB=1, LSB=0 is 0.8 ms */
/* MSB=0, LSB=1 is 0.9 ms */
#define FSI_MMODE_CRS0SHFT 18 /* Clk rate selection 0 shift */
#define FSI_MMODE_CRS0MASK 0x3ff /* Clk rate selection 0 mask */
#define FSI_MMODE_CRS1SHFT 8 /* Clk rate selection 1 shift */
#define FSI_MMODE_CRS1MASK 0x3ff /* Clk rate selection 1 mask */
/* MRESB: Reset bridge */
#define FSI_MRESB_RST_GEN 0x80000000 /* General reset */
#define FSI_MRESB_RST_ERR 0x40000000 /* Error Reset */
/* MRESP: Reset port */
#define FSI_MRESP_RST_ALL_MASTER 0x20000000 /* Reset all FSI masters */
#define FSI_MRESP_RST_ALL_LINK 0x10000000 /* Reset all FSI port contr. */
#define FSI_MRESP_RST_MCR 0x08000000 /* Reset FSI master reg. */
#define FSI_MRESP_RST_PYE 0x04000000 /* Reset FSI parity error */
#define FSI_MRESP_RST_ALL 0xfc000000 /* Reset any error */
/* MECTRL: Error control */
#define FSI_MECTRL_EOAE 0x8000 /* Enable machine check when */
/* master 0 in error */
#define FSI_MECTRL_P8_AUTO_TERM 0x4000 /* Auto terminate */
#define FSI_HUB_LINK_OFFSET 0x80000
#define FSI_HUB_LINK_SIZE 0x80000
#define FSI_HUB_MASTER_MAX_LINKS 8
/*
* Protocol definitions
*
* These are used by low level masters that bit-bang out the protocol
*/
/* 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
/*
* Structures and function prototypes
*
* These are common to all masters
*/
struct fsi_master {
struct device dev;
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,
uint32_t addr, const void *val, size_t size);
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,
bool enable);
int (*link_config)(struct fsi_master *, int link,
u8 t_send_delay, u8 t_echo_delay);
};
#define to_fsi_master(d) container_of(d, struct fsi_master, dev)
/**
* fsi_master registration & lifetime: the fsi_master_register() and
* fsi_master_unregister() functions will take ownership of the master, and
* ->dev in particular. The registration path performs a get_device(), which
* takes the first reference on the device. Similarly, the unregistration path
* performs a put_device(), which may well drop the last reference.
*
* This means that master implementations *may* need to hold their own
* reference (via get_device()) on master->dev. In particular, if the device's
* ->release callback frees the fsi_master, then fsi_master_unregister will
* invoke this free if no other reference is held.
*
* The same applies for the error path of fsi_master_register; if the call
* fails, dev->release will have been invoked.
*/
extern int fsi_master_register(struct fsi_master *master);
extern void fsi_master_unregister(struct fsi_master *master);
extern int fsi_master_rescan(struct fsi_master *master);
#endif /* DRIVERS_FSI_MASTER_H */
+759
View File
@@ -0,0 +1,759 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/device.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/fsi-sbefifo.h>
#include <linux/gfp.h>
#include <linux/idr.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/miscdevice.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/fsi-occ.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/unaligned.h>
#define OCC_SRAM_BYTES 8192
#define OCC_CMD_DATA_BYTES 8186
#define OCC_RESP_DATA_BYTES 8185
#define OCC_P9_SRAM_CMD_ADDR 0xFFFBE000
#define OCC_P9_SRAM_RSP_ADDR 0xFFFBF000
#define OCC_P10_SRAM_CMD_ADDR 0xFFFFD000
#define OCC_P10_SRAM_RSP_ADDR 0xFFFFE000
#define OCC_P10_SRAM_MODE 0x58 /* Normal mode, OCB channel 2 */
#define OCC_TIMEOUT_MS 1000
#define OCC_CMD_IN_PRG_WAIT_MS 50
enum versions { occ_p9, occ_p10 };
struct occ {
struct device *dev;
struct device *sbefifo;
char name[32];
int idx;
bool platform_hwmon;
u8 sequence_number;
void *buffer;
void *client_buffer;
size_t client_buffer_size;
size_t client_response_size;
enum versions version;
struct miscdevice mdev;
struct mutex occ_lock;
};
#define to_occ(x) container_of((x), struct occ, mdev)
struct occ_response {
u8 seq_no;
u8 cmd_type;
u8 return_status;
__be16 data_length;
u8 data[OCC_RESP_DATA_BYTES + 2]; /* two bytes checksum */
} __packed;
struct occ_client {
struct occ *occ;
struct mutex lock;
size_t data_size;
size_t read_offset;
u8 *buffer;
};
#define to_client(x) container_of((x), struct occ_client, xfr)
static DEFINE_IDA(occ_ida);
static int occ_open(struct inode *inode, struct file *file)
{
struct occ_client *client = kzalloc_obj(*client);
struct miscdevice *mdev = file->private_data;
struct occ *occ = to_occ(mdev);
if (!client)
return -ENOMEM;
client->buffer = kvmalloc(OCC_SRAM_BYTES, GFP_KERNEL);
if (!client->buffer) {
kfree(client);
return -ENOMEM;
}
client->occ = occ;
mutex_init(&client->lock);
file->private_data = client;
get_device(occ->dev);
return 0;
}
static ssize_t occ_read(struct file *file, char __user *buf, size_t len,
loff_t *offset)
{
struct occ_client *client = file->private_data;
ssize_t rc = 0;
if (!client)
return -ENODEV;
if (len > OCC_SRAM_BYTES)
return -EINVAL;
mutex_lock(&client->lock);
/* This should not be possible ... */
if (WARN_ON_ONCE(client->read_offset > client->data_size)) {
rc = -EIO;
goto done;
}
/* Grab how much data we have to read */
rc = min(len, client->data_size - client->read_offset);
if (copy_to_user(buf, client->buffer + client->read_offset, rc))
rc = -EFAULT;
else
client->read_offset += rc;
done:
mutex_unlock(&client->lock);
return rc;
}
static ssize_t occ_write(struct file *file, const char __user *buf,
size_t len, loff_t *offset)
{
struct occ_client *client = file->private_data;
size_t rlen, data_length;
ssize_t rc;
u8 *cmd;
if (!client)
return -ENODEV;
if (len > (OCC_CMD_DATA_BYTES + 3) || len < 3)
return -EINVAL;
mutex_lock(&client->lock);
/* Construct the command */
cmd = client->buffer;
/*
* Copy the user command (assume user data follows the occ command
* format)
* byte 0: command type
* bytes 1-2: data length (msb first)
* bytes 3-n: data
*/
if (copy_from_user(&cmd[1], buf, len)) {
rc = -EFAULT;
goto done;
}
/* Extract data length */
data_length = (cmd[2] << 8) + cmd[3];
if (data_length > OCC_CMD_DATA_BYTES) {
rc = -EINVAL;
goto done;
}
/* Submit command; 4 bytes before the data and 2 bytes after */
rlen = OCC_SRAM_BYTES;
rc = fsi_occ_submit(client->occ->dev, cmd, data_length + 6, cmd,
&rlen);
if (rc)
goto done;
/* Set read tracking data */
client->data_size = rlen;
client->read_offset = 0;
/* Done */
rc = len;
done:
mutex_unlock(&client->lock);
return rc;
}
static int occ_release(struct inode *inode, struct file *file)
{
struct occ_client *client = file->private_data;
put_device(client->occ->dev);
kvfree(client->buffer);
kfree(client);
return 0;
}
static const struct file_operations occ_fops = {
.owner = THIS_MODULE,
.open = occ_open,
.read = occ_read,
.write = occ_write,
.release = occ_release,
};
static void occ_save_ffdc(struct occ *occ, __be32 *resp, size_t parsed_len,
size_t resp_len)
{
if (resp_len > parsed_len) {
size_t dh = resp_len - parsed_len;
size_t ffdc_len = (dh - 1) * 4; /* SBE words are four bytes */
__be32 *ffdc = &resp[parsed_len];
if (ffdc_len > occ->client_buffer_size)
ffdc_len = occ->client_buffer_size;
memcpy(occ->client_buffer, ffdc, ffdc_len);
occ->client_response_size = ffdc_len;
}
}
static int occ_verify_checksum(struct occ *occ, struct occ_response *resp,
u16 data_length)
{
/* Fetch the two bytes after the data for the checksum. */
u16 checksum_resp = get_unaligned_be16(&resp->data[data_length]);
u16 checksum;
u16 i;
checksum = resp->seq_no;
checksum += resp->cmd_type;
checksum += resp->return_status;
checksum += (data_length >> 8) + (data_length & 0xFF);
for (i = 0; i < data_length; ++i)
checksum += resp->data[i];
if (checksum != checksum_resp) {
dev_err(occ->dev, "Bad checksum: %04x!=%04x\n", checksum,
checksum_resp);
return -EBADE;
}
return 0;
}
static int occ_getsram(struct occ *occ, u32 offset, void *data, ssize_t len)
{
u32 data_len = ((len + 7) / 8) * 8; /* must be multiples of 8 B */
size_t cmd_len, parsed_len, resp_data_len;
size_t resp_len = OCC_MAX_RESP_WORDS;
__be32 *resp = occ->buffer;
__be32 cmd[6];
int idx = 0, rc;
/*
* Magic sequence to do SBE getsram command. SBE will fetch data from
* specified SRAM address.
*/
switch (occ->version) {
default:
case occ_p9:
cmd_len = 5;
cmd[2] = cpu_to_be32(1); /* Normal mode */
cmd[3] = cpu_to_be32(OCC_P9_SRAM_RSP_ADDR + offset);
break;
case occ_p10:
idx = 1;
cmd_len = 6;
cmd[2] = cpu_to_be32(OCC_P10_SRAM_MODE);
cmd[3] = 0;
cmd[4] = cpu_to_be32(OCC_P10_SRAM_RSP_ADDR + offset);
break;
}
cmd[0] = cpu_to_be32(cmd_len);
cmd[1] = cpu_to_be32(SBEFIFO_CMD_GET_OCC_SRAM);
cmd[4 + idx] = cpu_to_be32(data_len);
rc = sbefifo_submit(occ->sbefifo, cmd, cmd_len, resp, &resp_len);
if (rc)
return rc;
rc = sbefifo_parse_status(occ->sbefifo, SBEFIFO_CMD_GET_OCC_SRAM,
resp, resp_len, &parsed_len);
if (rc > 0) {
dev_err(occ->dev, "SRAM read returned failure status: %08x\n",
rc);
occ_save_ffdc(occ, resp, parsed_len, resp_len);
return -ECOMM;
} else if (rc) {
return rc;
}
resp_data_len = be32_to_cpu(resp[parsed_len - 1]);
if (resp_data_len != data_len) {
dev_err(occ->dev, "SRAM read expected %d bytes got %zd\n",
data_len, resp_data_len);
rc = -EBADMSG;
} else {
memcpy(data, resp, len);
}
return rc;
}
static int occ_putsram(struct occ *occ, const void *data, ssize_t len,
u8 seq_no, u16 checksum)
{
u32 data_len = ((len + 7) / 8) * 8; /* must be multiples of 8 B */
size_t cmd_len, parsed_len, resp_data_len;
size_t resp_len = OCC_MAX_RESP_WORDS;
__be32 *buf = occ->buffer;
u8 *byte_buf;
int idx = 0, rc;
cmd_len = (occ->version == occ_p10) ? 6 : 5;
cmd_len += data_len >> 2;
/*
* Magic sequence to do SBE putsram command. SBE will transfer
* data to specified SRAM address.
*/
buf[0] = cpu_to_be32(cmd_len);
buf[1] = cpu_to_be32(SBEFIFO_CMD_PUT_OCC_SRAM);
switch (occ->version) {
default:
case occ_p9:
buf[2] = cpu_to_be32(1); /* Normal mode */
buf[3] = cpu_to_be32(OCC_P9_SRAM_CMD_ADDR);
break;
case occ_p10:
idx = 1;
buf[2] = cpu_to_be32(OCC_P10_SRAM_MODE);
buf[3] = 0;
buf[4] = cpu_to_be32(OCC_P10_SRAM_CMD_ADDR);
break;
}
buf[4 + idx] = cpu_to_be32(data_len);
memcpy(&buf[5 + idx], data, len);
byte_buf = (u8 *)&buf[5 + idx];
/*
* Overwrite the first byte with our sequence number and the last two
* bytes with the checksum.
*/
byte_buf[0] = seq_no;
byte_buf[len - 2] = checksum >> 8;
byte_buf[len - 1] = checksum & 0xff;
rc = sbefifo_submit(occ->sbefifo, buf, cmd_len, buf, &resp_len);
if (rc)
return rc;
rc = sbefifo_parse_status(occ->sbefifo, SBEFIFO_CMD_PUT_OCC_SRAM,
buf, resp_len, &parsed_len);
if (rc > 0) {
dev_err(occ->dev, "SRAM write returned failure status: %08x\n",
rc);
occ_save_ffdc(occ, buf, parsed_len, resp_len);
return -ECOMM;
} else if (rc) {
return rc;
}
if (parsed_len != 1) {
dev_err(occ->dev, "SRAM write response length invalid: %zd\n",
parsed_len);
rc = -EBADMSG;
} else {
resp_data_len = be32_to_cpu(buf[0]);
if (resp_data_len != data_len) {
dev_err(occ->dev,
"SRAM write expected %d bytes got %zd\n",
data_len, resp_data_len);
rc = -EBADMSG;
}
}
return rc;
}
static int occ_trigger_attn(struct occ *occ)
{
__be32 *buf = occ->buffer;
size_t cmd_len, parsed_len, resp_data_len;
size_t resp_len = OCC_MAX_RESP_WORDS;
int idx = 0, rc;
switch (occ->version) {
default:
case occ_p9:
cmd_len = 7;
buf[2] = cpu_to_be32(3); /* Circular mode */
buf[3] = 0;
break;
case occ_p10:
idx = 1;
cmd_len = 8;
buf[2] = cpu_to_be32(0xd0); /* Circular mode, OCB Channel 1 */
buf[3] = 0;
buf[4] = 0;
break;
}
buf[0] = cpu_to_be32(cmd_len); /* Chip-op length in words */
buf[1] = cpu_to_be32(SBEFIFO_CMD_PUT_OCC_SRAM);
buf[4 + idx] = cpu_to_be32(8); /* Data length in bytes */
buf[5 + idx] = cpu_to_be32(0x20010000); /* Trigger OCC attention */
buf[6 + idx] = 0;
rc = sbefifo_submit(occ->sbefifo, buf, cmd_len, buf, &resp_len);
if (rc)
return rc;
rc = sbefifo_parse_status(occ->sbefifo, SBEFIFO_CMD_PUT_OCC_SRAM,
buf, resp_len, &parsed_len);
if (rc > 0) {
dev_err(occ->dev, "SRAM attn returned failure status: %08x\n",
rc);
occ_save_ffdc(occ, buf, parsed_len, resp_len);
return -ECOMM;
} else if (rc) {
return rc;
}
if (parsed_len != 1) {
dev_err(occ->dev, "SRAM attn response length invalid: %zd\n",
parsed_len);
rc = -EBADMSG;
} else {
resp_data_len = be32_to_cpu(buf[0]);
if (resp_data_len != 8) {
dev_err(occ->dev,
"SRAM attn expected 8 bytes got %zd\n",
resp_data_len);
rc = -EBADMSG;
}
}
return rc;
}
static bool fsi_occ_response_not_ready(struct occ_response *resp, u8 seq_no,
u8 cmd_type)
{
return resp->return_status == OCC_RESP_CMD_IN_PRG ||
resp->return_status == OCC_RESP_CRIT_INIT ||
resp->seq_no != seq_no || resp->cmd_type != cmd_type;
}
int fsi_occ_submit(struct device *dev, const void *request, size_t req_len,
void *response, size_t *resp_len)
{
const unsigned long timeout = msecs_to_jiffies(OCC_TIMEOUT_MS);
const unsigned long wait_time =
msecs_to_jiffies(OCC_CMD_IN_PRG_WAIT_MS);
struct occ *occ = dev_get_drvdata(dev);
struct occ_response *resp = response;
size_t user_resp_len = *resp_len;
u8 seq_no;
u8 cmd_type;
u16 checksum = 0;
u16 resp_data_length;
const u8 *byte_request = (const u8 *)request;
unsigned long end;
int rc;
size_t i;
*resp_len = 0;
if (!occ)
return -ENODEV;
if (user_resp_len < 7) {
dev_dbg(dev, "Bad resplen %zd\n", user_resp_len);
return -EINVAL;
}
cmd_type = byte_request[1];
/* Checksum the request, ignoring first byte (sequence number). */
for (i = 1; i < req_len - 2; ++i)
checksum += byte_request[i];
rc = mutex_lock_interruptible(&occ->occ_lock);
if (rc)
return rc;
occ->client_buffer = response;
occ->client_buffer_size = user_resp_len;
occ->client_response_size = 0;
if (!occ->buffer) {
rc = -ENOENT;
goto done;
}
/*
* Get a sequence number and update the counter. Avoid a sequence
* number of 0 which would pass the response check below even if the
* OCC response is uninitialized. Any sequence number the user is
* trying to send is overwritten since this function is the only common
* interface to the OCC and therefore the only place we can guarantee
* unique sequence numbers.
*/
seq_no = occ->sequence_number++;
if (!occ->sequence_number)
occ->sequence_number = 1;
checksum += seq_no;
rc = occ_putsram(occ, request, req_len, seq_no, checksum);
if (rc)
goto done;
rc = occ_trigger_attn(occ);
if (rc)
goto done;
end = jiffies + timeout;
while (true) {
/* Read occ response header */
rc = occ_getsram(occ, 0, resp, 8);
if (rc)
goto done;
if (fsi_occ_response_not_ready(resp, seq_no, cmd_type)) {
if (time_after(jiffies, end)) {
dev_err(occ->dev,
"resp timeout status=%02x seq=%d cmd=%d, our seq=%d cmd=%d\n",
resp->return_status, resp->seq_no,
resp->cmd_type, seq_no, cmd_type);
rc = -ETIMEDOUT;
goto done;
}
set_current_state(TASK_UNINTERRUPTIBLE);
schedule_timeout(wait_time);
} else {
/* Extract size of response data */
resp_data_length =
get_unaligned_be16(&resp->data_length);
/*
* Message size is data length + 5 bytes header + 2
* bytes checksum
*/
if ((resp_data_length + 7) > user_resp_len) {
rc = -EMSGSIZE;
goto done;
}
/*
* Get the entire response including the header again,
* in case it changed
*/
if (resp_data_length > 1) {
rc = occ_getsram(occ, 0, resp,
resp_data_length + 7);
if (rc)
goto done;
if (!fsi_occ_response_not_ready(resp, seq_no,
cmd_type))
break;
} else {
break;
}
}
}
dev_dbg(dev, "resp_status=%02x resp_data_len=%d\n",
resp->return_status, resp_data_length);
rc = occ_verify_checksum(occ, resp, resp_data_length);
if (rc)
goto done;
occ->client_response_size = resp_data_length + 7;
done:
*resp_len = occ->client_response_size;
mutex_unlock(&occ->occ_lock);
return rc;
}
EXPORT_SYMBOL_GPL(fsi_occ_submit);
static int occ_unregister_platform_child(struct device *dev, void *data)
{
struct platform_device *hwmon_dev = to_platform_device(dev);
platform_device_unregister(hwmon_dev);
return 0;
}
static int occ_unregister_of_child(struct device *dev, void *data)
{
struct platform_device *hwmon_dev = to_platform_device(dev);
of_device_unregister(hwmon_dev);
if (dev->of_node)
of_node_clear_flag(dev->of_node, OF_POPULATED);
return 0;
}
static int occ_probe(struct platform_device *pdev)
{
int rc;
u32 reg;
char child_name[32];
struct occ *occ;
struct platform_device *hwmon_dev = NULL;
struct device_node *hwmon_node;
struct device *dev = &pdev->dev;
struct platform_device_info hwmon_dev_info = {
.parent = dev,
.name = "occ-hwmon",
};
occ = devm_kzalloc(dev, sizeof(*occ), GFP_KERNEL);
if (!occ)
return -ENOMEM;
/* SBE words are always four bytes */
occ->buffer = kvmalloc(OCC_MAX_RESP_WORDS * 4, GFP_KERNEL);
if (!occ->buffer)
return -ENOMEM;
occ->version = (uintptr_t)of_device_get_match_data(dev);
occ->dev = dev;
occ->sbefifo = dev->parent;
/*
* Quickly derive a pseudo-random number from jiffies so that
* re-probing the driver doesn't accidentally overlap sequence numbers.
*/
occ->sequence_number = (u8)((jiffies % 0xff) + 1);
mutex_init(&occ->occ_lock);
if (dev->of_node) {
rc = of_property_read_u32(dev->of_node, "reg", &reg);
if (!rc) {
/* make sure we don't have a duplicate from dts */
occ->idx = ida_alloc_range(&occ_ida, reg, reg,
GFP_KERNEL);
if (occ->idx < 0)
occ->idx = ida_alloc_min(&occ_ida, 1,
GFP_KERNEL);
} else {
occ->idx = ida_alloc_min(&occ_ida, 1, GFP_KERNEL);
}
} else {
occ->idx = ida_alloc_min(&occ_ida, 1, GFP_KERNEL);
}
platform_set_drvdata(pdev, occ);
snprintf(occ->name, sizeof(occ->name), "occ%d", occ->idx);
occ->mdev.fops = &occ_fops;
occ->mdev.minor = MISC_DYNAMIC_MINOR;
occ->mdev.name = occ->name;
occ->mdev.parent = dev;
rc = misc_register(&occ->mdev);
if (rc) {
dev_err(dev, "failed to register miscdevice: %d\n", rc);
ida_free(&occ_ida, occ->idx);
kvfree(occ->buffer);
return rc;
}
hwmon_node = of_get_child_by_name(dev->of_node, hwmon_dev_info.name);
if (hwmon_node) {
snprintf(child_name, sizeof(child_name), "%s.%d", hwmon_dev_info.name, occ->idx);
hwmon_dev = of_platform_device_create(hwmon_node, child_name, dev);
of_node_put(hwmon_node);
}
if (!hwmon_dev) {
occ->platform_hwmon = true;
hwmon_dev_info.id = occ->idx;
hwmon_dev = platform_device_register_full(&hwmon_dev_info);
if (IS_ERR(hwmon_dev))
dev_warn(dev, "failed to create hwmon device\n");
}
return 0;
}
static void occ_remove(struct platform_device *pdev)
{
struct occ *occ = platform_get_drvdata(pdev);
misc_deregister(&occ->mdev);
mutex_lock(&occ->occ_lock);
kvfree(occ->buffer);
occ->buffer = NULL;
mutex_unlock(&occ->occ_lock);
if (occ->platform_hwmon)
device_for_each_child(&pdev->dev, NULL, occ_unregister_platform_child);
else
device_for_each_child(&pdev->dev, NULL, occ_unregister_of_child);
ida_free(&occ_ida, occ->idx);
}
static const struct of_device_id occ_match[] = {
{
.compatible = "ibm,p9-occ",
.data = (void *)occ_p9
},
{
.compatible = "ibm,p10-occ",
.data = (void *)occ_p10
},
{ },
};
MODULE_DEVICE_TABLE(of, occ_match);
static struct platform_driver occ_driver = {
.driver = {
.name = "occ",
.of_match_table = occ_match,
},
.probe = occ_probe,
.remove = occ_remove,
};
static int occ_init(void)
{
return platform_driver_register(&occ_driver);
}
static void occ_exit(void)
{
platform_driver_unregister(&occ_driver);
ida_destroy(&occ_ida);
}
module_init(occ_init);
module_exit(occ_exit);
MODULE_AUTHOR("Eddie James <eajames@linux.ibm.com>");
MODULE_DESCRIPTION("BMC P9 OCC driver");
MODULE_LICENSE("GPL");
File diff suppressed because it is too large Load Diff
+614
View File
@@ -0,0 +1,614 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* SCOM FSI Client device driver
*
* Copyright (C) IBM Corporation 2016
*/
#include <linux/fsi.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <uapi/linux/fsi.h>
#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_FSI2PIB_RESET_REG 0x18
#define SCOM_STATUS_REG 0x1C /* Read */
#define SCOM_PIB_RESET_REG 0x1C /* Write */
/* 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_FSI2PIB_ERROR (SCOM_STATUS_PROTECTION | \
SCOM_STATUS_PARITY | \
SCOM_STATUS_PIB_ABORT)
#define SCOM_STATUS_ANY_ERR (SCOM_STATUS_FSI2PIB_ERROR | \
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_IND_RETRIES 10 /* Retries indirect not ready */
struct scom_device {
struct list_head link;
struct fsi_device *fsi_dev;
struct device dev;
struct cdev cdev;
struct mutex lock;
bool dead;
};
static int __put_scom(struct scom_device *scom_dev, uint64_t value,
uint32_t addr, uint32_t *status)
{
__be32 data, raw_status;
int rc;
data = cpu_to_be32((value >> 32) & 0xffffffff);
rc = fsi_device_write(scom_dev->fsi_dev, SCOM_DATA0_REG, &data,
sizeof(uint32_t));
if (rc)
return rc;
data = cpu_to_be32(value & 0xffffffff);
rc = fsi_device_write(scom_dev->fsi_dev, SCOM_DATA1_REG, &data,
sizeof(uint32_t));
if (rc)
return rc;
data = cpu_to_be32(SCOM_WRITE_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;
*status = be32_to_cpu(raw_status);
return 0;
}
static int __get_scom(struct scom_device *scom_dev, uint64_t *value,
uint32_t addr, uint32_t *status)
{
__be32 data, raw_status;
int rc;
*value = 0ULL;
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;
/*
* 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)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);
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, err;
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;
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;
return 0;
}
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, err;
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;
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;
return 0;
}
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_FSI2PIB_ERROR)
fsi_device_write(scom->fsi_dev, SCOM_FSI2PIB_RESET_REG, &dummy,
sizeof(uint32_t));
if (status & SCOM_STATUS_PROTECTION)
return -EPERM;
if (status & SCOM_STATUS_PARITY)
return -EIO;
if (status & SCOM_STATUS_PIB_ABORT)
return -EBUSY;
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;
int rc;
rc = raw_put_scom(scom, value, addr, &status);
if (rc)
return rc;
rc = handle_fsi2pib_status(scom, status);
if (rc)
return rc;
return handle_pib_status(scom,
(status & SCOM_STATUS_PIB_RESP_MASK)
>> SCOM_STATUS_PIB_RESP_SHIFT);
}
static int get_scom(struct scom_device *scom, uint64_t *value,
uint64_t addr)
{
uint32_t status;
int rc;
rc = raw_get_scom(scom, value, addr, &status);
if (rc)
return rc;
rc = handle_fsi2pib_status(scom, status);
if (rc)
return rc;
return handle_pib_status(scom,
(status & SCOM_STATUS_PIB_RESP_MASK)
>> SCOM_STATUS_PIB_RESP_SHIFT);
}
static ssize_t scom_read(struct file *filep, char __user *buf, size_t len,
loff_t *offset)
{
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;
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;
}
rc = copy_to_user(buf, &val, len);
if (rc)
dev_dbg(dev, "copy to user failed:%d\n", rc);
return rc ? rc : len;
}
static ssize_t scom_write(struct file *filep, const char __user *buf,
size_t len, loff_t *offset)
{
int rc;
struct scom_device *scom = filep->private_data;
struct device *dev = &scom->fsi_dev->dev;
uint64_t val;
if (len != sizeof(uint64_t))
return -EINVAL;
rc = copy_from_user(&val, buf, len);
if (rc) {
dev_dbg(dev, "copy from user failed:%d\n", rc);
return -EINVAL;
}
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;
}
return len;
}
static loff_t scom_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 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,
.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 fsi_device *fsi_dev)
{
struct device *dev = &fsi_dev->dev;
struct scom_device *scom;
int rc, didx;
scom = kzalloc_obj(*scom);
if (!scom)
return -ENOMEM;
fsi_set_drvdata(fsi_dev, scom);
mutex_init(&scom->lock);
/* 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;
/* 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 0;
err_free_minor:
fsi_free_minor(scom->dev.devt);
err:
put_device(&scom->dev);
return rc;
}
static void scom_remove(struct fsi_device *fsi_dev)
{
struct scom_device *scom = fsi_get_drvdata(fsi_dev);
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);
}
static const struct of_device_id scom_of_ids[] = {
{ .compatible = "ibm,fsi2pib" },
{ }
};
MODULE_DEVICE_TABLE(of, scom_of_ids);
static const struct fsi_device_id scom_ids[] = {
{
.engine_type = FSI_ENGID_SCOM,
.version = FSI_VERSION_ANY,
},
{ 0 }
};
static struct fsi_driver scom_drv = {
.id_table = scom_ids,
.probe = scom_probe,
.remove = scom_remove,
.drv = {
.name = "scom",
.of_match_table = scom_of_ids,
}
};
module_fsi_driver(scom_drv);
MODULE_DESCRIPTION("SCOM FSI Client device driver");
MODULE_LICENSE("GPL");
+28
View File
@@ -0,0 +1,28 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (C) IBM Corporation 2023 */
#ifndef DRIVERS_FSI_SLAVE_H
#define DRIVERS_FSI_SLAVE_H
#include <linux/cdev.h>
#include <linux/device.h>
struct fsi_master;
struct fsi_slave {
struct device dev;
struct fsi_master *master;
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_slave(d) container_of(d, struct fsi_slave, dev)
#endif /* DRIVERS_FSI_SLAVE_H */
+150
View File
@@ -0,0 +1,150 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (C) IBM Corporation 2023 */
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/fsi.h>
#include <linux/module.h>
#include "fsi-master-i2cr.h"
#include "fsi-slave.h"
struct i2cr_scom {
struct device dev;
struct cdev cdev;
struct fsi_master_i2cr *i2cr;
};
static loff_t i2cr_scom_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 ssize_t i2cr_scom_read(struct file *filep, char __user *buf, size_t len, loff_t *offset)
{
struct i2cr_scom *scom = filep->private_data;
u64 data;
int ret;
if (len != sizeof(data))
return -EINVAL;
ret = fsi_master_i2cr_read(scom->i2cr, (u32)*offset, &data);
if (ret)
return ret;
ret = copy_to_user(buf, &data, len);
if (ret)
return ret;
return len;
}
static ssize_t i2cr_scom_write(struct file *filep, const char __user *buf, size_t len,
loff_t *offset)
{
struct i2cr_scom *scom = filep->private_data;
u64 data;
int ret;
if (len != sizeof(data))
return -EINVAL;
ret = copy_from_user(&data, buf, len);
if (ret)
return ret;
ret = fsi_master_i2cr_write(scom->i2cr, (u32)*offset, data);
if (ret)
return ret;
return len;
}
static const struct file_operations i2cr_scom_fops = {
.owner = THIS_MODULE,
.open = simple_open,
.llseek = i2cr_scom_llseek,
.read = i2cr_scom_read,
.write = i2cr_scom_write,
};
static int i2cr_scom_probe(struct fsi_device *fsi_dev)
{
struct device *dev = &fsi_dev->dev;
struct i2cr_scom *scom;
int didx;
int ret;
if (!is_fsi_master_i2cr(fsi_dev->slave->master))
return -ENODEV;
scom = devm_kzalloc(dev, sizeof(*scom), GFP_KERNEL);
if (!scom)
return -ENOMEM;
scom->i2cr = to_fsi_master_i2cr(fsi_dev->slave->master);
dev_set_drvdata(dev, scom);
scom->dev.type = &fsi_cdev_type;
scom->dev.parent = dev;
device_initialize(&scom->dev);
ret = fsi_get_new_minor(fsi_dev, fsi_dev_scom, &scom->dev.devt, &didx);
if (ret)
return ret;
dev_set_name(&scom->dev, "scom%d", didx);
cdev_init(&scom->cdev, &i2cr_scom_fops);
ret = cdev_device_add(&scom->cdev, &scom->dev);
if (ret)
fsi_free_minor(scom->dev.devt);
return ret;
}
static void i2cr_scom_remove(struct fsi_device *fsi_dev)
{
struct i2cr_scom *scom = dev_get_drvdata(&fsi_dev->dev);
cdev_device_del(&scom->cdev, &scom->dev);
fsi_free_minor(scom->dev.devt);
}
static const struct of_device_id i2cr_scom_of_ids[] = {
{ .compatible = "ibm,i2cr-scom" },
{ }
};
MODULE_DEVICE_TABLE(of, i2cr_scom_of_ids);
static const struct fsi_device_id i2cr_scom_ids[] = {
{ 0x5, FSI_VERSION_ANY },
{ }
};
static struct fsi_driver i2cr_scom_driver = {
.probe = i2cr_scom_probe,
.remove = i2cr_scom_remove,
.id_table = i2cr_scom_ids,
.drv = {
.name = "i2cr_scom",
.of_match_table = i2cr_scom_of_ids,
}
};
module_fsi_driver(i2cr_scom_driver);
MODULE_AUTHOR("Eddie James <eajames@linux.ibm.com>");
MODULE_DESCRIPTION("IBM I2C Responder SCOM driver");
MODULE_LICENSE("GPL");