[PATCH linux v6 16/18] drivers/fsi: Add GPIO FSI master
Alistair Popple
alistair at popple.id.au
Wed Nov 2 14:56:36 AEDT 2016
Comments below.
On Sun, 30 Oct 2016 05:09:18 PM christopher.lee.bostic at gmail.com wrote:
> From: Chris Bostic <cbostic at us.ibm.com>
>
> Implement a FSI master using GPIO. Will generate FSI protocol for
> read and write commands to particular addresses. Sends master command
> and waits for and decodes a slave response. Includes Jeremy Kerr's
> original GPIO master base commit.
>
> Signed-off-by: Jeremy Kerr <jk at ozlabs.org>
>
> ---
>
> 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.
>
> V4 - Remove unnecessary comments about delays in various locations
>
> - Define non clock and data pins as optional and check for gpio
> descriptors == NULL before accessing. Don't return error if
> optional pins cannot be retrieved via devm.
>
> - serial_in: rearrange order of msg shift and input bit OR op.
>
> - serial_out: Fix mirror image issue on shift data out and set
> data out as necessary for bit count to be sent.
>
> - serial_out: make message parm a const
>
> - serial_out: fix 3x 'msg & data' repeat
>
> - poll_for_response: bits remaining was calculated with
> sizeof(size) should be size.
>
> - fsi_master_gpio_break: remove smode read
>
> - Replace dev_info with dev_dbg in proper places.
>
> - Add a bit count parm to serial_in, increment msg->bits
> on every bit received.
>
> - Remove magic numbers in poll_for_response that indicate
> how many bits to receive
>
> - Utilize the crc utilities to check input data and set
> crc for output data
>
> - Remove add_crc stub
>
> V5 - Rename pins from generic 'clk', 'data', etc in dts file to
> 'fsi_clk', 'fsi_data', etc...
>
> - serial_in: invert data bit during message assembly
> instead of inverting whole message after assembly.
>
> - Remove all instances of clearing out message field prior
> to calling serial_in, redundant.
>
> - Change name of build_command() to build_abs_ar_command()
> to make it more obvious its creating ABS AR type commands.
>
> - Remove unlikely( ) checks.
>
> - Indent dev_info string "master time out waiting for response"
>
> - poll_for_response: return FSI_GPIO_MAX busy instead of
> busy_count.
>
> - fsi_master_gpio_break: move 'logic reset' comment to above
> the delay call.
>
> - Add crc4 calculation initialization since data includes
> a start bit.
>
> V6 - Invert polarity of SDA line for start bit, echo delay, pre-
> D-POLL clocking, break command sequence (pre, during, post).
>
> - Add echo delay after sending D-POLL
>
> - Fill in user read data with received read data response
>
> - Clock the slave after each command completes to prime it
> for another operation
>
> - Increase D-POLL checks on BUSY response before flagging
> error
>
> - Add Alistair's crc calculator from pdbg. Having issues
> with the existing one.
>
> - Revert V5 changes to rename gpio pin names.
>
> - Return -EIO on all bus errors
>
> - Condense slave id and response id into one serial-in
> operation
>
> - Change D-POLL command to accommodate right alignment
>
> - Remove all dev_info( ) calls to reduce spam
>
> - serial_in remove cmd->bits = 0 / cmd->bits++ in for
> loop. Just set value at end.
>
> - Change v translator line to direction output once
> during init and use gpiod_set_value thereafter
>
> - Remove set/clear clocks and do it all in clock_toggle
>
> - Fill in slave id when encoding commands. Right align
> the command prior to serializing out.
>
> - gpio master defines number of links to 1, was undefined.
> ---
> .../devicetree/bindings/fsi-master-gpio.txt | 21 +
> .../devicetree/bindings/fsi/fsi-master-gpio.txt | 21 +
> arch/arm/boot/dts/aspeed-bmc-opp-palmetto.dts | 36 ++
> drivers/fsi/Kconfig | 6 +
> drivers/fsi/Makefile | 1 +
> drivers/fsi/fsi-master-gpio.c | 522
+++++++++++++++++++++
> 6 files changed, 607 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/fsi-master-gpio.txt
> create mode 100644 Documentation/devicetree/bindings/fsi/fsi-master-
gpio.txt
> create mode 100644 drivers/fsi/fsi-master-gpio.c
>
> diff --git a/Documentation/devicetree/bindings/fsi-master-gpio.txt
b/Documentation/devicetree/bindings/fsi-master-gpio.txt
> new file mode 100644
> index 0000000..d46be27
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/fsi-master-gpio.txt
> @@ -0,0 +1,21 @@
> +Device-tree bindings for gpio-based FSI master driver
> +-----------------------------------------------------
> +
> +Required properties:
> + - compatible = "ibm,fsi-master-gpio";
> + - clk-gpio;
> + - data-gpio;
> +
> +Optional properties:
> + - enable-gpio;
> + - trans-gpio;
> + - mux-gpio;
> +
> +fsi-master {
> + compatible = "ibm,fsi-master", "ibm,fsi-master-gpio";
> + clk-gpio = <&gpio 0>;
> + data-gpio = <&gpio 1>;
> + enable-gpio = <&gpio 2>; /* Enable FSI data in/out */
> + trans-gpio = <&gpio 3>; /* Voltage translator direction */
> + mux-gpio = <&gpio 4>; /* Multiplexer for FSI pins */
> +}
> diff --git a/Documentation/devicetree/bindings/fsi/fsi-master-gpio.txt
b/Documentation/devicetree/bindings/fsi/fsi-master-gpio.txt
> new file mode 100644
> index 0000000..ff3a62e
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/fsi/fsi-master-gpio.txt
> @@ -0,0 +1,21 @@
> +Device-tree bindings for gpio-based FSI master driver
> +-----------------------------------------------------
> +
> +Required properties:
> + - compatible = "ibm,fsi-master-gpio";
> + - clk-gpios;
> + - data-gpios;
> +
> +Optional properties:
> + - enable-gpios;
> + - trans-gpios;
> + - mux-gpios;
> +
> +fsi-master {
> + compatible = "ibm,fsi-master", "ibm,fsi-master-gpio";
> + clk-gpios = <&gpio 0 &gpio 6>;
> + data-gpios = <&gpio 1 &gpio 7>;
> + enable-gpios = <&gpio 2 &gpio 8>; /* Enable FSI data in/out */
> + trans-gpios = <&gpio 3 &gpio 9>; /* Volts translator direction
*/
> + mux-gpios = <&gpio 4> &gpio 10>; /* Multiplexer for FSI pins */
> +}
> diff --git a/arch/arm/boot/dts/aspeed-bmc-opp-palmetto.dts
b/arch/arm/boot/dts/aspeed-bmc-opp-palmetto.dts
> index 21619fd..9d9c2a9 100644
> --- a/arch/arm/boot/dts/aspeed-bmc-opp-palmetto.dts
> +++ b/arch/arm/boot/dts/aspeed-bmc-opp-palmetto.dts
> @@ -167,6 +167,42 @@
> output-low;
> line-name = "func_mode2";
> };
> +
> + pin_fsi_clk {
> + gpios = <ASPEED_GPIO(A, 4) GPIO_ACTIVE_HIGH>;
> + output-low;
> + line-name = "fsi_clk";
> + };
> +
> + pin_fsi_data {
> + gpios = <ASPEED_GPIO(A, 5) GPIO_ACTIVE_HIGH>;
> + output-low;
> + line-name = "fsi_data";
> + };
> +
> + pin_fsi_trans {
> + gpios = <ASPEED_GPIO(H, 6) GPIO_ACTIVE_HIGH>;
> + output-low;
> + line-name = "fsi_trans";
> + };
> +
> + pin_fsi_enable {
> + gpios = <ASPEED_GPIO(D, 0) GPIO_ACTIVE_HIGH>;
> + output-low;
> + line-name = "fsi_enable";
> + };
> +
> + pin_fsi_mux {
> + gpios = <ASPEED_GPIO(A, 6) GPIO_ACTIVE_HIGH>;
> + output-low;
> + line-name = "fsi_mux";
> + };
> +
> + fsi_master {
> + compatible = "ibm,fsi-master", "ibm,fsi-master-gpio";
> + clk-gpios = <&gpio 0 &gpio 6>;
> + data-gpios = <&gpio 1 &gpio 7>;
> + };
> };
>
> &vuart {
> diff --git a/drivers/fsi/Kconfig b/drivers/fsi/Kconfig
> index f065dbe..b21fe3c 100644
> --- a/drivers/fsi/Kconfig
> +++ b/drivers/fsi/Kconfig
> @@ -17,6 +17,12 @@ config FSI_MASTER_FAKE
> depends on FSI
> ---help---
> This option enables a fake FSI master driver for debugging.
> +
> +config FSI_MASTER_GPIO
> + tristate "GPIO-based FSI master"
> + depends on FSI
> + ---help---
> + This option enables a FSI master driver using GPIO lines.
> endif
>
> endmenu
> diff --git a/drivers/fsi/Makefile b/drivers/fsi/Makefile
> index 847c00c..2021ce5 100644
> --- a/drivers/fsi/Makefile
> +++ b/drivers/fsi/Makefile
> @@ -1,3 +1,4 @@
>
> obj-$(CONFIG_FSI) += fsi-core.o
> obj-$(CONFIG_FSI_MASTER_FAKE) += fsi-master-fake.o
> +obj-$(CONFIG_FSI_MASTER_GPIO) += fsi-master-gpio.o
> diff --git a/drivers/fsi/fsi-master-gpio.c b/drivers/fsi/fsi-master-gpio.c
> new file mode 100644
> index 0000000..175281a
> --- /dev/null
> +++ b/drivers/fsi/fsi-master-gpio.c
> @@ -0,0 +1,522 @@
> +/*
> + * A FSI master controller, using a simple GPIO bit-banging interface
> + */
> +
> +#include <linux/platform_device.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/module.h>
> +#include <linux/delay.h>
> +#include <linux/fsi.h>
> +
> +#include "fsi-master.h"
> +
> +#define FSI_GPIO_STD_DLY 1 /* Standard pin delay in nS */
> +#define FSI_ECHO_DELAY_CLOCKS 16 /* Number clocks for echo
delay */
> +#define FSI_PRE_BREAK_CLOCKS 50 /* Number clocks to prep for
break */
> +#define FSI_BREAK_CLOCKS 256 /* Number of clocks to issue
break */
> +#define FSI_POST_BREAK_CLOCKS 16000 /* Number clocks to set up
cfam */
> +#define FSI_INIT_CLOCKS 5000 /* Clock out any old data */
> +#define FSI_GPIO_STD_DELAY 10 /* Standard GPIO delay in nS
*/
> + /* todo: adjust down as low as */
> + /* possible or eliminate */
> +#define FSI_GPIO_CMD_DPOLL 0x000000000000002AULL
> +#define FSI_GPIO_CMD_DPOLL_SIZE 9
> +#define FSI_GPIO_DPOLL_CLOCKS 100 /* < 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 37
> +#define FSI_GPIO_CMD_SLV_SHIFT 62
> +#define FSI_GPIO_CMD_SIZE_16 0x0000001000000000ULL
> +#define FSI_GPIO_CMD_SIZE_32 0x0000003000000000ULL
> +#define FSI_GPIO_CMD_DT32_SHIFT 4
> +#define FSI_GPIO_CMD_DT16_SHIFT 20
> +#define FSI_GPIO_CMD_DT8_SHIFT 28
> +#define FSI_GPIO_CMD_DFLT_LEN 28
> +#define FSI_GPIO_CMD_CRC_SHIFT 60
> +
> +/* Bus errors */
> +#define FSI_GPIO_ERR_BUSY 1 /* Slave stuck in busy state
*/
> +#define FSI_GPIO_RESP_ERRA 2 /* Any (misc) Error */
> +#define FSI_GPIO_RESP_ERRC 3 /* Slave reports master CRC
error */
> +#define FSI_GPIO_MTOE 4 /* Master time out error */
> +#define FSI_GPIO_CRC_INVAL 5 /* Master reports slave CRC
error */
> +
> +/* Normal slave responses */
> +#define FSI_GPIO_RESP_BUSY 1
> +#define FSI_GPIO_RESP_ACK 0
> +#define FSI_GPIO_RESP_ACKD 4
> +
> +#define FSI_GPIO_MAX_BUSY 100
> +#define FSI_GPIO_MTOE_COUNT 1000
> +#define FSI_GPIO_DRAIN_BITS 20
> +#define FSI_GPIO_CRC_SIZE 4
> +#define FSI_GPIO_MSG_ID_SIZE 2
> +#define FSI_GPIO_MSG_RESPID_SIZE 2
> +#define FSI_GPIO_PRIME_SLAVE_CLOCKS 100
> +
> +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)
> +
> +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++) {
> + ndelay(FSI_GPIO_STD_DLY);
> + gpiod_set_value(master->gpio_clk, 0);
> + ndelay(FSI_GPIO_STD_DLY);
> + gpiod_set_value(master->gpio_clk, 1);
> + }
> +}
> +
> +static int sda_in(struct fsi_master_gpio *master)
> +{
> + ndelay(FSI_GPIO_STD_DLY);
> + 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);
> +}
> +
> +static void set_sda_input(struct fsi_master_gpio *master)
> +{
> + gpiod_direction_input(master->gpio_data);
> + if (master->gpio_trans)
> + gpiod_set_value(master->gpio_trans, 0);
> +}
> +
> +static void set_sda_output(struct fsi_master_gpio *master, int value)
> +{
> + gpiod_direction_output(master->gpio_data, value);
> + if (master->gpio_trans)
> + gpiod_set_value(master->gpio_trans, 1);
> +}
> +
> +static void serial_in(struct fsi_master_gpio *master, struct fsi_gpio_msg
*cmd,
> + uint8_t num_bits)
> +{
> + uint8_t bit;
> + uint64_t msg = 0;
> + uint8_t in_bit = 0;
> +
> + set_sda_input(master);
> +
> + for (bit = 0; bit < num_bits; bit++) {
> + clock_toggle(master, 1);
> + in_bit = sda_in(master);
> + msg <<= 1;
> + msg |= ~in_bit & 0x1; /* Data is negative active */
> + }
> + cmd->bits = num_bits;
> + cmd->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 negative active */
> + uint64_t sda_mask = 0x1ULL << (cmd->bits - 1);
> + uint64_t last_bit = ~0;
> + int next_bit;
> +
> + if (!cmd->bits) {
> + dev_warn(master->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;
> + }
> +}
> +
> +/*
> + * 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, 1);
> + 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;
> +
> + serial_in(master, &msg, FSI_GPIO_DRAIN_BITS);
> +}
> +
> +/*
> + * 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)
> +{
> +
> +}
> +
> +/*
> + * Taken from Alistaire's PDBG
> + */
> +static uint8_t crc4(uint8_t c, int b)
> +{
> + uint8_t m = 0;
> +
> + c &= 0xf;
> + m = b ^ ((c >> 3) & 0x1);
> + m = (m << 2) | (m << 1) | (m);
> + c <<= 1;
> + c ^= m;
> +
> + return c & 0xf;
> +}
> +
> +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, bit_count, response_id, id;
> + uint64_t resp = 0;
> + uint8_t bits_received = FSI_GPIO_MSG_ID_SIZE +
> + FSI_GPIO_MSG_RESPID_SIZE;
> + uint8_t crc_in;
> +
> + do {
> + for (i = 0; i < FSI_GPIO_MTOE_COUNT; i++) {
> + serial_in(master, &response, 1);
> + if (response.msg)
> + break;
> + }
> + if (i >= FSI_GPIO_MTOE_COUNT) {
> + dev_dbg(master->master.dev,
> + "Master time out waiting for response\n");
> + drain_response(master);
> + fsi_master_gpio_error(master, FSI_GPIO_MTOE);
> + return -EIO;
> + }
> +
> + /* Response received */
> + bit_count = FSI_GPIO_MSG_ID_SIZE + FSI_GPIO_MSG_RESPID_SIZE;
> + serial_in(master, &response, bit_count);
> +
> + response_id = response.msg & 0x3;
> + id = (response.msg >> FSI_GPIO_MSG_RESPID_SIZE) & 0x3;
> + dev_dbg(master->master.dev, "id:%d resp:%d\n", id,
response_id);
> +
> + resp = response.msg;
> +
> + switch (response_id) {
> + case FSI_GPIO_RESP_ACK:
> + if (expected == FSI_GPIO_RESP_ACKD)
> + bits_remaining = 8 * 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.
> + */
Interesting. Have HW folk confirmed this? I've encountered similar issues on
occasion, so it would be good to get confirmation that we need a minimum of 20
slave clocks prior to sending a d-poll.
> + set_sda_output(master, 1);
> + clock_toggle(master, FSI_GPIO_DPOLL_CLOCKS);
> + cmd.msg = FSI_GPIO_CMD_DPOLL;
> + cmd.bits = FSI_GPIO_CMD_DPOLL_SIZE;
> + serial_out(master, &cmd);
> + echo_delay(master);
> + continue;
> +
> + case FSI_GPIO_RESP_ERRA:
> + case FSI_GPIO_RESP_ERRC:
> + dev_dbg(master->master.dev, "ERR received: %d\n",
> + (int)response.msg);
> + drain_response(master);
Why do we have to call drain_response() for an error code? Isn't ERRA/C a well
defined bit sequence?
> + fsi_master_gpio_error(master, response.msg);
> + return -EIO;
> + }
> +
> + /* Read in the data field if applicable */
> + if (bits_remaining) {
> + serial_in(master, &response, bits_remaining);
> + resp <<= bits_remaining;
> + resp |= response.msg;
> + bits_received += bits_remaining;
> + *((uint32_t *)data) = response.msg;
> + }
> +
> + /* Include start bit */
> + crc_in = crc4(0, 1);
> + for (i = bits_received - 1; i >= 0; i--)
> + crc_in = crc4(crc_in, !!(resp & (1ULL << i)));
> +
> + /* Read in the crc and check it */
> + serial_in(master, &response, FSI_GPIO_CRC_SIZE);
> + if (crc_in != response.msg) {
> +
> + /* CRC's don't match - warn for now */
> + dev_dbg(master->master.dev, "ERR response CRC\n");
> + fsi_master_gpio_error(master, FSI_GPIO_CRC_INVAL);
> + }
> + /* Clock the slave enough to be ready for next operation */
> + clock_toggle(master, FSI_GPIO_PRIME_SLAVE_CLOCKS);
> + return 0;
> +
> + } while (busy_count++ < FSI_GPIO_MAX_BUSY);
> +
> + dev_dbg(master->master.dev, "ERR slave is stuck in busy state\n");
> + fsi_master_gpio_error(master, FSI_GPIO_ERR_BUSY);
> +
> + return -EIO;
> +}
> +
> +static void build_abs_ar_command(struct fsi_gpio_msg *cmd, uint64_t mode,
> + uint8_t slave, uint32_t addr, size_t size,
> + const void *data)
> +{
> + uint8_t crc;
> + int i;
> +
> + cmd->bits = FSI_GPIO_CMD_DFLT_LEN;
> + cmd->msg = FSI_GPIO_CMD_DEFAULT;
> + cmd->msg |= mode;
> +
> + /* todo: handle more than just slave id 0 */
This should be easily fixed and is needed to get the full 23-bit FSI address
space. For P8 at least I think you just use addr bits 22 & 23 as the slave id.
> + cmd->msg &= ~FSI_GPIO_CMD_SLAVE_MASK;
> + cmd->msg |= (((uint64_t)slave) << FSI_GPIO_CMD_SLV_SHIFT);
> + cmd->msg |= (((uint64_t)addr) << FSI_GPIO_CMD_ADDR_SHIFT);
Wouldn't it be best to apply a mask to addr limiting it to 21-bits? Or a check
to ensure addr <= (1<<23) - 1. Otherwise wont invalid addresses result in
invalid commands and/or errors as a result?
> + if (size == sizeof(uint8_t)) {
> + if (data) {
> + uint8_t cmd_data = *((uint8_t *)data);
> +
> + cmd->msg |=
> + ((uint64_t)cmd_data) <<
FSI_GPIO_CMD_DT8_SHIFT;
> + }
> + } else if (size == sizeof(uint16_t)) {
> + cmd->msg |= FSI_GPIO_CMD_SIZE_16;
> + if (data) {
> + uint16_t cmd_data = *((uint16_t *)data);
It might be best to do a memcpy here or make *data a uint64_t *. There is no
guarantee that *data has the correct alignment which from memory will result
in a data abort fault/exception to fix-up the alignment on ARMv5 and below
(ie. AST2400).
> + cmd->msg |=
> + ((uint64_t)cmd_data) <<
FSI_GPIO_CMD_DT16_SHIFT;
> + }
> + } else {
> + cmd->msg |= FSI_GPIO_CMD_SIZE_32;
> + if (data) {
> + uint32_t cmd_data = *((uint32_t *)data);
Ditto.
> + cmd->msg |=
> + ((uint64_t)cmd_data) <<
FSI_GPIO_CMD_DT32_SHIFT;
> + }
> + }
> +
> + if (mode == FSI_GPIO_CMD_WRITE)
> + cmd->bits += (8 * size);
> +
> + /* Include start bit */
> + crc = crc4(0, 1);
> + for (i = 63; i >= (64 - cmd->bits); i--)
> + crc = crc4(crc, !!(cmd->msg & (1ULL << i)));
> +
> + cmd->msg |= ((uint64_t)crc) << (FSI_GPIO_CMD_CRC_SHIFT - cmd->bits);
> + cmd->bits += FSI_GPIO_CRC_SIZE;
> +
> + /* Right align message */
> + cmd->msg >>= (64 - cmd->bits);
> +}
> +
> +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;
> +
> + if (link != 0)
> + return -ENODEV;
> +
> + build_abs_ar_command(&cmd, FSI_GPIO_CMD_READ, slave, addr, size,
NULL);
> + serial_out(master, &cmd);
> + echo_delay(master);
> +
> + return poll_for_response(master, FSI_GPIO_RESP_ACKD, size, val);
> +}
> +
> +static int fsi_master_gpio_write(struct fsi_master *_master, int link,
> + uint8_t slave, uint32_t addr, const void *val, size_t size)
> +{
> + struct fsi_master_gpio *master = to_fsi_master_gpio(_master);
> + struct fsi_gpio_msg cmd;
> +
> + if (link != 0)
> + return -ENODEV;
> +
> + build_abs_ar_command(&cmd, FSI_GPIO_CMD_WRITE, slave, addr, size,
val);
> + serial_out(master, &cmd);
> + echo_delay(master);
> +
> + return poll_for_response(master, FSI_GPIO_RESP_ACK, size, NULL);
> +}
> +
> +/*
> + * Issue a break command on link
> + */
> +static int fsi_master_gpio_break(struct fsi_master *_master, int link)
> +{
> + struct fsi_master_gpio *master = to_fsi_master_gpio(_master);
> +
> + if (link != 0)
> + return -ENODEV;
> +
> + set_sda_output(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);
> +
> + /* Wait for logic reset to take effect */
> + udelay(200);
> +
> + return 0;
> +}
> +
> +static void fsi_master_gpio_init(struct fsi_master_gpio *master)
> +{
> + if (master->gpio_mux)
> + gpiod_direction_output(master->gpio_mux, 1);
> + if (master->gpio_trans)
> + gpiod_direction_output(master->gpio_trans, 1);
> + if (master->gpio_enable)
> + gpiod_direction_output(master->gpio_enable, 0);
> + gpiod_direction_output(master->gpio_clk, 1);
> + gpiod_direction_output(master->gpio_data, 1);
> +
> + /* 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)
> +{
> + struct fsi_master_gpio *master = to_fsi_master_gpio(_master);
> +
> + if (link != 0)
> + return -ENODEV;
> +
> + if (master->gpio_enable)
> + gpiod_set_value(master->gpio_enable, 1);
> +
> + return 0;
> +}
> +
> +static int fsi_master_gpio_probe(struct platform_device *pdev)
> +{
> + struct fsi_master_gpio *master;
> + struct gpio_desc *gpio;
> +
> + master = devm_kzalloc(&pdev->dev, sizeof(*master), GFP_KERNEL);
> + if (!master)
> + return -ENOMEM;
> +
> + gpio = devm_gpiod_get(&pdev->dev, "clock", 0);
> + if (IS_ERR(gpio))
> + return PTR_ERR(gpio);
> + master->gpio_clk = gpio;
> +
> + gpio = devm_gpiod_get(&pdev->dev, "data", 0);
> + if (IS_ERR(gpio))
> + return PTR_ERR(gpio);
> + master->gpio_data = gpio;
> +
> + /* Optional pins */
> +
> + gpio = devm_gpiod_get(&pdev->dev, "trans", 0);
> + if (!IS_ERR(gpio))
> + master->gpio_trans = gpio;
> +
> + gpio = devm_gpiod_get(&pdev->dev, "enable", 0);
> + if (!IS_ERR(gpio))
> + master->gpio_enable = gpio;
> +
> + gpio = devm_gpiod_get(&pdev->dev, "mux", 0);
> + if (!IS_ERR(gpio))
> + master->gpio_mux = gpio;
> +
> + master->master.n_links = 1;
> + 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);
> +
> + platform_set_drvdata(pdev, master);
> + return fsi_master_register(&master->master);
> +}
> +
> +static int fsi_master_gpio_remove(struct platform_device *pdev)
> +{
> + struct fsi_master_gpio *master = platform_get_drvdata(pdev);
> +
> + devm_gpiod_put(&pdev->dev, master->gpio_clk);
> + devm_gpiod_put(&pdev->dev, master->gpio_data);
> + if (master->gpio_trans)
> + devm_gpiod_put(&pdev->dev, master->gpio_trans);
> + if (master->gpio_enable)
> + devm_gpiod_put(&pdev->dev, master->gpio_enable);
> + if (master->gpio_mux)
> + devm_gpiod_put(&pdev->dev, master->gpio_mux);
> +
> + fsi_master_unregister(&master->master);
> +
> + return 0;
> +}
> +
> +static const struct of_device_id fsi_master_gpio_match[] = {
> + { .compatible = "ibm,fsi-master-gpio" },
> + { },
> +};
> +
> +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_LICENSE("GPL");
>
More information about the openbmc
mailing list