summaryrefslogtreecommitdiffstats
path: root/drivers/staging/sm750fb/ddk750_swi2c.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/staging/sm750fb/ddk750_swi2c.c')
-rw-r--r--drivers/staging/sm750fb/ddk750_swi2c.c504
1 files changed, 504 insertions, 0 deletions
diff --git a/drivers/staging/sm750fb/ddk750_swi2c.c b/drivers/staging/sm750fb/ddk750_swi2c.c
new file mode 100644
index 000000000..0ef8d4ff2
--- /dev/null
+++ b/drivers/staging/sm750fb/ddk750_swi2c.c
@@ -0,0 +1,504 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2007 by Silicon Motion, Inc. (SMI)
+ *
+ * swi2c.c --- SM750/SM718 DDK
+ * This file contains the source code for I2C using software
+ * implementation.
+ */
+
+#include "ddk750_chip.h"
+#include "ddk750_reg.h"
+#include "ddk750_swi2c.h"
+#include "ddk750_power.h"
+
+/*
+ * I2C Software Master Driver:
+ * ===========================
+ * Each i2c cycle is split into 4 sections. Each of these section marks
+ * a point in time where the SCL or SDA may be changed.
+ *
+ * 1 Cycle == | Section I. | Section 2. | Section 3. | Section 4. |
+ * +-------------+-------------+-------------+-------------+
+ * | SCL set LOW |SCL no change| SCL set HIGH|SCL no change|
+ *
+ * ____________ _____________
+ * SCL == XXXX _____________ ____________ /
+ *
+ * I.e. the SCL may only be changed in section 1. and section 3. while
+ * the SDA may only be changed in section 2. and section 4. The table
+ * below gives the changes for these 2 lines in the varios sections.
+ *
+ * Section changes Table:
+ * ======================
+ * blank = no change, L = set bit LOW, H = set bit HIGH
+ *
+ * | 1.| 2.| 3.| 4.|
+ * ---------------+---+---+---+---+
+ * Tx Start SDA | | H | | L |
+ * SCL | L | | H | |
+ * ---------------+---+---+---+---+
+ * Tx Stop SDA | | L | | H |
+ * SCL | L | | H | |
+ * ---------------+---+---+---+---+
+ * Tx bit H SDA | | H | | |
+ * SCL | L | | H | |
+ * ---------------+---+---+---+---+
+ * Tx bit L SDA | | L | | |
+ * SCL | L | | H | |
+ * ---------------+---+---+---+---+
+ *
+ */
+
+/* GPIO pins used for this I2C. It ranges from 0 to 63. */
+static unsigned char sw_i2c_clk_gpio = DEFAULT_I2C_SCL;
+static unsigned char sw_i2c_data_gpio = DEFAULT_I2C_SDA;
+
+/*
+ * Below is the variable declaration for the GPIO pin register usage
+ * for the i2c Clock and i2c Data.
+ *
+ * Note:
+ * Notice that the GPIO usage for the i2c clock and i2c Data are
+ * separated. This is to make this code flexible enough when
+ * two separate GPIO pins for the clock and data are located
+ * in two different GPIO register set (worst case).
+ */
+
+/* i2c Clock GPIO Register usage */
+static unsigned long sw_i2c_clk_gpio_mux_reg = GPIO_MUX;
+static unsigned long sw_i2c_clk_gpio_data_reg = GPIO_DATA;
+static unsigned long sw_i2c_clk_gpio_data_dir_reg = GPIO_DATA_DIRECTION;
+
+/* i2c Data GPIO Register usage */
+static unsigned long sw_i2c_data_gpio_mux_reg = GPIO_MUX;
+static unsigned long sw_i2c_data_gpio_data_reg = GPIO_DATA;
+static unsigned long sw_i2c_data_gpio_data_dir_reg = GPIO_DATA_DIRECTION;
+
+/*
+ * This function puts a delay between command
+ */
+static void sw_i2c_wait(void)
+{
+ /* find a bug:
+ * peekIO method works well before suspend/resume
+ * but after suspend, peekIO(0x3ce,0x61) & 0x10
+ * always be non-zero,which makes the while loop
+ * never finish.
+ * use non-ultimate for loop below is safe
+ */
+
+ /* Change wait algorithm to use PCI bus clock,
+ * it's more reliable than counter loop ..
+ * write 0x61 to 0x3ce and read from 0x3cf
+ */
+ int i, tmp;
+
+ for (i = 0; i < 600; i++) {
+ tmp = i;
+ tmp += i;
+ }
+}
+
+/*
+ * This function set/reset the SCL GPIO pin
+ *
+ * Parameters:
+ * value - Bit value to set to the SCL or SDA (0 = low, 1 = high)
+ *
+ * Notes:
+ * When setting SCL to high, just set the GPIO as input where the pull up
+ * resistor will pull the signal up. Do not use software to pull up the
+ * signal because the i2c will fail when other device try to drive the
+ * signal due to SM50x will drive the signal to always high.
+ */
+static void sw_i2c_scl(unsigned char value)
+{
+ unsigned long gpio_data;
+ unsigned long gpio_dir;
+
+ gpio_dir = peek32(sw_i2c_clk_gpio_data_dir_reg);
+ if (value) { /* High */
+ /*
+ * Set direction as input. This will automatically
+ * pull the signal up.
+ */
+ gpio_dir &= ~(1 << sw_i2c_clk_gpio);
+ poke32(sw_i2c_clk_gpio_data_dir_reg, gpio_dir);
+ } else { /* Low */
+ /* Set the signal down */
+ gpio_data = peek32(sw_i2c_clk_gpio_data_reg);
+ gpio_data &= ~(1 << sw_i2c_clk_gpio);
+ poke32(sw_i2c_clk_gpio_data_reg, gpio_data);
+
+ /* Set direction as output */
+ gpio_dir |= (1 << sw_i2c_clk_gpio);
+ poke32(sw_i2c_clk_gpio_data_dir_reg, gpio_dir);
+ }
+}
+
+/*
+ * This function set/reset the SDA GPIO pin
+ *
+ * Parameters:
+ * value - Bit value to set to the SCL or SDA (0 = low, 1 = high)
+ *
+ * Notes:
+ * When setting SCL to high, just set the GPIO as input where the pull up
+ * resistor will pull the signal up. Do not use software to pull up the
+ * signal because the i2c will fail when other device try to drive the
+ * signal due to SM50x will drive the signal to always high.
+ */
+static void sw_i2c_sda(unsigned char value)
+{
+ unsigned long gpio_data;
+ unsigned long gpio_dir;
+
+ gpio_dir = peek32(sw_i2c_data_gpio_data_dir_reg);
+ if (value) { /* High */
+ /*
+ * Set direction as input. This will automatically
+ * pull the signal up.
+ */
+ gpio_dir &= ~(1 << sw_i2c_data_gpio);
+ poke32(sw_i2c_data_gpio_data_dir_reg, gpio_dir);
+ } else { /* Low */
+ /* Set the signal down */
+ gpio_data = peek32(sw_i2c_data_gpio_data_reg);
+ gpio_data &= ~(1 << sw_i2c_data_gpio);
+ poke32(sw_i2c_data_gpio_data_reg, gpio_data);
+
+ /* Set direction as output */
+ gpio_dir |= (1 << sw_i2c_data_gpio);
+ poke32(sw_i2c_data_gpio_data_dir_reg, gpio_dir);
+ }
+}
+
+/*
+ * This function read the data from the SDA GPIO pin
+ *
+ * Return Value:
+ * The SDA data bit sent by the Slave
+ */
+static unsigned char sw_i2c_read_sda(void)
+{
+ unsigned long gpio_dir;
+ unsigned long gpio_data;
+ unsigned long dir_mask = 1 << sw_i2c_data_gpio;
+
+ /* Make sure that the direction is input (High) */
+ gpio_dir = peek32(sw_i2c_data_gpio_data_dir_reg);
+ if ((gpio_dir & dir_mask) != ~dir_mask) {
+ gpio_dir &= ~(1 << sw_i2c_data_gpio);
+ poke32(sw_i2c_data_gpio_data_dir_reg, gpio_dir);
+ }
+
+ /* Now read the SDA line */
+ gpio_data = peek32(sw_i2c_data_gpio_data_reg);
+ if (gpio_data & (1 << sw_i2c_data_gpio))
+ return 1;
+ else
+ return 0;
+}
+
+/*
+ * This function sends ACK signal
+ */
+static void sw_i2c_ack(void)
+{
+ return; /* Single byte read is ok without it. */
+}
+
+/*
+ * This function sends the start command to the slave device
+ */
+static void sw_i2c_start(void)
+{
+ /* Start I2C */
+ sw_i2c_sda(1);
+ sw_i2c_scl(1);
+ sw_i2c_sda(0);
+}
+
+/*
+ * This function sends the stop command to the slave device
+ */
+static void sw_i2c_stop(void)
+{
+ /* Stop the I2C */
+ sw_i2c_scl(1);
+ sw_i2c_sda(0);
+ sw_i2c_sda(1);
+}
+
+/*
+ * This function writes one byte to the slave device
+ *
+ * Parameters:
+ * data - Data to be write to the slave device
+ *
+ * Return Value:
+ * 0 - Success
+ * -1 - Fail to write byte
+ */
+static long sw_i2c_write_byte(unsigned char data)
+{
+ unsigned char value = data;
+ int i;
+
+ /* Sending the data bit by bit */
+ for (i = 0; i < 8; i++) {
+ /* Set SCL to low */
+ sw_i2c_scl(0);
+
+ /* Send data bit */
+ if ((value & 0x80) != 0)
+ sw_i2c_sda(1);
+ else
+ sw_i2c_sda(0);
+
+ sw_i2c_wait();
+
+ /* Toggle clk line to one */
+ sw_i2c_scl(1);
+ sw_i2c_wait();
+
+ /* Shift byte to be sent */
+ value = value << 1;
+ }
+
+ /* Set the SCL Low and SDA High (prepare to get input) */
+ sw_i2c_scl(0);
+ sw_i2c_sda(1);
+
+ /* Set the SCL High for ack */
+ sw_i2c_wait();
+ sw_i2c_scl(1);
+ sw_i2c_wait();
+
+ /* Read SDA, until SDA==0 */
+ for (i = 0; i < 0xff; i++) {
+ if (!sw_i2c_read_sda())
+ break;
+
+ sw_i2c_scl(0);
+ sw_i2c_wait();
+ sw_i2c_scl(1);
+ sw_i2c_wait();
+ }
+
+ /* Set the SCL Low and SDA High */
+ sw_i2c_scl(0);
+ sw_i2c_sda(1);
+
+ if (i < 0xff)
+ return 0;
+ else
+ return -1;
+}
+
+/*
+ * This function reads one byte from the slave device
+ *
+ * Parameters:
+ * ack - Flag to indicate either to send the acknowledge
+ * message to the slave device or not
+ *
+ * Return Value:
+ * One byte data read from the Slave device
+ */
+static unsigned char sw_i2c_read_byte(unsigned char ack)
+{
+ int i;
+ unsigned char data = 0;
+
+ for (i = 7; i >= 0; i--) {
+ /* Set the SCL to Low and SDA to High (Input) */
+ sw_i2c_scl(0);
+ sw_i2c_sda(1);
+ sw_i2c_wait();
+
+ /* Set the SCL High */
+ sw_i2c_scl(1);
+ sw_i2c_wait();
+
+ /* Read data bits from SDA */
+ data |= (sw_i2c_read_sda() << i);
+ }
+
+ if (ack)
+ sw_i2c_ack();
+
+ /* Set the SCL Low and SDA High */
+ sw_i2c_scl(0);
+ sw_i2c_sda(1);
+
+ return data;
+}
+
+/*
+ * This function initializes GPIO port for SW I2C communication.
+ *
+ * Parameters:
+ * clk_gpio - The GPIO pin to be used as i2c SCL
+ * data_gpio - The GPIO pin to be used as i2c SDA
+ *
+ * Return Value:
+ * -1 - Fail to initialize the i2c
+ * 0 - Success
+ */
+static long sm750le_i2c_init(unsigned char clk_gpio, unsigned char data_gpio)
+{
+ int i;
+
+ /* Initialize the GPIO pin for the i2c Clock Register */
+ sw_i2c_clk_gpio_data_reg = GPIO_DATA_SM750LE;
+ sw_i2c_clk_gpio_data_dir_reg = GPIO_DATA_DIRECTION_SM750LE;
+
+ /* Initialize the Clock GPIO Offset */
+ sw_i2c_clk_gpio = clk_gpio;
+
+ /* Initialize the GPIO pin for the i2c Data Register */
+ sw_i2c_data_gpio_data_reg = GPIO_DATA_SM750LE;
+ sw_i2c_data_gpio_data_dir_reg = GPIO_DATA_DIRECTION_SM750LE;
+
+ /* Initialize the Data GPIO Offset */
+ sw_i2c_data_gpio = data_gpio;
+
+ /* Note that SM750LE don't have GPIO MUX and power is always on */
+
+ /* Clear the i2c lines. */
+ for (i = 0; i < 9; i++)
+ sw_i2c_stop();
+
+ return 0;
+}
+
+/*
+ * This function initializes the i2c attributes and bus
+ *
+ * Parameters:
+ * clk_gpio - The GPIO pin to be used as i2c SCL
+ * data_gpio - The GPIO pin to be used as i2c SDA
+ *
+ * Return Value:
+ * -1 - Fail to initialize the i2c
+ * 0 - Success
+ */
+long sm750_sw_i2c_init(unsigned char clk_gpio, unsigned char data_gpio)
+{
+ int i;
+
+ /*
+ * Return 0 if the GPIO pins to be used is out of range. The
+ * range is only from [0..63]
+ */
+ if ((clk_gpio > 31) || (data_gpio > 31))
+ return -1;
+
+ if (sm750_get_chip_type() == SM750LE)
+ return sm750le_i2c_init(clk_gpio, data_gpio);
+
+ /* Initialize the GPIO pin for the i2c Clock Register */
+ sw_i2c_clk_gpio_mux_reg = GPIO_MUX;
+ sw_i2c_clk_gpio_data_reg = GPIO_DATA;
+ sw_i2c_clk_gpio_data_dir_reg = GPIO_DATA_DIRECTION;
+
+ /* Initialize the Clock GPIO Offset */
+ sw_i2c_clk_gpio = clk_gpio;
+
+ /* Initialize the GPIO pin for the i2c Data Register */
+ sw_i2c_data_gpio_mux_reg = GPIO_MUX;
+ sw_i2c_data_gpio_data_reg = GPIO_DATA;
+ sw_i2c_data_gpio_data_dir_reg = GPIO_DATA_DIRECTION;
+
+ /* Initialize the Data GPIO Offset */
+ sw_i2c_data_gpio = data_gpio;
+
+ /* Enable the GPIO pins for the i2c Clock and Data (GPIO MUX) */
+ poke32(sw_i2c_clk_gpio_mux_reg,
+ peek32(sw_i2c_clk_gpio_mux_reg) & ~(1 << sw_i2c_clk_gpio));
+ poke32(sw_i2c_data_gpio_mux_reg,
+ peek32(sw_i2c_data_gpio_mux_reg) & ~(1 << sw_i2c_data_gpio));
+
+ /* Enable GPIO power */
+ sm750_enable_gpio(1);
+
+ /* Clear the i2c lines. */
+ for (i = 0; i < 9; i++)
+ sw_i2c_stop();
+
+ return 0;
+}
+
+/*
+ * This function reads the slave device's register
+ *
+ * Parameters:
+ * addr - i2c Slave device address which register
+ * to be read from
+ * reg - Slave device's register to be read
+ *
+ * Return Value:
+ * Register value
+ */
+unsigned char sm750_sw_i2c_read_reg(unsigned char addr, unsigned char reg)
+{
+ unsigned char data;
+
+ /* Send the Start signal */
+ sw_i2c_start();
+
+ /* Send the device address */
+ sw_i2c_write_byte(addr);
+
+ /* Send the register index */
+ sw_i2c_write_byte(reg);
+
+ /* Get the bus again and get the data from the device read address */
+ sw_i2c_start();
+ sw_i2c_write_byte(addr + 1);
+ data = sw_i2c_read_byte(1);
+
+ /* Stop swI2C and release the bus */
+ sw_i2c_stop();
+
+ return data;
+}
+
+/*
+ * This function writes a value to the slave device's register
+ *
+ * Parameters:
+ * addr - i2c Slave device address which register
+ * to be written
+ * reg - Slave device's register to be written
+ * data - Data to be written to the register
+ *
+ * Result:
+ * 0 - Success
+ * -1 - Fail
+ */
+long sm750_sw_i2c_write_reg(unsigned char addr,
+ unsigned char reg,
+ unsigned char data)
+{
+ long ret = 0;
+
+ /* Send the Start signal */
+ sw_i2c_start();
+
+ /* Send the device address and read the data. All should return success
+ * in order for the writing processed to be successful
+ */
+ if ((sw_i2c_write_byte(addr) != 0) ||
+ (sw_i2c_write_byte(reg) != 0) ||
+ (sw_i2c_write_byte(data) != 0)) {
+ ret = -1;
+ }
+
+ /* Stop i2c and release the bus */
+ sw_i2c_stop();
+
+ return ret;
+}