1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/pkg/devices/lib/spi/src/jz4780.cc Tue Nov 07 19:20:32 2023 +0100
1.3 @@ -0,0 +1,387 @@
1.4 +/*
1.5 + * Perform SPI communication using the JZ4780 SPI peripheral.
1.6 + *
1.7 + * Copyright (C) 2023 Paul Boddie <paul@boddie.org.uk>
1.8 + *
1.9 + * This program is free software; you can redistribute it and/or
1.10 + * modify it under the terms of the GNU General Public License as
1.11 + * published by the Free Software Foundation; either version 2 of
1.12 + * the License, or (at your option) any later version.
1.13 + *
1.14 + * This program is distributed in the hope that it will be useful,
1.15 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
1.16 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1.17 + * GNU General Public License for more details.
1.18 + *
1.19 + * You should have received a copy of the GNU General Public License
1.20 + * along with this program; if not, write to the Free Software
1.21 + * Foundation, Inc., 51 Franklin Street, Fifth Floor,
1.22 + * Boston, MA 02110-1301, USA
1.23 + */
1.24 +
1.25 +#include <l4/devices/spi-jz4780.h>
1.26 +#include <l4/devices/dma.h>
1.27 +#include <l4/sys/err.h>
1.28 +#include <string.h>
1.29 +
1.30 +
1.31 +
1.32 +/* Register definitions. */
1.33 +
1.34 +enum Regs
1.35 +{
1.36 + Ssi_data = 0x00, // SSIDR
1.37 + Ssi_control0 = 0x04, // SSICR0
1.38 + Ssi_control1 = 0x08, // SSICR1
1.39 + Ssi_status = 0x0c, // SSISR
1.40 + Ssi_interval_time = 0x10, // SSIITR
1.41 + Ssi_char_per_frame = 0x14, // SSIICR
1.42 + Ssi_clock = 0x18, // SSICGR
1.43 + Ssi_recv_counter = 0x1c, // SSIRCNT
1.44 +
1.45 + /* Register block offset. */
1.46 +
1.47 + Ssi_block_offset = 0x1000,
1.48 +};
1.49 +
1.50 +enum Ssi_data_bits : unsigned
1.51 +{
1.52 + Ssi_data_gpc = 0x1000,
1.53 +};
1.54 +
1.55 +enum Ssi_control0_bits : unsigned
1.56 +{
1.57 + Ssi_trans_endian_mask = 0xc0000,
1.58 + Ssi_trans_endian_msbyte_msbit = 0x00000,
1.59 + Ssi_trans_endian_msbyte_lsbit = 0x40000,
1.60 + Ssi_trans_endian_lsbyte_lsbit = 0x80000,
1.61 + Ssi_trans_endian_lsbyte_msbit = 0xc0000,
1.62 +
1.63 + Ssi_recv_endian_mask = 0x30000,
1.64 + Ssi_recv_endian_msbyte_msbit = 0x00000,
1.65 + Ssi_recv_endian_msbyte_lsbit = 0x10000,
1.66 + Ssi_recv_endian_lsbyte_lsbit = 0x20000,
1.67 + Ssi_recv_endian_lsbyte_msbit = 0x30000,
1.68 +
1.69 + Ssi_enable = 0x08000,
1.70 + Ssi_enable_trans_half_empty = 0x04000,
1.71 + Ssi_enable_recv_half_full = 0x02000,
1.72 + Ssi_enable_trans_error = 0x01000,
1.73 + Ssi_enable_recv_error = 0x00800,
1.74 + Ssi_loop = 0x00400,
1.75 + Ssi_recv_finish_control = 0x00200,
1.76 + Ssi_recv_finished = 0x00100,
1.77 + Ssi_enable_auto_clear_underrun = 0x00080,
1.78 + Ssi_select_pin_is_ce2 = 0x00040,
1.79 + Ssi_use_recv_count = 0x00010,
1.80 + Ssi_old_fifo_empty_mode = 0x00008,
1.81 + Ssi_trans_flush = 0x00004,
1.82 + Ssi_recv_flush = 0x00002,
1.83 + Ssi_disable_recv = 0x00001,
1.84 +};
1.85 +
1.86 +enum Ssi_control1_bits : unsigned
1.87 +{
1.88 + Ssi_active_mask = 0xc0000000,
1.89 + Ssi_active_ce_low = 0x00000000,
1.90 + Ssi_active_ce_high = 0x40000000,
1.91 + Ssi_active_ce2_low = 0x00000000,
1.92 + Ssi_active_ce2_high = 0x80000000,
1.93 +
1.94 + Ssi_clock_start_delay_mask = 0x30000000,
1.95 + Ssi_clock_start_delay_default = 0x00000000,
1.96 + Ssi_clock_start_delay_plus_1 = 0x10000000,
1.97 + Ssi_clock_start_delay_plus_2 = 0x20000000,
1.98 + Ssi_clock_start_delay_plus_3 = 0x30000000,
1.99 +
1.100 + Ssi_clock_stop_delay_mask = 0x0c000000,
1.101 + Ssi_clock_stop_delay_default = 0x00000000,
1.102 + Ssi_clock_stop_delay_plus_1 = 0x04000000,
1.103 + Ssi_clock_stop_delay_plus_2 = 0x08000000,
1.104 + Ssi_clock_stop_delay_plus_3 = 0x0c000000,
1.105 +
1.106 + Ssi_interval_assert_ce_or_ce2 = 0x01000000,
1.107 + Ssi_trans_empty_unfinished = 0x00800000,
1.108 +
1.109 + Ssi_format_mask = 0x00300000,
1.110 + Ssi_format_spi = 0x00000000,
1.111 + Ssi_format_ssp = 0x00100000,
1.112 + Ssi_format_microwire1 = 0x00200000,
1.113 + Ssi_format_microwire2 = 0x00300000,
1.114 +
1.115 + Ssi_trans_threshold_mask = 0x000f0000,
1.116 + Ssi_command_length_mask = 0x0000f000,
1.117 + Ssi_recv_threshold_mask = 0x00000f00,
1.118 + Ssi_char_length_mask = 0x000000f8,
1.119 +
1.120 + Spi_clock_assert_sample = 0x00000000, // phase #0
1.121 + Spi_clock_assert_drive = 0x00000002, // phase #1
1.122 + Spi_clock_idle_low_level = 0x00000000, // polarity #0
1.123 + Spi_clock_idle_high_level = 0x00000001, // polarity #1
1.124 +};
1.125 +
1.126 +enum Ssi_control1_shifts : unsigned
1.127 +{
1.128 + Ssi_trans_threshold_shift = 16,
1.129 + Ssi_command_length_shift = 12,
1.130 + Ssi_recv_threshold_shift = 8,
1.131 + Ssi_char_length_shift = 3,
1.132 +};
1.133 +
1.134 +enum Ssi_control1_limits : unsigned
1.135 +{
1.136 + Ssi_trans_threshold_limit = 15,
1.137 + Ssi_command_length_limit = 15,
1.138 + Ssi_recv_threshold_limit = 15,
1.139 + Ssi_char_length_limit = 30,
1.140 +};
1.141 +
1.142 +enum Ssi_status_bits : unsigned
1.143 +{
1.144 + Ssi_trans_char_count_mask = 0x00ff0000,
1.145 + Ssi_recv_char_count_mask = 0x0000ff00,
1.146 + Ssi_trans_ended = 0x00000080,
1.147 + Ssi_trans_busy = 0x00000040,
1.148 + Ssi_trans_fifo_full = 0x00000020,
1.149 + Ssi_recv_fifo_empty = 0x00000010,
1.150 + Ssi_trans_fifo_half_empty = 0x00000008,
1.151 + Ssi_recv_fifo_half_full = 0x00000004,
1.152 + Ssi_trans_underrun = 0x00000002,
1.153 + Ssi_recv_overrun = 0x00000001,
1.154 +};
1.155 +
1.156 +enum Ssi_status_shifts : unsigned
1.157 +{
1.158 + Ssi_trans_char_count_shift = 16,
1.159 + Ssi_recv_char_count_shift = 8,
1.160 +};
1.161 +
1.162 +enum Ssi_status_limits : unsigned
1.163 +{
1.164 + Ssi_trans_char_count_limit = 0xff,
1.165 + Ssi_recv_char_count_limit = 0xff,
1.166 +};
1.167 +
1.168 +enum Ssi_interval_time_bits : unsigned
1.169 +{
1.170 + Ssi_interval_clock_mask = 0x8000,
1.171 + Ssi_interval_clock_bit_clock = 0x0000,
1.172 + Ssi_interval_clock_32k_clock = 0x8000,
1.173 + Ssi_interval_time_mask = 0x3fff,
1.174 +};
1.175 +
1.176 +enum Ssi_char_per_frame_bits : unsigned
1.177 +{
1.178 + Ssi_char_per_frame_mask = 0x7,
1.179 +};
1.180 +
1.181 +enum Ssi_clock_bits : unsigned
1.182 +{
1.183 + Ssi_clock_frequency_mask = 0xff,
1.184 +};
1.185 +
1.186 +enum Ssi_recv_counter_bits : unsigned
1.187 +{
1.188 + Ssi_recv_counter_mask = 0xffff,
1.189 +};
1.190 +
1.191 +
1.192 +
1.193 +/* Initialise a channel. */
1.194 +
1.195 +Spi_jz4780_channel::Spi_jz4780_channel(l4_addr_t spi_start, l4_addr_t start,
1.196 + enum Clock_identifiers clock,
1.197 + Cpm_jz4780_chip *cpm,
1.198 + Dma_jz4780_channel *dma,
1.199 + enum Dma_jz4780_request_type request_type,
1.200 + uint64_t frequency)
1.201 +: _spi_start(spi_start), _clock(clock), _cpm(cpm), _dma(dma),
1.202 + _request_type(request_type), _frequency(frequency)
1.203 +{
1.204 + _regs = new Hw::Mmio_register_block<32>(start);
1.205 + _cpm->start_clock(clock);
1.206 +
1.207 + /* Disable the channel while configuring: send MSB first, big endian wire
1.208 + representation. Disable reception. */
1.209 +
1.210 + _regs[Ssi_control0] = Ssi_trans_endian_msbyte_msbit |
1.211 + Ssi_select_pin_is_ce2 |
1.212 + Ssi_disable_recv;
1.213 +
1.214 + /* Set default transfer properties. */
1.215 +
1.216 + configure_transfer(8);
1.217 +
1.218 + /* Select "normal" mode. */
1.219 +
1.220 + _regs[Ssi_interval_time] = 0;
1.221 +
1.222 + /* Limit the frequency to half that of the device clock. */
1.223 +
1.224 + if (_frequency >= _cpm->get_frequency(_clock))
1.225 + _frequency = _cpm->get_frequency(_clock) / 2;
1.226 +
1.227 + /* SSI_CLK = DEV_CLK / (2 * (divider + 1)) */
1.228 +
1.229 + uint32_t divider = _cpm->get_frequency(_clock) / (_frequency * 2) - 1;
1.230 +
1.231 + _regs[Ssi_clock] = divider < Ssi_clock_frequency_mask ? divider : Ssi_clock_frequency_mask;
1.232 +
1.233 + /* Enable the channel. */
1.234 +
1.235 + _regs[Ssi_control0] = _regs[Ssi_control0] | Ssi_enable;
1.236 +}
1.237 +
1.238 +/* NOTE: More transfer characteristics should be configurable. */
1.239 +
1.240 +void Spi_jz4780_channel::configure_transfer(uint8_t char_size)
1.241 +{
1.242 + uint32_t char_length;
1.243 +
1.244 + if (char_size < 2)
1.245 + char_length = 0;
1.246 + else
1.247 + {
1.248 + char_length = char_size - 2;
1.249 +
1.250 + if (char_size > Ssi_char_length_limit)
1.251 + char_length = Ssi_char_length_limit;
1.252 + }
1.253 +
1.254 + /* Clear the status. */
1.255 +
1.256 + _regs[Ssi_control0] = _regs[Ssi_control0] | Ssi_trans_flush | Ssi_recv_flush;
1.257 + _regs[Ssi_status] = 0;
1.258 +
1.259 + /* Indicate the desired character size.
1.260 +
1.261 + Use active low device selection, SPI format with active low clock, with
1.262 + data driven on the falling (asserted) clock and sampled on the rising
1.263 + clock
1.264 +
1.265 + The unfinished flag prevents the transaction from finishing if the FIFO is
1.266 + empty. It is not used here since it appears to prevent any meaningful
1.267 + testing of the busy and end flags. */
1.268 +
1.269 + _regs[Ssi_control1] = (char_length << Ssi_char_length_shift) |
1.270 + ((Ssi_trans_threshold_limit / 2) << Ssi_trans_threshold_shift) |
1.271 + Ssi_format_spi | Ssi_active_ce2_low |
1.272 + Spi_clock_assert_drive | Spi_clock_idle_high_level;
1.273 +}
1.274 +
1.275 +/* Transfer the given number of bytes from a buffer using the given unit size in
1.276 + bytes and character size in bits. */
1.277 +
1.278 +uint32_t
1.279 +Spi_jz4780_channel::send(uint32_t bytes, const uint8_t data[], uint8_t unit_size,
1.280 + uint8_t char_size)
1.281 +{
1.282 + configure_transfer(char_size);
1.283 +
1.284 + uint32_t transferred;
1.285 + uint32_t char_mask = (1 << char_size) - 1;
1.286 +
1.287 + for (transferred = 0; transferred < bytes; transferred += unit_size)
1.288 + {
1.289 + uint32_t value = 0;
1.290 +
1.291 + for (uint8_t byte = 0; byte < unit_size; byte++)
1.292 + value = (value << 8) | data[transferred + byte];
1.293 +
1.294 + /* Relocate any command bit to bit 16 for byte characters. */
1.295 +
1.296 + uint32_t command = (char_size < 16) && (value & (1 << char_size)) ? (1 << 16) : 0;
1.297 +
1.298 + /* Combine the significant portion of the character with the command. */
1.299 +
1.300 + value = (value & char_mask) | command;
1.301 + _regs[Ssi_data] = value;
1.302 + }
1.303 +
1.304 + /* Wait for the busy condition to clear or for a limited period. */
1.305 +
1.306 + for (unsigned int i = 0; i < (1 << 20) && (_regs[Ssi_status] & Ssi_trans_busy); i++);
1.307 +
1.308 + return transferred;
1.309 +}
1.310 +
1.311 +/* Transfer the given number of bytes from a DMA region using the given
1.312 + unit size in bytes and character size in bits. */
1.313 +
1.314 +uint32_t Spi_jz4780_channel::transfer(l4re_dma_space_dma_addr_t paddr,
1.315 + uint32_t count, uint8_t unit_size,
1.316 + uint8_t char_size)
1.317 +{
1.318 + configure_transfer(char_size);
1.319 +
1.320 + uint32_t transferred = 0;
1.321 + uint32_t unit_count = count / unit_size;
1.322 + uint32_t to_transfer = _dma->transfer(paddr, _spi_start + Ssi_data,
1.323 + unit_count, true, false,
1.324 + unit_size, unit_size, unit_size,
1.325 + _request_type);
1.326 +
1.327 + if (to_transfer)
1.328 + transferred = to_transfer ? (unit_count - _dma->wait()) * unit_size : 0;
1.329 +
1.330 + /* Wait for the busy condition to clear or for a limited period. */
1.331 +
1.332 + for (unsigned int i = 0; i < (1 << 20) && (_regs[Ssi_status] & Ssi_trans_busy); i++);
1.333 +
1.334 + return transferred;
1.335 +}
1.336 +
1.337 +
1.338 +
1.339 +/* Initialise the peripheral abstraction. */
1.340 +
1.341 +Spi_jz4780_chip::Spi_jz4780_chip(l4_addr_t spi_start, l4_addr_t start,
1.342 + l4_addr_t end, Cpm_jz4780_chip *cpm)
1.343 +: _spi_start(spi_start), _start(start), _end(end), _cpm(cpm)
1.344 +{
1.345 +}
1.346 +
1.347 +Spi_jz4780_channel *
1.348 +Spi_jz4780_chip::get_channel(uint8_t channel, Dma_jz4780_channel *dma,
1.349 + uint64_t frequency)
1.350 +{
1.351 + // NOTE: Only sending is supported.
1.352 +
1.353 + enum Dma_jz4780_request_type request_types[] = {Dma_request_ssi0_out, Dma_request_ssi1_out};
1.354 + enum Clock_identifiers clocks[] = {Clock_ssi0, Clock_ssi1};
1.355 +
1.356 + if (channel < 2)
1.357 + return new Spi_jz4780_channel(_spi_start + channel * Ssi_block_offset,
1.358 + _start + channel * Ssi_block_offset,
1.359 + clocks[channel],
1.360 + _cpm, dma, request_types[channel], frequency);
1.361 + else
1.362 + throw -L4_EINVAL;
1.363 +}
1.364 +
1.365 +
1.366 +
1.367 +/* C language interface. */
1.368 +
1.369 +void *jz4780_spi_init(l4_addr_t spi_start, l4_addr_t start, l4_addr_t end, void *cpm)
1.370 +{
1.371 + return new Spi_jz4780_chip(spi_start, start, end, static_cast<Cpm_jz4780_chip *>(cpm));
1.372 +}
1.373 +
1.374 +void *jz4780_spi_get_channel(void *spi, uint8_t channel, void *dma, uint64_t frequency)
1.375 +{
1.376 + return static_cast<Spi_jz4780_chip *>(spi)->get_channel(channel,
1.377 + static_cast<Dma_jz4780_channel *>(dma), frequency);
1.378 +}
1.379 +
1.380 +uint32_t jz4780_spi_send(void *channel, uint32_t bytes, const uint8_t data[],
1.381 + uint8_t unit_size, uint8_t char_size)
1.382 +{
1.383 + return static_cast<Spi_jz4780_channel *>(channel)->send(bytes, data, unit_size, char_size);
1.384 +}
1.385 +
1.386 +uint32_t jz4780_spi_transfer(void *channel, l4re_dma_space_dma_addr_t paddr,
1.387 + uint32_t count, uint8_t unit_size, uint8_t char_size)
1.388 +{
1.389 + return static_cast<Spi_jz4780_channel *>(channel)->transfer(paddr, count, unit_size, char_size);
1.390 +}