blob: 19c5ffc72b16e1a5ddd610bf0e1e0c0dfb404375 [file] [log] [blame]
/*
* Copyright (c) 2007 by Silicon Motion, Inc. (SMI)
*
* All rights are reserved. Reproduction or in part is prohibited
* without the written consent of the copyright owner.
*
* 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;
}