/*************************************************************************** * sam7fc - TWI/I2C Handling * * * * 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 #include #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 *)¶m; 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; }