FSI changes for v6.6

* New drivers for the I2C Responder master and SCOM device
 
  * Misc janitor fixes
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEE+nHMAt9PCBDH63wBa3ZZB4FHcJ4FAmTV7UQACgkQa3ZZB4FH
 cJ4Mbg//fI3gE56QIsMYNu2YfJb8ZFFH5xD/xn9xwAv34i/XudhX6eCigEHl420Z
 B/7yPyDePBK8f+Phb9pL1VCovnwdsG/7j3BMtVRV5172bGgkUGpIyNIS7K47/yhJ
 wRs9qmQsHu3PbKKzStdxxgOOZxiMZc8KzZjDYVBcWmX9RBTe6C4n8WpHbcvI3sIU
 YTdbfSOyZQIlurJuEtwa7FFOAWUTD66gvvpDWZZn1Ns0hh4WsNWQ0mIqVxVSr8Rl
 +hISZfBopZMAvMVt8LHvldBUUVAcb9z0KdsAuEe+DXv0uzkTN4FGxFD5YEloGnzR
 o0eYc7M6vEvH46qhLKYIXRLgLD+SM2+RRhMNaOhxbdVsw7+PWDUz7OWIsoPHmExP
 9cmJhZKg/l2JlVPxh+6NOjx2ygK1h0vkADZWc8UoiZD9ZHXFzEYTjuRvaa4YH9HN
 ENOh3S+9i2Zrue0EWNBSIEaKvKG6h63cUoZSSU4AMtMwC1H5JPKOyYtC+8+oLFsw
 HmRjxls5zdOLcK9JwPR80xSD/CRadT88qpkvRyFfWyuHenazJzo2HHCgrbVgDXq0
 wvR2/c2MWqhZ94LASnd4gpr7MicS1G5FiZn+csKP2dy2Pv4nyDYkOOmrXw8Q/bGZ
 X88Ohipm9imC0LVIRvXusrmKUUp5dzpQePaQoAaC8/KH8PqRC0U=
 =FLoZ
 -----END PGP SIGNATURE-----

Merge tag 'fsi-for-v6.6' of git://git.kernel.org/pub/scm/linux/kernel/git/joel/fsi into char-misc-next

Joen writes:

FSI changes for v6.6

 * New drivers for the I2C Responder master and SCOM device

 * Misc janitor fixes

* tag 'fsi-for-v6.6' of git://git.kernel.org/pub/scm/linux/kernel/git/joel/fsi:
  fsi: fix some spelling mistakes in comment
  fsi: master-ast-cf: Add MODULE_FIRMWARE macro
  docs: ABI: fix spelling/grammar in SBEFIFO timeout interface
  fsi: Add I2C Responder SCOM driver
  fsi: Add IBM I2C Responder virtual FSI master
  dt-bindings: fsi: Document the IBM I2C Responder virtual FSI master
  fsi: Lock mutex for master device registration
  fsi: Improve master indexing
  fsi: core: Switch to ida_alloc/free
  fsi: core: Fix legacy minor numbering
  fsi: core: Add trace events for scan and unregister
  fsi: aspeed: Reset master errors after CFAM reset
  fsi: sbefifo: Remove limits on user-specified read timeout
  fsi: sbefifo: Add configurable in-command timeout
  fsi: sbefifo: Don't check status during probe
  fsi: Use of_match_table for bus matching if specified
  fsi: Add aliased device numbering
  fsi: Move fsi_slave structure definition to header
  fsi: Use of_property_read_reg() to parse "reg"
  fsi: Explicitly include correct DT includes
This commit is contained in:
Greg Kroah-Hartman 2023-08-11 21:24:31 +02:00
commit 22884cf84c
20 changed files with 878 additions and 99 deletions

View file

@ -5,6 +5,6 @@ Description:
Indicates whether or not this SBE device has experienced a
timeout; i.e. the SBE did not respond within the time allotted
by the driver. A value of 1 indicates that a timeout has
ocurred and no transfers have completed since the timeout. A
value of 0 indicates that no timeout has ocurred, or if one
has, more recent transfers have completed successful.
occurred and no transfers have completed since the timeout. A
value of 0 indicates that no timeout has occurred, or if one
has, more recent transfers have completed successfully.

View file

@ -0,0 +1,41 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/fsi/ibm,i2cr-fsi-master.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: IBM I2C Responder virtual FSI master
maintainers:
- Eddie James <eajames@linux.ibm.com>
description: |
The I2C Responder (I2CR) is a an I2C device that's connected to an FSI CFAM
(see fsi.txt). The I2CR translates I2C bus operations to FSI CFAM reads and
writes or SCOM operations, thereby acting as an FSI master.
properties:
compatible:
enum:
- ibm,i2cr-fsi-master
reg:
maxItems: 1
required:
- compatible
- reg
additionalProperties: false
examples:
- |
i2c {
#address-cells = <1>;
#size-cells = <0>;
i2cr@20 {
compatible = "ibm,i2cr-fsi-master";
reg = <0x20>;
};
};

View file

@ -62,6 +62,15 @@ config FSI_MASTER_ASPEED
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
@ -85,4 +94,12 @@ config FSI_OCC
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

View file

@ -4,7 +4,9 @@ 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

View file

@ -16,6 +16,8 @@
#include <linux/idr.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/slab.h>
#include <linux/bitops.h>
#include <linux/cdev.h>
@ -23,6 +25,10 @@
#include <linux/uaccess.h>
#include "fsi-master.h"
#include "fsi-slave.h"
#define CREATE_TRACE_POINTS
#include <trace/events/fsi.h>
#define FSI_SLAVE_CONF_NEXT_MASK GENMASK(31, 31)
#define FSI_SLAVE_CONF_SLOTS_MASK GENMASK(23, 16)
@ -78,26 +84,6 @@ static const int engine_page_size = 0x400;
static DEFINE_IDA(master_ida);
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 CREATE_TRACE_POINTS
#include <trace/events/fsi.h>
#define to_fsi_master(d) container_of(d, struct fsi_master, dev)
#define to_fsi_slave(d) container_of(d, struct fsi_slave, dev)
static const int slave_retries = 2;
static int discard_errors;
@ -415,28 +401,18 @@ EXPORT_SYMBOL_GPL(fsi_slave_release_range);
static bool fsi_device_node_matches(struct device *dev, struct device_node *np,
uint32_t addr, uint32_t size)
{
unsigned int len, na, ns;
const __be32 *prop;
uint32_t psize;
u64 paddr, psize;
na = of_n_addr_cells(np);
ns = of_n_size_cells(np);
if (na != 1 || ns != 1)
if (of_property_read_reg(np, 0, &paddr, &psize))
return false;
prop = of_get_property(np, "reg", &len);
if (!prop || len != 8)
if (paddr != addr)
return false;
if (of_read_number(prop, 1) != addr)
return false;
psize = of_read_number(prop + 1, 1);
if (psize != size) {
dev_warn(dev,
"node %s matches probed address, but not size (got 0x%x, expected 0x%x)",
of_node_full_name(np), psize, size);
"node %pOF matches probed address, but not size (got 0x%llx, expected 0x%x)",
np, psize, size);
}
return true;
@ -653,24 +629,12 @@ static void fsi_slave_release(struct device *dev)
static bool fsi_slave_node_matches(struct device_node *np,
int link, uint8_t id)
{
unsigned int len, na, ns;
const __be32 *prop;
u64 addr;
na = of_n_addr_cells(np);
ns = of_n_size_cells(np);
/* Ensure we have the correct format for addresses and sizes in
* reg properties
*/
if (na != 2 || ns != 0)
if (of_property_read_reg(np, 0, &addr, NULL))
return false;
prop = of_get_property(np, "reg", &len);
if (!prop || len != 8)
return false;
return (of_read_number(prop, 1) == link) &&
(of_read_number(prop + 1, 1) == id);
return addr == (((u64)link << 32) | id);
}
/* Find a matching node for the slave at (link, id). Returns NULL if none
@ -949,9 +913,13 @@ static int __fsi_get_new_minor(struct fsi_slave *slave, enum fsi_dev_type type,
/* Check if we qualify for legacy numbering */
if (cid >= 0 && cid < 16 && type < 4) {
/* Try reserving the legacy number */
id = (cid << 4) | type;
id = ida_simple_get(&fsi_minor_ida, id, id + 1, GFP_KERNEL);
/*
* Try reserving the legacy number, which has 0 - 0x3f reserved
* in the ida range. cid goes up to 0xf and type contains two
* bits, so construct the id with the below two bit shift.
*/
id = (cid << 2) | type;
id = ida_alloc_range(&fsi_minor_ida, id, id, GFP_KERNEL);
if (id >= 0) {
*out_index = fsi_adjust_index(cid);
*out_dev = fsi_base_dev + id;
@ -962,8 +930,8 @@ static int __fsi_get_new_minor(struct fsi_slave *slave, enum fsi_dev_type type,
return id;
/* Fallback to non-legacy allocation */
}
id = ida_simple_get(&fsi_minor_ida, FSI_CHAR_LEGACY_TOP,
FSI_CHAR_MAX_DEVICES, GFP_KERNEL);
id = ida_alloc_range(&fsi_minor_ida, FSI_CHAR_LEGACY_TOP,
FSI_CHAR_MAX_DEVICES - 1, GFP_KERNEL);
if (id < 0)
return id;
*out_index = fsi_adjust_index(id);
@ -971,16 +939,42 @@ static int __fsi_get_new_minor(struct fsi_slave *slave, enum fsi_dev_type type,
return 0;
}
static const char *const fsi_dev_type_names[] = {
"cfam",
"sbefifo",
"scom",
"occ",
};
int fsi_get_new_minor(struct fsi_device *fdev, enum fsi_dev_type type,
dev_t *out_dev, int *out_index)
{
if (fdev->dev.of_node) {
int aid = of_alias_get_id(fdev->dev.of_node, fsi_dev_type_names[type]);
if (aid >= 0) {
/* Use the same scheme as the legacy numbers. */
int id = (aid << 2) | type;
id = ida_alloc_range(&fsi_minor_ida, id, id, GFP_KERNEL);
if (id >= 0) {
*out_index = aid;
*out_dev = fsi_base_dev + id;
return 0;
}
if (id != -ENOSPC)
return id;
}
}
return __fsi_get_new_minor(fdev->slave, type, out_dev, out_index);
}
EXPORT_SYMBOL_GPL(fsi_get_new_minor);
void fsi_free_minor(dev_t dev)
{
ida_simple_remove(&fsi_minor_ida, MINOR(dev));
ida_free(&fsi_minor_ida, MINOR(dev));
}
EXPORT_SYMBOL_GPL(fsi_free_minor);
@ -1210,6 +1204,7 @@ static int fsi_master_scan(struct fsi_master *master)
{
int link, rc;
trace_fsi_master_scan(master, true);
for (link = 0; link < master->n_links; link++) {
rc = fsi_master_link_enable(master, link);
if (rc) {
@ -1251,6 +1246,7 @@ static int fsi_master_remove_slave(struct device *dev, void *arg)
static void fsi_master_unscan(struct fsi_master *master)
{
trace_fsi_master_scan(master, false);
device_for_each_child(&master->dev, NULL, fsi_master_remove_slave);
}
@ -1313,41 +1309,53 @@ int fsi_master_register(struct fsi_master *master)
struct device_node *np;
mutex_init(&master->scan_lock);
master->idx = ida_simple_get(&master_ida, 0, INT_MAX, GFP_KERNEL);
/* Alloc the requested index if it's non-zero */
if (master->idx) {
master->idx = ida_alloc_range(&master_ida, master->idx,
master->idx, GFP_KERNEL);
} else {
master->idx = ida_alloc(&master_ida, GFP_KERNEL);
}
if (master->idx < 0)
return master->idx;
dev_set_name(&master->dev, "fsi%d", master->idx);
if (!dev_name(&master->dev))
dev_set_name(&master->dev, "fsi%d", master->idx);
master->dev.class = &fsi_master_class;
mutex_lock(&master->scan_lock);
rc = device_register(&master->dev);
if (rc) {
ida_simple_remove(&master_ida, master->idx);
return rc;
ida_free(&master_ida, master->idx);
goto out;
}
np = dev_of_node(&master->dev);
if (!of_property_read_bool(np, "no-scan-on-init")) {
mutex_lock(&master->scan_lock);
fsi_master_scan(master);
mutex_unlock(&master->scan_lock);
}
return 0;
out:
mutex_unlock(&master->scan_lock);
return rc;
}
EXPORT_SYMBOL_GPL(fsi_master_register);
void fsi_master_unregister(struct fsi_master *master)
{
if (master->idx >= 0) {
ida_simple_remove(&master_ida, master->idx);
master->idx = -1;
}
int idx = master->idx;
trace_fsi_master_unregister(master);
mutex_lock(&master->scan_lock);
fsi_master_unscan(master);
master->n_links = 0;
mutex_unlock(&master->scan_lock);
device_unregister(&master->dev);
ida_free(&master_ida, idx);
}
EXPORT_SYMBOL_GPL(fsi_master_unregister);
@ -1366,8 +1374,14 @@ static int fsi_bus_match(struct device *dev, struct device_driver *drv)
if (id->engine_type != fsi_dev->engine_type)
continue;
if (id->version == FSI_VERSION_ANY ||
id->version == fsi_dev->version)
return 1;
id->version == fsi_dev->version) {
if (drv->of_match_table) {
if (of_driver_match_device(dev, drv))
return 1;
} else {
return 1;
}
}
}
return 0;

View file

@ -376,7 +376,7 @@ static int aspeed_master_break(struct fsi_master *master, int link)
static void aspeed_master_release(struct device *dev)
{
struct fsi_master_aspeed *aspeed =
to_fsi_master_aspeed(dev_to_fsi_master(dev));
to_fsi_master_aspeed(to_fsi_master(dev));
kfree(aspeed);
}
@ -454,6 +454,8 @@ static ssize_t cfam_reset_store(struct device *dev, struct device_attribute *att
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);

View file

@ -1133,7 +1133,7 @@ static int fsi_master_acf_gpio_request(void *data)
/* Note: This doesn't require holding out mutex */
/* Write reqest */
/* Write request */
iowrite8(ARB_ARM_REQ, master->sram + ARB_REG);
/*
@ -1190,7 +1190,7 @@ static int fsi_master_acf_gpio_release(void *data)
static void fsi_master_acf_release(struct device *dev)
{
struct fsi_master_acf *master = to_fsi_master_acf(dev_to_fsi_master(dev));
struct fsi_master_acf *master = to_fsi_master_acf(to_fsi_master(dev));
/* Cleanup, stop coprocessor */
mutex_lock(&master->lock);
@ -1441,3 +1441,4 @@ static struct platform_driver fsi_master_acf = {
module_platform_driver(fsi_master_acf);
MODULE_LICENSE("GPL");
MODULE_FIRMWARE(FW_FILE_NAME);

View file

@ -761,7 +761,7 @@ static DEVICE_ATTR(external_mode, 0664,
static void fsi_master_gpio_release(struct device *dev)
{
struct fsi_master_gpio *master = to_fsi_master_gpio(dev_to_fsi_master(dev));
struct fsi_master_gpio *master = to_fsi_master_gpio(to_fsi_master(dev));
of_node_put(dev_of_node(master->dev));

View file

@ -105,7 +105,7 @@ static int hub_master_link_enable(struct fsi_master *master, int link,
static void hub_master_release(struct device *dev)
{
struct fsi_master_hub *hub = to_fsi_master_hub(dev_to_fsi_master(dev));
struct fsi_master_hub *hub = to_fsi_master_hub(to_fsi_master(dev));
kfree(hub);
}

View file

@ -0,0 +1,316 @@
// 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/mod_devicetable.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(sizeof(*i2cr), GFP_KERNEL);
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_new = 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");

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 */

View file

@ -136,7 +136,7 @@ struct fsi_master {
u8 t_send_delay, u8 t_echo_delay);
};
#define dev_to_fsi_master(d) container_of(d, struct fsi_master, dev)
#define to_fsi_master(d) container_of(d, struct fsi_master, dev)
/**
* fsi_master registration & lifetime: the fsi_master_register() and

View file

@ -15,7 +15,7 @@
#include <linux/mutex.h>
#include <linux/fsi-occ.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/sched.h>
#include <linux/slab.h>

View file

@ -22,8 +22,8 @@
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
@ -81,7 +81,7 @@
enum sbe_state
{
SBE_STATE_UNKNOWN = 0x0, // Unkown, initial state
SBE_STATE_UNKNOWN = 0x0, // Unknown, initial state
SBE_STATE_IPLING = 0x1, // IPL'ing - autonomous mode (transient)
SBE_STATE_ISTEP = 0x2, // ISTEP - Running IPL by steps (transient)
SBE_STATE_MPIPL = 0x3, // MPIPL
@ -127,6 +127,7 @@ struct sbefifo {
bool dead;
bool async_ffdc;
bool timed_out;
u32 timeout_in_cmd_ms;
u32 timeout_start_rsp_ms;
};
@ -136,6 +137,7 @@ struct sbefifo_user {
void *cmd_page;
void *pending_cmd;
size_t pending_len;
u32 cmd_timeout_ms;
u32 read_timeout_ms;
};
@ -508,7 +510,7 @@ static int sbefifo_send_command(struct sbefifo *sbefifo,
rc = sbefifo_wait(sbefifo, true, &status, timeout);
if (rc < 0)
return rc;
timeout = msecs_to_jiffies(SBEFIFO_TIMEOUT_IN_CMD);
timeout = msecs_to_jiffies(sbefifo->timeout_in_cmd_ms);
vacant = sbefifo_vacant(status);
len = chunk = min(vacant, remaining);
@ -730,7 +732,7 @@ static int __sbefifo_submit(struct sbefifo *sbefifo,
* @response: The output response buffer
* @resp_len: In: Response buffer size, Out: Response size
*
* This will perform the entire operation. If the reponse buffer
* This will perform the entire operation. If the response buffer
* overflows, returns -EOVERFLOW
*/
int sbefifo_submit(struct device *dev, const __be32 *command, size_t cmd_len,
@ -802,6 +804,7 @@ static int sbefifo_user_open(struct inode *inode, struct file *file)
return -ENOMEM;
}
mutex_init(&user->file_lock);
user->cmd_timeout_ms = SBEFIFO_TIMEOUT_IN_CMD;
user->read_timeout_ms = SBEFIFO_TIMEOUT_START_RSP;
return 0;
@ -845,9 +848,11 @@ static ssize_t sbefifo_user_read(struct file *file, char __user *buf,
rc = mutex_lock_interruptible(&sbefifo->lock);
if (rc)
goto bail;
sbefifo->timeout_in_cmd_ms = user->cmd_timeout_ms;
sbefifo->timeout_start_rsp_ms = user->read_timeout_ms;
rc = __sbefifo_submit(sbefifo, user->pending_cmd, cmd_len, &resp_iter);
sbefifo->timeout_start_rsp_ms = SBEFIFO_TIMEOUT_START_RSP;
sbefifo->timeout_in_cmd_ms = SBEFIFO_TIMEOUT_IN_CMD;
mutex_unlock(&sbefifo->lock);
if (rc < 0)
goto bail;
@ -937,6 +942,25 @@ static int sbefifo_user_release(struct inode *inode, struct file *file)
return 0;
}
static int sbefifo_cmd_timeout(struct sbefifo_user *user, void __user *argp)
{
struct device *dev = &user->sbefifo->dev;
u32 timeout;
if (get_user(timeout, (__u32 __user *)argp))
return -EFAULT;
if (timeout == 0) {
user->cmd_timeout_ms = SBEFIFO_TIMEOUT_IN_CMD;
dev_dbg(dev, "Command timeout reset to %us\n", user->cmd_timeout_ms / 1000);
return 0;
}
user->cmd_timeout_ms = timeout * 1000; /* user timeout is in sec */
dev_dbg(dev, "Command timeout set to %us\n", timeout);
return 0;
}
static int sbefifo_read_timeout(struct sbefifo_user *user, void __user *argp)
{
struct device *dev = &user->sbefifo->dev;
@ -947,17 +971,12 @@ static int sbefifo_read_timeout(struct sbefifo_user *user, void __user *argp)
if (timeout == 0) {
user->read_timeout_ms = SBEFIFO_TIMEOUT_START_RSP;
dev_dbg(dev, "Timeout reset to %d\n", user->read_timeout_ms);
dev_dbg(dev, "Timeout reset to %us\n", user->read_timeout_ms / 1000);
return 0;
}
if (timeout < 10 || timeout > 120)
return -EINVAL;
user->read_timeout_ms = timeout * 1000; /* user timeout is in sec */
dev_dbg(dev, "Timeout set to %d\n", user->read_timeout_ms);
dev_dbg(dev, "Timeout set to %us\n", timeout);
return 0;
}
@ -971,6 +990,9 @@ static long sbefifo_user_ioctl(struct file *file, unsigned int cmd, unsigned lon
mutex_lock(&user->file_lock);
switch (cmd) {
case FSI_SBEFIFO_CMD_TIMEOUT_SECONDS:
rc = sbefifo_cmd_timeout(user, (void __user *)arg);
break;
case FSI_SBEFIFO_READ_TIMEOUT_SECONDS:
rc = sbefifo_read_timeout(user, (void __user *)arg);
break;
@ -1025,16 +1047,9 @@ static int sbefifo_probe(struct device *dev)
sbefifo->fsi_dev = fsi_dev;
dev_set_drvdata(dev, sbefifo);
mutex_init(&sbefifo->lock);
sbefifo->timeout_in_cmd_ms = SBEFIFO_TIMEOUT_IN_CMD;
sbefifo->timeout_start_rsp_ms = SBEFIFO_TIMEOUT_START_RSP;
/*
* Try cleaning up the FIFO. If this fails, we still register the
* driver and will try cleaning things up again on the next access.
*/
rc = sbefifo_cleanup_hw(sbefifo);
if (rc && rc != -ESHUTDOWN)
dev_err(dev, "Initial HW cleanup failed, will retry later\n");
/* Create chardev for userspace access */
sbefifo->dev.type = &fsi_cdev_type;
sbefifo->dev.parent = dev;

View file

@ -10,6 +10,7 @@
#include <linux/cdev.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/mod_devicetable.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/list.h>
@ -587,6 +588,12 @@ static int scom_remove(struct device *dev)
return 0;
}
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,
@ -600,6 +607,7 @@ static struct fsi_driver scom_drv = {
.drv = {
.name = "scom",
.bus = &fsi_bus_type,
.of_match_table = scom_of_ids,
.probe = scom_probe,
.remove = scom_remove,
}

28
drivers/fsi/fsi-slave.h Normal file
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 */

154
drivers/fsi/i2cr-scom.c Normal file
View file

@ -0,0 +1,154 @@
// 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 <linux/mod_devicetable.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 device *dev)
{
struct fsi_device *fsi_dev = to_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 int i2cr_scom_remove(struct device *dev)
{
struct i2cr_scom *scom = dev_get_drvdata(dev);
cdev_device_del(&scom->cdev, &scom->dev);
fsi_free_minor(scom->dev.devt);
return 0;
}
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 = {
.id_table = i2cr_scom_ids,
.drv = {
.name = "i2cr_scom",
.bus = &fsi_bus_type,
.of_match_table = i2cr_scom_of_ids,
.probe = i2cr_scom_probe,
.remove = i2cr_scom_remove,
}
};
module_fsi_driver(i2cr_scom_driver);
MODULE_AUTHOR("Eddie James <eajames@linux.ibm.com>");
MODULE_DESCRIPTION("IBM I2C Responder SCOM driver");
MODULE_LICENSE("GPL");

View file

@ -122,6 +122,37 @@ TRACE_EVENT(fsi_master_break,
)
);
TRACE_EVENT(fsi_master_scan,
TP_PROTO(const struct fsi_master *master, bool scan),
TP_ARGS(master, scan),
TP_STRUCT__entry(
__field(int, master_idx)
__field(int, n_links)
__field(bool, scan)
),
TP_fast_assign(
__entry->master_idx = master->idx;
__entry->n_links = master->n_links;
__entry->scan = scan;
),
TP_printk("fsi%d (%d links) %s", __entry->master_idx, __entry->n_links,
__entry->scan ? "scan" : "unscan")
);
TRACE_EVENT(fsi_master_unregister,
TP_PROTO(const struct fsi_master *master),
TP_ARGS(master),
TP_STRUCT__entry(
__field(int, master_idx)
__field(int, n_links)
),
TP_fast_assign(
__entry->master_idx = master->idx;
__entry->n_links = master->n_links;
),
TP_printk("fsi%d (%d links)", __entry->master_idx, __entry->n_links)
);
TRACE_EVENT(fsi_slave_init,
TP_PROTO(const struct fsi_slave *slave),
TP_ARGS(slave),

View file

@ -0,0 +1,107 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#undef TRACE_SYSTEM
#define TRACE_SYSTEM fsi_master_i2cr
#if !defined(_TRACE_FSI_MASTER_I2CR_H) || defined(TRACE_HEADER_MULTI_READ)
#define _TRACE_FSI_MASTER_I2CR_H
#include <linux/tracepoint.h>
TRACE_EVENT(i2cr_i2c_error,
TP_PROTO(const struct i2c_client *client, uint32_t command, int rc),
TP_ARGS(client, command, rc),
TP_STRUCT__entry(
__field(int, bus)
__field(int, rc)
__array(unsigned char, command, sizeof(uint32_t))
__field(unsigned short, addr)
),
TP_fast_assign(
__entry->bus = client->adapter->nr;
__entry->rc = rc;
memcpy(__entry->command, &command, sizeof(uint32_t));
__entry->addr = client->addr;
),
TP_printk("%d-%02x command:{ %*ph } rc:%d", __entry->bus, __entry->addr,
(int)sizeof(uint32_t), __entry->command, __entry->rc)
);
TRACE_EVENT(i2cr_read,
TP_PROTO(const struct i2c_client *client, uint32_t command, uint64_t *data),
TP_ARGS(client, command, data),
TP_STRUCT__entry(
__field(int, bus)
__array(unsigned char, data, sizeof(uint64_t))
__array(unsigned char, command, sizeof(uint32_t))
__field(unsigned short, addr)
),
TP_fast_assign(
__entry->bus = client->adapter->nr;
memcpy(__entry->data, data, sizeof(uint64_t));
memcpy(__entry->command, &command, sizeof(uint32_t));
__entry->addr = client->addr;
),
TP_printk("%d-%02x command:{ %*ph } { %*ph }", __entry->bus, __entry->addr,
(int)sizeof(uint32_t), __entry->command, (int)sizeof(uint64_t), __entry->data)
);
TRACE_EVENT(i2cr_status,
TP_PROTO(const struct i2c_client *client, uint64_t status),
TP_ARGS(client, status),
TP_STRUCT__entry(
__field(uint64_t, status)
__field(int, bus)
__field(unsigned short, addr)
),
TP_fast_assign(
__entry->status = status;
__entry->bus = client->adapter->nr;
__entry->addr = client->addr;
),
TP_printk("%d-%02x %016llx", __entry->bus, __entry->addr, __entry->status)
);
TRACE_EVENT(i2cr_status_error,
TP_PROTO(const struct i2c_client *client, uint64_t status, uint64_t error, uint64_t log),
TP_ARGS(client, status, error, log),
TP_STRUCT__entry(
__field(uint64_t, error)
__field(uint64_t, log)
__field(uint64_t, status)
__field(int, bus)
__field(unsigned short, addr)
),
TP_fast_assign(
__entry->error = error;
__entry->log = log;
__entry->status = status;
__entry->bus = client->adapter->nr;
__entry->addr = client->addr;
),
TP_printk("%d-%02x status:%016llx error:%016llx log:%016llx", __entry->bus, __entry->addr,
__entry->status, __entry->error, __entry->log)
);
TRACE_EVENT(i2cr_write,
TP_PROTO(const struct i2c_client *client, uint32_t command, uint64_t data),
TP_ARGS(client, command, data),
TP_STRUCT__entry(
__field(int, bus)
__array(unsigned char, data, sizeof(uint64_t))
__array(unsigned char, command, sizeof(uint32_t))
__field(unsigned short, addr)
),
TP_fast_assign(
__entry->bus = client->adapter->nr;
memcpy(__entry->data, &data, sizeof(uint64_t));
memcpy(__entry->command, &command, sizeof(uint32_t));
__entry->addr = client->addr;
),
TP_printk("%d-%02x command:{ %*ph } { %*ph }", __entry->bus, __entry->addr,
(int)sizeof(uint32_t), __entry->command, (int)sizeof(uint64_t), __entry->data)
);
#endif
#include <trace/define_trace.h>

View file

@ -59,6 +59,16 @@ struct scom_access {
* /dev/sbefifo* ioctl interface
*/
/**
* FSI_SBEFIFO_CMD_TIMEOUT sets the timeout for writing data to the SBEFIFO.
*
* The command timeout is specified in seconds. The minimum value of command
* timeout is 1 seconds (default) and the maximum value of command timeout is
* 120 seconds. A command timeout of 0 will reset the value to the default of
* 1 seconds.
*/
#define FSI_SBEFIFO_CMD_TIMEOUT_SECONDS _IOW('s', 0x01, __u32)
/**
* FSI_SBEFIFO_READ_TIMEOUT sets the read timeout for response from SBE.
*