# HG changeset patch # User Paul Boddie # Date 1717769552 -7200 # Node ID 7b56ac3b3ef4389e4dbb1170f7a6c7e9e0d6125c # Parent bf3aeeccde0985fa2875f3e8e926972dbd9ceef6 Introduced a common base class for SPI operations, integrating the control functionality of any involved hybrid SPI channel to permit the use of a GPIO-driven data/command pin instead of a peripheral's GPC signal. Added dedicated X1600 support for SPI, largely equivalent to the JZ4780 support. diff -r bf3aeeccde09 -r 7b56ac3b3ef4 pkg/devices/lib/spi/include/spi-common.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/devices/lib/spi/include/spi-common.h Fri Jun 07 16:12:32 2024 +0200 @@ -0,0 +1,102 @@ +/* + * Perform SPI communication using the SPI peripheral. + * + * Copyright (C) 2023, 2024 Paul Boddie + * + * 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; either version 2 of + * the License, or (at your option) any later version. + * + * 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., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA + */ + +#pragma once + +#include +#include +#include + + + +#ifdef __cplusplus + +#include +#include +#include +#include + +/* SPI peripheral channel. */ + +class Spi_channel : public Spi_channel_base +{ + Hw::Register_block<32> _regs; + + /* Initialisation parameters. */ + + l4_addr_t _spi_start; + enum Clock_identifiers _clock; + Cpm_chip *_cpm; + Dma_channel *_dma; + int _request_type; + uint64_t _frequency; + + /* Common utilities. */ + + void configure_transfer(uint8_t char_size); + void wait_busy(); + +public: + explicit Spi_channel(l4_addr_t spi_start, l4_addr_t start, + enum Clock_identifiers clock, + Cpm_chip *cpm, + Dma_channel *dma, + int request_type, + uint64_t frequency); + + /* Convenience operations. */ + + virtual uint32_t send(uint32_t bytes, const uint8_t data[]); + + virtual uint32_t send_dc(uint32_t bytes, const uint8_t data[], const int dc[], + uint8_t char_size, bool big_endian = true); + + uint32_t send_units(uint32_t bytes, const uint8_t data[], uint8_t unit_size, + uint8_t char_size, bool big_endian = true); + + /* DMA operations. */ + + uint32_t transfer(l4_addr_t vaddr, l4re_dma_space_dma_addr_t paddr, + uint32_t count, uint8_t unit_size, uint8_t char_size, + l4_addr_t desc_vaddr = 0, + l4re_dma_space_dma_addr_t desc_paddr = 0); +}; + +/* SPI peripheral. */ + +class Spi_chip +{ +protected: + l4_addr_t _spi_start, _start, _end; + Cpm_chip *_cpm; + + virtual unsigned int num_channels() = 0; + + virtual Spi_channel *_get_channel(uint8_t channel, Dma_channel *dma, + uint64_t frequency) = 0; + +public: + explicit Spi_chip(l4_addr_t spi_start, l4_addr_t start, l4_addr_t end, Cpm_chip *cpm); + + Spi_channel *get_channel(uint8_t channel, Dma_channel *dma, uint64_t frequency); +}; + +#endif /* __cplusplus */ diff -r bf3aeeccde09 -r 7b56ac3b3ef4 pkg/devices/lib/spi/include/spi-jz4780.h --- a/pkg/devices/lib/spi/include/spi-jz4780.h Fri Jun 07 16:08:15 2024 +0200 +++ b/pkg/devices/lib/spi/include/spi-jz4780.h Fri Jun 07 16:12:32 2024 +0200 @@ -29,32 +29,13 @@ #ifdef __cplusplus -#include #include -#include -#include +#include /* SPI peripheral channel. */ -class Spi_jz4780_channel : public Spi_channel_base +class Spi_jz4780_channel : public Spi_channel { - Hw::Register_block<32> _regs; - - /* Initialisation parameters. */ - - l4_addr_t _spi_start; - enum Clock_identifiers _clock; - Cpm_chip *_cpm; - Dma_channel *_dma; - enum Dma_jz4780_request_type _request_type; - uint64_t _frequency; - - /* Common utilities. */ - - void configure_transfer(uint8_t char_size); - - void wait_busy(); - public: explicit Spi_jz4780_channel(l4_addr_t spi_start, l4_addr_t start, enum Clock_identifiers clock, @@ -62,37 +43,21 @@ Dma_channel *dma, enum Dma_jz4780_request_type request_type, uint64_t frequency); - - /* Convenience operations. */ - - virtual uint32_t send(uint32_t bytes, const uint8_t data[]); - - virtual uint32_t send_dc(uint32_t bytes, const uint8_t data[], const int dc[], - uint8_t char_size, bool big_endian = true); - - uint32_t send_units(uint32_t bytes, const uint8_t data[], uint8_t unit_size, - uint8_t char_size, bool big_endian = true); - - /* DMA operations. */ - - uint32_t transfer(l4_addr_t vaddr, l4re_dma_space_dma_addr_t paddr, - uint32_t count, uint8_t unit_size, uint8_t char_size, - l4_addr_t desc_vaddr = 0, - l4re_dma_space_dma_addr_t desc_paddr = 0); }; /* SPI peripheral. */ -class Spi_jz4780_chip +class Spi_jz4780_chip : public Spi_chip { -private: - l4_addr_t _spi_start, _start, _end; - Cpm_chip *_cpm; +protected: + unsigned int num_channels() + { return 2; } + + Spi_channel *_get_channel(uint8_t channel, Dma_channel *dma, + uint64_t frequency); public: explicit Spi_jz4780_chip(l4_addr_t spi_start, l4_addr_t start, l4_addr_t end, Cpm_chip *cpm); - - Spi_jz4780_channel *get_channel(uint8_t channel, Dma_channel *dma, uint64_t frequency); }; #endif /* __cplusplus */ diff -r bf3aeeccde09 -r 7b56ac3b3ef4 pkg/devices/lib/spi/include/spi-x1600.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/devices/lib/spi/include/spi-x1600.h Fri Jun 07 16:12:32 2024 +0200 @@ -0,0 +1,94 @@ +/* + * Perform SPI communication using the X1600 SPI peripheral. + * + * Copyright (C) 2023, 2024 Paul Boddie + * + * 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; either version 2 of + * the License, or (at your option) any later version. + * + * 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., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA + */ + +#pragma once + +#include +#include +#include + + + +#ifdef __cplusplus + +#include +#include + +/* SPI peripheral channel. */ + +class Spi_x1600_channel : public Spi_channel +{ +public: + explicit Spi_x1600_channel(l4_addr_t spi_start, l4_addr_t start, + enum Clock_identifiers clock, + Cpm_chip *cpm, + Dma_channel *dma, + enum Dma_x1600_request_type request_type, + uint64_t frequency); +}; + +/* SPI peripheral. */ + +class Spi_x1600_chip : public Spi_chip +{ +protected: + unsigned int num_channels() + { return 1; } + + Spi_channel *_get_channel(uint8_t channel, Dma_channel *dma, + uint64_t frequency); + +public: + explicit Spi_x1600_chip(l4_addr_t spi_start, l4_addr_t start, l4_addr_t end, Cpm_chip *cpm); +}; + +#endif /* __cplusplus */ + + + +/* C language interface. */ + +EXTERN_C_BEGIN + +void *x1600_spi_init(l4_addr_t spi_start, l4_addr_t start, l4_addr_t end, + void *cpm); + +void *x1600_spi_get_channel(void *spi, uint8_t channel, void *dma, uint64_t frequency); + +uint32_t x1600_spi_send(void *channel, uint32_t bytes, const uint8_t data[]); + +uint32_t x1600_spi_send_dc(void *channel, uint32_t bytes, const uint8_t data[], + const int dc[], uint8_t char_size, int big_endian); + +uint32_t x1600_spi_send_units(void *channel, uint32_t bytes, const uint8_t data[], + uint8_t unit_size, uint8_t char_size, int big_endian); + +uint32_t x1600_spi_transfer(void *channel, l4_addr_t vaddr, + l4re_dma_space_dma_addr_t paddr, uint32_t count, + uint8_t unit_size, uint8_t char_size); + +uint32_t x1600_spi_transfer_descriptor(void *channel, l4_addr_t vaddr, + l4re_dma_space_dma_addr_t paddr, + uint32_t count, uint8_t unit_size, + uint8_t char_size, l4_addr_t desc_vaddr, + l4re_dma_space_dma_addr_t desc_paddr); + +EXTERN_C_END diff -r bf3aeeccde09 -r 7b56ac3b3ef4 pkg/devices/lib/spi/include/spi.h --- a/pkg/devices/lib/spi/include/spi.h Fri Jun 07 16:08:15 2024 +0200 +++ b/pkg/devices/lib/spi/include/spi.h Fri Jun 07 16:12:32 2024 +0200 @@ -30,9 +30,23 @@ /* SPI channel abstractions. */ +class Spi_control_base +{ +public: + virtual void acquire_control(bool asserted) = 0; + + virtual void release_control() = 0; +}; + class Spi_channel_base { +protected: + Spi_control_base *_control = NULL; + public: + void set_control(Spi_control_base *control) + { _control = control; } + virtual uint32_t send(uint32_t bytes, const uint8_t data[]) = 0; virtual uint32_t send_dc(uint32_t bytes, const uint8_t data[], @@ -49,12 +63,4 @@ l4re_dma_space_dma_addr_t desc_paddr = 0) = 0; }; -class Spi_control_base -{ -public: - virtual void acquire_control(bool asserted) = 0; - - virtual void release_control() = 0; -}; - #endif /* __cplusplus */ diff -r bf3aeeccde09 -r 7b56ac3b3ef4 pkg/devices/lib/spi/src/Makefile --- a/pkg/devices/lib/spi/src/Makefile Fri Jun 07 16:08:15 2024 +0200 +++ b/pkg/devices/lib/spi/src/Makefile Fri Jun 07 16:12:32 2024 +0200 @@ -4,7 +4,7 @@ TARGET = libspi.o.a libspi.o.so PC_FILENAME := libdrivers-spi -SRC_CC := gpio.cc hybrid.cc jz4780.cc +SRC_CC := common.cc gpio.cc hybrid.cc jz4780.cc x1600.cc PRIVATE_INCDIR += $(PKGDIR)/lib/spi/include diff -r bf3aeeccde09 -r 7b56ac3b3ef4 pkg/devices/lib/spi/src/common.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/devices/lib/spi/src/common.cc Fri Jun 07 16:12:32 2024 +0200 @@ -0,0 +1,465 @@ +/* + * Perform SPI communication using the JZ4780/X1600 SPI peripheral. + * + * Copyright (C) 2023, 2024 Paul Boddie + * + * 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; either version 2 of + * the License, or (at your option) any later version. + * + * 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., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA + */ + +#include +#include +#include +#include +#include +#include + + + +/* Register definitions. */ + +enum Regs +{ + Ssi_data = 0x00, // SSIDR + Ssi_control0 = 0x04, // SSICR0 + Ssi_control1 = 0x08, // SSICR1 + Ssi_status = 0x0c, // SSISR + Ssi_interval_time = 0x10, // SSIITR + Ssi_char_per_frame = 0x14, // SSIICR + Ssi_clock = 0x18, // SSICGR + Ssi_recv_counter = 0x1c, // SSIRCNT +}; + +enum Ssi_data_bits : unsigned +{ + Ssi_data_gpc_set = 0x10000, + Ssi_data_gpc_unset = 0x00000, +}; + +enum Ssi_control0_bits : unsigned +{ + Ssi_trans_endian_mask = 0xc0000, + Ssi_trans_endian_msbyte_msbit = 0x00000, + Ssi_trans_endian_msbyte_lsbit = 0x40000, + Ssi_trans_endian_lsbyte_lsbit = 0x80000, + Ssi_trans_endian_lsbyte_msbit = 0xc0000, + + Ssi_recv_endian_mask = 0x30000, + Ssi_recv_endian_msbyte_msbit = 0x00000, + Ssi_recv_endian_msbyte_lsbit = 0x10000, + Ssi_recv_endian_lsbyte_lsbit = 0x20000, + Ssi_recv_endian_lsbyte_msbit = 0x30000, + + Ssi_enable = 0x08000, + Ssi_enable_trans_half_empty = 0x04000, + Ssi_enable_recv_half_full = 0x02000, + Ssi_enable_trans_error = 0x01000, + Ssi_enable_recv_error = 0x00800, + Ssi_loopback = 0x00400, + Ssi_recv_finish_control = 0x00200, + Ssi_recv_finished = 0x00100, + Ssi_enable_auto_clear_underrun = 0x00080, + Ssi_select_pin_is_ce2 = 0x00040, + Ssi_use_recv_count = 0x00010, + Ssi_old_fifo_empty_mode = 0x00008, + Ssi_trans_flush = 0x00004, + Ssi_recv_flush = 0x00002, + Ssi_disable_recv = 0x00001, +}; + +enum Ssi_control1_bits : unsigned +{ + Ssi_active_mask = 0xc0000000, + Ssi_active_ce_low = 0x00000000, + Ssi_active_ce_high = 0x40000000, + Ssi_active_ce2_low = 0x00000000, + Ssi_active_ce2_high = 0x80000000, + + Ssi_clock_start_delay_mask = 0x30000000, + Ssi_clock_start_delay_default = 0x00000000, + Ssi_clock_start_delay_plus_1 = 0x10000000, + Ssi_clock_start_delay_plus_2 = 0x20000000, + Ssi_clock_start_delay_plus_3 = 0x30000000, + + Ssi_clock_stop_delay_mask = 0x0c000000, + Ssi_clock_stop_delay_default = 0x00000000, + Ssi_clock_stop_delay_plus_1 = 0x04000000, + Ssi_clock_stop_delay_plus_2 = 0x08000000, + Ssi_clock_stop_delay_plus_3 = 0x0c000000, + + /* X1600... */ + + Ssi_gpc_level_from_gpc_bit = 0x00000000, + Ssi_gpc_level_from_gpc_level = 0x02000000, + + /* Common... */ + + Ssi_interval_assert_ce_or_ce2 = 0x01000000, + Ssi_trans_empty_unfinished = 0x00800000, + + Ssi_format_mask = 0x00300000, + Ssi_format_spi = 0x00000000, + Ssi_format_ssp = 0x00100000, + Ssi_format_microwire1 = 0x00200000, + Ssi_format_microwire2 = 0x00300000, + + Ssi_trans_threshold_mask = 0x000f0000, + Ssi_command_length_mask = 0x0000f000, + Ssi_recv_threshold_mask = 0x00000f00, + Ssi_char_length_mask = 0x000000f8, + + /* X1600... */ + + Ssi_gpc_level = 0x00000004, // see Ssi_gpc_level_from_gpc_level + + /* Common... */ + + Spi_clock_assert_sample = 0x00000000, // phase #0 + Spi_clock_assert_drive = 0x00000002, // phase #1 + Spi_clock_idle_low_level = 0x00000000, // polarity #0 + Spi_clock_idle_high_level = 0x00000001, // polarity #1 +}; + +enum Ssi_control1_shifts : unsigned +{ + Ssi_trans_threshold_shift = 16, + Ssi_command_length_shift = 12, + Ssi_recv_threshold_shift = 8, + Ssi_char_length_shift = 3, +}; + +enum Ssi_control1_limits : unsigned +{ + Ssi_trans_threshold_limit = 15, + Ssi_command_length_limit = 15, + Ssi_recv_threshold_limit = 15, + Ssi_char_length_limit = 30, +}; + +enum Ssi_status_bits : unsigned +{ + Ssi_trans_char_count_mask = 0x00ff0000, + Ssi_recv_char_count_mask = 0x0000ff00, + Ssi_trans_ended = 0x00000080, + Ssi_trans_busy = 0x00000040, + Ssi_trans_fifo_full = 0x00000020, + Ssi_recv_fifo_empty = 0x00000010, + Ssi_trans_fifo_half_empty = 0x00000008, + Ssi_recv_fifo_half_full = 0x00000004, + Ssi_trans_underrun = 0x00000002, + Ssi_recv_overrun = 0x00000001, +}; + +enum Ssi_status_shifts : unsigned +{ + Ssi_trans_char_count_shift = 16, + Ssi_recv_char_count_shift = 8, +}; + +enum Ssi_status_limits : unsigned +{ + Ssi_trans_char_count_limit = 0xff, + Ssi_recv_char_count_limit = 0xff, +}; + +enum Ssi_interval_time_bits : unsigned +{ + Ssi_interval_clock_mask = 0x8000, + Ssi_interval_clock_bit_clock = 0x0000, + Ssi_interval_clock_32k_clock = 0x8000, + Ssi_interval_time_mask = 0x3fff, +}; + +enum Ssi_char_per_frame_bits : unsigned +{ + Ssi_char_per_frame_mask = 0x7, +}; + +enum Ssi_clock_bits : unsigned +{ + Ssi_clock_frequency_mask = 0xff, +}; + +enum Ssi_recv_counter_bits : unsigned +{ + Ssi_recv_counter_mask = 0xffff, +}; + + + +/* Initialise a channel. */ + +Spi_channel::Spi_channel(l4_addr_t spi_start, l4_addr_t start, + enum Clock_identifiers clock, + Cpm_chip *cpm, + Dma_channel *dma, + int request_type, + uint64_t frequency) +: _spi_start(spi_start), _clock(clock), _cpm(cpm), _dma(dma), + _request_type(request_type), _frequency(frequency) +{ + _regs = new Hw::Mmio_register_block<32>(start); + _cpm->start_clock(clock); + + /* Disable the channel while configuring: send MSB first, big endian wire + representation. Disable reception. */ + + _regs[Ssi_control0] = Ssi_trans_endian_msbyte_msbit | + Ssi_select_pin_is_ce2 | + Ssi_disable_recv; + + /* Set default transfer properties. */ + + configure_transfer(8); + + /* Select "normal" mode. */ + + _regs[Ssi_interval_time] = 0; + + /* Limit the frequency to half that of the device clock. */ + + if (_frequency >= _cpm->get_frequency(_clock)) + _frequency = _cpm->get_frequency(_clock) / 2; + + /* SSI_CLK = DEV_CLK / (2 * (divider + 1)) */ + + uint32_t divider = _cpm->get_frequency(_clock) / (_frequency * 2) - 1; + + _regs[Ssi_clock] = divider < Ssi_clock_frequency_mask ? divider : Ssi_clock_frequency_mask; + + /* Enable the channel. */ + + _regs[Ssi_control0] = _regs[Ssi_control0] | Ssi_enable; +} + +/* NOTE: More transfer characteristics should be configurable. */ + +void Spi_channel::configure_transfer(uint8_t char_size) +{ + uint32_t char_length; + + if (char_size < 2) + char_length = 0; + else + { + char_length = char_size - 2; + + if (char_size > Ssi_char_length_limit) + char_length = Ssi_char_length_limit; + } + + /* Clear the status. */ + + _regs[Ssi_control0] = _regs[Ssi_control0] | Ssi_trans_flush | Ssi_recv_flush; + _regs[Ssi_status] = 0; + + /* Indicate the desired character size. + + Use active low device selection, SPI format with active low clock, with + data driven on the falling (asserted) clock and sampled on the rising + clock. */ + + _regs[Ssi_control1] = (char_length << Ssi_char_length_shift) | + ((Ssi_trans_threshold_limit / 2) << Ssi_trans_threshold_shift) | + Ssi_format_spi | Ssi_active_ce2_low | + Spi_clock_assert_sample | Spi_clock_idle_low_level; +} + +/* Transfer the given number of bytes from a buffer. */ + +uint32_t +Spi_channel::send(uint32_t bytes, const uint8_t data[]) +{ + return send_units(bytes, data, 1, 8, false); +} + +/* Transfer the given number of bytes from a buffer together with control + values. Return the number of bytes transferred. */ + +uint32_t +Spi_channel::send_dc(uint32_t bytes, const uint8_t data[], + const int dc[], uint8_t char_size, bool big_endian) +{ + configure_transfer(char_size); + + uint32_t transferred, char_unit; + uint8_t char_unit_size = ((char_size ? char_size - 1 : 0) / 8) + 1; + uint32_t char_mask = (1 << char_size) - 1; + bool last_control; + + for (transferred = 0, char_unit = 0; transferred < bytes; + transferred += char_unit_size, char_unit++) + { + uint32_t value = get_stored_value(&data[transferred], char_unit_size, big_endian); + + /* Relocate the data/command level to bit 16. */ + + uint32_t command = dc[char_unit] ? Ssi_data_gpc_set : Ssi_data_gpc_unset; + + /* Wait if the FIFO is full before sending. */ + + while (_regs[Ssi_status] & Ssi_trans_fifo_full); + + /* Combine the character with the data/command bit. */ + + if (_control == NULL) + _regs[Ssi_data] = (value & char_mask) | command; + else + { + /* Set the control level after waiting for sending to complete to ensure + synchronisation. */ + + bool this_control = dc[char_unit]; + + if (transferred && (this_control != last_control)) + wait_busy(); + + _control->acquire_control(this_control ? 1 : 0); + _regs[Ssi_data] = (value & char_mask); + last_control = this_control; + } + } + + wait_busy(); + + return transferred; +} + +/* Transfer the given number of bytes from a buffer using the given unit size in + bytes and character size in bits. The bytes are stored in a big endian + arrangement. Return the number of bytes transferred. */ + +uint32_t +Spi_channel::send_units(uint32_t bytes, const uint8_t data[], + uint8_t unit_size, uint8_t char_size, + bool big_endian) +{ + configure_transfer(char_size); + + uint32_t transferred; + uint32_t char_mask = (1 << char_size) - 1; + bool last_control; + + for (transferred = 0; transferred < bytes; transferred += unit_size) + { + uint32_t value = get_stored_value(&data[transferred], unit_size, big_endian); + + /* Relocate any command bit to bit 16 for byte characters. */ + + bool data_only = unit_size * 8 == char_size; + bool gpc_set = (char_size < 16) && (value & (1 << char_size)); + uint32_t command = gpc_set ? Ssi_data_gpc_set : Ssi_data_gpc_unset; + + /* Wait if the FIFO is full before sending. */ + + while (_regs[Ssi_status] & Ssi_trans_fifo_full); + + /* Combine the character portion of the unit with the command. */ + + if (_control == NULL) + _regs[Ssi_data] = (value & char_mask) | command; + else + { + /* Set the control level after waiting for sending to complete to ensure + synchronisation. */ + + bool this_control = gpc_set || data_only; + + if (transferred && (this_control != last_control)) + wait_busy(); + + _control->acquire_control(this_control ? 1 : 0); + _regs[Ssi_data] = (value & char_mask); + last_control = this_control; + } + } + + wait_busy(); + + return transferred; +} + +/* Transfer the given number of bytes from a DMA region using the given + unit size in bytes and character size in bits. Return the number of bytes + transferred. */ + +uint32_t +Spi_channel::transfer(l4_addr_t vaddr, + l4re_dma_space_dma_addr_t paddr, + uint32_t count, uint8_t unit_size, + uint8_t char_size, + l4_addr_t desc_vaddr, + l4re_dma_space_dma_addr_t desc_paddr) +{ + /* Employ a non-DMA transfer if no usable physical address is provided. + Assume little endian byte ordering in line with the native value + representation. */ + + if (!paddr) + return send_units(count, (const uint8_t *) vaddr, unit_size, char_size, + false); + + /* Configure and initiate a DMA transfer with optional descriptor. */ + + configure_transfer(char_size); + + uint32_t transferred = 0; + uint32_t unit_count = count / unit_size; + uint32_t to_transfer = _dma->transfer(paddr, _spi_start + Ssi_data, + unit_count, true, false, + unit_size, unit_size, unit_size, + _request_type, desc_vaddr, desc_paddr); + + /* Wait if not using a descriptor, which could be configured in a cycle to + cause an endless, repeating transfer, perhaps updating a display, for + example. */ + + if (to_transfer && !desc_vaddr) + { + transferred = to_transfer ? (unit_count - _dma->wait()) * unit_size : 0; + wait_busy(); + } + else + transferred = to_transfer * unit_size; + + return transferred; +} + +/* Wait for the busy condition to clear or for a limited period. */ + +void +Spi_channel::wait_busy() +{ + for (unsigned int i = 0; i < (1 << 20) && (_regs[Ssi_status] & Ssi_trans_busy); i++); +} + + + +/* Initialise the peripheral abstraction. */ + +Spi_chip::Spi_chip(l4_addr_t spi_start, l4_addr_t start, l4_addr_t end, + Cpm_chip *cpm) +: _spi_start(spi_start), _start(start), _end(end), _cpm(cpm) +{ +} + +Spi_channel * +Spi_chip::get_channel(uint8_t channel, Dma_channel *dma, uint64_t frequency) +{ + if (channel < num_channels()) + return _get_channel(channel, dma, frequency); + else + throw -L4_EINVAL; +} diff -r bf3aeeccde09 -r 7b56ac3b3ef4 pkg/devices/lib/spi/src/gpio.cc --- a/pkg/devices/lib/spi/src/gpio.cc Fri Jun 07 16:08:15 2024 +0200 +++ b/pkg/devices/lib/spi/src/gpio.cc Fri Jun 07 16:12:32 2024 +0200 @@ -83,7 +83,7 @@ /* Initialise pin levels. */ _enable_device->set(_enable_pin, 1); - _clock_device->set(_clock_pin, 1); + _clock_device->set(_clock_pin, 0); _data_device->set(_data_pin, 0); if ((_control_device != NULL) && (_control_pin >= 0) && (dc != NULL)) @@ -109,8 +109,8 @@ for (uint8_t bit = 0; bit < char_size; bit++) { /* NOTE: Data presented on falling clock level and sampled on rising clock - level. This is SPI mode 3, or 0 given that the enable level is - driven low immediately before the first bit is presented. */ + level. This is SPI mode 0 given that the enable level is driven + low immediately before the first bit is presented. */ _clock_device->set(_clock_pin, 0); _data_device->set(_data_pin, value & mask ? 1 : 0); @@ -128,6 +128,7 @@ } _enable_device->set(_enable_pin, 1); + _clock_device->set(_clock_pin, 0); return bytes; } diff -r bf3aeeccde09 -r 7b56ac3b3ef4 pkg/devices/lib/spi/src/hybrid.cc --- a/pkg/devices/lib/spi/src/hybrid.cc Fri Jun 07 16:08:15 2024 +0200 +++ b/pkg/devices/lib/spi/src/hybrid.cc Fri Jun 07 16:12:32 2024 +0200 @@ -32,6 +32,10 @@ _control_pin(control_pin), _control_alt_func(control_alt_func) { + /* Provide this control interface to the channel. */ + + if (_control_alt_func < 0) + _channel->set_control(this); } Spi_channel_base *Spi_hybrid::get_channel() diff -r bf3aeeccde09 -r 7b56ac3b3ef4 pkg/devices/lib/spi/src/jz4780.cc --- a/pkg/devices/lib/spi/src/jz4780.cc Fri Jun 07 16:08:15 2024 +0200 +++ b/pkg/devices/lib/spi/src/jz4780.cc Fri Jun 07 16:12:32 2024 +0200 @@ -1,5 +1,5 @@ /* - * Perform SPI communication using the JZ4780/X1600 SPI peripheral. + * Perform SPI communication using the JZ4780 SPI peripheral. * * Copyright (C) 2023, 2024 Paul Boddie * @@ -19,187 +19,15 @@ * Boston, MA 02110-1301, USA */ -#include -#include #include -#include -#include - - - -/* Register definitions. */ -enum Regs +enum Regs_jz4780 { - Ssi_data = 0x00, // SSIDR - Ssi_control0 = 0x04, // SSICR0 - Ssi_control1 = 0x08, // SSICR1 - Ssi_status = 0x0c, // SSISR - Ssi_interval_time = 0x10, // SSIITR - Ssi_char_per_frame = 0x14, // SSIICR - Ssi_clock = 0x18, // SSICGR - Ssi_recv_counter = 0x1c, // SSIRCNT - /* Register block offset. */ Ssi_block_offset = 0x1000, }; -enum Ssi_data_bits : unsigned -{ - Ssi_data_gpc_set = 0x10000, - Ssi_data_gpc_unset = 0x00000, -}; - -enum Ssi_control0_bits : unsigned -{ - Ssi_trans_endian_mask = 0xc0000, - Ssi_trans_endian_msbyte_msbit = 0x00000, - Ssi_trans_endian_msbyte_lsbit = 0x40000, - Ssi_trans_endian_lsbyte_lsbit = 0x80000, - Ssi_trans_endian_lsbyte_msbit = 0xc0000, - - Ssi_recv_endian_mask = 0x30000, - Ssi_recv_endian_msbyte_msbit = 0x00000, - Ssi_recv_endian_msbyte_lsbit = 0x10000, - Ssi_recv_endian_lsbyte_lsbit = 0x20000, - Ssi_recv_endian_lsbyte_msbit = 0x30000, - - Ssi_enable = 0x08000, - Ssi_enable_trans_half_empty = 0x04000, - Ssi_enable_recv_half_full = 0x02000, - Ssi_enable_trans_error = 0x01000, - Ssi_enable_recv_error = 0x00800, - Ssi_loopback = 0x00400, - Ssi_recv_finish_control = 0x00200, - Ssi_recv_finished = 0x00100, - Ssi_enable_auto_clear_underrun = 0x00080, - Ssi_select_pin_is_ce2 = 0x00040, - Ssi_use_recv_count = 0x00010, - Ssi_old_fifo_empty_mode = 0x00008, - Ssi_trans_flush = 0x00004, - Ssi_recv_flush = 0x00002, - Ssi_disable_recv = 0x00001, -}; - -enum Ssi_control1_bits : unsigned -{ - Ssi_active_mask = 0xc0000000, - Ssi_active_ce_low = 0x00000000, - Ssi_active_ce_high = 0x40000000, - Ssi_active_ce2_low = 0x00000000, - Ssi_active_ce2_high = 0x80000000, - - Ssi_clock_start_delay_mask = 0x30000000, - Ssi_clock_start_delay_default = 0x00000000, - Ssi_clock_start_delay_plus_1 = 0x10000000, - Ssi_clock_start_delay_plus_2 = 0x20000000, - Ssi_clock_start_delay_plus_3 = 0x30000000, - - Ssi_clock_stop_delay_mask = 0x0c000000, - Ssi_clock_stop_delay_default = 0x00000000, - Ssi_clock_stop_delay_plus_1 = 0x04000000, - Ssi_clock_stop_delay_plus_2 = 0x08000000, - Ssi_clock_stop_delay_plus_3 = 0x0c000000, - - /* X1600... */ - - Ssi_gpc_level_from_gpc_bit = 0x00000000, - Ssi_gpc_level_from_gpc_level = 0x02000000, - - /* Common... */ - - Ssi_interval_assert_ce_or_ce2 = 0x01000000, - Ssi_trans_empty_unfinished = 0x00800000, - - Ssi_format_mask = 0x00300000, - Ssi_format_spi = 0x00000000, - Ssi_format_ssp = 0x00100000, - Ssi_format_microwire1 = 0x00200000, - Ssi_format_microwire2 = 0x00300000, - - Ssi_trans_threshold_mask = 0x000f0000, - Ssi_command_length_mask = 0x0000f000, - Ssi_recv_threshold_mask = 0x00000f00, - Ssi_char_length_mask = 0x000000f8, - - /* X1600... */ - - Ssi_gpc_level = 0x00000004, // see Ssi_gpc_level_from_gpc_level - - /* Common... */ - - Spi_clock_assert_sample = 0x00000000, // phase #0 - Spi_clock_assert_drive = 0x00000002, // phase #1 - Spi_clock_idle_low_level = 0x00000000, // polarity #0 - Spi_clock_idle_high_level = 0x00000001, // polarity #1 -}; - -enum Ssi_control1_shifts : unsigned -{ - Ssi_trans_threshold_shift = 16, - Ssi_command_length_shift = 12, - Ssi_recv_threshold_shift = 8, - Ssi_char_length_shift = 3, -}; - -enum Ssi_control1_limits : unsigned -{ - Ssi_trans_threshold_limit = 15, - Ssi_command_length_limit = 15, - Ssi_recv_threshold_limit = 15, - Ssi_char_length_limit = 30, -}; - -enum Ssi_status_bits : unsigned -{ - Ssi_trans_char_count_mask = 0x00ff0000, - Ssi_recv_char_count_mask = 0x0000ff00, - Ssi_trans_ended = 0x00000080, - Ssi_trans_busy = 0x00000040, - Ssi_trans_fifo_full = 0x00000020, - Ssi_recv_fifo_empty = 0x00000010, - Ssi_trans_fifo_half_empty = 0x00000008, - Ssi_recv_fifo_half_full = 0x00000004, - Ssi_trans_underrun = 0x00000002, - Ssi_recv_overrun = 0x00000001, -}; - -enum Ssi_status_shifts : unsigned -{ - Ssi_trans_char_count_shift = 16, - Ssi_recv_char_count_shift = 8, -}; - -enum Ssi_status_limits : unsigned -{ - Ssi_trans_char_count_limit = 0xff, - Ssi_recv_char_count_limit = 0xff, -}; - -enum Ssi_interval_time_bits : unsigned -{ - Ssi_interval_clock_mask = 0x8000, - Ssi_interval_clock_bit_clock = 0x0000, - Ssi_interval_clock_32k_clock = 0x8000, - Ssi_interval_time_mask = 0x3fff, -}; - -enum Ssi_char_per_frame_bits : unsigned -{ - Ssi_char_per_frame_mask = 0x7, -}; - -enum Ssi_clock_bits : unsigned -{ - Ssi_clock_frequency_mask = 0xff, -}; - -enum Ssi_recv_counter_bits : unsigned -{ - Ssi_recv_counter_mask = 0xffff, -}; - /* Initialise a channel. */ @@ -210,205 +38,8 @@ Dma_channel *dma, enum Dma_jz4780_request_type request_type, uint64_t frequency) -: _spi_start(spi_start), _clock(clock), _cpm(cpm), _dma(dma), - _request_type(request_type), _frequency(frequency) -{ - _regs = new Hw::Mmio_register_block<32>(start); - _cpm->start_clock(clock); - - /* Disable the channel while configuring: send MSB first, big endian wire - representation. Disable reception. */ - - _regs[Ssi_control0] = Ssi_trans_endian_msbyte_msbit | - Ssi_select_pin_is_ce2 | - Ssi_disable_recv; - - /* Set default transfer properties. */ - - configure_transfer(8); - - /* Select "normal" mode. */ - - _regs[Ssi_interval_time] = 0; - - /* Limit the frequency to half that of the device clock. */ - - if (_frequency >= _cpm->get_frequency(_clock)) - _frequency = _cpm->get_frequency(_clock) / 2; - - /* SSI_CLK = DEV_CLK / (2 * (divider + 1)) */ - - uint32_t divider = _cpm->get_frequency(_clock) / (_frequency * 2) - 1; - - _regs[Ssi_clock] = divider < Ssi_clock_frequency_mask ? divider : Ssi_clock_frequency_mask; - - /* Enable the channel. */ - - _regs[Ssi_control0] = _regs[Ssi_control0] | Ssi_enable; -} - -/* NOTE: More transfer characteristics should be configurable. */ - -void Spi_jz4780_channel::configure_transfer(uint8_t char_size) -{ - uint32_t char_length; - - if (char_size < 2) - char_length = 0; - else - { - char_length = char_size - 2; - - if (char_size > Ssi_char_length_limit) - char_length = Ssi_char_length_limit; - } - - /* Clear the status. */ - - _regs[Ssi_control0] = _regs[Ssi_control0] | Ssi_trans_flush | Ssi_recv_flush; - _regs[Ssi_status] = 0; - - /* Indicate the desired character size. - - Use active low device selection, SPI format with active low clock, with - data driven on the falling (asserted) clock and sampled on the rising - clock - - The unfinished flag prevents the transaction from finishing if the FIFO is - empty. It is not used here since it appears to prevent any meaningful - testing of the busy and end flags. */ - - _regs[Ssi_control1] = (char_length << Ssi_char_length_shift) | - ((Ssi_trans_threshold_limit / 2) << Ssi_trans_threshold_shift) | - Ssi_format_spi | Ssi_active_ce2_low | - Spi_clock_assert_drive | Spi_clock_idle_high_level; -} - -/* Transfer the given number of bytes from a buffer. */ - -uint32_t -Spi_jz4780_channel::send(uint32_t bytes, const uint8_t data[]) -{ - return send_units(bytes, data, 1, 8, false); -} - -/* Transfer the given number of bytes from a buffer together with control - values. Return the number of bytes transferred. */ - -uint32_t -Spi_jz4780_channel::send_dc(uint32_t bytes, const uint8_t data[], - const int dc[], uint8_t char_size, bool big_endian) +: Spi_channel(spi_start, start, clock, cpm, dma, request_type, frequency) { - configure_transfer(char_size); - - uint32_t transferred, char_unit; - uint8_t char_unit_size = ((char_size ? char_size - 1 : 0) / 8) + 1; - uint32_t char_mask = (1 << char_size) - 1; - - for (transferred = 0, char_unit = 0; transferred < bytes; - transferred += char_unit_size, char_unit++) - { - uint32_t value = get_stored_value(&data[transferred], char_unit_size, big_endian); - - /* Relocate the data/command level to bit 16. */ - - uint32_t command = dc[char_unit] ? Ssi_data_gpc_set : Ssi_data_gpc_unset; - - /* Combine the character with the data/command bit. */ - - _regs[Ssi_data] = (value & char_mask) | command; - } - - wait_busy(); - - return transferred; -} - -/* Transfer the given number of bytes from a buffer using the given unit size in - bytes and character size in bits. The bytes are stored in a big endian - arrangement. Return the number of bytes transferred. */ - -uint32_t -Spi_jz4780_channel::send_units(uint32_t bytes, const uint8_t data[], - uint8_t unit_size, uint8_t char_size, - bool big_endian) -{ - configure_transfer(char_size); - - uint32_t transferred; - uint32_t char_mask = (1 << char_size) - 1; - - for (transferred = 0; transferred < bytes; transferred += unit_size) - { - uint32_t value = get_stored_value(&data[transferred], unit_size, big_endian); - - /* Relocate any command bit to bit 16 for byte characters. */ - - uint32_t command = (char_size < 16) && (value & (1 << char_size)) - ? Ssi_data_gpc_set : Ssi_data_gpc_unset; - - /* Combine the character portion of the unit with the command. */ - - _regs[Ssi_data] = (value & char_mask) | command; - } - - wait_busy(); - - return transferred; -} - -/* Transfer the given number of bytes from a DMA region using the given - unit size in bytes and character size in bits. Return the number of bytes - transferred. */ - -uint32_t -Spi_jz4780_channel::transfer(l4_addr_t vaddr, - l4re_dma_space_dma_addr_t paddr, - uint32_t count, uint8_t unit_size, - uint8_t char_size, - l4_addr_t desc_vaddr, - l4re_dma_space_dma_addr_t desc_paddr) -{ - /* Employ a non-DMA transfer if no usable physical address is provided. - Assume little endian byte ordering in line with the native value - representation. */ - - if (!paddr) - return send_units(count, (const uint8_t *) vaddr, unit_size, char_size, - false); - - /* Configure and initiate a DMA transfer with optional descriptor. */ - - configure_transfer(char_size); - - uint32_t transferred = 0; - uint32_t unit_count = count / unit_size; - uint32_t to_transfer = _dma->transfer(paddr, _spi_start + Ssi_data, - unit_count, true, false, - unit_size, unit_size, unit_size, - _request_type, desc_vaddr, desc_paddr); - - /* Wait if not using a descriptor, which could be configured in a cycle to - cause an endless, repeating transfer, perhaps updating a display, for - example. */ - - if (to_transfer && !desc_vaddr) - { - transferred = to_transfer ? (unit_count - _dma->wait()) * unit_size : 0; - wait_busy(); - } - else - transferred = to_transfer * unit_size; - - return transferred; -} - -/* Wait for the busy condition to clear or for a limited period. */ - -void -Spi_jz4780_channel::wait_busy() -{ - for (unsigned int i = 0; i < (1 << 20) && (_regs[Ssi_status] & Ssi_trans_busy); i++); } @@ -417,26 +48,23 @@ Spi_jz4780_chip::Spi_jz4780_chip(l4_addr_t spi_start, l4_addr_t start, l4_addr_t end, Cpm_chip *cpm) -: _spi_start(spi_start), _start(start), _end(end), _cpm(cpm) +: Spi_chip(spi_start, start, end, cpm) { } -Spi_jz4780_channel * -Spi_jz4780_chip::get_channel(uint8_t channel, Dma_channel *dma, - uint64_t frequency) +Spi_channel * +Spi_jz4780_chip::_get_channel(uint8_t channel, Dma_channel *dma, + uint64_t frequency) { // NOTE: Only sending is supported. enum Dma_jz4780_request_type request_types[] = {Dma_request_ssi0_out, Dma_request_ssi1_out}; enum Clock_identifiers clocks[] = {Clock_ssi0, Clock_ssi1}; - if (channel < 2) - return new Spi_jz4780_channel(_spi_start + channel * Ssi_block_offset, - _start + channel * Ssi_block_offset, - clocks[channel], - _cpm, dma, request_types[channel], frequency); - else - throw -L4_EINVAL; + return new Spi_jz4780_channel(_spi_start + channel * Ssi_block_offset, + _start + channel * Ssi_block_offset, + clocks[channel], + _cpm, dma, request_types[channel], frequency); } diff -r bf3aeeccde09 -r 7b56ac3b3ef4 pkg/devices/lib/spi/src/x1600.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/devices/lib/spi/src/x1600.cc Fri Jun 07 16:12:32 2024 +0200 @@ -0,0 +1,111 @@ +/* + * Perform SPI communication using the X1600 SPI peripheral. + * + * Copyright (C) 2023, 2024 Paul Boddie + * + * 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; either version 2 of + * the License, or (at your option) any later version. + * + * 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., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA + */ + +#include + + + +/* Initialise a channel. */ + +Spi_x1600_channel::Spi_x1600_channel(l4_addr_t spi_start, l4_addr_t start, + enum Clock_identifiers clock, + Cpm_chip *cpm, + Dma_channel *dma, + enum Dma_x1600_request_type request_type, + uint64_t frequency) +: Spi_channel(spi_start, start, clock, cpm, dma, request_type, frequency) +{ +} + + + +/* Initialise the peripheral abstraction. */ + +Spi_x1600_chip::Spi_x1600_chip(l4_addr_t spi_start, l4_addr_t start, + l4_addr_t end, Cpm_chip *cpm) +: Spi_chip(spi_start, start, end, cpm) +{ +} + +Spi_channel * +Spi_x1600_chip::_get_channel(uint8_t channel, Dma_channel *dma, + uint64_t frequency) +{ + (void) channel; + + // NOTE: Only sending is supported. + + return new Spi_x1600_channel(_spi_start, _start, Clock_ssi0, + _cpm, dma, Dma_request_ssi0_out, frequency); +} + + + +/* C language interface. */ + +void *x1600_spi_init(l4_addr_t spi_start, l4_addr_t start, l4_addr_t end, void *cpm) +{ + return new Spi_x1600_chip(spi_start, start, end, static_cast(cpm)); +} + +void *x1600_spi_get_channel(void *spi, uint8_t channel, void *dma, uint64_t frequency) +{ + return static_cast(spi)->get_channel(channel, + static_cast(dma), frequency); +} + +uint32_t x1600_spi_send(void *channel, uint32_t bytes, const uint8_t data[]) +{ + return static_cast(channel)->send(bytes, data); +} + +uint32_t x1600_spi_send_dc(void *channel, uint32_t bytes, const uint8_t data[], + const int dc[], uint8_t char_size, int big_endian) +{ + return static_cast(channel)->send_dc(bytes, data, dc, + char_size, big_endian); +} + +uint32_t x1600_spi_send_units(void *channel, uint32_t bytes, const uint8_t data[], + uint8_t unit_size, uint8_t char_size, int big_endian) +{ + return static_cast(channel)->send_units(bytes, data, + unit_size, char_size, big_endian); +} + +uint32_t x1600_spi_transfer(void *channel, l4_addr_t vaddr, + l4re_dma_space_dma_addr_t paddr, + uint32_t count, uint8_t unit_size, + uint8_t char_size) +{ + return static_cast(channel)->transfer(vaddr, paddr, + count, unit_size, char_size); +} + +uint32_t x1600_spi_transfer_descriptor(void *channel, l4_addr_t vaddr, + l4re_dma_space_dma_addr_t paddr, + uint32_t count, uint8_t unit_size, + uint8_t char_size, l4_addr_t desc_vaddr, + l4re_dma_space_dma_addr_t desc_paddr) +{ + return static_cast(channel)->transfer(vaddr, paddr, + count, unit_size, char_size, desc_vaddr, desc_paddr); +}