summaryrefslogtreecommitdiff
path: root/firmware/target
diff options
context:
space:
mode:
authorAidan MacDonald <amachronic@protonmail.com>2021-05-23 17:30:58 +0100
committerAidan MacDonald <amachronic@protonmail.com>2021-07-13 22:01:33 +0100
commit4c60bc9e681865fcfc149775a1ed7ccd2613d5bf (patch)
tree99f8d91af2c171cf3843f0c14d41a20d9dc29c4f /firmware/target
parent3abb7c5dd5be2ec6744bfc0a80967b20f1b59e30 (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')
-rw-r--r--firmware/target/hosted/sdl/sim-ui-defines.h8
-rw-r--r--firmware/target/mips/ingenic_x1000/debug-x1000.c6
-rw-r--r--firmware/target/mips/ingenic_x1000/msc-x1000.c13
-rw-r--r--firmware/target/mips/ingenic_x1000/nand-x1000.c2
-rw-r--r--firmware/target/mips/ingenic_x1000/shanlingq1/adc-target.h0
-rw-r--r--firmware/target/mips/ingenic_x1000/shanlingq1/audiohw-shanlingq1.c191
-rw-r--r--firmware/target/mips/ingenic_x1000/shanlingq1/backlight-shanlingq1.c63
-rw-r--r--firmware/target/mips/ingenic_x1000/shanlingq1/backlight-target.h33
-rw-r--r--firmware/target/mips/ingenic_x1000/shanlingq1/boot.make31
-rw-r--r--firmware/target/mips/ingenic_x1000/shanlingq1/button-shanlingq1.c195
-rw-r--r--firmware/target/mips/ingenic_x1000/shanlingq1/button-target.h56
-rw-r--r--firmware/target/mips/ingenic_x1000/shanlingq1/gpio-target.h32
-rw-r--r--firmware/target/mips/ingenic_x1000/shanlingq1/i2c-target.h40
-rw-r--r--firmware/target/mips/ingenic_x1000/shanlingq1/lcd-shanlingq1.c399
-rw-r--r--firmware/target/mips/ingenic_x1000/shanlingq1/power-shanlingq1.c140
-rw-r--r--firmware/target/mips/ingenic_x1000/shanlingq1/spl-shanlingq1.c116
-rw-r--r--firmware/target/mips/ingenic_x1000/spl-x1000.c2
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