diff options
author | Aidan MacDonald <amachronic@protonmail.com> | 2021-05-23 17:30:58 +0100 |
---|---|---|
committer | Aidan MacDonald <amachronic@protonmail.com> | 2021-07-13 22:01:33 +0100 |
commit | 4c60bc9e681865fcfc149775a1ed7ccd2613d5bf (patch) | |
tree | 99f8d91af2c171cf3843f0c14d41a20d9dc29c4f /firmware/target | |
parent | 3abb7c5dd5be2ec6744bfc0a80967b20f1b59e30 (diff) |
New port: Shanling Q1 native
- Audio playback works
- Touchscreen and buttons work
- Bootloader works and is capable of dual boot
- Plugins are working
- Cabbiev2 theme has been ported
- Stable for general usage
Thanks to Marc Aarts for porting Cabbiev2 and plugin bitmaps.
There's a few minor known issues:
- Bootloader must be installed manually using 'usbboot' as there is
no support in jztool yet.
- Keymaps may be lacking, need further testing and feedback.
- Some plugins may not be fully adapted to the screen size and could
benefit from further tweaking.
- LCD shows abnormal effects under some circumstances: for example,
after viewing a mostly black screen an afterimage appears briefly
when going back to a brightly-lit screen. Sudden power-off without
proper shutdown of the backlight causes a "dissolving" effect.
- CW2015 battery reporting driver is buggy, and disabled for now.
Battery reporting is currently voltage-based using the AXP192.
Change-Id: I635e83f02a880192c5a82cb0861ad3a61c137c3a
Diffstat (limited to 'firmware/target')
17 files changed, 1324 insertions, 3 deletions
diff --git a/firmware/target/hosted/sdl/sim-ui-defines.h b/firmware/target/hosted/sdl/sim-ui-defines.h index 5b4030bd37..5b83c1bf12 100644 --- a/firmware/target/hosted/sdl/sim-ui-defines.h +++ b/firmware/target/hosted/sdl/sim-ui-defines.h @@ -529,6 +529,14 @@ #define UI_LCD_POSY 15 +#elif defined(SHANLING_Q1) +#define UI_TITLE "Shanling Q1" +#define UI_WIDTH 466 +#define UI_HEIGHT 526 +#define UI_LCD_POSX 46 +#define UI_LCD_POSY 61 + + #elif defined(SIMULATOR) #error no UI defines #endif diff --git a/firmware/target/mips/ingenic_x1000/debug-x1000.c b/firmware/target/mips/ingenic_x1000/debug-x1000.c index fe469b1a72..1965b0b74e 100644 --- a/firmware/target/mips/ingenic_x1000/debug-x1000.c +++ b/firmware/target/mips/ingenic_x1000/debug-x1000.c @@ -152,6 +152,9 @@ extern bool dbg_fiiom3k_touchpad(void); #ifdef HAVE_AXP_PMU extern bool axp_debug_menu(void); #endif +#ifdef HAVE_CW2015 +extern bool cw2015_debug_menu(void); +#endif /* Menu definition */ static const struct { @@ -170,6 +173,9 @@ static const struct { #ifdef HAVE_AXP_PMU {"Power stats", &axp_debug_menu}, #endif +#ifdef HAVE_CW2015 + {"CW2015 debug", &cw2015_debug_menu}, +#endif }; static int hw_info_menu_action_cb(int btn, struct gui_synclist* lists) diff --git a/firmware/target/mips/ingenic_x1000/msc-x1000.c b/firmware/target/mips/ingenic_x1000/msc-x1000.c index 3b7df1dd01..d0359a53e2 100644 --- a/firmware/target/mips/ingenic_x1000/msc-x1000.c +++ b/firmware/target/mips/ingenic_x1000/msc-x1000.c @@ -42,7 +42,7 @@ #define DEBOUNCE_TIME (HZ/10) static const msc_config msc_configs[] = { -#ifdef FIIO_M3K +#if defined(FIIO_M3K) #define MSC_CLOCK_SOURCE X1000_CLK_SCLK_A { .msc_nr = 0, @@ -52,6 +52,17 @@ static const msc_config msc_configs[] = { .cd_gpio = GPIO_MSC0_CD, .cd_active_level = 0, }, +#elif defined(SHANLING_Q1) +#define MSC_CLOCK_SOURCE X1000_CLK_MPLL + { + .msc_nr = 0, + .msc_type = MSC_TYPE_SD, + .bus_width = 4, + .label = "microSD", + .cd_gpio = GPIO_MSC0_CD, + .cd_active_level = 0, + }, + /* NOTE: SDIO wifi card is on msc1 */ #else # error "Please add X1000 MSC config" #endif diff --git a/firmware/target/mips/ingenic_x1000/nand-x1000.c b/firmware/target/mips/ingenic_x1000/nand-x1000.c index b76efe65e5..de6eb2fb67 100644 --- a/firmware/target/mips/ingenic_x1000/nand-x1000.c +++ b/firmware/target/mips/ingenic_x1000/nand-x1000.c @@ -58,7 +58,7 @@ #define FREG_STATUS_ECC_UNCOR_ERR (2 << 4) const nand_chip supported_nand_chips[] = { -#if defined(FIIO_M3K) +#if defined(FIIO_M3K) || defined(SHANLING_Q1) { /* ATO25D1GA */ .mf_id = 0x9b, diff --git a/firmware/target/mips/ingenic_x1000/shanlingq1/adc-target.h b/firmware/target/mips/ingenic_x1000/shanlingq1/adc-target.h new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/firmware/target/mips/ingenic_x1000/shanlingq1/adc-target.h diff --git a/firmware/target/mips/ingenic_x1000/shanlingq1/audiohw-shanlingq1.c b/firmware/target/mips/ingenic_x1000/shanlingq1/audiohw-shanlingq1.c new file mode 100644 index 0000000000..7314f20412 --- /dev/null +++ b/firmware/target/mips/ingenic_x1000/shanlingq1/audiohw-shanlingq1.c @@ -0,0 +1,191 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2021 Aidan MacDonald + * + * 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 "audiohw.h" +#include "system.h" +#include "pcm_sampr.h" +#include "aic-x1000.h" +#include "i2c-x1000.h" +#include "gpio-x1000.h" +#include "x1000/aic.h" +#include "x1000/cpm.h" + +/* Codec has an dedicated oscillator connected, so it can operate + * as i2s master or slave. I can't distinguish any difference in + * terms of audio quality or power consumption. Code is left here + * for reference in case it proves useful to change it. */ +#define CODEC_MASTER_MODE 0 + +static int cur_fsel = HW_FREQ_48; +static int cur_vol_l = 0, cur_vol_r = 0; +static int cur_filter = 0; +static enum es9218_amp_mode cur_amp_mode = ES9218_AMP_MODE_1VRMS; + +static void codec_start(void) +{ + es9218_open(); + es9218_mute(true); + es9218_set_iface_role(CODEC_MASTER_MODE ? ES9218_IFACE_ROLE_MASTER + : ES9218_IFACE_ROLE_SLAVE); + es9218_set_iface_format(ES9218_IFACE_FORMAT_I2S, ES9218_IFACE_BITS_32); + es9218_set_dpll_bandwidth(10); + es9218_set_thd_compensation(true); + es9218_set_thd_coeffs(0, 0); + audiohw_set_filter_roll_off(cur_filter); + audiohw_set_frequency(cur_fsel); + audiohw_set_volume(cur_vol_l, cur_vol_r); + es9218_set_amp_mode(cur_amp_mode); +} + +static void codec_stop(void) +{ + es9218_mute(true); + es9218_close(); + mdelay(1); +} + +void audiohw_init(void) +{ + /* Configure AIC */ + aic_set_external_codec(true); + aic_set_i2s_mode(CODEC_MASTER_MODE ? AIC_I2S_SLAVE_MODE + : AIC_I2S_MASTER_MODE); + aic_enable_i2s_bit_clock(true); + + /* Open DAC driver */ + i2c_x1000_set_freq(1, I2C_FREQ_400K); + codec_start(); +} + +void audiohw_postinit(void) +{ + es9218_mute(false); +} + +void audiohw_close(void) +{ + codec_stop(); +} + +void audiohw_set_frequency(int fsel) +{ + int sampr = hw_freq_sampr[fsel]; + + /* choose clock gear setting, in line with the OF */ + enum es9218_clock_gear clkgear; + if(sampr <= 48000) + clkgear = ES9218_CLK_GEAR_4; + else if(sampr <= 96000) + clkgear = ES9218_CLK_GEAR_2; + else + clkgear = ES9218_CLK_GEAR_1; + + aic_enable_i2s_bit_clock(false); + es9218_set_clock_gear(clkgear); + + if(CODEC_MASTER_MODE) + es9218_set_nco_frequency(sampr); + else + aic_set_i2s_clock(X1000_CLK_SCLK_A, sampr, 64); + + aic_enable_i2s_bit_clock(true); + + /* save frequency selection */ + cur_fsel = fsel; +} + +static int round_step_up(int x, int step) +{ + int rem = x % step; + if(rem > 0) + rem -= step; + return x - rem; +} + +void audiohw_set_volume(int vol_l, int vol_r) +{ + /* save volume */ + cur_vol_l = vol_l; + cur_vol_r = vol_r; + + /* adjust the amp setting first */ + int amp = round_step_up(MAX(vol_l, vol_r), ES9218_AMP_VOLUME_STEP); + amp = MIN(amp, ES9218_AMP_VOLUME_MAX); + amp = MAX(amp, ES9218_AMP_VOLUME_MIN); + + /* adjust digital volumes */ + vol_l -= amp; + vol_l = MIN(vol_l, ES9218_DIG_VOLUME_MAX); + vol_l = MAX(vol_l, ES9218_DIG_VOLUME_MIN); + + vol_r -= amp; + vol_r = MIN(vol_r, ES9218_DIG_VOLUME_MAX); + vol_r = MAX(vol_r, ES9218_DIG_VOLUME_MIN); + + /* program DAC */ + es9218_set_amp_volume(amp); + es9218_set_dig_volume(vol_l, vol_r); +} + +void audiohw_set_filter_roll_off(int value) +{ + cur_filter = value; + es9218_set_filter(value); +} + +void audiohw_set_power_mode(int mode) +{ + enum es9218_amp_mode new_amp_mode; + if(mode == 0) + new_amp_mode = ES9218_AMP_MODE_2VRMS; + else + new_amp_mode = ES9218_AMP_MODE_1VRMS; + + if(new_amp_mode != cur_amp_mode) { + codec_stop(); + cur_amp_mode = new_amp_mode; + codec_start(); + es9218_mute(false); + } +} + +void es9218_set_power_pin(int level) +{ + gpio_set_level(GPIO_ES9218_POWER, level ? 1 : 0); +} + +void es9218_set_reset_pin(int level) +{ + gpio_set_level(GPIO_ES9218_RESET, level ? 1 : 0); +} + +uint32_t es9218_get_mclk(void) +{ + /* Measured by running the DAC in asynchronous I2S slave mode, + * and reading back the DPLL number from regs 0x42-0x45 while + * playing back 44.1 KHz audio. + * + * CLK = (44_100 * 2**32) / 0x4b46e5 + * = 38_393_403.29532737 + * ~ 38.4 Mhz + */ + return 38400000; +} diff --git a/firmware/target/mips/ingenic_x1000/shanlingq1/backlight-shanlingq1.c b/firmware/target/mips/ingenic_x1000/shanlingq1/backlight-shanlingq1.c new file mode 100644 index 0000000000..32c1b902aa --- /dev/null +++ b/firmware/target/mips/ingenic_x1000/shanlingq1/backlight-shanlingq1.c @@ -0,0 +1,63 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2021 Aidan MacDonald + * + * 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 "backlight.h" +#include "backlight-target.h" +#include "lcd.h" +#include "pwm-x1000.h" + +#define BL_LCD_CHN 0 +#define BL_LCD_PERIOD 10000 + +static int backlight_calc_duty(int period, int min_duty, int brightness) +{ + return min_duty + (period - min_duty) * brightness / MAX_BRIGHTNESS_SETTING; +} + +bool backlight_hw_init(void) +{ + pwm_init(BL_LCD_CHN); + pwm_enable(BL_LCD_CHN); + backlight_hw_brightness(MAX_BRIGHTNESS_SETTING); + return true; +} + +void backlight_hw_on(void) +{ + pwm_enable(BL_LCD_CHN); +#ifdef HAVE_LCD_ENABLE + lcd_enable(true); +#endif +} + +void backlight_hw_off(void) +{ + pwm_disable(BL_LCD_CHN); +#ifdef HAVE_LCD_ENABLE + lcd_enable(false); +#endif +} + +void backlight_hw_brightness(int brightness) +{ + int duty_ns = backlight_calc_duty(BL_LCD_PERIOD, 0, brightness); + pwm_set_period(BL_LCD_CHN, BL_LCD_PERIOD, duty_ns); +} diff --git a/firmware/target/mips/ingenic_x1000/shanlingq1/backlight-target.h b/firmware/target/mips/ingenic_x1000/shanlingq1/backlight-target.h new file mode 100644 index 0000000000..7298c1c06a --- /dev/null +++ b/firmware/target/mips/ingenic_x1000/shanlingq1/backlight-target.h @@ -0,0 +1,33 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2021 Aidan MacDonald + * + * 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. + * + ****************************************************************************/ + +#ifndef __BACKLIGHT_TARGET_H__ +#define __BACKLIGHT_TARGET_H__ + +#include <stdbool.h> + +extern bool backlight_hw_init(void); + +extern void backlight_hw_on(void); +extern void backlight_hw_off(void); +extern void backlight_hw_brightness(int brightness); + +#endif /* __BACKLIGHT_TARGET_H__ */ diff --git a/firmware/target/mips/ingenic_x1000/shanlingq1/boot.make b/firmware/target/mips/ingenic_x1000/shanlingq1/boot.make new file mode 100644 index 0000000000..639f570ea3 --- /dev/null +++ b/firmware/target/mips/ingenic_x1000/shanlingq1/boot.make @@ -0,0 +1,31 @@ +# __________ __ ___. +# Open \______ \ ____ ____ | | _\_ |__ _______ ___ +# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / +# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < +# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ +# \/ \/ \/ \/ \/ +# $Id$ +# + +include $(ROOTDIR)/lib/microtar/microtar.make + +.SECONDEXPANSION: + +# FIXME(q1): verify NAND parameters +$(BUILDDIR)/spl.q1: $(BUILDDIR)/spl.bin + $(call PRINTS,MKSPL $(@F))$(TOOLSDIR)/mkspl-x1000 -type=nand -ppb=2 -bpp=2 $< $@ + +$(BUILDDIR)/bootloader.ucl: $(BUILDDIR)/bootloader.bin + $(call PRINTS,UCLPACK $(@F))$(TOOLSDIR)/uclpack --nrv2e -9 $< $@ >/dev/null + +.PHONY: $(BUILDDIR)/bootloader-info.txt +$(BUILDDIR)/bootloader-info.txt: + $(call PRINTS,GEN $(@F))echo $(SVNVERSION) > $@ + +$(BUILDDIR)/$(BINARY): $(BUILDDIR)/spl.q1 \ + $(BUILDDIR)/bootloader.ucl \ + $(BUILDDIR)/bootloader-info.txt + $(call PRINTS,TAR $(@F))tar -C $(BUILDDIR) \ + --numeric-owner --no-acls --no-xattrs --no-selinux \ + --mode=0644 --owner=0 --group=0 \ + -cf $@ $(call full_path_subst,$(BUILDDIR)/%,%,$^) diff --git a/firmware/target/mips/ingenic_x1000/shanlingq1/button-shanlingq1.c b/firmware/target/mips/ingenic_x1000/shanlingq1/button-shanlingq1.c new file mode 100644 index 0000000000..27c49a7bd7 --- /dev/null +++ b/firmware/target/mips/ingenic_x1000/shanlingq1/button-shanlingq1.c @@ -0,0 +1,195 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2021 Aidan MacDonald + * Copyright (C) 2021 Dana Conrad + * + * 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 "button.h" +#include "touchscreen.h" +#include "ft6x06.h" +#include "axp-pmu.h" +#include "kernel.h" +#include "backlight.h" +#include "powermgmt.h" +#include "gpio-x1000.h" +#include "irq-x1000.h" +#include "i2c-x1000.h" +#include <stdbool.h> + +/* Volume wheel rotation */ +static volatile int wheel_pos = 0; + +/* Value of headphone detect register */ +static uint8_t hp_detect_reg = 0x00; + +/* Interval to poll the register */ +#define HPD_POLL_TIME (HZ/2) + +static int hp_detect_tmo_cb(struct timeout* tmo) +{ + i2c_descriptor* d = (i2c_descriptor*)tmo->data; + i2c_async_queue(AXP_PMU_BUS, TIMEOUT_NOBLOCK, I2C_Q_ADD, 0, d); + return HPD_POLL_TIME; +} + +static void hp_detect_init(void) +{ + /* TODO: replace this copy paste cruft with an API in axp-pmu */ + static struct timeout tmo; + static const uint8_t gpio_reg = AXP192_REG_GPIOSTATE1; + static i2c_descriptor desc = { + .slave_addr = AXP_PMU_ADDR, + .bus_cond = I2C_START | I2C_STOP, + .tran_mode = I2C_READ, + .buffer[0] = (void*)&gpio_reg, + .count[0] = 1, + .buffer[1] = &hp_detect_reg, + .count[1] = 1, + .callback = NULL, + .arg = 0, + .next = NULL, + }; + + /* Headphone detect is wired to AXP192 GPIO: set it to input state */ + i2c_reg_write1(AXP_PMU_BUS, AXP_PMU_ADDR, AXP192_REG_GPIO1FUNCTION, 0x01); + + /* Get an initial reading before startup */ + int r = i2c_reg_read1(AXP_PMU_BUS, AXP_PMU_ADDR, gpio_reg); + if(r >= 0) + hp_detect_reg = r; + + /* Poll the register every second */ + timeout_register(&tmo, &hp_detect_tmo_cb, HPD_POLL_TIME, (intptr_t)&desc); +} + +void button_init_device(void) +{ + /* Setup interrupts for the volume wheel */ + gpio_set_function(GPIO_WHEEL1, GPIOF_IRQ_EDGE(0)); + gpio_set_function(GPIO_WHEEL2, GPIOF_IRQ_EDGE(0)); + gpio_flip_edge_irq(GPIO_WHEEL1); + gpio_flip_edge_irq(GPIO_WHEEL2); + gpio_enable_irq(GPIO_WHEEL1); + gpio_enable_irq(GPIO_WHEEL2); + + /* Init touchscreen driver */ + i2c_x1000_set_freq(FT6x06_BUS, I2C_FREQ_400K); + ft6x06_init(); + + /* Reset touch controller */ + gpio_set_level(GPIO_FT6x06_POWER, 1); + gpio_set_level(GPIO_FT6x06_RESET, 0); + mdelay(5); + gpio_set_level(GPIO_FT6x06_RESET, 1); + + /* Enable ft6x06 interrupt */ + system_set_irq_handler(GPIO_TO_IRQ(GPIO_FT6x06_INTERRUPT), ft6x06_irq_handler); + gpio_set_function(GPIO_FT6x06_INTERRUPT, GPIOF_IRQ_EDGE(0)); + gpio_enable_irq(GPIO_FT6x06_INTERRUPT); + + /* Headphone detection */ + hp_detect_init(); +} + +int button_read_device(int* data) +{ + int r = 0; + + /* Read GPIO buttons, these are all active low */ + uint32_t b = REG_GPIO_PIN(GPIO_B); + if((b & (1 << 21)) == 0) r |= BUTTON_PREV; + if((b & (1 << 22)) == 0) r |= BUTTON_NEXT; + if((b & (1 << 28)) == 0) r |= BUTTON_PLAY; + if((b & (1 << 31)) == 0) r |= BUTTON_POWER; + + /* Check the wheel */ + int wheel_btn = 0; + int whpos = wheel_pos; + if(whpos > 3) + wheel_btn = BUTTON_VOL_DOWN; + else if(whpos < -3) + wheel_btn = BUTTON_VOL_UP; + + if(wheel_btn) { + wheel_pos = 0; + + /* Post the event (rapid motion is more reliable this way) */ + queue_post(&button_queue, wheel_btn, 0); + queue_post(&button_queue, wheel_btn|BUTTON_REL, 0); + + /* Poke the backlight */ + backlight_on(); + reset_poweroff_timer(); + } + + /* Handle touchscreen + * + * TODO: Support 2-point multitouch (useful for 3x3 grid mode) + * TODO: Support simple gestures by converting them to fake buttons + */ + int t = touchscreen_to_pixels(ft6x06_state.pos_x, ft6x06_state.pos_y, data); + if(ft6x06_state.event == FT6x06_EVT_PRESS || + ft6x06_state.event == FT6x06_EVT_CONTACT) { + /* Only set the button bit if the screen is being touched. */ + r |= t; + } + + return r; +} + +void touchscreen_enable_device(bool en) +{ + ft6x06_enable(en); + /* TODO: check if it's worth shutting off the power pin */ +} + +bool headphones_inserted(void) +{ + /* TODO: Also check if the headset button is detectable via an ADC. + * The AXP driver should probably get proper interrupt handling, + * that would be useful for more things than just GPIO polling. */ + return hp_detect_reg & 0x20 ? true : false; +} + +static void handle_wheel_irq(void) +{ + /* Wheel stuff adapted from button-erosqnative.c */ + static const int delta[16] = { 0, -1, 1, 0, + 1, 0, 0, -1, + -1, 0, 0, 1, + 0, 1, -1, 0 }; + static uint32_t state = 0; + state <<= 2; + state |= (REG_GPIO_PIN(GPIO_D) >> 2) & 3; + state &= 0xf; + + wheel_pos += delta[state]; +} + +void GPIOD02(void) +{ + handle_wheel_irq(); + gpio_flip_edge_irq(GPIO_WHEEL1); +} + +void GPIOD03(void) +{ + handle_wheel_irq(); + gpio_flip_edge_irq(GPIO_WHEEL2); +} diff --git a/firmware/target/mips/ingenic_x1000/shanlingq1/button-target.h b/firmware/target/mips/ingenic_x1000/shanlingq1/button-target.h new file mode 100644 index 0000000000..905d148afa --- /dev/null +++ b/firmware/target/mips/ingenic_x1000/shanlingq1/button-target.h @@ -0,0 +1,56 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2021 Aidan MacDonald + * + * 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. + * + ****************************************************************************/ + +#ifndef __BUTTON_TARGET_H__ +#define __BUTTON_TARGET_H__ + +#include <stdbool.h> + +/* physical buttons */ +#define BUTTON_POWER 0x00000001 +#define BUTTON_VOL_UP 0x00000002 /* up = wheel clockwise */ +#define BUTTON_VOL_DOWN 0x00000004 +#define BUTTON_PLAY 0x00000008 /* circle */ +#define BUTTON_NEXT 0x00000010 /* down */ +#define BUTTON_PREV 0x00000020 /* up */ + +/* compatibility hacks */ +#define BUTTON_LEFT BUTTON_MIDLEFT +#define BUTTON_RIGHT BUTTON_MIDRIGHT + +/* touchscreen "buttons" */ +#define BUTTON_TOPLEFT 0x00000040 +#define BUTTON_TOPMIDDLE 0x00000080 +#define BUTTON_TOPRIGHT 0x00000100 +#define BUTTON_MIDLEFT 0x00000200 +#define BUTTON_CENTER 0x00000400 +#define BUTTON_MIDRIGHT 0x00000800 +#define BUTTON_BOTTOMLEFT 0x00001000 +#define BUTTON_BOTTOMMIDDLE 0x00002000 +#define BUTTON_BOTTOMRIGHT 0x00004000 + +#define BUTTON_MAIN (BUTTON_POWER|BUTTON_VOL_UP|BUTTON_VOL_DOWN|\ + BUTTON_PLAY|BUTTON_NEXT|BUTTON_PREV) + +#define POWEROFF_BUTTON BUTTON_POWER +#define POWEROFF_COUNT 30 + +#endif /* __BUTTON_TARGET_H__ */ diff --git a/firmware/target/mips/ingenic_x1000/shanlingq1/gpio-target.h b/firmware/target/mips/ingenic_x1000/shanlingq1/gpio-target.h new file mode 100644 index 0000000000..7c71d12888 --- /dev/null +++ b/firmware/target/mips/ingenic_x1000/shanlingq1/gpio-target.h @@ -0,0 +1,32 @@ +/* Name Port Pins Function */ +DEFINE_PINGROUP(LCD_DATA, GPIO_A, 0xffff << 0, GPIOF_DEVICE(1)) +DEFINE_PINGROUP(LCD_CONTROL, GPIO_B, 0x1a << 16, GPIOF_DEVICE(1)) +DEFINE_PINGROUP(MSC0, GPIO_A, 0x3f << 20, GPIOF_DEVICE(1)) +DEFINE_PINGROUP(SFC, GPIO_A, 0x3f << 26, GPIOF_DEVICE(1)) +DEFINE_PINGROUP(I2S, GPIO_B, 0x1f << 0, GPIOF_DEVICE(1)) +DEFINE_PINGROUP(I2C0, GPIO_B, 3 << 23, GPIOF_DEVICE(0)) +DEFINE_PINGROUP(I2C1, GPIO_C, 3 << 26, GPIOF_DEVICE(0)) +DEFINE_PINGROUP(I2C2, GPIO_D, 3 << 0, GPIOF_DEVICE(1)) + +/* Name Pin Function */ +DEFINE_GPIO(FT6x06_INTERRUPT, GPIO_PA(16), GPIOF_INPUT) +DEFINE_GPIO(USB_DETECT, GPIO_PA(17), GPIOF_INPUT) +DEFINE_GPIO(FT6x06_RESET, GPIO_PA(19), GPIOF_OUTPUT(0)) +DEFINE_GPIO(LCD_PWR, GPIO_PB(6), GPIOF_OUTPUT(1)) +DEFINE_GPIO(FT6x06_POWER, GPIO_PB(8), GPIOF_OUTPUT(0)) +DEFINE_GPIO(MSC0_CD, GPIO_PB(9), GPIOF_INPUT) +DEFINE_GPIO(ES9218_POWER, GPIO_PB(13), GPIOF_OUTPUT(0)) +DEFINE_GPIO(LCD_RST, GPIO_PB(15), GPIOF_OUTPUT(1)) +DEFINE_GPIO(LCD_RD, GPIO_PB(16), GPIOF_OUTPUT(1)) +DEFINE_GPIO(LCD_CE, GPIO_PB(18), GPIOF_OUTPUT(1)) +DEFINE_GPIO(BTN_PREV, GPIO_PB(21), GPIOF_INPUT) +DEFINE_GPIO(BTN_NEXT, GPIO_PB(22), GPIOF_INPUT) +DEFINE_GPIO(USB_DRVVBUS, GPIO_PB(25), GPIOF_OUTPUT(0)) +DEFINE_GPIO(BTN_PLAY, GPIO_PB(28), GPIOF_INPUT) +DEFINE_GPIO(BTN_POWER, GPIO_PB(31), GPIOF_INPUT) +DEFINE_GPIO(AXP_IRQ, GPIO_PC(21), GPIOF_INPUT) +DEFINE_GPIO(USB_ID, GPIO_PC(23), GPIOF_INPUT) +DEFINE_GPIO(WHEEL1, GPIO_PD(2), GPIOF_INPUT) +DEFINE_GPIO(WHEEL2, GPIO_PD(3), GPIOF_INPUT) +DEFINE_GPIO(ES9218_GPIO2, GPIO_PD(4), GPIOF_OUTPUT(0)) +DEFINE_GPIO(ES9218_RESET, GPIO_PD(5), GPIOF_OUTPUT(0)) diff --git a/firmware/target/mips/ingenic_x1000/shanlingq1/i2c-target.h b/firmware/target/mips/ingenic_x1000/shanlingq1/i2c-target.h new file mode 100644 index 0000000000..af19aeb28c --- /dev/null +++ b/firmware/target/mips/ingenic_x1000/shanlingq1/i2c-target.h @@ -0,0 +1,40 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2021 Aidan MacDonald + * + * 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. + * + ****************************************************************************/ + +#ifndef __I2C_TARGET_H__ +#define __I2C_TARGET_H__ + +#define I2C_ASYNC_BUS_COUNT 3 +#define I2C_ASYNC_QUEUE_SIZE 4 + +#define FT6x06_BUS 0 +#define FT6x06_ADDR 0x38 + +#define ES9218_BUS 1 +#define ES9218_ADDR 0x48 + +#define AXP_PMU_BUS 2 +#define AXP_PMU_ADDR 0x34 + +#define CW2015_BUS 2 +#define CW2015_ADDR 0x62 + +#endif /* __I2C_TARGET_H__ */ diff --git a/firmware/target/mips/ingenic_x1000/shanlingq1/lcd-shanlingq1.c b/firmware/target/mips/ingenic_x1000/shanlingq1/lcd-shanlingq1.c new file mode 100644 index 0000000000..532a149185 --- /dev/null +++ b/firmware/target/mips/ingenic_x1000/shanlingq1/lcd-shanlingq1.c @@ -0,0 +1,399 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2021 Aidan MacDonald + * + * 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 "lcd.h" +#include "system.h" +#include "lcd-x1000.h" +#include "gpio-x1000.h" + +/* LCD controller is probably an RM68090. + */ + +static const uint32_t q1_lcd_cmd_enable[] = { + LCD_INSTR_CMD, 0x00, + LCD_INSTR_CMD, 0xbe, + LCD_INSTR_DAT, 0xc3, + LCD_INSTR_DAT, 0x29, + + LCD_INSTR_CMD, 0x00, + LCD_INSTR_CMD, 0x01, + LCD_INSTR_DAT, 0x01, + LCD_INSTR_DAT, 0x04, + + LCD_INSTR_CMD, 0x00, + LCD_INSTR_CMD, 0x02, + LCD_INSTR_DAT, 0x01, + LCD_INSTR_DAT, 0x00, + + LCD_INSTR_CMD, 0x00, + LCD_INSTR_CMD, 0x03, + LCD_INSTR_DAT, 0x00, + LCD_INSTR_DAT, 0x10, + + LCD_INSTR_CMD, 0x00, + LCD_INSTR_CMD, 0x05, + LCD_INSTR_DAT, 0x00, + LCD_INSTR_DAT, 0x00, + + LCD_INSTR_CMD, 0x00, + LCD_INSTR_CMD, 0x06, + LCD_INSTR_DAT, 0x00, + LCD_INSTR_DAT, 0x00, + + LCD_INSTR_CMD, 0x00, + LCD_INSTR_CMD, 0x07, + LCD_INSTR_DAT, 0x01, + LCD_INSTR_DAT, 0x03, + + LCD_INSTR_CMD, 0x00, + LCD_INSTR_CMD, 0x08, + LCD_INSTR_DAT, 0x03, + LCD_INSTR_DAT, 0x03, + + LCD_INSTR_CMD, 0x00, + LCD_INSTR_CMD, 0x0d, + LCD_INSTR_DAT, 0x00, + LCD_INSTR_DAT, 0x00, + + LCD_INSTR_CMD, 0x00, + LCD_INSTR_CMD, 0x10, + LCD_INSTR_DAT, 0x00, + LCD_INSTR_DAT, 0xc1, + + LCD_INSTR_CMD, 0x00, + LCD_INSTR_CMD, 0x11, + LCD_INSTR_DAT, 0xb1, + LCD_INSTR_DAT, 0x08, + + LCD_INSTR_CMD, 0x00, + LCD_INSTR_CMD, 0x12, + LCD_INSTR_DAT, 0xb1, + LCD_INSTR_DAT, 0x08, + + LCD_INSTR_CMD, 0x00, + LCD_INSTR_CMD, 0x13, + LCD_INSTR_DAT, 0x00, + LCD_INSTR_DAT, 0x0f, + + LCD_INSTR_CMD, 0x00, + LCD_INSTR_CMD, 0x14, + LCD_INSTR_DAT, 0x00, + LCD_INSTR_DAT, 0x14, + + LCD_INSTR_CMD, 0x00, + LCD_INSTR_CMD, 0x15, + LCD_INSTR_DAT, 0x00, + LCD_INSTR_DAT, 0x04, + + LCD_INSTR_CMD, 0x00, + LCD_INSTR_CMD, 0x16, + LCD_INSTR_DAT, 0x00, + LCD_INSTR_DAT, 0x00, + + LCD_INSTR_CMD, 0x00, + LCD_INSTR_CMD, 0x22, + LCD_INSTR_DAT, 0x00, + LCD_INSTR_DAT, 0x00, + + LCD_INSTR_CMD, 0x00, + LCD_INSTR_CMD, 0x23, + LCD_INSTR_DAT, 0x00, + LCD_INSTR_DAT, 0x00, + + LCD_INSTR_CMD, 0x00, + LCD_INSTR_CMD, 0x30, + LCD_INSTR_DAT, 0x7c, + LCD_INSTR_DAT, 0x3f, + + LCD_INSTR_CMD, 0x00, + LCD_INSTR_CMD, 0x32, + LCD_INSTR_DAT, 0x00, + LCD_INSTR_DAT, 0x00, + + LCD_INSTR_CMD, 0x00, + LCD_INSTR_CMD, 0x70, + LCD_INSTR_DAT, 0x00, + LCD_INSTR_DAT, 0x01, + + LCD_INSTR_CMD, 0x00, + LCD_INSTR_CMD, 0x91, + LCD_INSTR_DAT, 0x01, + LCD_INSTR_DAT, 0x00, + + LCD_INSTR_CMD, 0x00, + LCD_INSTR_CMD, 0xe0, + LCD_INSTR_DAT, 0x00, + LCD_INSTR_DAT, 0x01, + + LCD_INSTR_CMD, 0x00, + LCD_INSTR_CMD, 0xe1, + LCD_INSTR_DAT, 0x00, + LCD_INSTR_DAT, 0x61, + + LCD_INSTR_CMD, 0x01, + LCD_INSTR_CMD, 0x00, + LCD_INSTR_DAT, 0x10, + LCD_INSTR_DAT, 0x30, + + LCD_INSTR_CMD, 0x01, + LCD_INSTR_CMD, 0x01, + LCD_INSTR_DAT, 0xf6, + LCD_INSTR_DAT, 0x3f, + + LCD_INSTR_CMD, 0x01, + LCD_INSTR_CMD, 0x02, + LCD_INSTR_DAT, 0x50, + LCD_INSTR_DAT, 0x1f, + + LCD_INSTR_CMD, 0x01, + LCD_INSTR_CMD, 0x03, + LCD_INSTR_DAT, 0x00, + LCD_INSTR_DAT, 0x30, + + LCD_INSTR_CMD, 0x01, + LCD_INSTR_CMD, 0x08, + LCD_INSTR_DAT, 0x03, + LCD_INSTR_DAT, 0x00, + + LCD_INSTR_CMD, 0x01, + LCD_INSTR_CMD, 0x11, + LCD_INSTR_DAT, 0x00, + LCD_INSTR_DAT, 0x01, + + LCD_INSTR_CMD, 0x01, + LCD_INSTR_CMD, 0x35, + LCD_INSTR_DAT, 0x76, + LCD_INSTR_DAT, 0x66, + + LCD_INSTR_CMD, 0x01, + LCD_INSTR_CMD, 0x39, + LCD_INSTR_DAT, 0x00, + LCD_INSTR_DAT, 0x26, + + LCD_INSTR_CMD, 0x04, + LCD_INSTR_CMD, 0x00, + LCD_INSTR_DAT, 0x00, + LCD_INSTR_DAT, 0xc7, + + LCD_INSTR_CMD, 0x04, + LCD_INSTR_CMD, 0x01, + LCD_INSTR_DAT, 0x00, + LCD_INSTR_DAT, 0x00, + + LCD_INSTR_CMD, 0x06, + LCD_INSTR_CMD, 0x06, + LCD_INSTR_DAT, 0x00, + LCD_INSTR_DAT, 0x00, + + LCD_INSTR_CMD, 0x03, + LCD_INSTR_CMD, 0x00, + LCD_INSTR_DAT, 0x0d, + LCD_INSTR_DAT, 0x0e, + + LCD_INSTR_CMD, 0x03, + LCD_INSTR_CMD, 0x01, + LCD_INSTR_DAT, 0x00, + LCD_INSTR_DAT, 0x03, + + LCD_INSTR_CMD, 0x03, + LCD_INSTR_CMD, 0x02, + LCD_INSTR_DAT, 0x08, + LCD_INSTR_DAT, 0x08, + + LCD_INSTR_CMD, 0x03, + LCD_INSTR_CMD, 0x03, + LCD_INSTR_DAT, 0x02, + LCD_INSTR_DAT, 0x01, + + LCD_INSTR_CMD, 0x03, + LCD_INSTR_CMD, 0x04, + LCD_INSTR_DAT, 0x03, + LCD_INSTR_DAT, 0x01, + + LCD_INSTR_CMD, 0x03, + LCD_INSTR_CMD, 0x05, + LCD_INSTR_DAT, 0x00, + LCD_INSTR_DAT, 0x04, + + LCD_INSTR_CMD, 0x03, + LCD_INSTR_CMD, 0x06, + LCD_INSTR_DAT, 0x1b, + LCD_INSTR_DAT, 0x21, + + LCD_INSTR_CMD, 0x03, + LCD_INSTR_CMD, 0x07, + LCD_INSTR_DAT, 0x0f, + LCD_INSTR_DAT, 0x0e, + + LCD_INSTR_CMD, 0x03, + LCD_INSTR_CMD, 0x08, + LCD_INSTR_DAT, 0x01, + LCD_INSTR_DAT, 0x04, + + LCD_INSTR_CMD, 0x03, + LCD_INSTR_CMD, 0x09, + LCD_INSTR_DAT, 0x08, + LCD_INSTR_DAT, 0x08, + + LCD_INSTR_CMD, 0x03, + LCD_INSTR_CMD, 0x0a, + LCD_INSTR_DAT, 0x02, + LCD_INSTR_DAT, 0x01, + + LCD_INSTR_CMD, 0x03, + LCD_INSTR_CMD, 0x0b, + LCD_INSTR_DAT, 0x03, + LCD_INSTR_DAT, 0x01, + + LCD_INSTR_CMD, 0x03, + LCD_INSTR_CMD, 0x0c, + LCD_INSTR_DAT, 0x00, + LCD_INSTR_DAT, 0x03, + + LCD_INSTR_CMD, 0x03, + LCD_INSTR_CMD, 0x0d, + LCD_INSTR_DAT, 0x31, + LCD_INSTR_DAT, 0x34, + + /* X start */ + LCD_INSTR_CMD, 0x02, + LCD_INSTR_CMD, 0x10, + LCD_INSTR_DAT, 0x00, + LCD_INSTR_DAT, 0x1e, /* 30 */ + + /* X end */ + LCD_INSTR_CMD, 0x02, + LCD_INSTR_CMD, 0x11, + LCD_INSTR_DAT, 0x01, + LCD_INSTR_DAT, 0x85, /* 389 */ + + /* Y start */ + LCD_INSTR_CMD, 0x02, + LCD_INSTR_CMD, 0x12, + LCD_INSTR_DAT, 0x00, + LCD_INSTR_DAT, 0x00, /* 0 */ + + /* Y end */ + LCD_INSTR_CMD, 0x02, + LCD_INSTR_CMD, 0x13, + LCD_INSTR_DAT, 0x01, + LCD_INSTR_DAT, 0x8f, /* 399 */ + + /* RAM write start X? */ + LCD_INSTR_CMD, 0x02, + LCD_INSTR_CMD, 0x00, + LCD_INSTR_DAT, 0x00, + LCD_INSTR_DAT, 0x1e, + + /* RAM write start Y? */ + LCD_INSTR_CMD, 0x02, + LCD_INSTR_CMD, 0x01, + LCD_INSTR_DAT, 0x00, + LCD_INSTR_DAT, 0x00, + + LCD_INSTR_CMD, 0x00, + LCD_INSTR_CMD, 0x03, + LCD_INSTR_DAT, 0x00, + LCD_INSTR_DAT, 0x30, + + LCD_INSTR_CMD, 0x02, + LCD_INSTR_CMD, 0x02, + LCD_INSTR_END, +}; + +/* NOTE this sleep mode may not be saving power, but it gets rid of the + * ghost image that would otherwise remain on the display */ +static const uint32_t q1_lcd_cmd_sleep[] = { + LCD_INSTR_CMD, 0x00, + LCD_INSTR_CMD, 0x10, + LCD_INSTR_DAT, 0x00, + LCD_INSTR_DAT, 0x03, + + LCD_INSTR_CMD, 0x00, + LCD_INSTR_CMD, 0x07, + LCD_INSTR_DAT, 0x01, + LCD_INSTR_DAT, 0x01, + + LCD_INSTR_END, +}; + +static const uint32_t q1_lcd_cmd_wake[] = { + LCD_INSTR_CMD, 0x00, + LCD_INSTR_CMD, 0x07, + LCD_INSTR_DAT, 0x01, + LCD_INSTR_DAT, 0x03, + + LCD_INSTR_CMD, 0x00, + LCD_INSTR_CMD, 0x10, + LCD_INSTR_DAT, 0x00, + LCD_INSTR_DAT, 0xc1, + + LCD_INSTR_END, +}; + +static const uint8_t __attribute__((aligned(64))) + q1_lcd_dma_wr_cmd[] = {0x02, 0x02, 0x02, 0x02}; + +const struct lcd_tgt_config lcd_tgt_config = { + .bus_width = 8, + .cmd_width = 8, + .use_6800_mode = 0, + .use_serial = 0, + .clk_polarity = 0, + .dc_polarity = 0, + .wr_polarity = 1, + .te_enable = 0, + .big_endian = 1, + .dma_wr_cmd_buf = &q1_lcd_dma_wr_cmd, + .dma_wr_cmd_size = sizeof(q1_lcd_dma_wr_cmd), +}; + +void lcd_tgt_enable(bool enable) +{ + if(enable) { + /* power on the panel */ + gpio_set_level(GPIO_LCD_PWR, 1); + gpio_set_level(GPIO_LCD_RST, 1); + gpio_set_level(GPIO_LCD_CE, 1); + gpio_set_level(GPIO_LCD_RD, 1); + mdelay(50); + gpio_set_level(GPIO_LCD_RST, 0); + mdelay(100); + gpio_set_level(GPIO_LCD_RST, 1); + mdelay(50); + gpio_set_level(GPIO_LCD_CE, 0); + + /* Start the controller */ + lcd_set_clock(X1000_CLK_MPLL, 50000000); + lcd_exec_commands(q1_lcd_cmd_enable); + } else { + /* FIXME: Shanling Q1 LCD power down sequence + * not important because we don't use it but it'd be nice to know */ + } +} + +void lcd_tgt_sleep(bool sleep) +{ + if(sleep) + lcd_exec_commands(q1_lcd_cmd_sleep); + else + lcd_exec_commands(q1_lcd_cmd_wake); +} diff --git a/firmware/target/mips/ingenic_x1000/shanlingq1/power-shanlingq1.c b/firmware/target/mips/ingenic_x1000/shanlingq1/power-shanlingq1.c new file mode 100644 index 0000000000..17fbe1cede --- /dev/null +++ b/firmware/target/mips/ingenic_x1000/shanlingq1/power-shanlingq1.c @@ -0,0 +1,140 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2021 Aidan MacDonald + * + * 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 "power.h" +#include "adc.h" +#include "system.h" +#include "axp-pmu.h" +#ifdef HAVE_CW2015 +# include "cw2015.h" +#endif +#ifdef HAVE_USB_CHARGING_ENABLE +# include "usb_core.h" +#endif + +#include "i2c-x1000.h" + +/* TODO: Better(?) battery reporting for Q1 using CW2015 driver + * + * The CW2015 has its own quirks so the driver has to be more complicated + * than "read stuff from I2C," unfortunately. Without fixing the quirks it + * is probably worse than the simple voltage-based method. + * + * A bigger problem is that it shares an I2C bus with the AXP192, but when + * we attempt to communicate with both chips, they start returning bogus + * data intermittently. Ususally, reads will return 0 but sometimes they + * can return other nonzero bogus data. It could be that one or the other is + * pulling the bus line down inappropriately, or maybe the hardware does not + * respect the bus free time between start/stop conditions and one of the + * devices is getting confused. + */ + +const unsigned short battery_level_dangerous[BATTERY_TYPES_COUNT] = +{ + 3470 +}; + +/* the OF shuts down at this voltage */ +const unsigned short battery_level_shutoff[BATTERY_TYPES_COUNT] = +{ + 3400 +}; + +/* voltages (millivolt) of 0%, 10%, ... 100% when charging disabled */ +const unsigned short percent_to_volt_discharge[BATTERY_TYPES_COUNT][11] = +{ + { 3400, 3639, 3697, 3723, 3757, 3786, 3836, 3906, 3980, 4050, 4159 } +}; + +/* voltages (millivolt) of 0%, 10%, ... 100% when charging enabled */ +const unsigned short percent_to_volt_charge[11] = +{ + 3485, 3780, 3836, 3857, 3890, 3930, 3986, 4062, 4158, 4185, 4196 +}; + +void power_init(void) +{ + i2c_x1000_set_freq(AXP_PMU_BUS, I2C_FREQ_400K); + axp_init(); +#ifdef HAVE_CW2015 + cw2015_init(); +#endif + + /* Change supply voltage from the default of 1250 mV to 1200 mV, + * this matches the original firmware's settings. Didn't observe + * any obviously bad behavior at 1250 mV, but better to be safe. */ + axp_supply_set_voltage(AXP_SUPPLY_DCDC2, 1200); + + /* For now, just turn everything on... definitely the touchscreen + * is powered by one of the outputs */ + i2c_reg_modify1(AXP_PMU_BUS, AXP_PMU_ADDR, + AXP_REG_PWROUTPUTCTRL1, 0, 0x05, NULL); + i2c_reg_modify1(AXP_PMU_BUS, AXP_PMU_ADDR, + AXP_REG_PWROUTPUTCTRL2, 0, 0x0f, NULL); + i2c_reg_modify1(AXP_PMU_BUS, AXP_PMU_ADDR, + AXP_REG_DCDCWORKINGMODE, 0, 0xc0, NULL); + + /* Delay to give power output time to stabilize */ + mdelay(20); +} + +#ifdef HAVE_USB_CHARGING_ENABLE +void usb_charging_maxcurrent_change(int maxcurrent) +{ + axp_set_charge_current(maxcurrent); +} +#endif + +void power_off(void) +{ + axp_power_off(); + while(1); +} + +bool charging_state(void) +{ + return axp_battery_status() == AXP_BATT_CHARGING; +} + +int _battery_voltage(void) +{ + /* CW2015 can also read battery voltage, but the AXP consistently + * reads ~20-30 mV higher so I suspect it's the "real" voltage. */ + return axp_adc_read(ADC_BATTERY_VOLTAGE); +} + +#if defined(HAVE_CW2015) && (CONFIG_BATTERY_MEASURE & PERCENTAGE_MEASURE) != 0 +int _battery_level(void) +{ + return cw2015_get_soc(); +} +#endif + +#if defined(HAVE_CW2015) && (CONFIG_BATTERY_MEASURE & TIME_MEASURE) != 0 +int _battery_time(void) +{ + return cw2015_get_rrt(); +} +#endif + +void adc_init(void) +{ +} diff --git a/firmware/target/mips/ingenic_x1000/shanlingq1/spl-shanlingq1.c b/firmware/target/mips/ingenic_x1000/shanlingq1/spl-shanlingq1.c new file mode 100644 index 0000000000..33303c5e6b --- /dev/null +++ b/firmware/target/mips/ingenic_x1000/shanlingq1/spl-shanlingq1.c @@ -0,0 +1,116 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2021 Aidan MacDonald + * + * 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 "clk-x1000.h" +#include "spl-x1000.h" +#include "gpio-x1000.h" + +#define CMDLINE_COMMON \ + "mem=64M@0x0 no_console_suspend console=ttyS2,115200n8 lpj=5009408 ip=off" +#define CMDLINE_NORMAL \ + " init=/linuxrc ubi.mtd=5 root=ubi0:rootfs ubi.mtd=6 rootfstype=ubifs rw" + +static int dualboot_setup(void) +{ + spl_dualboot_init_clocktree(); + spl_dualboot_init_uart2(); + + /* load PDMA MCU firmware */ + jz_writef(CPM_CLKGR, PDMA(0)); + return spl_storage_read(0x4000, 0x2000, (void*)0xb3422000); +} + +const struct spl_boot_option spl_boot_options[] = { + [BOOT_OPTION_ROCKBOX] = { + .storage_addr = 0x6800, + .storage_size = 102 * 1024, + .load_addr = X1000_DRAM_BASE, + .exec_addr = X1000_DRAM_BASE, + .flags = BOOTFLAG_UCLPACK, + }, + [BOOT_OPTION_OFW_PLAYER] = { + .storage_addr = 0x140000, + .storage_size = 8 * 1024 * 1024, + .load_addr = 0x80efffc0, + .exec_addr = 0x80f00000, + .cmdline = CMDLINE_COMMON CMDLINE_NORMAL, + .cmdline_addr = 0x80004000, + .setup = dualboot_setup, + }, + [BOOT_OPTION_OFW_RECOVERY] = { + .storage_addr = 0x940000, + .storage_size = 10 * 1024 * 1024, + .load_addr = 0x80efffc0, + .exec_addr = 0x80f00000, + .cmdline = CMDLINE_COMMON, + .cmdline_addr = 0x80004000, + .setup = dualboot_setup, + }, +}; + +int spl_get_boot_option(void) +{ + /* Button debounce time in OST clock cycles */ + const uint32_t btn_stable_time = 100 * (X1000_EXCLK_FREQ / 4000); + + /* Buttons to poll */ + const unsigned port = GPIO_B; + const uint32_t recov_pin = (1 << 22); /* Next */ + const uint32_t orig_fw_pin = (1 << 21); /* Prev */ + + uint32_t pin = -1, lastpin = 0; + uint32_t deadline = 0; + int iter_count = 30; /* to avoid an infinite loop */ + + /* set GPIOs to input state */ + gpioz_configure(port, recov_pin|orig_fw_pin, GPIOF_INPUT); + + /* Poll until we get a stable reading */ + do { + lastpin = pin; + pin = ~REG_GPIO_PIN(port) & (recov_pin|orig_fw_pin); + if(pin != lastpin) { + deadline = __ost_read32() + btn_stable_time; + iter_count -= 1; + } + } while(iter_count > 0 && __ost_read32() < deadline); + + if(iter_count >= 0 && (pin & orig_fw_pin)) { + if(pin & recov_pin) + return BOOT_OPTION_OFW_RECOVERY; + else + return BOOT_OPTION_OFW_PLAYER; + } + + return BOOT_OPTION_ROCKBOX; +} + +void spl_error(void) +{ + /* Flash the backlight */ + int level = 0; + while(1) { + gpio_set_function(GPIO_PC(25), GPIOF_OUTPUT(level)); + mdelay(100); + level = 1 - level; + } +} diff --git a/firmware/target/mips/ingenic_x1000/spl-x1000.c b/firmware/target/mips/ingenic_x1000/spl-x1000.c index 284b963e97..72dc53b2b7 100644 --- a/firmware/target/mips/ingenic_x1000/spl-x1000.c +++ b/firmware/target/mips/ingenic_x1000/spl-x1000.c @@ -34,7 +34,7 @@ #include "ucl_decompress.h" #include <string.h> -#ifdef FIIO_M3K +#if defined(FIIO_M3K) || defined(SHANLING_Q1) # define SPL_DDR_MEMORYSIZE 64 # define SPL_DDR_AUTOSR_EN 1 # define SPL_DDR_NEED_BYPASS 1 |