[PATCH linux v3 17/18] drivers/fsi: Add GPIO master functionality
christopher.lee.bostic at gmail.com
christopher.lee.bostic at gmail.com
Wed Oct 12 08:40:02 AEDT 2016
From: Chris Bostic <cbostic at us.ibm.com>
Add setup of the GPIO pins for FSI master function. Setup I/O directions,
define all pins and setup their initial directions. Define serial out
operation.
V3 - Added remainder of base I/O ops (excluding dev tree and crc calcs).
- Add encoding of all read/write commands and send data over sda
- Check for and decode response from slave.
- Add set enable pin in master->link_enable() master specific function.
Signed-off-by: Chris Bostic <cbostic at us.ibm.com>
---
drivers/fsi/fsi-master-gpio.c | 355 ++++++++++++++++++++++++++++++++++++++++--
1 file changed, 344 insertions(+), 11 deletions(-)
diff --git a/drivers/fsi/fsi-master-gpio.c b/drivers/fsi/fsi-master-gpio.c
index 166370a..fe757cc 100644
--- a/drivers/fsi/fsi-master-gpio.c
+++ b/drivers/fsi/fsi-master-gpio.c
@@ -5,15 +5,52 @@
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <linux/module.h>
+#include <linux/delay.h>
#include "fsi-master.h"
#include "fsi-cfam.h"
#include "fsi-slave.h"
+#define FSI_ECHO_DELAY_CLOCKS 16 /* Number clocks for echo delay */
+#define FSI_PRE_BREAK_CLOCKS 50 /* Number clocks to prep for break */
+#define FSI_BREAK_CLOCKS 256 /* Number of clocks to issue break */
+#define FSI_POST_BREAK_CLOCKS 16000 /* Number clocks to set up cfam */
+#define FSI_INIT_CLOCKS 5000 /* Clear out any old data and states */
+
+#define FSI_GPIO_STD_DELAY 10 /* Standard GPIO delay in nS */
+ /* todo: adjust down as low as */
+ /* possible or eliminate */
+
+#define FSI_GPIO_CMD_DPOLL 0x0000808A
+#define FSI_GPIO_CMD_DPOLL_SIZE 10
+#define FSI_GPIO_DPOLL_CLOCKS 24 /* < 21 will cause slave to hang */
+#define FSI_GPIO_CMD_DEFAULT 0x2000000000000000ULL
+#define FSI_GPIO_CMD_WRITE 0
+#define FSI_GPIO_CMD_READ 0x0400000000000000ULL
+#define FSI_GPIO_CMD_SLAVE_MASK 0xC000000000000000ULL
+#define FSI_GPIO_CMD_ADDR_SHIFT 3
+#define FSI_GPIO_CMD_SIZE_16 0x0000001000000000ULL
+#define FSI_GPIO_CMD_SIZE_32 0x0000003000000000ULL
+#define FSI_GPIO_CMD_DATA_SHIFT 28
+#define FSI_GPIO_CMD_DFLT_LEN 32
+#define FSI_GPIO_RESP_BUSY 1
+#define FSI_GPIO_RESP_ERRA 2
+#define FSI_GPIO_RESP_ERRC 3
+#define FSI_GPIO_RESP_ACK 0
+#define FSI_GPIO_RESP_ACKD 4
+#define FSI_GPIO_MAX_BUSY 100
+#define FSI_GPIO_MTOE_COUNT 1000
+#define FSI_GPIO_MTOE 1
+#define FSI_GPIO_DRAIN_BITS 20
+#define FSI_GPIO_CRC_SIZE 4
+
struct fsi_master_gpio {
struct fsi_master master;
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 */
};
#define to_fsi_master_gpio(m) container_of(m, struct fsi_master_gpio, master)
@@ -23,33 +60,287 @@ struct fsi_gpio_msg {
uint8_t bits;
};
+static void add_crc(struct fsi_gpio_msg *cmd)
+{
+
+}
+
+static void set_clock(struct fsi_master_gpio *master)
+{
+ /* This delay is required - do not remove */
+ ndelay(FSI_GPIO_STD_DELAY);
+ gpiod_set_value(master->gpio_clk, 1);
+}
+
+static void clear_clock(struct fsi_master_gpio *master)
+{
+ /* This delay is required - do not remove */
+ ndelay(FSI_GPIO_STD_DELAY);
+ gpiod_set_value(master->gpio_clk, 0);
+}
+
+static void clock_toggle(struct fsi_master_gpio *master, int count)
+{
+ int i;
+
+ for (i = 0; i < count; i++) {
+ clear_clock(master);
+ set_clock(master);
+ }
+}
+
+static int sda_in(struct fsi_master_gpio *master)
+{
+ return gpiod_get_value(master->gpio_data);
+}
+
+static void sda_out(struct fsi_master_gpio *master, int value)
+{
+ gpiod_set_value(master->gpio_data, value);
+
+ /* Required -do not remove */
+ ndelay(FSI_GPIO_STD_DELAY);
+}
+
+static void set_sda_input(struct fsi_master_gpio *master)
+{
+ gpiod_direction_input(master->gpio_data);
+ gpiod_direction_output(master->gpio_trans, 0);
+}
+
+static void set_sda_output(struct fsi_master_gpio *master,
+ int value)
+{
+ gpiod_direction_output(master->gpio_data, value);
+ gpiod_direction_output(master->gpio_trans, 1);
+}
+
+static void serial_in(struct fsi_master_gpio *master, struct fsi_gpio_msg *cmd)
+{
+ uint8_t bit;
+ uint64_t msg = 0;
+ uint8_t in_bit = 0;
+
+ set_sda_input(master);
+
+ for (bit = 0; bit < cmd->bits; bit++) {
+ clock_toggle(master, 1);
+ in_bit = sda_in(master);
+ msg |= in_bit;
+ msg <<= 1;
+ }
+ cmd->msg = ~msg; /* Data is negative active */
+}
+
+static void serial_out(struct fsi_master_gpio *master, struct fsi_gpio_msg *cmd)
+{
+ uint8_t bit;
+ uint64_t msg = ~cmd->msg; /* Data is negative active */
+ uint64_t sda_mask = 1;
+ uint64_t last_bit = ~0;
+
+ set_sda_output(master, 0);
+
+ /* Send the start bit */
+ sda_out(master, 1);
+ clock_toggle(master, 1);
+
+ /* Send the message */
+ for (bit = 0; bit < cmd->bits; bit++) {
+ if (last_bit ^ (msg & sda_mask)) {
+ sda_out(master, msg & sda_mask);
+ last_bit = msg & sda_mask;
+ }
+ clock_toggle(master, 1);
+ msg >>= 1;
+ }
+}
+
+/*
+ * Clock out some 0's after every message to ride out line reflections
+ */
+static void echo_delay(struct fsi_master_gpio *master)
+{
+ set_sda_output(master, 0);
+ clock_toggle(master, FSI_ECHO_DELAY_CLOCKS);
+}
+
+/*
+ * Used in bus error cases only. Clears out any remaining data the slave
+ * is attempting to send
+ */
+static void drain_response(struct fsi_master_gpio *master)
+{
+ struct fsi_gpio_msg msg;
+
+ msg.bits = FSI_GPIO_DRAIN_BITS;
+ serial_in(master, &msg);
+}
+
+/*
+ * Store information on master errors so handler can detect and clean
+ * up the bus
+ */
+static void fsi_master_gpio_error(struct fsi_master_gpio *master, int error)
+{
+
+}
+
+static int poll_for_response(struct fsi_master_gpio *master, uint8_t expected,
+ uint8_t size, void *data)
+{
+ int busy_count = 0, i;
+ struct fsi_gpio_msg response, cmd;
+ int bits_remaining = 0;
+
+ do {
+ response.msg = 0;
+ response.bits = 1;
+ for (i = 0; i < FSI_GPIO_MTOE_COUNT; i++) {
+ serial_in(master, &response);
+ if (response.msg)
+ break;
+ }
+ if (unlikely(i >= FSI_GPIO_MTOE_COUNT)) {
+ dev_info(master->master.dev,
+ "Master time out waiting for response\n");
+ drain_response(master);
+ fsi_master_gpio_error(master, FSI_GPIO_MTOE);
+ return FSI_GPIO_MTOE;
+ }
+
+ /* Response received */
+ response.msg = 0;
+ response.bits = 2;
+ serial_in(master, &response);
+ dev_info(master->master.dev, "cfam id:%d\n", (int)response.msg);
+
+ response.msg = 0;
+ response.bits = 2;
+ serial_in(master, &response);
+ dev_info(master->master.dev, "response id:%d\n",
+ (int)response.msg);
+
+ switch (response.msg) {
+ case FSI_GPIO_RESP_ACK:
+ if (expected == FSI_GPIO_RESP_ACKD)
+ bits_remaining = 8 * sizeof(size);
+ break;
+
+ case FSI_GPIO_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.
+ */
+ set_sda_output(master, 0);
+ clock_toggle(master, FSI_GPIO_DPOLL_CLOCKS);
+ cmd.msg = FSI_GPIO_CMD_DPOLL;
+ cmd.bits = FSI_GPIO_CMD_DPOLL_SIZE;
+ serial_out(master, &cmd);
+ continue;
+
+ case FSI_GPIO_RESP_ERRA:
+ case FSI_GPIO_RESP_ERRC:
+ dev_info(master->master.dev, "ERR received: %d\n",
+ (int)response.msg);
+ drain_response(master);
+ fsi_master_gpio_error(master, response.msg);
+ return response.msg;
+ }
+
+ /* Read in the data field if applicable */
+ if (bits_remaining) {
+ response.msg = 0;
+ response.bits = bits_remaining;
+ serial_in(master, &response);
+ }
+
+ /* Read in the crc and check it */
+ response.msg = 0;
+ response.bits = FSI_GPIO_CRC_SIZE;
+ serial_in(master, &response);
+
+ /* todo: verify crc is correct, flag error if not */
+
+ return 0;
+
+ } while (busy_count++ < FSI_GPIO_MAX_BUSY);
+
+ return busy_count;
+}
+
+static void build_command(struct fsi_gpio_msg *cmd, uint64_t mode,
+ uint8_t slave, uint32_t addr, size_t size,
+ const void *data)
+{
+ cmd->bits = FSI_GPIO_CMD_DFLT_LEN;
+ cmd->msg = FSI_GPIO_CMD_DEFAULT;
+ cmd->msg |= mode;
+
+ /* todo: handle more than just slave id 0 */
+ cmd->msg &= ~FSI_GPIO_CMD_SLAVE_MASK;
+
+ cmd->msg |= (addr << FSI_GPIO_CMD_ADDR_SHIFT);
+ if (size == sizeof(uint8_t)) {
+ if (data)
+ cmd->msg |=
+ *((uint8_t *)data) >> FSI_GPIO_CMD_DATA_SHIFT;
+ } else if (size == sizeof(uint16_t)) {
+ cmd->msg |= FSI_GPIO_CMD_SIZE_16;
+ if (data)
+ cmd->msg |=
+ *((uint16_t *)data) >> FSI_GPIO_CMD_DATA_SHIFT;
+ } else {
+ cmd->msg |= FSI_GPIO_CMD_SIZE_32;
+ if (data)
+ cmd->msg |=
+ *((uint32_t *)data) >> FSI_GPIO_CMD_DATA_SHIFT;
+ }
+ if (mode == FSI_GPIO_CMD_WRITE)
+ cmd->bits += (8 * size);
+
+ add_crc(cmd);
+}
+
static int fsi_master_gpio_read(struct fsi_master *_master, int link,
uint8_t slave, 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;
- /* todo: format read into a message, send, poll for response */
- (void)master;
+ /* Send the command */
+ build_command(&cmd, FSI_GPIO_CMD_READ, slave, addr, size, NULL);
+ serial_out(master, &cmd);
+ echo_delay(master);
+ rc = poll_for_response(master, FSI_GPIO_RESP_ACKD, size, val);
- return 0;
+ return rc;
}
static int fsi_master_gpio_write(struct fsi_master *_master, int link,
uint8_t slave, uint32_t addr, const void *val, size_t size)
{
+ int rc;
struct fsi_master_gpio *master = to_fsi_master_gpio(_master);
+ struct fsi_gpio_msg cmd;
if (link != 0)
return -ENODEV;
- /* todo: format write into a message, send, poll for response */
- (void)master;
+ /* Send the command */
+ build_command(&cmd, FSI_GPIO_CMD_WRITE, slave, addr, size, val);
+ serial_out(master, &cmd);
+ echo_delay(master);
- return 0;
+ rc = poll_for_response(master, FSI_GPIO_RESP_ACK, size, NULL);
+
+ return rc;
}
/*
@@ -58,14 +349,40 @@ static int fsi_master_gpio_write(struct fsi_master *_master, int link,
static int fsi_master_gpio_break(struct fsi_master *_master, int link)
{
struct fsi_master_gpio *master = to_fsi_master_gpio(_master);
+ uint32_t smode;
+ int rc;
if (link != 0)
return -ENODEV;
- /* todo: send the break pattern over gpio */
- (void)master;
+ set_sda_output(master, 0);
+ clock_toggle(master, FSI_PRE_BREAK_CLOCKS);
- return 0;
+ sda_out(master, 1);
+ clock_toggle(master, FSI_BREAK_CLOCKS);
+
+ echo_delay(master);
+
+ sda_out(master, 0);
+ clock_toggle(master, FSI_POST_BREAK_CLOCKS);
+
+ udelay(200); /* wait for logic reset to take effect */
+ rc = _master->read(_master, link, 3, FSI_SLAVE_BASE + FSI_SMODE,
+ &smode, sizeof(smode));
+ dev_info(_master->dev, "smode after break:%08x rc:%d\n", smode, rc);
+
+ return rc;
+}
+
+static void fsi_master_gpio_init(struct fsi_master_gpio *master)
+{
+ gpiod_direction_output(master->gpio_mux, 1);
+ gpiod_direction_output(master->gpio_clk, 1);
+ set_sda_output(master, 1);
+ gpiod_direction_output(master->gpio_enable, 0);
+
+ /* todo: evaluate if clocks can be reduced */
+ clock_toggle(master, FSI_INIT_CLOCKS);
}
static int fsi_master_gpio_link_enable(struct fsi_master *_master, int link)
@@ -75,8 +392,7 @@ static int fsi_master_gpio_link_enable(struct fsi_master *_master, int link)
if (link != 0)
return -ENODEV;
- /* todo: set the enable pin */
- (void)master;
+ gpiod_set_value(master->gpio_enable, 1);
return 0;
}
@@ -100,11 +416,28 @@ static int fsi_master_gpio_probe(struct platform_device *pdev)
return PTR_ERR(gpio);
master->gpio_data = gpio;
+ gpio = devm_gpiod_get(&pdev->dev, "trans", 0);
+ if (IS_ERR(gpio))
+ return PTR_ERR(gpio);
+ master->gpio_trans = gpio;
+
+ gpio = devm_gpiod_get(&pdev->dev, "enable", 0);
+ if (IS_ERR(gpio))
+ return PTR_ERR(gpio);
+ master->gpio_enable = gpio;
+
+ gpio = devm_gpiod_get(&pdev->dev, "mux", 0);
+ if (IS_ERR(gpio))
+ return PTR_ERR(gpio);
+ master->gpio_mux = gpio;
+
master->master.read = fsi_master_gpio_read;
master->master.write = fsi_master_gpio_write;
master->master.send_break = fsi_master_gpio_break;
master->master.link_enable = fsi_master_gpio_link_enable;
+ fsi_master_gpio_init(master);
+
return fsi_master_register(&master->master);
}
--
1.8.2.2
More information about the openbmc
mailing list