summaryrefslogtreecommitdiffstats
path: root/firmware/target/mips/ingenic_jz47xx
diff options
context:
space:
mode:
authorSolomon Peachy <pizza@shaftnet.org>2018-06-28 06:24:26 -0400
committerMichael Giacomelli <giac2000@hotmail.com>2018-07-28 10:56:31 -0400
commit0662793ca0050e823cd1207cc4689a1cba5068bd (patch)
tree08cd2ec59c9044c96b697b5bf8d0640841d044e0 /firmware/target/mips/ingenic_jz47xx
parentb3e2bd619b1b7ea94ef29d32db48e80b347a1990 (diff)
downloadrockbox-0662793ca0050e823cd1207cc4689a1cba5068bd.tar.gz
rockbox-0662793ca0050e823cd1207cc4689a1cba5068bd.tar.bz2
rockbox-0662793ca0050e823cd1207cc4689a1cba5068bd.zip
Add cleaned-up xDuoo X3 support
Cleaned up, rebased, and forward-ported from the xvortex fork. (original credit to vsoftster@gmail.com) Change-Id: Ibcc023a0271ea81e901450a88317708c2683236d Signed-off-by: Solomon Peachy <pizza@shaftnet.org>
Diffstat (limited to 'firmware/target/mips/ingenic_jz47xx')
-rw-r--r--firmware/target/mips/ingenic_jz47xx/app.lds6
-rw-r--r--firmware/target/mips/ingenic_jz47xx/ata-nand-jz4740.c2
-rw-r--r--firmware/target/mips/ingenic_jz47xx/ata-nand-jz4760.c692
-rw-r--r--firmware/target/mips/ingenic_jz47xx/ata-sd-jz4740.c2
-rw-r--r--firmware/target/mips/ingenic_jz47xx/ata-sd-jz4760.c1487
-rw-r--r--firmware/target/mips/ingenic_jz47xx/backlight-target.h2
-rw-r--r--firmware/target/mips/ingenic_jz47xx/codec-jz4760.c293
-rw-r--r--firmware/target/mips/ingenic_jz47xx/crt0.S2
-rw-r--r--firmware/target/mips/ingenic_jz47xx/debug-jz4760.c146
-rw-r--r--firmware/target/mips/ingenic_jz47xx/dma-target.h31
-rw-r--r--firmware/target/mips/ingenic_jz47xx/dma_acc-jz4760.c102
-rw-r--r--firmware/target/mips/ingenic_jz47xx/i2c-jz4760.c355
-rw-r--r--firmware/target/mips/ingenic_jz47xx/kernel-jz4760.c53
-rw-r--r--firmware/target/mips/ingenic_jz47xx/lcd-jz4760.c28
-rw-r--r--firmware/target/mips/ingenic_jz47xx/pcm-jz4760.c240
-rw-r--r--firmware/target/mips/ingenic_jz47xx/system-jz4760.c710
-rw-r--r--firmware/target/mips/ingenic_jz47xx/system-target.h14
-rw-r--r--firmware/target/mips/ingenic_jz47xx/timer-jz4760.c101
-rw-r--r--firmware/target/mips/ingenic_jz47xx/usb-jz4760.c870
-rw-r--r--firmware/target/mips/ingenic_jz47xx/xdebug.h31
-rw-r--r--firmware/target/mips/ingenic_jz47xx/xduoo_x3/adc-target.h28
-rw-r--r--firmware/target/mips/ingenic_jz47xx/xduoo_x3/ata-sd-target.h48
-rw-r--r--firmware/target/mips/ingenic_jz47xx/xduoo_x3/backlight-xduoo_x3.c50
-rw-r--r--firmware/target/mips/ingenic_jz47xx/xduoo_x3/button-target.h46
-rw-r--r--firmware/target/mips/ingenic_jz47xx/xduoo_x3/lcd-xduoo_x3.c420
-rw-r--r--firmware/target/mips/ingenic_jz47xx/xduoo_x3/power-xduoo_x3.c46
-rw-r--r--firmware/target/mips/ingenic_jz47xx/xduoo_x3/sadc-xduoo_x3.c214
27 files changed, 6013 insertions, 6 deletions
diff --git a/firmware/target/mips/ingenic_jz47xx/app.lds b/firmware/target/mips/ingenic_jz47xx/app.lds
index a8ac6ff0bf..85c332b182 100644
--- a/firmware/target/mips/ingenic_jz47xx/app.lds
+++ b/firmware/target/mips/ingenic_jz47xx/app.lds
@@ -5,8 +5,10 @@ OUTPUT_ARCH(MIPS)
ENTRY(_start)
STARTUP(target/mips/ingenic_jz47xx/crt0.o)
-#define DRAMORIG 0x80004000
-#define DRAMSIZE (MEMORYSIZE * 0x100000)
+#define STUBOFFSET 0x4000
+
+#define DRAMORIG (0x80000000 + STUBOFFSET)
+#define DRAMSIZE (MEMORYSIZE * 0x100000 - STUBOFFSET)
#define IRAMORIG 0x80000000
#define IRAMSIZE 16K
diff --git a/firmware/target/mips/ingenic_jz47xx/ata-nand-jz4740.c b/firmware/target/mips/ingenic_jz47xx/ata-nand-jz4740.c
index ac4092f043..0ce0ed1e19 100644
--- a/firmware/target/mips/ingenic_jz47xx/ata-nand-jz4740.c
+++ b/firmware/target/mips/ingenic_jz47xx/ata-nand-jz4740.c
@@ -20,7 +20,7 @@
****************************************************************************/
#include "config.h"
-#include "jz4740.h"
+#include "cpu.h"
#include "nand.h"
#include "nand_id.h"
#include "system.h"
diff --git a/firmware/target/mips/ingenic_jz47xx/ata-nand-jz4760.c b/firmware/target/mips/ingenic_jz47xx/ata-nand-jz4760.c
new file mode 100644
index 0000000000..b3cc589528
--- /dev/null
+++ b/firmware/target/mips/ingenic_jz47xx/ata-nand-jz4760.c
@@ -0,0 +1,692 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2016 by Roman Stolyarov
+ *
+ * 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 "config.h"
+#include "cpu.h"
+#include "nand.h"
+#include "nand_id.h"
+#include "system.h"
+#include "panic.h"
+#include "kernel.h"
+#include "storage.h"
+#include "string.h"
+/*#define LOGF_ENABLE*/
+#include "logf.h"
+
+//#define USE_DMA
+//#define USE_ECC
+
+/*
+ * Standard NAND flash commands
+ */
+#define NAND_CMD_READ0 0
+#define NAND_CMD_READ1 1
+#define NAND_CMD_RNDOUT 5
+#define NAND_CMD_PAGEPROG 0x10
+#define NAND_CMD_READOOB 0x50
+#define NAND_CMD_ERASE1 0x60
+#define NAND_CMD_STATUS 0x70
+#define NAND_CMD_STATUS_MULTI 0x71
+#define NAND_CMD_SEQIN 0x80
+#define NAND_CMD_RNDIN 0x85
+#define NAND_CMD_READID 0x90
+#define NAND_CMD_ERASE2 0xd0
+#define NAND_CMD_RESET 0xff
+
+/* Extended commands for large page devices */
+#define NAND_CMD_READSTART 0x30
+#define NAND_CMD_RNDOUTSTART 0xE0
+#define NAND_CMD_CACHEDPROG 0x15
+
+/* Status bits */
+#define NAND_STATUS_FAIL 0x01
+#define NAND_STATUS_FAIL_N1 0x02
+#define NAND_STATUS_TRUE_READY 0x20
+#define NAND_STATUS_READY 0x40
+#define NAND_STATUS_WP 0x80
+
+/*
+ * NAND parameter struct
+ */
+struct nand_param {
+ unsigned int bus_width; /* data bus width: 8-bit/16-bit */
+ unsigned int row_cycle; /* row address cycles: 2/3 */
+ unsigned int page_size; /* page size in bytes: 512/2048/4096 */
+ unsigned int oob_size; /* oob size in bytes: 16/64/128 */
+ unsigned int page_per_block; /* pages per block: 32/64/128 */
+ unsigned int bad_block_pos; /* bad block pos in oob: 0/5 */
+};
+
+/*
+ * jz4760_nand.c
+ *
+ * NAND read routine for JZ4760
+ *
+ * Copyright (c) 2005-2008 Ingenic Semiconductor Inc.
+ *
+ */
+
+#define CFG_NAND_BASE 0xBA000000
+#define NAND_ADDR_OFFSET 0x00800000
+#define NAND_CMD_OFFSET 0x00400000
+
+#define CFG_NAND_SMCR1 0x0d444400
+
+#define NAND_DATAPORT CFG_NAND_BASE
+#define NAND_ADDRPORT (CFG_NAND_BASE | NAND_ADDR_OFFSET)
+#define NAND_COMMPORT (CFG_NAND_BASE | NAND_CMD_OFFSET)
+
+#define ECC_BLOCK 512
+#define ECC_POS 24
+#define PAR_SIZE 13
+
+#define __nand_cmd(n) (REG8(NAND_COMMPORT) = (n))
+#define __nand_addr(n) (REG8(NAND_ADDRPORT) = (n))
+#define __nand_data8() (REG8(NAND_DATAPORT))
+#define __nand_data16() (REG16(NAND_DATAPORT))
+
+#define __nand_select() (REG_NEMC_NFCSR |= NEMC_NFCSR_NFE1 | NEMC_NFCSR_NFCE1)
+#define __nand_deselect() (REG_NEMC_NFCSR &= ~(NEMC_NFCSR_NFE1 | NEMC_NFCSR_NFCE1))
+
+/*--------------------------------------------------------------*/
+
+static struct nand_info* chip_info = NULL;
+static struct nand_info* bank;
+static unsigned long nand_size;
+static struct nand_param internal_param;
+static struct mutex nand_mtx;
+#ifdef USE_DMA
+static struct mutex nand_dma_mtx;
+static struct semaphore nand_dma_complete;
+#endif
+static unsigned char temp_page[2048]; /* Max page size */
+
+static inline void jz_nand_wait_ready(void)
+{
+ unsigned int timeout = 1000;
+ while ((REG_GPIO_PXPIN(0) & 0x00100000) && timeout--);
+ while (!(REG_GPIO_PXPIN(0) & 0x00100000));
+}
+
+#ifndef USE_DMA
+static inline void jz_nand_read_buf16(void *buf, int count)
+{
+ register int i;
+ register unsigned short *p = (unsigned short *)buf;
+
+ for (i = 0; i < count; i += 2)
+ *p++ = __nand_data16();
+}
+
+static inline void jz_nand_read_buf8(void *buf, int count)
+{
+ register int i;
+ register unsigned char *p = (unsigned char *)buf;
+
+ for (i = 0; i < count; i++)
+ *p++ = __nand_data8();
+}
+#else
+static void jz_nand_write_dma(void *source, unsigned int len, int bw)
+{
+ mutex_lock(&nand_dma_mtx);
+
+ if(((unsigned int)source < 0xa0000000) && len)
+ dma_cache_wback_inv((unsigned long)source, len);
+
+ dma_enable();
+
+ REG_DMAC_DCCSR(DMA_NAND_CHANNEL) = DMAC_DCCSR_NDES;
+ REG_DMAC_DSAR(DMA_NAND_CHANNEL) = PHYSADDR((unsigned long)source);
+ REG_DMAC_DTAR(DMA_NAND_CHANNEL) = PHYSADDR((unsigned long)NAND_DATAPORT);
+ REG_DMAC_DTCR(DMA_NAND_CHANNEL) = len / 16;
+ REG_DMAC_DRSR(DMA_NAND_CHANNEL) = DMAC_DRSR_RS_AUTO;
+ REG_DMAC_DCMD(DMA_NAND_CHANNEL) = (DMAC_DCMD_SAI| DMAC_DCMD_DAI | DMAC_DCMD_SWDH_32 | DMAC_DCMD_DS_16BYTE |
+ (bw == 8 ? DMAC_DCMD_DWDH_8 : DMAC_DCMD_DWDH_16));
+
+ REG_DMAC_DCCSR(DMA_NAND_CHANNEL) |= DMAC_DCCSR_EN; /* Enable DMA channel */
+#if 1
+ while( REG_DMAC_DTCR(DMA_NAND_CHANNEL) )
+ yield();
+#else
+ REG_DMAC_DCMD(DMA_NAND_CHANNEL) |= DMAC_DCMD_TIE; /* Enable DMA interrupt */
+ semaphore_wait(&nand_dma_complete, TIMEOUT_BLOCK);
+#endif
+
+ REG_DMAC_DCCSR(DMA_NAND_CHANNEL) &= ~DMAC_DCCSR_EN; /* Disable DMA channel */
+
+ dma_disable();
+
+ mutex_unlock(&nand_dma_mtx);
+}
+
+static void jz_nand_read_dma(void *target, unsigned int len, int bw)
+{
+ mutex_lock(&nand_dma_mtx);
+
+ if(((unsigned int)target < 0xa0000000) && len)
+ dma_cache_wback_inv((unsigned long)target, len);
+
+ dma_enable();
+
+ REG_DMAC_DCCSR(DMA_NAND_CHANNEL) = DMAC_DCCSR_NDES ;
+ REG_DMAC_DSAR(DMA_NAND_CHANNEL) = PHYSADDR((unsigned long)NAND_DATAPORT);
+ REG_DMAC_DTAR(DMA_NAND_CHANNEL) = PHYSADDR((unsigned long)target);
+ REG_DMAC_DTCR(DMA_NAND_CHANNEL) = len / 4;
+ REG_DMAC_DRSR(DMA_NAND_CHANNEL) = DMAC_DRSR_RS_AUTO;
+ REG_DMAC_DCMD(DMA_NAND_CHANNEL) = (DMAC_DCMD_SAI| DMAC_DCMD_DAI | DMAC_DCMD_DWDH_32 | DMAC_DCMD_DS_32BIT |
+ (bw == 8 ? DMAC_DCMD_SWDH_8 : DMAC_DCMD_SWDH_16));
+ REG_DMAC_DCCSR(DMA_NAND_CHANNEL) |= DMAC_DCCSR_EN; /* Enable DMA channel */
+#if 1
+ while( REG_DMAC_DTCR(DMA_NAND_CHANNEL) )
+ yield();
+#else
+ REG_DMAC_DCMD(DMA_NAND_CHANNEL) |= DMAC_DCMD_TIE; /* Enable DMA interrupt */
+ semaphore_wait(&nand_dma_complete, TIMEOUT_BLOCK);
+#endif
+
+ //REG_DMAC_DCCSR(DMA_NAND_CHANNEL) &= ~DMAC_DCCSR_EN; /* Disable DMA channel */
+
+ dma_disable();
+
+ mutex_unlock(&nand_dma_mtx);
+}
+
+void DMA_CALLBACK(DMA_NAND_CHANNEL)(void)
+{
+ if (REG_DMAC_DCCSR(DMA_NAND_CHANNEL) & DMAC_DCCSR_HLT)
+ REG_DMAC_DCCSR(DMA_NAND_CHANNEL) &= ~DMAC_DCCSR_HLT;
+
+ if (REG_DMAC_DCCSR(DMA_NAND_CHANNEL) & DMAC_DCCSR_AR)
+ REG_DMAC_DCCSR(DMA_NAND_CHANNEL) &= ~DMAC_DCCSR_AR;
+
+ if (REG_DMAC_DCCSR(DMA_NAND_CHANNEL) & DMAC_DCCSR_CT)
+ REG_DMAC_DCCSR(DMA_NAND_CHANNEL) &= ~DMAC_DCCSR_CT;
+
+ if (REG_DMAC_DCCSR(DMA_NAND_CHANNEL) & DMAC_DCCSR_TT)
+ REG_DMAC_DCCSR(DMA_NAND_CHANNEL) &= ~DMAC_DCCSR_TT;
+
+ semaphore_release(&nand_dma_complete);
+}
+#endif /* USE_DMA */
+
+static inline void jz_nand_read_buf(void *buf, int count, int bw)
+{
+#ifdef USE_DMA
+ if (bw == 8)
+ jz_nand_read_dma(buf, count, 8);
+ else
+ jz_nand_read_dma(buf, count, 16);
+#else
+ if (bw == 8)
+ jz_nand_read_buf8(buf, count);
+ else
+ jz_nand_read_buf16(buf, count);
+#endif
+}
+
+#ifdef USE_ECC
+/*
+ * Correct 1~9-bit errors in 512-bytes data
+ */
+static void jz_rs_correct(unsigned char *dat, int idx, int mask)
+{
+ int i, j;
+ unsigned short d, d1, dm;
+
+ i = (idx * 9) >> 3;
+ j = (idx * 9) & 0x7;
+
+ i = (j == 0) ? (i - 1) : i;
+ j = (j == 0) ? 7 : (j - 1);
+
+ if (i > 512)
+ return;
+
+ if (i == 512)
+ d = dat[i - 1];
+ else
+ d = (dat[i] << 8) | dat[i - 1];
+
+ d1 = (d >> j) & 0x1ff;
+ d1 ^= mask;
+
+ dm = ~(0x1ff << j);
+ d = (d & dm) | (d1 << j);
+
+ dat[i - 1] = d & 0xff;
+ if (i < 512)
+ dat[i] = (d >> 8) & 0xff;
+}
+#endif
+
+/*
+ * Read oob
+ */
+static int jz_nand_read_oob(unsigned long page_addr, unsigned char *buf, int size)
+{
+ struct nand_param *nandp = &internal_param;
+ int page_size, row_cycle, bus_width;
+ int col_addr;
+
+ page_size = nandp->page_size;
+ row_cycle = nandp->row_cycle;
+ bus_width = nandp->bus_width;
+
+ if (page_size >= 2048)
+ col_addr = page_size;
+ else
+ col_addr = 0;
+
+ if (page_size >= 2048)
+ /* Send READ0 command */
+ __nand_cmd(NAND_CMD_READ0);
+ else
+ /* Send READOOB command */
+ __nand_cmd(NAND_CMD_READOOB);
+
+ /* Send column address */
+ __nand_addr(col_addr & 0xff);
+ if (page_size >= 2048)
+ __nand_addr((col_addr >> 8) & 0xff);
+
+ /* Send page address */
+ __nand_addr(page_addr & 0xff);
+ __nand_addr((page_addr >> 8) & 0xff);
+ if (row_cycle == 3)
+ __nand_addr((page_addr >> 16) & 0xff);
+
+ /* Send READSTART command for 2048 ps NAND */
+ if (page_size >= 2048)
+ __nand_cmd(NAND_CMD_READSTART);
+
+ /* Wait for device ready */
+ jz_nand_wait_ready();
+
+ /* Read oob data */
+ jz_nand_read_buf(buf, size, bus_width);
+
+ return 0;
+}
+
+
+/*
+ * nand_read_page()
+ *
+ * Input:
+ *
+ * block - block number: 0, 1, 2, ...
+ * page - page number within a block: 0, 1, 2, ...
+ * dst - pointer to target buffer
+ */
+static int jz_nand_read_page(unsigned long page_addr, unsigned char *dst)
+{
+ struct nand_param *nandp = &internal_param;
+ int page_size, oob_size, page_per_block;
+ int row_cycle, bus_width, ecc_count;
+ int i;
+#ifdef USE_ECC
+ int j;
+#endif
+ unsigned char *data_buf;
+ unsigned char oob_buf[nandp->oob_size];
+
+ page_size = nandp->page_size;
+ oob_size = nandp->oob_size;
+ page_per_block = nandp->page_per_block;
+ row_cycle = nandp->row_cycle;
+ bus_width = nandp->bus_width;
+
+ /*
+ * Read oob data
+ */
+ jz_nand_read_oob(page_addr, oob_buf, oob_size);
+
+ /*
+ * Read page data
+ */
+
+ /* Send READ0 command */
+ __nand_cmd(NAND_CMD_READ0);
+
+ /* Send column address */
+ __nand_addr(0);
+ if (page_size >= 2048)
+ __nand_addr(0);
+
+ /* Send page address */
+ __nand_addr(page_addr & 0xff);
+ __nand_addr((page_addr >> 8) & 0xff);
+ if (row_cycle >= 3)
+ __nand_addr((page_addr >> 16) & 0xff);
+
+ /* Send READSTART command for 2048 ps NAND */
+ if (page_size >= 2048)
+ __nand_cmd(NAND_CMD_READSTART);
+
+ /* Wait for device ready */
+ jz_nand_wait_ready();
+
+ /* Read page data */
+ data_buf = dst;
+
+ ecc_count = page_size / ECC_BLOCK;
+
+ for (i = 0; i < ecc_count; i++)
+ {
+#ifdef USE_ECC
+ volatile unsigned char *paraddr = (volatile unsigned char *)EMC_NFPAR0;
+ unsigned int stat;
+
+ /* Enable RS decoding */
+ REG_EMC_NFINTS = 0x0;
+ __ecc_decoding_4bit();
+#endif
+
+ /* Read data */
+ jz_nand_read_buf((void *)data_buf, ECC_BLOCK, bus_width);
+
+#ifdef USE_ECC
+ /* Set PAR values */
+ for (j = 0; j < PAR_SIZE; j++)
+ *paraddr++ = oob_buf[ECC_POS + i*PAR_SIZE + j];
+
+ /* Set PRDY */
+ REG_EMC_NFECR |= EMC_NFECR_PRDY;
+
+ /* Wait for completion */
+ __ecc_decode_sync();
+
+ /* Disable decoding */
+ __ecc_disable();
+
+ /* Check result of decoding */
+ stat = REG_EMC_NFINTS;
+ if (stat & EMC_NFINTS_ERR)
+ {
+ /* Error occurred */
+ if (stat & EMC_NFINTS_UNCOR)
+ {
+ /* Uncorrectable error occurred */
+ logf("Uncorrectable ECC error at NAND page address 0x%lx", page_addr);
+ return -1;
+ }
+ else
+ {
+ unsigned int errcnt, index, mask;
+
+ errcnt = (stat & EMC_NFINTS_ERRCNT_MASK) >> EMC_NFINTS_ERRCNT_BIT;
+ switch (errcnt)
+ {
+ case 4:
+ index = (REG_EMC_NFERR3 & EMC_NFERR_INDEX_MASK) >> EMC_NFERR_INDEX_BIT;
+ mask = (REG_EMC_NFERR3 & EMC_NFERR_MASK_MASK) >> EMC_NFERR_MASK_BIT;
+ jz_rs_correct(data_buf, index, mask);
+ case 3:
+ index = (REG_EMC_NFERR2 & EMC_NFERR_INDEX_MASK) >> EMC_NFERR_INDEX_BIT;
+ mask = (REG_EMC_NFERR2 & EMC_NFERR_MASK_MASK) >> EMC_NFERR_MASK_BIT;
+ jz_rs_correct(data_buf, index, mask);
+ case 2:
+ index = (REG_EMC_NFERR1 & EMC_NFERR_INDEX_MASK) >> EMC_NFERR_INDEX_BIT;
+ mask = (REG_EMC_NFERR1 & EMC_NFERR_MASK_MASK) >> EMC_NFERR_MASK_BIT;
+ jz_rs_correct(data_buf, index, mask);
+ case 1:
+ index = (REG_EMC_NFERR0 & EMC_NFERR_INDEX_MASK) >> EMC_NFERR_INDEX_BIT;
+ mask = (REG_EMC_NFERR0 & EMC_NFERR_MASK_MASK) >> EMC_NFERR_MASK_BIT;
+ jz_rs_correct(data_buf, index, mask);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+#endif
+
+ data_buf += ECC_BLOCK;
+ }
+
+ return 0;
+}
+
+static int jz_nand_init(void)
+{
+ unsigned char cData[5];
+
+ __gpio_as_nand_16bit(1);
+
+ REG_NEMC_SMCR1 = CFG_NAND_SMCR1 | 0x40;
+
+ __nand_select();
+
+ __nand_cmd(NAND_CMD_READID);
+ __nand_addr(NAND_CMD_READ0);
+ cData[0] = __nand_data8();
+ cData[1] = __nand_data8();
+ cData[2] = __nand_data8();
+ cData[3] = __nand_data8();
+ cData[4] = __nand_data8();
+
+ __nand_deselect();
+
+ logf("NAND chip %d: 0x%x 0x%x 0x%x 0x%x 0x%x", i+1, cData[0], cData[1],
+ cData[2], cData[3], cData[4]);
+
+ bank = nand_identify(cData);
+
+ if(bank == NULL)
+ {
+ panicf("Unknown NAND flash chip: 0x%x 0x%x 0x%x 0x%x 0x%x", cData[0],
+ cData[1], cData[2], cData[3], cData[4]);
+ return -1; /* panicf() doesn't return though */
+ }
+
+ chip_info = bank;
+
+ internal_param.bus_width = 16;
+ internal_param.row_cycle = chip_info->row_cycles;
+ internal_param.page_size = chip_info->page_size;
+ internal_param.oob_size = chip_info->spare_size;
+ internal_param.page_per_block = chip_info->pages_per_block;
+ internal_param.bad_block_pos = 0;
+
+ nand_size = ((chip_info->page_size * chip_info->blocks_per_bank * chip_info->pages_per_block) - 0x200000) / 512;
+
+ return 0;
+}
+
+int nand_init(void)
+{
+ int res = 0;
+ static bool inited = false;
+
+ if(!inited)
+ {
+ res = jz_nand_init();
+ mutex_init(&nand_mtx);
+#ifdef USE_DMA
+ mutex_init(&nand_dma_mtx);
+ semaphore_init(&nand_dma_complete, 1, 0);
+ system_enable_irq(DMA_IRQ(DMA_NAND_CHANNEL));
+#endif
+
+ inited = true;
+ }
+
+ return res;
+}
+
+static inline int read_sector(unsigned long start, unsigned int count,
+ void* buf, unsigned int chip_size)
+{
+ register int ret;
+
+ if(UNLIKELY(start % chip_size == 0 && count == chip_size))
+ ret = jz_nand_read_page(start / chip_size, buf);
+ else
+ {
+ ret = jz_nand_read_page(start / chip_size, temp_page);
+ memcpy(buf, temp_page + (start % chip_size), count);
+ }
+
+ return ret;
+}
+
+static inline int write_sector(unsigned long start, unsigned int count,
+ const void* buf, unsigned int chip_size)
+{
+ int ret = 0;
+
+ (void)start;
+ (void)count;
+ (void)buf;
+ (void)chip_size;
+
+ /* TODO */
+
+ return ret;
+}
+
+int nand_read_sectors(IF_MV(int drive,) unsigned long start, int count, void* buf)
+{
+#ifdef HAVE_MULTIVOLUME
+ (void)drive;
+#endif
+ int ret = 0;
+ unsigned int _count, chip_size = chip_info->page_size;
+ unsigned long _start;
+
+ logf("start");
+ mutex_lock(&nand_mtx);
+
+ _start = start << 9;
+ _start += 0x200000; /* skip BL */
+ _count = count << 9;
+
+ __nand_select();
+ ret = read_sector(_start, _count, buf, chip_size);
+ __nand_deselect();
+
+ mutex_unlock(&nand_mtx);
+
+ logf("nand_read_sectors(%ld, %d, 0x%x): %d", start, count, (int)buf, ret);
+
+ return ret;
+}
+
+int nand_write_sectors(IF_MV(int drive,) unsigned long start, int count, const void* buf)
+{
+#ifdef HAVE_MULTIVOLUME
+ (void)drive;
+#endif
+ int ret = 0;
+ unsigned int _count, chip_size = chip_info->page_size;
+ unsigned long _start;
+
+ logf("start");
+ mutex_lock(&nand_mtx);
+
+ _start = start << 9;
+ _start += chip_info->page_size * chip_info->pages_per_block; /* skip BL */
+ _count = count << 9;
+
+ __nand_select();
+ ret = write_sector(_start, _count, buf, chip_size);
+ __nand_deselect();
+
+ mutex_unlock(&nand_mtx);
+
+ logf("nand_write_sectors(%ld, %d, 0x%x): %d", start, count, (int)buf, ret);
+
+ return ret;
+}
+
+#ifdef HAVE_STORAGE_FLUSH
+int nand_flush(void)
+{
+ return 0;
+}
+#endif
+
+void nand_spindown(int seconds)
+{
+ /* null */
+ (void)seconds;
+}
+
+void nand_sleep(void)
+{
+ /* null */
+}
+
+void nand_spin(void)
+{
+ /* null */
+}
+
+void nand_enable(bool on)
+{
+ /* null - flash controller is enabled/disabled as needed. */
+ (void)on;
+}
+
+/* TODO */
+long nand_last_disk_activity(void)
+{
+ return 0;
+}
+
+int nand_spinup_time(void)
+{
+ return 0;
+}
+
+void nand_sleepnow(void)
+{
+}
+
+#ifdef STORAGE_GET_INFO
+void nand_get_info(IF_MV(int drive,) struct storage_info *info)
+{
+#ifdef HAVE_MULTIVOLUME
+ (void)drive;
+#endif
+
+ /* firmware version */
+ info->revision="0.00";
+
+ info->vendor="Rockbox";
+ info->product="NAND Storage";
+
+ /* blocks count */
+ info->num_sectors = nand_size;
+ info->sector_size = 512;
+}
+#endif
+
+#ifdef CONFIG_STORAGE_MULTI
+int nand_num_drives(int first_drive)
+{
+ /* We don't care which logical drive number(s) we have been assigned */
+ (void)first_drive;
+
+ return 1;
+}
+#endif
diff --git a/firmware/target/mips/ingenic_jz47xx/ata-sd-jz4740.c b/firmware/target/mips/ingenic_jz47xx/ata-sd-jz4740.c
index 9565551df6..9215d7d08a 100644
--- a/firmware/target/mips/ingenic_jz47xx/ata-sd-jz4740.c
+++ b/firmware/target/mips/ingenic_jz47xx/ata-sd-jz4740.c
@@ -21,7 +21,7 @@
#include "config.h"
#include "gcc_extensions.h"
-#include "jz4740.h"
+#include "cpu.h"
#include "ata-sd-target.h"
#include "led.h"
#include "sdmmc.h"
diff --git a/firmware/target/mips/ingenic_jz47xx/ata-sd-jz4760.c b/firmware/target/mips/ingenic_jz47xx/ata-sd-jz4760.c
new file mode 100644
index 0000000000..c34f74a202
--- /dev/null
+++ b/firmware/target/mips/ingenic_jz47xx/ata-sd-jz4760.c
@@ -0,0 +1,1487 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2016 by Roman Stolyarov
+ *
+ * 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 "config.h"
+#include "gcc_extensions.h"
+#include "cpu.h"
+#include "ata-sd-target.h"
+#include "dma-target.h"
+#include "led.h"
+#include "sdmmc.h"
+#include "logf.h"
+#include "storage.h"
+#include "string.h"
+
+static long last_disk_activity = -1;
+static tCardInfo card[NUM_DRIVES];
+
+static struct mutex sd_mtx;
+static struct semaphore sd_wakeup;
+
+static int use_4bit[NUM_DRIVES];
+static int num_6[NUM_DRIVES];
+static int sd2_0[NUM_DRIVES];
+
+#define SD_DMA_ENABLE 1
+
+//#define DEBUG(x...) logf(x)
+#define DEBUG(x, ...)
+
+/* volumes */
+#define SD_SLOT_1 0 /* SD card 1 */
+#define SD_SLOT_2 1 /* SD card 2 */
+
+#define MSC_CHN(n) (2-n)
+
+#define SD_IRQ_MASK(n) \
+do { \
+ REG_MSC_IMASK(n) = 0xffff; \
+ REG_MSC_IREG(n) = 0xffff; \
+} while (0)
+
+/* Error codes */
+enum sd_result_t
+{
+ SD_NO_RESPONSE = -1,
+ SD_NO_ERROR = 0,
+ SD_ERROR_OUT_OF_RANGE,
+ SD_ERROR_ADDRESS,
+ SD_ERROR_BLOCK_LEN,
+ SD_ERROR_ERASE_SEQ,
+ SD_ERROR_ERASE_PARAM,
+ SD_ERROR_WP_VIOLATION,
+ SD_ERROR_CARD_IS_LOCKED,
+ SD_ERROR_LOCK_UNLOCK_FAILED,
+ SD_ERROR_COM_CRC,
+ SD_ERROR_ILLEGAL_COMMAND,
+ SD_ERROR_CARD_ECC_FAILED,
+ SD_ERROR_CC,
+ SD_ERROR_GENERAL,
+ SD_ERROR_UNDERRUN,
+ SD_ERROR_OVERRUN,
+ SD_ERROR_CID_CSD_OVERWRITE,
+ SD_ERROR_STATE_MISMATCH,
+ SD_ERROR_HEADER_MISMATCH,
+ SD_ERROR_TIMEOUT,
+ SD_ERROR_CRC,
+ SD_ERROR_DRIVER_FAILURE,
+};
+
+/* Standard MMC/SD clock speeds */
+#define MMC_CLOCK_SLOW 400000 /* 400 kHz for initial setup */
+#define SD_CLOCK_FAST 24000000 /* 24 MHz for SD Cards */
+#define SD_CLOCK_HIGH 48000000 /* 48 MHz for SD Cards */
+
+/* Extra commands for state control */
+/* Use negative numbers to disambiguate */
+#define SD_CIM_RESET -1
+
+/* Proprietary commands, illegal/reserved according to SD Specification 2.00 */
+ /* class 1 */
+#define SD_READ_DAT_UNTIL_STOP 11 /* adtc [31:0] dadr R1 */
+
+ /* class 3 */
+#define SD_WRITE_DAT_UNTIL_STOP 20 /* adtc [31:0] data addr R1 */
+
+ /* class 4 */
+#define SD_PROGRAM_CID 26 /* adtc R1 */
+#define SD_PROGRAM_CSD 27 /* adtc R1 */
+
+ /* class 9 */
+#define SD_GO_IRQ_STATE 40 /* bcr R5 */
+
+/* Don't change the order of these; they are used in dispatch tables */
+enum sd_rsp_t
+{
+ RESPONSE_NONE = 0,
+ RESPONSE_R1 = 1,
+ RESPONSE_R1B = 2,
+ RESPONSE_R2_CID = 3,
+ RESPONSE_R2_CSD = 4,
+ RESPONSE_R3 = 5,
+ RESPONSE_R4 = 6,
+ RESPONSE_R5 = 7,
+ RESPONSE_R6 = 8,
+ RESPONSE_R7 = 9,
+};
+
+/*
+ MMC status in R1
+ Type
+ e : error bit
+ s : status bit
+ r : detected and set for the actual command response
+ x : detected and set during command execution. the host must poll
+ the card by sending status command in order to read these bits.
+ Clear condition
+ a : according to the card state
+ b : always related to the previous command. Reception of
+ a valid command will clear it (with a delay of one command)
+ c : clear by read
+ */
+
+#define R1_OUT_OF_RANGE (1 << 31) /* er, c */
+#define R1_ADDRESS_ERROR (1 << 30) /* erx, c */
+#define R1_BLOCK_LEN_ERROR (1 << 29) /* er, c */
+#define R1_ERASE_SEQ_ERROR (1 << 28) /* er, c */
+#define R1_ERASE_PARAM (1 << 27) /* ex, c */
+#define R1_WP_VIOLATION (1 << 26) /* erx, c */
+#define R1_CARD_IS_LOCKED (1 << 25) /* sx, a */
+#define R1_LOCK_UNLOCK_FAILED (1 << 24) /* erx, c */
+#define R1_COM_CRC_ERROR (1 << 23) /* er, b */
+#define R1_ILLEGAL_COMMAND (1 << 22) /* er, b */
+#define R1_CARD_ECC_FAILED (1 << 21) /* ex, c */
+#define R1_CC_ERROR (1 << 20) /* erx, c */
+#define R1_ERROR (1 << 19) /* erx, c */
+#define R1_UNDERRUN (1 << 18) /* ex, c */
+#define R1_OVERRUN (1 << 17) /* ex, c */
+#define R1_CID_CSD_OVERWRITE (1 << 16) /* erx, c, CID/CSD overwrite */
+#define R1_WP_ERASE_SKIP (1 << 15) /* sx, c */
+#define R1_CARD_ECC_DISABLED (1 << 14) /* sx, a */
+#define R1_ERASE_RESET (1 << 13) /* sr, c */
+#define R1_STATUS(x) (x & 0xFFFFE000)
+#define R1_CURRENT_STATE(x) ((x & 0x00001E00) >> 9) /* sx, b (4 bits) */
+#define R1_READY_FOR_DATA (1 << 8) /* sx, a */
+#define R1_APP_CMD (1 << 7) /* sr, c */
+
+/* These are unpacked versions of the actual responses */
+struct sd_response_r1
+{
+ unsigned char cmd;
+ unsigned int status;
+};
+
+struct sd_response_r3
+{
+ unsigned int ocr;
+};
+
+#define SD_CARD_BUSY 0x80000000 /* Card Power up status bit */
+
+struct sd_request
+{
+ int index; /* Slot index - used for CS lines */
+ int cmd; /* Command to send */
+ unsigned int arg; /* Argument to send */
+ enum sd_rsp_t rtype; /* Response type expected */
+
+ /* Data transfer (these may be modified at the low level) */
+ unsigned short nob; /* Number of blocks to transfer*/
+ unsigned short block_len; /* Block length */
+ unsigned char *buffer; /* Data buffer */
+ unsigned int cnt; /* Data length, for PIO */
+
+ /* Results */
+ unsigned char response[18]; /* Buffer to store response - CRC is optional */
+ enum sd_result_t result;
+};
+
+#define SD_OCR_ARG 0x00ff8000 /* Argument of OCR */
+
+/***********************************************************************
+ * SD Events
+ */
+#define SD_EVENT_NONE 0x00 /* No events */
+#define SD_EVENT_RX_DATA_DONE 0x01 /* Rx data done */
+#define SD_EVENT_TX_DATA_DONE 0x02 /* Tx data done */
+#define SD_EVENT_PROG_DONE 0x04 /* Programming is done */
+
+/**************************************************************************
+ * Utility functions
+ **************************************************************************/
+
+#define PARSE_U32(_buf,_index) \
+ (((unsigned int)_buf[_index]) << 24) | (((unsigned int)_buf[_index+1]) << 16) | \
+ (((unsigned int)_buf[_index+2]) << 8) | ((unsigned int)_buf[_index+3]);
+
+#define PARSE_U16(_buf,_index) \
+ (((unsigned short)_buf[_index]) << 8) | ((unsigned short)_buf[_index+1]);
+
+static int sd_unpack_r1(struct sd_request *request, struct sd_response_r1 *r1)
+{
+ unsigned char *buf = request->response;
+
+ if (request->result)
+ return request->result;
+
+ r1->cmd = buf[0];
+ r1->status = PARSE_U32(buf,1);
+
+ DEBUG("sd_unpack_r1: cmd=%d status=%08x", r1->cmd, r1->status);
+
+ if (R1_STATUS(r1->status)) {
+ if (r1->status & R1_OUT_OF_RANGE) return SD_ERROR_OUT_OF_RANGE;
+ if (r1->status & R1_ADDRESS_ERROR) return SD_ERROR_ADDRESS;
+ if (r1->status & R1_BLOCK_LEN_ERROR) return SD_ERROR_BLOCK_LEN;
+ if (r1->status & R1_ERASE_SEQ_ERROR) return SD_ERROR_ERASE_SEQ;
+ if (r1->status & R1_ERASE_PARAM) return SD_ERROR_ERASE_PARAM;
+ if (r1->status & R1_WP_VIOLATION) return SD_ERROR_WP_VIOLATION;
+ //if (r1->status & R1_CARD_IS_LOCKED) return SD_ERROR_CARD_IS_LOCKED;
+ if (r1->status & R1_LOCK_UNLOCK_FAILED) return SD_ERROR_LOCK_UNLOCK_FAILED;
+ if (r1->status & R1_COM_CRC_ERROR) return SD_ERROR_COM_CRC;
+ if (r1->status & R1_ILLEGAL_COMMAND) return SD_ERROR_ILLEGAL_COMMAND;
+ if (r1->status & R1_CARD_ECC_FAILED) return SD_ERROR_CARD_ECC_FAILED;
+ if (r1->status & R1_CC_ERROR) return SD_ERROR_CC;
+ if (r1->status & R1_ERROR) return SD_ERROR_GENERAL;
+ if (r1->status & R1_UNDERRUN) return SD_ERROR_UNDERRUN;
+ if (r1->status & R1_OVERRUN) return SD_ERROR_OVERRUN;
+ if (r1->status & R1_CID_CSD_OVERWRITE) return SD_ERROR_CID_CSD_OVERWRITE;
+ }
+
+ if (buf[0] != request->cmd)
+ return SD_ERROR_HEADER_MISMATCH;
+
+ /* This should be last - it's the least dangerous error */
+
+ return 0;
+}
+
+static int sd_unpack_r6(struct sd_request *request, struct sd_response_r1 *r1, unsigned long *rca)
+{
+ unsigned char *buf = request->response;
+
+ if (request->result)
+ return request->result;
+
+ *rca = PARSE_U16(buf,1); /* Save RCA returned by the SD Card */
+
+ *(buf+1) = 0;
+ *(buf+2) = 0;
+
+ return sd_unpack_r1(request, r1);
+}
+
+static int sd_unpack_r3(struct sd_request *request, struct sd_response_r3 *r3)
+{
+ unsigned char *buf = request->response;
+
+ if (request->result) return request->result;
+
+ r3->ocr = PARSE_U32(buf,1);
+ DEBUG("sd_unpack_r3: ocr=%08x", r3->ocr);
+
+ if (buf[0] != 0x3f)
+ return SD_ERROR_HEADER_MISMATCH;
+
+ return 0;
+}
+
+/* Stop the MMC clock and wait while it happens */
+static inline int jz_sd_stop_clock(const int drive)
+{
+ register int timeout = 1000;
+
+ //DEBUG("stop MMC clock");
+ REG_MSC_STRPCL(MSC_CHN(drive)) = MSC_STRPCL_CLOCK_CONTROL_STOP;
+
+ while (timeout && (REG_MSC_STAT(MSC_CHN(drive)) & MSC_STAT_CLK_EN))
+ {
+ timeout--;
+ if (timeout == 0)
+ {
+ DEBUG("Timeout on stop clock waiting");
+ return SD_ERROR_TIMEOUT;
+ }
+ udelay(1);
+ }
+ //DEBUG("clock off time is %d microsec", timeout);
+ return SD_NO_ERROR;
+}
+
+/* Start the MMC clock and operation */
+static inline int jz_sd_start_clock(const int drive)
+{
+ REG_MSC_STRPCL(MSC_CHN(drive)) = MSC_STRPCL_CLOCK_CONTROL_START | MSC_STRPCL_START_OP;
+ return SD_NO_ERROR;
+}
+
+static int jz_sd_check_status(const int drive, struct sd_request *request)
+{
+ (void)request;
+ unsigned int status = REG_MSC_STAT(MSC_CHN(drive));
+
+ /* Checking for response or data timeout */
+ if (status & (MSC_STAT_TIME_OUT_RES | MSC_STAT_TIME_OUT_READ))
+ {
+ DEBUG("SD timeout, MSC_STAT 0x%x CMD %d", status,
+ request->cmd);
+ return SD_ERROR_TIMEOUT;
+ }
+
+ /* Checking for CRC error */
+ if (status &
+ (MSC_STAT_CRC_READ_ERROR | MSC_STAT_CRC_WRITE_ERROR |
+ MSC_STAT_CRC_RES_ERR))
+ {
+ DEBUG("SD CRC error, MSC_STAT 0x%x", status);
+ return SD_ERROR_CRC;
+
+ }
+
+
+ /* Checking for FIFO empty */
+ /*if(status & MSC_STAT_DATA_FIFO_EMPTY && request->rtype != RESPONSE_NONE)
+ {
+ DEBUG("SD FIFO empty, MSC_STAT 0x%x", status);
+ return SD_ERROR_UNDERRUN;
+ }*/
+
+ return SD_NO_ERROR;
+}
+
+/* Obtain response to the command and store it to response buffer */
+static void jz_sd_get_response(const int drive, struct sd_request *request)
+{
+ int i;
+ unsigned char *buf;
+ unsigned int data;
+
+ DEBUG("fetch response for request %d, cmd %d", request->rtype,
+ request->cmd);
+ buf = request->response;
+ request->result = SD_NO_ERROR;
+
+ switch (request->rtype)
+ {
+ case RESPONSE_R1:
+ case RESPONSE_R1B:
+ case RESPONSE_R7:
+ case RESPONSE_R6:
+ case RESPONSE_R3:
+ case RESPONSE_R4:
+ case RESPONSE_R5:
+ {
+ data = REG_MSC_RES(MSC_CHN(drive));
+ buf[0] = (data >> 8) & 0xff;
+ buf[1] = data & 0xff;
+ data = REG_MSC_RES(MSC_CHN(drive));
+ buf[2] = (data >> 8) & 0xff;
+ buf[3] = data & 0xff;
+ data = REG_MSC_RES(MSC_CHN(drive));
+ buf[4] = data & 0xff;
+
+ DEBUG("request %d, response [%02x %02x %02x %02x %02x]",
+ request->rtype, buf[0], buf[1], buf[2],
+ buf[3], buf[4]);
+ break;
+ }
+ case RESPONSE_R2_CID:
+ case RESPONSE_R2_CSD:
+ {
+ for (i = 0; i < 16; i += 2)
+ {
+ data = REG_MSC_RES(MSC_CHN(drive));
+ buf[i] = (data >> 8) & 0xff;
+ buf[i + 1] = data & 0xff;
+ }
+ DEBUG("request %d, response []", request->rtype);
+ break;
+ }
+ case RESPONSE_NONE:
+ DEBUG("No response");
+ break;
+
+ default:
+ DEBUG("unhandled response type for request %d",
+ request->rtype);
+ break;
+ }
+}
+
+static int jz_sd_receive_data(const int drive, struct sd_request *req)
+{
+ unsigned int nob = req->nob;
+ unsigned int wblocklen = (unsigned int) (req->block_len + 3) >> 2; /* length in word */
+ unsigned char *buf = req->buffer;
+ unsigned int *wbuf = (unsigned int *) buf;
+ unsigned int waligned = (((unsigned int) buf & 0x3) == 0); /* word aligned ? */
+ unsigned int stat, timeout, data, cnt;
+
+ for (; nob >= 1; nob--)
+ {
+ timeout = 0x3FFFFFF;
+
+ while (timeout)
+ {
+ timeout--;
+ stat = REG_MSC_STAT(MSC_CHN(drive));
+
+ if (stat & MSC_STAT_TIME_OUT_READ)
+ return SD_ERROR_TIMEOUT;
+ else if (stat & MSC_STAT_CRC_READ_ERROR)
+ return SD_ERROR_CRC;
+ else if (!(stat & MSC_STAT_DATA_FIFO_EMPTY)
+ || (stat & MSC_STAT_DATA_FIFO_AFULL))
+ /* Ready to read data */
+ break;
+
+ udelay(1);
+ }
+
+ if (!timeout)
+ return SD_ERROR_TIMEOUT;
+
+ /* Read data from RXFIFO. It could be FULL or PARTIAL FULL */
+ DEBUG("Receive Data = %d", wblocklen);
+ cnt = wblocklen;
+ while (cnt)
+ {
+ data = REG_MSC_RXFIFO(MSC_CHN(drive));
+ if (waligned)
+ *wbuf++ = data;
+ else
+ {
+ *buf++ = (unsigned char) (data >> 0);
+ *buf++ = (unsigned char) (data >> 8);
+ *buf++ = (unsigned char) (data >> 16);
+ *buf++ = (unsigned char) (data >> 24);
+ }
+ cnt--;
+ while (cnt
+ && (REG_MSC_STAT(MSC_CHN(drive)) &
+ MSC_STAT_DATA_FIFO_EMPTY));
+ }
+ }
+
+ return SD_NO_ERROR;
+}
+
+static int jz_sd_transmit_data(const int drive, struct sd_request *req)
+{
+ unsigned int nob = req->nob;
+ unsigned int wblocklen = (unsigned int) (req->block_len + 3) >> 2; /* length in word */
+ unsigned char *buf = req->buffer;
+ unsigned int *wbuf = (unsigned int *) buf;
+ unsigned int waligned = (((unsigned int) buf & 0x3) == 0); /* word aligned ? */
+ unsigned int stat, timeout, data, cnt;
+
+ for (; nob >= 1; nob--)
+ {
+ timeout = 0x3FFFFFF;
+
+ while (timeout)
+ {
+ timeout--;
+ stat = REG_MSC_STAT(MSC_CHN(drive));
+
+ if (stat &
+ (MSC_STAT_CRC_WRITE_ERROR |
+ MSC_STAT_CRC_WRITE_ERROR_NOSTS))
+ return SD_ERROR_CRC;
+ else if (!(stat & MSC_STAT_DATA_FIFO_FULL))
+ /* Ready to write data */
+ break;
+
+ udelay(1);
+ }
+
+ if (!timeout)
+ return SD_ERROR_TIMEOUT;
+
+ /* Write data to TXFIFO */
+ cnt = wblocklen;
+ while (cnt)
+ {
+ while (REG_MSC_STAT(MSC_CHN(drive)) & MSC_STAT_DATA_FIFO_FULL);
+
+ if (waligned)
+ REG_MSC_TXFIFO(MSC_CHN(drive)) = *wbuf++;
+ else
+ {
+ data = *buf++;
+ data |= *buf++ << 8;
+ data |= *buf++ << 16;
+ data |= *buf++ << 24;
+ REG_MSC_TXFIFO(MSC_CHN(drive)) = data;
+ }
+
+ cnt--;
+ }
+ }
+
+ return SD_NO_ERROR;
+}
+
+#if SD_DMA_ENABLE
+static void jz_sd_receive_data_dma(const int drive, struct sd_request *req)
+{
+ unsigned int waligned = (((unsigned int)req->buffer & 0x3) == 0); /* word aligned ? */
+ unsigned int size = req->block_len * req->nob;
+
+ if (!waligned)
+ {
+ jz_sd_receive_data(drive, req);
+ return;
+ }
+
+ /* flush dcache */
+ dma_cache_wback_inv((unsigned long) req->buffer, size);
+
+ /* setup dma channel */
+ REG_DMAC_DCCSR(DMA_SD_RX_CHANNEL) = 0;
+ REG_DMAC_DSAR(DMA_SD_RX_CHANNEL) = PHYSADDR(MSC_RXFIFO(MSC_CHN(drive))); /* DMA source addr */
+ REG_DMAC_DTAR(DMA_SD_RX_CHANNEL) = PHYSADDR((unsigned long)req->buffer); /* DMA dest addr */
+ REG_DMAC_DTCR(DMA_SD_RX_CHANNEL) = (size + 3) >> 2; /* DMA transfer count */
+ REG_DMAC_DRSR(DMA_SD_RX_CHANNEL) = (drive == SD_SLOT_1) ? DMAC_DRSR_RS_MSC2IN : DMAC_DRSR_RS_MSC1IN; /* DMA request type */
+
+ REG_DMAC_DCMD(DMA_SD_RX_CHANNEL) =
+ DMAC_DCMD_DAI | DMAC_DCMD_SWDH_32 | DMAC_DCMD_DWDH_32 |
+ DMAC_DCMD_DS_32BIT;
+ REG_DMAC_DCCSR(DMA_SD_RX_CHANNEL) = DMAC_DCCSR_EN | DMAC_DCCSR_NDES;
+
+ /* wait for dma completion */
+ while (REG_DMAC_DTCR(DMA_SD_RX_CHANNEL));
+
+ /* clear status and disable channel */
+ REG_DMAC_DCCSR(DMA_SD_RX_CHANNEL) = 0;
+}
+
+static void jz_sd_transmit_data_dma(const int drive, struct sd_request *req)
+{
+ unsigned int waligned = (((unsigned int)req->buffer & 0x3) == 0); /* word aligned ? */
+ unsigned int size = req->block_len * req->nob;
+
+ if (!waligned)
+ {
+ jz_sd_transmit_data(drive, req);
+ return;
+ }
+
+ /* flush dcache */
+ dma_cache_wback_inv((unsigned long) req->buffer, size);
+
+ /* setup dma channel */
+ REG_DMAC_DCCSR(DMA_SD_RX_CHANNEL) = 0;
+ REG_DMAC_DSAR(DMA_SD_TX_CHANNEL) = PHYSADDR((unsigned long) req->buffer); /* DMA source addr */
+ REG_DMAC_DTAR(DMA_SD_TX_CHANNEL) = PHYSADDR(MSC_TXFIFO(MSC_CHN(drive))); /* DMA dest addr */
+ REG_DMAC_DTCR(DMA_SD_TX_CHANNEL) = (size + 3) >> 2; /* DMA transfer count */
+ REG_DMAC_DRSR(DMA_SD_TX_CHANNEL) = (drive == SD_SLOT_1) ? DMAC_DRSR_RS_MSC2OUT : DMAC_DRSR_RS_MSC1OUT; /* DMA request type */
+
+ REG_DMAC_DCMD(DMA_SD_TX_CHANNEL) =
+ DMAC_DCMD_SAI | DMAC_DCMD_SWDH_32 | DMAC_DCMD_DWDH_32 |
+ DMAC_DCMD_DS_32BIT;
+ REG_DMAC_DCCSR(DMA_SD_TX_CHANNEL) = DMAC_DCCSR_EN | DMAC_DCCSR_NDES;
+
+ /* wait for dma completion */
+ while (REG_DMAC_DTCR(DMA_SD_TX_CHANNEL));
+
+ /* clear status and disable channel */
+ REG_DMAC_DCCSR(DMA_SD_TX_CHANNEL) = 0;
+}
+
+void DMA_CALLBACK(DMA_SD_RX_CHANNEL)(void)
+{
+ if (REG_DMAC_DCCSR(DMA_SD_RX_CHANNEL) & DMAC_DCCSR_AR)
+ {
+ logf("SD RX DMA address error");
+ REG_DMAC_DCCSR(DMA_SD_RX_CHANNEL) &= ~DMAC_DCCSR_AR;
+ }
+
+ if (REG_DMAC_DCCSR(DMA_SD_RX_CHANNEL) & DMAC_DCCSR_HLT)
+ {
+ logf("SD RX DMA halt");
+ REG_DMAC_DCCSR(DMA_SD_RX_CHANNEL) &= ~DMAC_DCCSR_HLT;
+ }
+
+ if (REG_DMAC_DCCSR(DMA_SD_RX_CHANNEL) & DMAC_DCCSR_TT)
+ {
+ REG_DMAC_DCCSR(DMA_SD_RX_CHANNEL) &= ~DMAC_DCCSR_TT;
+ //sd_rx_dma_callback();
+ }
+}
+
+void DMA_CALLBACK(DMA_SD_TX_CHANNEL)(void)
+{
+ if (REG_DMAC_DCCSR(DMA_SD_TX_CHANNEL) & DMAC_DCCSR_AR)
+ {
+ logf("SD TX DMA address error: %x, %x, %x", var1, var2, var3);
+ REG_DMAC_DCCSR(DMA_SD_TX_CHANNEL) &= ~DMAC_DCCSR_AR;
+ }
+
+ if (REG_DMAC_DCCSR(DMA_SD_TX_CHANNEL) & DMAC_DCCSR_HLT)
+ {
+ logf("SD TX DMA halt");
+ REG_DMAC_DCCSR(DMA_SD_TX_CHANNEL) &= ~DMAC_DCCSR_HLT;
+ }
+
+ if (REG_DMAC_DCCSR(DMA_SD_TX_CHANNEL) & DMAC_DCCSR_TT)
+ {
+ REG_DMAC_DCCSR(DMA_SD_TX_CHANNEL) &= ~DMAC_DCCSR_TT;
+ //sd_tx_dma_callback();
+ }
+}
+#endif /* SD_DMA_ENABLE */
+
+static inline unsigned int jz_sd_calc_clkrt(const int drive, unsigned int rate)
+{
+ unsigned int clkrt;
+ unsigned int clk_src = sd2_0[drive] ? SD_CLOCK_HIGH : SD_CLOCK_FAST;
+
+ clkrt = 0;
+ while (rate < clk_src)
+ {
+ clkrt++;
+ clk_src >>= 1;
+ }
+ return clkrt;
+}
+
+static inline void cpm_select_msc_clk(unsigned int rate)
+{
+ unsigned int div = __cpm_get_pllout2() / rate;
+
+ REG_CPM_MSCCDR = div - 1;
+}
+
+/* Set the MMC clock frequency */
+static void jz_sd_set_clock(const int drive, unsigned int rate)
+{
+ int clkrt;
+
+ jz_sd_stop_clock(drive);
+
+ /* select clock source from CPM */
+ cpm_select_msc_clk(rate);
+
+ __cpm_enable_pll_change();
+ clkrt = jz_sd_calc_clkrt(drive, rate);
+ REG_MSC_CLKRT(MSC_CHN(drive)) = clkrt;
+
+ DEBUG("set clock to %u Hz clkrt=%d", rate, clkrt);
+}
+
+/********************************************************************************************************************
+** Name: int jz_sd_exec_cmd()
+** Function: send command to the card, and get a response
+** Input: struct sd_request *req: SD request
+** Output: 0: right >0: error code
+********************************************************************************************************************/
+static int jz_sd_exec_cmd(const int drive, struct sd_request *request)
+{
+ unsigned int cmdat = 0, events = 0;
+ int retval, timeout = 0x3fffff;
+
+ /* Indicate we have no result yet */
+ request->result = SD_NO_RESPONSE;
+
+ if (request->cmd == SD_CIM_RESET) {
+ /* On reset, 1-bit bus width */
+ use_4bit[drive] = 0;
+
+ /* Reset MMC/SD controller */
+ __msc_reset(MSC_CHN(drive));
+
+ /* On reset, drop SD clock down */
+ jz_sd_set_clock(drive, MMC_CLOCK_SLOW);
+
+ /* On reset, stop SD clock */
+ jz_sd_stop_clock(drive);
+ }
+ if (request->cmd == SD_SET_BUS_WIDTH)
+ {
+ if (request->arg == 0x2)
+ {
+ DEBUG("Use 4-bit bus width");
+ use_4bit[drive] = 1;
+ }
+ else
+ {
+ DEBUG("Use 1-bit bus width");
+ use_4bit[drive] = 0;
+ }
+ }
+
+ /* stop clock */
+ jz_sd_stop_clock(drive);
+
+ /* mask all interrupts */
+ //REG_MSC_IMASK(MSC_CHN(drive)) = 0xffff;
+ /* clear status */
+ REG_MSC_IREG(MSC_CHN(drive)) = 0xffff;
+ /*open interrupt */
+ REG_MSC_IMASK(MSC_CHN(drive)) = (~7);
+ /* use 4-bit bus width when possible */
+ if (use_4bit[drive])
+ cmdat |= MSC_CMDAT_BUS_WIDTH_4BIT;
+
+ /* Set command type and events */
+ switch (request->cmd)
+ {
+ /* SD core extra command */
+ case SD_CIM_RESET:
+ cmdat |= MSC_CMDAT_INIT; /* Initialization sequence sent prior to command */
+ break;
+ /* bc - broadcast - no response */
+ case SD_GO_IDLE_STATE:
+ case SD_SET_DSR:
+ break;
+
+ /* bcr - broadcast with response */
+ case SD_APP_OP_COND:
+ case SD_ALL_SEND_CID:
+ case SD_GO_IRQ_STATE:
+ break;
+
+ /* adtc - addressed with data transfer */
+ case SD_READ_DAT_UNTIL_STOP:
+ case SD_READ_SINGLE_BLOCK:
+ case SD_READ_MULTIPLE_BLOCK:
+ case SD_SEND_SCR:
+#if SD_DMA_ENABLE
+ cmdat |=
+ MSC_CMDAT_DATA_EN | MSC_CMDAT_READ | MSC_CMDAT_DMA_EN;
+#else
+ cmdat |= MSC_CMDAT_DATA_EN | MSC_CMDAT_READ;
+#endif
+ events = SD_EVENT_RX_DATA_DONE;
+ break;
+
+ case 6:
+ if (num_6[drive] < 2)
+ {
+#if SD_DMA_ENABLE
+ cmdat |=
+ MSC_CMDAT_DATA_EN | MSC_CMDAT_READ |
+ MSC_CMDAT_DMA_EN;
+#else
+ cmdat |= MSC_CMDAT_DATA_EN | MSC_CMDAT_READ;
+#endif
+ events = SD_EVENT_RX_DATA_DONE;
+ }
+ break;
+
+ case SD_WRITE_DAT_UNTIL_STOP:
+ case SD_WRITE_BLOCK:
+ case SD_WRITE_MULTIPLE_BLOCK:
+ case SD_PROGRAM_CID:
+ case SD_PROGRAM_CSD:
+ case SD_LOCK_UNLOCK:
+#if SD_DMA_ENABLE
+ cmdat |=
+ MSC_CMDAT_DATA_EN | MSC_CMDAT_WRITE | MSC_CMDAT_DMA_EN;
+#else
+ cmdat |= MSC_CMDAT_DATA_EN | MSC_CMDAT_WRITE;
+#endif
+ events = SD_EVENT_TX_DATA_DONE | SD_EVENT_PROG_DONE;
+ break;
+
+ case SD_STOP_TRANSMISSION:
+ events = SD_EVENT_PROG_DONE;
+ break;
+
+ /* ac - no data transfer */
+ default:
+ break;
+ }
+
+ /* Set response type */
+ switch (request->rtype)
+ {
+ case RESPONSE_NONE:
+ break;
+ case RESPONSE_R1B:
+ cmdat |= MSC_CMDAT_BUSY;
+ /* FALLTHRU */
+ case RESPONSE_R1:
+ case RESPONSE_R7:
+ cmdat |= MSC_CMDAT_RESPONSE_R1;
+ break;
+ case RESPONSE_R2_CID:
+ case RESPONSE_R2_CSD:
+ cmdat |= MSC_CMDAT_RESPONSE_R2;
+ break;
+ case RESPONSE_R3:
+ cmdat |= MSC_CMDAT_RESPONSE_R3;
+ break;
+ case RESPONSE_R4:
+ cmdat |= MSC_CMDAT_RESPONSE_R4;
+ break;
+ case RESPONSE_R5:
+ cmdat |= MSC_CMDAT_RESPONSE_R5;
+ break;
+ case RESPONSE_R6:
+ cmdat |= MSC_CMDAT_RESPONSE_R6;
+ break;
+ default:
+ break;
+ }
+
+ /* Set command index */
+ if (request->cmd == SD_CIM_RESET)
+ REG_MSC_CMD(MSC_CHN(drive)) = SD_GO_IDLE_STATE;
+ else
+ REG_MSC_CMD(MSC_CHN(drive)) = request->cmd;
+
+ /* Set argument */
+ REG_MSC_ARG(MSC_CHN(drive)) = request->arg;
+
+ /* Set block length and nob */
+ if (request->cmd == SD_SEND_SCR)
+ { /* get SCR from DataFIFO */
+ REG_MSC_BLKLEN(MSC_CHN(drive)) = 8;
+ REG_MSC_NOB(MSC_CHN(drive)) = 1;
+ }
+ else
+ {
+ REG_MSC_BLKLEN(MSC_CHN(drive)) = request->block_len;
+ REG_MSC_NOB(MSC_CHN(drive)) = request->nob;
+ }
+
+ /* Set command */
+ REG_MSC_CMDAT(MSC_CHN(drive)) = cmdat;
+
+ DEBUG("Send cmd %d cmdat: %x arg: %x resp %d", request->cmd,
+ cmdat, request->arg, request->rtype);
+
+ /* Start SD clock and send command to card */
+ jz_sd_start_clock(drive);
+
+ /* Wait for command completion */
+ //__intc_unmask_irq(IRQ_MSC);
+ //semaphore_wait(&sd_wakeup, 100);
+ while (timeout-- && !(REG_MSC_STAT(MSC_CHN(drive)) & MSC_STAT_END_CMD_RES));
+
+
+ if (timeout == 0)
+ return SD_ERROR_TIMEOUT;
+
+ REG_MSC_IREG(MSC_CHN(drive)) = MSC_IREG_END_CMD_RES; /* clear flag */
+
+ /* Check for status */
+ retval = jz_sd_check_status(drive, request);
+ if (retval)
+ return retval;
+
+ /* Complete command with no response */
+ if (request->rtype == RESPONSE_NONE)
+ return SD_NO_ERROR;
+
+ /* Get response */
+ jz_sd_get_response(drive, request);
+
+ /* Start data operation */
+ if (events & (SD_EVENT_RX_DATA_DONE | SD_EVENT_TX_DATA_DONE))
+ {
+ if (events & SD_EVENT_RX_DATA_DONE)
+ {
+ if (request->cmd == SD_SEND_SCR)
+ {
+ /* SD card returns SCR register as data.
+ SD core expect it in the response buffer,
+ after normal response. */
+ request->buffer =
+ (unsigned char *) ((unsigned int) request->response + 5);
+ }
+#if SD_DMA_ENABLE
+ jz_sd_receive_data_dma(drive, request);
+#else
+ jz_sd_receive_data(drive, request);
+#endif
+ }
+
+ if (events & SD_EVENT_TX_DATA_DONE)
+ {
+#if SD_DMA_ENABLE
+ jz_sd_transmit_data_dma(drive, request);
+#else
+ jz_sd_transmit_data(drive, request);
+#endif
+ }
+ //__intc_unmask_irq(IRQ_MSC);
+ //semaphore_wait(&sd_wakeup, 100);
+ /* Wait for Data Done */
+ while (!(REG_MSC_IREG(MSC_CHN(drive)) & MSC_IREG_DATA_TRAN_DONE));
+ REG_MSC_IREG(MSC_CHN(drive)) = MSC_IREG_DATA_TRAN_DONE; /* clear status */
+ }
+
+ /* Wait for Prog Done event */
+ if (events & SD_EVENT_PROG_DONE)
+ {
+ //__intc_unmask_irq(IRQ_MSC);
+ //semaphore_wait(&sd_wakeup, 100);
+ while (!(REG_MSC_IREG(MSC_CHN(drive)) & MSC_IREG_PRG_DONE));
+ REG_MSC_IREG(MSC_CHN(drive)) = MSC_IREG_PRG_DONE; /* clear status */
+ }
+
+ /* Command completed */
+
+ return SD_NO_ERROR; /* return successfully */
+}
+
+/*******************************************************************************************************************
+** Name: int sd_chkcard()
+** Function: check whether card is insert entirely
+** Input: NULL
+** Output: 1: insert entirely 0: not insert entirely
+********************************************************************************************************************/
+static int jz_sd_chkcard(const int drive)
+{
+ return (__gpio_get_pin((drive == SD_SLOT_1) ? PIN_SD1_CD : PIN_SD2_CD) == 0 ? 1 : 0);
+}
+
+/* MSC interrupt handler */
+void MSC(void)
+{
+ //semaphore_release(&sd_wakeup);
+ logf("MSC interrupt");
+}
+
+#ifdef HAVE_HOTSWAP
+static void sd_gpio_setup_irq(const int drive, bool inserted)
+{
+ int pin = (drive == SD_SLOT_1) ? PIN_SD1_CD : PIN_SD2_CD;
+ int irq = (drive == SD_SLOT_1) ? IRQ_SD1_CD : IRQ_SD2_CD;
+ if(inserted)
+ __gpio_as_irq_rise_edge(pin);
+ else
+ __gpio_as_irq_fall_edge(pin);
+ system_enable_irq(irq);
+}
+#endif
+
+/*******************************************************************************************************************
+** Name: void sd_hardware_init()
+** Function: initialize the hardware condiction that access sd card
+** Input: NULL
+** Output: NULL
+********************************************************************************************************************/
+static void jz_sd_hardware_init(const int drive)
+{
+ if (drive == SD_SLOT_1)
+ __cpm_start_msc2(); /* enable mmc2 clock */
+ else
+ __cpm_start_msc1(); /* enable mmc1 clock */
+#ifdef HAVE_HOTSWAP
+ sd_gpio_setup_irq(drive, jz_sd_chkcard(drive));
+#endif
+ __msc_reset(MSC_CHN(drive)); /* reset mmc/sd controller */
+ SD_IRQ_MASK(MSC_CHN(drive)); /* mask all IRQs */
+ jz_sd_stop_clock(drive); /* stop SD clock */
+}
+
+static int sd_send_cmd(const int drive, struct sd_request *request, int cmd, unsigned int arg,
+ unsigned short nob, unsigned short block_len,
+ enum sd_rsp_t rtype, unsigned char* buffer)
+{
+ request->cmd = cmd;
+ request->arg = arg;
+ request->rtype = rtype;
+ request->nob = nob;
+ request->block_len = block_len;
+ request->buffer = buffer;
+ request->cnt = nob * block_len;
+
+ return jz_sd_exec_cmd(drive, request);
+}
+
+static void sd_simple_cmd(const int drive, struct sd_request *request, int cmd, unsigned int arg,
+ enum sd_rsp_t rtype)
+{
+ sd_send_cmd(drive, request, cmd, arg, 0, 0, rtype, NULL);
+}
+
+#define SD_INIT_DOING 0
+#define SD_INIT_PASSED 1
+#define SD_INIT_FAILED 2
+static int sd_init_card_state(const int drive, struct sd_request *request)
+{
+ struct sd_response_r1 r1;
+ struct sd_response_r3 r3;
+ int retval, i, ocr = 0x40300000, limit_41 = 0;
+
+ switch (request->cmd)
+ {
+ case SD_GO_IDLE_STATE: /* No response to parse */
+ sd_simple_cmd(drive, request, SD_SEND_IF_COND, 0x1AA, RESPONSE_R1);
+ break;
+
+ case SD_SEND_IF_COND:
+ retval = sd_unpack_r1(request, &r1);
+ sd_simple_cmd(drive, request, SD_APP_CMD, 0, RESPONSE_R1);
+ break;
+
+ case SD_APP_CMD:
+ retval = sd_unpack_r1(request, &r1);
+ if (retval & (limit_41 < 100))
+ {
+ DEBUG("sd_init_card_state: unable to SD_APP_CMD error=%d",
+ retval);
+ limit_41++;
+ sd_simple_cmd(drive, request, SD_APP_OP_COND, ocr, RESPONSE_R3);
+ }
+ else if (limit_41 < 100)
+ {
+ limit_41++;
+ sd_simple_cmd(drive, request, SD_APP_OP_COND, ocr, RESPONSE_R3);
+ }
+ else
+ /* reset the card to idle*/
+ sd_simple_cmd(drive, request, SD_GO_IDLE_STATE, 0, RESPONSE_NONE);
+ break;
+
+ case SD_APP_OP_COND:
+ retval = sd_unpack_r3(request, &r3);
+ if (retval)
+ break;
+
+ DEBUG("sd_init_card_state: read ocr value = 0x%08x", r3.ocr);
+ card[drive].ocr = r3.ocr;
+
+ if(!(r3.ocr & SD_CARD_BUSY || ocr == 0))
+ {
+ sleep(HZ / 100);
+ sd_simple_cmd(drive, request, SD_APP_CMD, 0, RESPONSE_R1);
+ }
+ else
+ {
+ /* Set the data bus width to 4 bits */
+ use_4bit[drive] = 1;
+ sd_simple_cmd(drive, request, SD_ALL_SEND_CID, 0, RESPONSE_R2_CID);
+ }
+ break;
+
+ case SD_ALL_SEND_CID:
+ for(i=0; i<4; i++)
+ card[drive].cid[i] = ((request->response[1+i*4]<<24) | (request->response[2+i*4]<<16) |
+ (request->response[3+i*4]<< 8) | request->response[4+i*4]);
+
+ logf("CID: %08lx%08lx%08lx%08lx", card[drive].cid[0], card[drive].cid[1], card[drive].cid[2], card[drive].cid[3]);
+ sd_simple_cmd(drive, request, SD_SEND_RELATIVE_ADDR, 0, RESPONSE_R6);
+ break;
+ case SD_SEND_RELATIVE_ADDR:
+ retval = sd_unpack_r6(request, &r1, &card[drive].rca);
+ card[drive].rca = card[drive].rca << 16;
+ DEBUG("sd_init_card_state: Get RCA from SD: 0x%04lx Status: %x", card[drive].rca, r1.status);
+ if (retval)
+ {
+ DEBUG("sd_init_card_state: unable to SET_RELATIVE_ADDR error=%d",
+ retval);
+ return SD_INIT_FAILED;
+ }
+
+ sd_simple_cmd(drive, request, SD_SEND_CSD, card[drive].rca, RESPONSE_R2_CSD);
+ break;
+
+ case SD_SEND_CSD:
+ for(i=0; i<4; i++)
+ card[drive].csd[i] = ((request->response[1+i*4]<<24) | (request->response[2+i*4]<<16) |
+ (request->response[3+i*4]<< 8) | request->response[4+i*4]);
+
+ sd_parse_csd(&card[drive]);
+ sd2_0[drive] = (card_extract_bits(card[drive].csd, 127, 2) == 1);
+
+ logf("CSD: %08lx%08lx%08lx%08lx", card[drive].csd[0], card[drive].csd[1], card[drive].csd[2], card[drive].csd[3]);
+ DEBUG("SD card is ready");
+ jz_sd_set_clock(drive, SD_CLOCK_FAST);
+ return SD_INIT_PASSED;
+
+ default:
+ DEBUG("sd_init_card_state: error! Illegal last cmd %d", request->cmd);
+ return SD_INIT_FAILED;
+ }
+
+ return SD_INIT_DOING;
+}
+
+static int sd_switch(const int drive, struct sd_request *request, int mode, int group,
+ unsigned char value, unsigned char * resp)
+{
+ unsigned int arg;
+
+ mode = !!mode;
+ value &= 0xF;
+ arg = (mode << 31 | 0x00FFFFFF);
+ arg &= ~(0xF << (group * 4));
+ arg |= value << (group * 4);
+ sd_send_cmd(drive, request, 6, arg, 1, 64, RESPONSE_R1, resp);
+
+ return 0;
+}
+
+/*
+ * Fetches and decodes switch information
+ */
+static int sd_read_switch(const int drive, struct sd_request *request)
+{
+ unsigned int status[64 / 4];
+
+ memset((unsigned char *)status, 0, 64);
+ sd_switch(drive, request, 0, 0, 1, (unsigned char*) status);
+
+ if (((unsigned char *)status)[13] & 0x02)
+ return 0;
+ else
+ return 1;
+}
+
+/*
+ * Test if the card supports high-speed mode and, if so, switch to it.
+ */
+static int sd_switch_hs(const int drive, struct sd_request *request)
+{
+ unsigned int status[64 / 4];
+
+ sd_switch(drive, request, 1, 0, 1, (unsigned char*) status);
+ return 0;
+}
+
+static int sd_select_card(const int drive)
+{
+ struct sd_request request;
+ struct sd_response_r1 r1;
+ int retval;
+
+ sd_simple_cmd(drive, &request, SD_SELECT_CARD, card[drive].rca,
+ RESPONSE_R1B);
+ retval = sd_unpack_r1(&request, &r1);
+ if (retval)
+ return retval;
+
+ if (sd2_0[drive])
+ {
+ retval = sd_read_switch(drive, &request);
+ if (!retval)
+ {
+ sd_switch_hs(drive, &request);
+ jz_sd_set_clock(drive, SD_CLOCK_HIGH);
+ }
+ }
+ num_6[drive] = 3;
+ sd_simple_cmd(drive, &request, SD_APP_CMD, card[drive].rca,
+ RESPONSE_R1);
+ retval = sd_unpack_r1(&request, &r1);
+ if (retval)
+ return retval;
+ sd_simple_cmd(drive, &request, SD_SET_BUS_WIDTH, 2, RESPONSE_R1);
+ retval = sd_unpack_r1(&request, &r1);
+ if (retval)
+ return retval;
+
+ card[drive].initialized = 1;
+
+ return 0;
+}
+
+static int sd_init_device(const int drive)
+{
+ int retval = 0;
+ struct sd_request init_req;
+ register int timeout = 1000;
+
+ mutex_lock(&sd_mtx);
+
+ /* Initialise card data as blank */
+ memset(&card[drive], 0, sizeof(tCardInfo));
+
+ sd2_0[drive] = 0;
+ num_6[drive] = 0;
+ use_4bit[drive] = 0;
+
+ /* reset mmc/sd controller */
+ jz_sd_hardware_init(drive);
+
+ sd_simple_cmd(drive, &init_req, SD_CIM_RESET, 0, RESPONSE_NONE);
+ sd_simple_cmd(drive, &init_req, SD_GO_IDLE_STATE, 0, RESPONSE_NONE);
+
+ sleep(HZ/2); /* Give the card/controller some rest */
+
+ while(timeout-- && ((retval = sd_init_card_state(drive, &init_req)) == SD_INIT_DOING));
+ retval = (retval == SD_INIT_PASSED ? sd_select_card(drive) : -1);
+
+ if (drive == SD_SLOT_1)
+ __cpm_stop_msc2(); /* disable SD1 clock */
+ else
+ __cpm_stop_msc1(); /* disable SD2 clock */
+
+ mutex_unlock(&sd_mtx);
+
+ return retval;
+}
+
+int sd_init(void)
+{
+ static bool inited = false;
+
+ sd_init_gpio(); /* init GPIO */
+
+#if SD_DMA_ENABLE
+ __dmac_channel_enable_clk(DMA_SD_RX_CHANNEL);
+ __dmac_channel_enable_clk(DMA_SD_TX_CHANNEL);
+#endif
+
+ if(!inited)
+ {
+ semaphore_init(&sd_wakeup, 1, 0);
+ mutex_init(&sd_mtx);
+ inited = true;
+ }
+
+ for (int drive = 0; drive < NUM_DRIVES; drive++)
+ sd_init_device(drive);
+
+ return 0;
+}
+
+static inline bool card_detect_target(const int drive)
+{
+ return (jz_sd_chkcard(drive) == 1);
+}
+
+tCardInfo* card_get_info_target(const int drive)
+{
+ return &card[drive];
+}
+
+static inline void sd_start_transfer(const int drive)
+{
+ mutex_lock(&sd_mtx);
+ if (drive == SD_SLOT_1)
+ __cpm_start_msc2();
+ else
+ __cpm_start_msc1();
+ led(true);
+}
+
+static inline void sd_stop_transfer(const int drive)
+{
+ led(false);
+ if (drive == SD_SLOT_1)
+ __cpm_stop_msc2();
+ else
+ __cpm_stop_msc1();
+ mutex_unlock(&sd_mtx);
+}
+
+int sd_read_sectors(const int drive, unsigned long start, int count, void* buf)
+{
+ sd_start_transfer(drive);
+
+ struct sd_request request;
+ struct sd_response_r1 r1;
+ int retval = -1;
+
+ if (!card_detect_target(drive) || count == 0 || start > card[drive].numblocks)
+ goto err;
+
+ if(card[drive].initialized == 0 && !sd_init_device(drive))
+ goto err;
+
+ sd_simple_cmd(drive, &request, SD_SEND_STATUS, card[drive].rca, RESPONSE_R1);
+ retval = sd_unpack_r1(&request, &r1);
+ if (retval && (retval != SD_ERROR_STATE_MISMATCH))
+ goto err;
+
+ sd_simple_cmd(drive, &request, SD_SET_BLOCKLEN, SD_BLOCK_SIZE, RESPONSE_R1);
+ if ((retval = sd_unpack_r1(&request, &r1)))
+ goto err;
+
+ if (sd2_0[drive])
+ {
+ sd_send_cmd(drive, &request, SD_READ_MULTIPLE_BLOCK, start,
+ count, SD_BLOCK_SIZE, RESPONSE_R1, buf);
+ if ((retval = sd_unpack_r1(&request, &r1)))
+ goto err;
+ }
+ else
+ {
+ sd_send_cmd(drive, &request, SD_READ_MULTIPLE_BLOCK,
+ start * SD_BLOCK_SIZE, count,
+ SD_BLOCK_SIZE, RESPONSE_R1, buf);
+ if ((retval = sd_unpack_r1(&request, &r1)))
+ goto err;
+ }
+
+ last_disk_activity = current_tick;
+
+ sd_simple_cmd(drive, &request, SD_STOP_TRANSMISSION, 0, RESPONSE_R1B);
+ if ((retval = sd_unpack_r1(&request, &r1)))
+ goto err;
+
+err:
+ sd_stop_transfer(drive);
+
+ return retval;
+}
+
+int sd_write_sectors(const int drive, unsigned long start, int count, const void* buf)
+{
+ sd_start_transfer(drive);
+
+ struct sd_request request;
+ struct sd_response_r1 r1;
+ int retval = -1;
+
+ if (!card_detect_target(drive) || count == 0 || start > card[drive].numblocks)
+ goto err;
+
+ if(card[drive].initialized == 0 && !sd_init_device(drive))
+ goto err;
+
+ sd_simple_cmd(drive, &request, SD_SEND_STATUS, card[drive].rca, RESPONSE_R1);
+ retval = sd_unpack_r1(&request, &r1);
+ if (retval && (retval != SD_ERROR_STATE_MISMATCH))
+ goto err;
+
+ sd_simple_cmd(drive, &request, SD_SET_BLOCKLEN, SD_BLOCK_SIZE, RESPONSE_R1);
+ if ((retval = sd_unpack_r1(&request, &r1)))
+ goto err;
+
+ if (sd2_0[drive])
+ {
+ sd_send_cmd(drive, &request, SD_WRITE_MULTIPLE_BLOCK, start,
+ count, SD_BLOCK_SIZE, RESPONSE_R1,
+ (void*)buf);
+ if ((retval = sd_unpack_r1(&request, &r1)))
+ goto err;
+ }
+ else
+ {
+ sd_send_cmd(drive, &request, SD_WRITE_MULTIPLE_BLOCK,
+ start * SD_BLOCK_SIZE, count,
+ SD_BLOCK_SIZE, RESPONSE_R1, (void*)buf);
+ if ((retval = sd_unpack_r1(&request, &r1)))
+ goto err;
+ }
+
+ last_disk_activity = current_tick;
+
+ sd_simple_cmd(drive, &request, SD_STOP_TRANSMISSION, 0, RESPONSE_R1B);
+ if ((retval = sd_unpack_r1(&request, &r1)))
+ goto err;
+
+err:
+ sd_stop_transfer(drive);
+
+ return retval;
+}
+
+long sd_last_disk_activity(void)
+{
+ return last_disk_activity;
+}
+
+int sd_spinup_time(void)
+{
+ return 0;
+}
+
+void sd_enable(bool on)
+{
+ (void)on;
+}
+
+bool sd_disk_is_active(void)
+{
+ return false;
+}
+
+int sd_soft_reset(void)
+{
+ return 0;
+}
+
+#ifdef HAVE_HOTSWAP
+bool sd_removable(const int drive)
+{
+ (void)drive;
+ return true;
+}
+
+static int sd1_oneshot_callback(struct timeout *tmo)
+{
+ int state = card_detect_target(SD_SLOT_1);
+
+ /* This is called only if the state was stable for 300ms - check state
+ * and post appropriate event. */
+ queue_broadcast(state ? SYS_HOTSWAP_INSERTED : SYS_HOTSWAP_EXTRACTED,
+ 0);
+
+ sd_gpio_setup_irq(SD_SLOT_1, state);
+
+ return 0;
+ (void)tmo;
+}
+
+static int sd2_oneshot_callback(struct timeout *tmo)
+{
+ int state = card_detect_target(SD_SLOT_2);
+
+ /* This is called only if the state was stable for 300ms - check state
+ * and post appropriate event. */
+ queue_broadcast(state ? SYS_HOTSWAP_INSERTED : SYS_HOTSWAP_EXTRACTED,
+ 1);
+
+ sd_gpio_setup_irq(SD_SLOT_2, state);
+
+ return 0;
+ (void)tmo;
+}
+
+/* called on insertion/removal interrupt */
+void GPIO_SD1_CD(void)
+{
+ static struct timeout sd1_oneshot;
+ timeout_register(&sd1_oneshot, sd1_oneshot_callback, (3*HZ/10), 0);
+}
+
+void GPIO_SD2_CD(void)
+{
+ static struct timeout sd2_oneshot;
+ timeout_register(&sd2_oneshot, sd2_oneshot_callback, (3*HZ/10), 0);
+}
+#endif
+
+bool sd_present(const int drive)
+{
+ return card_detect_target(drive);
+}
+
+#ifdef CONFIG_STORAGE_MULTI
+int sd_num_drives(int first_drive)
+{
+ return NUM_DRIVES;
+}
+#endif /* CONFIG_STORAGE_MULTI */
+
+int sd_event(long id, intptr_t data)
+{
+ int rc = 0;
+
+ switch (id)
+ {
+#ifdef HAVE_HOTSWAP
+ case SYS_HOTSWAP_INSERTED:
+ case SYS_HOTSWAP_EXTRACTED:
+ /* Force card init for new card, re-init for re-inserted one or
+ * clear if the last attempt to init failed with an error. */
+ mutex_lock(&sd_mtx); /* lock-out card activity */
+ card[data].initialized = 0;
+ mutex_unlock(&sd_mtx);
+ break;
+#endif /* HAVE_HOTSWAP */
+ default:
+ rc = storage_event_default_handler(id, data, last_disk_activity,
+ STORAGE_SD);
+ break;
+ }
+
+ return rc;
+}
diff --git a/firmware/target/mips/ingenic_jz47xx/backlight-target.h b/firmware/target/mips/ingenic_jz47xx/backlight-target.h
index 0dc7ce387a..1b61d13e4c 100644
--- a/firmware/target/mips/ingenic_jz47xx/backlight-target.h
+++ b/firmware/target/mips/ingenic_jz47xx/backlight-target.h
@@ -26,6 +26,8 @@
bool backlight_hw_init(void);
void backlight_hw_on(void);
void backlight_hw_off(void);
+#ifdef HAVE_BACKLIGHT_BRIGHTNESS
void backlight_hw_brightness(int brightness);
+#endif /* HAVE_BACKLIGHT_BRIGHTNESS */
#endif /* BACKLIGHT_TARGET_H */
diff --git a/firmware/target/mips/ingenic_jz47xx/codec-jz4760.c b/firmware/target/mips/ingenic_jz47xx/codec-jz4760.c
new file mode 100644
index 0000000000..f25dc70eb4
--- /dev/null
+++ b/firmware/target/mips/ingenic_jz47xx/codec-jz4760.c
@@ -0,0 +1,293 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ *
+ * Copyright (C) 2016 by Roman Stolyarov
+ *
+ * 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 "config.h"
+#include "audio.h"
+#include "sound.h"
+#include "cpu.h"
+#include "system.h"
+#include "pcm_sw_volume.h"
+#include "cs4398.h"
+#include "kernel.h"
+
+#define PIN_CS_RST (32*1+10)
+#define PIN_CODEC_PWRON (32*1+13)
+#define PIN_AP_MUTE (32*1+14)
+#define PIN_JD_CON (32*1+16)
+
+static void pop_ctrl(const int val)
+{
+ if(val)
+ __gpio_clear_pin(PIN_JD_CON);
+ else
+ __gpio_set_pin(PIN_JD_CON);
+}
+
+static void amp_enable(const int val)
+{
+ if(val)
+ __gpio_set_pin(PIN_CODEC_PWRON);
+ else
+ __gpio_clear_pin(PIN_CODEC_PWRON);
+}
+
+static void dac_enable(const int val)
+{
+ if(val)
+ __gpio_set_pin(PIN_CS_RST);
+ else
+ __gpio_clear_pin(PIN_CS_RST);
+}
+
+static void ap_mute(bool mute)
+{
+ if(mute)
+ __gpio_clear_pin(PIN_AP_MUTE);
+ else
+ __gpio_set_pin(PIN_AP_MUTE);
+}
+
+static void audiohw_mute(bool mute)
+{
+ if(mute)
+ cs4398_write_reg(CS4398_REG_MUTE, cs4398_read_reg(CS4398_REG_MUTE) | CS4398_MUTE_A | CS4398_MUTE_B);
+ else
+ cs4398_write_reg(CS4398_REG_MUTE, cs4398_read_reg(CS4398_REG_MUTE) & ~(CS4398_MUTE_A | CS4398_MUTE_B));
+}
+
+void audiohw_preinit(void)
+{
+ cs4398_write_reg(CS4398_REG_MISC, CS4398_CPEN | CS4398_PDN);
+ cs4398_write_reg(CS4398_REG_MODECTL, CS4398_FM_SINGLE | CS4398_DEM_NONE | CS4398_DIF_LJUST);
+ cs4398_write_reg(CS4398_REG_VOLMIX, CS4398_ATAPI_A_L | CS4398_ATAPI_B_R);
+ cs4398_write_reg(CS4398_REG_MUTE, CS4398_MUTEP_LOW);
+ cs4398_write_reg(CS4398_REG_VOL_A, 0xff);
+ cs4398_write_reg(CS4398_REG_VOL_B, 0xff);
+ cs4398_write_reg(CS4398_REG_RAMPFILT, CS4398_ZERO_CROSS | CS4398_SOFT_RAMP);
+ cs4398_write_reg(CS4398_REG_MISC, CS4398_CPEN);
+}
+
+void audiohw_init(void)
+{
+ __gpio_as_func1(3*32+12); // BCK
+ __gpio_as_func0(3*32+13); // LRCK
+ __gpio_as_func2(4*32+5); // MCLK
+ __gpio_as_func0(4*32+7); // DO
+
+ pop_ctrl(0);
+ ap_mute(true);
+ amp_enable(0);
+ dac_enable(0);
+
+ __gpio_as_output(PIN_JD_CON);
+ __gpio_as_output(PIN_AP_MUTE);
+ __gpio_as_output(PIN_CODEC_PWRON);
+ __gpio_as_output(PIN_CS_RST);
+
+ mdelay(100);
+ amp_enable(1);
+
+ /* set AIC clk PLL1 */
+ __cpm_select_i2sclk_pll();
+ __cpm_select_i2sclk_pll1();
+
+ __cpm_enable_pll_change();
+ __cpm_set_i2sdiv(43-1);
+
+ __cpm_start_aic();
+
+ /* Init AIC */
+ __i2s_enable_sclk();
+ __i2s_external_codec();
+ __i2s_select_msbjustified();
+ __i2s_as_master();
+ __i2s_enable_transmit_dma();
+ __i2s_set_transmit_trigger(24);
+ __i2s_set_oss_sample_size(16);
+ __i2s_enable();
+
+ /* Init DAC */
+ dac_enable(1);
+ udelay(1);
+ audiohw_preinit();
+}
+
+static int vol_tenthdb2hw(const int tdb)
+{
+ if (tdb < CS4398_VOLUME_MIN) {
+ return 0xff;
+ } else if (tdb > CS4398_VOLUME_MAX) {
+ return 0x00;
+ } else {
+ return (-tdb/5);
+ }
+}
+
+void audiohw_set_volume(int vol_l, int vol_r)
+{
+ cs4398_write_reg(CS4398_REG_VOL_A, vol_tenthdb2hw(vol_l));
+ cs4398_write_reg(CS4398_REG_VOL_B, vol_tenthdb2hw(vol_r));
+}
+
+void audiohw_set_lineout_volume(int vol_l, int vol_r)
+{
+#if 0 /* unused */
+ cs4398_write_reg(CS4398_REG_VOL_A, vol_tenthdb2hw(vol_l));
+ cs4398_write_reg(CS4398_REG_VOL_B, vol_tenthdb2hw(vol_r));
+#else
+ (void)vol_l;
+ (void)vol_r;
+#endif
+}
+
+void audiohw_set_filter_roll_off(int value)
+{
+ /* 0 = fast (sharp);
+ 1 = slow */
+ if (value == 0) {
+ cs4398_write_reg(CS4398_REG_RAMPFILT, cs4398_read_reg(CS4398_REG_RAMPFILT) & ~CS4398_FILT_SEL);
+ } else {
+ cs4398_write_reg(CS4398_REG_RAMPFILT, cs4398_read_reg(CS4398_REG_RAMPFILT) | CS4398_FILT_SEL);
+ }
+}
+
+void pll1_init(unsigned int freq);
+void audiohw_set_frequency(int fsel)
+{
+ unsigned int pll1_speed;
+ unsigned char mclk_div, bclk_div, func_mode;
+
+ switch(fsel)
+ {
+ case HW_FREQ_8:
+ pll1_speed = 426000000;
+ mclk_div = 52;
+ bclk_div = 16;
+ func_mode = 0;
+ break;
+ case HW_FREQ_11:
+ pll1_speed = 508000000;
+ mclk_div = 45;
+ bclk_div = 16;
+ func_mode = 0;
+ break;
+ case HW_FREQ_12:
+ pll1_speed = 516000000;
+ mclk_div = 42;
+ bclk_div = 16;
+ func_mode = 0;
+ break;
+ case HW_FREQ_16:
+ pll1_speed = 426000000;
+ mclk_div = 52;
+ bclk_div = 8;
+ func_mode = 0;
+ break;
+ case HW_FREQ_22:
+ pll1_speed = 508000000;
+ mclk_div = 45;
+ bclk_div = 8;
+ func_mode = 0;
+ break;
+ case HW_FREQ_24:
+ pll1_speed = 516000000;
+ mclk_div = 42;
+ bclk_div = 8;
+ func_mode = 0;
+ break;
+ case HW_FREQ_32:
+ pll1_speed = 426000000;
+ mclk_div = 52;
+ bclk_div = 4;
+ func_mode = 0;
+ break;
+ case HW_FREQ_44:
+ pll1_speed = 508000000;
+ mclk_div = 45;
+ bclk_div = 4;
+ func_mode = 0;
+ break;
+ case HW_FREQ_48:
+ pll1_speed = 516000000;
+ mclk_div = 42;
+ bclk_div = 4;
+ func_mode = 0;
+ break;
+ case HW_FREQ_64:
+ pll1_speed = 426000000;
+ mclk_div = 52;
+ bclk_div = 2;
+ func_mode = 1;
+ break;
+ case HW_FREQ_88:
+ pll1_speed = 508000000;
+ mclk_div = 45;
+ bclk_div = 2;
+ func_mode = 1;
+ break;
+ case HW_FREQ_96:
+ pll1_speed = 516000000;
+ mclk_div = 42;
+ bclk_div = 2;
+ func_mode = 1;
+ break;
+ default:
+ return;
+ }
+
+ __i2s_stop_bitclk();
+
+ /* 0 = Single-Speed Mode (<50KHz);
+ 1 = Double-Speed Mode (50-100KHz);
+ 2 = Quad-Speed Mode; (100-200KHz) */
+ cs4398_write_reg(CS4398_REG_MODECTL, (cs4398_read_reg(CS4398_REG_MODECTL) & ~CS4398_FM_MASK) | func_mode);
+ if (func_mode == 2)
+ cs4398_write_reg(CS4398_REG_MISC, cs4398_read_reg(CS4398_REG_MISC) | CS4398_MCLKDIV2);
+ else
+ cs4398_write_reg(CS4398_REG_MISC, cs4398_read_reg(CS4398_REG_MISC) & ~CS4398_MCLKDIV2);
+
+ pll1_init(pll1_speed);
+ __cpm_enable_pll_change();
+ __cpm_set_i2sdiv(mclk_div-1);
+ __i2s_set_i2sdiv(bclk_div-1);
+ __i2s_start_bitclk();
+}
+
+void audiohw_postinit(void)
+{
+ sleep(HZ);
+ audiohw_mute(false);
+ ap_mute(false);
+ pop_ctrl(1);
+}
+
+void audiohw_close(void)
+{
+ pop_ctrl(0);
+ sleep(HZ/10);
+ ap_mute(true);
+ audiohw_mute(true);
+ amp_enable(0);
+ dac_enable(0);
+ __i2s_disable();
+ __cpm_stop_aic();
+ sleep(HZ);
+ pop_ctrl(1);
+}
diff --git a/firmware/target/mips/ingenic_jz47xx/crt0.S b/firmware/target/mips/ingenic_jz47xx/crt0.S
index 0ae365022a..49de3e6a01 100644
--- a/firmware/target/mips/ingenic_jz47xx/crt0.S
+++ b/firmware/target/mips/ingenic_jz47xx/crt0.S
@@ -50,6 +50,7 @@
.set noat
#ifdef BOOTLOADER
+#ifndef XDUOO_X3
/* These will get filled in by scramble */
.word 0 /* Empty */
.word 0 /* Filesize */
@@ -65,6 +66,7 @@ _relocate_loop:
bne t1, t2, _relocate_loop
sw t3, -4(t1)
#endif
+#endif
_start:
la ra, _start
diff --git a/firmware/target/mips/ingenic_jz47xx/debug-jz4760.c b/firmware/target/mips/ingenic_jz47xx/debug-jz4760.c
new file mode 100644
index 0000000000..88fc351946
--- /dev/null
+++ b/firmware/target/mips/ingenic_jz47xx/debug-jz4760.c
@@ -0,0 +1,146 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2016 by Roman Stolyarov
+ *
+ * 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 "config.h"
+#include "system.h"
+#include "cpu.h"
+#include <stdarg.h>
+#include <stdio.h>
+#include "lcd.h"
+#include "kernel.h"
+#include "font.h"
+#include "button.h"
+#include "timefuncs.h"
+
+#define CFG_UART_BASE UART1_BASE /* Base of the UART channel */
+
+void serial_putc (const char c)
+{
+ volatile u8 *uart_lsr = (volatile u8 *)(CFG_UART_BASE + OFF_LSR);
+ volatile u8 *uart_tdr = (volatile u8 *)(CFG_UART_BASE + OFF_TDR);
+
+ if (c == '\n') serial_putc ('\r');
+
+ /* Wait for fifo to shift out some bytes */
+ while ( !((*uart_lsr & (UARTLSR_TDRQ | UARTLSR_TEMT)) == 0x60) );
+
+ *uart_tdr = (u8)c;
+}
+
+void serial_puts (const char *s)
+{
+ while (*s) {
+ serial_putc (*s++);
+ }
+}
+
+void serial_putsf(const char *format, ...)
+{
+ static char printfbuf[256];
+ int len;
+ unsigned char *ptr;
+ va_list ap;
+ va_start(ap, format);
+
+ ptr = printfbuf;
+ len = vsnprintf(ptr, sizeof(printfbuf), format, ap);
+ va_end(ap);
+
+ serial_puts(ptr);
+ serial_putc('\n');
+}
+
+void serial_put_hex(unsigned int d)
+{
+ char c[12];
+ int i;
+ for(i = 0; i < 8;i++)
+ {
+ c[i] = (d >> ((7 - i) * 4)) & 0xf;
+ if(c[i] < 10)
+ c[i] += 0x30;
+ else
+ c[i] += (0x41 - 10);
+ }
+ c[8] = '\n';
+ c[9] = 0;
+ serial_puts(c);
+
+}
+
+void serial_put_dec(unsigned int d)
+{
+ char c[16];
+ int i;
+ int j = 0;
+ int x = d;
+
+ while (x /= 10)
+ j++;
+
+ for (i = j; i >= 0; i--) {
+ c[i] = d % 10;
+ c[i] += 0x30;
+ d /= 10;
+ }
+ c[j + 1] = '\n';
+ c[j + 2] = 0;
+ serial_puts(c);
+}
+
+void serial_dump_data(unsigned char* data, int len)
+{
+ int i;
+ for(i=0; i<len; i++)
+ {
+ unsigned char a = ((*data)>>4) & 0xf;
+ if(a < 10)
+ a += 0x30;
+ else
+ a += (0x41 - 10);
+ serial_putc( a );
+
+ a = (*data) & 0xf;
+ if(a < 10)
+ a += 0x30;
+ else
+ a += (0x41 - 10);
+ serial_putc( a );
+
+ serial_putc( ' ' );
+
+ data++;
+ }
+
+ serial_putc( '\n' );
+}
+
+bool dbg_ports(void)
+{
+ serial_puts("dbg_ports\n");
+ return false;
+}
+
+bool dbg_hw_info(void)
+{
+ serial_puts("dbg_hw_info\n");
+ return false;
+}
diff --git a/firmware/target/mips/ingenic_jz47xx/dma-target.h b/firmware/target/mips/ingenic_jz47xx/dma-target.h
new file mode 100644
index 0000000000..792d8fe87c
--- /dev/null
+++ b/firmware/target/mips/ingenic_jz47xx/dma-target.h
@@ -0,0 +1,31 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2016 by Roman Stolyarov
+ *
+ * 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 __DMA_TARGET_H_
+#define __DMA_TARGET_H_
+
+#include "system.h"
+#include <string.h>
+
+void memset_dma(void *target, int c, size_t len, unsigned int bits);
+void memcpy_dma(void *target, const void *source, size_t len, unsigned int bits);
+
+#endif /* __DMA_TARGET_H_ */
diff --git a/firmware/target/mips/ingenic_jz47xx/dma_acc-jz4760.c b/firmware/target/mips/ingenic_jz47xx/dma_acc-jz4760.c
new file mode 100644
index 0000000000..4cdea2ad08
--- /dev/null
+++ b/firmware/target/mips/ingenic_jz47xx/dma_acc-jz4760.c
@@ -0,0 +1,102 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2016 by Roman Stolyarov
+ *
+ * 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 "dma-target.h"
+
+#define MDMA_CHANNEL 0
+
+void memset_dma(void *target, int c, size_t len, unsigned int bits)
+{
+ unsigned int d;
+ unsigned char *dp;
+
+ if(((unsigned int)target < 0xa0000000) && len)
+ dma_cache_wback_inv((unsigned long)target, len);
+
+ dp = (unsigned char *)((unsigned int)(&d) | 0xa0000000);
+ *(dp + 0) = c;
+ *(dp + 1) = c;
+ *(dp + 2) = c;
+ *(dp + 3) = c;
+
+ REG_MDMAC_DCCSR(MDMA_CHANNEL) = 0;
+ REG_MDMAC_DSAR(MDMA_CHANNEL) = PHYSADDR((unsigned long)dp);
+ REG_MDMAC_DTAR(MDMA_CHANNEL) = PHYSADDR((unsigned long)target);
+ REG_MDMAC_DRSR(MDMA_CHANNEL) = DMAC_DRSR_RS_AUTO;
+ switch (bits)
+ {
+ case 8:
+ REG_MDMAC_DTCR(MDMA_CHANNEL) = len;
+ REG_MDMAC_DCMD(MDMA_CHANNEL) = DMAC_DCMD_DAI | DMAC_DCMD_SWDH_8 | DMAC_DCMD_DWDH_8 | DMAC_DCMD_DS_8BIT;
+ break;
+ case 16:
+ REG_MDMAC_DTCR(MDMA_CHANNEL) = len / 2;
+ REG_MDMAC_DCMD(MDMA_CHANNEL) = DMAC_DCMD_DAI | DMAC_DCMD_SWDH_16 | DMAC_DCMD_DWDH_16 | DMAC_DCMD_DS_16BIT;
+ break;
+ case 32:
+ REG_MDMAC_DTCR(MDMA_CHANNEL) = len / 4;
+ REG_MDMAC_DCMD(MDMA_CHANNEL) = DMAC_DCMD_DAI | DMAC_DCMD_SWDH_32 | DMAC_DCMD_DWDH_32 | DMAC_DCMD_DS_32BIT;
+ break;
+ default:
+ return;
+ }
+ REG_MDMAC_DCCSR(MDMA_CHANNEL) = DMAC_DCCSR_EN | DMAC_DCCSR_NDES;
+
+ while (REG_MDMAC_DTCR(MDMA_CHANNEL));
+
+ REG_MDMAC_DCCSR(MDMA_CHANNEL) = 0;
+}
+
+void memcpy_dma(void *target, const void *source, size_t len, unsigned int bits)
+{
+ if(((unsigned int)source < 0xa0000000) && len)
+ dma_cache_wback_inv((unsigned long)source, len);
+
+ if(((unsigned int)target < 0xa0000000) && len)
+ dma_cache_wback_inv((unsigned long)target, len);
+
+ REG_MDMAC_DCCSR(MDMA_CHANNEL) = 0;
+ REG_MDMAC_DSAR(MDMA_CHANNEL) = PHYSADDR((unsigned long)source);
+ REG_MDMAC_DTAR(MDMA_CHANNEL) = PHYSADDR((unsigned long)target);
+ REG_MDMAC_DRSR(MDMA_CHANNEL) = DMAC_DRSR_RS_AUTO;
+ switch (bits)
+ {
+ case 8:
+ REG_MDMAC_DTCR(MDMA_CHANNEL) = len;
+ REG_MDMAC_DCMD(MDMA_CHANNEL) = DMAC_DCMD_SAI | DMAC_DCMD_DAI | DMAC_DCMD_SWDH_8 | DMAC_DCMD_DWDH_8 | DMAC_DCMD_DS_8BIT;
+ break;
+ case 16:
+ REG_MDMAC_DTCR(MDMA_CHANNEL) = len / 2;
+ REG_MDMAC_DCMD(MDMA_CHANNEL) = DMAC_DCMD_SAI | DMAC_DCMD_DAI | DMAC_DCMD_SWDH_16 | DMAC_DCMD_DWDH_16 | DMAC_DCMD_DS_16BIT;
+ break;
+ case 32:
+ REG_MDMAC_DTCR(MDMA_CHANNEL) = len / 4;
+ REG_MDMAC_DCMD(MDMA_CHANNEL) = DMAC_DCMD_SAI | DMAC_DCMD_DAI | DMAC_DCMD_SWDH_32 | DMAC_DCMD_DWDH_32 | DMAC_DCMD_DS_32BIT;
+ break;
+ default:
+ return;
+ }
+ REG_MDMAC_DCCSR(MDMA_CHANNEL) = DMAC_DCCSR_EN | DMAC_DCCSR_NDES;
+
+ while (REG_MDMAC_DTCR(MDMA_CHANNEL));
+
+ REG_MDMAC_DCCSR(MDMA_CHANNEL) = 0;
+}
diff --git a/firmware/target/mips/ingenic_jz47xx/i2c-jz4760.c b/firmware/target/mips/ingenic_jz47xx/i2c-jz4760.c
new file mode 100644
index 0000000000..e35fe7a091
--- /dev/null
+++ b/firmware/target/mips/ingenic_jz47xx/i2c-jz4760.c
@@ -0,0 +1,355 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2016 by Roman Stolyarov
+ *
+ * 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 "config.h"
+#include "system.h"
+#include "cpu.h"
+#include "logf.h"
+#include "i2c.h"
+
+#define I2C_CHN 1
+#define I2C_CLK 100000
+
+#define I2C_READ 1
+#define I2C_WRITE 0
+
+#define I2C_M_RD 1
+#define I2C_M_WR 2
+
+#define TIMEOUT 100000
+
+static char i2c_rwflags;
+static int i2c_ctrl_rest = 0;
+static unsigned char *msg_buf;
+static int cmd_cnt;
+static volatile int cmd_flag;
+static int r_cnt;
+static unsigned char i2c_subaddr = 0;
+
+/*
+ * I2C bus protocol basic routines
+ */
+
+/* Interrupt handler */
+void I2C1(void)
+{
+ int timeout = TIMEOUT;
+
+ if (__i2c_abrt_7b_addr_nack(I2C_CHN)) {
+ int ret;
+ cmd_flag = -1;
+ __i2c_clear_interrupts(ret,I2C_CHN);
+ REG_I2C_INTM(I2C_CHN) = 0x0;
+ return;
+ }
+
+ /* first byte,when length > 1 */
+ if (cmd_flag == 0 && cmd_cnt > 1) {
+ cmd_flag = 1;
+ if (i2c_rwflags == I2C_M_RD) {
+ REG_I2C_DC(I2C_CHN) = I2C_READ << 8;
+ } else {
+ REG_I2C_DC(I2C_CHN) = (I2C_WRITE << 8) | *msg_buf++;
+ }
+ cmd_cnt--;
+ }
+
+ if (i2c_rwflags == I2C_M_RD) {
+ if (REG_I2C_STA(I2C_CHN) & I2C_STA_RFNE) {
+ *msg_buf++ = REG_I2C_DC(I2C_CHN) & 0xff;
+ r_cnt--;
+ }
+
+ REG_I2C_DC(I2C_CHN) = I2C_READ << 8;
+ } else {
+ REG_I2C_DC(I2C_CHN) = (I2C_WRITE << 8) | *msg_buf++;
+ }
+
+ cmd_cnt--;
+
+ if (!(cmd_cnt)) {
+ REG_I2C_INTM(I2C_CHN) = 0x0;
+ cmd_flag = 2;
+ if (i2c_rwflags == I2C_M_RD){
+ while (r_cnt > 2) {
+ if ((REG_I2C_STA(I2C_CHN) & I2C_STA_RFNE) && timeout) {
+ *msg_buf++ = REG_I2C_DC(I2C_CHN) & 0xff;
+ r_cnt--;
+ }
+ if (!(timeout--)) {
+ cmd_flag = -1;
+ return;
+ }
+ }
+ }
+ }
+
+ return;
+}
+
+static int i2c_set_clk(int i2c_clk)
+{
+ int dev_clk = __cpm_get_pclk();
+ int count = 0;
+
+ if (i2c_clk < 0 || i2c_clk > 400000)
+ goto Set_clk_err;
+
+ count = dev_clk/i2c_clk - 23;
+ if (count < 0)
+ goto Set_clk_err;
+
+ if (i2c_clk <= 100000) {
+ REG_I2C_CTRL(I2C_CHN) = 0x43 | i2c_ctrl_rest; /* standard speed mode*/
+ if (count%2 == 0) {
+ REG_I2C_SHCNT(I2C_CHN) = count/2 + 6 - 5;
+ REG_I2C_SLCNT(I2C_CHN) = count/2 + 8 + 5;
+ } else {
+ REG_I2C_SHCNT(I2C_CHN) = count/2 + 6 -5;
+ REG_I2C_SLCNT(I2C_CHN) = count/2 + 8 +5 + 1;
+ }
+ } else {
+ REG_I2C_CTRL(I2C_CHN) = 0x45 | i2c_ctrl_rest; /* high speed mode*/
+ if (count%2 == 0) {
+ REG_I2C_FHCNT(I2C_CHN) = count/2 + 6;
+ REG_I2C_FLCNT(I2C_CHN) = count/2 + 8;
+ } else {
+ REG_I2C_FHCNT(I2C_CHN) = count/2 + 6;
+ REG_I2C_FLCNT(I2C_CHN) = count/2 + 8 + 1;
+ }
+ }
+ return 0;
+
+Set_clk_err:
+
+ logf("i2c set sclk faild,i2c_clk=%d,dev_clk=%d.\n",i2c_clk,dev_clk);
+ return -1;
+}
+
+static int i2c_disable(void)
+{
+ int timeout = TIMEOUT;
+
+ __i2c_disable(I2C_CHN);
+ while(__i2c_is_enable(I2C_CHN) && (timeout > 0)) {
+ udelay(1);
+ timeout--;
+ }
+ if(timeout)
+ return 0;
+ else
+ return 1;
+}
+
+static int i2c_enable(void)
+{
+ int timeout = TIMEOUT;
+
+ __i2c_enable(I2C_CHN);
+ while(__i2c_is_disable(I2C_CHN) && (timeout > 0)) {
+ udelay(1);
+ timeout--;
+ }
+ if(timeout)
+ return 0;
+ else
+ return 1;
+}
+
+static void i2c_init_as_master(unsigned char address)
+{
+ if(i2c_disable())
+ logf("i2c not disable\n");
+
+ i2c_set_clk(I2C_CLK);
+
+ REG_I2C_TAR(I2C_CHN) = address; /* slave id needed write only once */
+ REG_I2C_INTM(I2C_CHN) = 0x0; /* unmask all interrupts */
+ REG_I2C_TXTL(I2C_CHN) = 0x1;
+
+ if(i2c_enable())
+ logf("i2c not enable\n");
+}
+
+int xfer_read_subaddr(unsigned char subaddr, unsigned char device, unsigned char *buf, int length)
+{
+ int timeout,r_i = 0;
+
+ cmd_cnt = length;
+ r_cnt = length;
+ msg_buf = buf;
+ i2c_rwflags = I2C_M_RD;
+ i2c_ctrl_rest = I2C_CTRL_REST;
+ i2c_init_as_master(device);
+
+ REG_I2C_DC(I2C_CHN) = (I2C_WRITE << 8) | subaddr;
+
+ cmd_flag = 0;
+ REG_I2C_INTM(I2C_CHN) = 0x10;
+ timeout = TIMEOUT;
+ while (cmd_flag != 2 && --timeout) {
+ if (cmd_flag == -1) {
+ r_i = 1;
+ goto R_dev_err;
+ }
+ udelay(10);
+ }
+ if (!timeout) {
+ r_i = 4;
+ goto R_timeout;
+ }
+
+ while (r_cnt) {
+ while (!(REG_I2C_STA(I2C_CHN) & I2C_STA_RFNE)) {
+ if ((cmd_flag == -1) ||
+ (REG_I2C_INTST(I2C_CHN) & I2C_INTST_TXABT) ||
+ REG_I2C_TXABRT(I2C_CHN)) {
+ int ret;
+ r_i = 2;
+ __i2c_clear_interrupts(ret,I2C_CHN);
+ goto R_dev_err;
+ }
+ }
+ *msg_buf++ = REG_I2C_DC(I2C_CHN) & 0xff;
+ r_cnt--;
+ }
+
+ timeout = TIMEOUT;
+ while ((REG_I2C_STA(I2C_CHN) & I2C_STA_MSTACT) && --timeout)
+ udelay(10);
+ if (!timeout){
+ r_i = 3;
+ goto R_timeout;
+ }
+
+ return 0;
+
+R_dev_err:
+R_timeout:
+
+ i2c_init_as_master(device);
+ if (r_i == 1) {
+ logf("Read i2c device 0x%2x failed in r_i = %d :device no ack.\n",device,r_i);
+ } else if (r_i == 2) {
+ logf("Read i2c device 0x%2x failed in r_i = %d :i2c abort.\n",device,r_i);
+ } else if (r_i == 3) {
+ logf("Read i2c device 0x%2x failed in r_i = %d :waite master inactive timeout.\n",device,r_i);
+ } else if (r_i == 4) {
+ logf("Read i2c device 0x%2x failed in r_i = %d.\n",device,r_i);
+ } else {
+ logf("Read i2c device 0x%2x failed in r_i = %d.\n",device,r_i);
+ }
+ return -1;
+}
+
+int xfer_write_subaddr(unsigned char subaddr, unsigned char device, const unsigned char *buf, int length)
+{
+ int timeout,w_i = 0;
+
+ cmd_cnt = length;
+ r_cnt = length;
+ msg_buf = (unsigned char *)buf;
+ i2c_rwflags = I2C_M_WR;
+ i2c_ctrl_rest = I2C_CTRL_REST;
+ i2c_init_as_master(device);
+
+ REG_I2C_DC(I2C_CHN) = (I2C_WRITE << 8) | subaddr;
+
+ cmd_flag = 0;
+ REG_I2C_INTM(I2C_CHN) = 0x10;
+
+ timeout = TIMEOUT;
+ while ((cmd_flag != 2) && (--timeout))
+ {
+ if (cmd_flag == -1){
+ w_i = 1;
+ goto W_dev_err;
+ }
+ udelay(10);
+ }
+
+ timeout = TIMEOUT;
+ while((!(REG_I2C_STA(I2C_CHN) & I2C_STA_TFE)) && --timeout){
+ udelay(10);
+ }
+ if (!timeout){
+ w_i = 2;
+ goto W_timeout;
+ }
+
+ timeout = TIMEOUT;
+ while (__i2c_master_active(I2C_CHN) && --timeout);
+ if (!timeout){
+ w_i = 3;
+ goto W_timeout;
+ }
+
+ if ((length == 1)&&
+ ((cmd_flag == -1) ||
+ (REG_I2C_INTST(I2C_CHN) & I2C_INTST_TXABT) ||
+ REG_I2C_TXABRT(I2C_CHN))) {
+ int ret;
+ w_i = 5;
+ __i2c_clear_interrupts(ret,I2C_CHN);
+ goto W_dev_err;
+ }
+
+ return 0;
+
+W_dev_err:
+W_timeout:
+
+ i2c_init_as_master(device);
+ if (w_i == 1) {
+ logf("Write i2c device 0x%2x failed in w_i=%d:device no ack. I2C_CHN:[%d]sxyzhang\n",device,w_i,I2C_CHN);
+ } else if (w_i == 2) {
+ logf("Write i2c device 0x%2x failed in w_i=%d:waite TF buff empty timeout.\n",device,w_i);
+ } else if (w_i == 3) {
+ logf("Write i2c device 0x%2x failed in w_i=%d:waite master inactive timeout.\n",device,w_i);
+ } else if (w_i == 5) {
+ logf("Write i2c device 0x%2x failed in w_i=%d:device no ack or abort.I2C_CHN:[%d]sxyzhang \n",device,w_i,I2C_CHN);
+ } else {
+ logf("Write i2c device 0x%2x failed in w_i=%d.\n",device,w_i);
+ }
+
+ return -1;
+}
+
+int i2c_read(int device, unsigned char* buf, int count)
+{
+ return xfer_read_subaddr(i2c_subaddr, device, &buf[0], count);
+}
+
+int i2c_write(int device, const unsigned char* buf, int count)
+{
+ if (count < 2)
+ {
+ i2c_subaddr = buf[0];
+ return 0;
+ }
+ return xfer_write_subaddr(buf[0], device, &buf[1], count-1);
+}
+
+void i2c_init(void)
+{
+ __gpio_as_i2c(I2C_CHN);
+ __cpm_start_i2c1();
+ system_enable_irq(IRQ_I2C1);
+}
diff --git a/firmware/target/mips/ingenic_jz47xx/kernel-jz4760.c b/firmware/target/mips/ingenic_jz47xx/kernel-jz4760.c
new file mode 100644
index 0000000000..ab0f152669
--- /dev/null
+++ b/firmware/target/mips/ingenic_jz47xx/kernel-jz4760.c
@@ -0,0 +1,53 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2016 by Roman Stolyarov
+ *
+ * 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 "config.h"
+#include "system.h"
+#include "kernel.h"
+#include "cpu.h"
+
+void tick_start(unsigned int interval_in_ms)
+{
+ unsigned int latch;
+
+ /* 12Mhz / 4 = 3Mhz */
+ latch = interval_in_ms*1000 * 3;
+
+ REG_OST_OSTCSR = OSTCSR_PRESCALE4 | OSTCSR_EXT_EN;
+ REG_OST_OSTDR = latch;
+ REG_OST_OSTCNTL = 0;
+ REG_OST_OSTCNTH = 0;
+
+ system_enable_irq(IRQ_TCU0);
+
+ REG_TCU_TMCR = TMCR_OSTMASK; /* unmask match irq */
+ REG_TCU_TSCR = TSCR_OST; /* enable timer clock */
+ REG_TCU_TESR = TESR_OST; /* start counting up */
+}
+
+/* Interrupt handler */
+void TCU0(void)
+{
+ REG_TCU_TFCR = TFCR_OSTFLAG; /* ACK timer */
+
+ /* Run through the list of tick tasks */
+ call_tick_tasks();
+}
diff --git a/firmware/target/mips/ingenic_jz47xx/lcd-jz4760.c b/firmware/target/mips/ingenic_jz47xx/lcd-jz4760.c
new file mode 100644
index 0000000000..03fb937d0a
--- /dev/null
+++ b/firmware/target/mips/ingenic_jz47xx/lcd-jz4760.c
@@ -0,0 +1,28 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2016 by Roman Stolyarov
+ *
+ * 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 <sys/types.h> /* off_t */
+
+#include "config.h"
+#include "cpu.h"
+#include "lcd.h"
+#include "lcd-target.h"
+#include "system.h"
+#include "kernel.h"
diff --git a/firmware/target/mips/ingenic_jz47xx/pcm-jz4760.c b/firmware/target/mips/ingenic_jz47xx/pcm-jz4760.c
new file mode 100644
index 0000000000..39df037f76
--- /dev/null
+++ b/firmware/target/mips/ingenic_jz47xx/pcm-jz4760.c
@@ -0,0 +1,240 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2016 by Roman Stolyarov
+ *
+ * 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 "kernel.h"
+#include "logf.h"
+#include "audio.h"
+#include "sound.h"
+#include "pcm.h"
+#include "pcm-internal.h"
+#include "cpu.h"
+
+
+/****************************************************************************
+ ** Playback DMA transfer
+ **/
+
+void pcm_play_dma_postinit(void)
+{
+ audiohw_postinit();
+
+ /* Flush FIFO */
+ __aic_flush_tfifo();
+}
+
+void pcm_play_dma_init(void)
+{
+ system_enable_irq(DMA_IRQ(DMA_AIC_TX_CHANNEL));
+
+ /* Initialize default register values. */
+ audiohw_init();
+}
+
+void pcm_dma_apply_settings(void)
+{
+ audiohw_set_frequency(pcm_fsel);
+}
+
+static const void* playback_address;
+static inline void set_dma(const void *addr, size_t size)
+{
+ int burst_size;
+ logf("%x %d %x", (unsigned int)addr, size, REG_AIC_SR);
+
+ dma_cache_wback_inv((unsigned long)addr, size);
+
+ if(size % 16)
+ {
+ if(size % 4)
+ {
+ size /= 2;
+ burst_size = DMAC_DCMD_DS_16BIT;
+ }
+ else
+ {
+ size /= 4;
+ burst_size = DMAC_DCMD_DS_32BIT;
+ }
+ }
+ else
+ {
+ size /= 16;
+ burst_size = DMAC_DCMD_DS_16BYTE;
+ }
+
+ REG_DMAC_DCCSR(DMA_AIC_TX_CHANNEL) = 0;
+ REG_DMAC_DSAR(DMA_AIC_TX_CHANNEL) = PHYSADDR((unsigned long)addr);
+ REG_DMAC_DTAR(DMA_AIC_TX_CHANNEL) = PHYSADDR((unsigned long)AIC_DR);
+ REG_DMAC_DTCR(DMA_AIC_TX_CHANNEL) = size;
+ REG_DMAC_DRSR(DMA_AIC_TX_CHANNEL) = DMAC_DRSR_RS_AICOUT;
+ REG_DMAC_DCMD(DMA_AIC_TX_CHANNEL) = (DMAC_DCMD_SAI | DMAC_DCMD_SWDH_32 | burst_size | DMAC_DCMD_DWDH_16 | DMAC_DCMD_TIE);
+ REG_DMAC_DCCSR(DMA_AIC_TX_CHANNEL) = DMAC_DCCSR_NDES | DMAC_DCCSR_EN;
+
+ playback_address = addr;
+}
+
+static inline void play_dma_callback(void)
+{
+ const void *start;
+ size_t size;
+
+ if (pcm_play_dma_complete_callback(PCM_DMAST_OK, &start, &size))
+ {
+ set_dma(start, size);
+ REG_DMAC_DCCSR(DMA_AIC_TX_CHANNEL) |= DMAC_DCCSR_EN;
+ pcm_play_dma_status_callback(PCM_DMAST_STARTED);
+ }
+}
+
+void DMA_CALLBACK(DMA_AIC_TX_CHANNEL)(void) __attribute__ ((section(".icode")));
+void DMA_CALLBACK(DMA_AIC_TX_CHANNEL)(void)
+{
+ if (REG_DMAC_DCCSR(DMA_AIC_TX_CHANNEL) & DMAC_DCCSR_AR)
+ {
+ logf("PCM DMA address error");
+ REG_DMAC_DCCSR(DMA_AIC_TX_CHANNEL) &= ~DMAC_DCCSR_AR;
+ }
+
+ if (REG_DMAC_DCCSR(DMA_AIC_TX_CHANNEL) & DMAC_DCCSR_HLT)
+ {
+ logf("PCM DMA halt");
+ REG_DMAC_DCCSR(DMA_AIC_TX_CHANNEL) &= ~DMAC_DCCSR_HLT;
+ }
+
+ if (REG_DMAC_DCCSR(DMA_AIC_TX_CHANNEL) & DMAC_DCCSR_TT)
+ {
+ REG_DMAC_DCCSR(DMA_AIC_TX_CHANNEL) &= ~DMAC_DCCSR_TT;
+ play_dma_callback();
+ }
+}
+
+void pcm_play_dma_start(const void *addr, size_t size)
+{
+ __dmac_channel_enable_clk(DMA_AIC_TX_CHANNEL);
+
+ set_dma(addr, size);
+
+ __aic_enable_replay();
+
+ __dmac_channel_enable_irq(DMA_AIC_TX_CHANNEL);
+}
+
+void pcm_play_dma_stop(void)
+{
+ int flags = disable_irq_save();
+
+ REG_DMAC_DCCSR(DMA_AIC_TX_CHANNEL) = (REG_DMAC_DCCSR(DMA_AIC_TX_CHANNEL) | DMAC_DCCSR_HLT) & ~DMAC_DCCSR_EN;
+
+ __dmac_channel_disable_clk(DMA_AIC_TX_CHANNEL);
+
+ __aic_disable_replay();
+
+ restore_irq(flags);
+}
+
+static unsigned int play_lock = 0;
+void pcm_play_lock(void)
+{
+ int flags = disable_irq_save();
+
+ if (++play_lock == 1)
+ __dmac_channel_disable_irq(DMA_AIC_TX_CHANNEL);
+
+ restore_irq(flags);
+}
+
+void pcm_play_unlock(void)
+{
+ int flags = disable_irq_save();
+
+ if (--play_lock == 0)
+ __dmac_channel_enable_irq(DMA_AIC_TX_CHANNEL);
+
+ restore_irq(flags);
+}
+
+void pcm_play_dma_pause(bool pause)
+{
+ int flags = disable_irq_save();
+
+ if(pause)
+ REG_DMAC_DCCSR(DMA_AIC_TX_CHANNEL) &= ~DMAC_DCCSR_EN;
+ else
+ REG_DMAC_DCCSR(DMA_AIC_TX_CHANNEL) |= DMAC_DCCSR_EN;
+
+ restore_irq(flags);
+}
+
+static int get_dma_count(void)
+{
+ int count = REG_DMAC_DTCR(DMA_AIC_TX_CHANNEL);
+ switch(REG_DMAC_DCMD(DMA_AIC_TX_CHANNEL) & DMAC_DCMD_DS_MASK)
+ {
+ case DMAC_DCMD_DS_16BIT:
+ count *= 2;
+ break;
+ case DMAC_DCMD_DS_32BIT:
+ count *= 4;
+ break;
+ case DMAC_DCMD_DS_16BYTE:
+ count *= 16;
+ break;
+ }
+
+ return count;
+}
+
+size_t pcm_get_bytes_waiting(void)
+{
+ int bytes, flags = disable_irq_save();
+
+ if(REG_DMAC_DCCSR(DMA_AIC_TX_CHANNEL) & DMAC_DCCSR_EN)
+ bytes = get_dma_count() & ~3;
+ else
+ bytes = 0;
+
+ restore_irq(flags);
+
+ return bytes;
+}
+
+const void * pcm_play_dma_get_peak_buffer(int *count)
+{
+ int flags = disable_irq_save();
+
+ const void* addr;
+ if(REG_DMAC_DCCSR(DMA_AIC_TX_CHANNEL) & DMAC_DCCSR_EN)
+ {
+ int bytes = get_dma_count();
+ *count = bytes >> 2;
+ addr = (const void*)((int)(playback_address + bytes + 2) & ~3);
+ }
+ else
+ {
+ *count = 0;
+ addr = NULL;
+ }
+
+ restore_irq(flags);
+
+ return addr;
+}
diff --git a/firmware/target/mips/ingenic_jz47xx/system-jz4760.c b/firmware/target/mips/ingenic_jz47xx/system-jz4760.c
new file mode 100644
index 0000000000..8472a7378f
--- /dev/null
+++ b/firmware/target/mips/ingenic_jz47xx/system-jz4760.c
@@ -0,0 +1,710 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2016 by Roman Stolyarov
+ *
+ * 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 "config.h"
+#include "cpu.h"
+#include "mips.h"
+#include "mmu-mips.h"
+#include "panic.h"
+#include "system.h"
+#include "kernel.h"
+#include "power.h"
+
+static int irq;
+static void UIRQ(void)
+{
+ panicf("Unhandled interrupt occurred: %d", irq);
+}
+
+#define intr(name) extern __attribute__((weak,alias("UIRQ"))) void name (void)
+
+intr(I2C1);intr(I2C0);intr(UART3);intr(UART2);intr(UART1);intr(UART0);intr(GPU);
+intr(SSI1);intr(SSI0);intr(TSSI);intr(KBC);intr(SADC);intr(ETH);intr(UHC);
+intr(OTG);intr(TCU2);intr(TCU1);intr(TCU0);intr(GPS);intr(IPU);intr(CIM);
+intr(LCD);intr(RTC);intr(OWI);intr(AIC);intr(MSC2);intr(MSC1);intr(MSC0);
+intr(SCC);intr(BCH);intr(PCM);intr(HARB0);intr(HARB2);intr(AOSD);intr(CPM);
+
+intr(DMA0);intr(DMA1);intr(DMA2);intr(DMA3);intr(DMA4);intr(DMA5);
+intr(DMA6);intr(DMA7);intr(DMA8);intr(DMA9);intr(DMA10);intr(DMA11);
+intr(MDMA0);intr(MDMA1);intr(MDMA2);
+intr(BDMA0);intr(BDMA1);intr(BDMA2);
+
+intr(GPIO0);intr(GPIO1);intr(GPIO2);intr(GPIO3);intr(GPIO4);intr(GPIO5);
+intr(GPIO6);intr(GPIO7);intr(GPIO8);intr(GPIO9);intr(GPIO10);intr(GPIO11);
+intr(GPIO12);intr(GPIO13);intr(GPIO14);intr(GPIO15);intr(GPIO16);intr(GPIO17);
+intr(GPIO18);intr(GPIO19);intr(GPIO20);intr(GPIO21);intr(GPIO22);intr(GPIO23);
+intr(GPIO24);intr(GPIO25);intr(GPIO26);intr(GPIO27);intr(GPIO28);intr(GPIO29);
+intr(GPIO30);intr(GPIO31);intr(GPIO32);intr(GPIO33);intr(GPIO34);intr(GPIO35);
+intr(GPIO36);intr(GPIO37);intr(GPIO38);intr(GPIO39);intr(GPIO40);intr(GPIO41);
+intr(GPIO42);intr(GPIO43);intr(GPIO44);intr(GPIO45);intr(GPIO46);intr(GPIO47);
+intr(GPIO48);intr(GPIO49);intr(GPIO50);intr(GPIO51);intr(GPIO52);intr(GPIO53);
+intr(GPIO54);intr(GPIO55);intr(GPIO56);intr(GPIO57);intr(GPIO58);intr(GPIO59);
+intr(GPIO60);intr(GPIO61);intr(GPIO62);intr(GPIO63);intr(GPIO64);intr(GPIO65);
+intr(GPIO66);intr(GPIO67);intr(GPIO68);intr(GPIO69);intr(GPIO70);intr(GPIO71);
+intr(GPIO72);intr(GPIO73);intr(GPIO74);intr(GPIO75);intr(GPIO76);intr(GPIO77);
+intr(GPIO78);intr(GPIO79);intr(GPIO80);intr(GPIO81);intr(GPIO82);intr(GPIO83);
+intr(GPIO84);intr(GPIO85);intr(GPIO86);intr(GPIO87);intr(GPIO88);intr(GPIO89);
+intr(GPIO90);intr(GPIO91);intr(GPIO92);intr(GPIO93);intr(GPIO94);intr(GPIO95);
+intr(GPIO96);intr(GPIO97);intr(GPIO98);intr(GPIO99);intr(GPIO100);intr(GPIO101);
+intr(GPIO102);intr(GPIO103);intr(GPIO104);intr(GPIO105);intr(GPIO106);
+intr(GPIO107);intr(GPIO108);intr(GPIO109);intr(GPIO110);intr(GPIO111);
+intr(GPIO112);intr(GPIO113);intr(GPIO114);intr(GPIO115);intr(GPIO116);
+intr(GPIO117);intr(GPIO118);intr(GPIO119);intr(GPIO120);intr(GPIO121);
+intr(GPIO122);intr(GPIO123);intr(GPIO124);intr(GPIO125);intr(GPIO126);
+intr(GPIO127);intr(GPIO128);intr(GPIO129);intr(GPIO130);intr(GPIO131);
+intr(GPIO132);intr(GPIO133);intr(GPIO134);intr(GPIO135);intr(GPIO136);
+intr(GPIO137);intr(GPIO138);intr(GPIO139);intr(GPIO140);intr(GPIO141);
+intr(GPIO142);intr(GPIO143);intr(GPIO144);intr(GPIO145);intr(GPIO146);
+intr(GPIO147);intr(GPIO148);intr(GPIO149);intr(GPIO150);intr(GPIO151);
+intr(GPIO152);intr(GPIO153);intr(GPIO154);intr(GPIO155);intr(GPIO156);
+intr(GPIO157);intr(GPIO158);intr(GPIO159);intr(GPIO160);intr(GPIO161);
+intr(GPIO162);intr(GPIO163);intr(GPIO164);intr(GPIO165);intr(GPIO166);
+intr(GPIO167);intr(GPIO168);intr(GPIO169);intr(GPIO170);intr(GPIO171);
+intr(GPIO172);intr(GPIO173);intr(GPIO174);intr(GPIO175);intr(GPIO176);
+intr(GPIO177);intr(GPIO178);intr(GPIO179);intr(GPIO180);intr(GPIO181);
+intr(GPIO182);intr(GPIO183);intr(GPIO184);intr(GPIO185);intr(GPIO186);
+intr(GPIO187);intr(GPIO188);intr(GPIO189);intr(GPIO190);intr(GPIO191);
+
+static void (* const irqvector[])(void) =
+{
+ I2C1,I2C0,UART3,UART2,UART1,UART0,GPU,SSI1,
+ SSI0,TSSI,UIRQ,KBC,UIRQ,UIRQ,UIRQ,UIRQ,
+ UIRQ,UIRQ,SADC,ETH,UHC,OTG,UIRQ,UIRQ,
+ UIRQ,TCU2,TCU1,TCU0,GPS,IPU,CIM,LCD,
+
+ RTC,OWI,AIC,MSC2,MSC1,MSC0,SCC,BCH, // 32
+ PCM,HARB0,HARB2,AOSD,CPM,UIRQ,
+
+ DMA0,DMA1,DMA2,DMA3,DMA4,DMA5,DMA6,DMA7, // 46
+ DMA8,DMA9,DMA10,DMA11,MDMA0,MDMA1,MDMA2,BDMA0,
+ BDMA1,BDMA2,
+
+ GPIO0,GPIO1,GPIO2,GPIO3,GPIO4,GPIO5,GPIO6,GPIO7, // 64
+ GPIO8,GPIO9,GPIO10,GPIO11,GPIO12,GPIO13,GPIO14,GPIO15,
+ GPIO16,GPIO17,GPIO18,GPIO19,GPIO20,GPIO21,GPIO22,GPIO23,
+ GPIO24,GPIO25,GPIO26,GPIO27,GPIO28,GPIO29,GPIO30,GPIO31,
+ GPIO32,GPIO33,GPIO34,GPIO35,GPIO36,GPIO37,GPIO38,GPIO39,
+ GPIO40,GPIO41,GPIO42,GPIO43,GPIO44,GPIO45,GPIO46,GPIO47,
+ GPIO48,GPIO49,GPIO50,GPIO51,GPIO52,GPIO53,GPIO54,GPIO55,
+ GPIO56,GPIO57,GPIO58,GPIO59,GPIO60,GPIO61,GPIO62,GPIO63,
+ GPIO64,GPIO65,GPIO66,GPIO67,GPIO68,GPIO69,GPIO70,GPIO71,
+ GPIO72,GPIO73,GPIO74,GPIO75,GPIO76,GPIO77,GPIO78,GPIO79,
+ GPIO80,GPIO81,GPIO82,GPIO83,GPIO84,GPIO85,GPIO86,GPIO87,
+ GPIO88,GPIO89,GPIO90,GPIO91,GPIO92,GPIO93,GPIO94,GPIO95,
+ GPIO96,GPIO97,GPIO98,GPIO99,GPIO100,GPIO101,GPIO102,GPIO103,
+ GPIO104,GPIO105,GPIO106,GPIO107,GPIO108,GPIO109,GPIO110,GPIO111,
+ GPIO112,GPIO113,GPIO114,GPIO115,GPIO116,GPIO117,GPIO118,GPIO119,
+ GPIO120,GPIO121,GPIO122,GPIO123,GPIO124,GPIO125,GPIO126,GPIO127,
+ GPIO128,GPIO129,GPIO130,GPIO131,GPIO132,GPIO133,GPIO134,GPIO135,
+ GPIO136,GPIO137,GPIO138,GPIO139,GPIO140,GPIO141,GPIO142,GPIO143,
+ GPIO144,GPIO145,GPIO146,GPIO147,GPIO148,GPIO149,GPIO150,GPIO151,
+ GPIO152,GPIO153,GPIO154,GPIO155,GPIO156,GPIO157,GPIO158,GPIO159,
+ GPIO160,GPIO161,GPIO162,GPIO163,GPIO164,GPIO165,GPIO166,GPIO167,
+ GPIO168,GPIO169,GPIO170,GPIO171,GPIO172,GPIO173,GPIO174,GPIO175,
+ GPIO176,GPIO177,GPIO178,GPIO179,GPIO180,GPIO181,GPIO182,GPIO183,
+ GPIO184,GPIO185,GPIO186,GPIO187,GPIO188,GPIO189,GPIO190,GPIO191
+};
+
+static unsigned int dma_irq_mask = 0;
+static unsigned char mdma_irq_mask = 0;
+static unsigned char bdma_irq_mask = 0;
+static unsigned int gpio_irq_mask[6] = {0};
+
+void system_enable_irq(unsigned int irq)
+{
+ register unsigned int t;
+ if ((irq >= IRQ_GPIO_0) && (irq <= IRQ_GPIO_0 + NUM_GPIO))
+ {
+ __gpio_unmask_irq(irq - IRQ_GPIO_0);
+ t = (irq - IRQ_GPIO_0) >> 5;
+ gpio_irq_mask[t] |= (1 << ((irq - IRQ_GPIO_0) & 0x1f));
+ __intc_unmask_irq(IRQ_GPIO0 - t);
+ }
+ else if ((irq >= IRQ_DMA_0) && (irq <= IRQ_DMA_0 + NUM_DMA))
+ {
+ __dmac_channel_enable_irq(irq - IRQ_DMA_0);
+ t = (irq - IRQ_DMA_0) / HALF_DMA_NUM;
+ dma_irq_mask |= (1 << (irq - IRQ_DMA_0));
+ __intc_unmask_irq(IRQ_DMAC0 - t);
+ }
+ else if ((irq >= IRQ_MDMA_0) && (irq <= IRQ_MDMA_0 + NUM_MDMA))
+ {
+ __mdmac_channel_enable_irq(irq - IRQ_MDMA_0);
+ mdma_irq_mask |= (1 << (irq - IRQ_MDMA_0));
+ __intc_unmask_irq(IRQ_MDMA);
+ }
+ else if ((irq >= IRQ_BDMA_0) && (irq <= IRQ_BDMA_0 + NUM_BDMA))
+ {
+ __bdmac_channel_enable_irq(irq - IRQ_BDMA_0);
+ bdma_irq_mask |= (1 << (irq - IRQ_BDMA_0));
+ __intc_unmask_irq(IRQ_BDMA);
+ }
+ else if (irq < IRQ_INTC_MAX)
+ __intc_unmask_irq(irq);
+}
+
+static void dis_irq(unsigned int irq)
+{
+ register unsigned int t;
+ if ((irq >= IRQ_GPIO_0) && (irq <= IRQ_GPIO_0 + NUM_GPIO))
+ {
+ __gpio_mask_irq(irq - IRQ_GPIO_0);
+ t = (irq - IRQ_GPIO_0) >> 5;
+ gpio_irq_mask[t] &= ~(1 << ((irq - IRQ_GPIO_0) & 0x1f));
+ if (!gpio_irq_mask[t])
+ __intc_mask_irq(IRQ_GPIO0 - t);
+ }
+ else if ((irq >= IRQ_DMA_0) && (irq < IRQ_DMA_0 + NUM_DMA))
+ {
+ __dmac_channel_disable_irq(irq - IRQ_DMA_0);
+ dma_irq_mask &= ~(1 << (irq - IRQ_DMA_0));
+ if (!(dma_irq_mask & 0x003F))
+ __intc_mask_irq(IRQ_DMAC0);
+ if (!(dma_irq_mask & 0x0FC0))
+ __intc_mask_irq(IRQ_DMAC1);
+ }
+ else if ((irq >= IRQ_MDMA_0) && (irq < IRQ_MDMA_0 + NUM_MDMA))
+ {
+ __mdmac_channel_disable_irq(irq - IRQ_MDMA_0);
+ mdma_irq_mask &= ~(1 << (irq - IRQ_MDMA_0));
+ if (!mdma_irq_mask)
+ __intc_mask_irq(IRQ_MDMA);
+ }
+ else if ((irq >= IRQ_BDMA_0) && (irq < IRQ_BDMA_0 + NUM_BDMA))
+ {
+ __bdmac_channel_disable_irq(irq - IRQ_BDMA_0);
+ bdma_irq_mask &= ~(1 << (irq - IRQ_BDMA_0));
+ if (!bdma_irq_mask)
+ __intc_mask_irq(IRQ_BDMA);
+ }
+ else if (irq < IRQ_INTC_MAX)
+ __intc_mask_irq(irq);
+}
+
+static void ack_irq(unsigned int irq)
+{
+ if ((irq >= IRQ_GPIO_0) && (irq <= IRQ_GPIO_0 + NUM_GPIO))
+ {
+ __gpio_ack_irq(irq - IRQ_GPIO_0);
+ }
+}
+
+static int get_irq_number(void)
+{
+ static unsigned long ipl0, ipl1;
+ register int irq0, irq1;
+
+ ipl0 |= REG_INTC_ICPR(0);
+ ipl1 |= REG_INTC_ICPR(1);
+
+ if (!(ipl0 || ipl1))
+ return -1;
+
+ __asm__ __volatile__("negu $8, %0 \n"
+ "and $8, %0, $8 \n"
+ "clz %0, %1 \n"
+ "li $8, 31 \n"
+ "subu %0, $8, %0 \n"
+ : "=r" (irq0)
+ : "r" (ipl0)
+ : "t0"
+ );
+
+ __asm__ __volatile__("negu $8, %0 \n"
+ "and $8, %0, $8 \n"
+ "clz %0, %1 \n"
+ "li $8, 31 \n"
+ "subu %0, $8, %0 \n"
+ : "=r" (irq1)
+ : "r" (ipl1)
+ : "t0"
+ );
+
+ if (UNLIKELY(irq0 < 0) && UNLIKELY(irq1 < 0))
+ return -1;
+
+ if (!(ipl0 & 3)) {
+ if (ipl0) {
+ irq = irq0;
+ ipl0 &= ~(1<<irq0);
+ } else {
+ irq = irq1 + 32;
+ ipl1 &= ~(1<<irq1);
+ }
+ } else {
+ if (ipl0 & 2) {
+ irq = 1;
+ ipl0 &= ~(1<<irq);
+ } else {
+ irq = 0;
+ ipl0 &= ~(1<<irq);
+ }
+ }
+
+ switch (irq)
+ {
+ case IRQ_GPIO0:
+ case IRQ_GPIO1:
+ case IRQ_GPIO2:
+ case IRQ_GPIO3:
+ case IRQ_GPIO4:
+ case IRQ_GPIO5:
+ irq = __gpio_get_irq() + IRQ_GPIO_0;
+ break;
+ case IRQ_DMAC0:
+ case IRQ_DMAC1:
+ irq = __dmac_get_irq() + IRQ_DMA_0;
+ break;
+ case IRQ_MDMA:
+ irq = __mdmac_get_irq() + IRQ_MDMA_0;
+ break;
+ case IRQ_BDMA:
+ irq = __bdmac_get_irq() + IRQ_BDMA_0;
+ break;
+ }
+
+ return irq;
+}
+
+void intr_handler(void)
+{
+ register int irq = get_irq_number();
+ if(UNLIKELY(irq < 0))
+ return;
+
+ ack_irq(irq);
+ if(LIKELY(irq >= 0))
+ irqvector[irq]();
+}
+
+#define EXC(x,y) case (x): return (y);
+static char* parse_exception(unsigned int cause)
+{
+ switch(cause & M_CauseExcCode)
+ {
+ EXC(EXC_INT, "Interrupt");
+ EXC(EXC_MOD, "TLB Modified");
+ EXC(EXC_TLBL, "TLB Exception (Load or Ifetch)");
+ EXC(EXC_ADEL, "Address Error (Load or Ifetch)");
+ EXC(EXC_ADES, "Address Error (Store)");
+ EXC(EXC_TLBS, "TLB Exception (Store)");
+ EXC(EXC_IBE, "Instruction Bus Error");
+ EXC(EXC_DBE, "Data Bus Error");
+ EXC(EXC_SYS, "Syscall");
+ EXC(EXC_BP, "Breakpoint");
+ EXC(EXC_RI, "Reserved Instruction");
+ EXC(EXC_CPU, "Coprocessor Unusable");
+ EXC(EXC_OV, "Overflow");
+ EXC(EXC_TR, "Trap Instruction");
+ EXC(EXC_FPE, "Floating Point Exception");
+ EXC(EXC_C2E, "COP2 Exception");
+ EXC(EXC_MDMX, "MDMX Exception");
+ EXC(EXC_WATCH, "Watch Exception");
+ EXC(EXC_MCHECK, "Machine Check Exception");
+ EXC(EXC_CacheErr, "Cache error caused re-entry to Debug Mode");
+ default:
+ return NULL;
+ }
+}
+
+void exception_handler(void* stack_ptr, unsigned int cause, unsigned int epc)
+{
+ panicf("Exception occurred: %s [0x%08x] at 0x%08x (stack at 0x%08x)", parse_exception(cause), read_c0_badvaddr(), epc, (unsigned int)stack_ptr);
+}
+
+void tlb_refill_handler(void)
+{
+ panicf("TLB refill handler at 0x%08lx! [0x%x]", read_c0_epc(), read_c0_badvaddr());
+}
+
+void udelay(unsigned int usec)
+{
+ unsigned int i = usec * (__cpm_get_cclk() / 2000000);
+ __asm__ __volatile__ (
+ ".set noreorder \n"
+ "1: \n"
+ "bne %0, $0, 1b \n"
+ "addi %0, %0, -1 \n"
+ ".set reorder \n"
+ : "=r" (i)
+ : "0" (i)
+ );
+}
+
+void mdelay(unsigned int msec)
+{
+ unsigned int i;
+ for(i=0; i<msec; i++)
+ udelay(1000);
+}
+
+#define MHZ (1000 * 1000)
+static inline unsigned int pll_calc_m_n_od(unsigned int speed, unsigned int xtal)
+{
+ const int pll_m_max = 0x7f, pll_m_min = 4;
+ const int pll_n_max = 0x0f, pll_n_min = 2;
+
+ int od[] = {1, 2, 4, 8};
+
+ unsigned int plcr_m_n_od = 0;
+ unsigned int distance;
+ unsigned int tmp, raw;
+
+ int i, j, k;
+ int m, n;
+
+ distance = 0xFFFFFFFF;
+
+ for (i = 0; i < (int)sizeof (od) / (int)sizeof(int); i++) {
+ /* Limit: 500MHZ <= CLK_OUT * OD <= 1500MHZ */
+ if ((speed * od[i]) < 500 * MHZ || (speed * od[i]) > 1500 * MHZ)
+ continue;
+ for (k = pll_n_min; k <= pll_n_max; k++) {
+ n = k;
+
+ /* Limit: 1MHZ <= XIN/N <= 50MHZ */
+ if ((xtal / n) < (1 * MHZ))
+ break;
+ if ((xtal / n) > (15 * MHZ))
+ continue;
+
+ for (j = pll_m_min; j <= pll_m_max; j++) {
+ m = j*2;
+
+ raw = xtal * m / n;
+ tmp = raw / od[i];
+
+ tmp = (tmp > speed) ? (tmp - speed) : (speed - tmp);
+
+ if (tmp < distance) {
+ distance = tmp;
+
+ plcr_m_n_od = (j << CPPCR0_PLLM_LSB)
+ | (k << CPPCR0_PLLN_LSB)
+ | (i << CPPCR0_PLLOD_LSB);
+
+ if (!distance) { /* Match. */
+ return plcr_m_n_od;
+ }
+ }
+ }
+ }
+ }
+ return plcr_m_n_od;
+}
+
+/* PLL output clock = EXTAL * NF / (NR * NO)
+ *
+ * NF = FD + 2, NR = RD + 2
+ * NO = 1 (if OD = 0), NO = 2 (if OD = 1 or 2), NO = 4 (if OD = 3)
+ */
+static void pll0_init(unsigned int freq)
+{
+ register unsigned int cfcr, plcr1;
+ int n2FR[9] = {
+ 0, 0, 1, 2, 3, 0, 4, 0, 5
+ };
+
+ /** divisors,
+ * for jz4760b,I:H:H2:P:M:S.
+ * DIV should be one of [1, 2, 3, 4, 6, 8]
+ */
+ int div[6] = {1, 4, 4, 4, 4, 4};
+ int usbdiv;
+
+ /* set ahb **/
+ REG32(HARB0_BASE) = 0x00300000;
+ REG32(0xb3070048) = 0x00000000;
+ REG32(HARB2_BASE) = 0x00FFFFFF;
+
+ cfcr = CPCCR_PCS |
+ (n2FR[div[0]] << CPCCR_CDIV_LSB) |
+ (n2FR[div[1]] << CPCCR_HDIV_LSB) |
+ (n2FR[div[2]] << CPCCR_H2DIV_LSB) |
+ (n2FR[div[3]] << CPCCR_PDIV_LSB) |
+ (n2FR[div[4]] << CPCCR_MDIV_LSB) |
+ (n2FR[div[5]] << CPCCR_SDIV_LSB);
+
+ // write REG_DDRC_CTRL 8 times to clear ddr fifo
+ REG_DDRC_CTRL = 0;
+ REG_DDRC_CTRL = 0;
+ REG_DDRC_CTRL = 0;
+ REG_DDRC_CTRL = 0;
+ REG_DDRC_CTRL = 0;
+ REG_DDRC_CTRL = 0;
+ REG_DDRC_CTRL = 0;
+ REG_DDRC_CTRL = 0;
+
+ if (CFG_EXTAL > 16000000)
+ cfcr |= CPCCR_ECS;
+ else
+ cfcr &= ~CPCCR_ECS;
+
+ cfcr &= ~CPCCR_MEM; /* mddr */
+ cfcr |= CPCCR_CE;
+
+ plcr1 = pll_calc_m_n_od(freq, CFG_EXTAL);
+ plcr1 |= (0x20 << CPPCR0_PLLST_LSB) /* PLL stable time */
+ | CPPCR0_PLLEN; /* enable PLL */
+
+ /*
+ * Init USB Host clock, pllout2 must be n*48MHz
+ * For JZ4760b UHC - River.
+ */
+ usbdiv = (cfcr & CPCCR_PCS) ? CPU_FREQ : (CPU_FREQ / 2);
+ REG_CPM_UHCCDR = usbdiv / 48000000 - 1;
+
+ /* init PLL */
+ REG_CPM_CPCCR = cfcr;
+ REG_CPM_CPPCR0 = plcr1;
+
+ __cpm_enable_pll_change();
+
+ /*wait for pll output stable ...*/
+ while (!(REG_CPM_CPPCR0 & CPPCR0_PLLS));
+
+ REG_CPM_CPPCR0 &= ~CPPCR0_LOCK;
+}
+
+void pll1_init(unsigned int freq)
+{
+ register unsigned int plcr2;
+
+ /* set CPM_CPCCR_MEM only for ddr1 or ddr2 */
+ plcr2 = pll_calc_m_n_od(freq, CFG_EXTAL)
+ | CPPCR1_PLL1EN; /* enable PLL1 */
+
+ /* init PLL_1 , source clock is extal clock */
+ REG_CPM_CPPCR1 = plcr2;
+
+ __cpm_enable_pll_change();
+
+ /*wait for pll_1 output stable ...*/
+ while (!(REG_CPM_CPPCR1 & CPPCR1_PLL1S));
+
+ REG_CPM_CPPCR1 &= ~CPPCR1_LOCK;
+}
+
+static void serial_setbrg(void)
+{
+ volatile u8 *uart_lcr = (volatile u8 *)(CFG_UART_BASE + OFF_LCR);
+ volatile u8 *uart_dlhr = (volatile u8 *)(CFG_UART_BASE + OFF_DLHR);
+ volatile u8 *uart_dllr = (volatile u8 *)(CFG_UART_BASE + OFF_DLLR);
+ volatile u8 *uart_umr = (volatile u8 *)(CFG_UART_BASE + OFF_UMR);
+ volatile u8 *uart_uacr = (volatile u8 *)(CFG_UART_BASE + OFF_UACR);
+ u16 baud_div, tmp;
+
+ *uart_umr = 16;
+ *uart_uacr = 0;
+ baud_div = 13; /* 57600 */
+
+ tmp = *uart_lcr;
+ tmp |= UARTLCR_DLAB;
+ *uart_lcr = tmp;
+
+ *uart_dlhr = (baud_div >> 8) & 0xff;
+ *uart_dllr = baud_div & 0xff;
+
+ tmp &= ~UARTLCR_DLAB;
+ *uart_lcr = tmp;
+}
+
+int serial_preinit(void)
+{
+ volatile u8 *uart_fcr = (volatile u8 *)(CFG_UART_BASE + OFF_FCR);
+ volatile u8 *uart_lcr = (volatile u8 *)(CFG_UART_BASE + OFF_LCR);
+ volatile u8 *uart_ier = (volatile u8 *)(CFG_UART_BASE + OFF_IER);
+ volatile u8 *uart_sircr = (volatile u8 *)(CFG_UART_BASE + OFF_SIRCR);
+
+ __gpio_as_uart1();
+ __cpm_start_uart1();
+
+ /* Disable port interrupts while changing hardware */
+ *uart_ier = 0;
+
+ /* Disable UART unit function */
+ *uart_fcr = ~UARTFCR_UUE;
+
+ /* Set both receiver and transmitter in UART mode (not SIR) */
+ *uart_sircr = ~(SIRCR_RSIRE | SIRCR_TSIRE);
+
+ /* Set databits, stopbits and parity. (8-bit data, 1 stopbit, no parity) */
+ *uart_lcr = UARTLCR_WLEN_8 | UARTLCR_STOP1;
+
+ /* Set baud rate */
+ serial_setbrg();
+
+ /* Enable UART unit, enable and clear FIFO */
+ *uart_fcr = UARTFCR_UUE | UARTFCR_FE | UARTFCR_TFLS | UARTFCR_RFLS;
+
+ return 0;
+}
+
+void usb_preinit(void)
+{
+ /* Clear ECS bit of CPCCR, 0:clock source is EXCLK, 1:clock source is EXCLK/2 */
+ REG_CPM_CPCCR &= ~CPCCR_ECS;
+
+ /* Clear all bits of USBCDR, 0:OTG clock source is pin EXCLK, PLL0 output, divider = 1:12MHZ */
+ REG_CPM_USBCDR = 0;
+
+ /* Set CE bit of CPCCR, it means frequence is changed immediately */
+ REG_CPM_CPCCR |= CPCCR_CE;
+
+ udelay(3);
+
+ /* Clear OTG bit of CLKGR0, 0:device can be accessed */
+ REG_CPM_CLKGR0 &= ~CLKGR0_OTG;
+
+ /* fil */
+ REG_CPM_USBVBFIL = 0x80;
+
+ /* rdt */
+ REG_CPM_USBRDT = (600 * (CPU_FREQ / 1000000)) / 1000;
+
+ /* rdt - filload_en */
+ REG_CPM_USBRDT |= (1 << 25);
+
+ /* TXRISETUNE & TXVREFTUNE. */
+ REG_CPM_USBPCR &= ~0x3f;
+ REG_CPM_USBPCR |= 0x35;
+
+ /* enable tx pre-emphasis */
+ REG_CPM_USBPCR |= 0x40;
+
+ /* most DC leave of tx */
+ REG_CPM_USBPCR |= 0xf;
+
+ /* Device Mode. */
+ REG_CPM_USBPCR &= ~(1 << 31);
+ REG_CPM_USBPCR |= USBPCR_VBUSVLDEXT;
+
+ /* phy reset */
+ REG_CPM_USBPCR |= USBPCR_POR;
+ udelay(30);
+ REG_CPM_USBPCR &= ~USBPCR_POR;
+ udelay(300);
+
+ /* Enable the USB PHY */
+ REG_CPM_OPCR |= OPCR_OTGPHY_ENABLE;
+
+ /* Wait PHY Clock Stable. */
+ udelay(300);
+}
+
+void dma_preinit(void)
+{
+ __cpm_start_mdma();
+ __cpm_start_dmac();
+
+ REG_MDMAC_DMACKES = 0x1;
+
+ REG_DMAC_DMACR(DMA_AIC_TX_CHANNEL) = DMAC_DMACR_DMAE | DMAC_DMACR_FAIC;
+ REG_DMAC_DMACR(DMA_SD_RX_CHANNEL) = DMAC_DMACR_DMAE | DMAC_DMACR_FMSC;
+ REG_DMAC_DMACR(DMA_SD_TX_CHANNEL) = DMAC_DMACR_DMAE | DMAC_DMACR_FMSC;
+}
+
+/* Gets called *before* main */
+void ICODE_ATTR system_main(void)
+{
+ int i;
+
+ __dcache_writeback_all();
+ __icache_invalidate_all();
+
+ write_c0_status(1 << 28 | 1 << 10 ); /* Enable CP | Mask interrupt 2 */
+
+ /* Disable all interrupts */
+ for(i=0; i<IRQ_INTC_MAX; i++)
+ dis_irq(i);
+
+ mmu_init();
+
+ pll0_init(CPU_FREQ);
+ pll1_init(CPU_FREQ);
+
+ serial_preinit();
+ usb_preinit();
+ dma_preinit();
+
+ /* Enable interrupts at core level */
+ enable_interrupt();
+}
+
+void system_reboot(void)
+{
+ REG_WDT_WCSR = WCSR_PRESCALE4 | WCSR_CLKIN_EXT;
+ REG_WDT_WCNT = 0;
+ REG_WDT_WDR = JZ_EXTAL/1000; /* reset after 4ms */
+ REG_TCU_TSCR = TSCR_WDT; /* enable wdt clock */
+ REG_WDT_WCER = WCER_TCEN; /* wdt start */
+ while (1);
+}
+
+void system_exception_wait(void)
+{
+ /* check for power button without including any .h file */
+ while(1)
+ {
+ if( (~REG_GPIO_PXPIN(0)) & (1 << 30) )
+ return;
+ asm volatile("nop");
+ }
+}
+
+void power_off(void)
+{
+ REG_CPM_RSR = 0x0;
+
+ /* Set minimum wakeup_n pin low-level assertion time for wakeup: 100ms */
+ rtc_write_reg(RTC_HWFCR, HWFCR_WAIT_TIME(1000));
+
+ /* Set reset pin low-level assertion time after wakeup: must > 60ms */
+ rtc_write_reg(RTC_HRCR, HRCR_WAIT_TIME(60));
+
+ /* clear wakeup status register */
+ rtc_write_reg(RTC_HWRSR, 0x0);
+
+ /* set wake up valid level as low */
+ rtc_write_reg(RTC_HWCR,0x8);
+
+ /* Put CPU to hibernate mode */
+ rtc_write_reg(RTC_HCR, HCR_PD);
+
+ while (1);
+}
+
+void system_init(void)
+{
+}
+
+int system_memory_guard(int newmode)
+{
+ (void)newmode;
+ return 0;
+}
+
+#ifdef HAVE_ADJUSTABLE_CPU_FREQ
+void set_cpu_frequency(long frequency)
+{
+ serial_putsf("set_cpu_frequency: %d\n", frequency);
+}
+#endif
diff --git a/firmware/target/mips/ingenic_jz47xx/system-target.h b/firmware/target/mips/ingenic_jz47xx/system-target.h
index 1c2e7d7173..9720d3a80c 100644
--- a/firmware/target/mips/ingenic_jz47xx/system-target.h
+++ b/firmware/target/mips/ingenic_jz47xx/system-target.h
@@ -25,7 +25,7 @@
#include <inttypes.h>
#include "config.h"
-#include "jz4740.h"
+#include "cpu.h"
#include "mipsregs.h"
#define CACHE_SIZE 16*1024
@@ -35,6 +35,8 @@
/* no optimized byteswap functions implemented for mips, yet */
#define NEED_GENERIC_BYTESWAPS
+#define STORAGE_WANTS_ALIGN
+
/* This one returns the old status */
static inline int set_interrupt_status(int status, int mask)
{
@@ -86,10 +88,18 @@ void mdelay(unsigned int msec);
void dma_enable(void);
void dma_disable(void);
+#if CONFIG_CPU == JZ4732
#define DMA_AIC_TX_CHANNEL 0
#define DMA_NAND_CHANNEL 1
#define DMA_USB_CHANNEL 2
#define DMA_LCD_CHANNEL 3
+#elif CONFIG_CPU == JZ4760B
+#define DMA_AIC_TX_CHANNEL 0
+#define DMA_NAND_CHANNEL 1
+#define DMA_USB_CHANNEL 2
+#define DMA_SD_RX_CHANNEL 3
+#define DMA_SD_TX_CHANNEL 4
+#endif
#define XDMA_CALLBACK(n) DMA ## n
#define DMA_CALLBACK(n) XDMA_CALLBACK(n)
@@ -103,7 +113,7 @@ void dma_disable(void);
*/
static inline void core_sleep(void)
{
-#if CONFIG_CPU == JZ4732
+#if CONFIG_CPU == JZ4732 || CONFIG_CPU == JZ4760B
__cpm_idle_mode();
#endif
asm volatile(".set mips32r2 \n"
diff --git a/firmware/target/mips/ingenic_jz47xx/timer-jz4760.c b/firmware/target/mips/ingenic_jz47xx/timer-jz4760.c
new file mode 100644
index 0000000000..0f16825b34
--- /dev/null
+++ b/firmware/target/mips/ingenic_jz47xx/timer-jz4760.c
@@ -0,0 +1,101 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2016 by Roman Stolyarov
+ *
+ * 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 "config.h"
+#include "cpu.h"
+#include "system.h"
+#include "timer.h"
+
+/* Interrupt handler */
+void TCU1(void)
+{
+ __tcu_clear_full_match_flag(5);
+
+ if (pfn_timer != NULL)
+ pfn_timer();
+}
+
+bool timer_set(long cycles, bool start)
+{
+ unsigned int divider = cycles, prescaler_bit = 0, prescaler = 1, old_irq;
+
+ if(cycles < 1)
+ return false;
+
+ if(start && pfn_unregister != NULL)
+ {
+ pfn_unregister();
+ pfn_unregister = NULL;
+ }
+
+ /* Increase prescale values starting from 0 to make the cycle count fit */
+ while(divider > 65535 && prescaler <= 1024)
+ {
+ prescaler <<= 2; /* 1, 4, 16, 64, 256, 1024 */
+ prescaler_bit++;
+ divider = cycles / prescaler;
+ }
+
+ old_irq = disable_irq_save();
+
+ __tcu_stop_counter(5);
+ if(start)
+ {
+ __tcu_disable_pwm_output(5);
+
+ __tcu_mask_half_match_irq(5);
+ __tcu_unmask_full_match_irq(5);
+
+ /* EXTAL clock = CFG_EXTAL (12Mhz in most targets) */
+ __tcu_select_extalclk(5);
+ }
+
+ REG_TCU_TCSR(5) = (REG_TCU_TCSR(5) & ~TCSR_PRESCALE_MASK) | (prescaler_bit << TCSR_PRESCALE_LSB);
+ REG_TCU_TCNT(5) = 0;
+ REG_TCU_TDHR(5) = 0;
+ REG_TCU_TDFR(5) = divider;
+
+ __tcu_clear_full_match_flag(5);
+
+ if(start)
+ {
+ system_enable_irq(IRQ_TCU1);
+ __tcu_start_counter(5);
+ }
+
+ restore_irq(old_irq);
+
+ return true;
+}
+
+bool timer_start(void)
+{
+ __tcu_start_counter(5);
+
+ return true;
+}
+
+void timer_stop(void)
+{
+ unsigned int old_irq = disable_irq_save();
+ __tcu_stop_counter(5);
+ restore_irq(old_irq);
+}
diff --git a/firmware/target/mips/ingenic_jz47xx/usb-jz4760.c b/firmware/target/mips/ingenic_jz47xx/usb-jz4760.c
new file mode 100644
index 0000000000..bc2158fb6f
--- /dev/null
+++ b/firmware/target/mips/ingenic_jz47xx/usb-jz4760.c
@@ -0,0 +1,870 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2016 by Roman Stolyarov
+ *
+ * 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 "config.h"
+/*#define LOGF_ENABLE*/
+#include "logf.h"
+#include "system.h"
+#include "usb_ch9.h"
+#include "usb_drv.h"
+#include "usb_core.h"
+#include "cpu.h"
+#include "thread.h"
+
+#define PIN_USB_DET (32*4+19)
+#define IRQ_USB_DET GPIO_IRQ(PIN_USB_DET)
+#define GPIO_USB_DET GPIO147
+
+#define PIN_USB_DRVVBUS (32*4+10)
+#define PIN_USB_OTG_ID (32*3+7)
+
+#define EP_BUF_LEFT(ep) ((ep)->length - (ep)->sent)
+#define EP_PTR(ep) ((void*)((unsigned int)(ep)->buf + (ep)->sent))
+#define EP_NUMBER(ep) (((int)(ep) - (int)&endpoints[0])/sizeof(struct usb_endpoint))
+#define EP_NUMBER2(ep) (EP_NUMBER((ep))/2)
+#define TOTAL_EP() (sizeof(endpoints)/sizeof(struct usb_endpoint))
+#define EP_IS_IN(ep) (EP_NUMBER((ep))%2)
+
+enum ep_type
+{
+ ep_control,
+ ep_bulk,
+ ep_interrupt,
+ ep_isochronous
+};
+
+struct usb_endpoint
+{
+ void *buf;
+ size_t length;
+ union
+ {
+ size_t sent;
+ size_t received;
+ };
+ bool busy;
+
+ const enum ep_type type;
+ const bool use_dma;
+
+ const long fifo_addr;
+ unsigned short fifo_size;
+
+ bool wait;
+ struct semaphore complete;
+};
+
+#define EP_INIT(_type, _fifo_addr, _fifo_size, _buf, _use_dma) \
+ { .type = (_type), .fifo_addr = (_fifo_addr), .fifo_size = (_fifo_size), \
+ .buf = (_buf), .use_dma = (_use_dma), .length = 0, .busy = false, .wait = false }
+
+static unsigned char ep0_rx_buf[64];
+static struct usb_endpoint endpoints[] =
+{
+ EP_INIT(ep_control, USB_FIFO_EP(0), 64, NULL, false),
+ EP_INIT(ep_control, USB_FIFO_EP(0), 64, &ep0_rx_buf, false),
+ EP_INIT(ep_bulk, USB_FIFO_EP(1), 512, NULL, false),
+ EP_INIT(ep_bulk, USB_FIFO_EP(1), 512, NULL, false),
+ EP_INIT(ep_interrupt, USB_FIFO_EP(2), 64, NULL, false),
+ EP_INIT(ep_interrupt, USB_FIFO_EP(2), 64, NULL, false),
+};
+
+static inline void select_endpoint(int ep)
+{
+ REG_USB_INDEX = ep;
+}
+
+static void readFIFO(struct usb_endpoint *ep, unsigned int size)
+{
+ logf("%s(EP%d, %d)", __func__, EP_NUMBER2(ep), size);
+
+ register unsigned char *ptr = (unsigned char*)EP_PTR(ep);
+ register unsigned int *ptr32 = (unsigned int*)ptr;
+ register unsigned int s = size >> 2;
+ register unsigned int x;
+
+ if(size > 0)
+ {
+ if( ((unsigned int)ptr & 3) == 0 )
+ {
+ while(s--)
+ *ptr32++ = REG32(ep->fifo_addr);
+
+ ptr = (unsigned char*)ptr32;
+ }
+ else
+ {
+ while(s--)
+ {
+ x = REG32(ep->fifo_addr);
+ *ptr++ = x & 0xFF; x >>= 8;
+ *ptr++ = x & 0xFF; x >>= 8;
+ *ptr++ = x & 0xFF; x >>= 8;
+ *ptr++ = x;
+ }
+ }
+
+ s = size & 3;
+ while(s--)
+ *ptr++ = REG8(ep->fifo_addr);
+ }
+}
+
+static void writeFIFO(struct usb_endpoint *ep, size_t size)
+{
+ logf("%s(EP%d, %d)", __func__, EP_NUMBER2(ep), size);
+
+ register unsigned int *d32 = (unsigned int *)EP_PTR(ep);
+ register size_t s = size >> 2;
+
+ if(size > 0)
+ {
+ while (s--)
+ REG32(ep->fifo_addr) = *d32++;
+
+ if( (s = size & 3) )
+ {
+ register unsigned char *d8 = (unsigned char *)d32;
+ while (s--)
+ REG8(ep->fifo_addr) = *d8++;
+ }
+ }
+}
+
+static void flushFIFO(struct usb_endpoint *ep)
+{
+ logf("%s(%d)", __func__, EP_NUMBER(ep));
+
+ switch (ep->type)
+ {
+ case ep_control:
+ break;
+
+ case ep_bulk:
+ case ep_interrupt:
+ case ep_isochronous:
+ if(EP_IS_IN(ep))
+ REG_USB_INCSR |= (USB_INCSR_FF | USB_INCSR_CDT);
+ else
+ REG_USB_OUTCSR |= (USB_OUTCSR_FF | USB_OUTCSR_CDT);
+ break;
+ }
+}
+
+static inline void ep_transfer_completed(struct usb_endpoint* ep)
+{
+ ep->sent = 0;
+ ep->length = 0;
+ ep->buf = NULL;
+ ep->busy = false;
+ if(ep->wait)
+ semaphore_release(&ep->complete);
+}
+
+static void EP0_send(void)
+{
+ struct usb_endpoint* ep = &endpoints[0];
+ unsigned int length;
+ unsigned char csr0;
+
+ select_endpoint(0);
+ csr0 = REG_USB_CSR0;
+
+ if(ep->sent == 0)
+ length = MIN(ep->length, ep->fifo_size);
+ else
+ length = MIN(EP_BUF_LEFT(ep), ep->fifo_size);
+
+ writeFIFO(ep, length);
+ ep->sent += length;
+
+ if(ep->sent >= ep->length)
+ {
+ REG_USB_CSR0 = (csr0 | USB_CSR0_INPKTRDY | USB_CSR0_DATAEND); /* Set data end! */
+ usb_core_transfer_complete(0, USB_DIR_IN, 0, ep->sent);
+ ep_transfer_completed(ep);
+ }
+ else
+ REG_USB_CSR0 = (csr0 | USB_CSR0_INPKTRDY);
+}
+
+static void EP0_handler(void)
+{
+ logf("%s()", __func__);
+
+ unsigned char csr0;
+ struct usb_endpoint *ep_send = &endpoints[0];
+ struct usb_endpoint *ep_recv = &endpoints[1];
+
+ /* Read CSR0 */
+ select_endpoint(0);
+ csr0 = REG_USB_CSR0;
+
+ /* Check for SentStall:
+ This bit is set when a STALL handshake is transmitted. The CPU should clear this bit.
+ */
+ if(csr0 & USB_CSR0_SENTSTALL)
+ {
+ REG_USB_CSR0 = csr0 & ~USB_CSR0_SENTSTALL;
+ return;
+ }
+
+ /* Check for SetupEnd:
+ This bit will be set when a control transaction ends before the DataEnd bit has been set.
+ An interrupt will be generated and the FIFO flushed at this time.
+ The bit is cleared by the CPU writing a 1 to the ServicedSetupEnd bit.
+ */
+ if(csr0 & USB_CSR0_SETUPEND)
+ {
+ REG_USB_CSR0 = csr0 | USB_CSR0_SVDSETUPEND;
+ return;
+ }
+
+ /* Call relevant routines for endpoint 0 state */
+ if(ep_send->busy)
+ EP0_send();
+ else if(csr0 & USB_CSR0_OUTPKTRDY) /* There is a packet in the fifo */
+ {
+ readFIFO(ep_recv, REG_USB_COUNT0);
+ REG_USB_CSR0 = csr0 | USB_CSR0_SVDOUTPKTRDY; /* clear OUTPKTRDY bit */
+ usb_core_control_request((struct usb_ctrlrequest*)ep_recv->buf);
+ }
+}
+
+static void EPIN_handler(unsigned int endpoint)
+{
+ struct usb_endpoint* ep = &endpoints[endpoint*2];
+ unsigned int length, csr;
+
+ select_endpoint(endpoint);
+ csr = REG_USB_INCSR;
+ logf("%s(%d): 0x%x", __func__, endpoint, csr);
+
+ if(!ep->busy)
+ {
+ logf("Entered EPIN handler without work!");
+ return;
+ }
+
+ if(csr & USB_INCSR_SENTSTALL)
+ {
+ REG_USB_INCSR = csr & ~USB_INCSR_SENTSTALL;
+ return;
+ }
+
+ if(ep->use_dma)
+ return;
+
+ if(csr & USB_INCSR_FFNOTEMPT)
+ {
+ logf("FIFO is not empty! 0x%x", csr);
+ return;
+ }
+
+ logf("EP%d: %d -> %d", endpoint, ep->sent, ep->length);
+
+ if(ep->sent == 0)
+ length = MIN(ep->length, ep->fifo_size);
+ else
+ length = MIN(EP_BUF_LEFT(ep), ep->fifo_size);
+
+ writeFIFO(ep, length);
+ REG_USB_INCSR = csr | USB_INCSR_INPKTRDY;
+ ep->sent += length;
+
+ if(ep->sent >= ep->length)
+ {
+ usb_core_transfer_complete(endpoint, USB_DIR_IN, 0, ep->sent);
+ ep_transfer_completed(ep);
+ logf("sent complete");
+ }
+}
+
+static void EPOUT_handler(unsigned int endpoint)
+{
+ struct usb_endpoint* ep = &endpoints[endpoint*2+1];
+ unsigned int size, csr;
+
+ if(!ep->busy)
+ {
+ logf("Entered EPOUT handler without work!");
+ return;
+ }
+
+ select_endpoint(endpoint);
+ while((csr = REG_USB_OUTCSR) & (USB_OUTCSR_SENTSTALL|USB_OUTCSR_OUTPKTRDY))
+ {
+ logf("%s(%d): 0x%x", __func__, endpoint, csr);
+ if(csr & USB_OUTCSR_SENTSTALL)
+ {
+ logf("stall sent, flushing fifo..");
+ flushFIFO(ep);
+ REG_USB_OUTCSR = csr & ~USB_OUTCSR_SENTSTALL;
+ return;
+ }
+
+ if(ep->use_dma)
+ return;
+
+ if(csr & USB_OUTCSR_OUTPKTRDY) /* There is a packet in the fifo */
+ {
+ size = REG_USB_OUTCOUNT;
+
+ readFIFO(ep, size);
+ ep->received += size;
+
+ /*if(csr & USB_OUTCSR_FFFULL)
+ csr &= ~USB_OUTCSR_FFFULL;*/
+
+ REG_USB_OUTCSR = csr & ~USB_OUTCSR_OUTPKTRDY;
+
+ logf("received: %d max length: %d", ep->received, ep->length);
+
+ if(size < ep->fifo_size || ep->received >= ep->length)
+ {
+ usb_core_transfer_complete(endpoint, USB_DIR_OUT, 0, ep->received);
+ ep_transfer_completed(ep);
+ logf("receive transfer_complete");
+ }
+ }
+ }
+}
+
+static void EPDMA_handler(int number)
+{
+ int endpoint = -1;
+ unsigned int size = 0;
+
+ if(number == USB_INTR_DMA_BULKIN)
+ endpoint = (REG_USB_CNTL(0) >> 4) & 0xF;
+ else if(number == USB_INTR_DMA_BULKOUT)
+ endpoint = (REG_USB_CNTL(1) >> 4) & 0xF;
+
+ struct usb_endpoint* ep = &endpoints[endpoint];
+ logf("DMA_BULK%d %d", number, endpoint);
+
+ if(number == USB_INTR_DMA_BULKIN)
+ size = (unsigned int)ep->buf - REG_USB_ADDR(0);
+ else if(number == USB_INTR_DMA_BULKOUT)
+ size = (unsigned int)ep->buf - REG_USB_ADDR(1);
+
+ if(number == USB_INTR_DMA_BULKOUT)
+ {
+ /* Disable DMA */
+ REG_USB_CNTL(1) = 0;
+
+ __dcache_invalidate_all();
+
+ select_endpoint(endpoint);
+ /* Read out last packet manually */
+ unsigned int lpack_size = REG_USB_OUTCOUNT;
+ if(lpack_size > 0)
+ {
+ ep->buf += ep->length - lpack_size;
+ readFIFO(ep, lpack_size);
+ REG_USB_OUTCSR &= ~USB_OUTCSR_OUTPKTRDY;
+ }
+ }
+ else if(number == USB_INTR_DMA_BULKIN && size % ep->fifo_size)
+ {
+ /* If the last packet is less than MAXP, set INPKTRDY manually */
+ REG_USB_INCSR |= USB_INCSR_INPKTRDY;
+ }
+
+ usb_core_transfer_complete(endpoint, EP_IS_IN(ep) ? USB_DIR_IN : USB_DIR_OUT,
+ 0, ep->length);
+ ep_transfer_completed(ep);
+}
+
+static void setup_endpoint(struct usb_endpoint *ep)
+{
+ int csr, csrh;
+
+ select_endpoint(EP_NUMBER2(ep));
+
+ ep->busy = false;
+ ep->wait = false;
+ ep->sent = 0;
+ ep->length = 0;
+
+ if(ep->type == ep_bulk)
+ {
+ if(REG_USB_POWER & USB_POWER_HSMODE)
+ ep->fifo_size = 512;
+ else
+ ep->fifo_size = 64;
+ }
+
+ if(EP_IS_IN(ep))
+ {
+ csr = (USB_INCSR_FF | USB_INCSR_CDT);
+ csrh = USB_INCSRH_MODE;
+
+ if(ep->use_dma)
+ csrh |= (USB_INCSRH_DMAREQENAB | USB_INCSRH_AUTOSET | USB_INCSRH_DMAREQMODE);
+
+ if(ep->type == ep_interrupt)
+ csrh |= USB_INCSRH_FRCDATATOG;
+
+ REG_USB_INMAXP = ep->fifo_size;
+ REG_USB_INCSR = csr;
+ REG_USB_INCSRH = csrh;
+ REG_USB_INTRINE |= USB_INTR_EP(EP_NUMBER2(ep));
+ }
+ else
+ {
+ csr = (USB_OUTCSR_FF | USB_OUTCSR_CDT);
+ csrh = 0;
+
+ if(ep->type == ep_interrupt)
+ csrh |= USB_OUTCSRH_DNYT;
+
+ if(ep->use_dma)
+ csrh |= (USB_OUTCSRH_DMAREQENAB | USB_OUTCSRH_AUTOCLR | USB_OUTCSRH_DMAREQMODE);
+
+ REG_USB_OUTMAXP = ep->fifo_size;
+ REG_USB_OUTCSR = csr;
+ REG_USB_OUTCSRH = csrh;
+ REG_USB_INTROUTE |= USB_INTR_EP(EP_NUMBER2(ep));
+ }
+}
+
+static void udc_reset(void)
+{
+ /* From the datasheet:
+
+ When a reset condition is detected on the USB, the controller performs the following actions:
+ * Sets FAddr to 0.
+ * Sets Index to 0.
+ * Flushes all endpoint FIFOs.
+ * Clears all control/status registers.
+ * Enables all endpoint interrupts.
+ * Generates a Reset interrupt.
+ */
+
+ logf("%s()", __func__);
+
+ unsigned int i;
+
+ REG_USB_FADDR = 0;
+ REG_USB_INDEX = 0;
+
+ /* Disable interrupts */
+ REG_USB_INTRINE = 0;
+ REG_USB_INTROUTE = 0;
+ REG_USB_INTRUSBE = 0;
+
+ /* Disable DMA */
+ REG_USB_CNTL(0) = 0;
+ REG_USB_CNTL(1) = 0;
+
+ /* High speed, softconnect */
+ REG_USB_POWER = (USB_POWER_SOFTCONN | USB_POWER_HSENAB);
+
+ /* Reset EP0 */
+ select_endpoint(0);
+ REG_USB_CSR0 = (USB_CSR0_SVDOUTPKTRDY | USB_CSR0_SVDSETUPEND | USB_CSR0_FLUSHFIFO);
+
+ /* Reset other endpoints */
+ for(i=2; i<TOTAL_EP(); i++)
+ setup_endpoint(&endpoints[i]);
+
+ /* Enable interrupts */
+ REG_USB_INTRINE |= USB_INTR_EP(0);
+ REG_USB_INTRUSBE |= USB_INTR_RESET;
+
+ usb_core_bus_reset();
+}
+
+/* Interrupt handler */
+void OTG(void)
+{
+ /* Read interrupt registers */
+ unsigned char intrUSB = REG_USB_INTRUSB & 0x07; /* Mask SOF */
+ unsigned short intrIn = REG_USB_INTRIN;
+ unsigned short intrOut = REG_USB_INTROUT;
+ unsigned char intrDMA = REG_USB_INTR;
+
+ logf("%x %x %x %x", intrUSB, intrIn, intrOut, intrDMA);
+
+ /* EPIN & EPOUT are all handled in DMA */
+ if(intrIn & USB_INTR_EP(0))
+ EP0_handler();
+ if(intrIn & USB_INTR_EP(1))
+ EPIN_handler(1);
+ if(intrIn & USB_INTR_EP(2))
+ EPIN_handler(2);
+ if(intrOut & USB_INTR_EP(1))
+ EPOUT_handler(1);
+ if(intrOut & USB_INTR_EP(2))
+ EPOUT_handler(2);
+ if(intrUSB & USB_INTR_RESET)
+ udc_reset();
+ if(intrUSB & USB_INTR_SUSPEND)
+ logf("USB suspend");
+ if(intrUSB & USB_INTR_RESUME)
+ logf("USB resume");
+ if(intrDMA & USB_INTR_DMA_BULKIN)
+ EPDMA_handler(USB_INTR_DMA_BULKIN);
+ if(intrDMA & USB_INTR_DMA_BULKOUT)
+ EPDMA_handler(USB_INTR_DMA_BULKOUT);
+}
+
+bool usb_drv_stalled(int endpoint, bool in)
+{
+ endpoint &= 0x7F;
+
+ logf("%s(%d, %s)", __func__, endpoint, in?"IN":"OUT");
+
+ select_endpoint(endpoint);
+
+ if(endpoint == EP_CONTROL)
+ return (REG_USB_CSR0 & USB_CSR0_SENDSTALL) != 0;
+ else
+ {
+ if(in)
+ return (REG_USB_INCSR & USB_INCSR_SENDSTALL) != 0;
+ else
+ return (REG_USB_OUTCSR & USB_OUTCSR_SENDSTALL) != 0;
+ }
+}
+
+void usb_drv_stall(int endpoint, bool stall, bool in)
+{
+ endpoint &= 0x7F;
+
+ logf("%s(%d,%s,%s)", __func__, endpoint, stall?"Y":"N", in?"IN":"OUT");
+
+ select_endpoint(endpoint);
+
+ if(endpoint == EP_CONTROL)
+ {
+ if(stall)
+ REG_USB_CSR0 |= USB_CSR0_SENDSTALL;
+ else
+ REG_USB_CSR0 &= ~USB_CSR0_SENDSTALL;
+ }
+ else
+ {
+ if(in)
+ {
+ if(stall)
+ REG_USB_INCSR |= USB_INCSR_SENDSTALL;
+ else
+ REG_USB_INCSR = (REG_USB_INCSR & ~USB_INCSR_SENDSTALL) | USB_INCSR_CDT;
+ }
+ else
+ {
+ if(stall)
+ REG_USB_OUTCSR |= USB_OUTCSR_SENDSTALL;
+ else
+ REG_USB_OUTCSR = (REG_USB_OUTCSR & ~USB_OUTCSR_SENDSTALL) | USB_OUTCSR_CDT;
+ }
+ }
+}
+
+int usb_detect(void)
+{
+ return (__gpio_get_pin(PIN_USB_DET) == 1)
+ ? USB_INSERTED : USB_EXTRACTED;
+}
+
+void usb_init_device(void)
+{
+ __gpio_clear_pin(PIN_USB_DRVVBUS);
+ __gpio_as_output(PIN_USB_DRVVBUS);
+
+ __gpio_as_input(PIN_USB_OTG_ID);
+ __gpio_as_input(PIN_USB_DET);
+
+ __gpio_disable_pull(PIN_USB_OTG_ID);
+ __gpio_disable_pull(PIN_USB_DET);
+
+#ifdef USB_STATUS_BY_EVENT
+ __gpio_as_irq_rise_edge(PIN_USB_DET);
+ system_enable_irq(IRQ_USB_DET);
+#endif
+
+ system_enable_irq(IRQ_OTG);
+
+ for(unsigned i=0; i<TOTAL_EP(); i++)
+ semaphore_init(&endpoints[i].complete, 1, 0);
+}
+
+#ifdef USB_STATUS_BY_EVENT
+static int usb_oneshot_callback(struct timeout *tmo)
+{
+ (void)tmo;
+ int state = usb_detect();
+
+ /* This is called only if the state was stable for HZ/16 - check state
+ * and post appropriate event. */
+ usb_status_event(state);
+
+ if(state == USB_EXTRACTED)
+ __gpio_as_irq_rise_edge(PIN_USB_DET);
+ else
+ __gpio_as_irq_fall_edge(PIN_USB_DET);
+
+ return 0;
+}
+
+void GPIO_USB_DET(void)
+{
+ static struct timeout usb_oneshot;
+ timeout_register(&usb_oneshot, usb_oneshot_callback, (HZ/16), 0);
+}
+#endif
+
+void usb_enable(bool on)
+{
+ if(on)
+ usb_core_init();
+ else
+ usb_core_exit();
+}
+
+void usb_attach(void)
+{
+ usb_enable(true);
+}
+
+void usb_drv_init(void)
+{
+ logf("%s()", __func__);
+
+ /* Dis- and reconnect from USB */
+ REG_USB_POWER &= ~USB_POWER_SOFTCONN;
+ mdelay(20);
+ REG_USB_POWER |= USB_POWER_SOFTCONN;
+ mdelay(20);
+
+ udc_reset();
+}
+
+void usb_drv_exit(void)
+{
+ logf("%s()", __func__);
+
+ REG_USB_FADDR = 0;
+ REG_USB_INDEX = 0;
+
+ /* Disable interrupts */
+ REG_USB_INTRINE = 0;
+ REG_USB_INTROUTE = 0;
+ REG_USB_INTRUSBE = 0;
+
+ /* Disable DMA */
+ REG_USB_CNTL(0) = 0;
+ REG_USB_CNTL(1) = 0;
+
+ /* Disconnect from USB */
+ REG_USB_POWER &= ~USB_POWER_SOFTCONN;
+}
+
+void usb_drv_set_address(int address)
+{
+ logf("%s(%d)", __func__, address);
+
+ REG_USB_FADDR = address;
+}
+
+static void usb_drv_send_internal(struct usb_endpoint* ep, void* ptr, int length, bool blocking)
+{
+ if(ep->type == ep_control && ptr == NULL && length == 0)
+ return; /* ACK request, handled in the ISR */
+
+ int flags = disable_irq_save();
+
+ ep->buf = ptr;
+ ep->sent = 0;
+ ep->length = length;
+ ep->busy = true;
+ if(blocking)
+ ep->wait = true;
+
+ if(ep->type == ep_control)
+ {
+ EP0_send();
+ }
+ else
+ {
+ if(ep->use_dma)
+ {
+ //dma_cache_wback_inv((unsigned long)ptr, length);
+ __dcache_writeback_all();
+ REG_USB_ADDR(0) = PHYSADDR((unsigned long)ptr);
+ REG_USB_COUNT(0) = length;
+ REG_USB_CNTL(0) = (USB_CNTL_INTR_EN | USB_CNTL_MODE_1 |
+ USB_CNTL_DIR_IN | USB_CNTL_ENA |
+ USB_CNTL_EP(EP_NUMBER2(ep)) | USB_CNTL_BURST_16);
+ }
+ else
+ EPIN_handler(EP_NUMBER2(ep));
+ }
+
+ restore_irq(flags);
+
+ if(blocking)
+ {
+ semaphore_wait(&ep->complete, TIMEOUT_BLOCK);
+ ep->wait = false;
+ }
+}
+
+int usb_drv_send_nonblocking(int endpoint, void* ptr, int length)
+{
+ logf("%s(%d, 0x%x, %d)", __func__, endpoint, (int)ptr, length);
+
+ usb_drv_send_internal(&endpoints[(endpoint & 0x7F)*2], ptr, length, false);
+
+ return 0;
+}
+
+int usb_drv_send(int endpoint, void* ptr, int length)
+{
+ logf("%s(%d, 0x%x, %d)", __func__, endpoint, (int)ptr, length);
+
+ usb_drv_send_internal(&endpoints[(endpoint & 0x7F)*2], ptr, length, true);
+
+ return 0;
+}
+
+int usb_drv_recv(int endpoint, void* ptr, int length)
+{
+ int flags;
+ struct usb_endpoint *ep;
+ endpoint &= 0x7F;
+
+ logf("%s(%d, 0x%x, %d)", __func__, endpoint, (int)ptr, length);
+
+ if(endpoint == EP_CONTROL)
+ return 0; /* all EP0 OUT transactions are handled within the ISR */
+ else
+ {
+ flags = disable_irq_save();
+ ep = &endpoints[endpoint*2+1];
+
+ ep->buf = ptr;
+ ep->received = 0;
+ ep->length = length;
+ ep->busy = true;
+ if(ep->use_dma)
+ {
+ //dma_cache_wback_inv((unsigned long)ptr, length);
+ __dcache_writeback_all();
+ REG_USB_ADDR(1) = PHYSADDR((unsigned long)ptr);
+ REG_USB_COUNT(1) = length;
+ REG_USB_CNTL(1) = (USB_CNTL_INTR_EN | USB_CNTL_MODE_1 |
+ USB_CNTL_ENA | USB_CNTL_EP(endpoint) |
+ USB_CNTL_BURST_16);
+ }
+ else
+ EPOUT_handler(endpoint);
+
+ restore_irq(flags);
+ return 0;
+ }
+}
+
+void usb_drv_set_test_mode(int mode)
+{
+ logf("%s(%d)", __func__, mode);
+
+ switch(mode)
+ {
+ case 0:
+ REG_USB_TESTMODE &= ~USB_TEST_ALL;
+ break;
+ case 1:
+ REG_USB_TESTMODE |= USB_TEST_J;
+ break;
+ case 2:
+ REG_USB_TESTMODE |= USB_TEST_K;
+ break;
+ case 3:
+ REG_USB_TESTMODE |= USB_TEST_SE0NAK;
+ break;
+ case 4:
+ REG_USB_TESTMODE |= USB_TEST_PACKET;
+ break;
+ }
+}
+
+int usb_drv_port_speed(void)
+{
+ return (REG_USB_POWER & USB_POWER_HSMODE) ? 1 : 0;
+}
+
+void usb_drv_cancel_all_transfers(void)
+{
+ logf("%s()", __func__);
+
+ unsigned int i, flags;
+ flags = disable_irq_save();
+
+ for(i=0; i<TOTAL_EP(); i++)
+ {
+ if(i != 1) /* ep0 out needs special handling */
+ endpoints[i].buf = NULL;
+
+ endpoints[i].sent = 0;
+ endpoints[i].length = 0;
+
+ select_endpoint(i/2);
+ flushFIFO(&endpoints[i]);
+ }
+ restore_irq(flags);
+}
+
+
+void usb_drv_release_endpoint(int ep)
+{
+ (void)ep;
+ logf("%s(%d, %s)", __func__, (ep & 0x7F), (ep >> 7) ? "IN" : "OUT");
+}
+
+int usb_drv_request_endpoint(int type, int dir)
+{
+ logf("%s(%d, %s)", __func__, type, (dir == USB_DIR_IN) ? "IN" : "OUT");
+
+ dir &= USB_ENDPOINT_DIR_MASK;
+ type &= USB_ENDPOINT_XFERTYPE_MASK;
+
+ /* There are only 3+2 endpoints, so hardcode this ... */
+ switch(type)
+ {
+ case USB_ENDPOINT_XFER_BULK:
+ if(dir == USB_DIR_IN)
+ return (1 | USB_DIR_IN);
+ else
+ return (1 | USB_DIR_OUT);
+
+ case USB_ENDPOINT_XFER_INT:
+ if(dir == USB_DIR_IN)
+ return (2 | USB_DIR_IN);
+ else
+ return (2 | USB_DIR_OUT);
+
+ default:
+ return -1;
+ }
+}
diff --git a/firmware/target/mips/ingenic_jz47xx/xdebug.h b/firmware/target/mips/ingenic_jz47xx/xdebug.h
new file mode 100644
index 0000000000..2f348e266b
--- /dev/null
+++ b/firmware/target/mips/ingenic_jz47xx/xdebug.h
@@ -0,0 +1,31 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2016 by Roman Stolyarov
+ *
+ * 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 __XDEBUG_H_
+#define __XDEBUG_H_
+
+void serial_puts(const char *s);
+void serial_putsf(const char *format, ...);
+void serial_put_hex(unsigned int d);
+void serial_put_dec(unsigned int d);
+void serial_dump_data(unsigned char* data, int len);
+
+#endif /* __XDEBUG_H_ */
diff --git a/firmware/target/mips/ingenic_jz47xx/xduoo_x3/adc-target.h b/firmware/target/mips/ingenic_jz47xx/xduoo_x3/adc-target.h
new file mode 100644
index 0000000000..c47406ca21
--- /dev/null
+++ b/firmware/target/mips/ingenic_jz47xx/xduoo_x3/adc-target.h
@@ -0,0 +1,28 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2016 by Roman Stolyarov
+ *
+ * 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 _ADC_TARGET_H_
+#define _ADC_TARGET_H_
+
+#define NUM_ADC_CHANNELS 4
+
+#define ADC_BUTTONS 0
+
+#endif /* _ADC_TARGET_H_ */
diff --git a/firmware/target/mips/ingenic_jz47xx/xduoo_x3/ata-sd-target.h b/firmware/target/mips/ingenic_jz47xx/xduoo_x3/ata-sd-target.h
new file mode 100644
index 0000000000..bb2cced15b
--- /dev/null
+++ b/firmware/target/mips/ingenic_jz47xx/xduoo_x3/ata-sd-target.h
@@ -0,0 +1,48 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2016 by Roman Stolyarov
+ *
+ * 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 ATA_SD_TARGET_H
+#define ATA_SD_TARGET_H
+
+#include "cpu.h"
+#include "system.h"
+
+#define PIN_SD1_CD (32*0+29) /* Pin to check card insertion */
+#define IRQ_SD1_CD GPIO_IRQ(PIN_SD1_CD)
+#define GPIO_SD1_CD GPIO29
+
+#define PIN_SD2_CD (32*0+28) /* Pin to check card insertion */
+#define IRQ_SD2_CD GPIO_IRQ(PIN_SD2_CD)
+#define GPIO_SD2_CD GPIO28
+
+static inline void sd_init_gpio(void)
+{
+ __gpio_as_msc1_pd_4bit();
+ __gpio_as_msc2_pb_4bit();
+
+ __gpio_as_input(PIN_SD1_CD);
+ __gpio_as_input(PIN_SD2_CD);
+
+ __gpio_disable_pull(PIN_SD1_CD);
+ __gpio_disable_pull(PIN_SD2_CD);
+}
+
+#endif
diff --git a/firmware/target/mips/ingenic_jz47xx/xduoo_x3/backlight-xduoo_x3.c b/firmware/target/mips/ingenic_jz47xx/xduoo_x3/backlight-xduoo_x3.c
new file mode 100644
index 0000000000..3f00b0b67d
--- /dev/null
+++ b/firmware/target/mips/ingenic_jz47xx/xduoo_x3/backlight-xduoo_x3.c
@@ -0,0 +1,50 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2016 by Roman Stolyarov
+ *
+ * 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 "config.h"
+#include "cpu.h"
+#include "backlight-target.h"
+#include "lcd.h"
+
+bool backlight_hw_init(void)
+{
+ return true;
+}
+
+void backlight_hw_on(void)
+{
+ lcd_enable(true);
+}
+
+void backlight_hw_off(void)
+{
+ lcd_enable(false);
+}
+
+void backlight_hw_brightness(int brightness)
+{
+ lcd_set_contrast(brightness*16-1);
+}
+
+void lcd_sleep(void)
+{
+ backlight_hw_off();
+}
diff --git a/firmware/target/mips/ingenic_jz47xx/xduoo_x3/button-target.h b/firmware/target/mips/ingenic_jz47xx/xduoo_x3/button-target.h
new file mode 100644
index 0000000000..2dd94b14bc
--- /dev/null
+++ b/firmware/target/mips/ingenic_jz47xx/xduoo_x3/button-target.h
@@ -0,0 +1,46 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2016 by Roman Stolyarov
+ *
+ * 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
+
+#define HAS_BUTTON_HOLD
+
+/* Main unit's buttons */
+#define BUTTON_POWER 0x00000001
+#define BUTTON_HOME 0x00000002
+#define BUTTON_OPTION 0x00000004
+#define BUTTON_PREV 0x00000008
+#define BUTTON_NEXT 0x00000010
+#define BUTTON_PLAY 0x00000020
+#define BUTTON_VOL_UP 0x00000040
+#define BUTTON_VOL_DOWN 0x00000080
+
+#define BUTTON_LEFT 0
+#define BUTTON_RIGHT 0
+
+#define BUTTON_MAIN (BUTTON_POWER | BUTTON_HOME | BUTTON_OPTION | BUTTON_PREV | \
+ BUTTON_NEXT | BUTTON_PLAY | BUTTON_VOL_UP | BUTTON_VOL_DOWN)
+
+/* Software power-off */
+#define POWEROFF_BUTTON BUTTON_POWER
+#define POWEROFF_COUNT 10
+
+#endif /* BUTTON_TARGET_H */
diff --git a/firmware/target/mips/ingenic_jz47xx/xduoo_x3/lcd-xduoo_x3.c b/firmware/target/mips/ingenic_jz47xx/xduoo_x3/lcd-xduoo_x3.c
new file mode 100644
index 0000000000..89251b727d
--- /dev/null
+++ b/firmware/target/mips/ingenic_jz47xx/xduoo_x3/lcd-xduoo_x3.c
@@ -0,0 +1,420 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2016 by Roman Stolyarov
+ *
+ * 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 "config.h"
+
+#include "lcd.h"
+#include "system.h"
+#include "cpu.h"
+#include "string.h"
+
+/* LCD pins */
+#define PIN_BL_EN (32*4+0)
+
+#define PIN_LCD_D0 (32*2+2)
+#define PIN_LCD_D1 (32*2+3)
+#define PIN_LCD_D2 (32*2+4)
+#define PIN_LCD_D3 (32*2+5)
+#define PIN_LCD_D4 (32*2+6)
+#define PIN_LCD_D5 (32*2+7)
+#define PIN_LCD_D6 (32*2+12)
+#define PIN_LCD_D7 (32*2+13)
+
+#define PIN_LCD_RD (32*2+8)
+#define PIN_LCD_DC (32*2+9)
+#define PIN_LCD_CS (32*2+14)
+#define PIN_LCD_RES (32*2+18)
+#define PIN_LCD_WR (32*2+19)
+
+/* LCD setup codes */
+#define LCD_SET_LOWER_COLUMN_ADDRESS ((char)0x00)
+#define LCD_SET_HIGHER_COLUMN_ADDRESS ((char)0x10)
+#define LCD_SET_DISPLAY_START_LINE ((char)0x40)
+#define LCD_SET_CONTRAST_CONTROL_REGISTER ((char)0x81)
+#define LCD_SET_CHARGE_PUMP ((char)0x8D)
+#define LCD_SET_SEGMENT_REMAP ((char)0xA0)
+#define LCD_SET_SEGMENT_REMAP_INV ((char)0xA1)
+#define LCD_SET_ENTIRE_DISPLAY_OFF ((char)0xA4)
+#define LCD_SET_ENTIRE_DISPLAY_ON ((char)0xA5)
+#define LCD_SET_NORMAL_DISPLAY ((char)0xA6)
+#define LCD_SET_REVERSE_DISPLAY ((char)0xA7)
+#define LCD_SET_MULTIPLEX_RATIO ((char)0xA8)
+#define LCD_SET_DC_DC ((char)0xAD)
+#define LCD_SET_DISPLAY_OFF ((char)0xAE)
+#define LCD_SET_DISPLAY_ON ((char)0xAF)
+#define LCD_SET_PAGE_ADDRESS ((char)0xB0)
+#define LCD_SET_COM_OUTPUT_SCAN_DIRECTION ((char)0xC0)
+#define LCD_SET_COM_OUTPUT_SCAN_DIRECTION_INV ((char)0xC8)
+#define LCD_SET_DISPLAY_OFFSET ((char)0xD3)
+#define LCD_SET_DISPLAY_CLOCK_AND_OSC_FREQ ((char)0xD5)
+#define LCD_SET_VCOM_HW_CONFIGURATION ((char)0xDA)
+#define LCD_SET_VCOM_DESELECT_LEVEL ((char)0xDB)
+#define LCD_SET_PRECHARGE_PERIOD ((char)0xD9)
+#define LCD_NOP ((char)0xE3)
+
+/* LCD command codes */
+#define LCD_CNTL_CONTRAST 0x81 /* Contrast */
+#define LCD_CNTL_OUTSCAN 0xc8 /* Output scan direction */
+#define LCD_CNTL_SEGREMAP 0xa1 /* Segment remap */
+#define LCD_CNTL_DISPON 0xaf /* Display on */
+
+#define LCD_CNTL_PAGE 0xb0 /* Page address */
+#define LCD_CNTL_HIGHCOL 0x10 /* Upper column address */
+#define LCD_CNTL_LOWCOL 0x00 /* Lower column address */
+
+#define LCD_COL_OFFSET 2 /* column offset */
+
+static inline void bitdelay(void)
+{
+ unsigned int i = 15;
+ __asm__ __volatile__ (
+ ".set noreorder \n"
+ "1: \n"
+ "bne %0, $0, 1b \n"
+ "addi %0, %0, -1 \n"
+ ".set reorder \n"
+ : "=r" (i)
+ : "0" (i)
+ );
+}
+
+void lcd_hw_init(void)
+{
+ REG_GPIO_PXFUNC(2) = 0x000C73FC; /* D0-D7 RD DC CS RES WR */
+ REG_GPIO_PXSELC(2) = 0x000C73FC;
+ REG_GPIO_PXDIRS(2) = 0x000C73FC;
+ REG_GPIO_PXDATS(2) = 0x000C73FC;
+ __gpio_clear_pin(PIN_BL_EN);
+ __gpio_as_output(PIN_BL_EN);
+ __gpio_clear_pin(PIN_LCD_RES);
+ udelay(1);
+ __gpio_set_pin(PIN_LCD_RES);
+ __gpio_clear_pin(PIN_LCD_CS);
+}
+
+void lcd_write_command(int byte)
+{
+ __gpio_clear_pin(PIN_LCD_DC);
+ REG_GPIO_PXDATC(2) = 0x000030FC;
+ REG_GPIO_PXDATS(2) = ((byte & 0xC0) << 6) | ((byte & 0x3F) << 2);
+ __gpio_clear_pin(PIN_LCD_WR);
+ bitdelay();
+ __gpio_set_pin(PIN_LCD_WR);
+ bitdelay();
+}
+
+void lcd_write_data(const fb_data* p_bytes, int count)
+{
+ __gpio_set_pin(PIN_LCD_DC);
+ while (count--)
+ {
+ REG_GPIO_PXDATC(2) = 0x000030FC;
+ REG_GPIO_PXDATS(2) = ((*p_bytes & 0xC0) << 6) | ((*p_bytes & 0x3F) << 2);
+ p_bytes++;
+ __gpio_clear_pin(PIN_LCD_WR);
+ bitdelay();
+ __gpio_set_pin(PIN_LCD_WR);
+ bitdelay();
+ }
+}
+
+void lcd_enable_power(bool onoff)
+{
+ if (onoff)
+ __gpio_set_pin(PIN_BL_EN);
+ else
+ __gpio_clear_pin(PIN_BL_EN);
+}
+
+/** globals **/
+
+static bool display_on = false; /* used by lcd_enable */
+
+/*** hardware configuration ***/
+
+void lcd_set_contrast(int val)
+{
+ lcd_write_command(LCD_CNTL_CONTRAST);
+ lcd_write_command(val);
+}
+
+void lcd_set_invert_display(bool yesno)
+{
+ if (yesno)
+ lcd_write_command(LCD_SET_REVERSE_DISPLAY);
+ else
+ lcd_write_command(LCD_SET_NORMAL_DISPLAY);
+}
+
+/* turn the display upside down (call lcd_update() afterwards) */
+void lcd_set_flip(bool yesno)
+{
+ if (yesno)
+ {
+ lcd_write_command(LCD_SET_SEGMENT_REMAP);
+ lcd_write_command(LCD_SET_COM_OUTPUT_SCAN_DIRECTION);
+ }
+ else
+ {
+ lcd_write_command(LCD_SET_SEGMENT_REMAP_INV);
+ lcd_write_command(LCD_SET_COM_OUTPUT_SCAN_DIRECTION_INV);
+ }
+}
+
+#ifdef HAVE_LCD_ENABLE
+void lcd_enable(bool enable)
+{
+ if(display_on == enable)
+ return;
+
+ if( (display_on = enable) ) /* simple '=' is not a typo ! */
+ {
+ lcd_enable_power(enable);
+ lcd_write_command(LCD_SET_DISPLAY_ON);
+ send_event(LCD_EVENT_ACTIVATION, NULL);
+ }
+ else
+ {
+ lcd_write_command(LCD_SET_DISPLAY_OFF);
+ lcd_enable_power(enable);
+ }
+}
+
+bool lcd_active(void)
+{
+ return display_on;
+}
+#endif
+
+/* LCD init, largely based on what OF does */
+void lcd_init_device(void)
+{
+ int i;
+
+ lcd_hw_init();
+
+ /* Set display off */
+ lcd_write_command(LCD_SET_DISPLAY_OFF);
+
+ /* Set display clock and oscillator frequency */
+ lcd_write_command(LCD_SET_DISPLAY_CLOCK_AND_OSC_FREQ);
+ lcd_write_command(0x80);
+
+ /* Set multiplex ratio*/
+ lcd_write_command(LCD_SET_MULTIPLEX_RATIO);
+ lcd_write_command(0x3F);
+
+ /* Set display offset */
+ lcd_write_command(LCD_SET_DISPLAY_OFFSET);
+ lcd_write_command(0x00);
+
+ /* Set starting line as 0 */
+ lcd_write_command(LCD_SET_DISPLAY_START_LINE);
+
+ /* Set charge pump */
+ lcd_write_command(LCD_SET_CHARGE_PUMP);
+ lcd_write_command(0x14); /* VCC Generated by Internal DC/DC Circuit */
+
+ /* Column 131 is remapped to SEG0 */
+ lcd_write_command(LCD_SET_SEGMENT_REMAP_INV);
+
+ /* Invert COM scan direction (N-1 to 0) */
+ lcd_write_command(LCD_SET_COM_OUTPUT_SCAN_DIRECTION_INV);
+
+ /* Set COM hardware configuration */
+ lcd_write_command(LCD_SET_VCOM_HW_CONFIGURATION);
+ lcd_write_command(0x12);
+
+ /* Set contrast control */
+ lcd_write_command(LCD_SET_CONTRAST_CONTROL_REGISTER);
+ lcd_write_command(0xCF); /* VCC Generated by Internal DC/DC Circuit */
+
+ /* Set pre-charge period */
+ lcd_write_command(LCD_SET_PRECHARGE_PERIOD);
+ lcd_write_command(0xF1); /* VCC Generated by Internal DC/DC Circuit */
+
+ /* Set VCOM deselect level */
+ lcd_write_command(LCD_SET_VCOM_DESELECT_LEVEL);
+ lcd_write_command(0x40);
+
+ /* Set normal display mode (not every pixel ON) */
+ lcd_write_command(LCD_SET_ENTIRE_DISPLAY_OFF);
+
+ /* Set normal display mode (not inverted) */
+ lcd_write_command(LCD_SET_NORMAL_DISPLAY);
+
+ fb_data p_bytes[LCD_WIDTH + 2 * LCD_COL_OFFSET];
+ memset(p_bytes, 0, sizeof(p_bytes)); /* fills with 0 : pixel off */
+ for(i = 0; i < 8; i++)
+ {
+ lcd_write_command (LCD_SET_PAGE_ADDRESS | (i /*& 0xf*/));
+ lcd_write_data(p_bytes, LCD_WIDTH + 2 * LCD_COL_OFFSET);
+ }
+
+ lcd_enable(true);
+
+ lcd_update();
+}
+
+/*** Update functions ***/
+
+/* Performance function that works with an external buffer
+ note that by and bheight are in 8-pixel units! */
+void lcd_blit_mono(const unsigned char *data, int x, int by, int width,
+ int bheight, int stride)
+{
+ if(!display_on)
+ return;
+
+ /* Copy display bitmap to hardware */
+ while (bheight--)
+ {
+ lcd_write_command (LCD_CNTL_PAGE | (by++ & 0xf));
+ lcd_write_command (LCD_CNTL_HIGHCOL | (((x+LCD_COL_OFFSET)>>4) & 0xf));
+ lcd_write_command (LCD_CNTL_LOWCOL | ((x+LCD_COL_OFFSET) & 0xf));
+
+ lcd_write_data(data, width);
+ data += stride;
+ }
+}
+
+
+#ifndef BOOTLOADER
+/* Helper function for lcd_grey_phase_blit(). */
+void lcd_grey_data(unsigned char *values, unsigned char *phases, int count) ICODE_ATTR;
+void lcd_grey_data(unsigned char *values, unsigned char *phases, int count)
+{
+ unsigned char a, b, c, d;
+
+ __gpio_set_pin(PIN_LCD_DC);
+ while(count--)
+ {
+ c = 0;
+ d = 8;
+ while(d--)
+ {
+ a = *phases;
+ b = *values++;
+ b += a & ~0x80;
+ *phases++ = b;
+ c <<= 1;
+ c |= a >> 7;
+ }
+ REG_GPIO_PXDATC(2) = 0x000030FC;
+ REG_GPIO_PXDATS(2) = ((c & 0xC0) << 6) | ((c & 0x3F) << 2);
+ __gpio_clear_pin(PIN_LCD_WR);
+ bitdelay();
+ __gpio_set_pin(PIN_LCD_WR);
+ bitdelay();
+ }
+}
+
+/* Performance function that works with an external buffer
+ note that by and bheight are in 8-pixel units! */
+void lcd_blit_grey_phase(unsigned char *values, unsigned char *phases,
+ int x, int by, int width, int bheight, int stride)
+{
+ if(!display_on)
+ return;
+
+ stride <<= 3; /* 8 pixels per block */
+ /* Copy display bitmap to hardware */
+ while (bheight--)
+ {
+ lcd_write_command (LCD_CNTL_PAGE | (by++ & 0xf));
+ lcd_write_command (LCD_CNTL_HIGHCOL | (((x+LCD_COL_OFFSET)>>4) & 0xf));
+ lcd_write_command (LCD_CNTL_LOWCOL | ((x+LCD_COL_OFFSET) & 0xf));
+
+ lcd_grey_data(values, phases, width);
+
+ values += stride;
+ phases += stride;
+ }
+}
+#endif
+
+/* Update the display.
+ This must be called after all other LCD functions that change the display. */
+void lcd_update(void) ICODE_ATTR;
+void lcd_update(void)
+{
+ int y;
+
+ if(!display_on)
+ return;
+
+ /* Copy display bitmap to hardware */
+ for (y = 0; y < LCD_FBHEIGHT; y++)
+ {
+ lcd_write_command (LCD_CNTL_PAGE | (y & 0xf));
+ lcd_write_command (LCD_CNTL_HIGHCOL | ((LCD_COL_OFFSET >> 4) & 0xf));
+ lcd_write_command (LCD_CNTL_LOWCOL | (LCD_COL_OFFSET & 0xf));
+
+ lcd_write_data (FBADDR(0, y), LCD_WIDTH);
+ }
+}
+
+/* Update a fraction of the display. */
+void lcd_update_rect(int, int, int, int) ICODE_ATTR;
+void lcd_update_rect(int x, int y, int width, int height)
+{
+ int ymax;
+
+ if(!display_on)
+ return;
+
+ /* The Y coordinates have to work on even 8 pixel rows */
+ if (x < 0)
+ {
+ width += x;
+ x = 0;
+ }
+
+ if (x + width > LCD_WIDTH)
+ width = LCD_WIDTH - x;
+
+ if (width <= 0)
+ return; /* nothing left to do, 0 is harmful to lcd_write_data() */
+
+ if (y < 0)
+ {
+ height += y;
+ y = 0;
+ }
+
+ if (y + height > LCD_HEIGHT)
+ height = LCD_HEIGHT - y;
+
+ if (height <= 0)
+ return; /* nothing left to do */
+
+ ymax = (y + height-1) >> 3;
+ y >>= 3;
+
+ /* Copy specified rectange bitmap to hardware */
+ for (; y <= ymax; y++)
+ {
+ lcd_write_command (LCD_CNTL_PAGE | (y & 0xf));
+ lcd_write_command (LCD_CNTL_HIGHCOL | (((x+LCD_COL_OFFSET) >> 4) & 0xf));
+ lcd_write_command (LCD_CNTL_LOWCOL | ((x+LCD_COL_OFFSET) & 0xf));
+
+ lcd_write_data (FBADDR(x,y), width);
+ }
+}
diff --git a/firmware/target/mips/ingenic_jz47xx/xduoo_x3/power-xduoo_x3.c b/firmware/target/mips/ingenic_jz47xx/xduoo_x3/power-xduoo_x3.c
new file mode 100644
index 0000000000..9ae602ba56
--- /dev/null
+++ b/firmware/target/mips/ingenic_jz47xx/xduoo_x3/power-xduoo_x3.c
@@ -0,0 +1,46 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2016 by Roman Stolyarov
+ *
+ * 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 "config.h"
+#include "power.h"
+#include "cpu.h"
+
+#define CHARGE_STAT_GPIO (32*1+6) /* STAT port */
+
+/* Detect which power sources are present. */
+unsigned int power_input_status(void)
+{
+ if(!__gpio_get_pin(CHARGE_STAT_GPIO))
+ return POWER_INPUT_USB_CHARGER;
+
+ return POWER_INPUT_NONE;
+}
+
+void power_init(void)
+{
+ __gpio_as_input(CHARGE_STAT_GPIO);
+ __gpio_disable_pull(CHARGE_STAT_GPIO);
+}
+
+bool charging_state(void)
+{
+ return (power_input_status() == POWER_INPUT_USB_CHARGER);
+}
diff --git a/firmware/target/mips/ingenic_jz47xx/xduoo_x3/sadc-xduoo_x3.c b/firmware/target/mips/ingenic_jz47xx/xduoo_x3/sadc-xduoo_x3.c
new file mode 100644
index 0000000000..d4a3548305
--- /dev/null
+++ b/firmware/target/mips/ingenic_jz47xx/xduoo_x3/sadc-xduoo_x3.c
@@ -0,0 +1,214 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2016 by Roman Stolyarov
+ *
+ * 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 "config.h"
+#include "system.h"
+#include "cpu.h"
+#include "button.h"
+#include "button-target.h"
+#include "powermgmt.h"
+#include "kernel.h"
+#include "backlight.h"
+#include "logf.h"
+#include "adc.h"
+
+#define PIN_BTN_POWER (32*0+30)
+#define PIN_BTN_HOLD (32*1+15)
+
+#define PIN_KEY_INT (32*4+13)
+#define KEY_INT_IRQ GPIO141
+
+#define PIN_CHARGE_CON (32*1+7)
+
+#define PIN_PH_DECT (32*1+11)
+#define IRQ_PH_DECT GPIO_IRQ(PIN_PH_DECT)
+#define GPIO_PH_DECT GPIO43
+
+#define PIN_LO_DECT (32*1+12)
+#define IRQ_LO_DECT GPIO_IRQ(PIN_LO_DECT)
+#define GPIO_LO_DECT GPIO44
+
+static volatile unsigned short bat_val,key_val;
+
+bool headphones_inserted(void)
+{
+ return (__gpio_get_pin(PIN_PH_DECT) != 0);
+}
+
+void button_init_device(void)
+{
+ key_val = 0xfff;
+
+ __gpio_as_input(PIN_BTN_POWER);
+ __gpio_as_input(PIN_BTN_HOLD);
+
+ __gpio_disable_pull(PIN_BTN_POWER);
+ __gpio_disable_pull(PIN_BTN_HOLD);
+
+ __gpio_as_irq_fall_edge(PIN_KEY_INT);
+ system_enable_irq(GPIO_IRQ(PIN_KEY_INT));
+
+ __gpio_set_pin(PIN_CHARGE_CON); /* 0.7 A */
+ __gpio_as_output(PIN_CHARGE_CON);
+
+ __gpio_as_input(PIN_LO_DECT);
+ __gpio_as_input(PIN_PH_DECT);
+
+ __gpio_disable_pull(PIN_LO_DECT);
+ __gpio_disable_pull(PIN_PH_DECT);
+}
+
+bool button_hold(void)
+{
+ return (__gpio_get_pin(PIN_BTN_HOLD) ? true : false);
+}
+
+int button_read_device(void)
+{
+ static bool hold_button = false;
+ bool hold_button_old;
+
+ hold_button_old = hold_button;
+ hold_button = (__gpio_get_pin(PIN_BTN_HOLD) ? true : false);
+
+ int btn = BUTTON_NONE;
+ bool gpio_btn = (__gpio_get_pin(PIN_BTN_POWER) ? false : true);
+
+ REG_SADC_ADCFG = ADCFG_VBAT_SEL + ADCFG_CMD_AUX(1);
+ REG_SADC_ADENA = ADENA_VBATEN + ADENA_AUXEN;
+
+#ifndef BOOTLOADER
+ if (hold_button != hold_button_old) {
+ backlight_hold_changed(hold_button);
+ }
+ if (hold_button) {
+ return BUTTON_NONE;
+ }
+#endif
+
+ if (gpio_btn)
+ btn |= BUTTON_POWER;
+
+ if (key_val < 261)
+ btn |= BUTTON_VOL_UP;
+ else
+ if (key_val < 653)
+ btn |= BUTTON_VOL_DOWN;
+ else
+ if (key_val < 1101)
+ btn |= BUTTON_PREV;
+ else
+ if (key_val < 1498)
+ btn |= BUTTON_NEXT;
+ else
+ if (key_val < 1839)
+ btn |= BUTTON_PLAY;
+ else
+ if (key_val < 2213)
+ btn |= BUTTON_OPTION;
+ else
+ if (key_val < 2600)
+ btn |= BUTTON_HOME;
+
+ return btn;
+}
+
+/* called on button press interrupt */
+void KEY_INT_IRQ(void)
+{
+}
+
+const unsigned short battery_level_dangerous[BATTERY_TYPES_COUNT] =
+{
+ /* 5% */
+ 3634
+};
+
+const unsigned short battery_level_shutoff[BATTERY_TYPES_COUNT] =
+{
+ /* 0% */
+ 3300
+};
+
+
+/* voltages (millivolt) of 0%, 10%, ... 100% when charging disabled */
+const unsigned short percent_to_volt_discharge[BATTERY_TYPES_COUNT][11] =
+{
+ { 3300, 3652, 3704, 3730, 3753, 3786, 3836, 3906, 3973, 4061, 4160 }
+};
+
+#if CONFIG_CHARGING
+/* voltages (millivolt) of 0%, 10%, ... 100% when charging enabled */
+const unsigned short percent_to_volt_charge[11] =
+ { 3300, 3652, 3704, 3730, 3753, 3786, 3836, 3906, 3973, 4061, 4160 };
+#endif /* CONFIG_CHARGING */
+
+/* VBAT = (BDATA/1024) * 2.5V */
+#define BATTERY_SCALE_FACTOR 2460
+
+/* Returns battery voltage from ADC [millivolts] */
+int _battery_voltage(void)
+{
+ return (bat_val*BATTERY_SCALE_FACTOR)>>10;
+}
+
+void adc_init(void)
+{
+ bat_val = 0xfff;
+
+ __cpm_start_sadc();
+ mdelay(10);
+ REG_SADC_ADENA = 0; /* Power Up */
+ mdelay(70);
+ REG_SADC_ADSTATE = 0;
+ REG_SADC_ADCTRL = ADCTRL_MASK_ALL - ADCTRL_ARDYM - ADCTRL_VRDYM;
+ REG_SADC_ADCFG = ADCFG_VBAT_SEL + ADCFG_CMD_AUX(1);
+ REG_SADC_ADCLK = (4 << 16) | (1 << 8) | 59; /* 200KHz */
+ system_enable_irq(IRQ_SADC);
+}
+
+void adc_close(void)
+{
+ REG_SADC_ADENA = ADENA_POWER; /* Power Down */
+ __intc_mask_irq(IRQ_SADC);
+ mdelay(20);
+ __cpm_stop_sadc();
+}
+
+/* Interrupt handler */
+void SADC(void)
+{
+ unsigned char state;
+ unsigned char sadcstate;
+
+ sadcstate = REG_SADC_ADSTATE;
+ state = REG_SADC_ADSTATE & (~REG_SADC_ADCTRL);
+ REG_SADC_ADSTATE &= sadcstate;
+
+ if(state & ADCTRL_ARDYM)
+ {
+ key_val = REG_SADC_ADADAT;
+ }
+ if(state & ADCTRL_VRDYM)
+ {
+ bat_val = REG_SADC_ADVDAT;
+ }
+}