mirror of
https://github.com/zephyrproject-rtos/zephyr
synced 2025-09-06 04:51:56 +00:00
Add a semaphore. Each task or a fiber that initiates a transmission waits on this semaphore for it to complete. Change-Id: Ia6cf961397614ead252ebea3949c016056289311 Signed-off-by: Dmitriy Korovkin <dmitriy.korovkin@windriver.com> Signed-off-by: Tomasz Bursztyka <tomasz.bursztyka@linux.intel.com>
920 lines
22 KiB
C
920 lines
22 KiB
C
/* dw_i2c.c - I2C file for Design Ware */
|
|
|
|
/*
|
|
* Copyright (c) 2015 Intel Corporation
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <stdbool.h>
|
|
|
|
#include <i2c.h>
|
|
#include <nanokernel.h>
|
|
#include <init.h>
|
|
#include <arch/cpu.h>
|
|
#include <string.h>
|
|
|
|
#include <board.h>
|
|
#include <errno.h>
|
|
#include <sys_io.h>
|
|
|
|
#ifdef CONFIG_SHARED_IRQ
|
|
#include <shared_irq.h>
|
|
#endif
|
|
|
|
#include "i2c_dw.h"
|
|
#include "i2c_dw_registers.h"
|
|
|
|
#ifndef CONFIG_I2C_DEBUG
|
|
#define DBG(...) { ; }
|
|
#else
|
|
#if defined(CONFIG_STDOUT_CONSOLE)
|
|
#include <stdio.h>
|
|
#define DBG printf
|
|
#else
|
|
#define DBG printk
|
|
#endif /* CONFIG_STDOUT_CONSOLE */
|
|
#endif /* CONFIG_I2C_DEBUG */
|
|
|
|
static inline uint32_t i2c_dw_memory_read(uint32_t base_addr, uint32_t offset)
|
|
{
|
|
return sys_read32(base_addr + offset);
|
|
}
|
|
|
|
|
|
static inline void i2c_dw_memory_write(uint32_t base_addr, uint32_t offset,
|
|
uint32_t val)
|
|
{
|
|
sys_write32(val, base_addr + offset);
|
|
}
|
|
|
|
|
|
static inline void _i2c_dw_data_ask(struct device *dev, uint8_t restart)
|
|
{
|
|
struct i2c_dw_rom_config const * const rom = dev->config->config_info;
|
|
struct i2c_dw_dev_config * const dw = dev->driver_data;
|
|
uint32_t data;
|
|
|
|
volatile struct i2c_dw_registers * const regs =
|
|
(struct i2c_dw_registers *)rom->base_address;
|
|
|
|
/* No more bytes to request */
|
|
if (dw->request_bytes == 0) {
|
|
return;
|
|
}
|
|
|
|
/* Tell controller to get another byte */
|
|
data = IC_DATA_CMD_CMD;
|
|
|
|
/* Send restart if needed) */
|
|
if (restart) {
|
|
data |= IC_DATA_CMD_RESTART;
|
|
}
|
|
|
|
/* After receiving the last byte, send STOP */
|
|
if (dw->request_bytes == 1) {
|
|
data |= IC_DATA_CMD_STOP;
|
|
}
|
|
|
|
regs->ic_data_cmd.raw = data;
|
|
|
|
dw->request_bytes--;
|
|
}
|
|
|
|
static void _i2c_dw_data_read(struct device *dev)
|
|
{
|
|
struct i2c_dw_rom_config const * const rom = dev->config->config_info;
|
|
struct i2c_dw_dev_config * const dw = dev->driver_data;
|
|
|
|
volatile struct i2c_dw_registers * const regs =
|
|
(struct i2c_dw_registers *)rom->base_address;
|
|
|
|
while (regs->ic_status.bits.rfne && (dw->rx_len > 0)) {
|
|
dw->rx_buffer[0] = regs->ic_data_cmd.raw;
|
|
|
|
dw->rx_buffer += 1;
|
|
dw->rx_len -= 1;
|
|
|
|
if (dw->rx_len == 0) {
|
|
break;
|
|
}
|
|
|
|
_i2c_dw_data_ask(dev, 0);
|
|
}
|
|
|
|
/* Nothing to receive anymore */
|
|
if (dw->rx_len == 0) {
|
|
dw->state &= ~I2C_DW_CMD_RECV;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
static void _i2c_dw_data_send(struct device *dev)
|
|
{
|
|
struct i2c_dw_rom_config const * const rom = dev->config->config_info;
|
|
struct i2c_dw_dev_config * const dw = dev->driver_data;
|
|
uint32_t data = 0;
|
|
|
|
volatile struct i2c_dw_registers * const regs =
|
|
(struct i2c_dw_registers *)rom->base_address;
|
|
|
|
/* Nothing to send anymore, mask the interrupt */
|
|
if (dw->tx_len == 0) {
|
|
regs->ic_intr_mask.bits.tx_empty = 0;
|
|
|
|
if (dw->rx_len > 0) {
|
|
/* Tell controller to grab a byte.
|
|
* RESTART if something has ben sent.
|
|
*/
|
|
_i2c_dw_data_ask(dev, (dw->state & I2C_DW_CMD_SEND));
|
|
|
|
/* QUIRK:
|
|
* If requesting more than one byte, the process has
|
|
* to be jump-started by requesting two bytes first.
|
|
*/
|
|
_i2c_dw_data_ask(dev, 0);
|
|
}
|
|
|
|
dw->state &= ~I2C_DW_CMD_SEND;
|
|
|
|
return;
|
|
}
|
|
|
|
while (regs->ic_status.bits.tfnf && (dw->tx_len > 0)) {
|
|
/* We have something to transmit to a specific host */
|
|
data = dw->tx_buffer[0];
|
|
|
|
/* If this is the last byte to write
|
|
* and nothing to receive, send STOP.
|
|
*/
|
|
if ((dw->tx_len == 1) && (dw->rx_len == 0)) {
|
|
data |= IC_DATA_CMD_STOP;
|
|
}
|
|
|
|
regs->ic_data_cmd.raw = data;
|
|
dw->tx_len -= 1;
|
|
dw->tx_buffer += 1;
|
|
}
|
|
}
|
|
|
|
static inline void _i2c_dw_transfer_complete(struct device *dev)
|
|
{
|
|
struct i2c_dw_rom_config const * const rom = dev->config->config_info;
|
|
struct i2c_dw_dev_config * const dw = dev->driver_data;
|
|
uint32_t cb_type = 0;
|
|
uint32_t value;
|
|
|
|
volatile struct i2c_dw_registers * const regs =
|
|
(struct i2c_dw_registers *)rom->base_address;
|
|
|
|
if (dw->state == I2C_DW_CMD_ERROR) {
|
|
cb_type = I2C_CB_ERROR;
|
|
} else if (dw->tx_buffer && !dw->tx_len) {
|
|
cb_type = I2C_CB_WRITE;
|
|
} else if (dw->rx_buffer && !dw->rx_len) {
|
|
cb_type = I2C_CB_READ;
|
|
}
|
|
|
|
if (cb_type) {
|
|
regs->ic_intr_mask.raw = DW_DISABLE_ALL_I2C_INT;
|
|
dw->state = I2C_DW_STATE_READY;
|
|
value = regs->ic_clr_intr;
|
|
|
|
synchronous_call_complete(&dw->sync);
|
|
if (dw->cb) {
|
|
dw->cb(dev, cb_type);
|
|
}
|
|
}
|
|
|
|
dw->state &= ~I2C_DW_BUSY;
|
|
}
|
|
|
|
void i2c_dw_isr(struct device *port)
|
|
{
|
|
struct i2c_dw_rom_config const * const rom = port->config->config_info;
|
|
struct i2c_dw_dev_config * const dw = port->driver_data;
|
|
uint32_t value = 0;
|
|
|
|
volatile struct i2c_dw_registers * const regs =
|
|
(struct i2c_dw_registers *)rom->base_address;
|
|
|
|
#if CONFIG_SHARED_IRQ
|
|
/* If using with shared IRQ, this function will be called
|
|
* by the shared IRQ driver. So check here if the interrupt
|
|
* is coming from the I2C controller (or somewhere else).
|
|
*/
|
|
if (!regs->ic_intr_stat.raw) {
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Causes of an interrupt:
|
|
* - STOP condition is detected
|
|
* - Transfer is aborted
|
|
* - Transmit FIFO is empy
|
|
* - Transmit FIFO is overflowing
|
|
* - Receive FIFO is full
|
|
* - Receive FIFO overflow
|
|
* - Received FIFO underrun
|
|
* - Transmit data required (tx_req)
|
|
* - Receive data available (rx_avail)
|
|
*/
|
|
|
|
DBG("I2C: interrupt received\n");
|
|
|
|
/*
|
|
* We got a STOP_DET, this means stop right after this byte has been
|
|
* handled.
|
|
*/
|
|
if (regs->ic_intr_stat.bits.stop_det) {
|
|
value = regs->ic_clr_stop_det;
|
|
_i2c_dw_transfer_complete(port);
|
|
}
|
|
|
|
/* Check if we are configured as a master device */
|
|
if (regs->ic_con.bits.master_mode) {
|
|
/* Check if the Master TX is ready for sending */
|
|
if (regs->ic_intr_stat.bits.tx_empty) {
|
|
_i2c_dw_data_send(port);
|
|
}
|
|
|
|
/* Check if the RX FIFO reached threshold */
|
|
if (regs->ic_intr_stat.bits.rx_full) {
|
|
_i2c_dw_data_read(port);
|
|
}
|
|
|
|
if ((DW_INTR_STAT_TX_ABRT | DW_INTR_STAT_TX_OVER |
|
|
DW_INTR_STAT_RX_OVER | DW_INTR_STAT_RX_UNDER) &
|
|
regs->ic_intr_stat.raw) {
|
|
dw->state = I2C_DW_CMD_ERROR;
|
|
_i2c_dw_transfer_complete(port);
|
|
}
|
|
} else { /* we must be configured as a slave device */
|
|
|
|
/* We have a read requested by the master device */
|
|
if (regs->ic_intr_stat.bits.rd_req &&
|
|
(!dw->app_config.bits.is_slave_read)) {
|
|
|
|
/* data is not ready to send */
|
|
if (regs->ic_intr_stat.bits.tx_abrt) {
|
|
/* clear the TX_ABRT interrupt */
|
|
value = regs->ic_clr_tx_abrt;
|
|
}
|
|
|
|
_i2c_dw_data_send(port);
|
|
value = regs->ic_clr_rd_req;
|
|
}
|
|
|
|
/* The slave device is ready to receive */
|
|
if (regs->ic_intr_stat.bits.rx_full &&
|
|
dw->app_config.bits.is_slave_read) {
|
|
_i2c_dw_data_read(port);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static int _i2c_dw_setup(struct device *dev)
|
|
{
|
|
struct i2c_dw_dev_config * const dw = dev->driver_data;
|
|
struct i2c_dw_rom_config const * const rom = dev->config->config_info;
|
|
uint32_t value = 0;
|
|
union ic_con_register ic_con;
|
|
int rc = DEV_OK;
|
|
volatile struct i2c_dw_registers * const regs =
|
|
(struct i2c_dw_registers *)rom->base_address;
|
|
|
|
ic_con.raw = 0;
|
|
|
|
/*
|
|
* Clear any interrupts currently waiting in the controller
|
|
* this is done by reading register 0x40
|
|
*/
|
|
value = regs->ic_clr_intr;
|
|
|
|
/* Set master or slave mode - (initialization = slave) */
|
|
if (dw->app_config.bits.is_master_device) {
|
|
/*
|
|
* Make sure to set both the master_mode and slave_disable_bit
|
|
* to both 0 or both 1
|
|
*/
|
|
DBG("I2C: host configured as Master Device\n");
|
|
ic_con.bits.master_mode = 1;
|
|
ic_con.bits.slave_disable = 1;
|
|
}
|
|
|
|
ic_con.bits.restart_en = 1;
|
|
|
|
/* Set addressing mode - (initialization = 7 bit) */
|
|
if (dw->app_config.bits.use_10_bit_addr) {
|
|
DBG("I2C: using 10-bit address\n");
|
|
ic_con.bits.addr_master_10bit = 1;
|
|
ic_con.bits.addr_slave_10bit = 1;
|
|
}
|
|
|
|
/* Setup the clock frequency and speed mode */
|
|
switch (dw->app_config.bits.speed) {
|
|
case I2C_SPEED_STANDARD:
|
|
DBG("I2C: speed set to STANDARD\n");
|
|
regs->ic_ss_scl_lcnt = dw->lcnt;
|
|
regs->ic_ss_scl_hcnt = dw->hcnt;
|
|
ic_con.bits.speed = I2C_DW_SPEED_STANDARD;
|
|
|
|
break;
|
|
case I2C_SPEED_FAST:
|
|
/* fall through */
|
|
case I2C_SPEED_FAST_PLUS:
|
|
DBG("I2C: speed set to FAST or FAST_PLUS\n");
|
|
regs->ic_fs_scl_lcnt = dw->lcnt;
|
|
regs->ic_fs_scl_hcnt = dw->hcnt;
|
|
ic_con.bits.speed = I2C_DW_SPEED_FAST;
|
|
|
|
break;
|
|
case I2C_SPEED_HIGH:
|
|
if (!dw->support_hs_mode) {
|
|
rc = DEV_INVALID_CONF;
|
|
break;
|
|
}
|
|
|
|
DBG("I2C: speed set to HIGH\n");
|
|
regs->ic_hs_scl_lcnt = dw->lcnt;
|
|
regs->ic_hs_scl_hcnt = dw->hcnt;
|
|
ic_con.bits.speed = I2C_DW_SPEED_HIGH;
|
|
|
|
break;
|
|
default:
|
|
DBG("I2C: invalid speed requested\n");
|
|
/* TODO change */
|
|
rc = DEV_INVALID_CONF;
|
|
}
|
|
|
|
DBG("I2C: lcnt = %d\n", dw->lcnt);
|
|
DBG("I2C: hcnt = %d\n", dw->hcnt);
|
|
|
|
/* Set the IC_CON register */
|
|
regs->ic_con = ic_con;
|
|
/* END of setup IC_CON */
|
|
|
|
/* Set RX fifo threshold level.
|
|
* Setting it to zero automatically triggers interrupt
|
|
* RX_FULL whenever there is data received.
|
|
*
|
|
* TODO: extend the threshold for multi-byte RX.
|
|
*/
|
|
regs->ic_rx_tl = 0;
|
|
|
|
/* Set TX fifo threshold level.
|
|
* TX_EMPTY interrupt is triggered only when the
|
|
* TX FIFO is truly empty.
|
|
*
|
|
* TODO: threshold set to just enough for TX
|
|
*/
|
|
regs->ic_tx_tl = 0;
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
static int _i2c_dw_transfer_init(struct device *dev,
|
|
uint8_t *write_buf, uint32_t write_len,
|
|
uint8_t *read_buf, uint32_t read_len,
|
|
uint16_t slave_address)
|
|
{
|
|
struct i2c_dw_rom_config const * const rom = dev->config->config_info;
|
|
struct i2c_dw_dev_config * const dw = dev->driver_data;
|
|
uint32_t value = 0;
|
|
int ret;
|
|
|
|
volatile struct i2c_dw_registers * const regs =
|
|
(struct i2c_dw_registers *)rom->base_address;
|
|
|
|
dw->state |= I2C_DW_BUSY;
|
|
if (write_len > 0) {
|
|
dw->state |= I2C_DW_CMD_SEND;
|
|
}
|
|
if (read_len > 0) {
|
|
dw->state |= I2C_DW_CMD_RECV;
|
|
}
|
|
|
|
dw->rx_len = read_len;
|
|
dw->rx_buffer = read_buf;
|
|
dw->tx_len = write_len;
|
|
dw->tx_buffer = write_buf;
|
|
dw->request_bytes = read_len;
|
|
|
|
/* Disable the device controller to be able set TAR */
|
|
regs->ic_enable.bits.enable = 0;
|
|
|
|
ret = _i2c_dw_setup(dev);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
/* Disable interrupts */
|
|
regs->ic_intr_mask.raw = 0;
|
|
|
|
/* Clear interrupts */
|
|
value = regs->ic_clr_intr;
|
|
|
|
if (regs->ic_con.bits.master_mode) {
|
|
/* Set address of target slave */
|
|
regs->ic_tar.bits.ic_tar = slave_address;
|
|
} else {
|
|
/* Set slave address for device */
|
|
regs->ic_sar.bits.ic_sar = slave_address;
|
|
}
|
|
|
|
return DEV_OK;
|
|
}
|
|
|
|
static int i2c_dw_transfer(struct device *dev,
|
|
uint8_t *write_buf, uint32_t write_len,
|
|
uint8_t *read_buf, uint32_t read_len,
|
|
uint16_t slave_address, uint32_t flags)
|
|
{
|
|
struct i2c_dw_rom_config const * const rom = dev->config->config_info;
|
|
struct i2c_dw_dev_config * const dw = dev->driver_data;
|
|
int ret;
|
|
|
|
volatile struct i2c_dw_registers * const regs =
|
|
(struct i2c_dw_registers *)rom->base_address;
|
|
|
|
/* First step, check if there is current activity */
|
|
if (regs->ic_status.bits.activity) {
|
|
return DEV_FAIL;
|
|
}
|
|
|
|
ret = _i2c_dw_transfer_init(dev, write_buf, write_len,
|
|
read_buf, read_len, slave_address);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
/* Trigger IRQ when TX_EMPTY */
|
|
regs->ic_con.bits.tx_empty_ctl = 1;
|
|
|
|
if (regs->ic_con.bits.master_mode) {
|
|
/* Enable necessary interrupts */
|
|
regs->ic_intr_mask.raw = (DW_ENABLE_TX_INT_I2C_MASTER |
|
|
DW_ENABLE_RX_INT_I2C_MASTER);
|
|
} else {
|
|
/* Enable necessary interrupts */
|
|
regs->ic_intr_mask.raw = DW_ENABLE_TX_INT_I2C_SLAVE;
|
|
}
|
|
|
|
/* Enable controller */
|
|
regs->ic_enable.bits.enable = 1;
|
|
|
|
synchronous_call_wait(&dw->sync);
|
|
|
|
return DEV_OK;
|
|
}
|
|
|
|
#define POLLING_TIMEOUT (sys_clock_ticks_per_sec / 10)
|
|
static int i2c_dw_poll_transfer(struct device *dev,
|
|
uint8_t *write_buf, uint32_t write_len,
|
|
uint8_t *read_buf, uint32_t read_len,
|
|
uint16_t slave_address, uint32_t flags)
|
|
{
|
|
struct i2c_dw_rom_config const * const rom = dev->config->config_info;
|
|
struct i2c_dw_dev_config * const dw = dev->driver_data;
|
|
uint32_t value = 0;
|
|
uint32_t start_time;
|
|
int ret = DEV_OK;
|
|
|
|
volatile struct i2c_dw_registers * const regs =
|
|
(struct i2c_dw_registers *)rom->base_address;
|
|
|
|
if (!regs->ic_con.bits.master_mode) {
|
|
/* Only acting as master is supported */
|
|
return DEV_INVALID_OP;
|
|
}
|
|
|
|
/* Wait for bus idle */
|
|
start_time = sys_tick_get_32();
|
|
while (regs->ic_status.bits.activity) {
|
|
if ((sys_tick_get_32() - start_time) > POLLING_TIMEOUT) {
|
|
return DEV_FAIL;
|
|
}
|
|
}
|
|
|
|
ret = _i2c_dw_transfer_init(dev, write_buf, write_len,
|
|
read_buf, read_len, slave_address);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
/* Enable controller */
|
|
regs->ic_enable.bits.enable = 1;
|
|
|
|
if (dw->tx_len == 0) {
|
|
goto do_receive;
|
|
}
|
|
|
|
/* Transmit */
|
|
while (dw->tx_len > 0) {
|
|
/* Wait for space in TX FIFO */
|
|
start_time = sys_tick_get_32();
|
|
while (!regs->ic_status.bits.tfnf) {
|
|
if ((sys_tick_get_32() - start_time) > POLLING_TIMEOUT) {
|
|
ret = DEV_FAIL;
|
|
goto finish;
|
|
}
|
|
}
|
|
|
|
_i2c_dw_data_send(dev);
|
|
}
|
|
|
|
/* Wait for TX FIFO empty to be sure everything is sent. */
|
|
start_time = sys_tick_get_32();
|
|
while (!regs->ic_status.bits.tfe) {
|
|
if ((sys_tick_get_32() - start_time) > POLLING_TIMEOUT) {
|
|
ret = DEV_FAIL;
|
|
goto finish;
|
|
}
|
|
}
|
|
|
|
do_receive:
|
|
/* Finalize TX when there is nothing more to send as
|
|
* the data send function has code to deal with the end of
|
|
* TX phase.
|
|
*/
|
|
_i2c_dw_data_send(dev);
|
|
|
|
/* Finish transfer when there is nothing to receive */
|
|
if (dw->rx_len == 0) {
|
|
goto stop_det;
|
|
}
|
|
|
|
while (dw->rx_len > 0) {
|
|
/* Wait for data in RX FIFO*/
|
|
start_time = sys_tick_get_32();
|
|
while (!regs->ic_status.bits.rfne) {
|
|
if ((sys_tick_get_32() - start_time) > POLLING_TIMEOUT) {
|
|
ret = DEV_FAIL;
|
|
goto finish;
|
|
}
|
|
}
|
|
|
|
_i2c_dw_data_read(dev);
|
|
}
|
|
|
|
stop_det:
|
|
/* Wait for transfer to complete */
|
|
start_time = sys_tick_get_32();
|
|
while (!regs->ic_raw_intr_stat.bits.stop_det) {
|
|
if ((sys_tick_get_32() - start_time) > POLLING_TIMEOUT) {
|
|
ret = DEV_FAIL;
|
|
goto finish;
|
|
}
|
|
}
|
|
value = regs->ic_clr_stop_det;
|
|
|
|
/* Wait for bus idle */
|
|
start_time = sys_tick_get_32();
|
|
while (regs->ic_status.bits.activity) {
|
|
if ((sys_tick_get_32() - start_time) > POLLING_TIMEOUT) {
|
|
ret = DEV_FAIL;
|
|
goto finish;
|
|
}
|
|
}
|
|
|
|
finish:
|
|
/* Disable controller when done */
|
|
regs->ic_enable.bits.enable = 0;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int i2c_dw_runtime_configure(struct device *dev, uint32_t config)
|
|
{
|
|
struct i2c_dw_rom_config const * const rom = dev->config->config_info;
|
|
struct i2c_dw_dev_config * const dw = dev->driver_data;
|
|
uint32_t value = 0;
|
|
uint32_t rc = DEV_OK;
|
|
|
|
volatile struct i2c_dw_registers * const regs =
|
|
(struct i2c_dw_registers *)rom->base_address;
|
|
|
|
dw->app_config.raw = config;
|
|
|
|
/* Make sure we have a supported speed for the DesignWare model */
|
|
/* and have setup the clock frequency and speed mode */
|
|
switch (dw->app_config.bits.speed) {
|
|
case I2C_SPEED_STANDARD:
|
|
/* Following the directions on DW spec page 59, IC_SS_SCL_LCNT
|
|
* must have register values larger than IC_FS_SPKLEN + 7
|
|
*/
|
|
if (I2C_STD_LCNT <= (regs->ic_fs_spklen + 7)) {
|
|
value = regs->ic_fs_spklen + 8;
|
|
} else {
|
|
value = I2C_STD_LCNT;
|
|
}
|
|
|
|
dw->lcnt = value;
|
|
|
|
/* Following the directions on DW spec page 59, IC_SS_SCL_HCNT
|
|
* must have register values larger than IC_FS_SPKLEN + 5
|
|
*/
|
|
if (I2C_STD_HCNT <= (regs->ic_fs_spklen + 5)) {
|
|
value = regs->ic_fs_spklen + 6;
|
|
} else {
|
|
value = I2C_STD_HCNT;
|
|
}
|
|
|
|
dw->hcnt = value;
|
|
break;
|
|
case I2C_SPEED_FAST:
|
|
/* fall through */
|
|
case I2C_SPEED_FAST_PLUS:
|
|
/*
|
|
* Following the directions on DW spec page 59, IC_FS_SCL_LCNT
|
|
* must have register values larger than IC_FS_SPKLEN + 7
|
|
*/
|
|
if (I2C_FS_LCNT <= (regs->ic_fs_spklen + 7)) {
|
|
value = regs->ic_fs_spklen + 8;
|
|
} else {
|
|
value = I2C_FS_LCNT;
|
|
}
|
|
|
|
dw->lcnt = value;
|
|
|
|
/*
|
|
* Following the directions on DW spec page 59, IC_FS_SCL_HCNT
|
|
* must have register values larger than IC_FS_SPKLEN + 5
|
|
*/
|
|
if (I2C_FS_HCNT <= (regs->ic_fs_spklen + 5)) {
|
|
value = regs->ic_fs_spklen + 6;
|
|
} else {
|
|
value = I2C_FS_HCNT;
|
|
}
|
|
|
|
dw->hcnt = value;
|
|
break;
|
|
case I2C_SPEED_HIGH:
|
|
if (dw->support_hs_mode) {
|
|
if (I2C_HS_LCNT <= (regs->ic_hs_spklen + 7)) {
|
|
value = regs->ic_hs_spklen + 8;
|
|
} else {
|
|
value = I2C_HS_LCNT;
|
|
}
|
|
|
|
dw->lcnt = value;
|
|
|
|
if (I2C_HS_HCNT <= (regs->ic_hs_spklen + 5)) {
|
|
value = regs->ic_hs_spklen + 6;
|
|
} else {
|
|
value = I2C_HS_HCNT;
|
|
}
|
|
|
|
dw->hcnt = value;
|
|
} else {
|
|
rc = DEV_INVALID_CONF;
|
|
}
|
|
break;
|
|
default:
|
|
/* TODO change */
|
|
rc = DEV_INVALID_CONF;
|
|
}
|
|
|
|
/*
|
|
* Clear any interrupts currently waiting in the controller
|
|
*/
|
|
value = regs->ic_clr_intr;
|
|
|
|
/*
|
|
* TEMPORARY HACK - The I2C does not work in any mode other than Master
|
|
* currently. This "hack" forces us to always be configured for master
|
|
* mode, until we can verify that Slave mode works correctly.
|
|
*/
|
|
dw->app_config.bits.is_master_device = 1;
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int i2c_dw_set_callback(struct device *dev, i2c_callback cb)
|
|
{
|
|
struct i2c_dw_dev_config * const dw = dev->driver_data;
|
|
|
|
dw->cb = cb;
|
|
|
|
return DEV_OK;
|
|
}
|
|
|
|
static int i2c_dw_suspend(struct device *dev)
|
|
{
|
|
DBG("I2C: suspend called - function not yet implemented\n");
|
|
/* TODO - add this code */
|
|
return DEV_OK;
|
|
}
|
|
|
|
|
|
static int i2c_dw_resume(struct device *dev)
|
|
{
|
|
DBG("I2C: resume called - function not yet implemented\n");
|
|
/* TODO - add this code */
|
|
return DEV_OK;
|
|
}
|
|
|
|
|
|
static struct i2c_driver_api funcs = {
|
|
.configure = i2c_dw_runtime_configure,
|
|
.set_callback = i2c_dw_set_callback,
|
|
.transfer = i2c_dw_transfer,
|
|
.suspend = i2c_dw_suspend,
|
|
.resume = i2c_dw_resume,
|
|
.poll_transfer = i2c_dw_poll_transfer,
|
|
};
|
|
|
|
|
|
#ifdef CONFIG_PCI
|
|
static inline int i2c_dw_pci_setup(struct device *dev)
|
|
{
|
|
struct i2c_dw_rom_config *rom = dev->config->config_info;
|
|
|
|
pci_bus_scan_init();
|
|
|
|
if (!pci_bus_scan(&rom->pci_dev)) {
|
|
DBG("Could not find device\n");
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PCI_ENUMERATION
|
|
rom->base_address = rom->pci_dev.addr;
|
|
rom->irq_num = rom->pci_dev.irq;
|
|
#endif
|
|
pci_enable_regs(&rom->pci_dev);
|
|
|
|
pci_show(&rom->pci_dev);
|
|
|
|
return 1;
|
|
}
|
|
#else
|
|
#define i2c_dw_pci_setup(_unused_) (1)
|
|
#endif /* CONFIG_PCI */
|
|
|
|
int i2c_dw_initialize(struct device *port)
|
|
{
|
|
struct i2c_dw_rom_config const * const rom = port->config->config_info;
|
|
struct i2c_dw_dev_config * const dev = port->driver_data;
|
|
|
|
volatile struct i2c_dw_registers *regs;
|
|
|
|
if (!i2c_dw_pci_setup(port)) {
|
|
return DEV_NOT_CONFIG;
|
|
}
|
|
|
|
synchronous_call_init(&dev->sync);
|
|
|
|
regs = (struct i2c_dw_registers *) rom->base_address;
|
|
|
|
/* verify that we have a valid DesignWare register first */
|
|
if (regs->ic_comp_type != I2C_DW_MAGIC_KEY) {
|
|
port->driver_api = NULL;
|
|
DBG("I2C: DesignWare magic key not found, check base address.");
|
|
DBG(" Stopping initialization\n");
|
|
return DEV_NOT_CONFIG;
|
|
}
|
|
|
|
port->driver_api = &funcs;
|
|
|
|
/*
|
|
* grab the default value on initialization. This should be set to the
|
|
* IC_MAX_SPEED_MODE in the hardware. If it does support high speed we
|
|
* can move provide support for it
|
|
*/
|
|
if (regs->ic_con.bits.speed == I2C_DW_SPEED_HIGH) {
|
|
DBG("I2C: high speed supported\n");
|
|
dev->support_hs_mode = true;
|
|
} else {
|
|
DBG("I2C: high speed NOT supported\n");
|
|
dev->support_hs_mode = false;
|
|
}
|
|
|
|
rom->config_func(port);
|
|
|
|
if (i2c_dw_runtime_configure(port, dev->app_config.raw) != DEV_OK) {
|
|
DBG("I2C: Cannot set default configuration 0x%x\n",
|
|
dev->app_config.raw);
|
|
return DEV_NOT_CONFIG;
|
|
}
|
|
|
|
dev->state = I2C_DW_STATE_READY;
|
|
|
|
return DEV_OK;
|
|
}
|
|
|
|
/* system bindings */
|
|
#if CONFIG_I2C_DW_0
|
|
void i2c_config_0(struct device *port);
|
|
|
|
struct i2c_dw_rom_config i2c_config_dw_0 = {
|
|
.base_address = CONFIG_I2C_DW_0_BASE,
|
|
#ifdef CONFIG_I2C_DW_0_IRQ_DIRECT
|
|
.irq_num = CONFIG_I2C_DW_0_IRQ,
|
|
#endif
|
|
.config_func = i2c_config_0,
|
|
|
|
#ifdef CONFIG_GPIO_DW_0_IRQ_SHARED
|
|
.shared_irq_dev_name = CONFIG_I2C_DW_0_IRQ_SHARED_NAME,
|
|
#endif
|
|
};
|
|
|
|
struct i2c_dw_dev_config i2c_0_runtime = {
|
|
.app_config.raw = CONFIG_I2C_DW_0_DEFAULT_CFG,
|
|
};
|
|
|
|
DECLARE_DEVICE_INIT_CONFIG(i2c_0,
|
|
CONFIG_I2C_DW_0_NAME,
|
|
&i2c_dw_initialize,
|
|
&i2c_config_dw_0);
|
|
|
|
SYS_DEFINE_DEVICE(i2c_0, &i2c_0_runtime, SECONDARY, CONFIG_I2C_INIT_PRIORITY);
|
|
struct device *i2c_dw_isr_0_device = SYS_GET_DEVICE(i2c_0);
|
|
|
|
#ifdef CONFIG_I2C_DW_0_IRQ_DIRECT
|
|
IRQ_CONNECT_STATIC(i2c_dw_0,
|
|
CONFIG_I2C_DW_0_IRQ,
|
|
CONFIG_I2C_DW_0_INT_PRIORITY,
|
|
i2c_dw_isr,
|
|
0,
|
|
I2C_DW_IRQ_FLAGS);
|
|
#endif
|
|
|
|
void i2c_config_0(struct device *port)
|
|
{
|
|
struct i2c_dw_rom_config * const config = port->config->config_info;
|
|
struct device *shared_irq_dev;
|
|
|
|
#if defined(CONFIG_I2C_DW_0_IRQ_DIRECT)
|
|
ARG_UNUSED(shared_irq_dev);
|
|
IRQ_CONFIG(i2c_dw_0, config->irq_num);
|
|
irq_enable(config->irq_num);
|
|
#elif defined(CONFIG_I2C_DW_0_IRQ_SHARED)
|
|
ARG_UNUSED(config);
|
|
shared_irq_dev = device_get_binding(config->shared_irq_dev_name);
|
|
shared_irq_isr_register(shared_irq_dev, (isr_t)i2c_dw_isr, port);
|
|
shared_irq_enable(shared_irq_dev, port);
|
|
#endif
|
|
}
|
|
#endif /* CONFIG_I2C_DW_0 */
|
|
|
|
|
|
/*
|
|
* Adding in I2C1
|
|
*/
|
|
#if CONFIG_I2C_DW_1
|
|
void i2c_config_1(struct device *port);
|
|
|
|
struct i2c_dw_rom_config i2c_config_dw_1 = {
|
|
.base_address = CONFIG_I2C_DW_1_BASE,
|
|
.irq_num = CONFIG_I2C_DW_1_IRQ,
|
|
.config_func = i2c_config_1,
|
|
};
|
|
|
|
struct i2c_dw_dev_config i2c_1_runtime = {
|
|
.app_config.raw = CONFIG_I2C_DW_1_DEFAULT_CFG,
|
|
};
|
|
|
|
DECLARE_DEVICE_INIT_CONFIG(i2c_1,
|
|
CONFIG_I2C_DW_1_NAME,
|
|
&i2c_dw_initialize,
|
|
&i2c_config_dw_1);
|
|
|
|
SYS_DEFINE_DEVICE(i2c_1, &i2c_1_runtime, SECONDARY,
|
|
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
|
|
struct device *i2c_dw_isr_1_device = SYS_GET_DEVICE(i2c_1);
|
|
|
|
IRQ_CONNECT_STATIC(i2c_dw_1,
|
|
CONFIG_I2C_DW_1_IRQ,
|
|
CONFIG_I2C_DW_1_INT_PRIORITY,
|
|
i2c_dw_isr,
|
|
0,
|
|
I2C_DW_IRQ_FLAGS);
|
|
|
|
void i2c_config_1(struct device *port)
|
|
{
|
|
struct i2c_dw_rom_config * const config = port->config->config_info;
|
|
struct device *shared_irq_dev;
|
|
|
|
ARG_UNUSED(shared_irq_dev);
|
|
IRQ_CONFIG(i2c_dw_1, config->irq_num);
|
|
irq_enable(config->irq_num);
|
|
}
|
|
|
|
#endif /* CONFIG_I2C_DW_1 */
|