paul@221 | 1 | /* |
paul@221 | 2 | * Perform SPI communication using the JZ4780 SPI peripheral. |
paul@221 | 3 | * |
paul@221 | 4 | * Copyright (C) 2023 Paul Boddie <paul@boddie.org.uk> |
paul@221 | 5 | * |
paul@221 | 6 | * This program is free software; you can redistribute it and/or |
paul@221 | 7 | * modify it under the terms of the GNU General Public License as |
paul@221 | 8 | * published by the Free Software Foundation; either version 2 of |
paul@221 | 9 | * the License, or (at your option) any later version. |
paul@221 | 10 | * |
paul@221 | 11 | * This program is distributed in the hope that it will be useful, |
paul@221 | 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
paul@221 | 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
paul@221 | 14 | * GNU General Public License for more details. |
paul@221 | 15 | * |
paul@221 | 16 | * You should have received a copy of the GNU General Public License |
paul@221 | 17 | * along with this program; if not, write to the Free Software |
paul@221 | 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, |
paul@221 | 19 | * Boston, MA 02110-1301, USA |
paul@221 | 20 | */ |
paul@221 | 21 | |
paul@221 | 22 | #include <l4/devices/spi-jz4780.h> |
paul@221 | 23 | #include <l4/devices/dma.h> |
paul@221 | 24 | #include <l4/sys/err.h> |
paul@221 | 25 | #include <string.h> |
paul@221 | 26 | |
paul@221 | 27 | |
paul@221 | 28 | |
paul@221 | 29 | /* Register definitions. */ |
paul@221 | 30 | |
paul@221 | 31 | enum Regs |
paul@221 | 32 | { |
paul@221 | 33 | Ssi_data = 0x00, // SSIDR |
paul@221 | 34 | Ssi_control0 = 0x04, // SSICR0 |
paul@221 | 35 | Ssi_control1 = 0x08, // SSICR1 |
paul@221 | 36 | Ssi_status = 0x0c, // SSISR |
paul@221 | 37 | Ssi_interval_time = 0x10, // SSIITR |
paul@221 | 38 | Ssi_char_per_frame = 0x14, // SSIICR |
paul@221 | 39 | Ssi_clock = 0x18, // SSICGR |
paul@221 | 40 | Ssi_recv_counter = 0x1c, // SSIRCNT |
paul@221 | 41 | |
paul@221 | 42 | /* Register block offset. */ |
paul@221 | 43 | |
paul@221 | 44 | Ssi_block_offset = 0x1000, |
paul@221 | 45 | }; |
paul@221 | 46 | |
paul@221 | 47 | enum Ssi_data_bits : unsigned |
paul@221 | 48 | { |
paul@221 | 49 | Ssi_data_gpc = 0x1000, |
paul@221 | 50 | }; |
paul@221 | 51 | |
paul@221 | 52 | enum Ssi_control0_bits : unsigned |
paul@221 | 53 | { |
paul@221 | 54 | Ssi_trans_endian_mask = 0xc0000, |
paul@221 | 55 | Ssi_trans_endian_msbyte_msbit = 0x00000, |
paul@221 | 56 | Ssi_trans_endian_msbyte_lsbit = 0x40000, |
paul@221 | 57 | Ssi_trans_endian_lsbyte_lsbit = 0x80000, |
paul@221 | 58 | Ssi_trans_endian_lsbyte_msbit = 0xc0000, |
paul@221 | 59 | |
paul@221 | 60 | Ssi_recv_endian_mask = 0x30000, |
paul@221 | 61 | Ssi_recv_endian_msbyte_msbit = 0x00000, |
paul@221 | 62 | Ssi_recv_endian_msbyte_lsbit = 0x10000, |
paul@221 | 63 | Ssi_recv_endian_lsbyte_lsbit = 0x20000, |
paul@221 | 64 | Ssi_recv_endian_lsbyte_msbit = 0x30000, |
paul@221 | 65 | |
paul@221 | 66 | Ssi_enable = 0x08000, |
paul@221 | 67 | Ssi_enable_trans_half_empty = 0x04000, |
paul@221 | 68 | Ssi_enable_recv_half_full = 0x02000, |
paul@221 | 69 | Ssi_enable_trans_error = 0x01000, |
paul@221 | 70 | Ssi_enable_recv_error = 0x00800, |
paul@221 | 71 | Ssi_loop = 0x00400, |
paul@221 | 72 | Ssi_recv_finish_control = 0x00200, |
paul@221 | 73 | Ssi_recv_finished = 0x00100, |
paul@221 | 74 | Ssi_enable_auto_clear_underrun = 0x00080, |
paul@221 | 75 | Ssi_select_pin_is_ce2 = 0x00040, |
paul@221 | 76 | Ssi_use_recv_count = 0x00010, |
paul@221 | 77 | Ssi_old_fifo_empty_mode = 0x00008, |
paul@221 | 78 | Ssi_trans_flush = 0x00004, |
paul@221 | 79 | Ssi_recv_flush = 0x00002, |
paul@221 | 80 | Ssi_disable_recv = 0x00001, |
paul@221 | 81 | }; |
paul@221 | 82 | |
paul@221 | 83 | enum Ssi_control1_bits : unsigned |
paul@221 | 84 | { |
paul@221 | 85 | Ssi_active_mask = 0xc0000000, |
paul@221 | 86 | Ssi_active_ce_low = 0x00000000, |
paul@221 | 87 | Ssi_active_ce_high = 0x40000000, |
paul@221 | 88 | Ssi_active_ce2_low = 0x00000000, |
paul@221 | 89 | Ssi_active_ce2_high = 0x80000000, |
paul@221 | 90 | |
paul@221 | 91 | Ssi_clock_start_delay_mask = 0x30000000, |
paul@221 | 92 | Ssi_clock_start_delay_default = 0x00000000, |
paul@221 | 93 | Ssi_clock_start_delay_plus_1 = 0x10000000, |
paul@221 | 94 | Ssi_clock_start_delay_plus_2 = 0x20000000, |
paul@221 | 95 | Ssi_clock_start_delay_plus_3 = 0x30000000, |
paul@221 | 96 | |
paul@221 | 97 | Ssi_clock_stop_delay_mask = 0x0c000000, |
paul@221 | 98 | Ssi_clock_stop_delay_default = 0x00000000, |
paul@221 | 99 | Ssi_clock_stop_delay_plus_1 = 0x04000000, |
paul@221 | 100 | Ssi_clock_stop_delay_plus_2 = 0x08000000, |
paul@221 | 101 | Ssi_clock_stop_delay_plus_3 = 0x0c000000, |
paul@221 | 102 | |
paul@221 | 103 | Ssi_interval_assert_ce_or_ce2 = 0x01000000, |
paul@221 | 104 | Ssi_trans_empty_unfinished = 0x00800000, |
paul@221 | 105 | |
paul@221 | 106 | Ssi_format_mask = 0x00300000, |
paul@221 | 107 | Ssi_format_spi = 0x00000000, |
paul@221 | 108 | Ssi_format_ssp = 0x00100000, |
paul@221 | 109 | Ssi_format_microwire1 = 0x00200000, |
paul@221 | 110 | Ssi_format_microwire2 = 0x00300000, |
paul@221 | 111 | |
paul@221 | 112 | Ssi_trans_threshold_mask = 0x000f0000, |
paul@221 | 113 | Ssi_command_length_mask = 0x0000f000, |
paul@221 | 114 | Ssi_recv_threshold_mask = 0x00000f00, |
paul@221 | 115 | Ssi_char_length_mask = 0x000000f8, |
paul@221 | 116 | |
paul@221 | 117 | Spi_clock_assert_sample = 0x00000000, // phase #0 |
paul@221 | 118 | Spi_clock_assert_drive = 0x00000002, // phase #1 |
paul@221 | 119 | Spi_clock_idle_low_level = 0x00000000, // polarity #0 |
paul@221 | 120 | Spi_clock_idle_high_level = 0x00000001, // polarity #1 |
paul@221 | 121 | }; |
paul@221 | 122 | |
paul@221 | 123 | enum Ssi_control1_shifts : unsigned |
paul@221 | 124 | { |
paul@221 | 125 | Ssi_trans_threshold_shift = 16, |
paul@221 | 126 | Ssi_command_length_shift = 12, |
paul@221 | 127 | Ssi_recv_threshold_shift = 8, |
paul@221 | 128 | Ssi_char_length_shift = 3, |
paul@221 | 129 | }; |
paul@221 | 130 | |
paul@221 | 131 | enum Ssi_control1_limits : unsigned |
paul@221 | 132 | { |
paul@221 | 133 | Ssi_trans_threshold_limit = 15, |
paul@221 | 134 | Ssi_command_length_limit = 15, |
paul@221 | 135 | Ssi_recv_threshold_limit = 15, |
paul@221 | 136 | Ssi_char_length_limit = 30, |
paul@221 | 137 | }; |
paul@221 | 138 | |
paul@221 | 139 | enum Ssi_status_bits : unsigned |
paul@221 | 140 | { |
paul@221 | 141 | Ssi_trans_char_count_mask = 0x00ff0000, |
paul@221 | 142 | Ssi_recv_char_count_mask = 0x0000ff00, |
paul@221 | 143 | Ssi_trans_ended = 0x00000080, |
paul@221 | 144 | Ssi_trans_busy = 0x00000040, |
paul@221 | 145 | Ssi_trans_fifo_full = 0x00000020, |
paul@221 | 146 | Ssi_recv_fifo_empty = 0x00000010, |
paul@221 | 147 | Ssi_trans_fifo_half_empty = 0x00000008, |
paul@221 | 148 | Ssi_recv_fifo_half_full = 0x00000004, |
paul@221 | 149 | Ssi_trans_underrun = 0x00000002, |
paul@221 | 150 | Ssi_recv_overrun = 0x00000001, |
paul@221 | 151 | }; |
paul@221 | 152 | |
paul@221 | 153 | enum Ssi_status_shifts : unsigned |
paul@221 | 154 | { |
paul@221 | 155 | Ssi_trans_char_count_shift = 16, |
paul@221 | 156 | Ssi_recv_char_count_shift = 8, |
paul@221 | 157 | }; |
paul@221 | 158 | |
paul@221 | 159 | enum Ssi_status_limits : unsigned |
paul@221 | 160 | { |
paul@221 | 161 | Ssi_trans_char_count_limit = 0xff, |
paul@221 | 162 | Ssi_recv_char_count_limit = 0xff, |
paul@221 | 163 | }; |
paul@221 | 164 | |
paul@221 | 165 | enum Ssi_interval_time_bits : unsigned |
paul@221 | 166 | { |
paul@221 | 167 | Ssi_interval_clock_mask = 0x8000, |
paul@221 | 168 | Ssi_interval_clock_bit_clock = 0x0000, |
paul@221 | 169 | Ssi_interval_clock_32k_clock = 0x8000, |
paul@221 | 170 | Ssi_interval_time_mask = 0x3fff, |
paul@221 | 171 | }; |
paul@221 | 172 | |
paul@221 | 173 | enum Ssi_char_per_frame_bits : unsigned |
paul@221 | 174 | { |
paul@221 | 175 | Ssi_char_per_frame_mask = 0x7, |
paul@221 | 176 | }; |
paul@221 | 177 | |
paul@221 | 178 | enum Ssi_clock_bits : unsigned |
paul@221 | 179 | { |
paul@221 | 180 | Ssi_clock_frequency_mask = 0xff, |
paul@221 | 181 | }; |
paul@221 | 182 | |
paul@221 | 183 | enum Ssi_recv_counter_bits : unsigned |
paul@221 | 184 | { |
paul@221 | 185 | Ssi_recv_counter_mask = 0xffff, |
paul@221 | 186 | }; |
paul@221 | 187 | |
paul@221 | 188 | |
paul@221 | 189 | |
paul@221 | 190 | /* Initialise a channel. */ |
paul@221 | 191 | |
paul@221 | 192 | Spi_jz4780_channel::Spi_jz4780_channel(l4_addr_t spi_start, l4_addr_t start, |
paul@221 | 193 | enum Clock_identifiers clock, |
paul@221 | 194 | Cpm_jz4780_chip *cpm, |
paul@221 | 195 | Dma_jz4780_channel *dma, |
paul@221 | 196 | enum Dma_jz4780_request_type request_type, |
paul@221 | 197 | uint64_t frequency) |
paul@221 | 198 | : _spi_start(spi_start), _clock(clock), _cpm(cpm), _dma(dma), |
paul@221 | 199 | _request_type(request_type), _frequency(frequency) |
paul@221 | 200 | { |
paul@221 | 201 | _regs = new Hw::Mmio_register_block<32>(start); |
paul@221 | 202 | _cpm->start_clock(clock); |
paul@221 | 203 | |
paul@221 | 204 | /* Disable the channel while configuring: send MSB first, big endian wire |
paul@221 | 205 | representation. Disable reception. */ |
paul@221 | 206 | |
paul@221 | 207 | _regs[Ssi_control0] = Ssi_trans_endian_msbyte_msbit | |
paul@221 | 208 | Ssi_select_pin_is_ce2 | |
paul@221 | 209 | Ssi_disable_recv; |
paul@221 | 210 | |
paul@221 | 211 | /* Set default transfer properties. */ |
paul@221 | 212 | |
paul@221 | 213 | configure_transfer(8); |
paul@221 | 214 | |
paul@221 | 215 | /* Select "normal" mode. */ |
paul@221 | 216 | |
paul@221 | 217 | _regs[Ssi_interval_time] = 0; |
paul@221 | 218 | |
paul@221 | 219 | /* Limit the frequency to half that of the device clock. */ |
paul@221 | 220 | |
paul@221 | 221 | if (_frequency >= _cpm->get_frequency(_clock)) |
paul@221 | 222 | _frequency = _cpm->get_frequency(_clock) / 2; |
paul@221 | 223 | |
paul@221 | 224 | /* SSI_CLK = DEV_CLK / (2 * (divider + 1)) */ |
paul@221 | 225 | |
paul@221 | 226 | uint32_t divider = _cpm->get_frequency(_clock) / (_frequency * 2) - 1; |
paul@221 | 227 | |
paul@221 | 228 | _regs[Ssi_clock] = divider < Ssi_clock_frequency_mask ? divider : Ssi_clock_frequency_mask; |
paul@221 | 229 | |
paul@221 | 230 | /* Enable the channel. */ |
paul@221 | 231 | |
paul@221 | 232 | _regs[Ssi_control0] = _regs[Ssi_control0] | Ssi_enable; |
paul@221 | 233 | } |
paul@221 | 234 | |
paul@221 | 235 | /* NOTE: More transfer characteristics should be configurable. */ |
paul@221 | 236 | |
paul@221 | 237 | void Spi_jz4780_channel::configure_transfer(uint8_t char_size) |
paul@221 | 238 | { |
paul@221 | 239 | uint32_t char_length; |
paul@221 | 240 | |
paul@221 | 241 | if (char_size < 2) |
paul@221 | 242 | char_length = 0; |
paul@221 | 243 | else |
paul@221 | 244 | { |
paul@221 | 245 | char_length = char_size - 2; |
paul@221 | 246 | |
paul@221 | 247 | if (char_size > Ssi_char_length_limit) |
paul@221 | 248 | char_length = Ssi_char_length_limit; |
paul@221 | 249 | } |
paul@221 | 250 | |
paul@221 | 251 | /* Clear the status. */ |
paul@221 | 252 | |
paul@221 | 253 | _regs[Ssi_control0] = _regs[Ssi_control0] | Ssi_trans_flush | Ssi_recv_flush; |
paul@221 | 254 | _regs[Ssi_status] = 0; |
paul@221 | 255 | |
paul@221 | 256 | /* Indicate the desired character size. |
paul@221 | 257 | |
paul@221 | 258 | Use active low device selection, SPI format with active low clock, with |
paul@221 | 259 | data driven on the falling (asserted) clock and sampled on the rising |
paul@221 | 260 | clock |
paul@221 | 261 | |
paul@221 | 262 | The unfinished flag prevents the transaction from finishing if the FIFO is |
paul@221 | 263 | empty. It is not used here since it appears to prevent any meaningful |
paul@221 | 264 | testing of the busy and end flags. */ |
paul@221 | 265 | |
paul@221 | 266 | _regs[Ssi_control1] = (char_length << Ssi_char_length_shift) | |
paul@221 | 267 | ((Ssi_trans_threshold_limit / 2) << Ssi_trans_threshold_shift) | |
paul@221 | 268 | Ssi_format_spi | Ssi_active_ce2_low | |
paul@221 | 269 | Spi_clock_assert_drive | Spi_clock_idle_high_level; |
paul@221 | 270 | } |
paul@221 | 271 | |
paul@221 | 272 | /* Transfer the given number of bytes from a buffer using the given unit size in |
paul@221 | 273 | bytes and character size in bits. */ |
paul@221 | 274 | |
paul@221 | 275 | uint32_t |
paul@221 | 276 | Spi_jz4780_channel::send(uint32_t bytes, const uint8_t data[], uint8_t unit_size, |
paul@221 | 277 | uint8_t char_size) |
paul@221 | 278 | { |
paul@221 | 279 | configure_transfer(char_size); |
paul@221 | 280 | |
paul@221 | 281 | uint32_t transferred; |
paul@221 | 282 | uint32_t char_mask = (1 << char_size) - 1; |
paul@221 | 283 | |
paul@221 | 284 | for (transferred = 0; transferred < bytes; transferred += unit_size) |
paul@221 | 285 | { |
paul@221 | 286 | uint32_t value = 0; |
paul@221 | 287 | |
paul@221 | 288 | for (uint8_t byte = 0; byte < unit_size; byte++) |
paul@221 | 289 | value = (value << 8) | data[transferred + byte]; |
paul@221 | 290 | |
paul@221 | 291 | /* Relocate any command bit to bit 16 for byte characters. */ |
paul@221 | 292 | |
paul@221 | 293 | uint32_t command = (char_size < 16) && (value & (1 << char_size)) ? (1 << 16) : 0; |
paul@221 | 294 | |
paul@221 | 295 | /* Combine the significant portion of the character with the command. */ |
paul@221 | 296 | |
paul@221 | 297 | value = (value & char_mask) | command; |
paul@221 | 298 | _regs[Ssi_data] = value; |
paul@221 | 299 | } |
paul@221 | 300 | |
paul@221 | 301 | /* Wait for the busy condition to clear or for a limited period. */ |
paul@221 | 302 | |
paul@221 | 303 | for (unsigned int i = 0; i < (1 << 20) && (_regs[Ssi_status] & Ssi_trans_busy); i++); |
paul@221 | 304 | |
paul@221 | 305 | return transferred; |
paul@221 | 306 | } |
paul@221 | 307 | |
paul@221 | 308 | /* Transfer the given number of bytes from a DMA region using the given |
paul@221 | 309 | unit size in bytes and character size in bits. */ |
paul@221 | 310 | |
paul@221 | 311 | uint32_t Spi_jz4780_channel::transfer(l4re_dma_space_dma_addr_t paddr, |
paul@221 | 312 | uint32_t count, uint8_t unit_size, |
paul@221 | 313 | uint8_t char_size) |
paul@221 | 314 | { |
paul@221 | 315 | configure_transfer(char_size); |
paul@221 | 316 | |
paul@221 | 317 | uint32_t transferred = 0; |
paul@221 | 318 | uint32_t unit_count = count / unit_size; |
paul@221 | 319 | uint32_t to_transfer = _dma->transfer(paddr, _spi_start + Ssi_data, |
paul@221 | 320 | unit_count, true, false, |
paul@221 | 321 | unit_size, unit_size, unit_size, |
paul@221 | 322 | _request_type); |
paul@221 | 323 | |
paul@221 | 324 | if (to_transfer) |
paul@221 | 325 | transferred = to_transfer ? (unit_count - _dma->wait()) * unit_size : 0; |
paul@221 | 326 | |
paul@221 | 327 | /* Wait for the busy condition to clear or for a limited period. */ |
paul@221 | 328 | |
paul@221 | 329 | for (unsigned int i = 0; i < (1 << 20) && (_regs[Ssi_status] & Ssi_trans_busy); i++); |
paul@221 | 330 | |
paul@221 | 331 | return transferred; |
paul@221 | 332 | } |
paul@221 | 333 | |
paul@221 | 334 | |
paul@221 | 335 | |
paul@221 | 336 | /* Initialise the peripheral abstraction. */ |
paul@221 | 337 | |
paul@221 | 338 | Spi_jz4780_chip::Spi_jz4780_chip(l4_addr_t spi_start, l4_addr_t start, |
paul@221 | 339 | l4_addr_t end, Cpm_jz4780_chip *cpm) |
paul@221 | 340 | : _spi_start(spi_start), _start(start), _end(end), _cpm(cpm) |
paul@221 | 341 | { |
paul@221 | 342 | } |
paul@221 | 343 | |
paul@221 | 344 | Spi_jz4780_channel * |
paul@221 | 345 | Spi_jz4780_chip::get_channel(uint8_t channel, Dma_jz4780_channel *dma, |
paul@221 | 346 | uint64_t frequency) |
paul@221 | 347 | { |
paul@221 | 348 | // NOTE: Only sending is supported. |
paul@221 | 349 | |
paul@221 | 350 | enum Dma_jz4780_request_type request_types[] = {Dma_request_ssi0_out, Dma_request_ssi1_out}; |
paul@221 | 351 | enum Clock_identifiers clocks[] = {Clock_ssi0, Clock_ssi1}; |
paul@221 | 352 | |
paul@221 | 353 | if (channel < 2) |
paul@221 | 354 | return new Spi_jz4780_channel(_spi_start + channel * Ssi_block_offset, |
paul@221 | 355 | _start + channel * Ssi_block_offset, |
paul@221 | 356 | clocks[channel], |
paul@221 | 357 | _cpm, dma, request_types[channel], frequency); |
paul@221 | 358 | else |
paul@221 | 359 | throw -L4_EINVAL; |
paul@221 | 360 | } |
paul@221 | 361 | |
paul@221 | 362 | |
paul@221 | 363 | |
paul@221 | 364 | /* C language interface. */ |
paul@221 | 365 | |
paul@221 | 366 | void *jz4780_spi_init(l4_addr_t spi_start, l4_addr_t start, l4_addr_t end, void *cpm) |
paul@221 | 367 | { |
paul@221 | 368 | return new Spi_jz4780_chip(spi_start, start, end, static_cast<Cpm_jz4780_chip *>(cpm)); |
paul@221 | 369 | } |
paul@221 | 370 | |
paul@221 | 371 | void *jz4780_spi_get_channel(void *spi, uint8_t channel, void *dma, uint64_t frequency) |
paul@221 | 372 | { |
paul@221 | 373 | return static_cast<Spi_jz4780_chip *>(spi)->get_channel(channel, |
paul@221 | 374 | static_cast<Dma_jz4780_channel *>(dma), frequency); |
paul@221 | 375 | } |
paul@221 | 376 | |
paul@221 | 377 | uint32_t jz4780_spi_send(void *channel, uint32_t bytes, const uint8_t data[], |
paul@221 | 378 | uint8_t unit_size, uint8_t char_size) |
paul@221 | 379 | { |
paul@221 | 380 | return static_cast<Spi_jz4780_channel *>(channel)->send(bytes, data, unit_size, char_size); |
paul@221 | 381 | } |
paul@221 | 382 | |
paul@221 | 383 | uint32_t jz4780_spi_transfer(void *channel, l4re_dma_space_dma_addr_t paddr, |
paul@221 | 384 | uint32_t count, uint8_t unit_size, uint8_t char_size) |
paul@221 | 385 | { |
paul@221 | 386 | return static_cast<Spi_jz4780_channel *>(channel)->transfer(paddr, count, unit_size, char_size); |
paul@221 | 387 | } |