rockbox/firmware/target/arm/rk27xx/i2c-rk27xx.c
Marcin Bukat e6c0bd0350 rk27xx: fix i2c driver
Change-Id: I205cc92f452c1990c64da7e91b2baf00b920c922
2013-04-09 09:31:40 +02:00

288 lines
6.2 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2011 by Marcin Bukat
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include "config.h"
#include "system.h"
#include "kernel.h"
#include "i2c-rk27xx.h"
/* Driver for the rockchip rk27xx built-in I2C controller in master mode
Both the i2c_read and i2c_write function take the following arguments:
* slave, the address of the i2c slave device to read from / write to
* address, optional sub-address in the i2c slave (unused if -1)
* len, number of bytes to be transfered
* data, pointer to data to be transfered
A return value other than 0 indicates an error.
*/
static struct mutex i2c_mtx;
static bool i2c_stop(void)
{
long timeout = current_tick + HZ/50;
I2C_CONR |= (1<<4); /* NACK */
I2C_LCMR |= (1<<2) | (1<<1); /* resume op, stop */
while (I2C_LCMR & (1<<1))
if (TIME_AFTER(current_tick, timeout))
return false;
return true;
}
static bool i2c_write_byte(uint8_t data, bool start)
{
long timeout = current_tick + HZ/50;
unsigned int isr_status;
I2C_CONR = (1<<3) | (1<<2); /* master port enable, MTX mode, ACK enable */
I2C_MTXR = data;
if (start)
I2C_LCMR = (1<<2) | (1<<0); /* resume op, start bit */
else
I2C_LCMR = (1<<2); /* resume op */
/* wait for ACK from slave */
do
{
isr_status = I2C_ISR;
if (isr_status & (1<<7))
{
i2c_stop();
I2C_ISR = 0;
return false;
}
if (TIME_AFTER(current_tick, timeout))
return false;
} while ((isr_status & (1<<0)) == 0);
/* clear status bit */
I2C_ISR &= ~(1<<0);
return true;
}
static bool i2c_read_byte(unsigned char *data)
{
long timeout = current_tick + HZ/50;
I2C_LCMR = (1<<2); /* resume op */
while (!(I2C_ISR & (1<<1)))
if (TIME_AFTER(current_tick, timeout))
return false;
*data = I2C_MRXR;
/* clear status bit */
I2C_ISR &= ~(1<<1);
return true;
}
/* route i2c bus to internal codec or external bus
* internal codec has 0x4e i2c slave address so
* access to this address is routed to internal bus.
* All other addresses are routed to external pads
*/
static void i2c_iomux(unsigned char slave)
{
unsigned long muxa = SCU_IOMUXA_CON & ~(0x1f<<14);
if ((slave & 0xfe) == (0x27<<1))
{
/* internal codec */
SCU_IOMUXA_CON = (muxa | (1<<16) | (1<<14));
}
else
{
/* external I2C bus */
SCU_IOMUXA_CON = (muxa | (1<<18));
}
}
void i2c_init(void)
{
mutex_init(&i2c_mtx);
/* ungate i2c module clock */
SCU_CLKCFG &= ~CLKCFG_I2C;
I2C_OPR |= (1<<7); /* reset state machine */
sleep(HZ/100);
I2C_OPR &= ~((1<<7) | (1<<6)); /* clear ENABLE bit, deasert reset */
/* set I2C divider to stay within allowed SCL freq limit
* APBfreq = 50Mhz
* I2C_div = (I2CCDVR[5:3] + 1) * 2^((I2CCDVR[2:0] + 1))
* SCLfreq = APBfreq/(5*I2C_div)
*
* (5<<3) | (1<<0) 416.7 KHz (above spec)
* (6<<3) | (1<<0) 357.1 kHz
* (7<<3) | (1<<0) 312.4 kHz
* (6<<3) | (2<<0) 178.6 kHz
* (7<<3) | (2<<0) 156.3 kHz
*/
I2C_OPR = (I2C_OPR & ~(0x3F)) | (6<<3) | (1<<0);
I2C_IER = 0x00;
I2C_OPR |= (1<<6); /* enable i2c core */
/* turn off i2c module clock until we need to comunicate */
SCU_CLKCFG |= CLKCFG_I2C;
}
int i2c_write(unsigned char slave, int address, int len,
const unsigned char *data)
{
int ret = 0;
mutex_lock(&i2c_mtx);
i2c_iomux(slave);
/* ungate i2c clock */
SCU_CLKCFG &= ~CLKCFG_I2C;
/* clear all flags */
I2C_ISR = 0x00;
I2C_IER = 0x00;
/* START */
if (! i2c_write_byte(slave & ~1, true))
{
ret = 1;
goto end;
}
if (address >= 0)
{
if (! i2c_write_byte(address, false))
{
ret = 2;
goto end;
}
}
/* write data */
while (len--)
{
if (! i2c_write_byte(*data++, false))
{
ret = 4;
goto end;
}
}
/* STOP */
if (! i2c_stop())
{
ret = 5;
goto end;
}
end:
mutex_unlock(&i2c_mtx);
SCU_CLKCFG |= CLKCFG_I2C;
return ret;
}
int i2c_read(unsigned char slave, int address, int len, unsigned char *data)
{
int ret = 0;
mutex_lock(&i2c_mtx);
i2c_iomux(slave);
/* ungate i2c module clock */
SCU_CLKCFG &= ~CLKCFG_I2C;
/* clear all flags */
I2C_ISR = 0x00;
I2C_IER = 0x00;
if (address >= 0)
{
/* START */
if (! i2c_write_byte(slave & ~1, true))
{
ret = 1;
goto end;
}
/* write address */
if (! i2c_write_byte(address, false))
{
ret = 2;
goto end;
}
}
/* (repeated) START */
if (! i2c_write_byte(slave | 1, true))
{
ret = 3;
goto end;
}
I2C_CONR = (1<<2); /* master port enable, MRX mode, ACK enable */
while (len)
{
if (! i2c_read_byte(data++))
{
ret = 4;
goto end;
}
if (len == 1)
I2C_CONR |= (1<<4); /* NACK */
else
I2C_CONR &= ~(1<<4); /* ACK */
len--;
}
/* STOP */
if (! i2c_stop())
{
ret = 5;
goto end;
}
end:
mutex_unlock(&i2c_mtx);
SCU_CLKCFG |= CLKCFG_I2C;
return ret;
}