From 76f8925d237b88e53854e9782e4ad87294bc8da2 Mon Sep 17 00:00:00 2001 From: Andrew Rice Date: Sat, 19 Sep 2026 07:52:18 +1000 Subject: [PATCH] ipodnano3g: power, RTC, backlight, battery and audio Fills in the stubs the Nano 3G port was left with, taking each from how the original firmware drives the same hardware. Testing evidence: firmware/target/arm/s5l8702/ipodnano3g/TESTING.md. Co-authored-by: Claude Opus 5 Change-Id: Iea5314769502f5941a176600869591756c5e3233 --- apps/settings_list.c | 2 +- firmware/SOURCES | 4 +- firmware/drivers/audio/wm8975.c | 47 ++++++++++++- firmware/export/audiohw.h | 2 +- firmware/export/config/ipodnano3g.h | 15 ++--- firmware/export/wm8975.h | 3 + firmware/export/wmcodec.h | 6 ++ firmware/target/arm/ipod/button-clickwheel.c | 5 ++ .../target/arm/s5l8702/ipodnano3g/TESTING.md | 10 +++ .../arm/s5l8702/ipodnano3g/adc-nano3g.c | 43 ++++++++++-- .../arm/s5l8702/ipodnano3g/backlight-nano3g.c | 7 +- .../arm/s5l8702/ipodnano3g/cscodec-nano3g.c | 67 ------------------- .../arm/s5l8702/ipodnano3g/pmu-nano3g.c | 51 ++++++++++---- .../arm/s5l8702/ipodnano3g/pmu-target.h | 18 ++++- .../arm/s5l8702/ipodnano3g/power-nano3g.c | 14 +++- .../arm/s5l8702/ipodnano3g/rtc-nano3g.c | 27 +++----- .../arm/s5l8702/ipodnano3g/wmcodec-nano3g.c | 65 ++++++++++++++++++ firmware/target/arm/s5l8702/pcm-s5l8702.c | 29 ++++++-- 18 files changed, 286 insertions(+), 129 deletions(-) delete mode 100644 firmware/target/arm/s5l8702/ipodnano3g/cscodec-nano3g.c create mode 100644 firmware/target/arm/s5l8702/ipodnano3g/wmcodec-nano3g.c diff --git a/apps/settings_list.c b/apps/settings_list.c index 24edac9bb7..17f22dfd11 100644 --- a/apps/settings_list.c +++ b/apps/settings_list.c @@ -426,7 +426,7 @@ static const char graphic_numeric[] = "graphic,numeric"; # define DEFAULT_REC_MIC_GAIN 0 # define DEFAULT_REC_LEFT_GAIN 0 # define DEFAULT_REC_RIGHT_GAIN 0 -# elif defined(HAVE_WM8975) +# elif defined(HAVE_WM8975) || defined(HAVE_WM1870) # define DEFAULT_REC_MIC_GAIN 16 # define DEFAULT_REC_LEFT_GAIN 0 # define DEFAULT_REC_RIGHT_GAIN 0 diff --git a/firmware/SOURCES b/firmware/SOURCES index 0c36888baa..39326ccb9a 100644 --- a/firmware/SOURCES +++ b/firmware/SOURCES @@ -575,7 +575,7 @@ drivers/audio/wm8740.c drivers/audio/wm8751.c #elif defined(HAVE_WM8978) drivers/audio/wm8978.c -#elif defined(HAVE_WM8975) +#elif defined(HAVE_WM8975) || defined(HAVE_WM1870) drivers/audio/wm8975.c #elif defined(HAVE_WM8985) drivers/audio/wm8985.c @@ -1647,7 +1647,7 @@ target/arm/s5l8702/ipodnano3g/serial-nano3g.c #endif #ifndef BOOTLOADER target/arm/s5l8702/ipodnano3g/audio-nano3g.c -target/arm/s5l8702/ipodnano3g/cscodec-nano3g.c // TODO: -> wmcodec-nano3g.c +target/arm/s5l8702/ipodnano3g/wmcodec-nano3g.c #endif #endif /* IPOD_NANO3G */ diff --git a/firmware/drivers/audio/wm8975.c b/firmware/drivers/audio/wm8975.c index 46128cc79d..848d972f4c 100644 --- a/firmware/drivers/audio/wm8975.c +++ b/firmware/drivers/audio/wm8975.c @@ -25,6 +25,12 @@ * ****************************************************************************/ +/* + * Wolfson WM8975 codec driver. It also covers the WM1870 fitted to the iPod + * Nano 3G (HAVE_WM1870), which answers the same registers and adds a few + * above them; the differences are behind that define. + */ + #include "logf.h" #include "system.h" #include "kernel.h" @@ -34,6 +40,9 @@ #include "wmcodec.h" #include "audiohw.h" +#ifdef HAVE_WM1870 +#include "pcm_sampr.h" +#endif static unsigned short wm8975_regs[WM8975_NUM_REGISTERS] = { @@ -126,7 +135,35 @@ void audiohw_postinit(void) wm8975_write(PWRMGMT1, wm8975_regs[PWRMGMT1]); audiohw_mute(false); } -#else /* !IPOD_NANO2G */ +#elif defined(HAVE_WM1870) +/* The WM1870, set up as the original firmware does it. It answers the + * WM8975's registers and a few above them. */ + +void audiohw_preinit(void) +{ + wm8975_write(RESET, RESET_RESET); + + wm8975_write(PWRMGMT1, PWRMGMT1_VMIDSEL_50K | PWRMGMT1_VREF); + wm8975_write(AINTFCE, AINTFCE_MS | AINTFCE_LRP_I2S_RLO + | AINTFCE_IWL_16BIT | AINTFCE_FORMAT_I2S); + wm8975_write(PWRMGMT2, PWRMGMT2_DACL | PWRMGMT2_DACR + | PWRMGMT2_LOUT1 | PWRMGMT2_ROUT1); + + wm8975_write(LOUTMIX1, LOUTMIX1_LD2LO | LOUTMIX1_LI2LOVOL(2) + | LOUTMIX1_LMIXSEL_LADCIN); + wm8975_write(ROUTMIX2, ROUTMIX2_RD2RO | ROUTMIX2_RI2ROVOL(2)); + wm8975_write(ROUTMIX1, ROUTMIX1_RMIXSEL_RADCIN); + + wmcodec_write(WM1870_R67, 0x008); + wm8975_write(ADDCTRL2, ADDCTRL2_OUT3SW_MONOOUT | ADDCTRL2_LRCM); + wm8975_write(ADDCTRL3, ADDCTRL3_VROI); +} + +void audiohw_postinit(void) +{ + audiohw_mute(false); +} +#else /* !IPOD_NANO2G && !HAVE_WM1870 */ void audiohw_preinit(void) { /* POWER UP SEQUENCE */ @@ -240,7 +277,15 @@ void audiohw_close(void) /* Note: Disable output before calling this function */ void audiohw_set_frequency(int fsel) { +#ifdef HAVE_WM1870 + /* The rate codes depend on the MCLK, so the target supplies them */ + unsigned short sampctrl = wmcodec_sampctrl(hw_freq_sampr[fsel]); + + if (sampctrl) + wm8975_write(SAMPCTRL, sampctrl); +#else (void)fsel; +#endif } #ifdef HAVE_RECORDING diff --git a/firmware/export/audiohw.h b/firmware/export/audiohw.h index 6525cc0452..4b68144abb 100644 --- a/firmware/export/audiohw.h +++ b/firmware/export/audiohw.h @@ -169,7 +169,7 @@ struct sound_settings_info #include "wm8751.h" #elif defined(HAVE_WM8978) #include "wm8978.h" -#elif defined(HAVE_WM8975) +#elif defined(HAVE_WM8975) || defined(HAVE_WM1870) #include "wm8975.h" #elif defined(HAVE_WM8985) #include "wm8985.h" diff --git a/firmware/export/config/ipodnano3g.h b/firmware/export/config/ipodnano3g.h index 6bcb05f2e3..5e1b872e3a 100644 --- a/firmware/export/config/ipodnano3g.h +++ b/firmware/export/config/ipodnano3g.h @@ -138,22 +138,17 @@ #define HAVE_MORSE_INPUT /* define this if you have a real-time clock */ -// TODO -//#define CONFIG_RTC RTC_NANO3G -#define CONFIG_RTC 0 +#define CONFIG_RTC RTC_NANO3G /* Define if the device can wake from an RTC alarm */ //#define HAVE_RTC_ALARM #define CONFIG_LCD LCD_IPOD6GNANO3G4G -// TODO -#if 0 -/* Define the type of audio codec */ -#define HAVE_WM8975 -#endif -// XXX: dummy for preliminary build, WRONG CODEC!!! -#define HAVE_CS42L55 +/* Define the type of audio codec. The Nano 3G has a Wolfson WM1870 at I2C + * address 0x34, which the original firmware drives as a WM8975 plus a few + * registers above the WM8975's range, so wm8975.c covers it. */ +#define HAVE_WM1870 #define HAVE_PCM_DMA_ADDRESS diff --git a/firmware/export/wm8975.h b/firmware/export/wm8975.h index 8e5e109398..354f798033 100644 --- a/firmware/export/wm8975.h +++ b/firmware/export/wm8975.h @@ -313,4 +313,7 @@ void audiohw_enable_lineout(bool enable); #define WM8975_88200HZ 0x7f #define WM8975_96000HZ 0x5d +/* The WM1870 (HAVE_WM1870) answers these registers and a few above them */ +#define WM1870_R67 0x43 + #endif /* _WM8975_H */ diff --git a/firmware/export/wmcodec.h b/firmware/export/wmcodec.h index f57933eb51..d2938eb1be 100644 --- a/firmware/export/wmcodec.h +++ b/firmware/export/wmcodec.h @@ -20,3 +20,9 @@ void wmcodec_write(int reg, int data); +#ifdef HAVE_WM1870 +/* SAMPCTRL for a sample rate, which depends on the MCLK the target feeds + * the codec. Zero if the target cannot make that rate. */ +unsigned short wmcodec_sampctrl(unsigned long rate); +#endif + diff --git a/firmware/target/arm/ipod/button-clickwheel.c b/firmware/target/arm/ipod/button-clickwheel.c index fc10efc9a4..1f4ca13dc8 100644 --- a/firmware/target/arm/ipod/button-clickwheel.c +++ b/firmware/target/arm/ipod/button-clickwheel.c @@ -425,8 +425,13 @@ bool headphones_inserted(void) #if CONFIG_CPU==S5L8701 return ((PDAT14 & (1 << 5)) != 0); #elif CONFIG_CPU==S5L8702 || CONFIG_CPU==S5L8720 +#ifdef IPOD_NANO3G + /* GPIO 7.0, high with headphones in (measured) */ + return (PDAT(7) & (1 << 0)) != 0; +#else return ((PDAT10 & (1 << 6)) != 0); #endif +#endif } #endif diff --git a/firmware/target/arm/s5l8702/ipodnano3g/TESTING.md b/firmware/target/arm/s5l8702/ipodnano3g/TESTING.md index 88702883a4..bf26391dbc 100644 --- a/firmware/target/arm/s5l8702/ipodnano3g/TESTING.md +++ b/firmware/target/arm/s5l8702/ipodnano3g/TESTING.md @@ -21,3 +21,13 @@ Tested on the 4GB unit from DFU: host sees "Nano 3G NAND", 2113537 x 2048-byte sectors, write protect on. `nandcheck.py collect` reads it in seven minutes on Linux and Windows, no ECC failures or timeouts, and the archive passes the host FTL suite. + +## Power, RTC, backlight, battery, audio + +Tested on the 4GB unit: 44.1 kHz WAV plays with elapsed time running; +battery reads 4077 mV, charging true/false correctly with a cell below +full; a time set in Rockbox survives a reboot; backlight and brightness +work; "pause on headphone unplug" pauses on disconnect. + +Not tested: sample rates other than 44.1 kHz, and the charge-complete +status bits (never seen set). diff --git a/firmware/target/arm/s5l8702/ipodnano3g/adc-nano3g.c b/firmware/target/arm/s5l8702/ipodnano3g/adc-nano3g.c index c1e63e76f0..794dc07aa1 100644 --- a/firmware/target/arm/s5l8702/ipodnano3g/adc-nano3g.c +++ b/firmware/target/arm/s5l8702/ipodnano3g/adc-nano3g.c @@ -27,17 +27,51 @@ #include "pmu-target.h" #include "kernel.h" +/* Only the battery input is known: the original firmware selects it with + * 0x24 and converts with 2500 mV + raw * 2000 mV / 1023 (measured: 4062 mV + * on battery, 4110 mV charging). The USB data and accessory inputs are not + * identified yet and read as 0. */ +static const struct pmu_adc_channel adc_channels[] = +{ + [ADC_BATTERY] = + { + .name = "Battery", + .mux = 0x24, + .samples = 4, + .offset_mv = 2500, + .span_mv = 2000, + }, + [ADC_USBDATA] = + { + .name = "USB data", + }, + [ADC_ACCESSORY] = + { + .name = "Accessory", + }, +}; + +unsigned short adc_read_millivolts(int channel) +{ + const struct pmu_adc_channel *ch = &adc_channels[channel]; + + if (!ch->samples) + return 0; + return pmu_adc_raw2mv(ch, pmu_read_adc(ch)); +} + /* Returns battery voltage [millivolts] */ unsigned int adc_read_battery_voltage(void) { - return 0; + return adc_read_millivolts(ADC_BATTERY); } /* API functions */ unsigned short adc_read(int channel) { - (void) channel; - return 0; + const struct pmu_adc_channel *ch = &adc_channels[channel]; + + return ch->samples ? pmu_read_adc(ch) : 0; } int adc_read_accessory_resistor(void) @@ -53,8 +87,7 @@ unsigned int adc_read_usbdata_voltage(bool dp) const char *adc_name(int channel) { - (void) channel; - return ""; + return adc_channels[channel].name; } void adc_init(void) diff --git a/firmware/target/arm/s5l8702/ipodnano3g/backlight-nano3g.c b/firmware/target/arm/s5l8702/ipodnano3g/backlight-nano3g.c index 34fc198471..5b557aabd5 100644 --- a/firmware/target/arm/s5l8702/ipodnano3g/backlight-nano3g.c +++ b/firmware/target/arm/s5l8702/ipodnano3g/backlight-nano3g.c @@ -30,7 +30,7 @@ #include "lcd-s5l8702.h" #endif -// TODO: test +/* The original firmware writes the level to bits 0..4 as well */ void backlight_hw_brightness(int brightness) { pmu_write(D1671_REG_LEDCTL, @@ -43,8 +43,11 @@ void backlight_hw_on(void) if (!lcd_active()) lcd_awake(); #endif + /* As the original firmware turns it on: bits 7 and 6 set, + * bit 5 clear. Bit 6's meaning is unknown. */ pmu_write(D1671_REG_LEDCTL, - (pmu_read(D1671_REG_LEDCTL) | D1671_LEDCTL_ENABLE)); + (pmu_read(D1671_REG_LEDCTL) & ~0x20) + | D1671_LEDCTL_ENABLE | D1671_LEDCTL_UNKNOWN); } void backlight_hw_off(void) diff --git a/firmware/target/arm/s5l8702/ipodnano3g/cscodec-nano3g.c b/firmware/target/arm/s5l8702/ipodnano3g/cscodec-nano3g.c deleted file mode 100644 index e3ebfc9525..0000000000 --- a/firmware/target/arm/s5l8702/ipodnano3g/cscodec-nano3g.c +++ /dev/null @@ -1,67 +0,0 @@ -/*************************************************************************** - * __________ __ ___. - * Open \______ \ ____ ____ | | _\_ |__ _______ ___ - * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / - * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < - * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ - * \/ \/ \/ \/ \/ - * - * S5L8702-specific code for Cirrus codecs - * - * Copyright (c) 2010 Michael Sparmann - * - * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - ****************************************************************************/ - -#include "system.h" -#include "audiohw.h" -#include "i2c-s5l8702.h" -#include "s5l87xx.h" -#include "cscodec.h" - -void audiohw_init(void) -{ -#ifdef HAVE_CS42L55 - audiohw_preinit(); -#endif -} - -unsigned char cscodec_read(int reg) -{ - // TODO: not tested - unsigned char data; - i2c_read(0, 0x94, reg, 1, &data); - return data; -} - -void cscodec_write(int reg, unsigned char data) -{ - // XXX: not tested - i2c_write(0, 0x94, reg, 1, &data); -} - -void cscodec_power(bool state) -{ - (void)state; //TODO: Figure out which LDO this is -} - -void cscodec_reset(bool state) -{ - // XXX: not tested - if (state) PDAT(3) &= ~8; - else PDAT(3) |= 8; -} - -void cscodec_clock(bool state) -{ - // XXX: not tested - if (state) CLKCON3 &= ~0xffff; - else CLKCON3 |= 0x8000; -} diff --git a/firmware/target/arm/s5l8702/ipodnano3g/pmu-nano3g.c b/firmware/target/arm/s5l8702/ipodnano3g/pmu-nano3g.c index f3a6aa521a..e688e01402 100644 --- a/firmware/target/arm/s5l8702/ipodnano3g/pmu-nano3g.c +++ b/firmware/target/arm/s5l8702/ipodnano3g/pmu-nano3g.c @@ -112,16 +112,26 @@ void pmu_set_cpu_voltage(bool high) #endif #if (CONFIG_RTC == RTC_NANO3G) +/* The RTC, as the original firmware uses it: + * registers 0x40..0x45 hold seconds, minutes, hours, day, month and year + * (since 2000), in binary. Bit 6 of the seconds register is set whenever + * the time is written and masked off when it is read. The buffer holds the + * six values in that order. */ void pmu_read_rtc(unsigned char* buffer) { - // TODO - (void) buffer; + pmu_read_multiple(D1671_REG_RTCSEC, 6, buffer); + buffer[0] &= ~D1671_RTCSEC_SET; } void pmu_write_rtc(unsigned char* buffer) { - // TODO - (void) buffer; + int i; + + /* One register at a time, seconds first, as the original firmware + * does */ + for (i = 0; i < 6; i++) + pmu_write(D1671_REG_RTCSEC + i, + buffer[i] | (i == 0 ? D1671_RTCSEC_SET : 0)); } #endif @@ -132,7 +142,10 @@ void pmu_set_usblimit(bool fast_charge) } /* - * ADC + * ADC, as the original firmware drives it: write the channel's + * input selection to register 0x30 with bit 3 set to start a conversion, + * wait for bit 3 to clear, and read the 10-bit result from 0x32 (bits 9..2) + * and 0x31 (bits 1..0). 0x30 idles at 0x20. */ static struct mutex pmu_adc_mutex; @@ -140,21 +153,31 @@ static struct mutex pmu_adc_mutex; unsigned short pmu_adc_raw2mv( const struct pmu_adc_channel *ch, unsigned short raw) { - // TODO - (void) ch; - (void) raw; - return 0; + return ch->offset_mv + raw * ch->span_mv / 1023; } -/* returns raw value */ +/* returns raw value, averaged over ch->samples conversions */ unsigned short pmu_read_adc(const struct pmu_adc_channel *ch) { - // TODO - int raw = 0; + unsigned int sum = 0; + int i, tries; + mutex_lock(&pmu_adc_mutex); - (void) ch; + for (i = 0; i < ch->samples; i++) + { + pmu_write(D1671_REG_ADCCTL, ch->mux | D1671_ADCCTL_START); + for (tries = 0; tries < 20; tries++) + { + udelay(50); + if (!(pmu_read(D1671_REG_ADCCTL) & D1671_ADCCTL_START)) + break; + } + sum += (pmu_read(D1671_REG_ADCHI) << 2) + | (pmu_read(D1671_REG_ADCLO) & 3); + } + pmu_write(D1671_REG_ADCCTL, D1671_ADCCTL_IDLE); mutex_unlock(&pmu_adc_mutex); - return raw; + return ch->samples ? sum / ch->samples : 0; } /* diff --git a/firmware/target/arm/s5l8702/ipodnano3g/pmu-target.h b/firmware/target/arm/s5l8702/ipodnano3g/pmu-target.h index 5278102ffa..f8a50ef8e5 100644 --- a/firmware/target/arm/s5l8702/ipodnano3g/pmu-target.h +++ b/firmware/target/arm/s5l8702/ipodnano3g/pmu-target.h @@ -37,6 +37,9 @@ enum d1671_regs { D1671_REG_SYSCTRLA = 0x08, D1671_REG_LEDCTL = 0x20, D1671_REG_CHCTL = 0x21, // TBC + D1671_REG_ADCCTL = 0x30, + D1671_REG_ADCLO = 0x31, + D1671_REG_ADCHI = 0x32, D1671_REG_RTCSEC = 0x40, // TBC }; @@ -87,13 +90,26 @@ enum d1671_reg_chctl { D1671_CHCTL_FASTCHRG = 0x01, /* 100/500mA USB limit */ }; +enum d1671_reg_rtcsec { + /* set when the time is written, masked off when it is read */ + D1671_RTCSEC_SET = 0x40, +}; + +enum d1671_reg_adcctl { + D1671_ADCCTL_START = 0x08, + D1671_ADCCTL_IDLE = 0x20, /* register's value at rest */ +}; + /* GPIO for external PMU interrupt */ #define GPIO_EINT_PMU 0x7b struct pmu_adc_channel { const char *name; - // TODO + uint8_t mux; /* register 0x30 value selecting the input */ + uint8_t samples; /* conversions averaged, 0 = not readable */ + unsigned short offset_mv; /* millivolts at raw 0 */ + unsigned short span_mv; /* millivolts from raw 0 to raw 1023 */ }; void pmu_preinit(void); diff --git a/firmware/target/arm/s5l8702/ipodnano3g/power-nano3g.c b/firmware/target/arm/s5l8702/ipodnano3g/power-nano3g.c index 8a957aa89a..e4e8aad8bb 100644 --- a/firmware/target/arm/s5l8702/ipodnano3g/power-nano3g.c +++ b/firmware/target/arm/s5l8702/ipodnano3g/power-nano3g.c @@ -58,9 +58,19 @@ unsigned int power_input_status(void) return status; } +/* As the original firmware decides it: the charger is + * enabled (CHCTL bits 1..6), not suspended (SYSCTRLA bit 2), and not done: + * STATUSB bits 1..2 clear. Measured: they read 0 while a 4.1 V cell + * charges from USB, so they are taken as the charge-complete indication; + * they have not been seen set. */ bool charging_state(void) { - // TODO - return false; + if (!(power_input_status() & POWER_INPUT_CHARGER)) + return false; + if (!(pmu_read(D1671_REG_CHCTL) & 0x7e)) + return false; + if (pmu_read(D1671_REG_SYSCTRLA) & 0x04) + return false; + return !(pmu_read(D1671_REG_STATUSB) & 0x06); } #endif /* CONFIG_CHARGING */ diff --git a/firmware/target/arm/s5l8702/ipodnano3g/rtc-nano3g.c b/firmware/target/arm/s5l8702/ipodnano3g/rtc-nano3g.c index 215689c725..6f79e278e4 100644 --- a/firmware/target/arm/s5l8702/ipodnano3g/rtc-nano3g.c +++ b/firmware/target/arm/s5l8702/ipodnano3g/rtc-nano3g.c @@ -24,6 +24,8 @@ #include "pmu-target.h" #include "timefuncs.h" +/* The PMU keeps seconds, minutes, hours, day, month and year (since 2000) + * in binary, and no weekday; see pmu_read_rtc() */ void rtc_init(void) { @@ -31,20 +33,16 @@ void rtc_init(void) int rtc_read_datetime(struct tm *tm) { - unsigned int i; - unsigned char buf[7]; + unsigned char buf[6]; pmu_read_rtc(buf); - for (i = 0; i < sizeof(buf); i++) - buf[i] = BCD2DEC(buf[i]); - tm->tm_sec = buf[0]; tm->tm_min = buf[1]; tm->tm_hour = buf[2]; - tm->tm_mday = buf[4]; - tm->tm_mon = buf[5] - 1; - tm->tm_year = buf[6] + 100; + tm->tm_mday = buf[3]; + tm->tm_mon = buf[4] - 1; + tm->tm_year = buf[5] + 100; tm->tm_yday = 0; /* Not implemented for now */ set_day_of_week(tm); @@ -53,19 +51,14 @@ int rtc_read_datetime(struct tm *tm) int rtc_write_datetime(const struct tm *tm) { - unsigned int i; - unsigned char buf[7]; + unsigned char buf[6]; buf[0] = tm->tm_sec; buf[1] = tm->tm_min; buf[2] = tm->tm_hour; - buf[3] = tm->tm_wday; - buf[4] = tm->tm_mday; - buf[5] = tm->tm_mon + 1; - buf[6] = tm->tm_year - 100; - - for (i = 0; i < sizeof(buf); i++) - buf[i] = DEC2BCD(buf[i]); + buf[3] = tm->tm_mday; + buf[4] = tm->tm_mon + 1; + buf[5] = tm->tm_year - 100; pmu_write_rtc(buf); return 0; diff --git a/firmware/target/arm/s5l8702/ipodnano3g/wmcodec-nano3g.c b/firmware/target/arm/s5l8702/ipodnano3g/wmcodec-nano3g.c new file mode 100644 index 0000000000..2043bfb486 --- /dev/null +++ b/firmware/target/arm/s5l8702/ipodnano3g/wmcodec-nano3g.c @@ -0,0 +1,65 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * + * iPod Nano 3G code for the Wolfson codec (WM1870) + * + * Copyright (c) 2010 Michael Sparmann + * Copyright (C) 2026 by Andrew Rice + * + * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include "system.h" +#include "audiohw.h" +#include "i2c-s5l8702.h" +#include "wmcodec.h" + +/* The PCM driver sets the codec up (audiohw_preinit()) and controls its + * MCLK */ +void audiohw_init(void) +{ +} + +/* The codec runs from a 12 MHz MCLK here, so its rate codes are the USB + * mode ones, with a BCLK divider, as the original firmware sets them. */ +unsigned short wmcodec_sampctrl(unsigned long rate) +{ + static const struct + { + unsigned long rate; + unsigned short sampctrl; + } rates[] = + { + { SAMPR_8, 0x10d }, { SAMPR_11, 0x133 }, { SAMPR_12, 0x111 }, + { SAMPR_16, 0x115 }, { SAMPR_22, 0x137 }, { SAMPR_24, 0x139 }, + { SAMPR_32, 0x119 }, { SAMPR_44, 0x123 }, { SAMPR_48, 0x081 }, + { SAMPR_88, 0x0bf }, { SAMPR_96, 0x09d }, + }; + unsigned int i; + + for (i = 0; i < ARRAYLEN(rates); i++) + if (rates[i].rate == rate) + return rates[i].sampctrl; + return 0; +} + +/* 7-bit register and 9-bit value, as two bytes to address 0x34 on I2C + * bus 0. The codec does not acknowledge reads. */ +void wmcodec_write(int reg, int data) +{ + unsigned char d = data & 0xff; + + i2c_write(0, 0x34, (reg << 1) | ((data >> 8) & 1), 1, &d); +} diff --git a/firmware/target/arm/s5l8702/pcm-s5l8702.c b/firmware/target/arm/s5l8702/pcm-s5l8702.c index 9af30b9c4b..57ad20f7c4 100644 --- a/firmware/target/arm/s5l8702/pcm-s5l8702.c +++ b/firmware/target/arm/s5l8702/pcm-s5l8702.c @@ -80,6 +80,18 @@ static struct dmac_ch_cfg dma_play_ch_cfg = { #define CHUNK_MAX_BYTES (LLI_MAX_BYTES * 1) #define WATERMARK_BYTES (PCM_WATERMARK * 4) +/* I2S0 TX setup and start command. The Nano 3G uses the values Apple's + * firmware writes for its WM1870, which is the I2S clock master; the + * ipod6g values have TXCON bits 3-4 and TXCOM bit 3 set as well, and with + * them I2S0 never takes the codec's BCLK/LRCK. */ +#ifdef IPOD_NANO3G +#define I2STXCON_SETUP 0x0b100001 +#define I2STXCOM_START 0x6 +#else +#define I2STXCON_SETUP 0xb100019 +#define I2STXCOM_START 0xe +#endif + static volatile int locked = 0; static unsigned char dblbuf[2][WATERMARK_BYTES] CACHEALIGN_ATTR; static int active_dblbuf; @@ -152,7 +164,7 @@ static void sink_dma_start(const void* addr, size_t size) sink_dma_stop(); pcm_remaining = size; - I2STXCOM = 0xe; + I2STXCOM = I2STXCOM_START; dma_play_callback((void*)addr); } @@ -162,9 +174,16 @@ static void sink_dma_start(const void* addr, size_t size) /* set the configured PCM frequency */ static void sink_set_freq(uint16_t freq) { +#ifdef HAVE_CS42L55 static uint16_t last_clkcon3l = 0; - uint16_t clkcon3l; +#else + /* Not a valid setting, so the first call always programs MCLK. The + * CS42L55 driver starts MCLK itself; the WM8975 driver does not. */ + static uint16_t last_clkcon3l = 0xffff; +#endif + uint16_t clkcon3l = 0; /* OSC0 -> 12 MHz */ +#ifdef HAVE_CS42L55 /* For unknown reasons, s5l8702 I2S controller does not synchronize * with CS42L55 at 32000 Hz. To fix it, the CODEC is configured with * a sample rate of 48000 Hz and MCLK is decreased 1/3 to 8 Mhz, @@ -175,9 +194,7 @@ static void sink_set_freq(uint16_t freq) freq = HW_FREQ_48; clkcon3l = 0x3028; /* PLL2 / 3 / 9 -> 8 MHz */ } - else { - clkcon3l = 0; /* OSC0 -> 12 MHz */ - } +#endif /* configure MCLK */ /* TODO: maybe all CLKCON management should be moved to @@ -201,7 +218,7 @@ static void sink_dma_init(void) dmac_ch_init(&dma_play_ch, &dma_play_ch_cfg); - I2STXCON = 0xb100019; + I2STXCON = I2STXCON_SETUP; I2SCLKCON = 1; audiohw_preinit();