summaryrefslogblamecommitdiffstats
path: root/bsps/arm/atsam/i2c/atsam_i2c_bus.c
blob: fca48c580de5fdce8b99a3b61b3714148d42b9ae (plain) (tree)
1
2
3
4
5

                                           
  
                                                   
  



















                                                                              

   
                                   



















                                         
                                        

 




                                                                               
                                                        
 

                          


                                                    

                                       

                                         

                                                             
                        
                                    


                        
                                                                   

                               
 
                    

 




                                                                               
                                                         
 

                          
                                 
                                         
                                                     
                                       

                                                             
                        
                                    



                             


                                                            
         
                    















                                         

























































                                                                                

                                










                                                         
                                
                  






                                                                      
                                             
                                
                                        
 
                  


                                                


                                                                 










                                                                       



                                
                                              
 
                                                     
                            



                                                          
                            

                                                           


                 
                                                            

                                           
                                                          
                                                           
                                                               








                                                                    


                                                   
                

                           
                         





                                                            



                                                                           





                                            
                     


                         


                                                                     
 
                                            
 



                                                      
                                        

                                                                             




                                          








                                                       

                                                         















                                                                                
                                                                      







                                             
                                                                     











                                   





                                                                     
                                                            

                                  
                                                  
 
                                       


                                             
                        















                                                          
/* 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 <bsp/atsam-clock-config.h>
#include <bsp/atsam-i2c.h>

#include <rtems/irq-extension.h>

#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);
}