/** * @file * * @ingroup arm_beagle * * @brief BeagleBoard I2C bus initialization and API Support. */ /* * Copyright (c) 2016 Punit Vara * Copyright (c) 2017 Sichen Zhao * Copyright (c) 2019 Christian Mauderer * * The license and distribution terms for this file may be * found in the file LICENSE in this distribution or at * http://www.rtems.org/license/LICENSE. */ #include #include #include #include #include #include #include #include #include #include typedef struct bbb_i2c_bus { i2c_bus base; volatile bbb_i2c_regs *regs; struct { volatile uint32_t *ctrl_clkctrl; volatile uint32_t *i2c_clkctrl; volatile uint32_t *clkstctrl; } clkregs; rtems_id task_id; rtems_vector_number irq; i2c_msg *buffer; size_t buffer_pos; int error; uint32_t con_reg; } bbb_i2c_bus; #define TRANSFER_TIMEOUT_COUNT 100 #define FIFO_THRESHOLD 5 #define min(l,r) ((l) < (r) ? (l) : (r)) #if 0 #define debug_print(fmt, args...) printk("bbb-i2c: " fmt, ## args) #else #define debug_print(fmt, args...) #endif /* * Here we assume the number of i2c nodes * will be less than 100. */ #define PATH_LEN (strlen("/dev/i2c-xx") + 1) static int am335x_i2c_fill_registers( bbb_i2c_bus *bus, uint32_t base ) { bus->regs = (volatile bbb_i2c_regs *)base; /* * FIXME: Implement a clock driver to parse and setup clocks */ switch ((intptr_t) bus->regs) { case AM335X_I2C0_BASE: bus->clkregs.ctrl_clkctrl = ®(AM335X_SOC_CM_WKUP_REGS + AM335X_CM_WKUP_CONTROL_CLKCTRL); bus->clkregs.i2c_clkctrl = ®(AM335X_SOC_CM_WKUP_REGS + AM335X_CM_WKUP_I2C0_CLKCTRL); bus->clkregs.clkstctrl = ®(AM335X_SOC_CM_WKUP_REGS + AM335X_CM_WKUP_CLKSTCTRL); break; case AM335X_I2C1_BASE: bus->clkregs.ctrl_clkctrl = ®(AM335X_SOC_CM_WKUP_REGS + AM335X_CM_WKUP_CONTROL_CLKCTRL); bus->clkregs.i2c_clkctrl = ®(AM335X_CM_PER_ADDR + AM335X_CM_PER_I2C1_CLKCTRL); bus->clkregs.clkstctrl = NULL; break; case AM335X_I2C2_BASE: bus->clkregs.ctrl_clkctrl = ®(AM335X_SOC_CM_WKUP_REGS + AM335X_CM_WKUP_CONTROL_CLKCTRL); bus->clkregs.i2c_clkctrl = ®(AM335X_CM_PER_ADDR + AM335X_CM_PER_I2C2_CLKCTRL); bus->clkregs.clkstctrl = NULL; break; default: return EINVAL; } return 0; } static void am335x_i2c_module_clk_enable( bbb_i2c_bus *bus ) { volatile uint32_t *ctrl_clkctrl = bus->clkregs.ctrl_clkctrl; volatile uint32_t *i2c_clkctrl = bus->clkregs.i2c_clkctrl; volatile uint32_t *clkstctrl = bus->clkregs.clkstctrl; /* Writing to MODULEMODE field of AM335X_CM_WKUP_I2C0_CLKCTRL register. */ *i2c_clkctrl |= AM335X_CM_WKUP_I2C0_CLKCTRL_MODULEMODE_ENABLE; /* Waiting for MODULEMODE field to reflect the written value. */ while ( AM335X_CM_WKUP_I2C0_CLKCTRL_MODULEMODE_ENABLE != ( *i2c_clkctrl & AM335X_CM_WKUP_I2C0_CLKCTRL_MODULEMODE ) ) { /* busy wait */ } /* * Waiting for IDLEST field in AM335X_CM_WKUP_CONTROL_CLKCTRL * register to attain desired value. */ while ( ( AM335X_CM_WKUP_CONTROL_CLKCTRL_IDLEST_FUNC << AM335X_CM_WKUP_CONTROL_CLKCTRL_IDLEST_SHIFT ) != ( *ctrl_clkctrl & AM335X_CM_WKUP_I2C0_CLKCTRL_IDLEST ) ) { /* busy wait */ } if ( clkstctrl != NULL ) { /* * Waiting for CLKACTIVITY_I2C0_GFCLK field in AM335X_CM_WKUP_CLKSTCTRL * register to attain desired value. */ while ( AM335X_CM_WKUP_CLKSTCTRL_CLKACTIVITY_I2C0_GFCLK != ( *clkstctrl & AM335X_CM_WKUP_CLKSTCTRL_CLKACTIVITY_I2C0_GFCLK ) ) { /* busy wait */ } } /* * Waiting for IDLEST field in AM335X_CM_WKUP_I2C0_CLKCTRL register to attain * desired value. */ while ( ( AM335X_CM_WKUP_I2C0_CLKCTRL_IDLEST_FUNC << AM335X_CM_WKUP_I2C0_CLKCTRL_IDLEST_SHIFT ) != ( *i2c_clkctrl & AM335X_CM_WKUP_I2C0_CLKCTRL_IDLEST ) ) ; } static int am335x_i2c_set_clock( i2c_bus *base, unsigned long clock ) { bbb_i2c_bus *bus = (bbb_i2c_bus *) base; uint32_t prescaler, divider; prescaler = ( BBB_I2C_SYSCLK / BBB_I2C_INTERNAL_CLK ) - 1; bus->regs->BBB_I2C_PSC = prescaler; divider = BBB_I2C_INTERNAL_CLK / ( 2 * clock ); bus->regs->BBB_I2C_SCLL = ( divider - 7 ); bus->regs->BBB_I2C_SCLH = ( divider - 5 ); return 0; } static int am335x_i2c_reset( bbb_i2c_bus *bus ) { volatile bbb_i2c_regs *regs = bus->regs; int timeout = 100; int err; bus->con_reg = 0; regs->BBB_I2C_CON = bus->con_reg; rtems_counter_delay_nanoseconds(50000000); regs->BBB_I2C_SYSC = AM335X_I2C_SYSC_SRST; rtems_counter_delay_nanoseconds(1000000); regs->BBB_I2C_CON = AM335X_I2C_CON_I2C_EN; while ( !( regs->BBB_I2C_SYSS & AM335X_I2C_SYSS_RDONE ) && timeout >= 0 ) { --timeout; rtems_counter_delay_nanoseconds(100000); } if ( timeout <= 0 ) { debug_print( "ERROR: Timeout in soft-reset\n" ); return ETIMEDOUT; } /* Disable again after reset */ regs->BBB_I2C_CON = bus->con_reg; err = am335x_i2c_set_clock( &bus->base, I2C_BUS_CLOCK_DEFAULT ); if (err) { return err; } regs->BBB_I2C_BUF = AM335X_I2C_BUF_TXTRSH(FIFO_THRESHOLD) | AM335X_I2C_BUF_RXTRSH(FIFO_THRESHOLD); /* Enable the I2C controller in master mode. */ bus->con_reg |= AM335X_I2C_CON_I2C_EN | AM335X_I2C_CON_MST; regs->BBB_I2C_CON = bus->con_reg; regs->BBB_I2C_IRQENABLE_SET = AM335X_I2C_IRQSTATUS_XDR | AM335X_I2C_IRQSTATUS_XRDY | AM335X_I2C_IRQSTATUS_RDR | AM335X_I2C_IRQSTATUS_RRDY | AM335X_I2C_IRQSTATUS_ARDY | AM335X_I2C_IRQSTATUS_NACK | AM335X_I2C_IRQSTATUS_AL; return 0; } /* Return true if done. */ static bool am335x_i2c_transfer_intr(bbb_i2c_bus *bus, uint32_t status) { size_t i; size_t amount = 0; volatile bbb_i2c_regs *regs = bus->regs; /* Handle errors */ if ((status & AM335X_I2C_IRQSTATUS_NACK) != 0) { debug_print("NACK\n"); regs->BBB_I2C_IRQSTATUS = AM335X_I2C_IRQSTATUS_NACK; bus->error = ENXIO; } else if ((status & AM335X_I2C_IRQSTATUS_AL) != 0) { debug_print("Arbitration lost\n"); regs->BBB_I2C_IRQSTATUS = AM335X_I2C_IRQSTATUS_AL; bus->error = ENXIO; } /* Transfer finished? */ if ((status & AM335X_I2C_IRQSTATUS_ARDY) != 0) { debug_print("ARDY transaction complete\n"); if (bus->error != 0 && (bus->buffer->flags & I2C_M_STOP) == 0) { regs->BBB_I2C_CON = bus->con_reg | AM335X_I2C_CON_STOP; } regs->BBB_I2C_IRQSTATUS = AM335X_I2C_IRQSTATUS_ARDY | AM335X_I2C_IRQSTATUS_RDR | AM335X_I2C_IRQSTATUS_RRDY | AM335X_I2C_IRQSTATUS_XDR | AM335X_I2C_IRQSTATUS_XRDY; return true; } if (bus->buffer->flags & I2C_M_RD) { if (status & AM335X_I2C_IRQSTATUS_RDR) { debug_print("RDR\n"); /* last data received */ amount = bus->buffer->len - bus->buffer_pos; } else if (status & AM335X_I2C_IRQSTATUS_RRDY) { debug_print("RRDY\n"); /* FIFO threshold reached */ amount = min(FIFO_THRESHOLD, bus->buffer->len - bus->buffer_pos); } debug_print("Read %d bytes\n", amount); for (i = 0; i < amount; i++) { bus->buffer->buf[bus->buffer_pos] = (uint8_t)(regs->BBB_I2C_DATA); ++bus->buffer_pos; } if (status & AM335X_I2C_IRQSTATUS_RDR) { regs->BBB_I2C_IRQSTATUS =AM335X_I2C_IRQSTATUS_RDR; } if (status & AM335X_I2C_IRQSTATUS_RRDY) { regs->BBB_I2C_IRQSTATUS =AM335X_I2C_IRQSTATUS_RRDY; } } else { if (status & AM335X_I2C_IRQSTATUS_XDR) { debug_print("XDR\n"); /* Remaining TX data won't reach the FIFO threshold. */ amount = bus->buffer->len - bus->buffer_pos; } else if (status & AM335X_I2C_IRQSTATUS_XRDY) { debug_print("XRDY\n"); /* FIFO threshold reached */ amount = min(FIFO_THRESHOLD, bus->buffer->len - bus->buffer_pos); } debug_print("Write %d bytes\n", amount); for (i = 0; i < amount; i++) { regs->BBB_I2C_DATA = bus->buffer->buf[bus->buffer_pos]; ++bus->buffer_pos; } if (status & AM335X_I2C_IRQSTATUS_XDR) { regs->BBB_I2C_IRQSTATUS = AM335X_I2C_IRQSTATUS_XDR; } if (status & AM335X_I2C_IRQSTATUS_XRDY) { regs->BBB_I2C_IRQSTATUS = AM335X_I2C_IRQSTATUS_XRDY; } } return false; } static void am335x_i2c_interrupt( void *arg ) { bbb_i2c_bus *bus = arg; volatile bbb_i2c_regs *regs = bus->regs; uint32_t status; status = regs->BBB_I2C_IRQSTATUS; debug_print("interrupt: %08x\n", status); if (status == 0) { /* Why can this even happen? */ return; } if (bus->buffer == NULL) { debug_print("Buffer is NULL\n"); bus->error = EINVAL; } if (bus->buffer == NULL || am335x_i2c_transfer_intr(bus, status)) { rtems_status_code sc; sc = rtems_event_transient_send( bus->task_id ); _Assert( sc == RTEMS_SUCCESSFUL ); (void) sc; /* suppress warning in case of no assert */ } } static int am335x_i2c_transfer( i2c_bus *base, i2c_msg *msgs, uint32_t nmsgs ) { size_t i; int err = 0; bool repstart = false; int timeout = 0; bbb_i2c_bus *bus = (bbb_i2c_bus *) base; volatile bbb_i2c_regs *regs = bus->regs; uint32_t reg; rtems_status_code sc; bus->task_id = rtems_task_self(); for (i = 0; i < nmsgs; i++) { bus->buffer = &msgs[i]; bus->buffer_pos = 0; bus->error = 0; debug_print("processing %2d/%d: addr: 0x%04x, flags: 0x%04x, len: %d, buf: %p\n", i, nmsgs, msgs[i].addr, msgs[i].flags, msgs[i].len, msgs[i].buf); if (bus->buffer == NULL || bus->buffer->buf == NULL || bus->buffer->len == 0) { err = EINVAL; break; } /* * Send START when bus is busy on repeated starts. * Otherwise wait some time. */ if (!repstart) { timeout = 0; while ((regs->BBB_I2C_IRQSTATUS_RAW & AM335X_I2C_IRQSTATUS_BB) != 0 && timeout <= TRANSFER_TIMEOUT_COUNT) { ++timeout; rtems_task_wake_after(RTEMS_MICROSECONDS_TO_TICKS(1000)); } if (timeout > TRANSFER_TIMEOUT_COUNT) { err = EBUSY; break; } timeout = 0; } else { repstart = false; } if ((bus->buffer->flags & I2C_M_STOP) == 0) { repstart = true; } regs->BBB_I2C_SA = bus->buffer->addr; regs->BBB_I2C_CNT = bus->buffer->len; regs->BBB_I2C_BUF |= AM335X_I2C_BUF_RXFIFO_CLR | AM335X_I2C_BUF_TXFIFO_CLR; reg = bus->con_reg | AM335X_I2C_CON_START; if (!repstart) { reg |= AM335X_I2C_CON_STOP; } if ((bus->buffer->flags & I2C_M_RD) == 0) { reg |= AM335X_I2C_CON_TRX; } /* Implicit stop on last message. */ if (i == nmsgs - 1) { reg |= AM335X_I2C_CON_STOP; } regs->BBB_I2C_CON = reg; sc = rtems_event_transient_receive( RTEMS_WAIT, bus->base.timeout ); if ( sc != RTEMS_SUCCESSFUL ) { rtems_event_transient_clear(); err = ETIMEDOUT; break; } if (bus->error) { err = bus->error; break; } } if (timeout == 0) { while ((regs->BBB_I2C_IRQSTATUS_RAW & AM335X_I2C_IRQSTATUS_BB) != 0 && timeout <= TRANSFER_TIMEOUT_COUNT) { ++timeout; rtems_task_wake_after(RTEMS_MICROSECONDS_TO_TICKS(1000)); } } if ((regs->BBB_I2C_CON & AM335X_I2C_CON_MST) == 0) { regs->BBB_I2C_CON = bus->con_reg; } bus->buffer = NULL; return -err; } static void am335x_i2c_destroy( i2c_bus *base ) { bbb_i2c_bus *bus = (bbb_i2c_bus *) base; rtems_status_code sc; bus->regs->BBB_I2C_IRQENABLE_CLR = 0xFFFF; bus->regs->BBB_I2C_CON = 0; sc = rtems_interrupt_handler_remove( bus->irq, am335x_i2c_interrupt, bus ); _Assert( sc == RTEMS_SUCCESSFUL ); (void) sc; i2c_bus_destroy_and_free( &bus->base ); } static int am335x_i2c_bus_register( uint32_t reg_base, rtems_vector_number irq, const char *bus_path ) { bbb_i2c_bus *bus; rtems_status_code sc; int err; bus = (bbb_i2c_bus *) i2c_bus_alloc_and_init( sizeof( *bus ) ); bus->irq = irq; if ( bus == NULL ) { return -1; } err = am335x_i2c_fill_registers(bus, reg_base); if (err != 0) { printf("i2c: invalid register base\n"); ( *bus->base.destroy )( &bus->base ); rtems_set_errno_and_return_minus_one( err ); } am335x_i2c_module_clk_enable( bus ); err = am335x_i2c_reset( bus ); if (err != 0) { printk("i2c: reset timed out\n"); ( *bus->base.destroy )( &bus->base ); rtems_set_errno_and_return_minus_one( err ); } sc = rtems_interrupt_handler_install( bus->irq, "BBB_I2C", RTEMS_INTERRUPT_UNIQUE, (rtems_interrupt_handler) am335x_i2c_interrupt, bus ); if ( sc != RTEMS_SUCCESSFUL ) { ( *bus->base.destroy )( &bus->base ); rtems_set_errno_and_return_minus_one( EIO ); } bus->base.transfer = am335x_i2c_transfer; bus->base.set_clock = am335x_i2c_set_clock; bus->base.destroy = am335x_i2c_destroy; return i2c_bus_register( &bus->base, bus_path ); } void beagle_i2c_init(phandle_t node) { int err; int unit; char bus_path[PATH_LEN]; rtems_vector_number irq; rtems_ofw_memory_area reg; if (!rtems_ofw_is_node_compatible(node, "ti,omap4-i2c")) /* We cannot handle this device */ return ; unit = beagle_get_node_unit(node); if (unit < 0 || unit >= 100) { printk("i2c: cannot register device, node unit number invalid."); printk(" Valid range is 0 <= unit < 100\n"); return ; } err = rtems_ofw_get_prop(node, "rtems,path", (void *)bus_path, PATH_LEN); if (err < 0) { /* No path was provided in the device tree therefore use the default one */ snprintf(bus_path, PATH_LEN, "/dev/i2c-%d", unit); } else if (err >= PATH_LEN) { /* Null terminate the string */ bus_path[PATH_LEN - 1] = 0; printk("i2c: bus path too long, trucated %s\n", bus_path); } err = rtems_ofw_get_interrupts(node, &irq, sizeof(irq)); if (err < 1) { printk("i2c: cannot register device, irq missing in device tree\n"); return ; } err = rtems_ofw_get_reg(node, ®, sizeof(reg)); if (err <= 0) { printk("i2c: cannot register device, regs field missing\n"); return ; } err = am335x_i2c_bus_register(reg.start, irq, bus_path); if (err != 0) printk("i2c: Could not register device (%d)\n", err); }