# HG changeset patch # User Paul Boddie # Date 1698682963 -3600 # Node ID fa6dc9e0cc5f4a7ff59b92c9dd5f881f15dccc9f # Parent c70c29549f3935c887d52e5b4aa79d9b2ee23afb Refined and fixed the merged I2C implementation. diff -r c70c29549f39 -r fa6dc9e0cc5f pkg/devices/lib/i2c/include/i2c-common.h --- a/pkg/devices/lib/i2c/include/i2c-common.h Mon Oct 30 17:21:26 2023 +0100 +++ b/pkg/devices/lib/i2c/include/i2c-common.h Mon Oct 30 17:22:43 2023 +0100 @@ -37,6 +37,7 @@ { protected: Hw::Register_block<32> _regs; + enum Clock_identifiers _clock; Cpm_chip *_cpm; uint32_t _frequency; @@ -68,6 +69,13 @@ void queue_writes(); void store_reads(); + // Behaviour adaptations. + + virtual bool high_speed_mode() = 0; + virtual bool hold_enable() = 0; + virtual bool stop_hold() = 0; + uint32_t stop_command(); + public: explicit I2c_channel(l4_addr_t start, enum Clock_identifiers clock, Cpm_chip *cpm, uint32_t frequency); @@ -113,6 +121,9 @@ virtual unsigned int num_channels() = 0; + virtual I2c_channel *_get_channel(l4_addr_t addr, enum Clock_identifiers clock, + Cpm_chip *cpm, uint32_t frequency) = 0; + public: explicit I2c_chip(l4_addr_t start, l4_addr_t end, Cpm_chip *cpm, uint32_t frequency); diff -r c70c29549f39 -r fa6dc9e0cc5f pkg/devices/lib/i2c/include/i2c-jz4780.h --- a/pkg/devices/lib/i2c/include/i2c-jz4780.h Mon Oct 30 17:21:26 2023 +0100 +++ b/pkg/devices/lib/i2c/include/i2c-jz4780.h Mon Oct 30 17:22:43 2023 +0100 @@ -34,6 +34,16 @@ class I2c_jz4780_channel : public I2c_channel { +protected: + bool high_speed_mode() + { return false; } + + bool hold_enable() + { return true; } + + bool stop_hold() + { return true; } + public: explicit I2c_jz4780_channel(l4_addr_t start, enum Clock_identifiers clock, Cpm_chip *cpm, uint32_t frequency); @@ -47,6 +57,10 @@ unsigned int num_channels() { return 5; } + I2c_channel *_get_channel(l4_addr_t addr, enum Clock_identifiers clock, + Cpm_chip *cpm, uint32_t frequency) + { return new I2c_jz4780_channel(addr, clock, cpm, frequency); } + public: explicit I2c_jz4780_chip(l4_addr_t start, l4_addr_t end, Cpm_chip *cpm, uint32_t frequency); @@ -89,4 +103,6 @@ int jz4780_i2c_failed(void *i2c_channel); +void jz4780_i2c_stop(void *i2c_channel); + EXTERN_C_END diff -r c70c29549f39 -r fa6dc9e0cc5f pkg/devices/lib/i2c/include/i2c-x1600.h --- a/pkg/devices/lib/i2c/include/i2c-x1600.h Mon Oct 30 17:21:26 2023 +0100 +++ b/pkg/devices/lib/i2c/include/i2c-x1600.h Mon Oct 30 17:22:43 2023 +0100 @@ -34,6 +34,16 @@ class I2c_x1600_channel : public I2c_channel { +protected: + bool high_speed_mode() + { return true; } + + bool hold_enable() + { return false; } + + bool stop_hold() + { return false; } + public: explicit I2c_x1600_channel(l4_addr_t start, enum Clock_identifiers clock, Cpm_chip *cpm, uint32_t frequency); @@ -47,6 +57,10 @@ unsigned int num_channels() { return 2; } + I2c_channel *_get_channel(l4_addr_t addr, enum Clock_identifiers clock, + Cpm_chip *cpm, uint32_t frequency) + { return new I2c_x1600_channel(addr, clock, cpm, frequency); } + public: explicit I2c_x1600_chip(l4_addr_t start, l4_addr_t end, Cpm_chip *cpm, uint32_t frequency); @@ -89,4 +103,6 @@ int x1600_i2c_failed(void *i2c_channel); +void x1600_i2c_stop(void *i2c_channel); + EXTERN_C_END diff -r c70c29549f39 -r fa6dc9e0cc5f pkg/devices/lib/i2c/src/common.cc --- a/pkg/devices/lib/i2c/src/common.cc Mon Oct 30 17:21:26 2023 +0100 +++ b/pkg/devices/lib/i2c/src/common.cc Mon Oct 30 17:22:43 2023 +0100 @@ -25,6 +25,8 @@ #include #include + + /* NOTE: The X1600 is very similar to the JZ4780 with the registers renamed to I2C from SMB, with a few high speed registers, the I2C_RINTST, I2C_TXFLR and I2C_RXFLR registers, and a few other @@ -67,6 +69,10 @@ I2c_ack_call = 0x098, // I2C_ACKGC I2c_enable_status = 0x09c, // I2C_ENBST (read-only) + // JZ4780 variation... + + I2c_sda_hold_time_jz4780 = 0x0d0, // I2C_SDAHD + // X1600 only... High_high_count = 0x024, // I2C_HHCNT @@ -84,6 +90,9 @@ enum I2c_control_bits : unsigned { + I2c_stop_hold = 0x80, // STPHLD (JZ4780 only) + I2c_no_stop_hold = 0x00, // absent STPHLD field + I2c_disable_slave = 0x40, // SLVDIS (slave disabled) I2c_enable_restart = 0x20, // RESTART I2c_master_10bit = 0x10, // MATP (read-only) @@ -97,6 +106,9 @@ { I2c_speed_standard = 1, I2c_speed_fast = 2, + + // X1600 only... + I2c_speed_high = 3, }; @@ -125,8 +137,13 @@ enum I2c_hold_control_bits : unsigned { - /* The hold enable flag has been removed since the JZ4780 and the hold time - field widened. */ + // JZ4780... + + I2c_hold_enable = 0x100, // HDENB + I2c_hold_disable = 0x000, // HDENB + I2c_hold_mask_jz4780 = 0xff, + + // The X1600 loses the hold enable flag and widens the hold time field. I2c_hold_mask = 0xffff, }; @@ -138,9 +155,14 @@ enum I2c_command_bits : unsigned { + // X1600 only... + I2c_command_restart = 0x400, // RESTART: explicit restart before next byte I2c_command_stop = 0x200, // STOP: explicit stop after next byte I2c_command_no_stop = 0x000, + + // Common fields. + I2c_command_read = 0x100, // CMD I2c_command_write = 0x000, // CMD }; @@ -174,7 +196,7 @@ enum Clock_identifiers clock, Cpm_chip *cpm, uint32_t frequency) -: _cpm(cpm), _frequency(frequency) +: _clock(clock), _cpm(cpm), _frequency(frequency) { _regs = new Hw::Mmio_register_block<32>(start); _cpm->start_clock(clock); @@ -211,12 +233,10 @@ void I2c_channel::set_frequency() { - // The APB clock (PCLK) is used to drive I2C transfers. Its value must be - // obtained from the CPM unit. It is known as I2C_DEV_CLK here and is scaled - // to kHz in order to keep the numbers easily representable, as is the bus - // frequency. + // The I2C device clock is known as I2C_DEV_CLK here and is scaled to kHz in + // order to keep the numbers easily representable, as is the bus frequency. - uint32_t i2c_dev_clk = _cpm->get_frequency(Clock_pclock) / 1000; + uint32_t i2c_dev_clk = _cpm->get_frequency(_clock) / 1000; // Note that this is not I2C_DEV_CLK but the actual I2C bus frequency. @@ -224,19 +244,20 @@ // Select the appropriate speed. - unsigned int speed = (i2c_clk <= 100) ? I2c_speed_standard - : (i2c_clk <= 400 ? I2c_speed_fast - : I2c_speed_high); + unsigned int speed = (i2c_clk <= 100) ? I2c_speed_standard : + (!high_speed_mode() || (i2c_clk <= 400)) ? I2c_speed_fast : + I2c_speed_high; // NOTE: Permit broader configuration elsewhere. _regs[I2c_control] = (speed << I2c_speed_bit) | I2c_disable_slave | I2c_enable_restart | - I2c_enable_master; + I2c_enable_master | + (stop_hold() ? I2c_stop_hold : I2c_no_stop_hold); - // According to the programming manual, if the PCLK period is T{I2C_DEV_CLK} - // then the I2C clock period is... + // According to the programming manual, if the device clock period is + // T{I2C_DEV_CLK} then the I2C clock period is... // T{SCL} = T{SCL_high} + T{SCL_low} @@ -294,8 +315,6 @@ uint32_t high_reg, low_reg; uint32_t high_count, low_count; - int32_t hold_count; - uint32_t setup_count; // Level hold times: @@ -314,7 +333,7 @@ low_reg = Std_low_count; high_reg = Std_high_count; } - else if (i2c_clk <= 400) // 400 kHz + else if (!high_speed_mode() || (i2c_clk <= 400)) // 400 kHz { low_count = (i2c_dev_clk * 13) / (i2c_clk * 19) - 1; high_count = (i2c_dev_clk * 6) / (i2c_clk * 19) - 8; @@ -353,12 +372,13 @@ // Since the device clock is in kHz (scaled down by 1000) and the times are // given in ns (scaled up by 1000000000), a division of 1000000 is introduced. - hold_count = (i2c_dev_clk * 300) / 1000000 - 1; + int32_t hold_count = (i2c_dev_clk * 300) / 1000000 - 1; + uint32_t hold_mask = (uint32_t) (hold_enable() ? I2c_hold_mask_jz4780 : I2c_hold_mask); + uint32_t hold_reg = hold_enable() ? I2c_sda_hold_time_jz4780 : I2c_sda_hold_time; - _regs[I2c_sda_hold_time] = (_regs[I2c_sda_hold_time] & ~I2c_hold_mask) | - (hold_count < 0 ? 0 - : (hold_count < (int) I2c_hold_mask ? (uint32_t) hold_count - : I2c_hold_mask)); + _regs[hold_reg] = ((hold_count >= 0) && hold_enable() ? I2c_hold_enable : I2c_hold_disable) | + (hold_count < 0 ? 0 : + (uint32_t) hold_count < hold_mask ? (uint32_t) hold_count : hold_mask); // I2C_SDASU is apparently not used in master mode. @@ -366,15 +386,16 @@ // I2CSDASU = T{delay} / T{I2C_DEV_CLK} + 1 // I2CSDASU = I2C_DEV_CLK * T{delay} + 1 + uint32_t setup_count; + if (i2c_clk <= 100) setup_count = (i2c_dev_clk * 250) / 1000000 + 1; - else if (i2c_clk <= 400) + else if (!high_speed_mode() || (i2c_clk <= 400)) setup_count = (i2c_dev_clk * 100) / 1000000 + 1; else setup_count = (i2c_dev_clk * 50) / 1000000 + 1; - _regs[I2c_sda_setup_time] = (_regs[I2c_sda_setup_time] & ~I2c_setup_mask) | - (setup_count < I2c_setup_mask ? setup_count : I2c_setup_mask); + _regs[I2c_sda_setup_time] = (setup_count < I2c_setup_mask ? setup_count : I2c_setup_mask); } // Set the target address and enable transfer. @@ -501,6 +522,18 @@ +// Handle differences in stop condition management, producing the stop command +// field for the X1600. + +uint32_t +I2c_channel::stop_command() +{ + if (stop_hold()) + return 0; + + return _stop && (_reqpos == _total - 1) ? I2c_command_stop : I2c_command_no_stop; +} + // Send read commands for empty queue entries. void @@ -524,13 +557,16 @@ while (can_queue && can_send()) { - uint32_t stop = _stop && (_reqpos == _total - 1) ? I2c_command_stop : I2c_command_no_stop; - - _regs[I2c_data_command] = I2c_command_read | stop; + _regs[I2c_data_command] = I2c_command_read | stop_command(); _reqpos++; can_queue--; } + /* Extra read request on JZ4780. */ + + if (stop_hold() && _stop) + stop(); + // Update the threshold to be notified of any reduced remaining amount. set_read_threshold(); @@ -551,9 +587,7 @@ while (can_queue && can_send()) { - uint32_t stop = _stop && (_reqpos == _total - 1) ? I2c_command_stop : I2c_command_no_stop; - - _regs[I2c_data_command] = I2c_command_write | _buf[_reqpos] | stop; + _regs[I2c_data_command] = I2c_command_write | _buf[_reqpos] | stop_command(); _reqpos++; can_queue--; } @@ -675,6 +709,7 @@ void I2c_channel::stop() { + _regs[I2c_control] = _regs[I2c_control] & ~I2c_stop_hold; } @@ -700,7 +735,7 @@ enum Clock_identifiers clocks[] = {Clock_i2c0, Clock_i2c1, Clock_i2c2, Clock_i2c3, Clock_i2c4}; if (channel < num_channels()) - return new I2c_channel(block, clocks[channel], _cpm, _frequency); + return _get_channel(block, clocks[channel], _cpm, _frequency); else throw -L4_EINVAL; } diff -r c70c29549f39 -r fa6dc9e0cc5f pkg/devices/lib/i2c/src/jz4780.cc --- a/pkg/devices/lib/i2c/src/jz4780.cc Mon Oct 30 17:21:26 2023 +0100 +++ b/pkg/devices/lib/i2c/src/jz4780.cc Mon Oct 30 17:22:43 2023 +0100 @@ -128,3 +128,8 @@ { return static_cast(i2c_channel)->failed(); } + +void jz4780_i2c_stop(void *i2c_channel) +{ + static_cast(i2c_channel)->stop(); +} diff -r c70c29549f39 -r fa6dc9e0cc5f pkg/devices/lib/i2c/src/x1600.cc --- a/pkg/devices/lib/i2c/src/x1600.cc Mon Oct 30 17:21:26 2023 +0100 +++ b/pkg/devices/lib/i2c/src/x1600.cc Mon Oct 30 17:22:43 2023 +0100 @@ -114,3 +114,8 @@ { return static_cast(i2c_channel)->failed(); } + +void x1600_i2c_stop(void *i2c_channel) +{ + static_cast(i2c_channel)->stop(); +} diff -r c70c29549f39 -r fa6dc9e0cc5f pkg/landfall-examples/ci20_i2c/ci20_i2c.c --- a/pkg/landfall-examples/ci20_i2c/ci20_i2c.c Mon Oct 30 17:21:26 2023 +0100 +++ b/pkg/landfall-examples/ci20_i2c/ci20_i2c.c Mon Oct 30 17:22:43 2023 +0100 @@ -62,7 +62,7 @@ return start - index; } -static long i2c_read(void *i2c_channel, uint8_t *buf, unsigned length, +static int i2c_read(void *i2c_channel, uint8_t *buf, unsigned length, int stop, l4_cap_idx_t irqcap) { l4_msgtag_t tag; @@ -74,18 +74,21 @@ tag = l4_irq_receive(irqcap, l4_timeout(L4_IPC_TIMEOUT_NEVER, l4_timeout_from_us(1000))); if (l4_ipc_error(tag, l4_utcb())) - return 0; + break; if (jz4780_i2c_failed(i2c_channel)) - return 0; + break; jz4780_i2c_read(i2c_channel); } + if (stop) + jz4780_i2c_stop(i2c_channel); + return jz4780_i2c_have_read(i2c_channel); } -static long i2c_write(void *i2c_channel, uint8_t *buf, unsigned length, +static int i2c_write(void *i2c_channel, uint8_t *buf, unsigned length, int stop, l4_cap_idx_t irqcap) { l4_msgtag_t tag; @@ -98,14 +101,17 @@ tag = l4_irq_receive(irqcap, l4_timeout(L4_IPC_TIMEOUT_NEVER, l4_timeout_from_us(1000))); if ((err = l4_ipc_error(tag, l4_utcb()))) - return 0; + break; if (jz4780_i2c_failed(i2c_channel)) - return 0; + break; jz4780_i2c_write(i2c_channel); } + if (stop) + jz4780_i2c_stop(i2c_channel); + return jz4780_i2c_have_written(i2c_channel); }