summaryrefslogtreecommitdiff
path: root/drivers/fsi
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/fsi')
-rw-r--r--drivers/fsi/Kconfig1
-rw-r--r--drivers/fsi/fsi-core.c129
-rw-r--r--drivers/fsi/fsi-master-gpio.c89
-rw-r--r--drivers/fsi/fsi-master-hub.c27
-rw-r--r--drivers/fsi/fsi-master.h17
5 files changed, 248 insertions, 15 deletions
diff --git a/drivers/fsi/Kconfig b/drivers/fsi/Kconfig
index 513e35173aaa..a326ed663d3c 100644
--- a/drivers/fsi/Kconfig
+++ b/drivers/fsi/Kconfig
@@ -4,6 +4,7 @@
menuconfig FSI
tristate "FSI support"
+ depends on OF
select CRC4
---help---
FSI - the FRU Support Interface - is a simple bus for low-level
diff --git a/drivers/fsi/fsi-core.c b/drivers/fsi/fsi-core.c
index e318bf8c623c..4c03d6933646 100644
--- a/drivers/fsi/fsi-core.c
+++ b/drivers/fsi/fsi-core.c
@@ -18,6 +18,7 @@
#include <linux/fsi.h>
#include <linux/idr.h>
#include <linux/module.h>
+#include <linux/of.h>
#include <linux/slab.h>
#include <linux/bitops.h>
@@ -142,6 +143,7 @@ static void fsi_device_release(struct device *_device)
{
struct fsi_device *device = to_fsi_dev(_device);
+ of_node_put(device->dev.of_node);
kfree(device);
}
@@ -205,7 +207,7 @@ static int fsi_slave_report_and_clear_errors(struct fsi_slave *slave)
if (rc)
return rc;
- dev_info(&slave->dev, "status: 0x%08x, sisc: 0x%08x\n",
+ dev_dbg(&slave->dev, "status: 0x%08x, sisc: 0x%08x\n",
be32_to_cpu(stat), be32_to_cpu(irq));
/* clear interrupts */
@@ -334,6 +336,57 @@ extern void fsi_slave_release_range(struct fsi_slave *slave,
}
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;
+
+ na = of_n_addr_cells(np);
+ ns = of_n_size_cells(np);
+
+ if (na != 1 || ns != 1)
+ return false;
+
+ prop = of_get_property(np, "reg", &len);
+ if (!prop || len != 8)
+ 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);
+ }
+
+ return true;
+}
+
+/* Find a matching node for the slave engine at @address, using @size bytes
+ * of space. Returns NULL if not found, or a matching node with refcount
+ * already incremented.
+ */
+static struct device_node *fsi_device_find_of_node(struct fsi_device *dev)
+{
+ struct device_node *parent, *np;
+
+ parent = dev_of_node(&dev->slave->dev);
+ if (!parent)
+ return NULL;
+
+ for_each_child_of_node(parent, np) {
+ if (fsi_device_node_matches(&dev->dev, np,
+ dev->addr, dev->size))
+ return np;
+ }
+
+ return NULL;
+}
+
static int fsi_slave_scan(struct fsi_slave *slave)
{
uint32_t engine_addr;
@@ -402,6 +455,7 @@ static int fsi_slave_scan(struct fsi_slave *slave)
dev_set_name(&dev->dev, "%02x:%02x:%02x:%02x",
slave->master->idx, slave->link,
slave->id, i - 2);
+ dev->dev.of_node = fsi_device_find_of_node(dev);
rc = device_register(&dev->dev);
if (rc) {
@@ -558,9 +612,53 @@ static void fsi_slave_release(struct device *dev)
{
struct fsi_slave *slave = to_fsi_slave(dev);
+ of_node_put(dev->of_node);
kfree(slave);
}
+static bool fsi_slave_node_matches(struct device_node *np,
+ int link, uint8_t id)
+{
+ unsigned int len, na, ns;
+ const __be32 *prop;
+
+ 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)
+ 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);
+}
+
+/* Find a matching node for the slave at (link, id). Returns NULL if none
+ * found, or a matching node with refcount already incremented.
+ */
+static struct device_node *fsi_slave_find_of_node(struct fsi_master *master,
+ int link, uint8_t id)
+{
+ struct device_node *parent, *np;
+
+ parent = dev_of_node(&master->dev);
+ if (!parent)
+ return NULL;
+
+ for_each_child_of_node(parent, np) {
+ if (fsi_slave_node_matches(np, link, id))
+ return np;
+ }
+
+ return NULL;
+}
+
static int fsi_slave_init(struct fsi_master *master, int link, uint8_t id)
{
uint32_t chip_id, llmode;
@@ -589,7 +687,7 @@ static int fsi_slave_init(struct fsi_master *master, int link, uint8_t id)
return -EIO;
}
- dev_info(&master->dev, "fsi: found chip %08x at %02x:%02x:%02x\n",
+ dev_dbg(&master->dev, "fsi: found chip %08x at %02x:%02x:%02x\n",
chip_id, master->idx, link, id);
rc = fsi_slave_set_smode(master, link, id);
@@ -623,6 +721,7 @@ static int fsi_slave_init(struct fsi_master *master, int link, uint8_t id)
slave->master = master;
slave->dev.parent = &master->dev;
+ slave->dev.of_node = fsi_slave_find_of_node(master, link, id);
slave->dev.release = fsi_slave_release;
slave->link = link;
slave->id = id;
@@ -656,10 +755,13 @@ static int fsi_slave_init(struct fsi_master *master, int link, uint8_t id)
/* FSI master support */
static int fsi_check_access(uint32_t addr, size_t size)
{
- if (size != 1 && size != 2 && size != 4)
- return -EINVAL;
-
- if ((addr & 0x3) != (size & 0x3))
+ if (size == 4) {
+ if (addr & 0x3)
+ return -EINVAL;
+ } else if (size == 2) {
+ if (addr & 0x1)
+ return -EINVAL;
+ } else if (size != 1)
return -EINVAL;
return 0;
@@ -762,14 +864,20 @@ static void fsi_master_unscan(struct fsi_master *master)
device_for_each_child(&master->dev, NULL, fsi_master_remove_slave);
}
+int fsi_master_rescan(struct fsi_master *master)
+{
+ fsi_master_unscan(master);
+ return fsi_master_scan(master);
+}
+EXPORT_SYMBOL_GPL(fsi_master_rescan);
+
static ssize_t master_rescan_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct fsi_master *master = to_fsi_master(dev);
int rc;
- fsi_master_unscan(master);
- rc = fsi_master_scan(master);
+ rc = fsi_master_rescan(master);
if (rc < 0)
return rc;
@@ -793,6 +901,7 @@ static DEVICE_ATTR(break, 0200, NULL, master_break_store);
int fsi_master_register(struct fsi_master *master)
{
int rc;
+ struct device_node *np;
if (!master)
return -EINVAL;
@@ -820,7 +929,9 @@ int fsi_master_register(struct fsi_master *master)
return rc;
}
- fsi_master_scan(master);
+ np = dev_of_node(&master->dev);
+ if (!of_property_read_bool(np, "no-scan-on-init"))
+ fsi_master_scan(master);
return 0;
}
diff --git a/drivers/fsi/fsi-master-gpio.c b/drivers/fsi/fsi-master-gpio.c
index ae2618768508..3f487449a277 100644
--- a/drivers/fsi/fsi-master-gpio.c
+++ b/drivers/fsi/fsi-master-gpio.c
@@ -9,6 +9,7 @@
#include <linux/gpio/consumer.h>
#include <linux/io.h>
#include <linux/module.h>
+#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
@@ -59,6 +60,7 @@ struct fsi_master_gpio {
struct gpio_desc *gpio_trans; /* Voltage translator */
struct gpio_desc *gpio_enable; /* FSI enable */
struct gpio_desc *gpio_mux; /* Mux control */
+ bool external_mode;
};
#define CREATE_TRACE_POINTS
@@ -411,6 +413,12 @@ static int fsi_master_gpio_xfer(struct fsi_master_gpio *master, uint8_t slave,
int rc;
spin_lock_irqsave(&master->cmd_lock, flags);
+
+ if (master->external_mode) {
+ spin_unlock_irqrestore(&master->cmd_lock, flags);
+ return -EBUSY;
+ }
+
serial_out(master, cmd);
echo_delay(master);
rc = poll_for_response(master, slave, resp_len, resp);
@@ -461,12 +469,18 @@ static int fsi_master_gpio_term(struct fsi_master *_master,
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);
+ spin_lock_irqsave(&master->cmd_lock, flags);
+ if (master->external_mode) {
+ spin_unlock_irqrestore(&master->cmd_lock, flags);
+ return -EBUSY;
+ }
set_sda_output(master, 1);
sda_out(master, 1);
clock_toggle(master, FSI_PRE_BREAK_CLOCKS);
@@ -475,6 +489,7 @@ static int fsi_master_gpio_break(struct fsi_master *_master, int link)
echo_delay(master);
sda_out(master, 1);
clock_toggle(master, FSI_POST_BREAK_CLOCKS);
+ spin_unlock_irqrestore(&master->cmd_lock, flags);
/* Wait for logic reset to take effect */
udelay(200);
@@ -494,21 +509,84 @@ static void fsi_master_gpio_init(struct fsi_master_gpio *master)
clock_zeros(master, FSI_INIT_CLOCKS);
}
+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)
{
struct fsi_master_gpio *master = to_fsi_master_gpio(_master);
+ unsigned long flags;
+ int rc = -EBUSY;
if (link != 0)
return -ENODEV;
- gpiod_set_value(master->gpio_enable, 1);
- return 0;
+ spin_lock_irqsave(&master->cmd_lock, flags);
+ if (!master->external_mode) {
+ gpiod_set_value(master->gpio_enable, 1);
+ rc = 0;
+ }
+ spin_unlock_irqrestore(&master->cmd_lock, flags);
+
+ return rc;
+}
+
+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 flags, val;
+ bool external_mode;
+ int err;
+
+ err = kstrtoul(buf, 0, &val);
+ if (err)
+ return err;
+
+ external_mode = !!val;
+
+ spin_lock_irqsave(&master->cmd_lock, flags);
+
+ if (external_mode == master->external_mode) {
+ spin_unlock_irqrestore(&master->cmd_lock, flags);
+ return count;
+ }
+
+ master->external_mode = external_mode;
+ if (master->external_mode)
+ fsi_master_gpio_init_external(master);
+ else
+ fsi_master_gpio_init(master);
+ spin_unlock_irqrestore(&master->cmd_lock, flags);
+
+ fsi_master_rescan(&master->master);
+
+ return count;
+}
+
+static DEVICE_ATTR(external_mode, 0664,
+ external_mode_show, external_mode_store);
+
static int fsi_master_gpio_probe(struct platform_device *pdev)
{
struct fsi_master_gpio *master;
struct gpio_desc *gpio;
+ int rc;
master = devm_kzalloc(&pdev->dev, sizeof(*master), GFP_KERNEL);
if (!master)
@@ -516,6 +594,7 @@ static int fsi_master_gpio_probe(struct platform_device *pdev)
master->dev = &pdev->dev;
master->master.dev.parent = master->dev;
+ master->master.dev.of_node = of_node_get(dev_of_node(master->dev));
gpio = devm_gpiod_get(&pdev->dev, "clock", 0);
if (IS_ERR(gpio)) {
@@ -565,6 +644,10 @@ static int fsi_master_gpio_probe(struct platform_device *pdev)
fsi_master_gpio_init(master);
+ rc = device_create_file(&pdev->dev, &dev_attr_external_mode);
+ if (rc)
+ return rc;
+
return fsi_master_register(&master->master);
}
@@ -583,6 +666,8 @@ static int fsi_master_gpio_remove(struct platform_device *pdev)
devm_gpiod_put(&pdev->dev, master->gpio_mux);
fsi_master_unregister(&master->master);
+ of_node_put(master->master.dev.of_node);
+
return 0;
}
diff --git a/drivers/fsi/fsi-master-hub.c b/drivers/fsi/fsi-master-hub.c
index 133b9bff1d65..5885fc4a1ef0 100644
--- a/drivers/fsi/fsi-master-hub.c
+++ b/drivers/fsi/fsi-master-hub.c
@@ -16,6 +16,7 @@
#include <linux/delay.h>
#include <linux/fsi.h>
#include <linux/module.h>
+#include <linux/of.h>
#include <linux/slab.h>
#include "fsi-master.h"
@@ -253,7 +254,7 @@ static int hub_master_probe(struct device *dev)
reg = be32_to_cpu(__reg);
links = (reg >> 8) & 0xff;
- dev_info(dev, "hub version %08x (%d links)\n", reg, links);
+ 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);
@@ -274,6 +275,7 @@ static int hub_master_probe(struct device *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;
@@ -286,10 +288,19 @@ static int hub_master_probe(struct device *dev)
hub_master_init(hub);
rc = fsi_master_register(&hub->master);
- if (!rc)
- return 0;
+ 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;
- kfree(hub);
err_release:
fsi_slave_release_range(fsi_dev->slave, FSI_HUB_LINK_OFFSET,
FSI_HUB_LINK_SIZE * links);
@@ -302,6 +313,14 @@ static int hub_master_remove(struct device *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);
+
return 0;
}
diff --git a/drivers/fsi/fsi-master.h b/drivers/fsi/fsi-master.h
index 12f7b119567d..ee0b46086026 100644
--- a/drivers/fsi/fsi-master.h
+++ b/drivers/fsi/fsi-master.h
@@ -37,7 +37,24 @@ struct fsi_master {
#define dev_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 */