/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (c) 2016 embedded brains GmbH & Co. KG * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #define ATSAMV_I2C_IRQ_ERROR \ (TWIHS_IDR_ARBLST \ | TWIHS_IDR_TOUT \ | TWIHS_IDR_OVRE \ | TWIHS_IDR_UNRE \ | TWIHS_IDR_NACK) #define TEN_BIT_MASK 0xFC00 #define SEVEN_BIT_MASK 0xFF80 #define TEN_BIT_START_ADDR_MASK 0x78 #define LAST_TWO_BITS_MASK 0x03 #define LAST_BYTE_MASK 0x00FF static void atsam_i2c_disable_interrupts(Twihs *regs) { TWI_DisableIt(regs, 0xFFFFFFFF); } /* * Return true if the message is done right after this. That is the case if all * bytes are received but no stop is requested. */ static bool atsam_i2c_continue_read(Twihs *regs, atsam_i2c_bus *bus) { bool done = false; *bus->current_msg_byte = TWI_ReadByte(regs); ++bus->current_msg_byte; --bus->current_msg_todo; /* check for transfer finish */ if (bus->current_msg_todo == 0) { if (bus->stop_request){ TWI_DisableIt(regs, TWIHS_IDR_RXRDY); TWI_EnableIt(regs, TWIHS_IER_TXCOMP); } else { done = true; } } /* Last byte? */ else if (bus->current_msg_todo == 1 && bus->stop_request) { TWI_Stop(regs); } return done; } /* * Return true if the message is done right after this. That is the case if all * bytes are sent but no stop is requested. */ static bool atsam_i2c_continue_write(Twihs *regs, atsam_i2c_bus *bus) { bool done = false; /* Transfer finished ? */ if (bus->current_msg_todo == 0) { TWI_DisableIt(regs, TWIHS_IDR_TXRDY); if (bus->stop_request){ TWI_EnableIt(regs, TWIHS_IER_TXCOMP); TWI_SendSTOPCondition(regs); } else { done = true; } } /* Bytes remaining */ else { TWI_WriteByte(regs, *bus->current_msg_byte); ++bus->current_msg_byte; --bus->current_msg_todo; } return done; } static void atsam_i2c_next_packet(atsam_i2c_bus *bus) { i2c_msg *msg; ++bus->msgs; --bus->msg_todo; msg = &bus->msgs[0]; bus->current_msg_todo = msg->len; bus->current_msg_byte = msg->buf; } static bool atsam_i2c_set_address_size(const i2c_msg *msg) { bool rv = ((msg->flags & I2C_M_TEN) == 0) ? false : true; return rv; } static void atsam_i2c_set_address_regs(Twihs *regs, uint16_t address, bool ten_bit_addr, bool read_transfer) { /* No internal addresses used */ uint32_t mmr_temp = 0; uint32_t iadr_temp = 0; assert(regs != NULL); if (ten_bit_addr){ uint8_t addr_temp = TEN_BIT_START_ADDR_MASK; assert(address & TEN_BIT_MASK); mmr_temp = (1u << 8) | ((addr_temp & LAST_TWO_BITS_MASK) << 16); iadr_temp = (addr_temp & LAST_BYTE_MASK); } else { assert((address & SEVEN_BIT_MASK) == 0); mmr_temp = (address << 16); } if (read_transfer){ mmr_temp |= TWIHS_MMR_MREAD; } /* Set slave and number of internal address bytes */ regs->TWIHS_MMR = 0; regs->TWIHS_MMR = mmr_temp; /* Set internal address bytes */ regs->TWIHS_IADR = 0; regs->TWIHS_IADR = iadr_temp; } static void atsam_i2c_setup_read_transfer(Twihs *regs, bool ctrl, uint16_t slave_addr, bool send_stop) { TWI_EnableIt(regs, TWIHS_IER_RXRDY); atsam_i2c_set_address_regs(regs, slave_addr, ctrl, true); if (send_stop){ regs->TWIHS_CR = TWIHS_CR_START | TWIHS_CR_STOP; } else { regs->TWIHS_CR = TWIHS_CR_START; } } static void atsam_i2c_setup_write_transfer(atsam_i2c_bus *bus, Twihs *regs, bool ctrl, uint16_t slave_addr) { atsam_i2c_set_address_regs(regs, slave_addr, ctrl, false); TWI_WriteByte(regs, *bus->current_msg_byte); ++bus->current_msg_byte; --bus->current_msg_todo; TWI_EnableIt(regs, TWIHS_IER_TXRDY); } static void atsam_i2c_setup_transfer(atsam_i2c_bus *bus, Twihs *regs) { const i2c_msg *msgs = bus->msgs; bool send_stop = false; uint32_t msg_todo = bus->msg_todo; uint16_t slave_addr; bool ten_bit_addr = false; bool stop_needed = true; bool read; ten_bit_addr = atsam_i2c_set_address_size(msgs); if ((msg_todo > 1) && ((msgs[1].flags & I2C_M_NOSTART) != 0)){ stop_needed = false; } read = (msgs->flags & I2C_M_RD) != 0; slave_addr = msgs->addr; bus->stop_request = stop_needed; if (read){ if (bus->current_msg_todo == 1){ send_stop = true; } atsam_i2c_setup_read_transfer(regs, ten_bit_addr, slave_addr, send_stop); } else { atsam_i2c_setup_write_transfer(bus, regs, ten_bit_addr, slave_addr); } } static void atsam_i2c_interrupt(void *arg) { atsam_i2c_bus *bus = arg; uint32_t irqstatus; bool done = false; Twihs *regs = bus->regs; /* read interrupts */ irqstatus = TWI_GetMaskedStatus(regs); if((irqstatus & ATSAMV_I2C_IRQ_ERROR) != 0) { done = true; } else if ((irqstatus & TWIHS_SR_RXRDY) != 0) { done = atsam_i2c_continue_read(regs, bus); } else if ((irqstatus & TWIHS_SR_TXCOMP) != 0) { TWI_DisableIt(regs, TWIHS_IDR_TXCOMP); done = true; } else if ((irqstatus & TWIHS_SR_TXRDY) != 0) { done = atsam_i2c_continue_write(regs, bus); } if(done){ bus->err = irqstatus & ATSAMV_I2C_IRQ_ERROR; atsam_i2c_next_packet(bus); if (bus->msg_todo == 0 || bus->err != 0) { atsam_i2c_disable_interrupts(regs); rtems_binary_semaphore_post(&bus->sem); } else { atsam_i2c_setup_transfer(bus, regs); } } } static int atsam_i2c_transfer(i2c_bus *base, i2c_msg *msgs, uint32_t msg_count) { atsam_i2c_bus *bus = (atsam_i2c_bus *)base; Twihs *regs; uint32_t i; int eno; if (msg_count < 1){ return 0; } for (i = 0; i < msg_count; ++i) { if ((msgs[i].flags & I2C_M_RECV_LEN) != 0) { return -EINVAL; } if (msgs[i].len == 0) { /* Hardware doesn't support zero length messages */ return -EINVAL; } } bus->msgs = &msgs[0]; bus->msg_todo = msg_count; bus->current_msg_todo = msgs[0].len; bus->current_msg_byte = msgs[0].buf; bus->err = 0; regs = bus->regs; /* Start with a clean start. Enable error interrupts. */ TWI_ConfigureMaster(bus->regs, bus->output_clock, BOARD_MCK); TWI_EnableIt(regs, ATSAMV_I2C_IRQ_ERROR); atsam_i2c_setup_transfer(bus, regs); eno = rtems_binary_semaphore_wait_timed_ticks( &bus->sem, bus->base.timeout ); if (eno != 0 || bus->err != 0) { TWI_ConfigureMaster(bus->regs, bus->output_clock, BOARD_MCK); rtems_binary_semaphore_try_wait(&bus->sem); if (bus->err != 0) { return -EIO; } else { return -ETIMEDOUT; } } return 0; } static int atsam_i2c_set_clock(i2c_bus *base, unsigned long clock) { atsam_i2c_bus *bus = (atsam_i2c_bus *)base; bus->output_clock = clock; TWI_ConfigureMaster(bus->regs, clock, BOARD_MCK); return 0; } static void atsam_i2c_destroy(i2c_bus *base) { atsam_i2c_bus *bus = (atsam_i2c_bus *)base; rtems_status_code sc; sc = rtems_interrupt_handler_remove(bus->irq, atsam_i2c_interrupt, bus); assert(sc == RTEMS_SUCCESSFUL); i2c_bus_destroy_and_free(&bus->base); } static void atsam_i2c_init(atsam_i2c_bus *bus, uint32_t board_id, const Pin *pins) { /* Initialize the TWI */ PIO_Configure(pins, TWI_AMOUNT_PINS); /* Enable the TWI clock */ ENABLE_PERIPHERAL(board_id); TWI_ConfigureMaster(bus->regs, bus->output_clock, BOARD_MCK); } int i2c_bus_register_atsam( const char *bus_path, Twihs *register_base, rtems_vector_number irq, const Pin pins[TWI_AMOUNT_PINS] ) { atsam_i2c_bus *bus; rtems_status_code sc; bus = (atsam_i2c_bus *) i2c_bus_alloc_and_init(sizeof(*bus)); if (bus == NULL){ return -1; } rtems_binary_semaphore_init(&bus->sem, "ATSAM I2C"); bus->regs = register_base; bus->irq = irq; bus->output_clock = I2C_BUS_CLOCK_DEFAULT; atsam_i2c_init(bus, irq, pins); sc = rtems_interrupt_handler_install( irq, "ATSAM I2C", RTEMS_INTERRUPT_UNIQUE, atsam_i2c_interrupt, bus ); if(sc != RTEMS_SUCCESSFUL){ (*bus->base.destroy)(&bus->base); rtems_set_errno_and_return_minus_one(EIO); } bus->base.transfer = atsam_i2c_transfer; bus->base.set_clock = atsam_i2c_set_clock; bus->base.destroy = atsam_i2c_destroy; return i2c_bus_register(&bus->base, bus_path); }