# HG changeset patch # User Paul Boddie # Date 1698161691 -7200 # Node ID 158d7af927f799cb0caa12a0f7370613603eecd0 # Parent 4add12ca045f4be5083ff6eafdde3ddbb2ea9783 Added AIC/I2S support for the X1600. diff -r 4add12ca045f -r 158d7af927f7 pkg/devices/Control --- a/pkg/devices/Control Tue Oct 24 17:33:39 2023 +0200 +++ b/pkg/devices/Control Tue Oct 24 17:34:51 2023 +0200 @@ -21,6 +21,7 @@ provides: libdevice-lcd provides: libdevice-lcd-jz4740 provides: libdevice-util +provides: libdrivers-aic provides: libdrivers-common provides: libdrivers-cpm provides: libdrivers-dma diff -r 4add12ca045f -r 158d7af927f7 pkg/devices/lib/Makefile --- a/pkg/devices/lib/Makefile Tue Oct 24 17:33:39 2023 +0200 +++ b/pkg/devices/lib/Makefile Tue Oct 24 17:34:51 2023 +0200 @@ -1,10 +1,11 @@ PKGDIR ?= .. L4DIR ?= $(PKGDIR)/../.. -TARGET := common cpm dma gpio hdmi i2c keypad lcd panel pwm spi +TARGET := aic common cpm dma gpio hdmi i2c keypad lcd panel pwm spi include $(L4DIR)/mk/subdir.mk +aic: common cpm: common dma: common gpio: common diff -r 4add12ca045f -r 158d7af927f7 pkg/devices/lib/aic/Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/devices/lib/aic/Makefile Tue Oct 24 17:34:51 2023 +0200 @@ -0,0 +1,8 @@ +PKGDIR ?= ../.. +L4DIR ?= $(PKGDIR)/../.. + +TARGET := include src + +include $(L4DIR)/mk/subdir.mk + +src: include diff -r 4add12ca045f -r 158d7af927f7 pkg/devices/lib/aic/include/Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/devices/lib/aic/include/Makefile Tue Oct 24 17:34:51 2023 +0200 @@ -0,0 +1,4 @@ +PKGDIR = ../../.. +L4DIR ?= $(PKGDIR)/../.. + +include $(L4DIR)/mk/include.mk diff -r 4add12ca045f -r 158d7af927f7 pkg/devices/lib/aic/include/aic-x1600.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/devices/lib/aic/include/aic-x1600.h Tue Oct 24 17:34:51 2023 +0200 @@ -0,0 +1,106 @@ +/* + * AIC support for the X1600. + * + * Copyright (C) 2023 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 + +// AIC channel. + +class Aic_x1600_channel +{ +private: + l4_addr_t _aic_start; + Hw::Register_block<32> _regs; + enum Clock_identifiers _clock_rx, _clock_tx; + Cpm_x1600_chip *_cpm; + Dma_x1600_channel *_dma; + + // Buffer management. + + unsigned int _size = 0; + l4_addr_t _vaddr = 0; + l4re_dma_space_dma_addr_t _paddr = 0; + l4_cap_idx_t _mem = L4_INVALID_CAP; + +public: + Aic_x1600_channel(l4_addr_t aic_start, l4_addr_t start, + enum Clock_identifiers clock_rx, + enum Clock_identifiers clock_tx, + Cpm_x1600_chip *cpm, + Dma_x1600_channel *dma); + + long get_buffer(uint32_t count, l4_addr_t *addr); + unsigned int transfer(uint32_t count, uint32_t sample_rate, uint8_t sample_size); + +private: + void disable(); + void enable(); + + void config(); + uint32_t encode_input_size(uint8_t sample_size); + uint32_t encode_output_size(uint8_t sample_size); + void set_divider(uint32_t divider, uint32_t mask, uint32_t limit, int bit); + void set_recv_frequency(uint32_t sample_rate); + void set_trans_frequency(uint32_t sample_rate); +}; + +// AIC device control. + +class Aic_x1600_chip +{ +private: + l4_addr_t _aic_start, _start, _end; + Cpm_x1600_chip *_cpm; + +public: + Aic_x1600_chip(l4_addr_t aic_start, l4_addr_t start, l4_addr_t end, Cpm_x1600_chip *cpm); + + Aic_x1600_channel *get_channel(uint8_t channel, Dma_x1600_channel *dma); +}; + +#endif /* __cplusplus */ + + + +/* C language interface. */ + +EXTERN_C_BEGIN + +void *x1600_aic_init(l4_addr_t aic_start, l4_addr_t start, l4_addr_t end, void *cpm); + +void *x1600_aic_get_channel(void *aic, uint8_t channel, void *dma); + +long x1600_aic_get_buffer(void *channel, uint32_t count, l4_addr_t *addr); + +unsigned int x1600_aic_transfer(void *channel, uint32_t count, uint32_t sample_rate, uint8_t sample_size); + +EXTERN_C_END diff -r 4add12ca045f -r 158d7af927f7 pkg/devices/lib/aic/src/Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/devices/lib/aic/src/Makefile Tue Oct 24 17:34:51 2023 +0200 @@ -0,0 +1,13 @@ +PKGDIR ?= ../../.. +L4DIR ?= $(PKGDIR)/../.. + +TARGET = libaic.o.a libaic.o.so +PC_FILENAME := libdrivers-aic + +SRC_CC := x1600.cc + +PRIVATE_INCDIR += $(PKGDIR)/lib/aic/include + +REQUIRES_LIBS := l4re_c l4re_c-util libdrivers-common libdrivers-cpm libdrivers-gpio + +include $(L4DIR)/mk/lib.mk diff -r 4add12ca045f -r 158d7af927f7 pkg/devices/lib/aic/src/x1600.cc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/devices/lib/aic/src/x1600.cc Tue Oct 24 17:34:51 2023 +0200 @@ -0,0 +1,466 @@ +/* + * AIC support for the X1600. + * + * Copyright (C) 2023 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 + +enum Regs +{ + Aic_config = 0x000, // AICFR + Aic_control = 0x004, // AICCR + Aic_i2s_msb_control = 0x010, // I2SCR + Aic_fifo_status = 0x014, // AICSR + Aic_i2s_msb_status = 0x01c, // I2SSR + Aic_i2s_msb_divider = 0x030, // I2SDIV + Aic_fifo_data = 0x034, // AICDR + Aic_loop_data = 0x038, // AICLR + Aic_tfifo_loop = 0x03c, // AICTFLR +}; + +enum Aic_config_bits : unsigned +{ + Aic_enable_big_endian = 0x1000, // MSB + Aic_independent_clock = 0x0100, // DMODE + Aic_shared_clock = 0x0000, // DMODE + Aic_last_sample_uflow = 0x0040, // LSMP + Aic_select_i2s_msb = 0x0010, // AUSEL + Aic_reset = 0x0008, // RST + Aic_tmaster = 0x0004, // TMASTER + Aic_rmaster = 0x0002, // RMASTER + Aic_enable = 0x0001, // ENB + + Aic_rfifo_thold_bit = 24, // RFTH + Aic_tfifo_thold_bit = 16, // TFTH +}; + +enum Aic_fifo_limits : unsigned +{ + Aic_rfifo_max = 15, + Aic_tfifo_max = 31, +}; + +enum Aic_control_bits : unsigned +{ + Aic_enable_tfifo_loop_overrun = 0x80000000, // ETFLOR + Aic_enable_tfifo_loop_request = 0x40000000, // ETFLS + Aic_packed_data = 0x10000000, // PACK16 + + Aic_channel_number_mask = 0x07000000, // CHANNEL + Aic_channel_mono = 0x00000000, + Aic_channel_stereo = 0x01000000, + + Aic_enable_tfifo_loop = 0x00800000, // ETFL + Aic_enable_tfifo_loop_dma = 0x00400000, // TLDMS + + Aic_output_size_mask = 0x00380000, // OSS + Aic_output_size_8bit = 0x00000000, // 0 + Aic_output_size_16bit = 0x00080000, // 1 + Aic_output_size_18bit = 0x00100000, // 2 + Aic_output_size_20bit = 0x00180000, // 3 + Aic_output_size_24bit = 0x00200000, // 4 + + Aic_input_size_mask = 0x00070000, // ISS + Aic_input_size_8bit = 0x00000000, // 0 + Aic_input_size_16bit = 0x00010000, // 1 + Aic_input_size_18bit = 0x00020000, // 2 + Aic_input_size_20bit = 0x00030000, // 3 + Aic_input_size_24bit = 0x00040000, // 4 + + Aic_enable_recv_dma = 0x00008000, // RDMS + Aic_enable_trans_dma = 0x00004000, // TDMS + + Aic_mono_control_mask = 0x00003000, // MONOCTR + Aic_mono_control_both = 0x00000000, // 0 + Aic_mono_control_right = 0x00001000, // 1 + Aic_mono_control_left = 0x00002000, // 2 + + Aic_trans_byte_swap = 0x00000400, // ENDSW + Aic_tfifo_flush = 0x00000100, // TFLUSH + Aic_rfifo_flush = 0x00000080, // RFLUSH + Aic_enable_rfifo_overrun = 0x00000040, // EROR + Aic_enable_tfifo_underrun = 0x00000020, // ETUR + Aic_enable_rfifo_request = 0x00000010, // ERFS + Aic_enable_tfifo_request = 0x00000008, // ETFS + Aic_enable_loopback = 0x00000004, // ENLBF + Aic_enable_playback = 0x00000002, // ERPL + Aic_enable_record = 0x00000001, // EREC +}; + +enum Aic_i2s_msb_control_bits : unsigned +{ + Aic_i2s_msb_right_first = 0x20000, // RFIRST + Aic_i2s_msb_switch_lr = 0x10000, // SWLH + Aic_select_msb_justified = 0x00001, // AMSL + Aic_select_i2s = 0x00000, // AMSL +}; + +enum Aic_fifo_status_bits : unsigned +{ + Aic_rfifo_overrun = 0x40, // ROR + Aic_tfifo_underrun = 0x20, // TUR + Aic_rfifo_request = 0x10, // RFS + Aic_tfifo_request = 0x08, // TFS + Aic_tfifo_loop_overrun = 0x04, // TFLOR + Aic_tfifo_loop_request = 0x02, // TFLS + + Aic_rfifo_level_bit = 24, // RFL + Aic_tfifo_loop_level_bit = 15, // TFLL + Aic_tfifo_level_bit = 8, // TFL +}; + +enum Aic_i2s_msb_status_bits : unsigned +{ + Aic_trans_channel_busy = 0x20, // CHBSY + Aic_trans_busy = 0x10, // TBSY + Aic_recv_busy = 0x08, // RBSY + Aic_busy = 0x04, // BSY +}; + +enum Aic_i2s_msb_divider_bits : unsigned +{ + Aic_recv_divider_mask = 0x1ff0000, // RDIV + Aic_trans_divider_mask = 0x00001ff, // TDIV + + Aic_recv_divider_bit = 16, // RDIV + Aic_trans_divider_bit = 0, // TDIV +}; + +enum Aic_i2s_msb_divider_limits : unsigned +{ + Aic_recv_divider_limit = 0x1ff, // RDIV + Aic_trans_divider_limit = 0x1ff, // TDIV +}; + +enum Aic_fifo_data_limits : unsigned +{ + Aic_fifo_data_limit = 0xffffff, // DATA +}; + +enum Aic_loop_data_limits : unsigned +{ + Aic_loop_data_limit = 0xffffff, // DATALP +}; + +enum Aic_tfifo_loop_limits : unsigned +{ + Aic_tfifo_loop_limit = 0xf, // TFLTH +}; + + + +// Initialise a channel. + +Aic_x1600_channel::Aic_x1600_channel(l4_addr_t aic_start, l4_addr_t start, + enum Clock_identifiers clock_rx, + enum Clock_identifiers clock_tx, + Cpm_x1600_chip *cpm, + Dma_x1600_channel *dma) +: _aic_start(aic_start), _clock_rx(clock_rx), _clock_tx(clock_tx), _cpm(cpm), _dma(dma) +{ + _regs = new Hw::Mmio_register_block<32>(start); + _cpm->start_clock(clock_rx); + _cpm->start_clock(clock_tx); + config(); +} + +/* +I2S configuration. + +The system clock (SYS_CLK) for I2S in the X1600 is issued by the X1600 as the +master/system clock (MCLK) externally to the DAC/codec. + +SYS_CLK is divided to obtain BIT_CLK which is issued by the X1600 as BCLK. +This is dependent on the actual sample rate. + +LRCLK indicates the left/right channel for the issued data. + +Where clocks (MCLK, BCLK, LRCLK) are issued only by the X1600, share mode +is used. Otherwise, independent (6-line) mode is used. + +To drive the MAX9835A, no MCLK is needed. + +Initialisation involves the following: + +- Configure MCLK, BCLK, LRCLK as outputs +- Select I2S in Aic_i2s_msb_control (deselecting Aic_select_msb_justified) +- Set master mode in Aic_config (Aic_tmaster, also Aic_rmaster if independent + mode is used) +- Set the dividers in Aic_i2s_msb_divider for BCLK using the configured I2S + clock frequency +- Perform a reset by writing to Aic_reset in Aic_config, if necessary +- The MAX9835A may not need any configuration using I2C or some other bus +*/ + +void +Aic_x1600_channel::config() +{ + // NOTE: Setting transmit request threshold to 31 (15 * 2 + 1). + + printf("config = %08x\n", (uint32_t) _regs[Aic_config]); + + _regs[Aic_config] = Aic_shared_clock | Aic_select_i2s_msb | Aic_reset | + Aic_tmaster | Aic_enable | (15 << Aic_tfifo_thold_bit); + + _regs[Aic_i2s_msb_control] = Aic_select_i2s; + + printf("config = %08x\n", (uint32_t) _regs[Aic_config]); +} + +/* +Clock configuration: + +- SYS_CLK (MCLK) is Clock_i2s0_tx (or Clock_i2s0_rx) +- BIT_CLK (BCLK) is MCLK divided by the appropriate AIC/I2S divider +- BCLK should be 64 times the sample rate according to the manual + +This suggests that the X1600 always produces data for two 32-bit channels +regardless of the input data characteristics. +*/ + +void +Aic_x1600_channel::set_divider(uint32_t divider, uint32_t mask, uint32_t limit, + int bit) +{ + if (divider <= limit) + _regs[Aic_i2s_msb_divider] = (_regs[Aic_i2s_msb_divider] & ~mask) | + (divider << bit); +} + +void +Aic_x1600_channel::set_recv_frequency(uint32_t sample_rate) +{ + set_divider(_cpm->get_frequency(_clock_rx) / (sample_rate * 32 * 2), + Aic_recv_divider_mask, Aic_recv_divider_limit, Aic_recv_divider_bit); +} + +void +Aic_x1600_channel::set_trans_frequency(uint32_t sample_rate) +{ + printf("tx %d / (freq %d * %d * 2) = %d vs. %d\n", _cpm->get_frequency(_clock_tx), + sample_rate, 32, + _cpm->get_frequency(_clock_tx) / (sample_rate * 32 * 2), + Aic_trans_divider_limit); + + set_divider(_cpm->get_frequency(_clock_tx) / (sample_rate * 32 * 2), + Aic_trans_divider_mask, Aic_trans_divider_limit, Aic_trans_divider_bit); +} + +uint32_t +Aic_x1600_channel::encode_input_size(uint8_t sample_size) +{ + switch (sample_size) + { + case 16: + return Aic_input_size_16bit; + + case 18: + return Aic_input_size_18bit; + + case 20: + return Aic_input_size_20bit; + + case 24: + return Aic_input_size_24bit; + + default: + return Aic_input_size_8bit; + } +} + +uint32_t +Aic_x1600_channel::encode_output_size(uint8_t sample_size) +{ + switch (sample_size) + { + case 16: + return Aic_output_size_16bit; + + case 18: + return Aic_output_size_18bit; + + case 20: + return Aic_output_size_20bit; + + case 24: + return Aic_output_size_24bit; + + default: + return Aic_output_size_8bit; + } +} + +// Obtain a DMA-accessible buffer for sample transfers. + +long +Aic_x1600_channel::get_buffer(uint32_t count, l4_addr_t *addr) +{ + printf("get_buffer(%d)\n", count); + long err = get_dma_region(count, 8, &_vaddr, &_paddr, &_mem); + + if (err) + return err; + + // Set the region size as the requested size, not any allocated size. + + _size = count; + *addr = _vaddr; + printf("size = %d\n", _size); + return L4_EOK; +} + +// Transfer a sample using the given byte count (total sample size), sample rate +// (or frequency), sample unit size (or resolution, width). + +unsigned int +Aic_x1600_channel::transfer(uint32_t count, uint32_t sample_rate, uint8_t sample_size) +{ + printf("transfer(%d, %d, %d)\n", count, sample_rate, sample_size); + + if (count > _size) + return 0; + + /* To play a sample: + + - Configure the sample size using the Aic_output_size settings in Aic_control + - The sample size will be 16- or 24-bit for the MAX9835A, with the X1600 not + supporting 32-bit samples + - Configure the number of channels in Aic_control (Aic_channel_mono or + Aic_channel_stereo) + - For 16-bit samples, select Aic_packed_data in Aic_control if appropriate, + possibly not for the MAX983A + - If two channels are used, select Aic_i2s_msb_right_first in + Aic_i2s_msb_control if appropriate, as well as Aic_i2s_msb_switch_lr if + switching the channels + - Reconfigure the dividers if appropriate + - For DMA, set Aic_enable_trans_dma in Aic_control and the transmit FIFO + threshold value + */ + + // NOTE: Introduce sample size. + + set_trans_frequency(sample_rate); + + /* NOTE: The MAX98357A might require stereo input data, but perhaps the + peripheral can generate the appropriate LRCLK to make it accept mono + data. */ + + _regs[Aic_control] = encode_output_size(sample_size) | + Aic_channel_mono | + Aic_enable_trans_dma | + Aic_enable_playback; + + printf("control = %08x\n", (uint32_t) _regs[Aic_control]); + printf("config = %08x\n", (uint32_t) _regs[Aic_config]); + printf("divider = %d\n", (uint32_t) _regs[Aic_i2s_msb_divider]); + printf("status = %08x\n", (uint32_t) _regs[Aic_i2s_msb_status]); + printf("fifo = %08x\n", (uint32_t) _regs[Aic_fifo_status]); + + // Transfer from the allocated region to the FIFO. Use an incrementing source + // address with source width, destination width and transfer unit reflecting + // the sample size, and with transfers initiated by an empty AIC transmit + // FIFO. + + printf("transfer from %llx to %lx\n", _paddr, _aic_start + Aic_fifo_data); + + unsigned int sample_unit = (sample_size == 8) ? 1 : + (sample_size == 16) ? 2 : + 4; + unsigned int unit_count = count / sample_unit; + + unsigned int to_transfer = _dma->transfer(_paddr, + _aic_start + Aic_fifo_data, + unit_count, + true, + false, + sample_unit, + sample_unit, + sample_unit, + Dma_request_aic_out); + + printf("status = %08x\n", (uint32_t) _regs[Aic_i2s_msb_status]); + printf("fifo = %08x\n", (uint32_t) _regs[Aic_fifo_status]); + + unsigned int transferred = 0; + + if (to_transfer) + transferred = to_transfer ? (unit_count - _dma->wait()) * sample_unit : 0; + +#if 0 + if (transferred) + while (_regs[Aic_fifo_status]); + + _regs[Aic_control] = _regs[Aic_control] & ~Aic_enable_playback; +#endif + + return transferred; +} + + + +// Initialise the AIC/I2S controller. + +Aic_x1600_chip::Aic_x1600_chip(l4_addr_t aic_start, l4_addr_t start, l4_addr_t end, + Cpm_x1600_chip *cpm) +: _aic_start(aic_start), _start(start), _end(end), _cpm(cpm) +{ +} + +// Obtain a channel object. + +Aic_x1600_channel * +Aic_x1600_chip::get_channel(uint8_t channel, Dma_x1600_channel *dma) +{ + if (channel < 1) + return new Aic_x1600_channel(_aic_start, _start, Clock_i2s0_rx, Clock_i2s0_tx, _cpm, dma); + else + throw -L4_EINVAL; +} + + + +// C language interface functions. + +void *x1600_aic_init(l4_addr_t aic_start, l4_addr_t start, l4_addr_t end, void *cpm) +{ + return (void *) new Aic_x1600_chip(aic_start, start, end, static_cast(cpm)); +} + +void *x1600_aic_get_channel(void *aic, uint8_t channel, void *dma) +{ + return static_cast(aic)->get_channel(channel, + static_cast(dma)); +} + +long x1600_aic_get_buffer(void *channel, uint32_t count, l4_addr_t *addr) +{ + return static_cast(channel)->get_buffer(count, addr); +} + +unsigned int x1600_aic_transfer(void *channel, uint32_t count, uint32_t sample_rate, uint8_t sample_size) +{ + return static_cast(channel)->transfer(count, sample_rate, sample_size); +}