sam7fc/src/at91_twi.c

328 lines
8.3 KiB
C

/***************************************************************************
* Copyright (C) 02/2008 by Olaf Rempel *
* razzor@kopf-tisch.de *
* *
* 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; version 2 of the License *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include <stdint.h>
#include <stdio.h>
#include "AT91SAM7S256.h"
#include "board.h"
#include "at91_twi.h"
/*
* undocumented TWI_SR flags, at least OVRE seems to be present on a sam7s256
* taken from linux-2.6.24/include/asm-arm/arch-at91/at91_twi.h
*/
#define AT91_TWI_OVRE (1<<6) /* Overrun */
#define AT91_TWI_UNRE (1<<7) /* Underrun */
/*
* while ((cldiv = ((MCK / (2 * TWI)) -3) / (1 << ckdiv)) > 255)
* ckdiv++ ;
*
* works for TWI >= 100kHz
*/
#define TWI_CLK(x) (((MCK / (2 * (x))) -3)<<8 | ((MCK / (2 * (x))) -3))
enum twi_states {
TWI_IDLE = 0x00,
TWI_ERROR,
TWI_GENERIC_CMD = 0x10,
TWI_BLMC_UPDATE = 0x20,
};
static volatile uint32_t twi_state = TWI_IDLE;
static uint8_t *twi_data;
static uint32_t twi_size;
static uint32_t twi_count;
static void twi_isr(void)
{
/* get status */
uint32_t status = *AT91C_TWI_SR;
status &= *AT91C_TWI_IMR;
/* NACK - disable all interrupts and go to state TWI_ERROR */
if (status & AT91C_TWI_NACK) {
*AT91C_TWI_IDR = AT91C_TWI_TXCOMP | AT91C_TWI_RXRDY | AT91C_TWI_TXRDY | AT91C_TWI_NACK;
twi_state = TWI_ERROR;
return;
}
/* tx register ready for new data */
if (status & AT91C_TWI_TXRDY) {
if (twi_count != twi_size) {
/* feed next byte */
*AT91C_TWI_THR = twi_data[twi_count++];
} else {
/* wait for TXCOMP */
*AT91C_TWI_IDR = AT91C_TWI_RXRDY | AT91C_TWI_TXRDY;
*AT91C_TWI_IER = AT91C_TWI_TXCOMP | AT91C_TWI_NACK;
}
}
/* rx register has data */
if (status & AT91C_TWI_RXRDY) {
/* get data */
twi_data[twi_count++] = *AT91C_TWI_RHR;
/* transfer complete? */
if (twi_count == twi_size) {
/* send STOP and wait for TXCOMP */
*AT91C_TWI_CR = AT91C_TWI_STOP;
*AT91C_TWI_IDR = AT91C_TWI_TXRDY;
*AT91C_TWI_IER = AT91C_TWI_TXCOMP | AT91C_TWI_NACK;
}
}
/* transfer really complete? */
if (status & AT91C_TWI_TXCOMP) {
uint32_t addr = (*AT91C_TWI_MMR >> 16) & 0x7F;
/* are we doing a blmc update? */
if (twi_state == TWI_BLMC_UPDATE && addr != TWI_ADDR_BL4) {
/* increase address */
*AT91C_TWI_MMR += (1<<16);
/* send next value to next blmc */
*AT91C_TWI_THR = *twi_data++;
} else {
*AT91C_TWI_IDR = AT91C_TWI_TXCOMP | AT91C_TWI_RXRDY | AT91C_TWI_TXRDY | AT91C_TWI_NACK;
twi_state = TWI_IDLE;
}
}
}
uint32_t twi_setpwm(uint8_t *values)
{
if (twi_state == TWI_ERROR)
twi_state = TWI_IDLE;
if (twi_state != TWI_IDLE)
return 1;
twi_state = TWI_BLMC_UPDATE;
twi_data = values;
twi_size = 0;
twi_count = 0;
*AT91C_TWI_MMR = (TWI_ADDR_BL1 << 16) | AT91C_TWI_IADRSZ_1_BYTE;
*AT91C_TWI_IADR = CMD_SET_PWM;
*AT91C_TWI_THR = *twi_data++;
*AT91C_TWI_IER = AT91C_TWI_TXRDY | AT91C_TWI_NACK;
return 0;
}
uint32_t twi_cmd(uint8_t addr, struct twi_cmd *cmd)
{
if (twi_state == TWI_ERROR)
twi_state = TWI_IDLE;
if (twi_state != TWI_IDLE)
return 1;
/* TODO: locking needed? */
twi_state = TWI_GENERIC_CMD;
/* read transfer, or write transfer with payload */
if (cmd->mode & TWI_MODE_READ || cmd->size != 0) {
/* set address, direction, argument count and command bytes */
*AT91C_TWI_MMR = (addr << 16) | (cmd->mode & 0xFF) << 8;
*AT91C_TWI_IADR = cmd->cmd;
/* write transfer without payload */
} else {
/* use one cmd byte as payload (needed to start transfer) */
cmd->mode--;
*AT91C_TWI_MMR = (addr << 16) | (cmd->mode & 0xFF) << 8;
*AT91C_TWI_IADR = (cmd->cmd) >> 8;
}
/* isr needs data & size parameters */
twi_data = cmd->data;
twi_size = cmd->size;
twi_count = 0;
if (cmd->mode & TWI_MODE_READ) {
*AT91C_TWI_CR = AT91C_TWI_START;
*AT91C_TWI_IER = AT91C_TWI_RXRDY | AT91C_TWI_NACK;
} else {
*AT91C_TWI_THR = (twi_size != 0) ? cmd->data[twi_count++] : (cmd->cmd & 0xFF);
*AT91C_TWI_IER = AT91C_TWI_TXRDY | AT91C_TWI_NACK;
}
/*
* wait for end
* TODO: locking needed?
* TODO: timeout?
*/
while (twi_state != TWI_IDLE && twi_state != TWI_ERROR);
if (twi_state != TWI_IDLE) {
twi_state = TWI_IDLE;
return 1;
}
return 0;
}
uint32_t twi_read_eeprom(uint32_t addr, uint8_t *buf, uint32_t size)
{
struct twi_cmd cmd = {
.cmd = (addr & 0x7FFF),
.mode = TWI_MODE_READ | TWI_MODE_1_ARG,
.size = (size & 0x7FFF),
.data = buf,
};
if (twi_cmd(TWI_ADDR_EEPROM, &cmd) != 0)
size = 0;
return size;
}
uint32_t twi_write_eeprom(uint32_t addr, uint8_t *buf, uint32_t size)
{
uint32_t len = size;
while (len > 0) {
uint32_t count = 0x40 - (addr & 0x3F);
if (count > len)
count = len;
/* TODO: write complete polling */
volatile uint32_t x;
for (x = 0; x < 200000; x++);
struct twi_cmd cmd = {
.cmd = (addr & 0x7FFF),
.mode = TWI_MODE_WRITE | TWI_MODE_1_ARG,
.size = count,
.data = buf,
};
if (twi_cmd(TWI_ADDR_EEPROM, &cmd) != 0)
break;
addr += count;
buf += count;
len -= count;
}
return size - len;
}
void at91_twi_test(void)
{
uint32_t i;
for (i = TWI_ADDR_BL1; i <= TWI_ADDR_BL4; i++) {
printf("twi[0x%02lx] ", i);
struct twi_cmd cmd = {
.cmd = CMD_BOOT_LOADER,
.mode = TWI_MODE_WRITE | TWI_MODE_0_ARG,
};
twi_cmd(i, &cmd);
/* TODO: sleep */
volatile uint32_t x;
for (x = 0; x < 200000; x++);
uint8_t buf[16];
buf[0] = '\0';
cmd.cmd = CMD_GET_INFO;
cmd.mode = TWI_MODE_READ | TWI_MODE_0_ARG;
cmd.size = sizeof(buf);
cmd.data = buf;
twi_cmd(i, &cmd);
printf("boot:'%s' ", buf);
/* TODO: single 32bit write */
buf[0] = 0xFF;
buf[1] = 0xFF;
buf[2] = 0xFF;
cmd.cmd = CMD_GET_SIGNATURE;
cmd.size = 4;
twi_cmd(i, &cmd);
printf("sig:0x%02x%02x%02x\n\r", buf[0], buf[1], buf[2]);
cmd.cmd = CMD_BOOT_APPLICATION;
cmd.mode = TWI_MODE_WRITE | TWI_MODE_0_ARG;
cmd.size = 0;
twi_cmd(i, &cmd);
/* TODO: sleep */
for (x = 0; x < 200000; x++);
buf[0] = '\0';
cmd.cmd = CMD_GET_INFO;
cmd.mode = TWI_MODE_READ | TWI_MODE_0_ARG;
cmd.size = sizeof(buf);
cmd.data = buf;
twi_cmd(i, &cmd);
printf(" app :'%s' ", buf);
struct blmc_param param;
cmd.cmd = CMD_GET_PARAM;
cmd.mode = TWI_MODE_READ | TWI_MODE_0_ARG;
cmd.size = sizeof(param);
cmd.data = (uint8_t *)&param;
twi_cmd(i, &cmd);
printf("pwm:0x%02x-0x%02x Ilimit:0x%03x Imax:0x%03x\n\r",
param.pwm_min, param.pwm_max,
param.current_limit, param.current_max);
}
}
void at91_twi_init(void)
{
/* enable Clock */
*AT91C_PMC_PCER = (1 << AT91C_ID_TWI);
/* SDA & SCL from Peripheral A, Open Drain, no Pullup */
AT91S_PIO *pio = AT91C_BASE_PIOA;
/* do a software reset (bus not connected) */
*AT91C_TWI_CR = AT91C_TWI_SWRST;
pio->PIO_MDER = AT91C_PA3_TWD | AT91C_PA4_TWCK;
pio->PIO_PPUDR = AT91C_PA3_TWD | AT91C_PA4_TWCK;
pio->PIO_ASR = AT91C_PA3_TWD | AT91C_PA4_TWCK;
pio->PIO_PDR = AT91C_PA3_TWD | AT91C_PA4_TWCK;
/* set TWI Clock */
*AT91C_TWI_CWGR = TWI_CLK(400000); //| (5<<16);
/* disable all (known) interrupts */
*AT91C_TWI_IDR = AT91C_TWI_TXCOMP | AT91C_TWI_RXRDY | AT91C_TWI_TXRDY | AT91C_TWI_NACK;
/* level triggered, own vector */
AT91S_AIC *aic = AT91C_BASE_AIC;
aic->AIC_SMR[AT91C_ID_TWI] = IRQPRIO_TWI | AT91C_AIC_SRCTYPE_INT_HIGH_LEVEL;
aic->AIC_SVR[AT91C_ID_TWI] = (uint32_t)twi_isr;
aic->AIC_IECR = (1 << AT91C_ID_TWI);
/* enable teh monster */
*AT91C_TWI_CR = AT91C_TWI_MSEN;
}