summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/crypto/fsl/Makefile1
-rw-r--r--drivers/crypto/fsl/sec.c184
-rw-r--r--drivers/ddr/fsl/fsl_ddr_gen4.c8
-rw-r--r--drivers/fpga/fpga.c2
-rw-r--r--drivers/fpga/xilinx.c22
-rw-r--r--drivers/gpio/sunxi_gpio.c32
-rw-r--r--drivers/mmc/Kconfig9
-rw-r--r--drivers/mmc/Makefile1
-rw-r--r--drivers/mmc/mmc.c307
-rw-r--r--drivers/mmc/sh_sdhi.c695
-rw-r--r--drivers/mmc/sunxi_mmc.c120
-rw-r--r--drivers/mmc/zynq_sdhci.c4
-rw-r--r--drivers/mtd/nand/nand_base.c5
-rw-r--r--drivers/mtd/nand/omap_gpmc.c114
-rw-r--r--drivers/net/Makefile1
-rw-r--r--drivers/net/fm/eth.c30
-rw-r--r--drivers/net/fm/t1040.c3
-rw-r--r--drivers/net/mpc5xxx_fec.c5
-rw-r--r--drivers/net/mvgbe.c4
-rw-r--r--drivers/net/phy/Makefile1
-rw-r--r--drivers/net/phy/aquantia.c156
-rw-r--r--drivers/net/phy/phy.c3
-rw-r--r--drivers/net/vsc9953.c497
-rw-r--r--drivers/net/xilinx_ll_temac.c2
-rw-r--r--drivers/net/zynq_gem.c5
-rw-r--r--drivers/pci/pci.c30
-rw-r--r--drivers/pci/pci_rom.c9
-rw-r--r--drivers/pci/pci_tegra.c3
-rw-r--r--drivers/power/Kconfig64
-rw-r--r--drivers/power/axp209.c86
-rw-r--r--drivers/power/axp221.c278
-rw-r--r--drivers/power/pmic/Makefile1
-rw-r--r--drivers/power/pmic/pmic_tps62362.c47
-rw-r--r--drivers/serial/ns16550.c31
-rw-r--r--drivers/serial/serial_x86.c8
-rw-r--r--drivers/usb/eth/asix88179.c14
-rw-r--r--drivers/usb/gadget/composite.c8
-rw-r--r--drivers/usb/gadget/f_dfu.c8
-rw-r--r--drivers/usb/gadget/pxa25x_udc.c4
-rw-r--r--drivers/usb/host/ehci-hcd.c4
-rw-r--r--drivers/usb/host/ehci-sunxi.c213
-rw-r--r--drivers/usb/musb-new/Makefile1
-rw-r--r--drivers/usb/musb-new/musb_host.c12
-rw-r--r--drivers/usb/musb-new/musb_host.h1
-rw-r--r--drivers/usb/musb-new/musb_regs.h92
-rw-r--r--drivers/usb/musb-new/musb_uboot.c178
-rw-r--r--drivers/usb/musb-new/sunxi.c279
-rw-r--r--drivers/usb/musb-new/usb-compat.h1
-rw-r--r--drivers/video/Kconfig89
-rw-r--r--drivers/video/Makefile4
-rw-r--r--drivers/video/cfb_console.c13
-rw-r--r--drivers/video/hitachi_tx18d42vm_lcd.c81
-rw-r--r--drivers/video/hitachi_tx18d42vm_lcd.h9
-rw-r--r--drivers/video/mpc8xx_lcd.c49
-rw-r--r--drivers/video/pxa_lcd.c15
-rw-r--r--drivers/video/sed13806.c5
-rw-r--r--drivers/video/ssd2828.c436
-rw-r--r--drivers/video/ssd2828.h128
-rw-r--r--drivers/video/sunxi_display.c994
-rw-r--r--drivers/video/videomodes.c201
-rw-r--r--drivers/video/videomodes.h22
-rw-r--r--drivers/video/x86_fb.c1
62 files changed, 5027 insertions, 603 deletions
diff --git a/drivers/crypto/fsl/Makefile b/drivers/crypto/fsl/Makefile
index cb13d2e..067d0a9 100644
--- a/drivers/crypto/fsl/Makefile
+++ b/drivers/crypto/fsl/Makefile
@@ -6,5 +6,6 @@
# Version 2 as published by the Free Software Foundation.
#
+obj-y += sec.o
obj-$(CONFIG_FSL_CAAM) += jr.o fsl_hash.o jobdesc.o error.o
obj-$(CONFIG_CMD_BLOB) += fsl_blob.o
diff --git a/drivers/crypto/fsl/sec.c b/drivers/crypto/fsl/sec.c
new file mode 100644
index 0000000..443ee96
--- /dev/null
+++ b/drivers/crypto/fsl/sec.c
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2014 Freescale Semiconductor, Inc.
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <common.h>
+#include <libfdt.h>
+#include <fdt_support.h>
+#if CONFIG_SYS_FSL_SEC_COMPAT == 2 || CONFIG_SYS_FSL_SEC_COMPAT >= 4
+#include <fsl_sec.h>
+#endif
+
+/*
+ * update crypto node properties to a specified revision of the SEC
+ * called with sec_rev == 0 if not on an E processor
+ */
+#if CONFIG_SYS_FSL_SEC_COMPAT == 2 /* SEC 2.x/3.x */
+void fdt_fixup_crypto_node(void *blob, int sec_rev)
+{
+ static const struct sec_rev_prop {
+ u32 sec_rev;
+ u32 num_channels;
+ u32 channel_fifo_len;
+ u32 exec_units_mask;
+ u32 descriptor_types_mask;
+ } sec_rev_prop_list[] = {
+ { 0x0200, 4, 24, 0x07e, 0x01010ebf }, /* SEC 2.0 */
+ { 0x0201, 4, 24, 0x0fe, 0x012b0ebf }, /* SEC 2.1 */
+ { 0x0202, 1, 24, 0x04c, 0x0122003f }, /* SEC 2.2 */
+ { 0x0204, 4, 24, 0x07e, 0x012b0ebf }, /* SEC 2.4 */
+ { 0x0300, 4, 24, 0x9fe, 0x03ab0ebf }, /* SEC 3.0 */
+ { 0x0301, 4, 24, 0xbfe, 0x03ab0ebf }, /* SEC 3.1 */
+ { 0x0303, 4, 24, 0x97c, 0x03a30abf }, /* SEC 3.3 */
+ };
+ static char compat_strlist[ARRAY_SIZE(sec_rev_prop_list) *
+ sizeof("fsl,secX.Y")];
+ int crypto_node, sec_idx, err;
+ char *p;
+ u32 val;
+
+ /* locate crypto node based on lowest common compatible */
+ crypto_node = fdt_node_offset_by_compatible(blob, -1, "fsl,sec2.0");
+ if (crypto_node == -FDT_ERR_NOTFOUND)
+ return;
+
+ /* delete it if not on an E-processor */
+ if (crypto_node > 0 && !sec_rev) {
+ fdt_del_node(blob, crypto_node);
+ return;
+ }
+
+ /* else we got called for possible uprev */
+ for (sec_idx = 0; sec_idx < ARRAY_SIZE(sec_rev_prop_list); sec_idx++)
+ if (sec_rev_prop_list[sec_idx].sec_rev == sec_rev)
+ break;
+
+ if (sec_idx == ARRAY_SIZE(sec_rev_prop_list)) {
+ puts("warning: unknown SEC revision number\n");
+ return;
+ }
+
+ val = cpu_to_fdt32(sec_rev_prop_list[sec_idx].num_channels);
+ err = fdt_setprop(blob, crypto_node, "fsl,num-channels", &val, 4);
+ if (err < 0)
+ printf("WARNING: could not set crypto property: %s\n",
+ fdt_strerror(err));
+
+ val = cpu_to_fdt32(sec_rev_prop_list[sec_idx].descriptor_types_mask);
+ err = fdt_setprop(blob, crypto_node, "fsl,descriptor-types-mask",
+ &val, 4);
+ if (err < 0)
+ printf("WARNING: could not set crypto property: %s\n",
+ fdt_strerror(err));
+
+ val = cpu_to_fdt32(sec_rev_prop_list[sec_idx].exec_units_mask);
+ err = fdt_setprop(blob, crypto_node, "fsl,exec-units-mask", &val, 4);
+ if (err < 0)
+ printf("WARNING: could not set crypto property: %s\n",
+ fdt_strerror(err));
+
+ val = cpu_to_fdt32(sec_rev_prop_list[sec_idx].channel_fifo_len);
+ err = fdt_setprop(blob, crypto_node, "fsl,channel-fifo-len", &val, 4);
+ if (err < 0)
+ printf("WARNING: could not set crypto property: %s\n",
+ fdt_strerror(err));
+
+ val = 0;
+ while (sec_idx >= 0) {
+ p = compat_strlist + val;
+ val += sprintf(p, "fsl,sec%d.%d",
+ (sec_rev_prop_list[sec_idx].sec_rev & 0xff00) >> 8,
+ sec_rev_prop_list[sec_idx].sec_rev & 0x00ff) + 1;
+ sec_idx--;
+ }
+ err = fdt_setprop(blob, crypto_node, "compatible", &compat_strlist,
+ val);
+ if (err < 0)
+ printf("WARNING: could not set crypto property: %s\n",
+ fdt_strerror(err));
+}
+#elif CONFIG_SYS_FSL_SEC_COMPAT >= 4 /* SEC4 */
+static u8 caam_get_era(void)
+{
+ static const struct {
+ u16 ip_id;
+ u8 maj_rev;
+ u8 era;
+ } caam_eras[] = {
+ {0x0A10, 1, 1},
+ {0x0A10, 2, 2},
+ {0x0A12, 1, 3},
+ {0x0A14, 1, 3},
+ {0x0A14, 2, 4},
+ {0x0A16, 1, 4},
+ {0x0A10, 3, 4},
+ {0x0A11, 1, 4},
+ {0x0A18, 1, 4},
+ {0x0A11, 2, 5},
+ {0x0A12, 2, 5},
+ {0x0A13, 1, 5},
+ {0x0A1C, 1, 5}
+ };
+
+ ccsr_sec_t __iomem *sec = (void __iomem *)CONFIG_SYS_FSL_SEC_ADDR;
+ u32 secvid_ms = sec_in32(&sec->secvid_ms);
+ u32 ccbvid = sec_in32(&sec->ccbvid);
+ u16 ip_id = (secvid_ms & SEC_SECVID_MS_IPID_MASK) >>
+ SEC_SECVID_MS_IPID_SHIFT;
+ u8 maj_rev = (secvid_ms & SEC_SECVID_MS_MAJ_REV_MASK) >>
+ SEC_SECVID_MS_MAJ_REV_SHIFT;
+ u8 era = (ccbvid & SEC_CCBVID_ERA_MASK) >> SEC_CCBVID_ERA_SHIFT;
+
+ int i;
+
+ if (era) /* This is '0' prior to CAAM ERA-6 */
+ return era;
+
+ for (i = 0; i < ARRAY_SIZE(caam_eras); i++)
+ if (caam_eras[i].ip_id == ip_id &&
+ caam_eras[i].maj_rev == maj_rev)
+ return caam_eras[i].era;
+
+ return 0;
+}
+
+static void fdt_fixup_crypto_era(void *blob, u32 era)
+{
+ int err;
+ int crypto_node;
+
+ crypto_node = fdt_path_offset(blob, "crypto");
+ if (crypto_node < 0) {
+ printf("WARNING: Missing crypto node\n");
+ return;
+ }
+
+ err = fdt_setprop(blob, crypto_node, "fsl,sec-era", &era,
+ sizeof(era));
+ if (err < 0) {
+ printf("ERROR: could not set fsl,sec-era property: %s\n",
+ fdt_strerror(err));
+ }
+}
+
+void fdt_fixup_crypto_node(void *blob, int sec_rev)
+{
+ u8 era;
+
+ if (!sec_rev) {
+ fdt_del_node_and_alias(blob, "crypto");
+ return;
+ }
+
+ /* Add SEC ERA information in compatible */
+ era = caam_get_era();
+ if (era) {
+ fdt_fixup_crypto_era(blob, era);
+ } else {
+ printf("WARNING: Unable to get ERA for CAAM rev: %d\n",
+ sec_rev);
+ }
+}
+#endif
diff --git a/drivers/ddr/fsl/fsl_ddr_gen4.c b/drivers/ddr/fsl/fsl_ddr_gen4.c
index a3c01e7..4eef047 100644
--- a/drivers/ddr/fsl/fsl_ddr_gen4.c
+++ b/drivers/ddr/fsl/fsl_ddr_gen4.c
@@ -171,6 +171,14 @@ void fsl_ddr_set_memctl_regs(const fsl_ddr_cfg_regs_t *regs,
ddr_out32(&ddr->debug[i], regs->debug[i]);
}
}
+#ifdef CONFIG_SYS_FSL_ERRATUM_A008378
+ /* Erratum applies when accumulated ECC is used, or DBI is enabled */
+#define IS_ACC_ECC_EN(v) ((v) & 0x4)
+#define IS_DBI(v) ((((v) >> 12) & 0x3) == 0x2)
+ if (IS_ACC_ECC_EN(regs->ddr_sdram_cfg) ||
+ IS_DBI(regs->ddr_sdram_cfg_3))
+ ddr_setbits32(ddr->debug[28], 0x9 << 20);
+#endif
/*
* For RDIMMs, JEDEC spec requires clocks to be stable before reset is
diff --git a/drivers/fpga/fpga.c b/drivers/fpga/fpga.c
index 37946d5..d94eb5c 100644
--- a/drivers/fpga/fpga.c
+++ b/drivers/fpga/fpga.c
@@ -38,7 +38,7 @@ static void fpga_no_sup(char *fn, char *msg)
/* fpga_get_desc
* map a device number to a descriptor
*/
-static const fpga_desc *const fpga_get_desc(int devnum)
+const fpga_desc *const fpga_get_desc(int devnum)
{
fpga_desc *desc = (fpga_desc *)NULL;
diff --git a/drivers/fpga/xilinx.c b/drivers/fpga/xilinx.c
index adb4b8c..c765a74 100644
--- a/drivers/fpga/xilinx.c
+++ b/drivers/fpga/xilinx.c
@@ -139,6 +139,11 @@ int xilinx_load(xilinx_desc *desc, const void *buf, size_t bsize,
return FPGA_FAIL;
}
+ if (!desc->operations || !desc->operations->load) {
+ printf("%s: Missing load operation\n", __func__);
+ return FPGA_FAIL;
+ }
+
return desc->operations->load(desc, buf, bsize, bstype);
}
@@ -151,8 +156,10 @@ int xilinx_loadfs(xilinx_desc *desc, const void *buf, size_t bsize,
return FPGA_FAIL;
}
- if (!desc->operations->loadfs)
+ if (!desc->operations || !desc->operations->loadfs) {
+ printf("%s: Missing loadfs operation\n", __func__);
return FPGA_FAIL;
+ }
return desc->operations->loadfs(desc, buf, bsize, fpga_fsinfo);
}
@@ -165,6 +172,11 @@ int xilinx_dump(xilinx_desc *desc, const void *buf, size_t bsize)
return FPGA_FAIL;
}
+ if (!desc->operations || !desc->operations->dump) {
+ printf("%s: Missing dump operation\n", __func__);
+ return FPGA_FAIL;
+ }
+
return desc->operations->dump(desc, buf, bsize);
}
@@ -226,12 +238,14 @@ int xilinx_info(xilinx_desc *desc)
if (desc->name)
printf("Device name: \t%s\n", desc->name);
- if (desc->iface_fns) {
+ if (desc->iface_fns)
printf ("Device Function Table @ 0x%p\n", desc->iface_fns);
- desc->operations->info(desc);
- } else
+ else
printf ("No Device Function Table.\n");
+ if (desc->operations && desc->operations->info)
+ desc->operations->info(desc);
+
ret_val = FPGA_SUCCESS;
} else {
printf ("%s: Invalid device descriptor\n", __FUNCTION__);
diff --git a/drivers/gpio/sunxi_gpio.c b/drivers/gpio/sunxi_gpio.c
index 44135e5..6296092 100644
--- a/drivers/gpio/sunxi_gpio.c
+++ b/drivers/gpio/sunxi_gpio.c
@@ -18,6 +18,9 @@
#include <asm/io.h>
#include <asm/gpio.h>
#include <dm/device-internal.h>
+#ifdef CONFIG_AXP209_POWER
+#include <axp209.h>
+#endif
DECLARE_GLOBAL_DATA_PTR;
@@ -73,13 +76,22 @@ int gpio_free(unsigned gpio)
int gpio_direction_input(unsigned gpio)
{
+#ifdef AXP_GPIO
+ if (gpio >= SUNXI_GPIO_AXP0_START)
+ return axp_gpio_direction_input(gpio - SUNXI_GPIO_AXP0_START);
+#endif
sunxi_gpio_set_cfgpin(gpio, SUNXI_GPIO_INPUT);
- return sunxi_gpio_input(gpio);
+ return 0;
}
int gpio_direction_output(unsigned gpio, int value)
{
+#ifdef AXP_GPIO
+ if (gpio >= SUNXI_GPIO_AXP0_START)
+ return axp_gpio_direction_output(gpio - SUNXI_GPIO_AXP0_START,
+ value);
+#endif
sunxi_gpio_set_cfgpin(gpio, SUNXI_GPIO_OUTPUT);
return sunxi_gpio_output(gpio, value);
@@ -87,11 +99,19 @@ int gpio_direction_output(unsigned gpio, int value)
int gpio_get_value(unsigned gpio)
{
+#ifdef AXP_GPIO
+ if (gpio >= SUNXI_GPIO_AXP0_START)
+ return axp_gpio_get_value(gpio - SUNXI_GPIO_AXP0_START);
+#endif
return sunxi_gpio_input(gpio);
}
int gpio_set_value(unsigned gpio, int value)
{
+#ifdef AXP_GPIO
+ if (gpio >= SUNXI_GPIO_AXP0_START)
+ return axp_gpio_set_value(gpio - SUNXI_GPIO_AXP0_START, value);
+#endif
return sunxi_gpio_output(gpio, value);
}
@@ -101,6 +121,16 @@ int sunxi_name_to_gpio(const char *name)
int groupsize = 9 * 32;
long pin;
char *eptr;
+
+#ifdef AXP_GPIO
+ if (strncasecmp(name, "AXP0-", 5) == 0) {
+ name += 5;
+ pin = simple_strtol(name, &eptr, 10);
+ if (!*name || *eptr)
+ return -1;
+ return SUNXI_GPIO_AXP0_START + pin;
+ }
+#endif
if (*name == 'P' || *name == 'p')
name++;
if (*name >= 'A') {
diff --git a/drivers/mmc/Kconfig b/drivers/mmc/Kconfig
index e69de29..7ba85a2 100644
--- a/drivers/mmc/Kconfig
+++ b/drivers/mmc/Kconfig
@@ -0,0 +1,9 @@
+menu "MMC Host controller Support"
+
+config SH_SDHI
+ bool "SuperH/Renesas ARM SoCs on-chip SDHI host controller support"
+ depends on RMOBILE
+ help
+ Support for the on-chip SDHI host controller on SuperH/Renesas ARM SoCs platform
+
+endmenu
diff --git a/drivers/mmc/Makefile b/drivers/mmc/Makefile
index 461d7d8..4ba5878 100644
--- a/drivers/mmc/Makefile
+++ b/drivers/mmc/Makefile
@@ -30,6 +30,7 @@ obj-$(CONFIG_S3C_SDI) += s3c_sdi.o
obj-$(CONFIG_S5P_SDHCI) += s5p_sdhci.o
obj-$(CONFIG_SDHCI) += sdhci.o
obj-$(CONFIG_SH_MMCIF) += sh_mmcif.o
+obj-$(CONFIG_SH_SDHI) += sh_sdhi.o
obj-$(CONFIG_SOCFPGA_DWMMC) += socfpga_dw_mmc.o
obj-$(CONFIG_SPEAR_SDHCI) += spear_sdhci.o
obj-$(CONFIG_TEGRA_MMC) += tegra_mmc.o
diff --git a/drivers/mmc/mmc.c b/drivers/mmc/mmc.c
index 1eb9c27..b8039cd 100644
--- a/drivers/mmc/mmc.c
+++ b/drivers/mmc/mmc.c
@@ -486,7 +486,7 @@ static int mmc_change_freq(struct mmc *mmc)
char cardtype;
int err;
- mmc->card_caps = MMC_MODE_4BIT | MMC_MODE_8BIT;
+ mmc->card_caps = 0;
if (mmc_host_is_spi(mmc))
return 0;
@@ -495,6 +495,8 @@ static int mmc_change_freq(struct mmc *mmc)
if (mmc->version < MMC_VERSION_4)
return 0;
+ mmc->card_caps |= MMC_MODE_4BIT | MMC_MODE_8BIT;
+
err = mmc_send_ext_csd(mmc, ext_csd);
if (err)
@@ -605,6 +607,200 @@ int mmc_switch_part(int dev_num, unsigned int part_num)
return ret;
}
+int mmc_hwpart_config(struct mmc *mmc,
+ const struct mmc_hwpart_conf *conf,
+ enum mmc_hwpart_conf_mode mode)
+{
+ u8 part_attrs = 0;
+ u32 enh_size_mult;
+ u32 enh_start_addr;
+ u32 gp_size_mult[4];
+ u32 max_enh_size_mult;
+ u32 tot_enh_size_mult = 0;
+ u8 wr_rel_set;
+ int i, pidx, err;
+ ALLOC_CACHE_ALIGN_BUFFER(u8, ext_csd, MMC_MAX_BLOCK_LEN);
+
+ if (mode < MMC_HWPART_CONF_CHECK || mode > MMC_HWPART_CONF_COMPLETE)
+ return -EINVAL;
+
+ if (IS_SD(mmc) || (mmc->version < MMC_VERSION_4_41)) {
+ printf("eMMC >= 4.4 required for enhanced user data area\n");
+ return -EMEDIUMTYPE;
+ }
+
+ if (!(mmc->part_support & PART_SUPPORT)) {
+ printf("Card does not support partitioning\n");
+ return -EMEDIUMTYPE;
+ }
+
+ if (!mmc->hc_wp_grp_size) {
+ printf("Card does not define HC WP group size\n");
+ return -EMEDIUMTYPE;
+ }
+
+ /* check partition alignment and total enhanced size */
+ if (conf->user.enh_size) {
+ if (conf->user.enh_size % mmc->hc_wp_grp_size ||
+ conf->user.enh_start % mmc->hc_wp_grp_size) {
+ printf("User data enhanced area not HC WP group "
+ "size aligned\n");
+ return -EINVAL;
+ }
+ part_attrs |= EXT_CSD_ENH_USR;
+ enh_size_mult = conf->user.enh_size / mmc->hc_wp_grp_size;
+ if (mmc->high_capacity) {
+ enh_start_addr = conf->user.enh_start;
+ } else {
+ enh_start_addr = (conf->user.enh_start << 9);
+ }
+ } else {
+ enh_size_mult = 0;
+ enh_start_addr = 0;
+ }
+ tot_enh_size_mult += enh_size_mult;
+
+ for (pidx = 0; pidx < 4; pidx++) {
+ if (conf->gp_part[pidx].size % mmc->hc_wp_grp_size) {
+ printf("GP%i partition not HC WP group size "
+ "aligned\n", pidx+1);
+ return -EINVAL;
+ }
+ gp_size_mult[pidx] = conf->gp_part[pidx].size / mmc->hc_wp_grp_size;
+ if (conf->gp_part[pidx].size && conf->gp_part[pidx].enhanced) {
+ part_attrs |= EXT_CSD_ENH_GP(pidx);
+ tot_enh_size_mult += gp_size_mult[pidx];
+ }
+ }
+
+ if (part_attrs && ! (mmc->part_support & ENHNCD_SUPPORT)) {
+ printf("Card does not support enhanced attribute\n");
+ return -EMEDIUMTYPE;
+ }
+
+ err = mmc_send_ext_csd(mmc, ext_csd);
+ if (err)
+ return err;
+
+ max_enh_size_mult =
+ (ext_csd[EXT_CSD_MAX_ENH_SIZE_MULT+2] << 16) +
+ (ext_csd[EXT_CSD_MAX_ENH_SIZE_MULT+1] << 8) +
+ ext_csd[EXT_CSD_MAX_ENH_SIZE_MULT];
+ if (tot_enh_size_mult > max_enh_size_mult) {
+ printf("Total enhanced size exceeds maximum (%u > %u)\n",
+ tot_enh_size_mult, max_enh_size_mult);
+ return -EMEDIUMTYPE;
+ }
+
+ /* The default value of EXT_CSD_WR_REL_SET is device
+ * dependent, the values can only be changed if the
+ * EXT_CSD_HS_CTRL_REL bit is set. The values can be
+ * changed only once and before partitioning is completed. */
+ wr_rel_set = ext_csd[EXT_CSD_WR_REL_SET];
+ if (conf->user.wr_rel_change) {
+ if (conf->user.wr_rel_set)
+ wr_rel_set |= EXT_CSD_WR_DATA_REL_USR;
+ else
+ wr_rel_set &= ~EXT_CSD_WR_DATA_REL_USR;
+ }
+ for (pidx = 0; pidx < 4; pidx++) {
+ if (conf->gp_part[pidx].wr_rel_change) {
+ if (conf->gp_part[pidx].wr_rel_set)
+ wr_rel_set |= EXT_CSD_WR_DATA_REL_GP(pidx);
+ else
+ wr_rel_set &= ~EXT_CSD_WR_DATA_REL_GP(pidx);
+ }
+ }
+
+ if (wr_rel_set != ext_csd[EXT_CSD_WR_REL_SET] &&
+ !(ext_csd[EXT_CSD_WR_REL_PARAM] & EXT_CSD_HS_CTRL_REL)) {
+ puts("Card does not support host controlled partition write "
+ "reliability settings\n");
+ return -EMEDIUMTYPE;
+ }
+
+ if (ext_csd[EXT_CSD_PARTITION_SETTING] &
+ EXT_CSD_PARTITION_SETTING_COMPLETED) {
+ printf("Card already partitioned\n");
+ return -EPERM;
+ }
+
+ if (mode == MMC_HWPART_CONF_CHECK)
+ return 0;
+
+ /* Partitioning requires high-capacity size definitions */
+ if (!(ext_csd[EXT_CSD_ERASE_GROUP_DEF] & 0x01)) {
+ err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL,
+ EXT_CSD_ERASE_GROUP_DEF, 1);
+
+ if (err)
+ return err;
+
+ ext_csd[EXT_CSD_ERASE_GROUP_DEF] = 1;
+
+ /* update erase group size to be high-capacity */
+ mmc->erase_grp_size =
+ ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE] * 1024;
+
+ }
+
+ /* all OK, write the configuration */
+ for (i = 0; i < 4; i++) {
+ err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL,
+ EXT_CSD_ENH_START_ADDR+i,
+ (enh_start_addr >> (i*8)) & 0xFF);
+ if (err)
+ return err;
+ }
+ for (i = 0; i < 3; i++) {
+ err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL,
+ EXT_CSD_ENH_SIZE_MULT+i,
+ (enh_size_mult >> (i*8)) & 0xFF);
+ if (err)
+ return err;
+ }
+ for (pidx = 0; pidx < 4; pidx++) {
+ for (i = 0; i < 3; i++) {
+ err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL,
+ EXT_CSD_GP_SIZE_MULT+pidx*3+i,
+ (gp_size_mult[pidx] >> (i*8)) & 0xFF);
+ if (err)
+ return err;
+ }
+ }
+ err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL,
+ EXT_CSD_PARTITIONS_ATTRIBUTE, part_attrs);
+ if (err)
+ return err;
+
+ if (mode == MMC_HWPART_CONF_SET)
+ return 0;
+
+ /* The WR_REL_SET is a write-once register but shall be
+ * written before setting PART_SETTING_COMPLETED. As it is
+ * write-once we can only write it when completing the
+ * partitioning. */
+ if (wr_rel_set != ext_csd[EXT_CSD_WR_REL_SET]) {
+ err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL,
+ EXT_CSD_WR_REL_SET, wr_rel_set);
+ if (err)
+ return err;
+ }
+
+ /* Setting PART_SETTING_COMPLETED confirms the partition
+ * configuration but it only becomes effective after power
+ * cycle, so we do not adjust the partition related settings
+ * in the mmc struct. */
+
+ err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL,
+ EXT_CSD_PARTITION_SETTING,
+ EXT_CSD_PARTITION_SETTING_COMPLETED);
+ if (err)
+ return err;
+
+ return 0;
+}
+
int mmc_getcd(struct mmc *mmc)
{
int cd;
@@ -818,6 +1014,8 @@ static int mmc_startup(struct mmc *mmc)
ALLOC_CACHE_ALIGN_BUFFER(u8, ext_csd, MMC_MAX_BLOCK_LEN);
ALLOC_CACHE_ALIGN_BUFFER(u8, test_csd, MMC_MAX_BLOCK_LEN);
int timeout = 1000;
+ bool has_parts = false;
+ bool part_completed;
#ifdef CONFIG_MMC_SPI_CRC_ON
if (mmc_host_is_spi(mmc)) { /* enable CRC check for spi */
@@ -970,7 +1168,9 @@ static int mmc_startup(struct mmc *mmc)
if (!IS_SD(mmc) && (mmc->version >= MMC_VERSION_4)) {
/* check ext_csd version and capacity */
err = mmc_send_ext_csd(mmc, ext_csd);
- if (!err && (ext_csd[EXT_CSD_REV] >= 2)) {
+ if (err)
+ return err;
+ if (ext_csd[EXT_CSD_REV] >= 2) {
/*
* According to the JEDEC Standard, the value of
* ext_csd's capacity is valid if the value is more
@@ -1006,13 +1206,70 @@ static int mmc_startup(struct mmc *mmc)
break;
}
+ /* The partition data may be non-zero but it is only
+ * effective if PARTITION_SETTING_COMPLETED is set in
+ * EXT_CSD, so ignore any data if this bit is not set,
+ * except for enabling the high-capacity group size
+ * definition (see below). */
+ part_completed = !!(ext_csd[EXT_CSD_PARTITION_SETTING] &
+ EXT_CSD_PARTITION_SETTING_COMPLETED);
+
+ /* store the partition info of emmc */
+ mmc->part_support = ext_csd[EXT_CSD_PARTITIONING_SUPPORT];
+ if ((ext_csd[EXT_CSD_PARTITIONING_SUPPORT] & PART_SUPPORT) ||
+ ext_csd[EXT_CSD_BOOT_MULT])
+ mmc->part_config = ext_csd[EXT_CSD_PART_CONF];
+ if (part_completed &&
+ (ext_csd[EXT_CSD_PARTITIONING_SUPPORT] & ENHNCD_SUPPORT))
+ mmc->part_attr = ext_csd[EXT_CSD_PARTITIONS_ATTRIBUTE];
+
+ mmc->capacity_boot = ext_csd[EXT_CSD_BOOT_MULT] << 17;
+
+ mmc->capacity_rpmb = ext_csd[EXT_CSD_RPMB_MULT] << 17;
+
+ for (i = 0; i < 4; i++) {
+ int idx = EXT_CSD_GP_SIZE_MULT + i * 3;
+ uint mult = (ext_csd[idx + 2] << 16) +
+ (ext_csd[idx + 1] << 8) + ext_csd[idx];
+ if (mult)
+ has_parts = true;
+ if (!part_completed)
+ continue;
+ mmc->capacity_gp[i] = mult;
+ mmc->capacity_gp[i] *=
+ ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE];
+ mmc->capacity_gp[i] *= ext_csd[EXT_CSD_HC_WP_GRP_SIZE];
+ mmc->capacity_gp[i] <<= 19;
+ }
+
+ if (part_completed) {
+ mmc->enh_user_size =
+ (ext_csd[EXT_CSD_ENH_SIZE_MULT+2] << 16) +
+ (ext_csd[EXT_CSD_ENH_SIZE_MULT+1] << 8) +
+ ext_csd[EXT_CSD_ENH_SIZE_MULT];
+ mmc->enh_user_size *= ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE];
+ mmc->enh_user_size *= ext_csd[EXT_CSD_HC_WP_GRP_SIZE];
+ mmc->enh_user_size <<= 19;
+ mmc->enh_user_start =
+ (ext_csd[EXT_CSD_ENH_START_ADDR+3] << 24) +
+ (ext_csd[EXT_CSD_ENH_START_ADDR+2] << 16) +
+ (ext_csd[EXT_CSD_ENH_START_ADDR+1] << 8) +
+ ext_csd[EXT_CSD_ENH_START_ADDR];
+ if (mmc->high_capacity)
+ mmc->enh_user_start <<= 9;
+ }
+
/*
* Host needs to enable ERASE_GRP_DEF bit if device is
* partitioned. This bit will be lost every time after a reset
* or power off. This will affect erase size.
*/
+ if (part_completed)
+ has_parts = true;
if ((ext_csd[EXT_CSD_PARTITIONING_SUPPORT] & PART_SUPPORT) &&
- (ext_csd[EXT_CSD_PARTITIONS_ATTRIBUTE] & PART_ENH_ATTRIB)) {
+ (ext_csd[EXT_CSD_PARTITIONS_ATTRIBUTE] & PART_ENH_ATTRIB))
+ has_parts = true;
+ if (has_parts) {
err = mmc_switch(mmc, EXT_CSD_CMD_SET_NORMAL,
EXT_CSD_ERASE_GROUP_DEF, 1);
@@ -1020,19 +1277,18 @@ static int mmc_startup(struct mmc *mmc)
return err;
else
ext_csd[EXT_CSD_ERASE_GROUP_DEF] = 1;
+ }
+ if (ext_csd[EXT_CSD_ERASE_GROUP_DEF] & 0x01) {
/* Read out group size from ext_csd */
mmc->erase_grp_size =
- ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE] *
- MMC_MAX_BLOCK_LEN * 1024;
+ ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE] * 1024;
/*
* if high capacity and partition setting completed
* SEC_COUNT is valid even if it is smaller than 2 GiB
* JEDEC Standard JESD84-B45, 6.2.4
*/
- if (mmc->high_capacity &&
- (ext_csd[EXT_CSD_PARTITION_SETTING] &
- EXT_CSD_PARTITION_SETTING_COMPLETED)) {
+ if (mmc->high_capacity && part_completed) {
capacity = (ext_csd[EXT_CSD_SEC_CNT]) |
(ext_csd[EXT_CSD_SEC_CNT + 1] << 8) |
(ext_csd[EXT_CSD_SEC_CNT + 2] << 16) |
@@ -1049,23 +1305,11 @@ static int mmc_startup(struct mmc *mmc)
* (erase_gmul + 1);
}
- /* store the partition info of emmc */
- if ((ext_csd[EXT_CSD_PARTITIONING_SUPPORT] & PART_SUPPORT) ||
- ext_csd[EXT_CSD_BOOT_MULT])
- mmc->part_config = ext_csd[EXT_CSD_PART_CONF];
-
- mmc->capacity_boot = ext_csd[EXT_CSD_BOOT_MULT] << 17;
-
- mmc->capacity_rpmb = ext_csd[EXT_CSD_RPMB_MULT] << 17;
+ mmc->hc_wp_grp_size = 1024
+ * ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE]
+ * ext_csd[EXT_CSD_HC_WP_GRP_SIZE];
- for (i = 0; i < 4; i++) {
- int idx = EXT_CSD_GP_SIZE_MULT + i * 3;
- mmc->capacity_gp[i] = (ext_csd[idx + 2] << 16) +
- (ext_csd[idx + 1] << 8) + ext_csd[idx];
- mmc->capacity_gp[i] *=
- ext_csd[EXT_CSD_HC_ERASE_GRP_SIZE];
- mmc->capacity_gp[i] *= ext_csd[EXT_CSD_HC_WP_GRP_SIZE];
- }
+ mmc->wr_rel_set = ext_csd[EXT_CSD_WR_REL_SET];
}
err = mmc_set_capacity(mmc, mmc->part_num);
@@ -1107,7 +1351,8 @@ static int mmc_startup(struct mmc *mmc)
mmc->tran_speed = 50000000;
else
mmc->tran_speed = 25000000;
- } else {
+ } else if (mmc->version >= MMC_VERSION_4) {
+ /* Only version 4 of MMC supports wider bus widths */
int idx;
/* An array of possible bus widths in order of preference */
@@ -1139,6 +1384,18 @@ static int mmc_startup(struct mmc *mmc)
unsigned int caps = ext_to_hostcaps[extw];
/*
+ * If the bus width is still not changed,
+ * don't try to set the default again.
+ * Otherwise, recover from switch attempts
+ * by switching to 1-bit bus width.
+ */
+ if (extw == EXT_CSD_BUS_WIDTH_1 &&
+ mmc->bus_width == 1) {
+ err = 0;
+ break;
+ }
+
+ /*
* Check to make sure the card and controller support
* these capabilities
*/
diff --git a/drivers/mmc/sh_sdhi.c b/drivers/mmc/sh_sdhi.c
new file mode 100644
index 0000000..cc62c89
--- /dev/null
+++ b/drivers/mmc/sh_sdhi.c
@@ -0,0 +1,695 @@
+/*
+ * drivers/mmc/sh_sdhi.c
+ *
+ * SD/MMC driver for Renesas rmobile ARM SoCs.
+ *
+ * Copyright (C) 2011,2013-2014 Renesas Electronics Corporation
+ * Copyright (C) 2014 Nobuhiro Iwamatsu <nobuhiro.iwamatsu.yj@renesas.com>
+ * Copyright (C) 2008-2009 Renesas Solutions Corp.
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#include <common.h>
+#include <malloc.h>
+#include <mmc.h>
+#include <asm/errno.h>
+#include <asm/io.h>
+#include <asm/arch/rmobile.h>
+#include <asm/arch/sh_sdhi.h>
+
+#define DRIVER_NAME "sh-sdhi"
+
+struct sh_sdhi_host {
+ unsigned long addr;
+ int ch;
+ int bus_shift;
+ unsigned long quirks;
+ unsigned char wait_int;
+ unsigned char sd_error;
+ unsigned char detect_waiting;
+};
+static inline void sh_sdhi_writew(struct sh_sdhi_host *host, int reg, u16 val)
+{
+ writew(val, host->addr + (reg << host->bus_shift));
+}
+
+static inline u16 sh_sdhi_readw(struct sh_sdhi_host *host, int reg)
+{
+ return readw(host->addr + (reg << host->bus_shift));
+}
+
+static void *mmc_priv(struct mmc *mmc)
+{
+ return (void *)mmc->priv;
+}
+
+static void sh_sdhi_detect(struct sh_sdhi_host *host)
+{
+ sh_sdhi_writew(host, SDHI_OPTION,
+ OPT_BUS_WIDTH_1 | sh_sdhi_readw(host, SDHI_OPTION));
+
+ host->detect_waiting = 0;
+}
+
+static int sh_sdhi_intr(void *dev_id)
+{
+ struct sh_sdhi_host *host = dev_id;
+ int state1 = 0, state2 = 0;
+
+ state1 = sh_sdhi_readw(host, SDHI_INFO1);
+ state2 = sh_sdhi_readw(host, SDHI_INFO2);
+
+ debug("%s: state1 = %x, state2 = %x\n", __func__, state1, state2);
+
+ /* CARD Insert */
+ if (state1 & INFO1_CARD_IN) {
+ sh_sdhi_writew(host, SDHI_INFO1, ~INFO1_CARD_IN);
+ if (!host->detect_waiting) {
+ host->detect_waiting = 1;
+ sh_sdhi_detect(host);
+ }
+ sh_sdhi_writew(host, SDHI_INFO1_MASK, INFO1M_RESP_END |
+ INFO1M_ACCESS_END | INFO1M_CARD_IN |
+ INFO1M_DATA3_CARD_RE | INFO1M_DATA3_CARD_IN);
+ return -EAGAIN;
+ }
+ /* CARD Removal */
+ if (state1 & INFO1_CARD_RE) {
+ sh_sdhi_writew(host, SDHI_INFO1, ~INFO1_CARD_RE);
+ if (!host->detect_waiting) {
+ host->detect_waiting = 1;
+ sh_sdhi_detect(host);
+ }
+ sh_sdhi_writew(host, SDHI_INFO1_MASK, INFO1M_RESP_END |
+ INFO1M_ACCESS_END | INFO1M_CARD_RE |
+ INFO1M_DATA3_CARD_RE | INFO1M_DATA3_CARD_IN);
+ sh_sdhi_writew(host, SDHI_SDIO_INFO1_MASK, SDIO_INFO1M_ON);
+ sh_sdhi_writew(host, SDHI_SDIO_MODE, SDIO_MODE_OFF);
+ return -EAGAIN;
+ }
+
+ if (state2 & INFO2_ALL_ERR) {
+ sh_sdhi_writew(host, SDHI_INFO2,
+ (unsigned short)~(INFO2_ALL_ERR));
+ sh_sdhi_writew(host, SDHI_INFO2_MASK,
+ INFO2M_ALL_ERR |
+ sh_sdhi_readw(host, SDHI_INFO2_MASK));
+ host->sd_error = 1;
+ host->wait_int = 1;
+ return 0;
+ }
+ /* Respons End */
+ if (state1 & INFO1_RESP_END) {
+ sh_sdhi_writew(host, SDHI_INFO1, ~INFO1_RESP_END);
+ sh_sdhi_writew(host, SDHI_INFO1_MASK,
+ INFO1M_RESP_END |
+ sh_sdhi_readw(host, SDHI_INFO1_MASK));
+ host->wait_int = 1;
+ return 0;
+ }
+ /* SD_BUF Read Enable */
+ if (state2 & INFO2_BRE_ENABLE) {
+ sh_sdhi_writew(host, SDHI_INFO2, ~INFO2_BRE_ENABLE);
+ sh_sdhi_writew(host, SDHI_INFO2_MASK,
+ INFO2M_BRE_ENABLE | INFO2M_BUF_ILL_READ |
+ sh_sdhi_readw(host, SDHI_INFO2_MASK));
+ host->wait_int = 1;
+ return 0;
+ }
+ /* SD_BUF Write Enable */
+ if (state2 & INFO2_BWE_ENABLE) {
+ sh_sdhi_writew(host, SDHI_INFO2, ~INFO2_BWE_ENABLE);
+ sh_sdhi_writew(host, SDHI_INFO2_MASK,
+ INFO2_BWE_ENABLE | INFO2M_BUF_ILL_WRITE |
+ sh_sdhi_readw(host, SDHI_INFO2_MASK));
+ host->wait_int = 1;
+ return 0;
+ }
+ /* Access End */
+ if (state1 & INFO1_ACCESS_END) {
+ sh_sdhi_writew(host, SDHI_INFO1, ~INFO1_ACCESS_END);
+ sh_sdhi_writew(host, SDHI_INFO1_MASK,
+ INFO1_ACCESS_END |
+ sh_sdhi_readw(host, SDHI_INFO1_MASK));
+ host->wait_int = 1;
+ return 0;
+ }
+ return -EAGAIN;
+}
+
+static int sh_sdhi_wait_interrupt_flag(struct sh_sdhi_host *host)
+{
+ int timeout = 10000000;
+
+ while (1) {
+ timeout--;
+ if (timeout < 0) {
+ debug(DRIVER_NAME": %s timeout\n", __func__);
+ return 0;
+ }
+
+ if (!sh_sdhi_intr(host))
+ break;
+
+ udelay(1); /* 1 usec */
+ }
+
+ return 1; /* Return value: NOT 0 = complete waiting */
+}
+
+static int sh_sdhi_clock_control(struct sh_sdhi_host *host, unsigned long clk)
+{
+ u32 clkdiv, i, timeout;
+
+ if (sh_sdhi_readw(host, SDHI_INFO2) & (1 << 14)) {
+ printf(DRIVER_NAME": Busy state ! Cannot change the clock\n");
+ return -EBUSY;
+ }
+
+ sh_sdhi_writew(host, SDHI_CLK_CTRL,
+ ~CLK_ENABLE & sh_sdhi_readw(host, SDHI_CLK_CTRL));
+
+ if (clk == 0)
+ return -EIO;
+
+ clkdiv = 0x80;
+ i = CONFIG_SH_SDHI_FREQ >> (0x8 + 1);
+ for (; clkdiv && clk >= (i << 1); (clkdiv >>= 1))
+ i <<= 1;
+
+ sh_sdhi_writew(host, SDHI_CLK_CTRL, clkdiv);
+
+ timeout = 100000;
+ /* Waiting for SD Bus busy to be cleared */
+ while (timeout--) {
+ if ((sh_sdhi_readw(host, SDHI_INFO2) & 0x2000))
+ break;
+ }
+
+ if (timeout)
+ sh_sdhi_writew(host, SDHI_CLK_CTRL,
+ CLK_ENABLE | sh_sdhi_readw(host, SDHI_CLK_CTRL));
+ else
+ return -EBUSY;
+
+ return 0;
+}
+
+static int sh_sdhi_sync_reset(struct sh_sdhi_host *host)
+{
+ u32 timeout;
+ sh_sdhi_writew(host, SDHI_SOFT_RST, SOFT_RST_ON);
+ sh_sdhi_writew(host, SDHI_SOFT_RST, SOFT_RST_OFF);
+ sh_sdhi_writew(host, SDHI_CLK_CTRL,
+ CLK_ENABLE | sh_sdhi_readw(host, SDHI_CLK_CTRL));
+
+ timeout = 100000;
+ while (timeout--) {
+ if (!(sh_sdhi_readw(host, SDHI_INFO2) & INFO2_CBUSY))
+ break;
+ udelay(100);
+ }
+
+ if (!timeout)
+ return -EBUSY;
+
+ if (host->quirks & SH_SDHI_QUIRK_16BIT_BUF)
+ sh_sdhi_writew(host, SDHI_HOST_MODE, 1);
+
+ return 0;
+}
+
+static int sh_sdhi_error_manage(struct sh_sdhi_host *host)
+{
+ unsigned short e_state1, e_state2;
+ int ret;
+
+ host->sd_error = 0;
+ host->wait_int = 0;
+
+ e_state1 = sh_sdhi_readw(host, SDHI_ERR_STS1);
+ e_state2 = sh_sdhi_readw(host, SDHI_ERR_STS2);
+ if (e_state2 & ERR_STS2_SYS_ERROR) {
+ if (e_state2 & ERR_STS2_RES_STOP_TIMEOUT)
+ ret = TIMEOUT;
+ else
+ ret = -EILSEQ;
+ debug("%s: ERR_STS2 = %04x\n",
+ DRIVER_NAME, sh_sdhi_readw(host, SDHI_ERR_STS2));
+ sh_sdhi_sync_reset(host);
+
+ sh_sdhi_writew(host, SDHI_INFO1_MASK,
+ INFO1M_DATA3_CARD_RE | INFO1M_DATA3_CARD_IN);
+ return ret;
+ }
+ if (e_state1 & ERR_STS1_CRC_ERROR || e_state1 & ERR_STS1_CMD_ERROR)
+ ret = -EILSEQ;
+ else
+ ret = TIMEOUT;
+
+ debug("%s: ERR_STS1 = %04x\n",
+ DRIVER_NAME, sh_sdhi_readw(host, SDHI_ERR_STS1));
+ sh_sdhi_sync_reset(host);
+ sh_sdhi_writew(host, SDHI_INFO1_MASK,
+ INFO1M_DATA3_CARD_RE | INFO1M_DATA3_CARD_IN);
+ return ret;
+}
+
+static int sh_sdhi_single_read(struct sh_sdhi_host *host, struct mmc_data *data)
+{
+ long time;
+ unsigned short blocksize, i;
+ unsigned short *p = (unsigned short *)data->dest;
+
+ if ((unsigned long)p & 0x00000001) {
+ debug(DRIVER_NAME": %s: The data pointer is unaligned.",
+ __func__);
+ return -EIO;
+ }
+
+ host->wait_int = 0;
+ sh_sdhi_writew(host, SDHI_INFO2_MASK,
+ ~(INFO2M_BRE_ENABLE | INFO2M_BUF_ILL_READ) &
+ sh_sdhi_readw(host, SDHI_INFO2_MASK));
+ sh_sdhi_writew(host, SDHI_INFO1_MASK,
+ ~INFO1M_ACCESS_END &
+ sh_sdhi_readw(host, SDHI_INFO1_MASK));
+ time = sh_sdhi_wait_interrupt_flag(host);
+ if (time == 0 || host->sd_error != 0)
+ return sh_sdhi_error_manage(host);
+
+ host->wait_int = 0;
+ blocksize = sh_sdhi_readw(host, SDHI_SIZE);
+ for (i = 0; i < blocksize / 2; i++)
+ *p++ = sh_sdhi_readw(host, SDHI_BUF0);
+
+ time = sh_sdhi_wait_interrupt_flag(host);
+ if (time == 0 || host->sd_error != 0)
+ return sh_sdhi_error_manage(host);
+
+ host->wait_int = 0;
+ return 0;
+}
+
+static int sh_sdhi_multi_read(struct sh_sdhi_host *host, struct mmc_data *data)
+{
+ long time;
+ unsigned short blocksize, i, sec;
+ unsigned short *p = (unsigned short *)data->dest;
+
+ if ((unsigned long)p & 0x00000001) {
+ debug(DRIVER_NAME": %s: The data pointer is unaligned.",
+ __func__);
+ return -EIO;
+ }
+
+ debug("%s: blocks = %d, blocksize = %d\n",
+ __func__, data->blocks, data->blocksize);
+
+ host->wait_int = 0;
+ for (sec = 0; sec < data->blocks; sec++) {
+ sh_sdhi_writew(host, SDHI_INFO2_MASK,
+ ~(INFO2M_BRE_ENABLE | INFO2M_BUF_ILL_READ) &
+ sh_sdhi_readw(host, SDHI_INFO2_MASK));
+
+ time = sh_sdhi_wait_interrupt_flag(host);
+ if (time == 0 || host->sd_error != 0)
+ return sh_sdhi_error_manage(host);
+
+ host->wait_int = 0;
+ blocksize = sh_sdhi_readw(host, SDHI_SIZE);
+ for (i = 0; i < blocksize / 2; i++)
+ *p++ = sh_sdhi_readw(host, SDHI_BUF0);
+ }
+
+ return 0;
+}
+
+static int sh_sdhi_single_write(struct sh_sdhi_host *host,
+ struct mmc_data *data)
+{
+ long time;
+ unsigned short blocksize, i;
+ const unsigned short *p = (const unsigned short *)data->src;
+
+ if ((unsigned long)p & 0x00000001) {
+ debug(DRIVER_NAME": %s: The data pointer is unaligned.",
+ __func__);
+ return -EIO;
+ }
+
+ debug("%s: blocks = %d, blocksize = %d\n",
+ __func__, data->blocks, data->blocksize);
+
+ host->wait_int = 0;
+ sh_sdhi_writew(host, SDHI_INFO2_MASK,
+ ~(INFO2M_BWE_ENABLE | INFO2M_BUF_ILL_WRITE) &
+ sh_sdhi_readw(host, SDHI_INFO2_MASK));
+ sh_sdhi_writew(host, SDHI_INFO1_MASK,
+ ~INFO1M_ACCESS_END &
+ sh_sdhi_readw(host, SDHI_INFO1_MASK));
+
+ time = sh_sdhi_wait_interrupt_flag(host);
+ if (time == 0 || host->sd_error != 0)
+ return sh_sdhi_error_manage(host);
+
+ host->wait_int = 0;
+ blocksize = sh_sdhi_readw(host, SDHI_SIZE);
+ for (i = 0; i < blocksize / 2; i++)
+ sh_sdhi_writew(host, SDHI_BUF0, *p++);
+
+ time = sh_sdhi_wait_interrupt_flag(host);
+ if (time == 0 || host->sd_error != 0)
+ return sh_sdhi_error_manage(host);
+
+ host->wait_int = 0;
+ return 0;
+}
+
+static int sh_sdhi_multi_write(struct sh_sdhi_host *host, struct mmc_data *data)
+{
+ long time;
+ unsigned short i, sec, blocksize;
+ const unsigned short *p = (const unsigned short *)data->src;
+
+ debug("%s: blocks = %d, blocksize = %d\n",
+ __func__, data->blocks, data->blocksize);
+
+ host->wait_int = 0;
+ for (sec = 0; sec < data->blocks; sec++) {
+ sh_sdhi_writew(host, SDHI_INFO2_MASK,
+ ~(INFO2M_BWE_ENABLE | INFO2M_BUF_ILL_WRITE) &
+ sh_sdhi_readw(host, SDHI_INFO2_MASK));
+
+ time = sh_sdhi_wait_interrupt_flag(host);
+ if (time == 0 || host->sd_error != 0)
+ return sh_sdhi_error_manage(host);
+
+ host->wait_int = 0;
+ blocksize = sh_sdhi_readw(host, SDHI_SIZE);
+ for (i = 0; i < blocksize / 2; i++)
+ sh_sdhi_writew(host, SDHI_BUF0, *p++);
+ }
+
+ return 0;
+}
+
+static void sh_sdhi_get_response(struct sh_sdhi_host *host, struct mmc_cmd *cmd)
+{
+ unsigned short i, j, cnt = 1;
+ unsigned short resp[8];
+ unsigned long *p1, *p2;
+
+ if (cmd->resp_type & MMC_RSP_136) {
+ cnt = 4;
+ resp[0] = sh_sdhi_readw(host, SDHI_RSP00);
+ resp[1] = sh_sdhi_readw(host, SDHI_RSP01);
+ resp[2] = sh_sdhi_readw(host, SDHI_RSP02);
+ resp[3] = sh_sdhi_readw(host, SDHI_RSP03);
+ resp[4] = sh_sdhi_readw(host, SDHI_RSP04);
+ resp[5] = sh_sdhi_readw(host, SDHI_RSP05);
+ resp[6] = sh_sdhi_readw(host, SDHI_RSP06);
+ resp[7] = sh_sdhi_readw(host, SDHI_RSP07);
+
+ /* SDHI REGISTER SPECIFICATION */
+ for (i = 7, j = 6; i > 0; i--) {
+ resp[i] = (resp[i] << 8) & 0xff00;
+ resp[i] |= (resp[j--] >> 8) & 0x00ff;
+ }
+ resp[0] = (resp[0] << 8) & 0xff00;
+
+ /* SDHI REGISTER SPECIFICATION */
+ p1 = ((unsigned long *)resp) + 3;
+
+ } else {
+ resp[0] = sh_sdhi_readw(host, SDHI_RSP00);
+ resp[1] = sh_sdhi_readw(host, SDHI_RSP01);
+
+ p1 = ((unsigned long *)resp);
+ }
+
+ p2 = (unsigned long *)cmd->response;
+#if defined(__BIG_ENDIAN_BITFIELD)
+ for (i = 0; i < cnt; i++) {
+ *p2++ = ((*p1 >> 16) & 0x0000ffff) |
+ ((*p1 << 16) & 0xffff0000);
+ p1--;
+ }
+#else
+ for (i = 0; i < cnt; i++)
+ *p2++ = *p1--;
+#endif /* __BIG_ENDIAN_BITFIELD */
+}
+
+static unsigned short sh_sdhi_set_cmd(struct sh_sdhi_host *host,
+ struct mmc_data *data, unsigned short opc)
+{
+ switch (opc) {
+ case SD_CMD_APP_SEND_OP_COND:
+ case SD_CMD_APP_SEND_SCR:
+ opc |= SDHI_APP;
+ break;
+ case SD_CMD_APP_SET_BUS_WIDTH:
+ /* SD_APP_SET_BUS_WIDTH*/
+ if (!data)
+ opc |= SDHI_APP;
+ else /* SD_SWITCH */
+ opc = SDHI_SD_SWITCH;
+ break;
+ default:
+ break;
+ }
+ return opc;
+}
+
+static unsigned short sh_sdhi_data_trans(struct sh_sdhi_host *host,
+ struct mmc_data *data, unsigned short opc)
+{
+ unsigned short ret;
+
+ switch (opc) {
+ case MMC_CMD_READ_MULTIPLE_BLOCK:
+ ret = sh_sdhi_multi_read(host, data);
+ break;
+ case MMC_CMD_WRITE_MULTIPLE_BLOCK:
+ ret = sh_sdhi_multi_write(host, data);
+ break;
+ case MMC_CMD_WRITE_SINGLE_BLOCK:
+ ret = sh_sdhi_single_write(host, data);
+ break;
+ case MMC_CMD_READ_SINGLE_BLOCK:
+ case SDHI_SD_APP_SEND_SCR:
+ case SDHI_SD_SWITCH: /* SD_SWITCH */
+ ret = sh_sdhi_single_read(host, data);
+ break;
+ default:
+ printf(DRIVER_NAME": SD: NOT SUPPORT CMD = d'%04d\n", opc);
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+static int sh_sdhi_start_cmd(struct sh_sdhi_host *host,
+ struct mmc_data *data, struct mmc_cmd *cmd)
+{
+ long time;
+ unsigned short opc = cmd->cmdidx;
+ int ret = 0;
+ unsigned long timeout;
+
+ debug("opc = %d, arg = %x, resp_type = %x\n",
+ opc, cmd->cmdarg, cmd->resp_type);
+
+ if (opc == MMC_CMD_STOP_TRANSMISSION) {
+ /* SDHI sends the STOP command automatically by STOP reg */
+ sh_sdhi_writew(host, SDHI_INFO1_MASK, ~INFO1M_ACCESS_END &
+ sh_sdhi_readw(host, SDHI_INFO1_MASK));
+
+ time = sh_sdhi_wait_interrupt_flag(host);
+ if (time == 0 || host->sd_error != 0)
+ return sh_sdhi_error_manage(host);
+
+ sh_sdhi_get_response(host, cmd);
+ return 0;
+ }
+
+ if (data) {
+ if ((opc == MMC_CMD_READ_MULTIPLE_BLOCK) ||
+ opc == MMC_CMD_WRITE_MULTIPLE_BLOCK) {
+ sh_sdhi_writew(host, SDHI_STOP, STOP_SEC_ENABLE);
+ sh_sdhi_writew(host, SDHI_SECCNT, data->blocks);
+ }
+ sh_sdhi_writew(host, SDHI_SIZE, data->blocksize);
+ }
+ opc = sh_sdhi_set_cmd(host, data, opc);
+
+ /*
+ * U-boot cannot use interrupt.
+ * So this flag may not be clear by timing
+ */
+ sh_sdhi_writew(host, SDHI_INFO1, ~INFO1_RESP_END);
+
+ sh_sdhi_writew(host, SDHI_INFO1_MASK,
+ INFO1M_RESP_END | sh_sdhi_readw(host, SDHI_INFO1_MASK));
+ sh_sdhi_writew(host, SDHI_ARG0,
+ (unsigned short)(cmd->cmdarg & ARG0_MASK));
+ sh_sdhi_writew(host, SDHI_ARG1,
+ (unsigned short)((cmd->cmdarg >> 16) & ARG1_MASK));
+
+ timeout = 100000;
+ /* Waiting for SD Bus busy to be cleared */
+ while (timeout--) {
+ if ((sh_sdhi_readw(host, SDHI_INFO2) & 0x2000))
+ break;
+ }
+
+ sh_sdhi_writew(host, SDHI_CMD, (unsigned short)(opc & CMD_MASK));
+
+ host->wait_int = 0;
+ sh_sdhi_writew(host, SDHI_INFO1_MASK,
+ ~INFO1M_RESP_END & sh_sdhi_readw(host, SDHI_INFO1_MASK));
+ sh_sdhi_writew(host, SDHI_INFO2_MASK,
+ ~(INFO2M_CMD_ERROR | INFO2M_CRC_ERROR |
+ INFO2M_END_ERROR | INFO2M_TIMEOUT |
+ INFO2M_RESP_TIMEOUT | INFO2M_ILA) &
+ sh_sdhi_readw(host, SDHI_INFO2_MASK));
+
+ time = sh_sdhi_wait_interrupt_flag(host);
+ if (!time)
+ return sh_sdhi_error_manage(host);
+
+ if (host->sd_error) {
+ switch (cmd->cmdidx) {
+ case MMC_CMD_ALL_SEND_CID:
+ case MMC_CMD_SELECT_CARD:
+ case SD_CMD_SEND_IF_COND:
+ case MMC_CMD_APP_CMD:
+ ret = TIMEOUT;
+ break;
+ default:
+ debug(DRIVER_NAME": Cmd(d'%d) err\n", opc);
+ debug(DRIVER_NAME": cmdidx = %d\n", cmd->cmdidx);
+ ret = sh_sdhi_error_manage(host);
+ break;
+ }
+ host->sd_error = 0;
+ host->wait_int = 0;
+ return ret;
+ }
+ if (sh_sdhi_readw(host, SDHI_INFO1) & INFO1_RESP_END)
+ return -EINVAL;
+
+ if (host->wait_int) {
+ sh_sdhi_get_response(host, cmd);
+ host->wait_int = 0;
+ }
+ if (data)
+ ret = sh_sdhi_data_trans(host, data, opc);
+
+ debug("ret = %d, resp = %08x, %08x, %08x, %08x\n",
+ ret, cmd->response[0], cmd->response[1],
+ cmd->response[2], cmd->response[3]);
+ return ret;
+}
+
+static int sh_sdhi_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd,
+ struct mmc_data *data)
+{
+ struct sh_sdhi_host *host = mmc_priv(mmc);
+ int ret;
+
+ host->sd_error = 0;
+
+ ret = sh_sdhi_start_cmd(host, data, cmd);
+
+ return ret;
+}
+
+static void sh_sdhi_set_ios(struct mmc *mmc)
+{
+ int ret;
+ struct sh_sdhi_host *host = mmc_priv(mmc);
+
+ ret = sh_sdhi_clock_control(host, mmc->clock);
+ if (ret)
+ return;
+
+ if (mmc->bus_width == 4)
+ sh_sdhi_writew(host, SDHI_OPTION, ~OPT_BUS_WIDTH_1 &
+ sh_sdhi_readw(host, SDHI_OPTION));
+ else
+ sh_sdhi_writew(host, SDHI_OPTION, OPT_BUS_WIDTH_1 |
+ sh_sdhi_readw(host, SDHI_OPTION));
+
+ debug("clock = %d, buswidth = %d\n", mmc->clock, mmc->bus_width);
+}
+
+static int sh_sdhi_initialize(struct mmc *mmc)
+{
+ struct sh_sdhi_host *host = mmc_priv(mmc);
+ int ret = sh_sdhi_sync_reset(host);
+
+ sh_sdhi_writew(host, SDHI_PORTSEL, USE_1PORT);
+
+#if defined(__BIG_ENDIAN_BITFIELD)
+ sh_sdhi_writew(host, SDHI_EXT_SWAP, SET_SWAP);
+#endif
+
+ sh_sdhi_writew(host, SDHI_INFO1_MASK, INFO1M_RESP_END |
+ INFO1M_ACCESS_END | INFO1M_CARD_RE |
+ INFO1M_DATA3_CARD_RE | INFO1M_DATA3_CARD_IN);
+
+ return ret;
+}
+
+static const struct mmc_ops sh_sdhi_ops = {
+ .send_cmd = sh_sdhi_send_cmd,
+ .set_ios = sh_sdhi_set_ios,
+ .init = sh_sdhi_initialize,
+};
+
+static struct mmc_config sh_sdhi_cfg = {
+ .name = DRIVER_NAME,
+ .ops = &sh_sdhi_ops,
+ .f_min = CLKDEV_INIT,
+ .f_max = CLKDEV_HS_DATA,
+ .voltages = MMC_VDD_32_33 | MMC_VDD_33_34,
+ .host_caps = MMC_MODE_4BIT | MMC_MODE_HS,
+ .part_type = PART_TYPE_DOS,
+ .b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT,
+};
+
+int sh_sdhi_init(unsigned long addr, int ch, unsigned long quirks)
+{
+ int ret = 0;
+ struct mmc *mmc;
+ struct sh_sdhi_host *host = NULL;
+
+ if (ch >= CONFIG_SYS_SH_SDHI_NR_CHANNEL)
+ return -ENODEV;
+
+ host = malloc(sizeof(struct sh_sdhi_host));
+ if (!host)
+ return -ENOMEM;
+
+ mmc = mmc_create(&sh_sdhi_cfg, host);
+ if (!mmc) {
+ ret = -1;
+ goto error;
+ }
+
+ host->ch = ch;
+ host->addr = addr;
+ host->quirks = quirks;
+
+ if (host->quirks & SH_SDHI_QUIRK_16BIT_BUF)
+ host->bus_shift = 1;
+
+ return ret;
+error:
+ if (host)
+ free(host);
+ return ret;
+}
diff --git a/drivers/mmc/sunxi_mmc.c b/drivers/mmc/sunxi_mmc.c
index 231f0a0..5104795 100644
--- a/drivers/mmc/sunxi_mmc.c
+++ b/drivers/mmc/sunxi_mmc.c
@@ -22,7 +22,6 @@ struct sunxi_mmc_host {
unsigned mmc_no;
uint32_t *mclkreg;
unsigned fatal_err;
- unsigned mod_clk;
struct sunxi_mmc *reg;
struct mmc_config cfg;
};
@@ -73,16 +72,77 @@ static int mmc_resource_init(int sdc_no)
mmchost->mmc_no = sdc_no;
cd_pin = sunxi_mmc_getcd_gpio(sdc_no);
- if (cd_pin != -1)
+ if (cd_pin != -1) {
ret = gpio_request(cd_pin, "mmc_cd");
+ if (!ret)
+ ret = gpio_direction_input(cd_pin);
+ }
return ret;
}
+static int mmc_set_mod_clk(struct sunxi_mmc_host *mmchost, unsigned int hz)
+{
+ unsigned int pll, pll_hz, div, n, oclk_dly, sclk_dly;
+
+ if (hz <= 24000000) {
+ pll = CCM_MMC_CTRL_OSCM24;
+ pll_hz = 24000000;
+ } else {
+#ifdef CONFIG_MACH_SUN9I
+ pll = CCM_MMC_CTRL_PLL_PERIPH0;
+ pll_hz = clock_get_pll4_periph0();
+#else
+ pll = CCM_MMC_CTRL_PLL6;
+ pll_hz = clock_get_pll6();
+#endif
+ }
+
+ div = pll_hz / hz;
+ if (pll_hz % hz)
+ div++;
+
+ n = 0;
+ while (div > 16) {
+ n++;
+ div = (div + 1) / 2;
+ }
+
+ if (n > 3) {
+ printf("mmc %u error cannot set clock to %u\n",
+ mmchost->mmc_no, hz);
+ return -1;
+ }
+
+ /* determine delays */
+ if (hz <= 400000) {
+ oclk_dly = 0;
+ sclk_dly = 7;
+ } else if (hz <= 25000000) {
+ oclk_dly = 0;
+ sclk_dly = 5;
+ } else if (hz <= 50000000) {
+ oclk_dly = 3;
+ sclk_dly = 5;
+ } else {
+ /* hz > 50000000 */
+ oclk_dly = 2;
+ sclk_dly = 4;
+ }
+
+ writel(CCM_MMC_CTRL_ENABLE | pll | CCM_MMC_CTRL_SCLK_DLY(sclk_dly) |
+ CCM_MMC_CTRL_N(n) | CCM_MMC_CTRL_OCLK_DLY(oclk_dly) |
+ CCM_MMC_CTRL_M(div), mmchost->mclkreg);
+
+ debug("mmc %u set mod-clk req %u parent %u n %u m %u rate %u\n",
+ mmchost->mmc_no, hz, pll_hz, 1u << n, div,
+ pll_hz / (1u << n) / div);
+
+ return 0;
+}
+
static int mmc_clk_io_on(int sdc_no)
{
- unsigned int pll_clk;
- unsigned int divider;
struct sunxi_mmc_host *mmchost = &mmc_host[sdc_no];
struct sunxi_ccm_reg *ccm = (struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
@@ -91,20 +151,18 @@ static int mmc_clk_io_on(int sdc_no)
/* config ahb clock */
setbits_le32(&ccm->ahb_gate0, 1 << AHB_GATE_OFFSET_MMC(sdc_no));
-#if defined(CONFIG_MACH_SUN6I) || defined(CONFIG_MACH_SUN8I)
+#if defined(CONFIG_MACH_SUN6I) || defined(CONFIG_MACH_SUN8I) || \
+ defined(CONFIG_MACH_SUN9I)
/* unassert reset */
setbits_le32(&ccm->ahb_reset0_cfg, 1 << AHB_RESET_OFFSET_MMC(sdc_no));
#endif
+#if defined(CONFIG_MACH_SUN9I)
+ /* sun9i has a mmc-common module, also set the gate and reset there */
+ writel(SUNXI_MMC_COMMON_CLK_GATE | SUNXI_MMC_COMMON_RESET,
+ SUNXI_MMC_COMMON_BASE + 4 * sdc_no);
+#endif
- /* config mod clock */
- pll_clk = clock_get_pll6();
- /* should be close to 100 MHz but no more, so round up */
- divider = ((pll_clk + 99999999) / 100000000) - 1;
- writel(CCM_MMC_CTRL_ENABLE | CCM_MMC_CTRL_PLL6 | divider,
- mmchost->mclkreg);
- mmchost->mod_clk = pll_clk / (divider + 1);
-
- return 0;
+ return mmc_set_mod_clk(mmchost, 24000000);
}
static int mmc_update_clk(struct mmc *mmc)
@@ -129,7 +187,7 @@ static int mmc_update_clk(struct mmc *mmc)
return 0;
}
-static int mmc_config_clock(struct mmc *mmc, unsigned div)
+static int mmc_config_clock(struct mmc *mmc)
{
struct sunxi_mmc_host *mmchost = mmc->priv;
unsigned rval = readl(&mmchost->reg->clkcr);
@@ -140,16 +198,17 @@ static int mmc_config_clock(struct mmc *mmc, unsigned div)
if (mmc_update_clk(mmc))
return -1;
- /* Change Divider Factor */
+ /* Set mod_clk to new rate */
+ if (mmc_set_mod_clk(mmchost, mmc->clock))
+ return -1;
+
+ /* Clear internal divider */
rval &= ~SUNXI_MMC_CLK_DIVIDER_MASK;
- rval |= div;
writel(rval, &mmchost->reg->clkcr);
- if (mmc_update_clk(mmc))
- return -1;
+
/* Re-enable Clock */
rval |= SUNXI_MMC_CLK_ENABLE;
writel(rval, &mmchost->reg->clkcr);
-
if (mmc_update_clk(mmc))
return -1;
@@ -159,18 +218,14 @@ static int mmc_config_clock(struct mmc *mmc, unsigned div)
static void mmc_set_ios(struct mmc *mmc)
{
struct sunxi_mmc_host *mmchost = mmc->priv;
- unsigned int clkdiv = 0;
- debug("set ios: bus_width: %x, clock: %d, mod_clk: %d\n",
- mmc->bus_width, mmc->clock, mmchost->mod_clk);
+ debug("set ios: bus_width: %x, clock: %d\n",
+ mmc->bus_width, mmc->clock);
/* Change clock first */
- clkdiv = (mmchost->mod_clk + (mmc->clock >> 1)) / mmc->clock / 2;
- if (mmc->clock) {
- if (mmc_config_clock(mmc, clkdiv)) {
- mmchost->fatal_err = 1;
- return;
- }
+ if (mmc->clock && mmc_config_clock(mmc) != 0) {
+ mmchost->fatal_err = 1;
+ return;
}
/* Change bus width */
@@ -311,7 +366,7 @@ static int mmc_send_cmd(struct mmc *mmc, struct mmc_cmd *cmd,
}
}
- error = mmc_rint_wait(mmc, 0xfffff, SUNXI_MMC_RINT_COMMAND_DONE, "cmd");
+ error = mmc_rint_wait(mmc, 1000, SUNXI_MMC_RINT_COMMAND_DONE, "cmd");
if (error)
goto out;
@@ -373,7 +428,7 @@ static int sunxi_mmc_getcd(struct mmc *mmc)
if (cd_pin == -1)
return 1;
- return !gpio_direction_input(cd_pin);
+ return !gpio_get_value(cd_pin);
}
static const struct mmc_ops sunxi_mmc_ops = {
@@ -395,7 +450,8 @@ struct mmc *sunxi_mmc_init(int sdc_no)
cfg->voltages = MMC_VDD_32_33 | MMC_VDD_33_34;
cfg->host_caps = MMC_MODE_4BIT;
cfg->host_caps |= MMC_MODE_HS_52MHz | MMC_MODE_HS;
-#if defined(CONFIG_MACH_SUN6I) || defined(CONFIG_MACH_SUN7I) || defined(CONFIG_MACH_SUN8I)
+#if defined(CONFIG_MACH_SUN6I) || defined(CONFIG_MACH_SUN7I) || \
+ defined(CONFIG_MACH_SUN8I) || defined(CONFIG_MACH_SUN9I)
cfg->host_caps |= MMC_MODE_HC;
#endif
cfg->b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT;
diff --git a/drivers/mmc/zynq_sdhci.c b/drivers/mmc/zynq_sdhci.c
index fdce2c2..7887f11 100644
--- a/drivers/mmc/zynq_sdhci.c
+++ b/drivers/mmc/zynq_sdhci.c
@@ -13,7 +13,7 @@
#include <sdhci.h>
#include <asm/arch/sys_proto.h>
-int zynq_sdhci_init(u32 regbase)
+int zynq_sdhci_init(phys_addr_t regbase)
{
struct sdhci_host *host = NULL;
@@ -40,7 +40,7 @@ int zynq_sdhci_of_init(const void *blob)
{
int offset = 0;
u32 ret = 0;
- u32 reg;
+ phys_addr_t reg;
debug("ZYNQ SDHCI: Initialization\n");
diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c
index 63bdf65..6db6566 100644
--- a/drivers/mtd/nand/nand_base.c
+++ b/drivers/mtd/nand/nand_base.c
@@ -1065,11 +1065,6 @@ static int nand_wait(struct mtd_info *mtd, struct nand_chip *chip)
}
}
#endif
-#ifdef PPCHAMELON_NAND_TIMER_HACK
- time_start = get_timer(0);
- while (get_timer(time_start) < 10)
- ;
-#endif /* PPCHAMELON_NAND_TIMER_HACK */
led_trigger_event(nand_led_trigger, LED_OFF);
status = (int)chip->read_byte(mtd);
diff --git a/drivers/mtd/nand/omap_gpmc.c b/drivers/mtd/nand/omap_gpmc.c
index 459904d..fc64f48 100644
--- a/drivers/mtd/nand/omap_gpmc.c
+++ b/drivers/mtd/nand/omap_gpmc.c
@@ -441,6 +441,115 @@ static int omap_correct_data_bch(struct mtd_info *mtd, uint8_t *dat,
return (err) ? err : error_count;
}
+#ifdef CONFIG_NAND_OMAP_GPMC_PREFETCH
+
+#define PREFETCH_CONFIG1_CS_SHIFT 24
+#define PREFETCH_FIFOTHRESHOLD_MAX 0x40
+#define PREFETCH_FIFOTHRESHOLD(val) ((val) << 8)
+#define PREFETCH_STATUS_COUNT(val) (val & 0x00003fff)
+#define PREFETCH_STATUS_FIFO_CNT(val) ((val >> 24) & 0x7F)
+#define ENABLE_PREFETCH (1 << 7)
+
+/**
+ * omap_prefetch_enable - configures and starts prefetch transfer
+ * @fifo_th: fifo threshold to be used for read/ write
+ * @count: number of bytes to be transferred
+ * @is_write: prefetch read(0) or write post(1) mode
+ * @cs: chip select to use
+ */
+static int omap_prefetch_enable(int fifo_th, unsigned int count, int is_write, int cs)
+{
+ uint32_t val;
+
+ if (fifo_th > PREFETCH_FIFOTHRESHOLD_MAX)
+ return -EINVAL;
+
+ if (readl(&gpmc_cfg->prefetch_control))
+ return -EBUSY;
+
+ /* Set the amount of bytes to be prefetched */
+ writel(count, &gpmc_cfg->prefetch_config2);
+
+ val = (cs << PREFETCH_CONFIG1_CS_SHIFT) | (is_write & 1) |
+ PREFETCH_FIFOTHRESHOLD(fifo_th) | ENABLE_PREFETCH;
+ writel(val, &gpmc_cfg->prefetch_config1);
+
+ /* Start the prefetch engine */
+ writel(1, &gpmc_cfg->prefetch_control);
+
+ return 0;
+}
+
+/**
+ * omap_prefetch_reset - disables and stops the prefetch engine
+ */
+static void omap_prefetch_reset(void)
+{
+ writel(0, &gpmc_cfg->prefetch_control);
+ writel(0, &gpmc_cfg->prefetch_config1);
+}
+
+static int __read_prefetch_aligned(struct nand_chip *chip, uint32_t *buf, int len)
+{
+ int ret;
+ uint32_t cnt;
+ struct omap_nand_info *info = chip->priv;
+
+ ret = omap_prefetch_enable(PREFETCH_FIFOTHRESHOLD_MAX, len, 0, info->cs);
+ if (ret < 0)
+ return ret;
+
+ do {
+ int i;
+
+ cnt = readl(&gpmc_cfg->prefetch_status);
+ cnt = PREFETCH_STATUS_FIFO_CNT(cnt);
+
+ for (i = 0; i < cnt / 4; i++) {
+ *buf++ = readl(CONFIG_SYS_NAND_BASE);
+ len -= 4;
+ }
+ } while (len);
+
+ omap_prefetch_reset();
+
+ return 0;
+}
+
+static void omap_nand_read_prefetch8(struct mtd_info *mtd, uint8_t *buf, int len)
+{
+ int ret;
+ uint32_t head, tail;
+ struct nand_chip *chip = mtd->priv;
+
+ /*
+ * If the destination buffer is unaligned, start with reading
+ * the overlap byte-wise.
+ */
+ head = ((uint32_t) buf) % 4;
+ if (head) {
+ nand_read_buf(mtd, buf, head);
+ buf += head;
+ len -= head;
+ }
+
+ /*
+ * Only transfer multiples of 4 bytes in a pre-fetched fashion.
+ * If there's a residue, care for it byte-wise afterwards.
+ */
+ tail = len % 4;
+
+ ret = __read_prefetch_aligned(chip, (uint32_t *) buf, len - tail);
+ if (ret < 0) {
+ /* fallback in case the prefetch engine is busy */
+ nand_read_buf(mtd, buf, len);
+ } else if (tail) {
+ buf += len - tail;
+ nand_read_buf(mtd, buf, tail);
+ }
+}
+#endif /* CONFIG_NAND_OMAP_GPMC_PREFETCH */
+
/**
* omap_read_page_bch - hardware ecc based page read function
* @mtd: mtd info structure
@@ -880,11 +989,12 @@ int board_nand_init(struct nand_chip *nand)
if (err)
return err;
-#ifdef CONFIG_SPL_BUILD
+#ifdef CONFIG_NAND_OMAP_GPMC_PREFETCH
+ /* TODO: Implement for 16-bit bus width */
if (nand->options & NAND_BUSWIDTH_16)
nand->read_buf = nand_read_buf16;
else
- nand->read_buf = nand_read_buf;
+ nand->read_buf = omap_nand_read_prefetch8;
#endif
nand->dev_ready = omap_dev_ready;
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index fb0cf8c..46c4ac6 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -66,3 +66,4 @@ obj-$(CONFIG_XILINX_LL_TEMAC) += xilinx_ll_temac.o xilinx_ll_temac_mdio.o \
xilinx_ll_temac_fifo.o xilinx_ll_temac_sdma.o
obj-$(CONFIG_ZYNQ_GEM) += zynq_gem.o
obj-$(CONFIG_FSL_MC_ENET) += fsl_mc/
+obj-$(CONFIG_VSC9953) += vsc9953.o
diff --git a/drivers/net/fm/eth.c b/drivers/net/fm/eth.c
index f1e39b9..1d1089d 100644
--- a/drivers/net/fm/eth.c
+++ b/drivers/net/fm/eth.c
@@ -410,10 +410,15 @@ static int fm_eth_open(struct eth_device *dev, bd_t *bd)
fmc_tx_port_graceful_stop_disable(fm_eth);
#ifdef CONFIG_PHYLIB
- ret = phy_startup(fm_eth->phydev);
- if (ret) {
- printf("%s: Could not initialize\n", fm_eth->phydev->dev->name);
- return ret;
+ if (fm_eth->phydev) {
+ ret = phy_startup(fm_eth->phydev);
+ if (ret) {
+ printf("%s: Could not initialize\n",
+ fm_eth->phydev->dev->name);
+ return ret;
+ }
+ } else {
+ return 0;
}
#else
fm_eth->phydev->speed = SPEED_1000;
@@ -447,7 +452,8 @@ static void fm_eth_halt(struct eth_device *dev)
/* disable bmi Rx port */
bmi_rx_port_disable(fm_eth->rx_port);
- phy_shutdown(fm_eth->phydev);
+ if (fm_eth->phydev)
+ phy_shutdown(fm_eth->phydev);
}
static int fm_eth_send(struct eth_device *dev, void *buf, int len)
@@ -625,11 +631,12 @@ static int init_phy(struct eth_device *dev)
if (fm_eth->bus) {
phydev = phy_connect(fm_eth->bus, fm_eth->phyaddr, dev,
fm_eth->enet_if);
- }
-
- if (!phydev) {
- printf("Failed to connect\n");
- return -1;
+ if (!phydev) {
+ printf("Failed to connect\n");
+ return -1;
+ }
+ } else {
+ return 0;
}
if (fm_eth->type == FM_ETH_1G_E) {
@@ -711,8 +718,7 @@ int fm_eth_initialize(struct ccsr_fman *reg, struct fm_eth_info *info)
if (!fm_eth_startup(fm_eth))
return 0;
- if (init_phy(dev))
- return 0;
+ init_phy(dev);
/* clear the ethernet address */
for (i = 0; i < 6; i++)
diff --git a/drivers/net/fm/t1040.c b/drivers/net/fm/t1040.c
index d2a097e..0458366 100644
--- a/drivers/net/fm/t1040.c
+++ b/drivers/net/fm/t1040.c
@@ -50,7 +50,8 @@ phy_interface_t fman_port_enet_if(enum fm_port port)
switch (port) {
case FM1_DTSEC1:
case FM1_DTSEC2:
- if (is_serdes_configured(QSGMII_SW1_A + port - FM1_DTSEC1))
+ if (is_serdes_configured(QSGMII_SW1_A + port - FM1_DTSEC1) ||
+ is_serdes_configured(SGMII_SW1_MAC1 + port - FM1_DTSEC1))
return PHY_INTERFACE_MODE_QSGMII;
case FM1_DTSEC3:
case FM1_DTSEC4:
diff --git a/drivers/net/mpc5xxx_fec.c b/drivers/net/mpc5xxx_fec.c
index d9d6f4f..d2a8ae0 100644
--- a/drivers/net/mpc5xxx_fec.c
+++ b/drivers/net/mpc5xxx_fec.c
@@ -407,13 +407,8 @@ static int mpc5xxx_fec_init_phy(struct eth_device *dev, bd_t * bis)
*/
if (fec->xcv_type == SEVENWIRE) {
/* 10MBit with 7-wire operation */
-#if defined(CONFIG_TOTAL5200)
- /* 7-wire and USB2 on Ethernet */
- *(vu_long *)MPC5XXX_GPS_PORT_CONFIG |= 0x00030000;
-#else /* !CONFIG_TOTAL5200 */
/* 7-wire only */
*(vu_long *)MPC5XXX_GPS_PORT_CONFIG |= 0x00020000;
-#endif /* CONFIG_TOTAL5200 */
} else {
/* 100MBit with MD operation */
*(vu_long *)MPC5XXX_GPS_PORT_CONFIG |= 0x00050000;
diff --git a/drivers/net/mvgbe.c b/drivers/net/mvgbe.c
index 6ef6cac..6b31a82 100644
--- a/drivers/net/mvgbe.c
+++ b/drivers/net/mvgbe.c
@@ -35,6 +35,10 @@
DECLARE_GLOBAL_DATA_PTR;
+#ifndef CONFIG_MVGBE_PORTS
+# define CONFIG_MVGBE_PORTS {0, 0}
+#endif
+
#define MV_PHY_ADR_REQUEST 0xee
#define MVGBE_SMI_REG (((struct mvgbe_registers *)MVGBE0_BASE)->smi)
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index f46bf00..d096db8 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -11,6 +11,7 @@ obj-$(CONFIG_MV88E6352_SWITCH) += mv88e6352.o
obj-$(CONFIG_PHYLIB) += phy.o
obj-$(CONFIG_PHYLIB_10G) += generic_10g.o
+obj-$(CONFIG_PHY_AQUANTIA) += aquantia.o
obj-$(CONFIG_PHY_ATHEROS) += atheros.o
obj-$(CONFIG_PHY_BROADCOM) += broadcom.o
obj-$(CONFIG_PHY_CORTINA) += cortina.o
diff --git a/drivers/net/phy/aquantia.c b/drivers/net/phy/aquantia.c
new file mode 100644
index 0000000..ef4da4e
--- /dev/null
+++ b/drivers/net/phy/aquantia.c
@@ -0,0 +1,156 @@
+/*
+ * Aquantia PHY drivers
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * Copyright 2014 Freescale Semiconductor, Inc.
+ */
+#include <config.h>
+#include <common.h>
+#include <phy.h>
+
+#ifndef CONFIG_PHYLIB_10G
+#error The Aquantia PHY needs 10G support
+#endif
+
+#define AQUNTIA_10G_CTL 0x20
+#define AQUNTIA_VENDOR_P1 0xc400
+
+#define AQUNTIA_SPEED_LSB_MASK 0x2000
+#define AQUNTIA_SPEED_MSB_MASK 0x40
+
+int aquantia_config(struct phy_device *phydev)
+{
+ u32 val = phy_read(phydev, MDIO_MMD_PMAPMD, MII_BMCR);
+
+ if (phydev->interface == PHY_INTERFACE_MODE_SGMII) {
+ /* 1000BASE-T mode */
+ phydev->advertising = SUPPORTED_1000baseT_Full;
+ phydev->supported = phydev->advertising;
+
+ val = (val & ~AQUNTIA_SPEED_LSB_MASK) | AQUNTIA_SPEED_MSB_MASK;
+ phy_write(phydev, MDIO_MMD_PMAPMD, MII_BMCR, val);
+ } else if (phydev->interface == PHY_INTERFACE_MODE_XGMII) {
+ /* 10GBASE-T mode */
+ phydev->advertising = SUPPORTED_10000baseT_Full;
+ phydev->supported = phydev->advertising;
+
+ if (!(val & AQUNTIA_SPEED_LSB_MASK) ||
+ !(val & AQUNTIA_SPEED_MSB_MASK))
+ phy_write(phydev, MDIO_MMD_PMAPMD, MII_BMCR,
+ AQUNTIA_SPEED_LSB_MASK |
+ AQUNTIA_SPEED_MSB_MASK);
+ } else if (phydev->interface == PHY_INTERFACE_MODE_SGMII_2500) {
+ /* 2.5GBASE-T mode */
+ phydev->advertising = SUPPORTED_1000baseT_Full;
+ phydev->supported = phydev->advertising;
+
+ phy_write(phydev, MDIO_MMD_AN, AQUNTIA_10G_CTL, 1);
+ phy_write(phydev, MDIO_MMD_AN, AQUNTIA_VENDOR_P1, 0x9440);
+ } else if (phydev->interface == PHY_INTERFACE_MODE_MII) {
+ /* 100BASE-TX mode */
+ phydev->advertising = SUPPORTED_100baseT_Full;
+ phydev->supported = phydev->advertising;
+
+ val = (val & ~AQUNTIA_SPEED_MSB_MASK) | AQUNTIA_SPEED_LSB_MASK;
+ phy_write(phydev, MDIO_MMD_PMAPMD, MII_BMCR, val);
+ }
+ return 0;
+}
+
+int aquantia_startup(struct phy_device *phydev)
+{
+ u32 reg, speed;
+ int i = 0;
+
+ phydev->duplex = DUPLEX_FULL;
+
+ /* if the AN is still in progress, wait till timeout. */
+ phy_read(phydev, MDIO_MMD_AN, MDIO_STAT1);
+ reg = phy_read(phydev, MDIO_MMD_AN, MDIO_STAT1);
+ if (!(reg & MDIO_AN_STAT1_COMPLETE)) {
+ printf("%s Waiting for PHY auto negotiation to complete",
+ phydev->dev->name);
+ do {
+ udelay(1000);
+ reg = phy_read(phydev, MDIO_MMD_AN, MDIO_STAT1);
+ if ((i++ % 500) == 0)
+ printf(".");
+ } while (!(reg & MDIO_AN_STAT1_COMPLETE) &&
+ i < (4 * PHY_ANEG_TIMEOUT));
+
+ if (i > PHY_ANEG_TIMEOUT)
+ printf(" TIMEOUT !\n");
+ }
+
+ /* Read twice because link state is latched and a
+ * read moves the current state into the register */
+ phy_read(phydev, MDIO_MMD_AN, MDIO_STAT1);
+ reg = phy_read(phydev, MDIO_MMD_AN, MDIO_STAT1);
+ if (reg < 0 || !(reg & MDIO_STAT1_LSTATUS))
+ phydev->link = 0;
+ else
+ phydev->link = 1;
+
+ speed = phy_read(phydev, MDIO_MMD_PMAPMD, MII_BMCR);
+ if (speed & AQUNTIA_SPEED_MSB_MASK) {
+ if (speed & AQUNTIA_SPEED_LSB_MASK)
+ phydev->speed = SPEED_10000;
+ else
+ phydev->speed = SPEED_1000;
+ } else {
+ if (speed & AQUNTIA_SPEED_LSB_MASK)
+ phydev->speed = SPEED_100;
+ else
+ phydev->speed = SPEED_10;
+ }
+
+ return 0;
+}
+
+struct phy_driver aq1202_driver = {
+ .name = "Aquantia AQ1202",
+ .uid = 0x3a1b445,
+ .mask = 0xfffffff0,
+ .features = PHY_10G_FEATURES,
+ .mmds = (MDIO_MMD_PMAPMD | MDIO_MMD_PCS|
+ MDIO_MMD_PHYXS | MDIO_MMD_AN |
+ MDIO_MMD_VEND1),
+ .config = &aquantia_config,
+ .startup = &aquantia_startup,
+ .shutdown = &gen10g_shutdown,
+};
+
+struct phy_driver aq2104_driver = {
+ .name = "Aquantia AQ2104",
+ .uid = 0x3a1b460,
+ .mask = 0xfffffff0,
+ .features = PHY_10G_FEATURES,
+ .mmds = (MDIO_MMD_PMAPMD | MDIO_MMD_PCS|
+ MDIO_MMD_PHYXS | MDIO_MMD_AN |
+ MDIO_MMD_VEND1),
+ .config = &aquantia_config,
+ .startup = &aquantia_startup,
+ .shutdown = &gen10g_shutdown,
+};
+
+struct phy_driver aqr105_driver = {
+ .name = "Aquantia AQR105",
+ .uid = 0x3a1b4a2,
+ .mask = 0xfffffff0,
+ .features = PHY_10G_FEATURES,
+ .mmds = (MDIO_MMD_PMAPMD | MDIO_MMD_PCS|
+ MDIO_MMD_PHYXS | MDIO_MMD_AN |
+ MDIO_MMD_VEND1),
+ .config = &aquantia_config,
+ .startup = &aquantia_startup,
+ .shutdown = &gen10g_shutdown,
+};
+int phy_aquantia_init(void)
+{
+ phy_register(&aq1202_driver);
+ phy_register(&aq2104_driver);
+ phy_register(&aqr105_driver);
+
+ return 0;
+}
diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c
index 5b04c85..df7e945 100644
--- a/drivers/net/phy/phy.c
+++ b/drivers/net/phy/phy.c
@@ -442,6 +442,9 @@ static LIST_HEAD(phy_drivers);
int phy_init(void)
{
+#ifdef CONFIG_PHY_AQUANTIA
+ phy_aquantia_init();
+#endif
#ifdef CONFIG_PHY_ATHEROS
phy_atheros_init();
#endif
diff --git a/drivers/net/vsc9953.c b/drivers/net/vsc9953.c
new file mode 100644
index 0000000..9fc3c18
--- /dev/null
+++ b/drivers/net/vsc9953.c
@@ -0,0 +1,497 @@
+/*
+ * Copyright 2014 Freescale Semiconductor, Inc.
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * Driver for the Vitesse VSC9953 L2 Switch
+ */
+
+#include <asm/io.h>
+#include <asm/fsl_serdes.h>
+#include <fm_eth.h>
+#include <asm/fsl_memac.h>
+#include <vsc9953.h>
+
+static struct vsc9953_info vsc9953_l2sw = {
+ .port[0] = VSC9953_PORT_INFO_INITIALIZER(0),
+ .port[1] = VSC9953_PORT_INFO_INITIALIZER(1),
+ .port[2] = VSC9953_PORT_INFO_INITIALIZER(2),
+ .port[3] = VSC9953_PORT_INFO_INITIALIZER(3),
+ .port[4] = VSC9953_PORT_INFO_INITIALIZER(4),
+ .port[5] = VSC9953_PORT_INFO_INITIALIZER(5),
+ .port[6] = VSC9953_PORT_INFO_INITIALIZER(6),
+ .port[7] = VSC9953_PORT_INFO_INITIALIZER(7),
+ .port[8] = VSC9953_PORT_INFO_INITIALIZER(8),
+ .port[9] = VSC9953_PORT_INFO_INITIALIZER(9),
+};
+
+void vsc9953_port_info_set_mdio(int port, struct mii_dev *bus)
+{
+ if (!VSC9953_PORT_CHECK(port))
+ return;
+
+ vsc9953_l2sw.port[port].bus = bus;
+}
+
+void vsc9953_port_info_set_phy_address(int port, int address)
+{
+ if (!VSC9953_PORT_CHECK(port))
+ return;
+
+ vsc9953_l2sw.port[port].phyaddr = address;
+}
+
+void vsc9953_port_info_set_phy_int(int port, phy_interface_t phy_int)
+{
+ if (!VSC9953_PORT_CHECK(port))
+ return;
+
+ vsc9953_l2sw.port[port].enet_if = phy_int;
+}
+
+void vsc9953_port_enable(int port)
+{
+ if (!VSC9953_PORT_CHECK(port))
+ return;
+
+ vsc9953_l2sw.port[port].enabled = 1;
+}
+
+void vsc9953_port_disable(int port)
+{
+ if (!VSC9953_PORT_CHECK(port))
+ return;
+
+ vsc9953_l2sw.port[port].enabled = 0;
+}
+
+static void vsc9953_mdio_write(struct vsc9953_mii_mng *phyregs, int port_addr,
+ int regnum, int value)
+{
+ int timeout = 50000;
+
+ out_le32(&phyregs->miimcmd, (0x1 << 31) | ((port_addr & 0x1f) << 25) |
+ ((regnum & 0x1f) << 20) | ((value & 0xffff) << 4) |
+ (0x1 << 1));
+ asm("sync");
+
+ while ((in_le32(&phyregs->miimstatus) & 0x8) && --timeout)
+ udelay(1);
+
+ if (timeout == 0)
+ debug("Timeout waiting for MDIO write\n");
+}
+
+static int vsc9953_mdio_read(struct vsc9953_mii_mng *phyregs, int port_addr,
+ int regnum)
+{
+ int value = 0xFFFF;
+ int timeout = 50000;
+
+ while ((in_le32(&phyregs->miimstatus) & MIIMIND_OPR_PEND) && --timeout)
+ udelay(1);
+ if (timeout == 0) {
+ debug("Timeout waiting for MDIO operation to finish\n");
+ return value;
+ }
+
+ /* Put the address of the phy, and the register
+ * number into MIICMD
+ */
+ out_le32(&phyregs->miimcmd, (0x1 << 31) | ((port_addr & 0x1f) << 25) |
+ ((regnum & 0x1f) << 20) | ((value & 0xffff) << 4) |
+ (0x2 << 1));
+
+ timeout = 50000;
+ /* Wait for the the indication that the read is done */
+ while ((in_le32(&phyregs->miimstatus) & 0x8) && --timeout)
+ udelay(1);
+ if (timeout == 0)
+ debug("Timeout waiting for MDIO read\n");
+
+ /* Grab the value read from the PHY */
+ value = in_le32(&phyregs->miimdata);
+
+ if ((value & 0x00030000) == 0)
+ return value & 0x0000ffff;
+
+ return value;
+}
+
+static int init_phy(struct eth_device *dev)
+{
+ struct vsc9953_port_info *l2sw_port = dev->priv;
+ struct phy_device *phydev = NULL;
+
+#ifdef CONFIG_PHYLIB
+ if (!l2sw_port->bus)
+ return 0;
+ phydev = phy_connect(l2sw_port->bus, l2sw_port->phyaddr, dev,
+ l2sw_port->enet_if);
+ if (!phydev) {
+ printf("Failed to connect\n");
+ return -1;
+ }
+
+ phydev->supported &= SUPPORTED_10baseT_Half |
+ SUPPORTED_10baseT_Full |
+ SUPPORTED_100baseT_Half |
+ SUPPORTED_100baseT_Full |
+ SUPPORTED_1000baseT_Full;
+ phydev->advertising = phydev->supported;
+
+ l2sw_port->phydev = phydev;
+
+ phy_config(phydev);
+#endif
+
+ return 0;
+}
+
+static int vsc9953_port_init(int port)
+{
+ struct eth_device *dev;
+
+ /* Internal ports never have a PHY */
+ if (VSC9953_INTERNAL_PORT_CHECK(port))
+ return 0;
+
+ /* alloc eth device */
+ dev = (struct eth_device *)calloc(1, sizeof(struct eth_device));
+ if (!dev)
+ return 1;
+
+ sprintf(dev->name, "SW@PORT%d", port);
+ dev->priv = &vsc9953_l2sw.port[port];
+ dev->init = NULL;
+ dev->halt = NULL;
+ dev->send = NULL;
+ dev->recv = NULL;
+
+ if (init_phy(dev)) {
+ free(dev);
+ return 1;
+ }
+
+ return 0;
+}
+
+void vsc9953_init(bd_t *bis)
+{
+ u32 i, hdx_cfg = 0, phy_addr = 0;
+ int timeout;
+ struct vsc9953_system_reg *l2sys_reg;
+ struct vsc9953_qsys_reg *l2qsys_reg;
+ struct vsc9953_dev_gmii *l2dev_gmii_reg;
+ struct vsc9953_analyzer *l2ana_reg;
+ struct vsc9953_devcpu_gcb *l2dev_gcb;
+
+ l2dev_gmii_reg = (struct vsc9953_dev_gmii *)(VSC9953_OFFSET +
+ VSC9953_DEV_GMII_OFFSET);
+
+ l2ana_reg = (struct vsc9953_analyzer *)(VSC9953_OFFSET +
+ VSC9953_ANA_OFFSET);
+
+ l2sys_reg = (struct vsc9953_system_reg *)(VSC9953_OFFSET +
+ VSC9953_SYS_OFFSET);
+
+ l2qsys_reg = (struct vsc9953_qsys_reg *)(VSC9953_OFFSET +
+ VSC9953_QSYS_OFFSET);
+
+ l2dev_gcb = (struct vsc9953_devcpu_gcb *)(VSC9953_OFFSET +
+ VSC9953_DEVCPU_GCB);
+
+ out_le32(&l2dev_gcb->chip_regs.soft_rst,
+ CONFIG_VSC9953_SOFT_SWC_RST_ENA);
+ timeout = 50000;
+ while ((in_le32(&l2dev_gcb->chip_regs.soft_rst) &
+ CONFIG_VSC9953_SOFT_SWC_RST_ENA) && --timeout)
+ udelay(1); /* busy wait for vsc9953 soft reset */
+ if (timeout == 0)
+ debug("Timeout waiting for VSC9953 to reset\n");
+
+ out_le32(&l2sys_reg->sys.reset_cfg, CONFIG_VSC9953_MEM_ENABLE |
+ CONFIG_VSC9953_MEM_INIT);
+
+ timeout = 50000;
+ while ((in_le32(&l2sys_reg->sys.reset_cfg) &
+ CONFIG_VSC9953_MEM_INIT) && --timeout)
+ udelay(1); /* busy wait for vsc9953 memory init */
+ if (timeout == 0)
+ debug("Timeout waiting for VSC9953 memory to initialize\n");
+
+ out_le32(&l2sys_reg->sys.reset_cfg, (in_le32(&l2sys_reg->sys.reset_cfg)
+ | CONFIG_VSC9953_CORE_ENABLE));
+
+ /* VSC9953 Setting to be done once only */
+ out_le32(&l2qsys_reg->sys.ext_cpu_cfg, 0x00000b00);
+
+ for (i = 0; i < VSC9953_MAX_PORTS; i++) {
+ if (vsc9953_port_init(i))
+ printf("Failed to initialize l2switch port %d\n", i);
+
+ /* Enable VSC9953 GMII Ports Port ID 0 - 7 */
+ if (VSC9953_INTERNAL_PORT_CHECK(i)) {
+ out_le32(&l2ana_reg->pfc[i].pfc_cfg,
+ CONFIG_VSC9953_PFC_FC_QSGMII);
+ out_le32(&l2sys_reg->pause_cfg.mac_fc_cfg[i],
+ CONFIG_VSC9953_MAC_FC_CFG_QSGMII);
+ } else {
+ out_le32(&l2ana_reg->pfc[i].pfc_cfg,
+ CONFIG_VSC9953_PFC_FC);
+ out_le32(&l2sys_reg->pause_cfg.mac_fc_cfg[i],
+ CONFIG_VSC9953_MAC_FC_CFG);
+ }
+ out_le32(&l2dev_gmii_reg->port_mode.clock_cfg,
+ CONFIG_VSC9953_CLOCK_CFG);
+ out_le32(&l2dev_gmii_reg->mac_cfg_status.mac_ena_cfg,
+ CONFIG_VSC9953_MAC_ENA_CFG);
+ out_le32(&l2dev_gmii_reg->mac_cfg_status.mac_mode_cfg,
+ CONFIG_VSC9953_MAC_MODE_CFG);
+ out_le32(&l2dev_gmii_reg->mac_cfg_status.mac_ifg_cfg,
+ CONFIG_VSC9953_MAC_IFG_CFG);
+ /* mac_hdx_cfg varies with port id*/
+ hdx_cfg = (CONFIG_VSC9953_MAC_HDX_CFG | (i << 16));
+ out_le32(&l2dev_gmii_reg->mac_cfg_status.mac_hdx_cfg, hdx_cfg);
+ out_le32(&l2sys_reg->sys.front_port_mode[i],
+ CONFIG_VSC9953_FRONT_PORT_MODE);
+ out_le32(&l2qsys_reg->sys.switch_port_mode[i],
+ CONFIG_VSC9953_PORT_ENA);
+ out_le32(&l2dev_gmii_reg->mac_cfg_status.mac_maxlen_cfg,
+ CONFIG_VSC9953_MAC_MAX_LEN);
+ out_le32(&l2sys_reg->pause_cfg.pause_cfg[i],
+ CONFIG_VSC9953_PAUSE_CFG);
+ /* WAIT FOR 2 us*/
+ udelay(2);
+
+ l2dev_gmii_reg = (struct vsc9953_dev_gmii *)(
+ (char *)l2dev_gmii_reg
+ + T1040_SWITCH_GMII_DEV_OFFSET);
+
+ /* Initialize Lynx PHY Wrappers */
+ phy_addr = 0;
+ if (vsc9953_l2sw.port[i].enet_if ==
+ PHY_INTERFACE_MODE_QSGMII)
+ phy_addr = (i + 0x4) & 0x1F;
+ else if (vsc9953_l2sw.port[i].enet_if ==
+ PHY_INTERFACE_MODE_SGMII)
+ phy_addr = (i + 1) & 0x1F;
+
+ if (phy_addr) {
+ /* SGMII IF mode + AN enable */
+ vsc9953_mdio_write(&l2dev_gcb->mii_mng[0], phy_addr,
+ 0x14, PHY_SGMII_IF_MODE_AN |
+ PHY_SGMII_IF_MODE_SGMII);
+ /* Dev ability according to SGMII specification */
+ vsc9953_mdio_write(&l2dev_gcb->mii_mng[0], phy_addr,
+ 0x4, PHY_SGMII_DEV_ABILITY_SGMII);
+ /* Adjust link timer for SGMII
+ * 1.6 ms in units of 8 ns = 2 * 10^5 = 0x30d40
+ */
+ vsc9953_mdio_write(&l2dev_gcb->mii_mng[0], phy_addr,
+ 0x13, 0x0003);
+ vsc9953_mdio_write(&l2dev_gcb->mii_mng[0], phy_addr,
+ 0x12, 0x0d40);
+ /* Restart AN */
+ vsc9953_mdio_write(&l2dev_gcb->mii_mng[0], phy_addr,
+ 0x0, PHY_SGMII_CR_DEF_VAL |
+ PHY_SGMII_CR_RESET_AN);
+
+ timeout = 50000;
+ while ((vsc9953_mdio_read(&l2dev_gcb->mii_mng[0],
+ phy_addr, 0x01) & 0x0020) && --timeout)
+ udelay(1); /* wait for AN to complete */
+ if (timeout == 0)
+ debug("Timeout waiting for AN to complete\n");
+ }
+ }
+
+ printf("VSC9953 L2 switch initialized\n");
+ return;
+}
+
+#ifdef CONFIG_VSC9953_CMD
+/* Enable/disable status of a VSC9953 port */
+static void vsc9953_port_status_set(int port_nr, u8 enabled)
+{
+ u32 val;
+ struct vsc9953_qsys_reg *l2qsys_reg;
+
+ /* Administrative down */
+ if (vsc9953_l2sw.port[port_nr].enabled == 0)
+ return;
+
+ l2qsys_reg = (struct vsc9953_qsys_reg *)(VSC9953_OFFSET +
+ VSC9953_QSYS_OFFSET);
+
+ val = in_le32(&l2qsys_reg->sys.switch_port_mode[port_nr]);
+ if (enabled == 1)
+ val |= (1 << 13);
+ else
+ val &= ~(1 << 13);
+
+ out_le32(&l2qsys_reg->sys.switch_port_mode[port_nr], val);
+}
+
+/* Set all VSC9953 ports' status */
+static void vsc9953_port_all_status_set(u8 enabled)
+{
+ int i;
+
+ for (i = 0; i < VSC9953_MAX_PORTS; i++)
+ vsc9953_port_status_set(i, enabled);
+}
+
+/* Start autonegotiation for a VSC9953 PHY */
+static void vsc9953_phy_autoneg(int port_nr)
+{
+ if (!vsc9953_l2sw.port[port_nr].phydev)
+ return;
+
+ if (vsc9953_l2sw.port[port_nr].phydev->drv->startup(
+ vsc9953_l2sw.port[port_nr].phydev))
+ printf("Failed to start PHY for port %d\n", port_nr);
+}
+
+/* Start autonegotiation for all VSC9953 PHYs */
+static void vsc9953_phy_all_autoneg(void)
+{
+ int i;
+
+ for (i = 0; i < VSC9953_MAX_PORTS; i++)
+ vsc9953_phy_autoneg(i);
+}
+
+/* Print a VSC9953 port's configuration */
+static void vsc9953_port_config_show(int port)
+{
+ int speed;
+ int duplex;
+ int link;
+ u8 enabled;
+ u32 val;
+ struct vsc9953_qsys_reg *l2qsys_reg;
+
+ l2qsys_reg = (struct vsc9953_qsys_reg *)(VSC9953_OFFSET +
+ VSC9953_QSYS_OFFSET);
+
+ val = in_le32(&l2qsys_reg->sys.switch_port_mode[port]);
+ enabled = vsc9953_l2sw.port[port].enabled &
+ ((val & 0x00002000) >> 13);
+
+ /* internal ports (8 and 9) are fixed */
+ if (VSC9953_INTERNAL_PORT_CHECK(port)) {
+ link = 1;
+ speed = SPEED_2500;
+ duplex = DUPLEX_FULL;
+ } else {
+ if (vsc9953_l2sw.port[port].phydev) {
+ link = vsc9953_l2sw.port[port].phydev->link;
+ speed = vsc9953_l2sw.port[port].phydev->speed;
+ duplex = vsc9953_l2sw.port[port].phydev->duplex;
+ } else {
+ link = -1;
+ speed = -1;
+ duplex = -1;
+ }
+ }
+
+ printf("%8d ", port);
+ printf("%8s ", enabled == 1 ? "enabled" : "disabled");
+ printf("%8s ", link == 1 ? "up" : "down");
+
+ switch (speed) {
+ case SPEED_10:
+ printf("%8d ", 10);
+ break;
+ case SPEED_100:
+ printf("%8d ", 100);
+ break;
+ case SPEED_1000:
+ printf("%8d ", 1000);
+ break;
+ case SPEED_2500:
+ printf("%8d ", 2500);
+ break;
+ case SPEED_10000:
+ printf("%8d ", 10000);
+ break;
+ default:
+ printf("%8s ", "-");
+ }
+
+ printf("%8s\n", duplex == DUPLEX_FULL ? "full" : "half");
+}
+
+/* Print VSC9953 ports' configuration */
+static void vsc9953_port_all_config_show(void)
+{
+ int i;
+
+ for (i = 0; i < VSC9953_MAX_PORTS; i++)
+ vsc9953_port_config_show(i);
+}
+
+/* function to interpret commands starting with "ethsw " */
+static int do_ethsw(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
+{
+ u8 enable;
+ u32 port;
+
+ if (argc < 4)
+ return -1;
+
+ if (strcmp(argv[1], "port"))
+ return -1;
+
+ if (!strcmp(argv[3], "show")) {
+ if (!strcmp(argv[2], "all")) {
+ vsc9953_phy_all_autoneg();
+ printf("%8s %8s %8s %8s %8s\n",
+ "Port", "Status", "Link", "Speed",
+ "Duplex");
+ vsc9953_port_all_config_show();
+ return 0;
+ } else {
+ port = simple_strtoul(argv[2], NULL, 10);
+ if (!VSC9953_PORT_CHECK(port))
+ return -1;
+ vsc9953_phy_autoneg(port);
+ printf("%8s %8s %8s %8s %8s\n",
+ "Port", "Status", "Link", "Speed",
+ "Duplex");
+ vsc9953_port_config_show(port);
+ return 0;
+ }
+ } else if (!strcmp(argv[3], "enable")) {
+ enable = 1;
+ } else if (!strcmp(argv[3], "disable")) {
+ enable = 0;
+ } else {
+ return -1;
+ }
+
+ if (!strcmp(argv[2], "all")) {
+ vsc9953_port_all_status_set(enable);
+ return 0;
+ } else {
+ port = simple_strtoul(argv[2], NULL, 10);
+ if (!VSC9953_PORT_CHECK(port))
+ return -1;
+ vsc9953_port_status_set(port, enable);
+ return 0;
+ }
+
+ return -1;
+}
+
+U_BOOT_CMD(ethsw, 5, 0, do_ethsw,
+ "vsc9953 l2 switch commands",
+ "port <port_nr> enable|disable\n"
+ " - enable/disable an l2 switch port\n"
+ " port_nr=0..9; use \"all\" for all ports\n"
+ "ethsw port <port_nr> show\n"
+ " - show an l2 switch port's configuration\n"
+ " port_nr=0..9; use \"all\" for all ports\n"
+);
+#endif /* CONFIG_VSC9953_CMD */
diff --git a/drivers/net/xilinx_ll_temac.c b/drivers/net/xilinx_ll_temac.c
index dab78d0..7cc8657 100644
--- a/drivers/net/xilinx_ll_temac.c
+++ b/drivers/net/xilinx_ll_temac.c
@@ -231,7 +231,7 @@ static int ll_temac_init(struct eth_device *dev, bd_t *bis)
struct ll_temac *ll_temac = dev->priv;
int ret;
- printf("%s: Xilinx XPS LocalLink Tri-Mode Ether MAC #%d at 0x%08X.\n",
+ printf("%s: Xilinx XPS LocalLink Tri-Mode Ether MAC #%d at 0x%08lx.\n",
dev->name, dev->index, dev->iobase);
if (!ll_temac_setup_ctrl(dev))
diff --git a/drivers/net/zynq_gem.c b/drivers/net/zynq_gem.c
index 3cadd23..430e228 100644
--- a/drivers/net/zynq_gem.c
+++ b/drivers/net/zynq_gem.c
@@ -489,7 +489,8 @@ static int zynq_gem_miiphy_write(const char *devname, uchar addr,
return phywrite(dev, addr, reg, val);
}
-int zynq_gem_initialize(bd_t *bis, int base_addr, int phy_addr, u32 emio)
+int zynq_gem_initialize(bd_t *bis, phys_addr_t base_addr,
+ int phy_addr, u32 emio)
{
struct eth_device *dev;
struct zynq_gem_priv *priv;
@@ -521,7 +522,7 @@ int zynq_gem_initialize(bd_t *bis, int base_addr, int phy_addr, u32 emio)
priv->phyaddr = phy_addr;
priv->emio = emio;
- sprintf(dev->name, "Gem.%x", base_addr);
+ sprintf(dev->name, "Gem.%lx", base_addr);
dev->iobase = base_addr;
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index 3daf73c..950a247 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -19,6 +19,8 @@
#include <asm/io.h>
#include <pci.h>
+DECLARE_GLOBAL_DATA_PTR;
+
#define PCI_HOSE_OP(rw, size, type) \
int pci_hose_##rw##_config_##size(struct pci_controller *hose, \
pci_dev_t dev, \
@@ -123,6 +125,14 @@ void *pci_map_bar(pci_dev_t pdev, int bar, int flags)
static struct pci_controller* hose_head;
+struct pci_controller *pci_get_hose_head(void)
+{
+ if (gd->hose)
+ return gd->hose;
+
+ return hose_head;
+}
+
void pci_register_hose(struct pci_controller* hose)
{
struct pci_controller **phose = &hose_head;
@@ -139,7 +149,7 @@ struct pci_controller *pci_bus_to_hose(int bus)
{
struct pci_controller *hose;
- for (hose = hose_head; hose; hose = hose->next) {
+ for (hose = pci_get_hose_head(); hose; hose = hose->next) {
if (bus >= hose->first_busno && bus <= hose->last_busno)
return hose;
}
@@ -152,7 +162,7 @@ struct pci_controller *find_hose_by_cfg_addr(void *cfg_addr)
{
struct pci_controller *hose;
- for (hose = hose_head; hose; hose = hose->next) {
+ for (hose = pci_get_hose_head(); hose; hose = hose->next) {
if (hose->cfg_addr == cfg_addr)
return hose;
}
@@ -162,7 +172,7 @@ struct pci_controller *find_hose_by_cfg_addr(void *cfg_addr)
int pci_last_busno(void)
{
- struct pci_controller *hose = hose_head;
+ struct pci_controller *hose = pci_get_hose_head();
if (!hose)
return -1;
@@ -181,19 +191,14 @@ pci_dev_t pci_find_devices(struct pci_device_id *ids, int index)
pci_dev_t bdf;
int i, bus, found_multi = 0;
- for (hose = hose_head; hose; hose = hose->next) {
+ for (hose = pci_get_hose_head(); hose; hose = hose->next) {
#ifdef CONFIG_SYS_SCSI_SCAN_BUS_REVERSE
for (bus = hose->last_busno; bus >= hose->first_busno; bus--)
#else
for (bus = hose->first_busno; bus <= hose->last_busno; bus++)
#endif
for (bdf = PCI_BDF(bus, 0, 0);
-#if defined(CONFIG_ELPPC) || defined(CONFIG_PPMC7XX)
- bdf < PCI_BDF(bus, PCI_MAX_PCI_DEVICES - 1,
- PCI_MAX_PCI_FUNCTIONS - 1);
-#else
bdf < PCI_BDF(bus + 1, 0, 0);
-#endif
bdf += PCI_BDF(0, 0, 1)) {
if (pci_skip_dev(hose, bdf))
continue;
@@ -233,7 +238,7 @@ pci_dev_t pci_find_devices(struct pci_device_id *ids, int index)
pci_dev_t pci_find_device(unsigned int vendor, unsigned int device, int index)
{
- static struct pci_device_id ids[2] = {{}, {0, 0}};
+ struct pci_device_id ids[2] = { {}, {0, 0} };
ids[0].vendor = vendor;
ids[0].device = device;
@@ -709,11 +714,10 @@ int pci_hose_scan_bus(struct pci_controller *hose, int bus)
int pci_hose_scan(struct pci_controller *hose)
{
#if defined(CONFIG_PCI_BOOTDELAY)
- static int pcidelay_done;
char *s;
int i;
- if (!pcidelay_done) {
+ if (!gd->pcidelay_done) {
/* wait "pcidelay" ms (if defined)... */
s = getenv("pcidelay");
if (s) {
@@ -721,7 +725,7 @@ int pci_hose_scan(struct pci_controller *hose)
for (i = 0; i < val; i++)
udelay(1000);
}
- pcidelay_done = 1;
+ gd->pcidelay_done = 1;
}
#endif /* CONFIG_PCI_BOOTDELAY */
diff --git a/drivers/pci/pci_rom.c b/drivers/pci/pci_rom.c
index af6a3ae..7d25cc9 100644
--- a/drivers/pci/pci_rom.c
+++ b/drivers/pci/pci_rom.c
@@ -156,6 +156,8 @@ int pci_rom_load(uint16_t class, struct pci_rom_header *rom_header,
target = (void *)PCI_VGA_RAM_IMAGE_START;
if (target != rom_header) {
+ ulong start = get_timer(0);
+
debug("Copying VGA ROM Image from %p to %p, 0x%x bytes\n",
rom_header, target, rom_size);
memcpy(target, rom_header, rom_size);
@@ -163,6 +165,7 @@ int pci_rom_load(uint16_t class, struct pci_rom_header *rom_header,
printf("VGA ROM copy failed\n");
return -EFAULT;
}
+ debug("Copy took %lums\n", get_timer(start));
}
*ram_headerp = target;
@@ -205,7 +208,7 @@ int vbe_get_video_info(struct graphic_device *gdev)
gdev->vprBase = vesa->phys_base_ptr;
gdev->cprBase = vesa->phys_base_ptr;
- return 0;
+ return gdev->winSizeX ? 0 : -ENOSYS;
#else
return -ENOSYS;
#endif
@@ -244,7 +247,7 @@ int pci_run_vga_bios(pci_dev_t dev, int (*int15_handler)(void), bool emulate)
defined(CONFIG_FRAMEBUFFER_VESA_MODE)
vesa_mode = CONFIG_FRAMEBUFFER_VESA_MODE;
#endif
- debug("Selected vesa mode %d\b", vesa_mode);
+ debug("Selected vesa mode %#x\n", vesa_mode);
if (emulate) {
#ifdef CONFIG_BIOSEMU
BE_VGAInfo *info;
@@ -272,7 +275,7 @@ int pci_run_vga_bios(pci_dev_t dev, int (*int15_handler)(void), bool emulate)
return -ENOSYS;
#endif
}
- debug("Final vesa mode %d\n", mode_info.video_mode);
+ debug("Final vesa mode %#x\n", mode_info.video_mode);
return 0;
}
diff --git a/drivers/pci/pci_tegra.c b/drivers/pci/pci_tegra.c
index a03ad5f..f9e05ad 100644
--- a/drivers/pci/pci_tegra.c
+++ b/drivers/pci/pci_tegra.c
@@ -458,6 +458,7 @@ static int tegra_pcie_parse_port_info(const void *fdt, int node,
unsigned int *index,
unsigned int *lanes)
{
+ struct fdt_pci_addr addr;
pci_dev_t bdf;
int err;
@@ -469,7 +470,7 @@ static int tegra_pcie_parse_port_info(const void *fdt, int node,
*lanes = err;
- err = fdtdec_pci_get_bdf(fdt, node, &bdf);
+ err = fdtdec_get_pci_bdf(fdt, node, &addr, &bdf);
if (err < 0) {
error("failed to parse \"reg\" property");
return err;
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index 1ec7c0e..f8f0239 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -1,47 +1,75 @@
config AXP221_POWER
- boolean "axp221 pmic support"
- depends on MACH_SUN6I
+ boolean "axp221 / axp223 pmic support"
+ depends on MACH_SUN6I || MACH_SUN8I
default y
---help---
- Say y here to enable support for the axp221 pmic found on most sun6i
- (A31) boards.
+ Say y here to enable support for the axp221 / axp223 pmic found on most
+ sun6i (A31) / sun8i (A23) boards.
+
+config AXP221_DCDC1_VOLT
+ int "axp221 dcdc1 voltage"
+ depends on AXP221_POWER
+ default 3000
+ ---help---
+ Set the voltage (mV) to program the axp221 dcdc1 at, set to 0 to
+ disable dcdc1. This is typically used as generic 3.3V IO voltage for
+ things like GPIO-s, sdcard interfaces, etc. On most boards this is
+ undervolted to 3.0V to safe battery.
config AXP221_DLDO1_VOLT
int "axp221 dldo1 voltage"
depends on AXP221_POWER
- default -1
+ default 0
---help---
- Set the voltage (mV) to program the axp221 dldo1 at, set to -1 to
- disable dldo1.
+ Set the voltage (mV) to program the axp221 dldo1 at, set to 0 to
+ disable dldo1. On sun6i (A31) boards with ethernet this is often used
+ to power the ethernet phy. On sun8i (A23) boards this is often used to
+ power the wifi.
config AXP221_DLDO4_VOLT
int "axp221 dldo4 voltage"
depends on AXP221_POWER
- default -1
+ default 0
---help---
- Set the voltage (mV) to program the axp221 dldo4 at, set to -1 to
+ Set the voltage (mV) to program the axp221 dldo4 at, set to 0 to
disable dldo4.
config AXP221_ALDO1_VOLT
int "axp221 aldo1 voltage"
depends on AXP221_POWER
- default -1
+ default 0
---help---
- Set the voltage (mV) to program the axp221 aldo1 at, set to -1 to
- disable aldo1.
+ Set the voltage (mV) to program the axp221 aldo1 at, set to 0 to
+ disable aldo1. On sun6i (A31) boards which have a wifi module this is
+ often used to power the wifi module.
config AXP221_ALDO2_VOLT
int "axp221 aldo2 voltage"
depends on AXP221_POWER
- default -1
+ default 0 if MACH_SUN6I
+ default 2500 if MACH_SUN8I
---help---
- Set the voltage (mV) to program the axp221 aldo2 at, set to -1 to
- disable aldo2.
+ Set the voltage (mV) to program the axp221 aldo2 at, set to 0 to
+ disable aldo2. On sun6i (A31) boards this is typically unused and
+ should be disabled, if it is used for LPDDR2 it should be set to 1.8V.
+ On sun8i (A23) this is typically connected to VDD-DLL and must be set
+ to 2.5V.
config AXP221_ALDO3_VOLT
int "axp221 aldo3 voltage"
depends on AXP221_POWER
- default -1
+ default 3000
+ ---help---
+ Set the voltage (mV) to program the axp221 aldo3 at, set to 0 to
+ disable aldo3. This is typically connected to VCC-PLL and AVCC and
+ must be set to 3V.
+
+config AXP221_ELDO3_VOLT
+ int "axp221 eldo3 voltage"
+ depends on AXP221_POWER
+ default 0
---help---
- Set the voltage (mV) to program the axp221 aldo3 at, set to -1 to
- disable aldo3.
+ Set the voltage (mV) to program the axp221 eldo3 at, set to 0 to
+ disable eldo3. On some A31(s) tablets it might be used to supply
+ 1.2V for the SSD2828 chip (converter of parallel LCD interface
+ into MIPI DSI).
diff --git a/drivers/power/axp209.c b/drivers/power/axp209.c
index 9798e5b..4565398 100644
--- a/drivers/power/axp209.c
+++ b/drivers/power/axp209.c
@@ -16,8 +16,18 @@ enum axp209_reg {
AXP209_DCDC3_VOLTAGE = 0x27,
AXP209_LDO24_VOLTAGE = 0x28,
AXP209_LDO3_VOLTAGE = 0x29,
+ AXP209_IRQ_ENABLE1 = 0x40,
+ AXP209_IRQ_ENABLE2 = 0x41,
+ AXP209_IRQ_ENABLE3 = 0x42,
+ AXP209_IRQ_ENABLE4 = 0x43,
+ AXP209_IRQ_ENABLE5 = 0x44,
AXP209_IRQ_STATUS5 = 0x4c,
AXP209_SHUTDOWN = 0x32,
+ AXP209_GPIO0_CTRL = 0x90,
+ AXP209_GPIO1_CTRL = 0x92,
+ AXP209_GPIO2_CTRL = 0x93,
+ AXP209_GPIO_STATE = 0x94,
+ AXP209_GPIO3_CTRL = 0x95,
};
#define AXP209_POWER_STATUS_ON_BY_DC (1 << 0)
@@ -27,6 +37,15 @@ enum axp209_reg {
#define AXP209_POWEROFF (1 << 7)
+#define AXP209_GPIO_OUTPUT_LOW 0x00 /* Drive pin low */
+#define AXP209_GPIO_OUTPUT_HIGH 0x01 /* Drive pin high */
+#define AXP209_GPIO_INPUT 0x02 /* Float pin */
+
+/* GPIO3 is different from the others */
+#define AXP209_GPIO3_OUTPUT_LOW 0x00 /* Drive pin low, Output mode */
+#define AXP209_GPIO3_OUTPUT_HIGH 0x02 /* Float pin, Output mode */
+#define AXP209_GPIO3_INPUT 0x06 /* Float pin, Input mode */
+
static int axp209_write(enum axp209_reg reg, u8 val)
{
return i2c_write(0x34, reg, 1, &val, 1);
@@ -129,7 +148,7 @@ int axp209_set_ldo4(int mvolt)
int axp209_init(void)
{
u8 ver;
- int rc;
+ int i, rc;
rc = axp209_read(AXP209_CHIP_VERSION, &ver);
if (rc)
@@ -141,6 +160,13 @@ int axp209_init(void)
if (ver != 0x1)
return -1;
+ /* Mask all interrupts */
+ for (i = AXP209_IRQ_ENABLE1; i <= AXP209_IRQ_ENABLE5; i++) {
+ rc = axp209_write(i, 0);
+ if (rc)
+ return rc;
+ }
+
return 0;
}
@@ -165,3 +191,61 @@ int axp209_power_button(void)
return v & AXP209_IRQ5_PEK_DOWN;
}
+
+static u8 axp209_get_gpio_ctrl_reg(unsigned int pin)
+{
+ switch (pin) {
+ case 0: return AXP209_GPIO0_CTRL;
+ case 1: return AXP209_GPIO1_CTRL;
+ case 2: return AXP209_GPIO2_CTRL;
+ case 3: return AXP209_GPIO3_CTRL;
+ }
+ return 0;
+}
+
+int axp_gpio_direction_input(unsigned int pin)
+{
+ u8 reg = axp209_get_gpio_ctrl_reg(pin);
+ /* GPIO3 is "special" */
+ u8 val = (pin == 3) ? AXP209_GPIO3_INPUT : AXP209_GPIO_INPUT;
+
+ return axp209_write(reg, val);
+}
+
+int axp_gpio_direction_output(unsigned int pin, unsigned int val)
+{
+ u8 reg = axp209_get_gpio_ctrl_reg(pin);
+
+ if (val) {
+ val = (pin == 3) ? AXP209_GPIO3_OUTPUT_HIGH :
+ AXP209_GPIO_OUTPUT_HIGH;
+ } else {
+ val = (pin == 3) ? AXP209_GPIO3_OUTPUT_LOW :
+ AXP209_GPIO_OUTPUT_LOW;
+ }
+
+ return axp209_write(reg, val);
+}
+
+int axp_gpio_get_value(unsigned int pin)
+{
+ u8 val, mask;
+ int rc;
+
+ if (pin == 3) {
+ rc = axp209_read(AXP209_GPIO3_CTRL, &val);
+ mask = 1;
+ } else {
+ rc = axp209_read(AXP209_GPIO_STATE, &val);
+ mask = 1 << (pin + 4);
+ }
+ if (rc)
+ return rc;
+
+ return (val & mask) ? 1 : 0;
+}
+
+int axp_gpio_set_value(unsigned int pin, unsigned int val)
+{
+ return axp_gpio_direction_output(pin, val);
+}
diff --git a/drivers/power/axp221.c b/drivers/power/axp221.c
index 941193a..58bbd45 100644
--- a/drivers/power/axp221.c
+++ b/drivers/power/axp221.c
@@ -1,4 +1,10 @@
/*
+ * AXP221 and AXP223 driver
+ *
+ * IMPORTANT when making changes to this file check that the registers
+ * used are the same for the axp221 and axp223.
+ *
+ * (C) Copyright 2014 Hans de Goede <hdegoede@redhat.com>
* (C) Copyright 2013 Oliver Schinagl <oliver@schinagl.nl>
*
* SPDX-License-Identifier: GPL-2.0+
@@ -7,8 +13,50 @@
#include <common.h>
#include <errno.h>
#include <asm/arch/p2wi.h>
+#include <asm/arch/rsb.h>
#include <axp221.h>
+/*
+ * The axp221 uses the p2wi bus, the axp223 is identical (for all registers
+ * used sofar) but uses the rsb bus. These functions abstract this.
+ */
+static int pmic_bus_init(void)
+{
+#ifdef CONFIG_MACH_SUN6I
+ p2wi_init();
+ return p2wi_change_to_p2wi_mode(AXP221_CHIP_ADDR, AXP221_CTRL_ADDR,
+ AXP221_INIT_DATA);
+#else
+ int ret;
+
+ rsb_init();
+
+ ret = rsb_set_device_mode(AXP223_DEVICE_MODE_DATA);
+ if (ret)
+ return ret;
+
+ return rsb_set_device_address(AXP223_DEVICE_ADDR, AXP223_RUNTIME_ADDR);
+#endif
+}
+
+static int pmic_bus_read(const u8 addr, u8 *data)
+{
+#ifdef CONFIG_MACH_SUN6I
+ return p2wi_read(addr, data);
+#else
+ return rsb_read(AXP223_RUNTIME_ADDR, addr, data);
+#endif
+}
+
+static int pmic_bus_write(const u8 addr, u8 data)
+{
+#ifdef CONFIG_MACH_SUN6I
+ return p2wi_write(addr, data);
+#else
+ return rsb_write(AXP223_RUNTIME_ADDR, addr, data);
+#endif
+}
+
static u8 axp221_mvolt_to_cfg(int mvolt, int min, int max, int div)
{
if (mvolt < min)
@@ -24,12 +72,25 @@ static int axp221_setbits(u8 reg, u8 bits)
int ret;
u8 val;
- ret = p2wi_read(reg, &val);
+ ret = pmic_bus_read(reg, &val);
if (ret)
return ret;
val |= bits;
- return p2wi_write(reg, val);
+ return pmic_bus_write(reg, val);
+}
+
+static int axp221_clrbits(u8 reg, u8 bits)
+{
+ int ret;
+ u8 val;
+
+ ret = pmic_bus_read(reg, &val);
+ if (ret)
+ return ret;
+
+ val &= ~bits;
+ return pmic_bus_write(reg, val);
}
int axp221_set_dcdc1(unsigned int mvolt)
@@ -37,40 +98,89 @@ int axp221_set_dcdc1(unsigned int mvolt)
int ret;
u8 cfg = axp221_mvolt_to_cfg(mvolt, 1600, 3400, 100);
- ret = p2wi_write(AXP221_DCDC1_CTRL, cfg);
+ if (mvolt == 0)
+ return axp221_clrbits(AXP221_OUTPUT_CTRL1,
+ AXP221_OUTPUT_CTRL1_DCDC1_EN);
+
+ ret = pmic_bus_write(AXP221_DCDC1_CTRL, cfg);
if (ret)
return ret;
- return axp221_setbits(AXP221_OUTPUT_CTRL2,
- AXP221_OUTPUT_CTRL2_DCDC1_EN);
+ ret = axp221_setbits(AXP221_OUTPUT_CTRL2,
+ AXP221_OUTPUT_CTRL2_DCDC1SW_EN);
+ if (ret)
+ return ret;
+
+ return axp221_setbits(AXP221_OUTPUT_CTRL1,
+ AXP221_OUTPUT_CTRL1_DCDC1_EN);
}
int axp221_set_dcdc2(unsigned int mvolt)
{
+ int ret;
u8 cfg = axp221_mvolt_to_cfg(mvolt, 600, 1540, 20);
- return p2wi_write(AXP221_DCDC2_CTRL, cfg);
+ if (mvolt == 0)
+ return axp221_clrbits(AXP221_OUTPUT_CTRL1,
+ AXP221_OUTPUT_CTRL1_DCDC2_EN);
+
+ ret = pmic_bus_write(AXP221_DCDC2_CTRL, cfg);
+ if (ret)
+ return ret;
+
+ return axp221_setbits(AXP221_OUTPUT_CTRL1,
+ AXP221_OUTPUT_CTRL1_DCDC2_EN);
}
int axp221_set_dcdc3(unsigned int mvolt)
{
+ int ret;
u8 cfg = axp221_mvolt_to_cfg(mvolt, 600, 1860, 20);
- return p2wi_write(AXP221_DCDC3_CTRL, cfg);
+ if (mvolt == 0)
+ return axp221_clrbits(AXP221_OUTPUT_CTRL1,
+ AXP221_OUTPUT_CTRL1_DCDC3_EN);
+
+ ret = pmic_bus_write(AXP221_DCDC3_CTRL, cfg);
+ if (ret)
+ return ret;
+
+ return axp221_setbits(AXP221_OUTPUT_CTRL1,
+ AXP221_OUTPUT_CTRL1_DCDC3_EN);
}
int axp221_set_dcdc4(unsigned int mvolt)
{
+ int ret;
u8 cfg = axp221_mvolt_to_cfg(mvolt, 600, 1540, 20);
- return p2wi_write(AXP221_DCDC4_CTRL, cfg);
+ if (mvolt == 0)
+ return axp221_clrbits(AXP221_OUTPUT_CTRL1,
+ AXP221_OUTPUT_CTRL1_DCDC4_EN);
+
+ ret = pmic_bus_write(AXP221_DCDC4_CTRL, cfg);
+ if (ret)
+ return ret;
+
+ return axp221_setbits(AXP221_OUTPUT_CTRL1,
+ AXP221_OUTPUT_CTRL1_DCDC4_EN);
}
int axp221_set_dcdc5(unsigned int mvolt)
{
+ int ret;
u8 cfg = axp221_mvolt_to_cfg(mvolt, 1000, 2550, 50);
- return p2wi_write(AXP221_DCDC5_CTRL, cfg);
+ if (mvolt == 0)
+ return axp221_clrbits(AXP221_OUTPUT_CTRL1,
+ AXP221_OUTPUT_CTRL1_DCDC5_EN);
+
+ ret = pmic_bus_write(AXP221_DCDC5_CTRL, cfg);
+ if (ret)
+ return ret;
+
+ return axp221_setbits(AXP221_OUTPUT_CTRL1,
+ AXP221_OUTPUT_CTRL1_DCDC5_EN);
}
int axp221_set_dldo1(unsigned int mvolt)
@@ -78,7 +188,11 @@ int axp221_set_dldo1(unsigned int mvolt)
int ret;
u8 cfg = axp221_mvolt_to_cfg(mvolt, 700, 3300, 100);
- ret = p2wi_write(AXP221_DLDO1_CTRL, cfg);
+ if (mvolt == 0)
+ return axp221_clrbits(AXP221_OUTPUT_CTRL2,
+ AXP221_OUTPUT_CTRL2_DLDO1_EN);
+
+ ret = pmic_bus_write(AXP221_DLDO1_CTRL, cfg);
if (ret)
return ret;
@@ -91,7 +205,11 @@ int axp221_set_dldo2(unsigned int mvolt)
int ret;
u8 cfg = axp221_mvolt_to_cfg(mvolt, 700, 3300, 100);
- ret = p2wi_write(AXP221_DLDO2_CTRL, cfg);
+ if (mvolt == 0)
+ return axp221_clrbits(AXP221_OUTPUT_CTRL2,
+ AXP221_OUTPUT_CTRL2_DLDO2_EN);
+
+ ret = pmic_bus_write(AXP221_DLDO2_CTRL, cfg);
if (ret)
return ret;
@@ -104,7 +222,11 @@ int axp221_set_dldo3(unsigned int mvolt)
int ret;
u8 cfg = axp221_mvolt_to_cfg(mvolt, 700, 3300, 100);
- ret = p2wi_write(AXP221_DLDO3_CTRL, cfg);
+ if (mvolt == 0)
+ return axp221_clrbits(AXP221_OUTPUT_CTRL2,
+ AXP221_OUTPUT_CTRL2_DLDO3_EN);
+
+ ret = pmic_bus_write(AXP221_DLDO3_CTRL, cfg);
if (ret)
return ret;
@@ -117,7 +239,11 @@ int axp221_set_dldo4(unsigned int mvolt)
int ret;
u8 cfg = axp221_mvolt_to_cfg(mvolt, 700, 3300, 100);
- ret = p2wi_write(AXP221_DLDO4_CTRL, cfg);
+ if (mvolt == 0)
+ return axp221_clrbits(AXP221_OUTPUT_CTRL2,
+ AXP221_OUTPUT_CTRL2_DLDO4_EN);
+
+ ret = pmic_bus_write(AXP221_DLDO4_CTRL, cfg);
if (ret)
return ret;
@@ -130,7 +256,11 @@ int axp221_set_aldo1(unsigned int mvolt)
int ret;
u8 cfg = axp221_mvolt_to_cfg(mvolt, 700, 3300, 100);
- ret = p2wi_write(AXP221_ALDO1_CTRL, cfg);
+ if (mvolt == 0)
+ return axp221_clrbits(AXP221_OUTPUT_CTRL1,
+ AXP221_OUTPUT_CTRL1_ALDO1_EN);
+
+ ret = pmic_bus_write(AXP221_ALDO1_CTRL, cfg);
if (ret)
return ret;
@@ -143,7 +273,11 @@ int axp221_set_aldo2(unsigned int mvolt)
int ret;
u8 cfg = axp221_mvolt_to_cfg(mvolt, 700, 3300, 100);
- ret = p2wi_write(AXP221_ALDO2_CTRL, cfg);
+ if (mvolt == 0)
+ return axp221_clrbits(AXP221_OUTPUT_CTRL1,
+ AXP221_OUTPUT_CTRL1_ALDO2_EN);
+
+ ret = pmic_bus_write(AXP221_ALDO2_CTRL, cfg);
if (ret)
return ret;
@@ -156,7 +290,11 @@ int axp221_set_aldo3(unsigned int mvolt)
int ret;
u8 cfg = axp221_mvolt_to_cfg(mvolt, 700, 3300, 100);
- ret = p2wi_write(AXP221_ALDO3_CTRL, cfg);
+ if (mvolt == 0)
+ return axp221_clrbits(AXP221_OUTPUT_CTRL3,
+ AXP221_OUTPUT_CTRL3_ALDO3_EN);
+
+ ret = pmic_bus_write(AXP221_ALDO3_CTRL, cfg);
if (ret)
return ret;
@@ -164,23 +302,123 @@ int axp221_set_aldo3(unsigned int mvolt)
AXP221_OUTPUT_CTRL3_ALDO3_EN);
}
+int axp221_set_eldo(int eldo_num, unsigned int mvolt)
+{
+ int ret;
+ u8 cfg = axp221_mvolt_to_cfg(mvolt, 700, 3300, 100);
+ u8 addr, bits;
+
+ switch (eldo_num) {
+ case 3:
+ addr = AXP221_ELDO3_CTRL;
+ bits = AXP221_OUTPUT_CTRL2_ELDO3_EN;
+ break;
+ case 2:
+ addr = AXP221_ELDO2_CTRL;
+ bits = AXP221_OUTPUT_CTRL2_ELDO2_EN;
+ break;
+ case 1:
+ addr = AXP221_ELDO1_CTRL;
+ bits = AXP221_OUTPUT_CTRL2_ELDO1_EN;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (mvolt == 0)
+ return axp221_clrbits(AXP221_OUTPUT_CTRL2, bits);
+
+ ret = pmic_bus_write(addr, cfg);
+ if (ret)
+ return ret;
+
+ return axp221_setbits(AXP221_OUTPUT_CTRL2, bits);
+}
+
int axp221_init(void)
{
+ /* This cannot be 0 because it is used in SPL before BSS is ready */
+ static int needs_init = 1;
u8 axp_chip_id;
int ret;
- p2wi_init();
- ret = p2wi_change_to_p2wi_mode(AXP221_CHIP_ADDR, AXP221_CTRL_ADDR,
- AXP221_INIT_DATA);
+ if (!needs_init)
+ return 0;
+
+ ret = pmic_bus_init();
if (ret)
return ret;
- ret = p2wi_read(AXP221_CHIP_ID, &axp_chip_id);
+ ret = pmic_bus_read(AXP221_CHIP_ID, &axp_chip_id);
if (ret)
return ret;
if (!(axp_chip_id == 0x6 || axp_chip_id == 0x7 || axp_chip_id == 0x17))
return -ENODEV;
+ needs_init = 0;
return 0;
}
+
+int axp221_get_sid(unsigned int *sid)
+{
+ u8 *dest = (u8 *)sid;
+ int i, ret;
+
+ ret = axp221_init();
+ if (ret)
+ return ret;
+
+ ret = pmic_bus_write(AXP221_PAGE, 1);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < 16; i++) {
+ ret = pmic_bus_read(AXP221_SID + i, &dest[i]);
+ if (ret)
+ return ret;
+ }
+
+ pmic_bus_write(AXP221_PAGE, 0);
+
+ for (i = 0; i < 4; i++)
+ sid[i] = be32_to_cpu(sid[i]);
+
+ return 0;
+}
+
+static int axp_drivebus_setup(void)
+{
+ int ret;
+
+ ret = axp221_init();
+ if (ret)
+ return ret;
+
+ /* Set N_VBUSEN pin to output / DRIVEBUS function */
+ return axp221_clrbits(AXP221_MISC_CTRL, AXP221_MISC_CTRL_N_VBUSEN_FUNC);
+}
+
+int axp_drivebus_enable(void)
+{
+ int ret;
+
+ ret = axp_drivebus_setup();
+ if (ret)
+ return ret;
+
+ /* Set DRIVEBUS high */
+ return axp221_setbits(AXP221_VBUS_IPSOUT, AXP221_VBUS_IPSOUT_DRIVEBUS);
+}
+
+int axp_drivebus_disable(void)
+{
+ int ret;
+
+ ret = axp_drivebus_setup();
+ if (ret)
+ return ret;
+
+ /* Set DRIVEBUS low */
+ return axp221_clrbits(AXP221_VBUS_IPSOUT, AXP221_VBUS_IPSOUT_DRIVEBUS);
+}
diff --git a/drivers/power/pmic/Makefile b/drivers/power/pmic/Makefile
index e7b07eb..985cfdb 100644
--- a/drivers/power/pmic/Makefile
+++ b/drivers/power/pmic/Makefile
@@ -14,5 +14,6 @@ obj-$(CONFIG_POWER_PFUZE100) += pmic_pfuze100.o
obj-$(CONFIG_POWER_TPS65090_I2C) += pmic_tps65090.o
obj-$(CONFIG_POWER_TPS65090_EC) += pmic_tps65090_ec.o
obj-$(CONFIG_POWER_TPS65217) += pmic_tps65217.o
+obj-$(CONFIG_POWER_TPS65218) += pmic_tps62362.o
obj-$(CONFIG_POWER_TPS65218) += pmic_tps65218.o
obj-$(CONFIG_POWER_TPS65910) += pmic_tps65910.o
diff --git a/drivers/power/pmic/pmic_tps62362.c b/drivers/power/pmic/pmic_tps62362.c
new file mode 100644
index 0000000..2123685
--- /dev/null
+++ b/drivers/power/pmic/pmic_tps62362.c
@@ -0,0 +1,47 @@
+/*
+ * (C) Copyright 2014 Texas Instruments Incorporated - http://www.ti.com
+ * Author: Felipe Balbi <balbi@ti.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <common.h>
+#include <i2c.h>
+#include <asm/errno.h>
+#include <power/pmic.h>
+#include <power/tps62362.h>
+
+/**
+ * tps62362_voltage_update() - Function to change a voltage level, as this
+ * is a multi-step process.
+ * @reg: Register address to write to
+ * @volt_sel: Voltage register value to write
+ * @return: 0 on success, 1 on failure
+ */
+int tps62362_voltage_update(unsigned char reg, unsigned char volt_sel)
+{
+ if (reg > TPS62362_NUM_REGS)
+ return 1;
+
+ return i2c_write(TPS62362_I2C_ADDR, reg, 1, &volt_sel, 1);
+}
+
+int power_tps62362_init(unsigned char bus)
+{
+ static const char name[] = "TPS62362";
+ struct pmic *p = pmic_alloc();
+
+ if (!p) {
+ printf("%s: POWER allocation error!\n", __func__);
+ return -ENOMEM;
+ }
+
+ p->name = name;
+ p->interface = PMIC_I2C;
+ p->number_of_regs = TPS62362_NUM_REGS;
+ p->hw.i2c.addr = TPS62362_I2C_ADDR;
+ p->hw.i2c.tx_num = 1;
+ p->bus = bus;
+
+ return 0;
+}
diff --git a/drivers/serial/ns16550.c b/drivers/serial/ns16550.c
index af5beba..70c9462 100644
--- a/drivers/serial/ns16550.c
+++ b/drivers/serial/ns16550.c
@@ -289,7 +289,38 @@ int ns16550_serial_ofdata_to_platdata(struct udevice *dev)
struct ns16550_platdata *plat = dev->platdata;
fdt_addr_t addr;
+ /* try Processor Local Bus device first */
addr = fdtdec_get_addr(gd->fdt_blob, dev->of_offset, "reg");
+#ifdef CONFIG_PCI
+ if (addr == FDT_ADDR_T_NONE) {
+ /* then try pci device */
+ struct fdt_pci_addr pci_addr;
+ u32 bar;
+ int ret;
+
+ /* we prefer to use a memory-mapped register */
+ ret = fdtdec_get_pci_addr(gd->fdt_blob, dev->of_offset,
+ FDT_PCI_SPACE_MEM32, "reg",
+ &pci_addr);
+ if (ret) {
+ /* try if there is any i/o-mapped register */
+ ret = fdtdec_get_pci_addr(gd->fdt_blob,
+ dev->of_offset,
+ FDT_PCI_SPACE_IO,
+ "reg", &pci_addr);
+ if (ret)
+ return ret;
+ }
+
+ ret = fdtdec_get_pci_bar32(gd->fdt_blob, dev->of_offset,
+ &pci_addr, &bar);
+ if (ret)
+ return ret;
+
+ addr = bar;
+ }
+#endif
+
if (addr == FDT_ADDR_T_NONE)
return -EINVAL;
diff --git a/drivers/serial/serial_x86.c b/drivers/serial/serial_x86.c
index e81e035..4bf6062 100644
--- a/drivers/serial/serial_x86.c
+++ b/drivers/serial/serial_x86.c
@@ -6,9 +6,12 @@
#include <common.h>
#include <dm.h>
+#include <fdtdec.h>
#include <ns16550.h>
#include <serial.h>
+DECLARE_GLOBAL_DATA_PTR;
+
static const struct udevice_id x86_serial_ids[] = {
{ .compatible = "x86-uart" },
{ }
@@ -22,10 +25,13 @@ static int x86_serial_ofdata_to_platdata(struct udevice *dev)
ret = ns16550_serial_ofdata_to_platdata(dev);
if (ret)
return ret;
- plat->clock = 1843200;
+
+ plat->clock = fdtdec_get_int(gd->fdt_blob, dev->of_offset,
+ "clock-frequency", 1843200);
return 0;
}
+
U_BOOT_DRIVER(serial_ns16550) = {
.name = "serial_x86",
.id = UCLASS_SERIAL,
diff --git a/drivers/usb/eth/asix88179.c b/drivers/usb/eth/asix88179.c
index b8ca720..0ef85db 100644
--- a/drivers/usb/eth/asix88179.c
+++ b/drivers/usb/eth/asix88179.c
@@ -271,6 +271,19 @@ static int asix_read_mac(struct eth_device *eth)
return 0;
}
+static int asix_write_mac(struct eth_device *eth)
+{
+ struct ueth_data *dev = (struct ueth_data *)eth->priv;
+ int ret;
+
+ ret = asix_write_cmd(dev, AX_ACCESS_MAC, AX_NODE_ID, ETH_ALEN,
+ ETH_ALEN, eth->enetaddr);
+ if (ret < 0)
+ debug("Failed to set MAC address: %02x\n", ret);
+
+ return ret;
+}
+
static int asix_basic_reset(struct ueth_data *dev)
{
struct asix_private *dev_priv = (struct asix_private *)dev->dev_priv;
@@ -686,6 +699,7 @@ int ax88179_eth_get_info(struct usb_device *dev, struct ueth_data *ss,
eth->send = asix_send;
eth->recv = asix_recv;
eth->halt = asix_halt;
+ eth->write_hwaddr = asix_write_mac;
eth->priv = ss;
if (asix_basic_reset(ss))
diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c
index a4c5606..98c2da6 100644
--- a/drivers/usb/gadget/composite.c
+++ b/drivers/usb/gadget/composite.c
@@ -761,6 +761,14 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
if (value >= 0)
value = min(w_length, (u16) value);
break;
+ case USB_DT_BOS:
+ /*
+ * The USB compliance test (USB 2.0 Command Verifier)
+ * issues this request. We should not run into the
+ * default path here. But return for now until
+ * the superspeed support is added.
+ */
+ break;
default:
goto unknown;
}
diff --git a/drivers/usb/gadget/f_dfu.c b/drivers/usb/gadget/f_dfu.c
index ead71eb..77a1567 100644
--- a/drivers/usb/gadget/f_dfu.c
+++ b/drivers/usb/gadget/f_dfu.c
@@ -780,6 +780,13 @@ static int dfu_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
return 0;
}
+static int __dfu_get_alt(struct usb_function *f, unsigned intf)
+{
+ struct f_dfu *f_dfu = func_to_dfu(f);
+
+ return f_dfu->altsetting;
+}
+
/* TODO: is this really what we need here? */
static void dfu_disable(struct usb_function *f)
{
@@ -806,6 +813,7 @@ static int dfu_bind_config(struct usb_configuration *c)
f_dfu->usb_function.bind = dfu_bind;
f_dfu->usb_function.unbind = dfu_unbind;
f_dfu->usb_function.set_alt = dfu_set_alt;
+ f_dfu->usb_function.get_alt = __dfu_get_alt;
f_dfu->usb_function.disable = dfu_disable;
f_dfu->usb_function.strings = dfu_generic_strings;
f_dfu->usb_function.setup = dfu_handle;
diff --git a/drivers/usb/gadget/pxa25x_udc.c b/drivers/usb/gadget/pxa25x_udc.c
index 8945c5b..d4460b2 100644
--- a/drivers/usb/gadget/pxa25x_udc.c
+++ b/drivers/usb/gadget/pxa25x_udc.c
@@ -1950,11 +1950,11 @@ int usb_gadget_register_driver(struct usb_gadget_driver *driver)
dev->watchdog.period = 5000 * CONFIG_SYS_HZ / 1000000; /* 5 ms */
dev->watchdog.function = udc_watchdog;
+ dev->mach = &mach_info;
+
udc_disable(dev);
udc_reinit(dev);
- dev->mach = &mach_info;
-
dev->gadget.name = "pxa2xx_udc";
retval = driver->bind(&dev->gadget);
if (retval) {
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
index bc76066..f1fb190 100644
--- a/drivers/usb/host/ehci-hcd.c
+++ b/drivers/usb/host/ehci-hcd.c
@@ -1148,7 +1148,7 @@ disable_periodic(struct ehci_ctrl *ctrl)
struct int_queue *
create_int_queue(struct usb_device *dev, unsigned long pipe, int queuesize,
- int elementsize, void *buffer)
+ int elementsize, void *buffer, int interval)
{
struct ehci_ctrl *ctrl = dev->controller;
struct int_queue *result = NULL;
@@ -1398,7 +1398,7 @@ submit_int_msg(struct usb_device *dev, unsigned long pipe, void *buffer,
debug("dev=%p, pipe=%lu, buffer=%p, length=%d, interval=%d",
dev, pipe, buffer, length, interval);
- queue = create_int_queue(dev, pipe, 1, length, buffer);
+ queue = create_int_queue(dev, pipe, 1, length, buffer, interval);
if (!queue)
return -1;
diff --git a/drivers/usb/host/ehci-sunxi.c b/drivers/usb/host/ehci-sunxi.c
index cc9a8fa..eda9f69 100644
--- a/drivers/usb/host/ehci-sunxi.c
+++ b/drivers/usb/host/ehci-sunxi.c
@@ -9,199 +9,23 @@
* SPDX-License-Identifier: GPL-2.0+
*/
-#include <asm/arch/clock.h>
-#include <asm/arch/cpu.h>
-#include <asm/gpio.h>
-#include <asm/io.h>
+#include <asm/arch/usbc.h>
#include <common.h>
#include "ehci.h"
-#define SUNXI_USB_PMU_IRQ_ENABLE 0x800
-#define SUNXI_USB_CSR 0x404
-#define SUNXI_USB_PASSBY_EN 1
-
-#define SUNXI_EHCI_AHB_ICHR8_EN (1 << 10)
-#define SUNXI_EHCI_AHB_INCR4_BURST_EN (1 << 9)
-#define SUNXI_EHCI_AHB_INCRX_ALIGN_EN (1 << 8)
-#define SUNXI_EHCI_ULPI_BYPASS_EN (1 << 0)
-
-static struct sunxi_ehci_hcd {
- struct usb_hcd *hcd;
- int usb_rst_mask;
- int ahb_clk_mask;
- int gpio_vbus;
- int irq;
- int id;
-} sunxi_echi_hcd[] = {
- {
- .usb_rst_mask = CCM_USB_CTRL_PHY1_RST | CCM_USB_CTRL_PHY1_CLK,
- .ahb_clk_mask = 1 << AHB_GATE_OFFSET_USB_EHCI0,
-#ifndef CONFIG_MACH_SUN6I
- .irq = 39,
-#else
- .irq = 72,
-#endif
- .id = 1,
- },
-#if (CONFIG_USB_MAX_CONTROLLER_COUNT > 1)
- {
- .usb_rst_mask = CCM_USB_CTRL_PHY2_RST | CCM_USB_CTRL_PHY2_CLK,
- .ahb_clk_mask = 1 << AHB_GATE_OFFSET_USB_EHCI1,
-#ifndef CONFIG_MACH_SUN6I
- .irq = 40,
-#else
- .irq = 74,
-#endif
- .id = 2,
- }
-#endif
-};
-
-static int enabled_hcd_count;
-
-static void *get_io_base(int hcd_id)
-{
- switch (hcd_id) {
- case 0:
- return (void *)SUNXI_USB0_BASE;
- case 1:
- return (void *)SUNXI_USB1_BASE;
- case 2:
- return (void *)SUNXI_USB2_BASE;
- default:
- return NULL;
- }
-}
-
-static int get_vbus_gpio(int hcd_id)
-{
- switch (hcd_id) {
- case 1: return sunxi_name_to_gpio(CONFIG_USB1_VBUS_PIN);
- case 2: return sunxi_name_to_gpio(CONFIG_USB2_VBUS_PIN);
- }
- return -1;
-}
-
-static void usb_phy_write(struct sunxi_ehci_hcd *sunxi_ehci, int addr,
- int data, int len)
-{
- int j = 0, usbc_bit = 0;
- void *dest = get_io_base(0) + SUNXI_USB_CSR;
-
- usbc_bit = 1 << (sunxi_ehci->id * 2);
- for (j = 0; j < len; j++) {
- /* set the bit address to be written */
- clrbits_le32(dest, 0xff << 8);
- setbits_le32(dest, (addr + j) << 8);
-
- clrbits_le32(dest, usbc_bit);
- /* set data bit */
- if (data & 0x1)
- setbits_le32(dest, 1 << 7);
- else
- clrbits_le32(dest, 1 << 7);
-
- setbits_le32(dest, usbc_bit);
-
- clrbits_le32(dest, usbc_bit);
-
- data >>= 1;
- }
-}
-
-static void sunxi_usb_phy_init(struct sunxi_ehci_hcd *sunxi_ehci)
-{
- /* The following comments are machine
- * translated from Chinese, you have been warned!
- */
-
- /* adjust PHY's magnitude and rate */
- usb_phy_write(sunxi_ehci, 0x20, 0x14, 5);
-
- /* threshold adjustment disconnect */
-#if defined CONFIG_MACH_SUN4I || defined CONFIG_MACH_SUN6I
- usb_phy_write(sunxi_ehci, 0x2a, 3, 2);
-#else
- usb_phy_write(sunxi_ehci, 0x2a, 2, 2);
-#endif
-
- return;
-}
-
-static void sunxi_usb_passby(struct sunxi_ehci_hcd *sunxi_ehci, int enable)
-{
- unsigned long bits = 0;
- void *addr = get_io_base(sunxi_ehci->id) + SUNXI_USB_PMU_IRQ_ENABLE;
-
- bits = SUNXI_EHCI_AHB_ICHR8_EN |
- SUNXI_EHCI_AHB_INCR4_BURST_EN |
- SUNXI_EHCI_AHB_INCRX_ALIGN_EN |
- SUNXI_EHCI_ULPI_BYPASS_EN;
-
- if (enable)
- setbits_le32(addr, bits);
- else
- clrbits_le32(addr, bits);
-
- return;
-}
-
-static void sunxi_ehci_enable(struct sunxi_ehci_hcd *sunxi_ehci)
-{
- struct sunxi_ccm_reg *ccm = (struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
-
- setbits_le32(&ccm->usb_clk_cfg, sunxi_ehci->usb_rst_mask);
- setbits_le32(&ccm->ahb_gate0, sunxi_ehci->ahb_clk_mask);
-#ifdef CONFIG_MACH_SUN6I
- setbits_le32(&ccm->ahb_reset0_cfg, sunxi_ehci->ahb_clk_mask);
-#endif
-
- sunxi_usb_phy_init(sunxi_ehci);
-
- sunxi_usb_passby(sunxi_ehci, SUNXI_USB_PASSBY_EN);
-
- if (sunxi_ehci->gpio_vbus != -1)
- gpio_direction_output(sunxi_ehci->gpio_vbus, 1);
-}
-
-static void sunxi_ehci_disable(struct sunxi_ehci_hcd *sunxi_ehci)
-{
- struct sunxi_ccm_reg *ccm = (struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
-
- if (sunxi_ehci->gpio_vbus != -1)
- gpio_direction_output(sunxi_ehci->gpio_vbus, 0);
-
- sunxi_usb_passby(sunxi_ehci, !SUNXI_USB_PASSBY_EN);
-
-#ifdef CONFIG_MACH_SUN6I
- clrbits_le32(&ccm->ahb_reset0_cfg, sunxi_ehci->ahb_clk_mask);
-#endif
- clrbits_le32(&ccm->ahb_gate0, sunxi_ehci->ahb_clk_mask);
- clrbits_le32(&ccm->usb_clk_cfg, sunxi_ehci->usb_rst_mask);
-}
-
int ehci_hcd_init(int index, enum usb_init_type init, struct ehci_hccr **hccr,
struct ehci_hcor **hcor)
{
- struct sunxi_ccm_reg *ccm = (struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
- struct sunxi_ehci_hcd *sunxi_ehci = &sunxi_echi_hcd[index];
int err;
- sunxi_ehci->gpio_vbus = get_vbus_gpio(sunxi_ehci->id);
+ err = sunxi_usbc_request_resources(index + 1);
+ if (err)
+ return err;
- /* enable common PHY only once */
- if (index == 0)
- setbits_le32(&ccm->usb_clk_cfg, CCM_USB_CTRL_PHYGATE);
+ sunxi_usbc_enable(index + 1);
+ sunxi_usbc_vbus_enable(index + 1);
- if (sunxi_ehci->gpio_vbus != -1) {
- err = gpio_request(sunxi_ehci->gpio_vbus, "ehci_vbus");
- if (err)
- return err;
- }
-
- sunxi_ehci_enable(sunxi_ehci);
-
- *hccr = get_io_base(sunxi_ehci->id);
+ *hccr = sunxi_usbc_get_io_base(index + 1);
*hcor = (struct ehci_hcor *)((uint32_t) *hccr
+ HC_LENGTH(ehci_readl(&(*hccr)->cr_capbase)));
@@ -210,30 +34,13 @@ int ehci_hcd_init(int index, enum usb_init_type init, struct ehci_hccr **hccr,
(uint32_t)*hccr, (uint32_t)*hcor,
(uint32_t)HC_LENGTH(ehci_readl(&(*hccr)->cr_capbase)));
- enabled_hcd_count++;
-
return 0;
}
int ehci_hcd_stop(int index)
{
- struct sunxi_ccm_reg *ccm = (struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
- struct sunxi_ehci_hcd *sunxi_ehci = &sunxi_echi_hcd[index];
- int err;
-
- sunxi_ehci_disable(sunxi_ehci);
-
- if (sunxi_ehci->gpio_vbus != -1) {
- err = gpio_free(sunxi_ehci->gpio_vbus);
- if (err)
- return err;
- }
+ sunxi_usbc_vbus_disable(index + 1);
+ sunxi_usbc_disable(index + 1);
- /* disable common PHY only once, for the last enabled hcd */
- if (enabled_hcd_count == 1)
- clrbits_le32(&ccm->usb_clk_cfg, CCM_USB_CTRL_PHYGATE);
-
- enabled_hcd_count--;
-
- return 0;
+ return sunxi_usbc_free_resources(index + 1);
}
diff --git a/drivers/usb/musb-new/Makefile b/drivers/usb/musb-new/Makefile
index 3facf0f..9edeece 100644
--- a/drivers/usb/musb-new/Makefile
+++ b/drivers/usb/musb-new/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_MUSB_HOST) += musb_host.o musb_core.o musb_uboot.o
obj-$(CONFIG_USB_MUSB_DSPS) += musb_dsps.o
obj-$(CONFIG_USB_MUSB_AM35X) += am35x.o
obj-$(CONFIG_USB_MUSB_OMAP2PLUS) += omap2430.o
+obj-$(CONFIG_USB_MUSB_SUNXI) += sunxi.o
ccflags-y := $(call cc-option,-Wno-unused-variable) \
$(call cc-option,-Wno-unused-but-set-variable) \
diff --git a/drivers/usb/musb-new/musb_host.c b/drivers/usb/musb-new/musb_host.c
index bbcee88..437309c 100644
--- a/drivers/usb/musb-new/musb_host.c
+++ b/drivers/usb/musb-new/musb_host.c
@@ -2130,8 +2130,6 @@ done:
return ret;
}
-
-#ifndef __UBOOT__
/*
* abort a transfer that's at the head of a hardware queue.
* called with controller locked, irqs blocked
@@ -2195,7 +2193,14 @@ static int musb_cleanup_urb(struct urb *urb, struct musb_qh *qh)
return status;
}
-static int musb_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
+#ifndef __UBOOT__
+static int musb_urb_dequeue(
+#else
+int musb_urb_dequeue(
+#endif
+ struct usb_hcd *hcd,
+ struct urb *urb,
+ int status)
{
struct musb *musb = hcd_to_musb(hcd);
struct musb_qh *qh;
@@ -2253,6 +2258,7 @@ done:
return ret;
}
+#ifndef __UBOOT__
/* disable an endpoint */
static void
musb_h_disable(struct usb_hcd *hcd, struct usb_host_endpoint *hep)
diff --git a/drivers/usb/musb-new/musb_host.h b/drivers/usb/musb-new/musb_host.h
index ebebe0c..546b4a2 100644
--- a/drivers/usb/musb-new/musb_host.h
+++ b/drivers/usb/musb-new/musb_host.h
@@ -110,5 +110,6 @@ static inline struct urb *next_urb(struct musb_qh *qh)
#ifdef __UBOOT__
int musb_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags);
+int musb_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status);
#endif
#endif /* _MUSB_HOST_H */
diff --git a/drivers/usb/musb-new/musb_regs.h b/drivers/usb/musb-new/musb_regs.h
index 03f2655..27e4ed4 100644
--- a/drivers/usb/musb-new/musb_regs.h
+++ b/drivers/usb/musb-new/musb_regs.h
@@ -216,6 +216,9 @@
#ifndef CONFIG_BLACKFIN
+/* SUNXI has different reg addresses, but identical r/w functions */
+#ifndef CONFIG_ARCH_SUNXI
+
/*
* Common USB registers
*/
@@ -318,6 +321,85 @@
#define MUSB_BUSCTL_OFFSET(_epnum, _offset) \
(0x80 + (8*(_epnum)) + (_offset))
+#else /* CONFIG_ARCH_SUNXI */
+
+/*
+ * Common USB registers
+ */
+
+#define MUSB_FADDR 0x0098
+#define MUSB_POWER 0x0040
+
+#define MUSB_INTRTX 0x0044
+#define MUSB_INTRRX 0x0046
+#define MUSB_INTRTXE 0x0048
+#define MUSB_INTRRXE 0x004A
+#define MUSB_INTRUSB 0x004C
+#define MUSB_INTRUSBE 0x0050
+#define MUSB_FRAME 0x0054
+#define MUSB_INDEX 0x0042
+#define MUSB_TESTMODE 0x007C
+
+/* Get offset for a given FIFO from musb->mregs */
+#define MUSB_FIFO_OFFSET(epnum) (0x00 + ((epnum) * 4))
+
+/*
+ * Additional Control Registers
+ */
+
+#define MUSB_DEVCTL 0x0041
+
+/* These are always controlled through the INDEX register */
+#define MUSB_TXFIFOSZ 0x0090
+#define MUSB_RXFIFOSZ 0x0094
+#define MUSB_TXFIFOADD 0x0092
+#define MUSB_RXFIFOADD 0x0096
+
+#define MUSB_EPINFO 0x0078
+#define MUSB_RAMINFO 0x0079
+#define MUSB_LINKINFO 0x007A
+#define MUSB_VPLEN 0x007B
+#define MUSB_HS_EOF1 0x007C
+#define MUSB_FS_EOF1 0x007D
+#define MUSB_LS_EOF1 0x007E
+
+/* Offsets to endpoint registers */
+#define MUSB_TXMAXP 0x0080
+#define MUSB_TXCSR 0x0082
+#define MUSB_CSR0 0x0082
+#define MUSB_RXMAXP 0x0084
+#define MUSB_RXCSR 0x0086
+#define MUSB_RXCOUNT 0x0088
+#define MUSB_COUNT0 0x0088
+#define MUSB_TXTYPE 0x008C
+#define MUSB_TYPE0 0x008C
+#define MUSB_TXINTERVAL 0x008D
+#define MUSB_NAKLIMIT0 0x008D
+#define MUSB_RXTYPE 0x008E
+#define MUSB_RXINTERVAL 0x008F
+
+#define MUSB_CONFIGDATA 0x00b0 /* musb_read_configdata adds 0x10 ! */
+#define MUSB_FIFOSIZE 0x0090
+
+/* Offsets to endpoint registers in indexed model (using INDEX register) */
+#define MUSB_INDEXED_OFFSET(_epnum, _offset) (_offset)
+
+#define MUSB_TXCSR_MODE 0x2000
+
+/* "bus control"/target registers, for host side multipoint (external hubs) */
+#define MUSB_TXFUNCADDR 0x0098
+#define MUSB_TXHUBADDR 0x009A
+#define MUSB_TXHUBPORT 0x009B
+
+#define MUSB_RXFUNCADDR 0x009C
+#define MUSB_RXHUBADDR 0x009E
+#define MUSB_RXHUBPORT 0x009F
+
+/* Endpoint is selected with MUSB_INDEX. */
+#define MUSB_BUSCTL_OFFSET(_epnum, _offset) (_offset)
+
+#endif /* CONFIG_ARCH_SUNXI */
+
static inline void musb_write_txfifosz(void __iomem *mbase, u8 c_size)
{
musb_writeb(mbase, MUSB_TXFIFOSZ, c_size);
@@ -340,7 +422,9 @@ static inline void musb_write_rxfifoadd(void __iomem *mbase, u16 c_off)
static inline void musb_write_ulpi_buscontrol(void __iomem *mbase, u8 val)
{
+#ifndef CONFIG_ARCH_SUNXI /* No ulpi on sunxi */
musb_writeb(mbase, MUSB_ULPI_BUSCONTROL, val);
+#endif
}
static inline u8 musb_read_txfifosz(void __iomem *mbase)
@@ -365,7 +449,11 @@ static inline u16 musb_read_rxfifoadd(void __iomem *mbase)
static inline u8 musb_read_ulpi_buscontrol(void __iomem *mbase)
{
+#ifdef CONFIG_ARCH_SUNXI /* No ulpi on sunxi */
+ return 0;
+#else
return musb_readb(mbase, MUSB_ULPI_BUSCONTROL);
+#endif
}
static inline u8 musb_read_configdata(void __iomem *mbase)
@@ -376,7 +464,11 @@ static inline u8 musb_read_configdata(void __iomem *mbase)
static inline u16 musb_read_hwvers(void __iomem *mbase)
{
+#ifdef CONFIG_ARCH_SUNXI
+ return 0; /* Unknown version */
+#else
return musb_readw(mbase, MUSB_HWVERS);
+#endif
}
static inline void __iomem *musb_read_target_reg_base(u8 i, void __iomem *mbase)
diff --git a/drivers/usb/musb-new/musb_uboot.c b/drivers/usb/musb-new/musb_uboot.c
index 2676f09..6e58ddf 100644
--- a/drivers/usb/musb-new/musb_uboot.c
+++ b/drivers/usb/musb-new/musb_uboot.c
@@ -12,6 +12,11 @@
#include "musb_gadget.h"
#ifdef CONFIG_MUSB_HOST
+struct int_queue {
+ struct usb_host_endpoint hep;
+ struct urb urb;
+};
+
static struct musb *host;
static struct usb_hcd hcd;
static enum usb_device_speed host_speed;
@@ -25,45 +30,42 @@ static void musb_host_complete_urb(struct urb *urb)
static struct usb_host_endpoint hep;
static struct urb urb;
-static struct urb *construct_urb(struct usb_device *dev, int endpoint_type,
- unsigned long pipe, void *buffer, int len,
- struct devrequest *setup, int interval)
+static void construct_urb(struct urb *urb, struct usb_host_endpoint *hep,
+ struct usb_device *dev, int endpoint_type,
+ unsigned long pipe, void *buffer, int len,
+ struct devrequest *setup, int interval)
{
int epnum = usb_pipeendpoint(pipe);
int is_in = usb_pipein(pipe);
- memset(&urb, 0, sizeof(struct urb));
- memset(&hep, 0, sizeof(struct usb_host_endpoint));
- INIT_LIST_HEAD(&hep.urb_list);
- INIT_LIST_HEAD(&urb.urb_list);
- urb.ep = &hep;
- urb.complete = musb_host_complete_urb;
- urb.status = -EINPROGRESS;
- urb.dev = dev;
- urb.pipe = pipe;
- urb.transfer_buffer = buffer;
- urb.transfer_dma = (unsigned long)buffer;
- urb.transfer_buffer_length = len;
- urb.setup_packet = (unsigned char *)setup;
-
- urb.ep->desc.wMaxPacketSize =
+ memset(urb, 0, sizeof(struct urb));
+ memset(hep, 0, sizeof(struct usb_host_endpoint));
+ INIT_LIST_HEAD(&hep->urb_list);
+ INIT_LIST_HEAD(&urb->urb_list);
+ urb->ep = hep;
+ urb->complete = musb_host_complete_urb;
+ urb->status = -EINPROGRESS;
+ urb->dev = dev;
+ urb->pipe = pipe;
+ urb->transfer_buffer = buffer;
+ urb->transfer_dma = (unsigned long)buffer;
+ urb->transfer_buffer_length = len;
+ urb->setup_packet = (unsigned char *)setup;
+
+ urb->ep->desc.wMaxPacketSize =
__cpu_to_le16(is_in ? dev->epmaxpacketin[epnum] :
dev->epmaxpacketout[epnum]);
- urb.ep->desc.bmAttributes = endpoint_type;
- urb.ep->desc.bEndpointAddress =
+ urb->ep->desc.bmAttributes = endpoint_type;
+ urb->ep->desc.bEndpointAddress =
(is_in ? USB_DIR_IN : USB_DIR_OUT) | epnum;
- urb.ep->desc.bInterval = interval;
-
- return &urb;
+ urb->ep->desc.bInterval = interval;
}
-#define MUSB_HOST_TIMEOUT 0x3ffffff
-
static int submit_urb(struct usb_hcd *hcd, struct urb *urb)
{
struct musb *host = hcd->hcd_priv;
int ret;
- int timeout;
+ unsigned long timeout;
ret = musb_urb_enqueue(hcd, urb, 0);
if (ret < 0) {
@@ -71,12 +73,16 @@ static int submit_urb(struct usb_hcd *hcd, struct urb *urb)
return ret;
}
- timeout = MUSB_HOST_TIMEOUT;
+ timeout = get_timer(0) + USB_TIMEOUT_MS(urb->pipe);
do {
if (ctrlc())
return -EIO;
host->isr(0, host);
- } while ((urb->dev->status & USB_ST_NOT_PROC) && --timeout);
+ } while (urb->status == -EINPROGRESS &&
+ get_timer(0) < timeout);
+
+ if (urb->status == -EINPROGRESS)
+ musb_urb_dequeue(hcd, urb, -ETIME);
return urb->status;
}
@@ -84,38 +90,117 @@ static int submit_urb(struct usb_hcd *hcd, struct urb *urb)
int submit_control_msg(struct usb_device *dev, unsigned long pipe,
void *buffer, int len, struct devrequest *setup)
{
- struct urb *urb = construct_urb(dev, USB_ENDPOINT_XFER_CONTROL, pipe,
- buffer, len, setup, 0);
+ construct_urb(&urb, &hep, dev, USB_ENDPOINT_XFER_CONTROL, pipe,
+ buffer, len, setup, 0);
/* Fix speed for non hub-attached devices */
if (!dev->parent)
dev->speed = host_speed;
- return submit_urb(&hcd, urb);
+ return submit_urb(&hcd, &urb);
}
int submit_bulk_msg(struct usb_device *dev, unsigned long pipe,
void *buffer, int len)
{
- struct urb *urb = construct_urb(dev, USB_ENDPOINT_XFER_BULK, pipe,
- buffer, len, NULL, 0);
- return submit_urb(&hcd, urb);
+ construct_urb(&urb, &hep, dev, USB_ENDPOINT_XFER_BULK, pipe,
+ buffer, len, NULL, 0);
+ return submit_urb(&hcd, &urb);
}
int submit_int_msg(struct usb_device *dev, unsigned long pipe,
void *buffer, int len, int interval)
{
- struct urb *urb = construct_urb(dev, USB_ENDPOINT_XFER_INT, pipe,
- buffer, len, NULL, interval);
- return submit_urb(&hcd, urb);
+ construct_urb(&urb, &hep, dev, USB_ENDPOINT_XFER_INT, pipe,
+ buffer, len, NULL, interval);
+ return submit_urb(&hcd, &urb);
}
-int usb_lowlevel_init(int index, enum usb_init_type init, void **controller)
+struct int_queue *create_int_queue(struct usb_device *dev, unsigned long pipe,
+ int queuesize, int elementsize, void *buffer, int interval)
+{
+ struct int_queue *queue;
+ int ret, index = usb_pipein(pipe) * 16 + usb_pipeendpoint(pipe);
+
+ if (queuesize != 1) {
+ printf("ERROR musb int-queues only support queuesize 1\n");
+ return NULL;
+ }
+
+ if (dev->int_pending & (1 << index)) {
+ printf("ERROR int-urb is already pending on pipe %lx\n", pipe);
+ return NULL;
+ }
+
+ queue = malloc(sizeof(*queue));
+ if (!queue)
+ return NULL;
+
+ construct_urb(&queue->urb, &queue->hep, dev, USB_ENDPOINT_XFER_INT,
+ pipe, buffer, elementsize, NULL, interval);
+
+ ret = musb_urb_enqueue(&hcd, &queue->urb, 0);
+ if (ret < 0) {
+ printf("Failed to enqueue URB to controller\n");
+ free(queue);
+ return NULL;
+ }
+
+ dev->int_pending |= 1 << index;
+ return queue;
+}
+
+int destroy_int_queue(struct usb_device *dev, struct int_queue *queue)
+{
+ int index = usb_pipein(queue->urb.pipe) * 16 +
+ usb_pipeendpoint(queue->urb.pipe);
+
+ if (queue->urb.status == -EINPROGRESS)
+ musb_urb_dequeue(&hcd, &queue->urb, -ETIME);
+
+ dev->int_pending &= ~(1 << index);
+ free(queue);
+ return 0;
+}
+
+void *poll_int_queue(struct usb_device *dev, struct int_queue *queue)
{
+ if (queue->urb.status != -EINPROGRESS)
+ return NULL; /* URB has already completed in a prev. poll */
+
+ host->isr(0, host);
+
+ if (queue->urb.status != -EINPROGRESS)
+ return queue->urb.transfer_buffer; /* Done */
+
+ return NULL; /* URB still pending */
+}
+
+void usb_reset_root_port(void)
+{
+ void *mbase = host->mregs;
u8 power;
+
+ power = musb_readb(mbase, MUSB_POWER);
+ power &= 0xf0;
+ musb_writeb(mbase, MUSB_POWER, MUSB_POWER_RESET | power);
+ mdelay(50);
+ power = musb_readb(mbase, MUSB_POWER);
+ musb_writeb(mbase, MUSB_POWER, ~MUSB_POWER_RESET & power);
+ host->isr(0, host);
+ host_speed = (musb_readb(mbase, MUSB_POWER) & MUSB_POWER_HSMODE) ?
+ USB_SPEED_HIGH :
+ (musb_readb(mbase, MUSB_DEVCTL) & MUSB_DEVCTL_FSDEV) ?
+ USB_SPEED_FULL : USB_SPEED_LOW;
+ mdelay((host_speed == USB_SPEED_LOW) ? 200 : 50);
+}
+
+int usb_lowlevel_init(int index, enum usb_init_type init, void **controller)
+{
void *mbase;
- int timeout = MUSB_HOST_TIMEOUT;
+ /* USB spec says it may take up to 1 second for a device to connect */
+ unsigned long timeout = get_timer(0) + 1000;
if (!host) {
printf("MUSB host is not registered\n");
@@ -127,20 +212,11 @@ int usb_lowlevel_init(int index, enum usb_init_type init, void **controller)
do {
if (musb_readb(mbase, MUSB_DEVCTL) & MUSB_DEVCTL_HM)
break;
- } while (--timeout);
- if (!timeout)
+ } while (get_timer(0) < timeout);
+ if (get_timer(0) >= timeout)
return -ENODEV;
- power = musb_readb(mbase, MUSB_POWER);
- musb_writeb(mbase, MUSB_POWER, MUSB_POWER_RESET | power);
- udelay(30000);
- power = musb_readb(mbase, MUSB_POWER);
- musb_writeb(mbase, MUSB_POWER, ~MUSB_POWER_RESET & power);
- host->isr(0, host);
- host_speed = (musb_readb(mbase, MUSB_POWER) & MUSB_POWER_HSMODE) ?
- USB_SPEED_HIGH :
- (musb_readb(mbase, MUSB_DEVCTL) & MUSB_DEVCTL_FSDEV) ?
- USB_SPEED_FULL : USB_SPEED_LOW;
+ usb_reset_root_port();
host->is_active = 1;
hcd.hcd_priv = host;
diff --git a/drivers/usb/musb-new/sunxi.c b/drivers/usb/musb-new/sunxi.c
new file mode 100644
index 0000000..778916d
--- /dev/null
+++ b/drivers/usb/musb-new/sunxi.c
@@ -0,0 +1,279 @@
+/*
+ * Allwinner SUNXI "glue layer"
+ *
+ * Copyright © 2015 Hans de Goede <hdegoede@redhat.com>
+ * Copyright © 2013 Jussi Kivilinna <jussi.kivilinna@iki.fi>
+ *
+ * Based on the sw_usb "Allwinner OTG Dual Role Controller" code.
+ * Copyright 2007-2012 (C) Allwinner Technology Co., Ltd.
+ * javen <javen@allwinnertech.com>
+ *
+ * Based on the DA8xx "glue layer" code.
+ * Copyright (c) 2008-2009 MontaVista Software, Inc. <source@mvista.com>
+ * Copyright (C) 2005-2006 by Texas Instruments
+ *
+ * This file is part of the Inventra Controller Driver for Linux.
+ *
+ * The Inventra Controller Driver for Linux is free software; you
+ * can redistribute it and/or modify it under the terms of the GNU
+ * General Public License version 2 as published by the Free Software
+ * Foundation.
+ *
+ */
+#include <common.h>
+#include <asm/arch/cpu.h>
+#include <asm/arch/usbc.h>
+#include "linux-compat.h"
+#include "musb_core.h"
+
+/******************************************************************************
+ ******************************************************************************
+ * From the Allwinner driver
+ ******************************************************************************
+ ******************************************************************************/
+
+/******************************************************************************
+ * From include/sunxi_usb_bsp.h
+ ******************************************************************************/
+
+/* reg offsets */
+#define USBC_REG_o_ISCR 0x0400
+#define USBC_REG_o_PHYCTL 0x0404
+#define USBC_REG_o_PHYBIST 0x0408
+#define USBC_REG_o_PHYTUNE 0x040c
+
+#define USBC_REG_o_VEND0 0x0043
+
+/* Interface Status and Control */
+#define USBC_BP_ISCR_VBUS_VALID_FROM_DATA 30
+#define USBC_BP_ISCR_VBUS_VALID_FROM_VBUS 29
+#define USBC_BP_ISCR_EXT_ID_STATUS 28
+#define USBC_BP_ISCR_EXT_DM_STATUS 27
+#define USBC_BP_ISCR_EXT_DP_STATUS 26
+#define USBC_BP_ISCR_MERGED_VBUS_STATUS 25
+#define USBC_BP_ISCR_MERGED_ID_STATUS 24
+
+#define USBC_BP_ISCR_ID_PULLUP_EN 17
+#define USBC_BP_ISCR_DPDM_PULLUP_EN 16
+#define USBC_BP_ISCR_FORCE_ID 14
+#define USBC_BP_ISCR_FORCE_VBUS_VALID 12
+#define USBC_BP_ISCR_VBUS_VALID_SRC 10
+
+#define USBC_BP_ISCR_HOSC_EN 7
+#define USBC_BP_ISCR_VBUS_CHANGE_DETECT 6
+#define USBC_BP_ISCR_ID_CHANGE_DETECT 5
+#define USBC_BP_ISCR_DPDM_CHANGE_DETECT 4
+#define USBC_BP_ISCR_IRQ_ENABLE 3
+#define USBC_BP_ISCR_VBUS_CHANGE_DETECT_EN 2
+#define USBC_BP_ISCR_ID_CHANGE_DETECT_EN 1
+#define USBC_BP_ISCR_DPDM_CHANGE_DETECT_EN 0
+
+/******************************************************************************
+ * From usbc/usbc.c
+ ******************************************************************************/
+
+static u32 USBC_WakeUp_ClearChangeDetect(u32 reg_val)
+{
+ u32 temp = reg_val;
+
+ temp &= ~(1 << USBC_BP_ISCR_VBUS_CHANGE_DETECT);
+ temp &= ~(1 << USBC_BP_ISCR_ID_CHANGE_DETECT);
+ temp &= ~(1 << USBC_BP_ISCR_DPDM_CHANGE_DETECT);
+
+ return temp;
+}
+
+static void USBC_EnableIdPullUp(__iomem void *base)
+{
+ u32 reg_val;
+
+ reg_val = musb_readl(base, USBC_REG_o_ISCR);
+ reg_val |= (1 << USBC_BP_ISCR_ID_PULLUP_EN);
+ reg_val = USBC_WakeUp_ClearChangeDetect(reg_val);
+ musb_writel(base, USBC_REG_o_ISCR, reg_val);
+}
+
+static void USBC_DisableIdPullUp(__iomem void *base)
+{
+ u32 reg_val;
+
+ reg_val = musb_readl(base, USBC_REG_o_ISCR);
+ reg_val &= ~(1 << USBC_BP_ISCR_ID_PULLUP_EN);
+ reg_val = USBC_WakeUp_ClearChangeDetect(reg_val);
+ musb_writel(base, USBC_REG_o_ISCR, reg_val);
+}
+
+static void USBC_EnableDpDmPullUp(__iomem void *base)
+{
+ u32 reg_val;
+
+ reg_val = musb_readl(base, USBC_REG_o_ISCR);
+ reg_val |= (1 << USBC_BP_ISCR_DPDM_PULLUP_EN);
+ reg_val = USBC_WakeUp_ClearChangeDetect(reg_val);
+ musb_writel(base, USBC_REG_o_ISCR, reg_val);
+}
+
+static void USBC_DisableDpDmPullUp(__iomem void *base)
+{
+ u32 reg_val;
+
+ reg_val = musb_readl(base, USBC_REG_o_ISCR);
+ reg_val &= ~(1 << USBC_BP_ISCR_DPDM_PULLUP_EN);
+ reg_val = USBC_WakeUp_ClearChangeDetect(reg_val);
+ musb_writel(base, USBC_REG_o_ISCR, reg_val);
+}
+
+static void USBC_ForceIdToLow(__iomem void *base)
+{
+ u32 reg_val;
+
+ reg_val = musb_readl(base, USBC_REG_o_ISCR);
+ reg_val &= ~(0x03 << USBC_BP_ISCR_FORCE_ID);
+ reg_val |= (0x02 << USBC_BP_ISCR_FORCE_ID);
+ reg_val = USBC_WakeUp_ClearChangeDetect(reg_val);
+ musb_writel(base, USBC_REG_o_ISCR, reg_val);
+}
+
+static void USBC_ForceIdToHigh(__iomem void *base)
+{
+ u32 reg_val;
+
+ reg_val = musb_readl(base, USBC_REG_o_ISCR);
+ reg_val &= ~(0x03 << USBC_BP_ISCR_FORCE_ID);
+ reg_val |= (0x03 << USBC_BP_ISCR_FORCE_ID);
+ reg_val = USBC_WakeUp_ClearChangeDetect(reg_val);
+ musb_writel(base, USBC_REG_o_ISCR, reg_val);
+}
+
+static void USBC_ForceVbusValidDisable(__iomem void *base)
+{
+ u32 reg_val;
+
+ reg_val = musb_readl(base, USBC_REG_o_ISCR);
+ reg_val &= ~(0x03 << USBC_BP_ISCR_FORCE_VBUS_VALID);
+ reg_val = USBC_WakeUp_ClearChangeDetect(reg_val);
+ musb_writel(base, USBC_REG_o_ISCR, reg_val);
+}
+
+static void USBC_ForceVbusValidToHigh(__iomem void *base)
+{
+ u32 reg_val;
+
+ reg_val = musb_readl(base, USBC_REG_o_ISCR);
+ reg_val &= ~(0x03 << USBC_BP_ISCR_FORCE_VBUS_VALID);
+ reg_val |= (0x03 << USBC_BP_ISCR_FORCE_VBUS_VALID);
+ reg_val = USBC_WakeUp_ClearChangeDetect(reg_val);
+ musb_writel(base, USBC_REG_o_ISCR, reg_val);
+}
+
+static void USBC_ConfigFIFO_Base(void)
+{
+ u32 reg_value;
+
+ /* config usb fifo, 8kb mode */
+ reg_value = readl(SUNXI_SRAMC_BASE + 0x04);
+ reg_value &= ~(0x03 << 0);
+ reg_value |= (1 << 0);
+ writel(reg_value, SUNXI_SRAMC_BASE + 0x04);
+}
+
+/******************************************************************************
+ * MUSB Glue code
+ ******************************************************************************/
+
+static irqreturn_t sunxi_musb_interrupt(int irq, void *__hci)
+{
+ struct musb *musb = __hci;
+ irqreturn_t retval = IRQ_NONE;
+
+ /* read and flush interrupts */
+ musb->int_usb = musb_readb(musb->mregs, MUSB_INTRUSB);
+ if (musb->int_usb)
+ musb_writeb(musb->mregs, MUSB_INTRUSB, musb->int_usb);
+ musb->int_tx = musb_readw(musb->mregs, MUSB_INTRTX);
+ if (musb->int_tx)
+ musb_writew(musb->mregs, MUSB_INTRTX, musb->int_tx);
+ musb->int_rx = musb_readw(musb->mregs, MUSB_INTRRX);
+ if (musb->int_rx)
+ musb_writew(musb->mregs, MUSB_INTRRX, musb->int_rx);
+
+ if (musb->int_usb || musb->int_tx || musb->int_rx)
+ retval |= musb_interrupt(musb);
+
+ return retval;
+}
+
+static void sunxi_musb_enable(struct musb *musb)
+{
+ pr_debug("%s():\n", __func__);
+
+ /* select PIO mode */
+ musb_writeb(musb->mregs, USBC_REG_o_VEND0, 0);
+
+ if (is_host_enabled(musb)) {
+ /* port power on */
+ sunxi_usbc_vbus_enable(0);
+ }
+}
+
+static void sunxi_musb_disable(struct musb *musb)
+{
+ pr_debug("%s():\n", __func__);
+
+ /* Put the controller back in a pristane state for "usb reset" */
+ if (musb->is_active) {
+ sunxi_usbc_disable(0);
+ sunxi_usbc_enable(0);
+ musb->is_active = 0;
+ }
+}
+
+static int sunxi_musb_init(struct musb *musb)
+{
+ int err;
+
+ pr_debug("%s():\n", __func__);
+
+ err = sunxi_usbc_request_resources(0);
+ if (err)
+ return err;
+
+ musb->isr = sunxi_musb_interrupt;
+ sunxi_usbc_enable(0);
+
+ USBC_ConfigFIFO_Base();
+ USBC_EnableDpDmPullUp(musb->mregs);
+ USBC_EnableIdPullUp(musb->mregs);
+
+ if (is_host_enabled(musb)) {
+ /* Host mode */
+ USBC_ForceIdToLow(musb->mregs);
+ USBC_ForceVbusValidToHigh(musb->mregs);
+ } else {
+ /* Peripheral mode */
+ USBC_ForceIdToHigh(musb->mregs);
+ USBC_ForceVbusValidDisable(musb->mregs);
+ }
+
+ return 0;
+}
+
+static int sunxi_musb_exit(struct musb *musb)
+{
+ pr_debug("%s():\n", __func__);
+
+ USBC_DisableDpDmPullUp(musb->mregs);
+ USBC_DisableIdPullUp(musb->mregs);
+ sunxi_usbc_vbus_disable(0);
+ sunxi_usbc_disable(0);
+
+ return sunxi_usbc_free_resources(0);
+}
+
+const struct musb_platform_ops sunxi_musb_ops = {
+ .init = sunxi_musb_init,
+ .exit = sunxi_musb_exit,
+
+ .enable = sunxi_musb_enable,
+ .disable = sunxi_musb_disable,
+};
diff --git a/drivers/usb/musb-new/usb-compat.h b/drivers/usb/musb-new/usb-compat.h
index 27f656f..50bad37 100644
--- a/drivers/usb/musb-new/usb-compat.h
+++ b/drivers/usb/musb-new/usb-compat.h
@@ -48,6 +48,7 @@ struct urb {
list_add_tail(&urb->urb_list, &urb->ep->urb_list); \
ret; })
#define usb_hcd_unlink_urb_from_ep(hcd, urb) list_del_init(&urb->urb_list)
+#define usb_hcd_check_unlink_urb(hdc, urb, status) 0
static inline void usb_hcd_giveback_urb(struct usb_hcd *hcd,
struct urb *urb,
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index e69de29..ccbd7e2 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -0,0 +1,89 @@
+config VIDEO_X86
+ bool "Enable x86 video driver support"
+ depends on X86
+ default n
+ help
+ Turn on this option to enable a very simple driver which uses vesa
+ to discover the video mode and then provides a frame buffer for use
+ by U-Boot.
+
+config VIDEO_LCD_SSD2828
+ bool "SSD2828 bridge chip"
+ default n
+ ---help---
+ Support for the SSD2828 bridge chip, which can take pixel data coming
+ from a parallel LCD interface and translate it on the fly into MIPI DSI
+ interface for driving a MIPI compatible LCD panel. It uses SPI for
+ configuration.
+
+config VIDEO_LCD_SSD2828_TX_CLK
+ int "SSD2828 TX_CLK frequency (in MHz)"
+ depends on VIDEO_LCD_SSD2828
+ default 0
+ ---help---
+ The frequency of the crystal, which is clocking SSD2828. It may be
+ anything in the 8MHz-30MHz range and the exact value should be
+ retrieved from the board schematics. Or in the case of Allwinner
+ hardware, it can be usually found as 'lcd_xtal_freq' variable in
+ FEX files. It can be also set to 0 for selecting PCLK from the
+ parallel LCD interface instead of TX_CLK as the PLL clock source.
+
+config VIDEO_LCD_SSD2828_RESET
+ string "RESET pin of SSD2828"
+ depends on VIDEO_LCD_SSD2828
+ default ""
+ ---help---
+ The reset pin of SSD2828 chip. This takes a string in the format
+ understood by 'name_to_gpio' function, e.g. PH1 for pin 1 of port H.
+
+config VIDEO_LCD_HITACHI_TX18D42VM
+ bool "Hitachi tx18d42vm LVDS LCD panel support"
+ depends on VIDEO
+ default n
+ ---help---
+ Support for Hitachi tx18d42vm LVDS LCD panels, these panels have a
+ lcd controller which needs to be initialized over SPI, once that is
+ done they work like a regular LVDS panel.
+
+config VIDEO_LCD_SPI_CS
+ string "SPI CS pin for LCD related config job"
+ depends on VIDEO_LCD_SSD2828 || VIDEO_LCD_HITACHI_TX18D42VM
+ default ""
+ ---help---
+ This is one of the SPI communication pins, involved in setting up a
+ working LCD configuration. The exact role of SPI may differ for
+ different hardware setups. The option takes a string in the format
+ understood by 'name_to_gpio' function, e.g. PH1 for pin 1 of port H.
+
+config VIDEO_LCD_SPI_SCLK
+ string "SPI SCLK pin for LCD related config job"
+ depends on VIDEO_LCD_SSD2828 || VIDEO_LCD_HITACHI_TX18D42VM
+ default ""
+ ---help---
+ This is one of the SPI communication pins, involved in setting up a
+ working LCD configuration. The exact role of SPI may differ for
+ different hardware setups. The option takes a string in the format
+ understood by 'name_to_gpio' function, e.g. PH1 for pin 1 of port H.
+
+config VIDEO_LCD_SPI_MOSI
+ string "SPI MOSI pin for LCD related config job"
+ depends on VIDEO_LCD_SSD2828 || VIDEO_LCD_HITACHI_TX18D42VM
+ default ""
+ ---help---
+ This is one of the SPI communication pins, involved in setting up a
+ working LCD configuration. The exact role of SPI may differ for
+ different hardware setups. The option takes a string in the format
+ understood by 'name_to_gpio' function, e.g. PH1 for pin 1 of port H.
+
+config VIDEO_LCD_SPI_MISO
+ string "SPI MISO pin for LCD related config job (optional)"
+ depends on VIDEO_LCD_SSD2828
+ default ""
+ ---help---
+ This is one of the SPI communication pins, involved in setting up a
+ working LCD configuration. The exact role of SPI may differ for
+ different hardware setups. If wired up, this pin may provide additional
+ useful functionality. Such as bi-directional communication with the
+ hardware and LCD panel id retrieval (if the panel can report it). The
+ option takes a string in the format understood by 'name_to_gpio'
+ function, e.g. PH1 for pin 1 of port H.
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index 00b563f..c3fcf45 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -29,6 +29,8 @@ obj-$(CONFIG_VIDEO_COREBOOT) += coreboot_fb.o
obj-$(CONFIG_VIDEO_CT69000) += ct69000.o videomodes.o
obj-$(CONFIG_VIDEO_DA8XX) += da8xx-fb.o videomodes.o
obj-$(CONFIG_VIDEO_IMX25LCDC) += imx25lcdc.o videomodes.o
+obj-$(CONFIG_VIDEO_LCD_HITACHI_TX18D42VM) += hitachi_tx18d42vm_lcd.o
+obj-$(CONFIG_VIDEO_LCD_SSD2828) += ssd2828.o
obj-$(CONFIG_VIDEO_MB862xx) += mb862xx.o videomodes.o
obj-$(CONFIG_VIDEO_MB86R0xGDC) += mb86r0xgdc.o videomodes.o
obj-$(CONFIG_VIDEO_MX3) += mx3fb.o videomodes.o
@@ -39,7 +41,7 @@ obj-$(CONFIG_VIDEO_SANDBOX_SDL) += sandbox_sdl.o
obj-$(CONFIG_VIDEO_SED13806) += sed13806.o
obj-$(CONFIG_VIDEO_SM501) += sm501.o
obj-$(CONFIG_VIDEO_SMI_LYNXEM) += smiLynxEM.o videomodes.o
-obj-$(CONFIG_VIDEO_SUNXI) += sunxi_display.o
+obj-$(CONFIG_VIDEO_SUNXI) += sunxi_display.o videomodes.o
obj-$(CONFIG_VIDEO_TEGRA) += tegra.o
obj-$(CONFIG_VIDEO_VCXK) += bus_vcxk.o
obj-$(CONFIG_VIDEO_X86) += x86_fb.o
diff --git a/drivers/video/cfb_console.c b/drivers/video/cfb_console.c
index a653bb4..d4226e3 100644
--- a/drivers/video/cfb_console.c
+++ b/drivers/video/cfb_console.c
@@ -117,24 +117,11 @@
* Defines for the SED13806 driver
*/
#ifdef CONFIG_VIDEO_SED13806
-
-#ifndef CONFIG_TOTAL5200
#define VIDEO_FB_LITTLE_ENDIAN
-#endif
#define VIDEO_HW_RECTFILL
#define VIDEO_HW_BITBLT
#endif
-/*
- * Defines for the SED13806 driver
- */
-#ifdef CONFIG_VIDEO_SM501
-
-#ifdef CONFIG_HH405
-#define VIDEO_FB_LITTLE_ENDIAN
-#endif
-#endif
-
#ifdef CONFIG_VIDEO_MXS
#define VIDEO_FB_16BPP_WORD_SWAP
#endif
diff --git a/drivers/video/hitachi_tx18d42vm_lcd.c b/drivers/video/hitachi_tx18d42vm_lcd.c
new file mode 100644
index 0000000..1ce4a8c
--- /dev/null
+++ b/drivers/video/hitachi_tx18d42vm_lcd.c
@@ -0,0 +1,81 @@
+/*
+ * Hitachi tx18d42vm LVDS LCD panel driver
+ *
+ * (C) Copyright 2015 Hans de Goede <hdegoede@redhat.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <common.h>
+
+#include <asm/gpio.h>
+#include <errno.h>
+
+/*
+ * Very simple write only SPI support, this does not use the generic SPI infra
+ * because that assumes R/W SPI, requiring a MISO pin. Also the necessary glue
+ * code alone would be larger then this minimal version.
+ */
+static void lcd_panel_spi_write(int cs, int clk, int mosi,
+ unsigned int data, int bits)
+{
+ int i, offset;
+
+ gpio_direction_output(cs, 0);
+ for (i = 0; i < bits; i++) {
+ gpio_direction_output(clk, 0);
+ offset = (bits - 1) - i;
+ gpio_direction_output(mosi, (data >> offset) & 1);
+ udelay(2);
+ gpio_direction_output(clk, 1);
+ udelay(2);
+ }
+ gpio_direction_output(cs, 1);
+ udelay(2);
+}
+
+int hitachi_tx18d42vm_init(void)
+{
+ const u16 init_data[] = {
+ 0x0029, /* reset */
+ 0x0025, /* standby */
+ 0x0840, /* enable normally black */
+ 0x0430, /* enable FRC/dither */
+ 0x385f, /* enter test mode(1) */
+ 0x3ca4, /* enter test mode(2) */
+ 0x3409, /* enable SDRRS, enlarge OE width */
+ 0x4041, /* adopt 2 line / 1 dot */
+ };
+ int i, cs, clk, mosi, ret = 0;
+
+ cs = name_to_gpio(CONFIG_VIDEO_LCD_SPI_CS);
+ clk = name_to_gpio(CONFIG_VIDEO_LCD_SPI_SCLK);
+ mosi = name_to_gpio(CONFIG_VIDEO_LCD_SPI_MOSI);
+
+ if (cs == -1 || clk == -1 || mosi == 1) {
+ printf("Error tx18d42vm spi gpio config is invalid\n");
+ return -EINVAL;
+ }
+
+ if (gpio_request(cs, "tx18d42vm-spi-cs") != 0 ||
+ gpio_request(clk, "tx18d42vm-spi-clk") != 0 ||
+ gpio_request(mosi, "tx18d42vm-spi-mosi") != 0) {
+ printf("Error cannot request tx18d42vm spi gpios\n");
+ ret = -EBUSY;
+ goto out;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(init_data); i++)
+ lcd_panel_spi_write(cs, clk, mosi, init_data[i], 16);
+
+ mdelay(50); /* All the tx18d42vm drivers have a delay here ? */
+
+ lcd_panel_spi_write(cs, clk, mosi, 0x00ad, 16); /* display on */
+
+out:
+ gpio_free(mosi);
+ gpio_free(clk);
+ gpio_free(cs);
+
+ return ret;
+}
diff --git a/drivers/video/hitachi_tx18d42vm_lcd.h b/drivers/video/hitachi_tx18d42vm_lcd.h
new file mode 100644
index 0000000..1b72800
--- /dev/null
+++ b/drivers/video/hitachi_tx18d42vm_lcd.h
@@ -0,0 +1,9 @@
+/*
+ * Hitachi tx18d42vm LVDS LCD panel driver
+ *
+ * (C) Copyright 2015 Hans de Goede <hdegoede@redhat.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+void hitachi_tx18d42vm_init(void);
diff --git a/drivers/video/mpc8xx_lcd.c b/drivers/video/mpc8xx_lcd.c
index 50eed89..add7215 100644
--- a/drivers/video/mpc8xx_lcd.c
+++ b/drivers/video/mpc8xx_lcd.c
@@ -34,11 +34,6 @@
#define CONFIG_LCD_INFO /* Display Logo, (C) and system info */
#endif
-#if defined(CONFIG_EDT32F10)
-#undef CONFIG_LCD_LOGO
-#undef CONFIG_LCD_INFO
-#endif
-
/*----------------------------------------------------------------------*/
#ifdef CONFIG_KYOCERA_KCS057QV1AJ
/*
@@ -224,20 +219,6 @@ vidinfo_t panel_info = {
};
#endif /* CONFIG_OPTREX_BW */
-/*-----------------------------------------------------------------*/
-#ifdef CONFIG_EDT32F10
-/*
- * Emerging Display Technologies 320x240. Passive, monochrome, single scan.
- */
-#define LCD_BPP LCD_MONOCHROME
-#define LCD_DF 10
-
-vidinfo_t panel_info = {
- 320, 240, 0, 0, CONFIG_SYS_HIGH, CONFIG_SYS_HIGH, CONFIG_SYS_HIGH, CONFIG_SYS_HIGH, CONFIG_SYS_LOW,
- LCD_BPP, 0, 0, 0, 0, 33, 0, 0, 0
-};
-#endif
-
/************************************************************************/
/* ----------------- chipset specific functions ----------------------- */
/************************************************************************/
@@ -305,7 +286,6 @@ void lcd_ctrl_init (void *lcdbase)
immr->im_clkrst.car_sccr &= ~0x1F;
immr->im_clkrst.car_sccr |= LCD_DF; /* was 8 */
-#if !defined(CONFIG_EDT32F10)
/* Enable LCD on port D.
*/
immr->im_ioport.iop_pdpar |= 0x1FFF;
@@ -315,14 +295,6 @@ void lcd_ctrl_init (void *lcdbase)
*/
immr->im_cpm.cp_pbpar |= 0x00005001;
immr->im_cpm.cp_pbdir |= 0x00005001;
-#else
- /* Enable LCD on port D.
- */
- immr->im_ioport.iop_pdpar |= 0x1DFF;
- immr->im_ioport.iop_pdpar &= ~0x0200;
- immr->im_ioport.iop_pddir |= 0x1FFF;
- immr->im_ioport.iop_pddat |= 0x0200;
-#endif
/* Load the physical address of the linear frame buffer
* into the LCD controller.
@@ -373,9 +345,7 @@ lcd_setcolreg (ushort regno, ushort red, ushort green, ushort blue)
colreg = ((red & 0x0F) << 8) |
((green & 0x0F) << 4) |
(blue & 0x0F) ;
-#ifdef CONFIG_SYS_INVERT_COLORS
- colreg ^= 0x0FFF;
-#endif
+
*cmap_ptr = colreg;
debug ("setcolreg: reg %2d @ %p: R=%02X G=%02X B=%02X => %02X%02X\n",
@@ -387,23 +357,6 @@ lcd_setcolreg (ushort regno, ushort red, ushort green, ushort blue)
/*----------------------------------------------------------------------*/
-#if LCD_BPP == LCD_MONOCHROME
-static
-void lcd_initcolregs (void)
-{
- volatile immap_t *immr = (immap_t *) CONFIG_SYS_IMMR;
- volatile cpm8xx_t *cp = &(immr->im_cpm);
- ushort regno;
-
- for (regno = 0; regno < 16; regno++) {
- cp->lcd_cmap[regno * 2] = 0;
- cp->lcd_cmap[(regno * 2) + 1] = regno & 0x0f;
- }
-}
-#endif
-
-/*----------------------------------------------------------------------*/
-
void lcd_enable (void)
{
volatile immap_t *immr = (immap_t *) CONFIG_SYS_IMMR;
diff --git a/drivers/video/pxa_lcd.c b/drivers/video/pxa_lcd.c
index e19f6ac..f66f615 100644
--- a/drivers/video/pxa_lcd.c
+++ b/drivers/video/pxa_lcd.c
@@ -379,21 +379,6 @@ lcd_setcolreg (ushort regno, ushort red, ushort green, ushort blue)
#endif /* LCD_COLOR8 */
/*----------------------------------------------------------------------*/
-#if LCD_BPP == LCD_MONOCHROME
-void lcd_initcolregs (void)
-{
- struct pxafb_info *fbi = &panel_info.pxa;
- cmap = (ushort *)fbi->palette;
- ushort regno;
-
- for (regno = 0; regno < 16; regno++) {
- cmap[regno * 2] = 0;
- cmap[(regno * 2) + 1] = regno & 0x0f;
- }
-}
-#endif /* LCD_MONOCHROME */
-
-/*----------------------------------------------------------------------*/
__weak void lcd_enable(void)
{
}
diff --git a/drivers/video/sed13806.c b/drivers/video/sed13806.c
index da653c0..cd7fac6 100644
--- a/drivers/video/sed13806.c
+++ b/drivers/video/sed13806.c
@@ -18,13 +18,8 @@
#define writeByte(ptrReg,value) \
*(volatile unsigned char *)(sed13806.isaBase + ptrReg) = value
-#ifdef CONFIG_TOTAL5200
-#define writeWord(ptrReg,value) \
- (*(volatile unsigned short *)(sed13806.isaBase + ptrReg) = value)
-#else
#define writeWord(ptrReg,value) \
(*(volatile unsigned short *)(sed13806.isaBase + ptrReg) = ((value >> 8 ) & 0xff) | ((value << 8) & 0xff00))
-#endif
GraphicDevice sed13806;
diff --git a/drivers/video/ssd2828.c b/drivers/video/ssd2828.c
new file mode 100644
index 0000000..8b09082
--- /dev/null
+++ b/drivers/video/ssd2828.c
@@ -0,0 +1,436 @@
+/*
+ * (C) 2015 Siarhei Siamashka <siarhei.siamashka@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+/*
+ * Support for the SSD2828 bridge chip, which can take pixel data coming
+ * from a parallel LCD interface and translate it on the flight into MIPI DSI
+ * interface for driving a MIPI compatible TFT display.
+ */
+
+#include <common.h>
+#include <mipi_display.h>
+#include <asm/arch/gpio.h>
+#include <asm/gpio.h>
+
+#include "videomodes.h"
+#include "ssd2828.h"
+
+#define SSD2828_DIR 0xB0
+#define SSD2828_VICR1 0xB1
+#define SSD2828_VICR2 0xB2
+#define SSD2828_VICR3 0xB3
+#define SSD2828_VICR4 0xB4
+#define SSD2828_VICR5 0xB5
+#define SSD2828_VICR6 0xB6
+#define SSD2828_CFGR 0xB7
+#define SSD2828_VCR 0xB8
+#define SSD2828_PCR 0xB9
+#define SSD2828_PLCR 0xBA
+#define SSD2828_CCR 0xBB
+#define SSD2828_PSCR1 0xBC
+#define SSD2828_PSCR2 0xBD
+#define SSD2828_PSCR3 0xBE
+#define SSD2828_PDR 0xBF
+#define SSD2828_OCR 0xC0
+#define SSD2828_MRSR 0xC1
+#define SSD2828_RDCR 0xC2
+#define SSD2828_ARSR 0xC3
+#define SSD2828_LCR 0xC4
+#define SSD2828_ICR 0xC5
+#define SSD2828_ISR 0xC6
+#define SSD2828_ESR 0xC7
+#define SSD2828_DAR1 0xC9
+#define SSD2828_DAR2 0xCA
+#define SSD2828_DAR3 0xCB
+#define SSD2828_DAR4 0xCC
+#define SSD2828_DAR5 0xCD
+#define SSD2828_DAR6 0xCE
+#define SSD2828_HTTR1 0xCF
+#define SSD2828_HTTR2 0xD0
+#define SSD2828_LRTR1 0xD1
+#define SSD2828_LRTR2 0xD2
+#define SSD2828_TSR 0xD3
+#define SSD2828_LRR 0xD4
+#define SSD2828_PLLR 0xD5
+#define SSD2828_TR 0xD6
+#define SSD2828_TECR 0xD7
+#define SSD2828_ACR1 0xD8
+#define SSD2828_ACR2 0xD9
+#define SSD2828_ACR3 0xDA
+#define SSD2828_ACR4 0xDB
+#define SSD2828_IOCR 0xDC
+#define SSD2828_VICR7 0xDD
+#define SSD2828_LCFR 0xDE
+#define SSD2828_DAR7 0xDF
+#define SSD2828_PUCR1 0xE0
+#define SSD2828_PUCR2 0xE1
+#define SSD2828_PUCR3 0xE2
+#define SSD2828_CBCR1 0xE9
+#define SSD2828_CBCR2 0xEA
+#define SSD2828_CBSR 0xEB
+#define SSD2828_ECR 0xEC
+#define SSD2828_VSDR 0xED
+#define SSD2828_TMR 0xEE
+#define SSD2828_GPIO1 0xEF
+#define SSD2828_GPIO2 0xF0
+#define SSD2828_DLYA01 0xF1
+#define SSD2828_DLYA23 0xF2
+#define SSD2828_DLYB01 0xF3
+#define SSD2828_DLYB23 0xF4
+#define SSD2828_DLYC01 0xF5
+#define SSD2828_DLYC23 0xF6
+#define SSD2828_ACR5 0xF7
+#define SSD2828_RR 0xFF
+
+#define SSD2828_CFGR_HS (1 << 0)
+#define SSD2828_CFGR_CKE (1 << 1)
+#define SSD2828_CFGR_SLP (1 << 2)
+#define SSD2828_CFGR_VEN (1 << 3)
+#define SSD2828_CFGR_HCLK (1 << 4)
+#define SSD2828_CFGR_CSS (1 << 5)
+#define SSD2828_CFGR_DCS (1 << 6)
+#define SSD2828_CFGR_REN (1 << 7)
+#define SSD2828_CFGR_ECD (1 << 8)
+#define SSD2828_CFGR_EOT (1 << 9)
+#define SSD2828_CFGR_LPE (1 << 10)
+#define SSD2828_CFGR_TXD (1 << 11)
+
+#define SSD2828_VIDEO_MODE_NON_BURST_WITH_SYNC_PULSES (0 << 2)
+#define SSD2828_VIDEO_MODE_NON_BURST_WITH_SYNC_EVENTS (1 << 2)
+#define SSD2828_VIDEO_MODE_BURST (2 << 2)
+
+#define SSD2828_VIDEO_PIXEL_FORMAT_16BPP 0
+#define SSD2828_VIDEO_PIXEL_FORMAT_18BPP_PACKED 1
+#define SSD2828_VIDEO_PIXEL_FORMAT_18BPP_LOOSELY_PACKED 2
+#define SSD2828_VIDEO_PIXEL_FORMAT_24BPP 3
+
+#define SSD2828_LP_CLOCK_DIVIDER(n) (((n) - 1) & 0x3F)
+
+/*
+ * SPI transfer, using the "24-bit 3 wire" mode (that's how it is called in
+ * the SSD2828 documentation). The 'dout' input parameter specifies 24-bits
+ * of data to be written to SSD2828. Returns the lowest 16-bits of data,
+ * that is received back.
+ */
+static u32 soft_spi_xfer_24bit_3wire(const struct ssd2828_config *drv, u32 dout)
+{
+ int j, bitlen = 24;
+ u32 tmpdin = 0;
+ /*
+ * According to the "24 Bit 3 Wire SPI Interface Timing Characteristics"
+ * and "TX_CLK Timing Characteristics" tables in the SSD2828 datasheet,
+ * the lowest possible 'tx_clk' clock frequency is 8MHz, and SPI runs
+ * at 1/8 of that after reset. So using 1 microsecond delays is safe in
+ * the main loop. But the delays around chip select pin manipulations
+ * need to be longer (up to 16 'tx_clk' cycles, or 2 microseconds in
+ * the worst case).
+ */
+ const int spi_delay_us = 1;
+ const int spi_cs_delay_us = 2;
+
+ gpio_set_value(drv->csx_pin, 0);
+ udelay(spi_cs_delay_us);
+ for (j = bitlen - 1; j >= 0; j--) {
+ gpio_set_value(drv->sck_pin, 0);
+ gpio_set_value(drv->sdi_pin, (dout & (1 << j)) != 0);
+ udelay(spi_delay_us);
+ if (drv->sdo_pin != -1)
+ tmpdin = (tmpdin << 1) | gpio_get_value(drv->sdo_pin);
+ gpio_set_value(drv->sck_pin, 1);
+ udelay(spi_delay_us);
+ }
+ udelay(spi_cs_delay_us);
+ gpio_set_value(drv->csx_pin, 1);
+ udelay(spi_cs_delay_us);
+ return tmpdin & 0xFFFF;
+}
+
+/*
+ * Read from a SSD2828 hardware register (regnum >= 0xB0)
+ */
+static u32 read_hw_register(const struct ssd2828_config *cfg, u8 regnum)
+{
+ soft_spi_xfer_24bit_3wire(cfg, 0x700000 | regnum);
+ return soft_spi_xfer_24bit_3wire(cfg, 0x730000);
+}
+
+/*
+ * Write to a SSD2828 hardware register (regnum >= 0xB0)
+ */
+static void write_hw_register(const struct ssd2828_config *cfg, u8 regnum,
+ u16 val)
+{
+ soft_spi_xfer_24bit_3wire(cfg, 0x700000 | regnum);
+ soft_spi_xfer_24bit_3wire(cfg, 0x720000 | val);
+}
+
+/*
+ * Send MIPI command to the LCD panel (cmdnum < 0xB0)
+ */
+static void send_mipi_dcs_command(const struct ssd2828_config *cfg, u8 cmdnum)
+{
+ /* Set packet size to 1 (a single command with no parameters) */
+ write_hw_register(cfg, SSD2828_PSCR1, 1);
+ /* Send the command */
+ write_hw_register(cfg, SSD2828_PDR, cmdnum);
+}
+
+/*
+ * Reset SSD2828
+ */
+static void ssd2828_reset(const struct ssd2828_config *cfg)
+{
+ /* RESET needs 10 milliseconds according to the datasheet */
+ gpio_set_value(cfg->reset_pin, 0);
+ mdelay(10);
+ gpio_set_value(cfg->reset_pin, 1);
+ mdelay(10);
+}
+
+static int ssd2828_enable_gpio(const struct ssd2828_config *cfg)
+{
+ if (gpio_request(cfg->csx_pin, "ssd2828_csx")) {
+ printf("SSD2828: request for 'ssd2828_csx' pin failed\n");
+ return 1;
+ }
+ if (gpio_request(cfg->sck_pin, "ssd2828_sck")) {
+ gpio_free(cfg->csx_pin);
+ printf("SSD2828: request for 'ssd2828_sck' pin failed\n");
+ return 1;
+ }
+ if (gpio_request(cfg->sdi_pin, "ssd2828_sdi")) {
+ gpio_free(cfg->csx_pin);
+ gpio_free(cfg->sck_pin);
+ printf("SSD2828: request for 'ssd2828_sdi' pin failed\n");
+ return 1;
+ }
+ if (gpio_request(cfg->reset_pin, "ssd2828_reset")) {
+ gpio_free(cfg->csx_pin);
+ gpio_free(cfg->sck_pin);
+ gpio_free(cfg->sdi_pin);
+ printf("SSD2828: request for 'ssd2828_reset' pin failed\n");
+ return 1;
+ }
+ if (cfg->sdo_pin != -1 && gpio_request(cfg->sdo_pin, "ssd2828_sdo")) {
+ gpio_free(cfg->csx_pin);
+ gpio_free(cfg->sck_pin);
+ gpio_free(cfg->sdi_pin);
+ gpio_free(cfg->reset_pin);
+ printf("SSD2828: request for 'ssd2828_sdo' pin failed\n");
+ return 1;
+ }
+ gpio_direction_output(cfg->reset_pin, 0);
+ gpio_direction_output(cfg->csx_pin, 1);
+ gpio_direction_output(cfg->sck_pin, 1);
+ gpio_direction_output(cfg->sdi_pin, 1);
+ if (cfg->sdo_pin != -1)
+ gpio_direction_input(cfg->sdo_pin);
+
+ return 0;
+}
+
+static int ssd2828_free_gpio(const struct ssd2828_config *cfg)
+{
+ gpio_free(cfg->csx_pin);
+ gpio_free(cfg->sck_pin);
+ gpio_free(cfg->sdi_pin);
+ gpio_free(cfg->reset_pin);
+ if (cfg->sdo_pin != -1)
+ gpio_free(cfg->sdo_pin);
+ return 1;
+}
+
+/*
+ * PLL configuration register settings.
+ *
+ * See the "PLL Configuration Register Description" in the SSD2828 datasheet.
+ */
+static u32 construct_pll_config(u32 desired_pll_freq_kbps,
+ u32 reference_freq_khz)
+{
+ u32 div_factor = 1, mul_factor, fr = 0;
+ u32 output_freq_kbps;
+
+ /* The intermediate clock after division can't be less than 5MHz */
+ while (reference_freq_khz / (div_factor + 1) >= 5000)
+ div_factor++;
+ if (div_factor > 31)
+ div_factor = 31;
+
+ mul_factor = DIV_ROUND_UP(desired_pll_freq_kbps * div_factor,
+ reference_freq_khz);
+
+ output_freq_kbps = reference_freq_khz * mul_factor / div_factor;
+
+ if (output_freq_kbps >= 501000)
+ fr = 3;
+ else if (output_freq_kbps >= 251000)
+ fr = 2;
+ else if (output_freq_kbps >= 126000)
+ fr = 1;
+
+ return (fr << 14) | (div_factor << 8) | mul_factor;
+}
+
+static u32 decode_pll_config(u32 pll_config, u32 reference_freq_khz)
+{
+ u32 mul_factor = pll_config & 0xFF;
+ u32 div_factor = (pll_config >> 8) & 0x1F;
+ if (mul_factor == 0)
+ mul_factor = 1;
+ if (div_factor == 0)
+ div_factor = 1;
+ return reference_freq_khz * mul_factor / div_factor;
+}
+
+static int ssd2828_configure_video_interface(const struct ssd2828_config *cfg,
+ const struct ctfb_res_modes *mode)
+{
+ u32 val;
+
+ /* RGB Interface Control Register 1 */
+ write_hw_register(cfg, SSD2828_VICR1, (mode->vsync_len << 8) |
+ (mode->hsync_len));
+
+ /* RGB Interface Control Register 2 */
+ u32 vbp = mode->vsync_len + mode->upper_margin;
+ u32 hbp = mode->hsync_len + mode->left_margin;
+ write_hw_register(cfg, SSD2828_VICR2, (vbp << 8) | hbp);
+
+ /* RGB Interface Control Register 3 */
+ write_hw_register(cfg, SSD2828_VICR3, (mode->lower_margin << 8) |
+ (mode->right_margin));
+
+ /* RGB Interface Control Register 4 */
+ write_hw_register(cfg, SSD2828_VICR4, mode->xres);
+
+ /* RGB Interface Control Register 5 */
+ write_hw_register(cfg, SSD2828_VICR5, mode->yres);
+
+ /* RGB Interface Control Register 6 */
+ val = SSD2828_VIDEO_MODE_BURST;
+ switch (cfg->ssd2828_color_depth) {
+ case 16:
+ val |= SSD2828_VIDEO_PIXEL_FORMAT_16BPP;
+ break;
+ case 18:
+ val |= cfg->mipi_dsi_loosely_packed_pixel_format ?
+ SSD2828_VIDEO_PIXEL_FORMAT_18BPP_LOOSELY_PACKED :
+ SSD2828_VIDEO_PIXEL_FORMAT_18BPP_PACKED;
+ break;
+ case 24:
+ val |= SSD2828_VIDEO_PIXEL_FORMAT_24BPP;
+ break;
+ default:
+ printf("SSD2828: unsupported color depth\n");
+ return 1;
+ }
+ write_hw_register(cfg, SSD2828_VICR6, val);
+
+ /* Lane Configuration Register */
+ write_hw_register(cfg, SSD2828_LCFR,
+ cfg->mipi_dsi_number_of_data_lanes - 1);
+
+ return 0;
+}
+
+int ssd2828_init(const struct ssd2828_config *cfg,
+ const struct ctfb_res_modes *mode)
+{
+ u32 lp_div, pll_freq_kbps, reference_freq_khz, pll_config;
+ /* The LP clock speed is limited by 10MHz */
+ const u32 mipi_dsi_low_power_clk_khz = 10000;
+ /*
+ * This is just the reset default value of CFGR register (0x301).
+ * Because we are not always able to read back from SPI, have
+ * it initialized here.
+ */
+ u32 cfgr_reg = SSD2828_CFGR_EOT | /* EOT Packet Enable */
+ SSD2828_CFGR_ECD | /* Disable ECC and CRC */
+ SSD2828_CFGR_HS; /* Data lanes are in HS mode */
+
+ /* Initialize the pins */
+ if (ssd2828_enable_gpio(cfg) != 0)
+ return 1;
+
+ /* Reset the chip */
+ ssd2828_reset(cfg);
+
+ /*
+ * If there is a pin to read data back from SPI, then we are lucky. Try
+ * to check if SPI is configured correctly and SSD2828 is actually able
+ * to talk back.
+ */
+ if (cfg->sdo_pin != -1) {
+ if (read_hw_register(cfg, SSD2828_DIR) != 0x2828 ||
+ read_hw_register(cfg, SSD2828_CFGR) != cfgr_reg) {
+ printf("SSD2828: SPI communication failed.\n");
+ ssd2828_free_gpio(cfg);
+ return 1;
+ }
+ }
+
+ /*
+ * Pick the reference clock for PLL. If we know the exact 'tx_clk'
+ * clock speed, then everything is good. If not, then we can fallback
+ * to 'pclk' (pixel clock from the parallel LCD interface). In the
+ * case of using this fallback, it is necessary to have parallel LCD
+ * already initialized and running at this point.
+ */
+ reference_freq_khz = cfg->ssd2828_tx_clk_khz;
+ if (reference_freq_khz == 0) {
+ reference_freq_khz = mode->pixclock_khz;
+ /* Use 'pclk' as the reference clock for PLL */
+ cfgr_reg |= SSD2828_CFGR_CSS;
+ }
+
+ /*
+ * Setup the parallel LCD timings in the appropriate registers.
+ */
+ if (ssd2828_configure_video_interface(cfg, mode) != 0) {
+ ssd2828_free_gpio(cfg);
+ return 1;
+ }
+
+ /* Configuration Register */
+ cfgr_reg &= ~SSD2828_CFGR_HS; /* Data lanes are in LP mode */
+ cfgr_reg |= SSD2828_CFGR_CKE; /* Clock lane is in HS mode */
+ cfgr_reg |= SSD2828_CFGR_DCS; /* Only use DCS packets */
+ write_hw_register(cfg, SSD2828_CFGR, cfgr_reg);
+
+ /* PLL Configuration Register */
+ pll_config = construct_pll_config(
+ cfg->mipi_dsi_bitrate_per_data_lane_mbps * 1000,
+ reference_freq_khz);
+ write_hw_register(cfg, SSD2828_PLCR, pll_config);
+
+ pll_freq_kbps = decode_pll_config(pll_config, reference_freq_khz);
+ lp_div = DIV_ROUND_UP(pll_freq_kbps, mipi_dsi_low_power_clk_khz * 8);
+
+ /* VC Control Register */
+ write_hw_register(cfg, SSD2828_VCR, 0);
+
+ /* Clock Control Register */
+ write_hw_register(cfg, SSD2828_CCR, SSD2828_LP_CLOCK_DIVIDER(lp_div));
+
+ /* PLL Control Register */
+ write_hw_register(cfg, SSD2828_PCR, 1); /* Enable PLL */
+
+ /* Wait for PLL lock */
+ udelay(500);
+
+ send_mipi_dcs_command(cfg, MIPI_DCS_EXIT_SLEEP_MODE);
+ mdelay(cfg->mipi_dsi_delay_after_exit_sleep_mode_ms);
+
+ send_mipi_dcs_command(cfg, MIPI_DCS_SET_DISPLAY_ON);
+ mdelay(cfg->mipi_dsi_delay_after_set_display_on_ms);
+
+ cfgr_reg |= SSD2828_CFGR_HS; /* Enable HS mode for data lanes */
+ cfgr_reg |= SSD2828_CFGR_VEN; /* Enable video pipeline */
+ write_hw_register(cfg, SSD2828_CFGR, cfgr_reg);
+
+ return 0;
+}
diff --git a/drivers/video/ssd2828.h b/drivers/video/ssd2828.h
new file mode 100644
index 0000000..1af6fa4
--- /dev/null
+++ b/drivers/video/ssd2828.h
@@ -0,0 +1,128 @@
+/*
+ * (C) 2015 Siarhei Siamashka <siarhei.siamashka@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+/*
+ * Support for the SSD2828 bridge chip, which can take pixel data coming
+ * from a parallel LCD interface and translate it on the flight into MIPI DSI
+ * interface for driving a MIPI compatible TFT display.
+ *
+ * Implemented as a utility function. To be used from display drivers, which are
+ * responsible for driving parallel LCD hardware in front of the video pipeline.
+ */
+
+#ifndef _SSD2828_H
+#define _SSD2828_H
+
+struct ctfb_res_modes;
+
+struct ssd2828_config {
+ /*********************************************************************/
+ /* SSD2828 configuration */
+ /*********************************************************************/
+
+ /*
+ * The pins, which are used for SPI communication. This is only used
+ * for configuring SSD2828, so the performance is irrelevant (only
+ * around a hundred of bytes is moved). Also these can be any arbitrary
+ * GPIO pins (not necessarily the pins having hardware SPI function).
+ * Moreover, the 'sdo' pin may be even not wired up in some devices.
+ *
+ * These configuration variables need to be set as pin numbers for
+ * the standard u-boot GPIO interface (gpio_get_value/gpio_set_value
+ * functions). Note that -1 value can be used for the pins, which are
+ * not really wired up.
+ */
+ int csx_pin;
+ int sck_pin;
+ int sdi_pin;
+ int sdo_pin;
+ /* SSD2828 reset pin (shared with LCD panel reset) */
+ int reset_pin;
+
+ /*
+ * The SSD2828 has its own dedicated clock source 'tx_clk' (connected
+ * to TX_CLK_XIO/TX_CLK_XIN pins), which is necessary at least for
+ * clocking SPI after reset. The exact clock speed is not strictly,
+ * defined, but the datasheet says that it must be somewhere in the
+ * 8MHz - 30MHz range (see "TX_CLK Timing" section). It can be also
+ * used as a reference clock for PLL. If the exact clock frequency
+ * is known, then it can be specified here. If it is unknown, or the
+ * information is not trustworthy, then it can be set to 0.
+ *
+ * If unsure, set to 0.
+ */
+ int ssd2828_tx_clk_khz;
+
+ /*
+ * This is not a property of the used LCD panel, but more like a
+ * property of the SSD2828 wiring. See the "SSD2828QN4 RGB data
+ * arrangement" table in the datasheet. The SSD2828 pins are arranged
+ * in such a way that 18bpp and 24bpp configurations are completely
+ * incompatible with each other.
+ *
+ * Depending on the color depth, this must be set to 16, 18 or 24.
+ */
+ int ssd2828_color_depth;
+
+ /*********************************************************************/
+ /* LCD panel configuration */
+ /*********************************************************************/
+
+ /*
+ * The number of lanes in the MIPI DSI interface. May vary from 1 to 4.
+ *
+ * This information can be found in the LCD panel datasheet.
+ */
+ int mipi_dsi_number_of_data_lanes;
+
+ /*
+ * Data transfer bit rate per lane. Please note that it is expected
+ * to be higher than the pixel clock rate of the used video mode when
+ * multiplied by the number of lanes. This is perfectly normal because
+ * MIPI DSI handles data transfers in periodic bursts, and uses the
+ * idle time between bursts for sending configuration information and
+ * commands. Or just for saving power.
+ *
+ * The necessary Mbps/lane information can be found in the LCD panel
+ * datasheet. Note that the transfer rate can't be always set precisely
+ * and it may be rounded *up* (introducing no more than 10Mbps error).
+ */
+ int mipi_dsi_bitrate_per_data_lane_mbps;
+
+ /*
+ * Setting this to 1 enforces packing of 18bpp pixel data in 24bpp
+ * envelope when sending it over the MIPI DSI link.
+ *
+ * If unsure, set to 0.
+ */
+ int mipi_dsi_loosely_packed_pixel_format;
+
+ /*
+ * According to the "Example for system sleep in and out" section in
+ * the SSD2828 datasheet, some LCD panel specific delays are necessary
+ * after MIPI DCS commands EXIT_SLEEP_MODE and SET_DISPLAY_ON.
+ *
+ * For example, Allwinner uses 100 milliseconds delay after
+ * EXIT_SLEEP_MODE and 200 milliseconds delay after SET_DISPLAY_ON.
+ */
+ int mipi_dsi_delay_after_exit_sleep_mode_ms;
+ int mipi_dsi_delay_after_set_display_on_ms;
+};
+
+/*
+ * Initialize the SSD2828 chip. It needs the 'ssd2828_config' structure
+ * and also the video mode timings.
+ *
+ * The right place to insert this function call is after the parallel LCD
+ * interface is initialized and before turning on the backlight. This is
+ * advised in the "Example for system sleep in and out" section of the
+ * SSD2828 datasheet. And also SS2828 may use 'pclk' as the clock source
+ * for PLL, which means that the input signal must be already there.
+ */
+int ssd2828_init(const struct ssd2828_config *cfg,
+ const struct ctfb_res_modes *mode);
+
+#endif
diff --git a/drivers/video/sunxi_display.c b/drivers/video/sunxi_display.c
index d241397..af728b5 100644
--- a/drivers/video/sunxi_display.c
+++ b/drivers/video/sunxi_display.c
@@ -11,26 +11,68 @@
#include <asm/arch/clock.h>
#include <asm/arch/display.h>
+#include <asm/arch/gpio.h>
#include <asm/global_data.h>
+#include <asm/gpio.h>
#include <asm/io.h>
+#include <errno.h>
#include <fdtdec.h>
#include <fdt_support.h>
-#include <linux/fb.h>
#include <video_fb.h>
+#include "videomodes.h"
+#include "hitachi_tx18d42vm_lcd.h"
+#include "ssd2828.h"
+
+#ifdef CONFIG_VIDEO_LCD_BL_PWM_ACTIVE_LOW
+#define PWM_ON 0
+#define PWM_OFF 1
+#else
+#define PWM_ON 1
+#define PWM_OFF 0
+#endif
DECLARE_GLOBAL_DATA_PTR;
+enum sunxi_monitor {
+ sunxi_monitor_none,
+ sunxi_monitor_dvi,
+ sunxi_monitor_hdmi,
+ sunxi_monitor_lcd,
+ sunxi_monitor_vga,
+};
+#define SUNXI_MONITOR_LAST sunxi_monitor_vga
+
struct sunxi_display {
GraphicDevice graphic_device;
- bool enabled;
+ enum sunxi_monitor monitor;
+ unsigned int depth;
} sunxi_display;
-static int sunxi_hdmi_hpd_detect(void)
+#ifdef CONFIG_VIDEO_HDMI
+
+/*
+ * Wait up to 200ms for value to be set in given part of reg.
+ */
+static int await_completion(u32 *reg, u32 mask, u32 val)
+{
+ unsigned long tmo = timer_get_us() + 200000;
+
+ while ((readl(reg) & mask) != val) {
+ if (timer_get_us() > tmo) {
+ printf("DDC: timeout reading EDID\n");
+ return -ETIME;
+ }
+ }
+ return 0;
+}
+
+static int sunxi_hdmi_hpd_detect(int hpd_delay)
{
struct sunxi_ccm_reg * const ccm =
(struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
struct sunxi_hdmi_reg * const hdmi =
(struct sunxi_hdmi_reg *)SUNXI_HDMI_BASE;
+ unsigned long tmo = timer_get_us() + hpd_delay * 1000;
/* Set pll3 to 300MHz */
clock_set_pll3(300000000);
@@ -51,12 +93,21 @@ static int sunxi_hdmi_hpd_detect(void)
writel(SUNXI_HDMI_CTRL_ENABLE, &hdmi->ctrl);
writel(SUNXI_HDMI_PAD_CTRL0_HDP, &hdmi->pad_ctrl0);
- udelay(1000);
+ while (timer_get_us() < tmo) {
+ if (readl(&hdmi->hpd) & SUNXI_HDMI_HPD_DETECT)
+ return 1;
+ }
+
+ return 0;
+}
- if (readl(&hdmi->hpd) & SUNXI_HDMI_HPD_DETECT)
- return 1;
+static void sunxi_hdmi_shutdown(void)
+{
+ struct sunxi_ccm_reg * const ccm =
+ (struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
+ struct sunxi_hdmi_reg * const hdmi =
+ (struct sunxi_hdmi_reg *)SUNXI_HDMI_BASE;
- /* No need to keep these running */
clrbits_le32(&hdmi->ctrl, SUNXI_HDMI_CTRL_ENABLE);
clrbits_le32(&ccm->hdmi_clk_cfg, CCM_HDMI_CTRL_GATE);
clrbits_le32(&ccm->ahb_gate1, 1 << AHB_GATE_OFFSET_HDMI);
@@ -64,10 +115,279 @@ static int sunxi_hdmi_hpd_detect(void)
clrbits_le32(&ccm->ahb_reset1_cfg, 1 << AHB_RESET_OFFSET_HDMI);
#endif
clock_set_pll3(0);
+}
+
+static int sunxi_hdmi_ddc_do_command(u32 cmnd, int offset, int n)
+{
+ struct sunxi_hdmi_reg * const hdmi =
+ (struct sunxi_hdmi_reg *)SUNXI_HDMI_BASE;
+
+ setbits_le32(&hdmi->ddc_fifo_ctrl, SUNXI_HDMI_DDC_FIFO_CTRL_CLEAR);
+ writel(SUNXI_HMDI_DDC_ADDR_EDDC_SEGMENT(offset >> 8) |
+ SUNXI_HMDI_DDC_ADDR_EDDC_ADDR |
+ SUNXI_HMDI_DDC_ADDR_OFFSET(offset) |
+ SUNXI_HMDI_DDC_ADDR_SLAVE_ADDR, &hdmi->ddc_addr);
+#ifndef CONFIG_MACH_SUN6I
+ writel(n, &hdmi->ddc_byte_count);
+ writel(cmnd, &hdmi->ddc_cmnd);
+#else
+ writel(n << 16 | cmnd, &hdmi->ddc_cmnd);
+#endif
+ setbits_le32(&hdmi->ddc_ctrl, SUNXI_HMDI_DDC_CTRL_START);
+
+ return await_completion(&hdmi->ddc_ctrl, SUNXI_HMDI_DDC_CTRL_START, 0);
+}
+
+static int sunxi_hdmi_ddc_read(int offset, u8 *buf, int count)
+{
+ struct sunxi_hdmi_reg * const hdmi =
+ (struct sunxi_hdmi_reg *)SUNXI_HDMI_BASE;
+ int i, n;
+
+ while (count > 0) {
+ if (count > 16)
+ n = 16;
+ else
+ n = count;
+
+ if (sunxi_hdmi_ddc_do_command(
+ SUNXI_HDMI_DDC_CMND_EXPLICIT_EDDC_READ,
+ offset, n))
+ return -ETIME;
+
+ for (i = 0; i < n; i++)
+ *buf++ = readb(&hdmi->ddc_fifo_data);
+
+ offset += n;
+ count -= n;
+ }
return 0;
}
+static int sunxi_hdmi_edid_get_block(int block, u8 *buf)
+{
+ int r, retries = 2;
+
+ do {
+ r = sunxi_hdmi_ddc_read(block * 128, buf, 128);
+ if (r)
+ continue;
+ r = edid_check_checksum(buf);
+ if (r) {
+ printf("EDID block %d: checksum error%s\n",
+ block, retries ? ", retrying" : "");
+ }
+ } while (r && retries--);
+
+ return r;
+}
+
+static int sunxi_hdmi_edid_get_mode(struct ctfb_res_modes *mode)
+{
+ struct edid1_info edid1;
+ struct edid_cea861_info cea681[4];
+ struct edid_detailed_timing *t =
+ (struct edid_detailed_timing *)edid1.monitor_details.timing;
+ struct sunxi_hdmi_reg * const hdmi =
+ (struct sunxi_hdmi_reg *)SUNXI_HDMI_BASE;
+ struct sunxi_ccm_reg * const ccm =
+ (struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
+ int i, r, ext_blocks = 0;
+
+ /* SUNXI_HDMI_CTRL_ENABLE & PAD_CTRL0 are already set by hpd_detect */
+ writel(SUNXI_HDMI_PAD_CTRL1 | SUNXI_HDMI_PAD_CTRL1_HALVE,
+ &hdmi->pad_ctrl1);
+ writel(SUNXI_HDMI_PLL_CTRL | SUNXI_HDMI_PLL_CTRL_DIV(15),
+ &hdmi->pll_ctrl);
+ writel(SUNXI_HDMI_PLL_DBG0_PLL3, &hdmi->pll_dbg0);
+
+ /* Reset i2c controller */
+ setbits_le32(&ccm->hdmi_clk_cfg, CCM_HDMI_CTRL_DDC_GATE);
+ writel(SUNXI_HMDI_DDC_CTRL_ENABLE |
+ SUNXI_HMDI_DDC_CTRL_SDA_ENABLE |
+ SUNXI_HMDI_DDC_CTRL_SCL_ENABLE |
+ SUNXI_HMDI_DDC_CTRL_RESET, &hdmi->ddc_ctrl);
+ if (await_completion(&hdmi->ddc_ctrl, SUNXI_HMDI_DDC_CTRL_RESET, 0))
+ return -EIO;
+
+ writel(SUNXI_HDMI_DDC_CLOCK, &hdmi->ddc_clock);
+#ifndef CONFIG_MACH_SUN6I
+ writel(SUNXI_HMDI_DDC_LINE_CTRL_SDA_ENABLE |
+ SUNXI_HMDI_DDC_LINE_CTRL_SCL_ENABLE, &hdmi->ddc_line_ctrl);
+#endif
+
+ r = sunxi_hdmi_edid_get_block(0, (u8 *)&edid1);
+ if (r == 0) {
+ r = edid_check_info(&edid1);
+ if (r) {
+ printf("EDID: invalid EDID data\n");
+ r = -EINVAL;
+ }
+ }
+ if (r == 0) {
+ ext_blocks = edid1.extension_flag;
+ if (ext_blocks > 4)
+ ext_blocks = 4;
+ for (i = 0; i < ext_blocks; i++) {
+ if (sunxi_hdmi_edid_get_block(1 + i,
+ (u8 *)&cea681[i]) != 0) {
+ ext_blocks = i;
+ break;
+ }
+ }
+ }
+
+ /* Disable DDC engine, no longer needed */
+ clrbits_le32(&hdmi->ddc_ctrl, SUNXI_HMDI_DDC_CTRL_ENABLE);
+ clrbits_le32(&ccm->hdmi_clk_cfg, CCM_HDMI_CTRL_DDC_GATE);
+
+ if (r)
+ return r;
+
+ /* We want version 1.3 or 1.2 with detailed timing info */
+ if (edid1.version != 1 || (edid1.revision < 3 &&
+ !EDID1_INFO_FEATURE_PREFERRED_TIMING_MODE(edid1))) {
+ printf("EDID: unsupported version %d.%d\n",
+ edid1.version, edid1.revision);
+ return -EINVAL;
+ }
+
+ /* Take the first usable detailed timing */
+ for (i = 0; i < 4; i++, t++) {
+ r = video_edid_dtd_to_ctfb_res_modes(t, mode);
+ if (r == 0)
+ break;
+ }
+ if (i == 4) {
+ printf("EDID: no usable detailed timing found\n");
+ return -ENOENT;
+ }
+
+ /* Check for basic audio support, if found enable hdmi output */
+ sunxi_display.monitor = sunxi_monitor_dvi;
+ for (i = 0; i < ext_blocks; i++) {
+ if (cea681[i].extension_tag != EDID_CEA861_EXTENSION_TAG ||
+ cea681[i].revision < 2)
+ continue;
+
+ if (EDID_CEA861_SUPPORTS_BASIC_AUDIO(cea681[i]))
+ sunxi_display.monitor = sunxi_monitor_hdmi;
+ }
+
+ return 0;
+}
+
+#endif /* CONFIG_VIDEO_HDMI */
+
+#ifdef CONFIG_MACH_SUN4I
+/*
+ * Testing has shown that on sun4i the display backend engine does not have
+ * deep enough fifo-s causing flickering / tearing in full-hd mode due to
+ * fifo underruns. So on sun4i we use the display frontend engine to do the
+ * dma from memory, as the frontend does have deep enough fifo-s.
+ */
+
+static const u32 sun4i_vert_coef[32] = {
+ 0x00004000, 0x000140ff, 0x00033ffe, 0x00043ffd,
+ 0x00063efc, 0xff083dfc, 0x000a3bfb, 0xff0d39fb,
+ 0xff0f37fb, 0xff1136fa, 0xfe1433fb, 0xfe1631fb,
+ 0xfd192ffb, 0xfd1c2cfb, 0xfd1f29fb, 0xfc2127fc,
+ 0xfc2424fc, 0xfc2721fc, 0xfb291ffd, 0xfb2c1cfd,
+ 0xfb2f19fd, 0xfb3116fe, 0xfb3314fe, 0xfa3611ff,
+ 0xfb370fff, 0xfb390dff, 0xfb3b0a00, 0xfc3d08ff,
+ 0xfc3e0600, 0xfd3f0400, 0xfe3f0300, 0xff400100,
+};
+
+static const u32 sun4i_horz_coef[64] = {
+ 0x40000000, 0x00000000, 0x40fe0000, 0x0000ff03,
+ 0x3ffd0000, 0x0000ff05, 0x3ffc0000, 0x0000ff06,
+ 0x3efb0000, 0x0000ff08, 0x3dfb0000, 0x0000ff09,
+ 0x3bfa0000, 0x0000fe0d, 0x39fa0000, 0x0000fe0f,
+ 0x38fa0000, 0x0000fe10, 0x36fa0000, 0x0000fe12,
+ 0x33fa0000, 0x0000fd16, 0x31fa0000, 0x0000fd18,
+ 0x2ffa0000, 0x0000fd1a, 0x2cfa0000, 0x0000fc1e,
+ 0x29fa0000, 0x0000fc21, 0x27fb0000, 0x0000fb23,
+ 0x24fb0000, 0x0000fb26, 0x21fb0000, 0x0000fb29,
+ 0x1ffc0000, 0x0000fa2b, 0x1cfc0000, 0x0000fa2e,
+ 0x19fd0000, 0x0000fa30, 0x16fd0000, 0x0000fa33,
+ 0x14fd0000, 0x0000fa35, 0x11fe0000, 0x0000fa37,
+ 0x0ffe0000, 0x0000fa39, 0x0dfe0000, 0x0000fa3b,
+ 0x0afe0000, 0x0000fa3e, 0x08ff0000, 0x0000fb3e,
+ 0x06ff0000, 0x0000fb40, 0x05ff0000, 0x0000fc40,
+ 0x03ff0000, 0x0000fd41, 0x01ff0000, 0x0000fe42,
+};
+
+static void sunxi_frontend_init(void)
+{
+ struct sunxi_ccm_reg * const ccm =
+ (struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
+ struct sunxi_de_fe_reg * const de_fe =
+ (struct sunxi_de_fe_reg *)SUNXI_DE_FE0_BASE;
+ int i;
+
+ /* Clocks on */
+ setbits_le32(&ccm->ahb_gate1, 1 << AHB_GATE_OFFSET_DE_FE0);
+ setbits_le32(&ccm->dram_clk_gate, 1 << CCM_DRAM_GATE_OFFSET_DE_FE0);
+ clock_set_de_mod_clock(&ccm->fe0_clk_cfg, 300000000);
+
+ setbits_le32(&de_fe->enable, SUNXI_DE_FE_ENABLE_EN);
+
+ for (i = 0; i < 32; i++) {
+ writel(sun4i_horz_coef[2 * i], &de_fe->ch0_horzcoef0[i]);
+ writel(sun4i_horz_coef[2 * i + 1], &de_fe->ch0_horzcoef1[i]);
+ writel(sun4i_vert_coef[i], &de_fe->ch0_vertcoef[i]);
+ writel(sun4i_horz_coef[2 * i], &de_fe->ch1_horzcoef0[i]);
+ writel(sun4i_horz_coef[2 * i + 1], &de_fe->ch1_horzcoef1[i]);
+ writel(sun4i_vert_coef[i], &de_fe->ch1_vertcoef[i]);
+ }
+
+ setbits_le32(&de_fe->frame_ctrl, SUNXI_DE_FE_FRAME_CTRL_COEF_RDY);
+}
+
+static void sunxi_frontend_mode_set(const struct ctfb_res_modes *mode,
+ unsigned int address)
+{
+ struct sunxi_de_fe_reg * const de_fe =
+ (struct sunxi_de_fe_reg *)SUNXI_DE_FE0_BASE;
+
+ setbits_le32(&de_fe->bypass, SUNXI_DE_FE_BYPASS_CSC_BYPASS);
+ writel(CONFIG_SYS_SDRAM_BASE + address, &de_fe->ch0_addr);
+ writel(mode->xres * 4, &de_fe->ch0_stride);
+ writel(SUNXI_DE_FE_INPUT_FMT_ARGB8888, &de_fe->input_fmt);
+ writel(SUNXI_DE_FE_OUTPUT_FMT_ARGB8888, &de_fe->output_fmt);
+
+ writel(SUNXI_DE_FE_HEIGHT(mode->yres) | SUNXI_DE_FE_WIDTH(mode->xres),
+ &de_fe->ch0_insize);
+ writel(SUNXI_DE_FE_HEIGHT(mode->yres) | SUNXI_DE_FE_WIDTH(mode->xres),
+ &de_fe->ch0_outsize);
+ writel(SUNXI_DE_FE_FACTOR_INT(1), &de_fe->ch0_horzfact);
+ writel(SUNXI_DE_FE_FACTOR_INT(1), &de_fe->ch0_vertfact);
+
+ writel(SUNXI_DE_FE_HEIGHT(mode->yres) | SUNXI_DE_FE_WIDTH(mode->xres),
+ &de_fe->ch1_insize);
+ writel(SUNXI_DE_FE_HEIGHT(mode->yres) | SUNXI_DE_FE_WIDTH(mode->xres),
+ &de_fe->ch1_outsize);
+ writel(SUNXI_DE_FE_FACTOR_INT(1), &de_fe->ch1_horzfact);
+ writel(SUNXI_DE_FE_FACTOR_INT(1), &de_fe->ch1_vertfact);
+
+ setbits_le32(&de_fe->frame_ctrl, SUNXI_DE_FE_FRAME_CTRL_REG_RDY);
+}
+
+static void sunxi_frontend_enable(void)
+{
+ struct sunxi_de_fe_reg * const de_fe =
+ (struct sunxi_de_fe_reg *)SUNXI_DE_FE0_BASE;
+
+ setbits_le32(&de_fe->frame_ctrl, SUNXI_DE_FE_FRAME_CTRL_FRM_START);
+}
+#else
+static void sunxi_frontend_init(void) {}
+static void sunxi_frontend_mode_set(const struct ctfb_res_modes *mode,
+ unsigned int address) {}
+static void sunxi_frontend_enable(void) {}
+#endif
+
/*
* This is the entity that mixes and matches the different layers and inputs.
* Allwinner calls it the back-end, but i like composer better.
@@ -80,14 +400,18 @@ static void sunxi_composer_init(void)
(struct sunxi_de_be_reg *)SUNXI_DE_BE0_BASE;
int i;
-#ifdef CONFIG_MACH_SUN6I
+ sunxi_frontend_init();
+
+#if defined CONFIG_MACH_SUN6I || defined CONFIG_MACH_SUN8I
/* Reset off */
setbits_le32(&ccm->ahb_reset1_cfg, 1 << AHB_RESET_OFFSET_DE_BE0);
#endif
/* Clocks on */
setbits_le32(&ccm->ahb_gate1, 1 << AHB_GATE_OFFSET_DE_BE0);
+#ifndef CONFIG_MACH_SUN4I /* On sun4i the frontend does the dma */
setbits_le32(&ccm->dram_clk_gate, 1 << CCM_DRAM_GATE_OFFSET_DE_BE0);
+#endif
clock_set_de_mod_clock(&ccm->be0_clk_cfg, 300000000);
/* Engine bug, clear registers after reset */
@@ -97,41 +421,72 @@ static void sunxi_composer_init(void)
setbits_le32(&de_be->mode, SUNXI_DE_BE_MODE_ENABLE);
}
-static void sunxi_composer_mode_set(struct fb_videomode *mode,
+static void sunxi_composer_mode_set(const struct ctfb_res_modes *mode,
unsigned int address)
{
struct sunxi_de_be_reg * const de_be =
(struct sunxi_de_be_reg *)SUNXI_DE_BE0_BASE;
+ sunxi_frontend_mode_set(mode, address);
+
writel(SUNXI_DE_BE_HEIGHT(mode->yres) | SUNXI_DE_BE_WIDTH(mode->xres),
&de_be->disp_size);
writel(SUNXI_DE_BE_HEIGHT(mode->yres) | SUNXI_DE_BE_WIDTH(mode->xres),
&de_be->layer0_size);
+#ifndef CONFIG_MACH_SUN4I /* On sun4i the frontend does the dma */
writel(SUNXI_DE_BE_LAYER_STRIDE(mode->xres), &de_be->layer0_stride);
writel(address << 3, &de_be->layer0_addr_low32b);
writel(address >> 29, &de_be->layer0_addr_high4b);
+#else
+ writel(SUNXI_DE_BE_LAYER_ATTR0_SRC_FE0, &de_be->layer0_attr0_ctrl);
+#endif
writel(SUNXI_DE_BE_LAYER_ATTR1_FMT_XRGB8888, &de_be->layer0_attr1_ctrl);
setbits_le32(&de_be->mode, SUNXI_DE_BE_MODE_LAYER0_ENABLE);
}
+static void sunxi_composer_enable(void)
+{
+ struct sunxi_de_be_reg * const de_be =
+ (struct sunxi_de_be_reg *)SUNXI_DE_BE0_BASE;
+
+ sunxi_frontend_enable();
+
+ setbits_le32(&de_be->reg_ctrl, SUNXI_DE_BE_REG_CTRL_LOAD_REGS);
+ setbits_le32(&de_be->mode, SUNXI_DE_BE_MODE_START);
+}
+
/*
* LCDC, what allwinner calls a CRTC, so timing controller and serializer.
*/
-static void sunxi_lcdc_pll_set(int dotclock, int *clk_div, int *clk_double)
+static void sunxi_lcdc_pll_set(int tcon, int dotclock,
+ int *clk_div, int *clk_double)
{
struct sunxi_ccm_reg * const ccm =
(struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
- int value, n, m, diff;
+ int value, n, m, min_m, max_m, diff;
int best_n = 0, best_m = 0, best_diff = 0x0FFFFFFF;
int best_double = 0;
+ if (tcon == 0) {
+#ifdef CONFIG_VIDEO_LCD_IF_PARALLEL
+ min_m = 6;
+ max_m = 127;
+#endif
+#ifdef CONFIG_VIDEO_LCD_IF_LVDS
+ min_m = max_m = 7;
+#endif
+ } else {
+ min_m = 1;
+ max_m = 15;
+ }
+
/*
* Find the lowest divider resulting in a matching clock, if there
* is no match, pick the closest lower clock, as monitors tend to
* not sync to higher frequencies.
*/
- for (m = 15; m > 0; m--) {
+ for (m = min_m; m <= max_m; m++) {
n = (m * dotclock) / 3000;
if ((n >= 9) && (n <= 127)) {
@@ -168,9 +523,17 @@ static void sunxi_lcdc_pll_set(int dotclock, int *clk_div, int *clk_double)
clock_set_pll3(best_n * 3000000);
- writel(CCM_LCD_CH1_CTRL_GATE |
- (best_double ? CCM_LCD_CH1_CTRL_PLL3_2X : CCM_LCD_CH1_CTRL_PLL3) |
- CCM_LCD_CH1_CTRL_M(best_m), &ccm->lcd0_ch1_clk_cfg);
+ if (tcon == 0) {
+ writel(CCM_LCD_CH0_CTRL_GATE | CCM_LCD_CH0_CTRL_RST |
+ (best_double ? CCM_LCD_CH0_CTRL_PLL3_2X :
+ CCM_LCD_CH0_CTRL_PLL3),
+ &ccm->lcd0_ch0_clk_cfg);
+ } else {
+ writel(CCM_LCD_CH1_CTRL_GATE |
+ (best_double ? CCM_LCD_CH1_CTRL_PLL3_2X :
+ CCM_LCD_CH1_CTRL_PLL3) |
+ CCM_LCD_CH1_CTRL_M(best_m), &ccm->lcd0_ch1_clk_cfg);
+ }
*clk_div = best_m;
*clk_double = best_double;
@@ -184,7 +547,7 @@ static void sunxi_lcdc_init(void)
(struct sunxi_lcdc_reg *)SUNXI_LCD0_BASE;
/* Reset off */
-#ifdef CONFIG_MACH_SUN6I
+#if defined CONFIG_MACH_SUN6I || defined CONFIG_MACH_SUN8I
setbits_le32(&ccm->ahb_reset1_cfg, 1 << AHB_RESET_OFFSET_LCD0);
#else
setbits_le32(&ccm->lcd0_ch0_clk_cfg, CCM_LCD_CH0_CTRL_RST);
@@ -192,6 +555,9 @@ static void sunxi_lcdc_init(void)
/* Clock on */
setbits_le32(&ccm->ahb_gate1, 1 << AHB_GATE_OFFSET_LCD0);
+#ifdef CONFIG_VIDEO_LCD_IF_LVDS
+ setbits_le32(&ccm->lvds_clk_cfg, CCM_LVDS_CTRL_RST);
+#endif
/* Init lcdc */
writel(0, &lcdc->ctrl); /* Disable tcon */
@@ -205,20 +571,175 @@ static void sunxi_lcdc_init(void)
writel(0xffffffff, &lcdc->tcon1_io_tristate);
}
-static void sunxi_lcdc_mode_set(struct fb_videomode *mode,
- int *clk_div, int *clk_double)
+static void sunxi_lcdc_enable(void)
{
struct sunxi_lcdc_reg * const lcdc =
(struct sunxi_lcdc_reg *)SUNXI_LCD0_BASE;
- int bp, total;
+
+ setbits_le32(&lcdc->ctrl, SUNXI_LCDC_CTRL_TCON_ENABLE);
+#ifdef CONFIG_VIDEO_LCD_IF_LVDS
+ setbits_le32(&lcdc->tcon0_lvds_intf, SUNXI_LCDC_TCON0_LVDS_INTF_ENABLE);
+ setbits_le32(&lcdc->lvds_ana0, SUNXI_LCDC_LVDS_ANA0);
+ setbits_le32(&lcdc->lvds_ana0, SUNXI_LCDC_LVDS_ANA0_UPDATE);
+ udelay(2); /* delay at least 1200 ns */
+ setbits_le32(&lcdc->lvds_ana1, SUNXI_LCDC_LVDS_ANA1_INIT1);
+ udelay(1); /* delay at least 120 ns */
+ setbits_le32(&lcdc->lvds_ana1, SUNXI_LCDC_LVDS_ANA1_INIT2);
+ setbits_le32(&lcdc->lvds_ana0, SUNXI_LCDC_LVDS_ANA0_UPDATE);
+#endif
+}
+
+static void sunxi_lcdc_panel_enable(void)
+{
+ int pin;
+
+ /*
+ * Start with backlight disabled to avoid the screen flashing to
+ * white while the lcd inits.
+ */
+ pin = sunxi_name_to_gpio(CONFIG_VIDEO_LCD_BL_EN);
+ if (pin != -1) {
+ gpio_request(pin, "lcd_backlight_enable");
+ gpio_direction_output(pin, 0);
+ }
+
+ pin = sunxi_name_to_gpio(CONFIG_VIDEO_LCD_BL_PWM);
+ if (pin != -1) {
+ gpio_request(pin, "lcd_backlight_pwm");
+ gpio_direction_output(pin, PWM_OFF);
+ }
+
+ /* Give the backlight some time to turn off and power up the panel. */
+ mdelay(40);
+ pin = sunxi_name_to_gpio(CONFIG_VIDEO_LCD_POWER);
+ if (pin != -1) {
+ gpio_request(pin, "lcd_power");
+ gpio_direction_output(pin, 1);
+ }
+}
+
+static void sunxi_lcdc_backlight_enable(void)
+{
+ int pin;
+
+ /*
+ * We want to have scanned out at least one frame before enabling the
+ * backlight to avoid the screen flashing to white when we enable it.
+ */
+ mdelay(40);
+
+ pin = sunxi_name_to_gpio(CONFIG_VIDEO_LCD_BL_EN);
+ if (pin != -1)
+ gpio_direction_output(pin, 1);
+
+ pin = sunxi_name_to_gpio(CONFIG_VIDEO_LCD_BL_PWM);
+ if (pin != -1)
+ gpio_direction_output(pin, PWM_ON);
+}
+
+static int sunxi_lcdc_get_clk_delay(const struct ctfb_res_modes *mode)
+{
+ int delay;
+
+ delay = mode->lower_margin + mode->vsync_len + mode->upper_margin - 2;
+ return (delay > 30) ? 30 : delay;
+}
+
+static void sunxi_lcdc_tcon0_mode_set(const struct ctfb_res_modes *mode)
+{
+ struct sunxi_lcdc_reg * const lcdc =
+ (struct sunxi_lcdc_reg *)SUNXI_LCD0_BASE;
+ int bp, clk_delay, clk_div, clk_double, pin, total, val;
+
+ for (pin = SUNXI_GPD(0); pin <= SUNXI_GPD(27); pin++)
+#ifdef CONFIG_VIDEO_LCD_IF_PARALLEL
+ sunxi_gpio_set_cfgpin(pin, SUNXI_GPD0_LCD0);
+#endif
+#ifdef CONFIG_VIDEO_LCD_IF_LVDS
+ sunxi_gpio_set_cfgpin(pin, SUNXI_GPD0_LVDS0);
+#endif
+
+ sunxi_lcdc_pll_set(0, mode->pixclock_khz, &clk_div, &clk_double);
+
+ /* Use tcon0 */
+ clrsetbits_le32(&lcdc->ctrl, SUNXI_LCDC_CTRL_IO_MAP_MASK,
+ SUNXI_LCDC_CTRL_IO_MAP_TCON0);
+
+ clk_delay = sunxi_lcdc_get_clk_delay(mode);
+ writel(SUNXI_LCDC_TCON0_CTRL_ENABLE |
+ SUNXI_LCDC_TCON0_CTRL_CLK_DELAY(clk_delay), &lcdc->tcon0_ctrl);
+
+ writel(SUNXI_LCDC_TCON0_DCLK_ENABLE |
+ SUNXI_LCDC_TCON0_DCLK_DIV(clk_div), &lcdc->tcon0_dclk);
+
+ writel(SUNXI_LCDC_X(mode->xres) | SUNXI_LCDC_Y(mode->yres),
+ &lcdc->tcon0_timing_active);
+
+ bp = mode->hsync_len + mode->left_margin;
+ total = mode->xres + mode->right_margin + bp;
+ writel(SUNXI_LCDC_TCON0_TIMING_H_TOTAL(total) |
+ SUNXI_LCDC_TCON0_TIMING_H_BP(bp), &lcdc->tcon0_timing_h);
+
+ bp = mode->vsync_len + mode->upper_margin;
+ total = mode->yres + mode->lower_margin + bp;
+ writel(SUNXI_LCDC_TCON0_TIMING_V_TOTAL(total) |
+ SUNXI_LCDC_TCON0_TIMING_V_BP(bp), &lcdc->tcon0_timing_v);
+
+#ifdef CONFIG_VIDEO_LCD_IF_PARALLEL
+ writel(SUNXI_LCDC_X(mode->hsync_len) | SUNXI_LCDC_Y(mode->vsync_len),
+ &lcdc->tcon0_timing_sync);
+
+ writel(0, &lcdc->tcon0_hv_intf);
+ writel(0, &lcdc->tcon0_cpu_intf);
+#endif
+#ifdef CONFIG_VIDEO_LCD_IF_LVDS
+ val = (sunxi_display.depth == 18) ? 1 : 0;
+ writel(SUNXI_LCDC_TCON0_LVDS_INTF_BITWIDTH(val), &lcdc->tcon0_lvds_intf);
+#endif
+
+ if (sunxi_display.depth == 18 || sunxi_display.depth == 16) {
+ writel(SUNXI_LCDC_TCON0_FRM_SEED, &lcdc->tcon0_frm_seed[0]);
+ writel(SUNXI_LCDC_TCON0_FRM_SEED, &lcdc->tcon0_frm_seed[1]);
+ writel(SUNXI_LCDC_TCON0_FRM_SEED, &lcdc->tcon0_frm_seed[2]);
+ writel(SUNXI_LCDC_TCON0_FRM_SEED, &lcdc->tcon0_frm_seed[3]);
+ writel(SUNXI_LCDC_TCON0_FRM_SEED, &lcdc->tcon0_frm_seed[4]);
+ writel(SUNXI_LCDC_TCON0_FRM_SEED, &lcdc->tcon0_frm_seed[5]);
+ writel(SUNXI_LCDC_TCON0_FRM_TAB0, &lcdc->tcon0_frm_table[0]);
+ writel(SUNXI_LCDC_TCON0_FRM_TAB1, &lcdc->tcon0_frm_table[1]);
+ writel(SUNXI_LCDC_TCON0_FRM_TAB2, &lcdc->tcon0_frm_table[2]);
+ writel(SUNXI_LCDC_TCON0_FRM_TAB3, &lcdc->tcon0_frm_table[3]);
+ writel(((sunxi_display.depth == 18) ?
+ SUNXI_LCDC_TCON0_FRM_CTRL_RGB666 :
+ SUNXI_LCDC_TCON0_FRM_CTRL_RGB565),
+ &lcdc->tcon0_frm_ctrl);
+ }
+
+ val = SUNXI_LCDC_TCON0_IO_POL_DCLK_PHASE(CONFIG_VIDEO_LCD_DCLK_PHASE);
+ if (!(mode->sync & FB_SYNC_HOR_HIGH_ACT))
+ val |= SUNXI_LCDC_TCON_HSYNC_MASK;
+ if (!(mode->sync & FB_SYNC_VERT_HIGH_ACT))
+ val |= SUNXI_LCDC_TCON_VSYNC_MASK;
+ writel(val, &lcdc->tcon0_io_polarity);
+
+ writel(0, &lcdc->tcon0_io_tristate);
+}
+
+#if defined CONFIG_VIDEO_HDMI || defined CONFIG_VIDEO_VGA
+static void sunxi_lcdc_tcon1_mode_set(const struct ctfb_res_modes *mode,
+ int *clk_div, int *clk_double,
+ bool use_portd_hvsync)
+{
+ struct sunxi_lcdc_reg * const lcdc =
+ (struct sunxi_lcdc_reg *)SUNXI_LCD0_BASE;
+ int bp, clk_delay, total, val;
/* Use tcon1 */
clrsetbits_le32(&lcdc->ctrl, SUNXI_LCDC_CTRL_IO_MAP_MASK,
SUNXI_LCDC_CTRL_IO_MAP_TCON1);
- /* Enabled, 0x1e start delay */
+ clk_delay = sunxi_lcdc_get_clk_delay(mode);
writel(SUNXI_LCDC_TCON1_CTRL_ENABLE |
- SUNXI_LCDC_TCON1_CTRL_CLK_DELAY(0x1e), &lcdc->tcon1_ctrl);
+ SUNXI_LCDC_TCON1_CTRL_CLK_DELAY(clk_delay), &lcdc->tcon1_ctrl);
writel(SUNXI_LCDC_X(mode->xres) | SUNXI_LCDC_Y(mode->yres),
&lcdc->tcon1_timing_source);
@@ -240,22 +761,75 @@ static void sunxi_lcdc_mode_set(struct fb_videomode *mode,
writel(SUNXI_LCDC_X(mode->hsync_len) | SUNXI_LCDC_Y(mode->vsync_len),
&lcdc->tcon1_timing_sync);
- sunxi_lcdc_pll_set(mode->pixclock, clk_div, clk_double);
+ if (use_portd_hvsync) {
+ sunxi_gpio_set_cfgpin(SUNXI_GPD(26), SUNXI_GPD0_LCD0);
+ sunxi_gpio_set_cfgpin(SUNXI_GPD(27), SUNXI_GPD0_LCD0);
+
+ val = 0;
+ if (mode->sync & FB_SYNC_HOR_HIGH_ACT)
+ val |= SUNXI_LCDC_TCON_HSYNC_MASK;
+ if (mode->sync & FB_SYNC_VERT_HIGH_ACT)
+ val |= SUNXI_LCDC_TCON_VSYNC_MASK;
+ writel(val, &lcdc->tcon1_io_polarity);
+
+ clrbits_le32(&lcdc->tcon1_io_tristate,
+ SUNXI_LCDC_TCON_VSYNC_MASK |
+ SUNXI_LCDC_TCON_HSYNC_MASK);
+ }
+ sunxi_lcdc_pll_set(1, mode->pixclock_khz, clk_div, clk_double);
}
+#endif /* CONFIG_VIDEO_HDMI || defined CONFIG_VIDEO_VGA */
-#ifdef CONFIG_MACH_SUN6I
-static void sunxi_drc_init(void)
+#ifdef CONFIG_VIDEO_HDMI
+
+static void sunxi_hdmi_setup_info_frames(const struct ctfb_res_modes *mode)
{
- struct sunxi_ccm_reg * const ccm =
- (struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
+ struct sunxi_hdmi_reg * const hdmi =
+ (struct sunxi_hdmi_reg *)SUNXI_HDMI_BASE;
+ u8 checksum = 0;
+ u8 avi_info_frame[17] = {
+ 0x82, 0x02, 0x0d, 0x00, 0x12, 0x00, 0x88, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00
+ };
+ u8 vendor_info_frame[19] = {
+ 0x81, 0x01, 0x06, 0x29, 0x03, 0x0c, 0x00, 0x40,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00
+ };
+ int i;
- /* On sun6i the drc must be clocked even when in pass-through mode */
- setbits_le32(&ccm->ahb_reset1_cfg, 1 << AHB_RESET_OFFSET_DRC0);
- clock_set_de_mod_clock(&ccm->iep_drc0_clk_cfg, 300000000);
+ if (mode->pixclock_khz <= 27000)
+ avi_info_frame[5] = 0x40; /* SD-modes, ITU601 colorspace */
+ else
+ avi_info_frame[5] = 0x80; /* HD-modes, ITU709 colorspace */
+
+ if (mode->xres * 100 / mode->yres < 156)
+ avi_info_frame[5] |= 0x18; /* 4 : 3 */
+ else
+ avi_info_frame[5] |= 0x28; /* 16 : 9 */
+
+ for (i = 0; i < ARRAY_SIZE(avi_info_frame); i++)
+ checksum += avi_info_frame[i];
+
+ avi_info_frame[3] = 0x100 - checksum;
+
+ for (i = 0; i < ARRAY_SIZE(avi_info_frame); i++)
+ writeb(avi_info_frame[i], &hdmi->avi_info_frame[i]);
+
+ writel(SUNXI_HDMI_QCP_PACKET0, &hdmi->qcp_packet0);
+ writel(SUNXI_HDMI_QCP_PACKET1, &hdmi->qcp_packet1);
+
+ for (i = 0; i < ARRAY_SIZE(vendor_info_frame); i++)
+ writeb(vendor_info_frame[i], &hdmi->vendor_info_frame[i]);
+
+ writel(SUNXI_HDMI_PKT_CTRL0, &hdmi->pkt_ctrl0);
+ writel(SUNXI_HDMI_PKT_CTRL1, &hdmi->pkt_ctrl1);
+
+ setbits_le32(&hdmi->video_ctrl, SUNXI_HDMI_VIDEO_CTRL_HDMI);
}
-#endif
-static void sunxi_hdmi_mode_set(struct fb_videomode *mode,
+static void sunxi_hdmi_mode_set(const struct ctfb_res_modes *mode,
int clk_div, int clk_double)
{
struct sunxi_hdmi_reg * const hdmi =
@@ -265,6 +839,12 @@ static void sunxi_hdmi_mode_set(struct fb_videomode *mode,
/* Write clear interrupt status bits */
writel(SUNXI_HDMI_IRQ_STATUS_BITS, &hdmi->irq);
+ if (sunxi_display.monitor == sunxi_monitor_hdmi)
+ sunxi_hdmi_setup_info_frames(mode);
+
+ /* Set input sync enable */
+ writel(SUNXI_HDMI_UNKNOWN_INPUT_SYNC, &hdmi->unknown);
+
/* Init various registers, select pll3 as clock source */
writel(SUNXI_HDMI_VIDEO_POL_TX_CLK, &hdmi->video_polarity);
writel(SUNXI_HDMI_PAD_CTRL0_RUN, &hdmi->pad_ctrl0);
@@ -301,84 +881,191 @@ static void sunxi_hdmi_mode_set(struct fb_videomode *mode,
setbits_le32(&hdmi->video_polarity, SUNXI_HDMI_VIDEO_POL_VER);
}
-static void sunxi_engines_init(void)
+static void sunxi_hdmi_enable(void)
{
- sunxi_composer_init();
- sunxi_lcdc_init();
-#ifdef CONFIG_MACH_SUN6I
- sunxi_drc_init();
-#endif
+ struct sunxi_hdmi_reg * const hdmi =
+ (struct sunxi_hdmi_reg *)SUNXI_HDMI_BASE;
+
+ udelay(100);
+ setbits_le32(&hdmi->video_ctrl, SUNXI_HDMI_VIDEO_CTRL_ENABLE);
}
-static void sunxi_mode_set(struct fb_videomode *mode, unsigned int address)
+#endif /* CONFIG_VIDEO_HDMI */
+
+#ifdef CONFIG_VIDEO_VGA
+
+static void sunxi_vga_mode_set(void)
{
- struct sunxi_de_be_reg * const de_be =
- (struct sunxi_de_be_reg *)SUNXI_DE_BE0_BASE;
- struct sunxi_lcdc_reg * const lcdc =
- (struct sunxi_lcdc_reg *)SUNXI_LCD0_BASE;
- struct sunxi_hdmi_reg * const hdmi =
- (struct sunxi_hdmi_reg *)SUNXI_HDMI_BASE;
- int clk_div, clk_double;
- int retries = 3;
+ struct sunxi_ccm_reg * const ccm =
+ (struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
+ struct sunxi_tve_reg * const tve =
+ (struct sunxi_tve_reg *)SUNXI_TVE0_BASE;
+
+ /* Clock on */
+ setbits_le32(&ccm->ahb_gate1, 1 << AHB_GATE_OFFSET_TVE0);
+
+ /* Set TVE in VGA mode */
+ writel(SUNXI_TVE_GCTRL_DAC_INPUT(0, 1) |
+ SUNXI_TVE_GCTRL_DAC_INPUT(1, 2) |
+ SUNXI_TVE_GCTRL_DAC_INPUT(2, 3), &tve->gctrl);
+ writel(SUNXI_TVE_GCTRL_CFG0_VGA, &tve->cfg0);
+ writel(SUNXI_TVE_GCTRL_DAC_CFG0_VGA, &tve->dac_cfg0);
+ writel(SUNXI_TVE_GCTRL_UNKNOWN1_VGA, &tve->unknown1);
+}
-retry:
- clrbits_le32(&hdmi->video_ctrl, SUNXI_HDMI_VIDEO_CTRL_ENABLE);
- clrbits_le32(&lcdc->ctrl, SUNXI_LCDC_CTRL_TCON_ENABLE);
- clrbits_le32(&de_be->mode, SUNXI_DE_BE_MODE_START);
+static void sunxi_vga_enable(void)
+{
+ struct sunxi_tve_reg * const tve =
+ (struct sunxi_tve_reg *)SUNXI_TVE0_BASE;
- sunxi_composer_mode_set(mode, address);
- sunxi_lcdc_mode_set(mode, &clk_div, &clk_double);
- sunxi_hdmi_mode_set(mode, clk_div, clk_double);
+ setbits_le32(&tve->gctrl, SUNXI_TVE_GCTRL_ENABLE);
+}
- setbits_le32(&de_be->reg_ctrl, SUNXI_DE_BE_REG_CTRL_LOAD_REGS);
- setbits_le32(&de_be->mode, SUNXI_DE_BE_MODE_START);
+#endif /* CONFIG_VIDEO_VGA */
- udelay(1000000 / mode->refresh + 500);
+static void sunxi_drc_init(void)
+{
+#if defined CONFIG_MACH_SUN6I || defined CONFIG_MACH_SUN8I
+ struct sunxi_ccm_reg * const ccm =
+ (struct sunxi_ccm_reg *)SUNXI_CCM_BASE;
- setbits_le32(&lcdc->ctrl, SUNXI_LCDC_CTRL_TCON_ENABLE);
+ /* On sun6i the drc must be clocked even when in pass-through mode */
+ setbits_le32(&ccm->ahb_reset1_cfg, 1 << AHB_RESET_OFFSET_DRC0);
+ clock_set_de_mod_clock(&ccm->iep_drc0_clk_cfg, 300000000);
+#endif
+}
- udelay(1000000 / mode->refresh + 500);
+#ifdef CONFIG_VIDEO_VGA_VIA_LCD
+static void sunxi_vga_external_dac_enable(void)
+{
+ int pin;
- setbits_le32(&hdmi->video_ctrl, SUNXI_HDMI_VIDEO_CTRL_ENABLE);
+ pin = sunxi_name_to_gpio(CONFIG_VIDEO_VGA_EXTERNAL_DAC_EN);
+ if (pin != -1) {
+ gpio_request(pin, "vga_enable");
+ gpio_direction_output(pin, 1);
+ }
+}
+#endif /* CONFIG_VIDEO_VGA_VIA_LCD */
- udelay(1000000 / mode->refresh + 500);
+#ifdef CONFIG_VIDEO_LCD_SSD2828
+static int sunxi_ssd2828_init(const struct ctfb_res_modes *mode)
+{
+ struct ssd2828_config cfg = {
+ .csx_pin = name_to_gpio(CONFIG_VIDEO_LCD_SPI_CS),
+ .sck_pin = name_to_gpio(CONFIG_VIDEO_LCD_SPI_SCLK),
+ .sdi_pin = name_to_gpio(CONFIG_VIDEO_LCD_SPI_MOSI),
+ .sdo_pin = name_to_gpio(CONFIG_VIDEO_LCD_SPI_MISO),
+ .reset_pin = name_to_gpio(CONFIG_VIDEO_LCD_SSD2828_RESET),
+ .ssd2828_tx_clk_khz = CONFIG_VIDEO_LCD_SSD2828_TX_CLK * 1000,
+ .ssd2828_color_depth = 24,
+#ifdef CONFIG_VIDEO_LCD_PANEL_MIPI_4_LANE_513_MBPS_VIA_SSD2828
+ .mipi_dsi_number_of_data_lanes = 4,
+ .mipi_dsi_bitrate_per_data_lane_mbps = 513,
+ .mipi_dsi_delay_after_exit_sleep_mode_ms = 100,
+ .mipi_dsi_delay_after_set_display_on_ms = 200
+#else
+#error MIPI LCD panel needs configuration parameters
+#endif
+ };
- /*
- * Sometimes the display pipeline does not sync up properly, if
- * this happens the hdmi fifo underrun or overrun bits are set.
- */
- if (readl(&hdmi->irq) &
- (SUNXI_HDMI_IRQ_STATUS_FIFO_UF | SUNXI_HDMI_IRQ_STATUS_FIFO_OF)) {
- if (retries--)
- goto retry;
- printf("HDMI fifo under or overrun\n");
+ if (cfg.csx_pin == -1 || cfg.sck_pin == -1 || cfg.sdi_pin == -1) {
+ printf("SSD2828: SPI pins are not properly configured\n");
+ return 1;
}
+ if (cfg.reset_pin == -1) {
+ printf("SSD2828: Reset pin is not properly configured\n");
+ return 1;
+ }
+
+ return ssd2828_init(&cfg, mode);
+}
+#endif /* CONFIG_VIDEO_LCD_SSD2828 */
+
+static void sunxi_engines_init(void)
+{
+ sunxi_composer_init();
+ sunxi_lcdc_init();
+ sunxi_drc_init();
+}
+
+static void sunxi_mode_set(const struct ctfb_res_modes *mode,
+ unsigned int address)
+{
+ int __maybe_unused clk_div, clk_double;
+
+ switch (sunxi_display.monitor) {
+ case sunxi_monitor_none:
+ break;
+ case sunxi_monitor_dvi:
+ case sunxi_monitor_hdmi:
+#ifdef CONFIG_VIDEO_HDMI
+ sunxi_composer_mode_set(mode, address);
+ sunxi_lcdc_tcon1_mode_set(mode, &clk_div, &clk_double, 0);
+ sunxi_hdmi_mode_set(mode, clk_div, clk_double);
+ sunxi_composer_enable();
+ sunxi_lcdc_enable();
+ sunxi_hdmi_enable();
+#endif
+ break;
+ case sunxi_monitor_lcd:
+ sunxi_lcdc_panel_enable();
+ if (IS_ENABLED(CONFIG_VIDEO_LCD_HITACHI_TX18D42VM)) {
+ mdelay(50); /* Wait for lcd controller power on */
+ hitachi_tx18d42vm_init();
+ }
+ sunxi_composer_mode_set(mode, address);
+ sunxi_lcdc_tcon0_mode_set(mode);
+ sunxi_composer_enable();
+ sunxi_lcdc_enable();
+#ifdef CONFIG_VIDEO_LCD_SSD2828
+ sunxi_ssd2828_init(mode);
+#endif
+ sunxi_lcdc_backlight_enable();
+ break;
+ case sunxi_monitor_vga:
+#ifdef CONFIG_VIDEO_VGA
+ sunxi_composer_mode_set(mode, address);
+ sunxi_lcdc_tcon1_mode_set(mode, &clk_div, &clk_double, 1);
+ sunxi_vga_mode_set();
+ sunxi_composer_enable();
+ sunxi_lcdc_enable();
+ sunxi_vga_enable();
+#elif defined CONFIG_VIDEO_VGA_VIA_LCD
+ sunxi_composer_mode_set(mode, address);
+ sunxi_lcdc_tcon0_mode_set(mode);
+ sunxi_composer_enable();
+ sunxi_lcdc_enable();
+ sunxi_vga_external_dac_enable();
+#endif
+ break;
+ }
+}
+
+static const char *sunxi_get_mon_desc(enum sunxi_monitor monitor)
+{
+ switch (monitor) {
+ case sunxi_monitor_none: return "none";
+ case sunxi_monitor_dvi: return "dvi";
+ case sunxi_monitor_hdmi: return "hdmi";
+ case sunxi_monitor_lcd: return "lcd";
+ case sunxi_monitor_vga: return "vga";
+ }
+ return NULL; /* never reached */
}
void *video_hw_init(void)
{
static GraphicDevice *graphic_device = &sunxi_display.graphic_device;
- /*
- * Vesa standard 1024x768@60
- * 65.0 1024 1048 1184 1344 768 771 777 806 -hsync -vsync
- */
- struct fb_videomode mode = {
- .name = "1024x768",
- .refresh = 60,
- .xres = 1024,
- .yres = 768,
- .pixclock = 65000,
- .left_margin = 160,
- .right_margin = 24,
- .upper_margin = 29,
- .lower_margin = 3,
- .hsync_len = 136,
- .vsync_len = 6,
- .sync = 0,
- .vmode = 0,
- .flag = 0,
- };
- int ret;
+ const struct ctfb_res_modes *mode;
+ struct ctfb_res_modes custom;
+ const char *options;
+#ifdef CONFIG_VIDEO_HDMI
+ int ret, hpd, hpd_delay, edid;
+#endif
+ char mon[16];
+ char *lcd_mode = CONFIG_VIDEO_LCD_MODE;
+ int i;
memset(&sunxi_display, 0, sizeof(struct sunxi_display));
@@ -386,16 +1073,98 @@ void *video_hw_init(void)
CONFIG_SUNXI_FB_SIZE >> 10);
gd->fb_base = gd->ram_top;
- ret = sunxi_hdmi_hpd_detect();
- if (!ret)
+ video_get_ctfb_res_modes(RES_MODE_1024x768, 24, &mode,
+ &sunxi_display.depth, &options);
+#ifdef CONFIG_VIDEO_HDMI
+ hpd = video_get_option_int(options, "hpd", 1);
+ hpd_delay = video_get_option_int(options, "hpd_delay", 500);
+ edid = video_get_option_int(options, "edid", 1);
+ sunxi_display.monitor = sunxi_monitor_dvi;
+#elif defined CONFIG_VIDEO_VGA_VIA_LCD
+ sunxi_display.monitor = sunxi_monitor_vga;
+#else
+ sunxi_display.monitor = sunxi_monitor_lcd;
+#endif
+ video_get_option_string(options, "monitor", mon, sizeof(mon),
+ sunxi_get_mon_desc(sunxi_display.monitor));
+ for (i = 0; i <= SUNXI_MONITOR_LAST; i++) {
+ if (strcmp(mon, sunxi_get_mon_desc(i)) == 0) {
+ sunxi_display.monitor = i;
+ break;
+ }
+ }
+ if (i > SUNXI_MONITOR_LAST)
+ printf("Unknown monitor: '%s', falling back to '%s'\n",
+ mon, sunxi_get_mon_desc(sunxi_display.monitor));
+
+#ifdef CONFIG_VIDEO_HDMI
+ /* If HDMI/DVI is selected do HPD & EDID, and handle fallback */
+ if (sunxi_display.monitor == sunxi_monitor_dvi ||
+ sunxi_display.monitor == sunxi_monitor_hdmi) {
+ /* Always call hdp_detect, as it also enables clocks, etc. */
+ ret = sunxi_hdmi_hpd_detect(hpd_delay);
+ if (ret) {
+ printf("HDMI connected: ");
+ if (edid && sunxi_hdmi_edid_get_mode(&custom) == 0)
+ mode = &custom;
+ } else if (hpd) {
+ sunxi_hdmi_shutdown();
+ /* Fallback to lcd / vga / none */
+ if (lcd_mode[0]) {
+ sunxi_display.monitor = sunxi_monitor_lcd;
+ } else {
+#if defined CONFIG_VIDEO_VGA_VIA_LCD || defined CONFIG_VIDEO_VGA
+ sunxi_display.monitor = sunxi_monitor_vga;
+#else
+ sunxi_display.monitor = sunxi_monitor_none;
+#endif
+ }
+ } /* else continue with hdmi/dvi without a cable connected */
+ }
+#endif
+
+ switch (sunxi_display.monitor) {
+ case sunxi_monitor_none:
+ return NULL;
+ case sunxi_monitor_dvi:
+ case sunxi_monitor_hdmi:
+#ifdef CONFIG_VIDEO_HDMI
+ break;
+#else
+ printf("HDMI/DVI not supported on this board\n");
+ sunxi_display.monitor = sunxi_monitor_none;
return NULL;
+#endif
+ case sunxi_monitor_lcd:
+ if (lcd_mode[0]) {
+ sunxi_display.depth = video_get_params(&custom, lcd_mode);
+ mode = &custom;
+ break;
+ }
+ printf("LCD not supported on this board\n");
+ sunxi_display.monitor = sunxi_monitor_none;
+ return NULL;
+ case sunxi_monitor_vga:
+#if defined CONFIG_VIDEO_VGA_VIA_LCD || defined CONFIG_VIDEO_VGA
+ sunxi_display.depth = 18;
+ break;
+#else
+ printf("VGA not supported on this board\n");
+ sunxi_display.monitor = sunxi_monitor_none;
+ return NULL;
+#endif
+ }
- printf("HDMI connected.\n");
- sunxi_display.enabled = true;
+ if (mode->vmode != FB_VMODE_NONINTERLACED) {
+ printf("Only non-interlaced modes supported, falling back to 1024x768\n");
+ mode = &res_mode_init[RES_MODE_1024x768];
+ } else {
+ printf("Setting up a %dx%d %s console\n", mode->xres,
+ mode->yres, sunxi_get_mon_desc(sunxi_display.monitor));
+ }
- printf("Setting up a %s console.\n", mode.name);
sunxi_engines_init();
- sunxi_mode_set(&mode, gd->fb_base - CONFIG_SYS_SDRAM_BASE);
+ sunxi_mode_set(mode, gd->fb_base - CONFIG_SYS_SDRAM_BASE);
/*
* These are the only members of this structure that are used. All the
@@ -405,8 +1174,8 @@ void *video_hw_init(void)
graphic_device->frameAdrs = gd->fb_base;
graphic_device->gdfIndex = GDF_32BIT_X888RGB;
graphic_device->gdfBytesPP = 4;
- graphic_device->winSizeX = mode.xres;
- graphic_device->winSizeY = mode.yres;
+ graphic_device->winSizeX = mode->xres;
+ graphic_device->winSizeY = mode->yres;
return graphic_device;
}
@@ -419,16 +1188,39 @@ int sunxi_simplefb_setup(void *blob)
{
static GraphicDevice *graphic_device = &sunxi_display.graphic_device;
int offset, ret;
+ const char *pipeline = NULL;
- if (!sunxi_display.enabled)
+#ifdef CONFIG_MACH_SUN4I
+#define PIPELINE_PREFIX "de_fe0-"
+#else
+#define PIPELINE_PREFIX
+#endif
+
+ switch (sunxi_display.monitor) {
+ case sunxi_monitor_none:
return 0;
+ case sunxi_monitor_dvi:
+ case sunxi_monitor_hdmi:
+ pipeline = PIPELINE_PREFIX "de_be0-lcd0-hdmi";
+ break;
+ case sunxi_monitor_lcd:
+ pipeline = PIPELINE_PREFIX "de_be0-lcd0";
+ break;
+ case sunxi_monitor_vga:
+#ifdef CONFIG_VIDEO_VGA
+ pipeline = PIPELINE_PREFIX "de_be0-lcd0-tve0";
+#elif defined CONFIG_VIDEO_VGA_VIA_LCD
+ pipeline = PIPELINE_PREFIX "de_be0-lcd0";
+#endif
+ break;
+ }
- /* Find a framebuffer node, with pipeline == "de_be0-lcd0-hdmi" */
+ /* Find a prefilled simpefb node, matching out pipeline config */
offset = fdt_node_offset_by_compatible(blob, -1,
"allwinner,simple-framebuffer");
while (offset >= 0) {
ret = fdt_find_string(blob, offset, "allwinner,pipeline",
- "de_be0-lcd0-hdmi");
+ pipeline);
if (ret == 0)
break;
offset = fdt_node_offset_by_compatible(blob, offset,
diff --git a/drivers/video/videomodes.c b/drivers/video/videomodes.c
index 18c1f3d..cf71ad1 100644
--- a/drivers/video/videomodes.c
+++ b/drivers/video/videomodes.c
@@ -58,6 +58,8 @@
****************************************************************************/
#include <common.h>
+#include <edid.h>
+#include <errno.h>
#include <linux/ctype.h>
#include "videomodes.h"
@@ -84,13 +86,26 @@ const struct ctfb_vesa_modes vesa_modes[VESA_MODES_COUNT] = {
{0x31B, RES_MODE_1280x1024, 24},
};
const struct ctfb_res_modes res_mode_init[RES_MODES_COUNT] = {
- /* x y pixclk le ri up lo hs vs s vmode */
- {640, 480, 39721, 40, 24, 32, 11, 96, 2, 0, FB_VMODE_NONINTERLACED},
- {800, 600, 27778, 64, 24, 22, 1, 72, 2, 0, FB_VMODE_NONINTERLACED},
- {1024, 768, 15384, 168, 8, 29, 3, 144, 4, 0, FB_VMODE_NONINTERLACED},
- {960, 720, 13100, 160, 40, 32, 8, 80, 4, 0, FB_VMODE_NONINTERLACED},
- {1152, 864, 12004, 200, 64, 32, 16, 80, 4, 0, FB_VMODE_NONINTERLACED},
- {1280, 1024, 9090, 200, 48, 26, 1, 184, 3, 0, FB_VMODE_NONINTERLACED},
+ /* x y hz pixclk ps/kHz le ri up lo hs vs s vmode */
+#ifndef CONFIG_VIDEO_STD_TIMINGS
+ { 640, 480, 60, 39721, 25180, 40, 24, 32, 11, 96, 2, 0, FB_VMODE_NONINTERLACED},
+ { 800, 600, 60, 27778, 36000, 64, 24, 22, 1, 72, 2, 0, FB_VMODE_NONINTERLACED},
+ {1024, 768, 60, 15384, 65000, 168, 8, 29, 3, 144, 4, 0, FB_VMODE_NONINTERLACED},
+ { 960, 720, 80, 13100, 76335, 160, 40, 32, 8, 80, 4, 0, FB_VMODE_NONINTERLACED},
+ {1152, 864, 60, 12004, 83300, 200, 64, 32, 16, 80, 4, 0, FB_VMODE_NONINTERLACED},
+ {1280, 1024, 60, 9090, 110000, 200, 48, 26, 1, 184, 3, 0, FB_VMODE_NONINTERLACED},
+#else
+ { 640, 480, 60, 39683, 25200, 48, 16, 33, 10, 96, 2, 0, FB_VMODE_NONINTERLACED},
+ { 800, 600, 60, 25000, 40000, 88, 40, 23, 1, 128, 4, FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED},
+ {1024, 768, 60, 15384, 65000, 160, 24, 29, 3, 136, 6, 0, FB_VMODE_NONINTERLACED},
+ { 960, 720, 75, 13468, 74250, 176, 72, 27, 1, 112, 2, 0, FB_VMODE_NONINTERLACED},
+ {1152, 864, 75, 9259, 108000, 256, 64, 32, 1, 128, 3, FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED},
+ {1280, 1024, 60, 9259, 108000, 248, 48, 38, 1, 112, 3, FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED},
+#endif
+ {1280, 720, 60, 13468, 74250, 220, 110, 20, 5, 40, 5, FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED},
+ {1360, 768, 60, 11696, 85500, 256, 64, 17, 3, 112, 7, 0, FB_VMODE_NONINTERLACED},
+ {1920, 1080, 60, 6734, 148500, 148, 88, 36, 4, 44, 5, FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED},
+ {1920, 1200, 60, 6494, 154000, 80, 48, 26, 3, 32, 6, FB_SYNC_HOR_HIGH_ACT, FB_VMODE_NONINTERLACED},
};
/************************************************************************
@@ -100,7 +115,7 @@ const struct ctfb_res_modes res_mode_init[RES_MODES_COUNT] = {
* returns the length to the next seperator
*/
static int
-video_get_param_len (char *start, char sep)
+video_get_param_len(const char *start, char sep)
{
int i = 0;
while ((*start != 0) && (*start != sep)) {
@@ -183,6 +198,7 @@ int video_get_params (struct ctfb_res_modes *pPar, char *penv)
while ((i = video_get_param_len (p, ',')) != 0) {
GET_OPTION ("x:", pPar->xres)
GET_OPTION ("y:", pPar->yres)
+ GET_OPTION ("refresh:", pPar->refresh)
GET_OPTION ("le:", pPar->left_margin)
GET_OPTION ("ri:", pPar->right_margin)
GET_OPTION ("up:", pPar->upper_margin)
@@ -192,6 +208,7 @@ int video_get_params (struct ctfb_res_modes *pPar, char *penv)
GET_OPTION ("sync:", pPar->sync)
GET_OPTION ("vmode:", pPar->vmode)
GET_OPTION ("pclk:", pPar->pixclock)
+ GET_OPTION ("pclk_khz:", pPar->pixclock_khz)
GET_OPTION ("depth:", bpp)
p += i;
if (*p != 0)
@@ -260,3 +277,171 @@ int video_get_video_mode(unsigned int *xres, unsigned int *yres,
return 1;
}
+
+/*
+ * Parse the 'video-mode' environment variable using video_get_video_mode()
+ * and lookup the matching ctfb_res_modes in res_mode_init.
+ *
+ * @default_mode: RES_MODE_##x## define for the mode to store in mode_ret
+ * when 'video-mode' is not set or does not contain a valid mode
+ * @default_depth: depth to set when 'video-mode' is not set
+ * @mode_ret: pointer where the mode will be stored
+ * @depth_ret: pointer where the depth will be stored
+ * @options: pointer to any remaining options, or NULL
+ */
+void video_get_ctfb_res_modes(int default_mode, unsigned int default_depth,
+ const struct ctfb_res_modes **mode_ret,
+ unsigned int *depth_ret,
+ const char **options)
+{
+ unsigned int i, xres, yres, depth, refresh;
+
+ *mode_ret = &res_mode_init[default_mode];
+ *depth_ret = default_depth;
+ *options = NULL;
+
+ if (!video_get_video_mode(&xres, &yres, &depth, &refresh, options))
+ return;
+
+ for (i = 0; i < RES_MODES_COUNT; i++) {
+ if (res_mode_init[i].xres == xres &&
+ res_mode_init[i].yres == yres &&
+ res_mode_init[i].refresh == refresh) {
+ *mode_ret = &res_mode_init[i];
+ *depth_ret = depth;
+ return;
+ }
+ }
+
+ printf("video-mode %dx%d-%d@%d not available, falling back to %dx%d-%d@%d\n",
+ xres, yres, depth, refresh, (*mode_ret)->xres,
+ (*mode_ret)->yres, *depth_ret, (*mode_ret)->refresh);
+}
+
+/*
+ * Find the named string option within the ',' separated options string, and
+ * store its value in dest.
+ *
+ * @options: ',' separated options string
+ * @name: name of the option to look for
+ * @dest: destination buffer to store the value of the option in
+ * @dest_len: length of dest
+ * @def: value to store in dest if the option is not present in options
+ */
+void video_get_option_string(const char *options, const char *name,
+ char *dest, int dest_len, const char *def)
+{
+ const char *p = options;
+ const int name_len = strlen(name);
+ int i, len;
+
+ while (p && (i = video_get_param_len(p, ',')) != 0) {
+ if (strncmp(p, name, name_len) == 0 && p[name_len] == '=') {
+ len = i - (name_len + 1);
+ if (len >= dest_len)
+ len = dest_len - 1;
+ memcpy(dest, &p[name_len + 1], len);
+ dest[len] = 0;
+ return;
+ }
+ p += i;
+ if (*p != 0)
+ p++; /* skip ',' */
+ }
+ strcpy(dest, def);
+}
+
+/*
+ * Find the named integer option within the ',' separated options string, and
+ * return its value.
+ *
+ * @options: ',' separated options string
+ * @name: name of the option to look for
+ * @def: value to return if the option is not present in options
+ */
+int video_get_option_int(const char *options, const char *name, int def)
+{
+ const char *p = options;
+ const int name_len = strlen(name);
+ int i;
+
+ while (p && (i = video_get_param_len(p, ',')) != 0) {
+ if (strncmp(p, name, name_len) == 0 && p[name_len] == '=')
+ return simple_strtoul(&p[name_len + 1], NULL, 10);
+
+ p += i;
+ if (*p != 0)
+ p++; /* skip ',' */
+ }
+ return def;
+}
+
+/**
+ * Convert an EDID detailed timing to a struct ctfb_res_modes
+ *
+ * @param t The EDID detailed timing to be converted
+ * @param mode Returns the converted timing
+ *
+ * @return 0 on success, or a negative errno on error
+ */
+int video_edid_dtd_to_ctfb_res_modes(struct edid_detailed_timing *t,
+ struct ctfb_res_modes *mode)
+{
+ int margin, h_total, v_total;
+
+ /* Check all timings are non 0 */
+ if (EDID_DETAILED_TIMING_PIXEL_CLOCK(*t) == 0 ||
+ EDID_DETAILED_TIMING_HORIZONTAL_ACTIVE(*t) == 0 ||
+ EDID_DETAILED_TIMING_HORIZONTAL_BLANKING(*t) == 0 ||
+ EDID_DETAILED_TIMING_VERTICAL_ACTIVE(*t) == 0 ||
+ EDID_DETAILED_TIMING_VERTICAL_BLANKING(*t) == 0 ||
+ EDID_DETAILED_TIMING_HSYNC_OFFSET(*t) == 0 ||
+ EDID_DETAILED_TIMING_HSYNC_PULSE_WIDTH(*t) == 0 ||
+ EDID_DETAILED_TIMING_VSYNC_OFFSET(*t) == 0 ||
+ EDID_DETAILED_TIMING_VSYNC_PULSE_WIDTH(*t) == 0 ||
+ /* 3d formats are not supported*/
+ EDID_DETAILED_TIMING_FLAG_STEREO(*t) != 0)
+ return -EINVAL;
+
+ mode->xres = EDID_DETAILED_TIMING_HORIZONTAL_ACTIVE(*t);
+ mode->yres = EDID_DETAILED_TIMING_VERTICAL_ACTIVE(*t);
+
+ h_total = mode->xres + EDID_DETAILED_TIMING_HORIZONTAL_BLANKING(*t);
+ v_total = mode->yres + EDID_DETAILED_TIMING_VERTICAL_BLANKING(*t);
+ mode->refresh = EDID_DETAILED_TIMING_PIXEL_CLOCK(*t) /
+ (h_total * v_total);
+
+ mode->pixclock_khz = EDID_DETAILED_TIMING_PIXEL_CLOCK(*t) / 1000;
+ mode->pixclock = 1000000000L / mode->pixclock_khz;
+
+ mode->right_margin = EDID_DETAILED_TIMING_HSYNC_OFFSET(*t);
+ mode->hsync_len = EDID_DETAILED_TIMING_HSYNC_PULSE_WIDTH(*t);
+ margin = EDID_DETAILED_TIMING_HORIZONTAL_BLANKING(*t) -
+ (mode->right_margin + mode->hsync_len);
+ if (margin <= 0)
+ return -EINVAL;
+
+ mode->left_margin = margin;
+
+ mode->lower_margin = EDID_DETAILED_TIMING_VSYNC_OFFSET(*t);
+ mode->vsync_len = EDID_DETAILED_TIMING_VSYNC_PULSE_WIDTH(*t);
+ margin = EDID_DETAILED_TIMING_VERTICAL_BLANKING(*t) -
+ (mode->lower_margin + mode->vsync_len);
+ if (margin <= 0)
+ return -EINVAL;
+
+ mode->upper_margin = margin;
+
+ mode->sync = 0;
+ if (EDID_DETAILED_TIMING_FLAG_HSYNC_POLARITY(*t))
+ mode->sync |= FB_SYNC_HOR_HIGH_ACT;
+ if (EDID_DETAILED_TIMING_FLAG_VSYNC_POLARITY(*t))
+ mode->sync |= FB_SYNC_VERT_HIGH_ACT;
+
+ if (EDID_DETAILED_TIMING_FLAG_INTERLACED(*t))
+ mode->vmode = FB_VMODE_INTERLACED;
+ else
+ mode->vmode = FB_VMODE_NONINTERLACED;
+
+ return 0;
+}
diff --git a/drivers/video/videomodes.h b/drivers/video/videomodes.h
index d83993a..82190a2 100644
--- a/drivers/video/videomodes.h
+++ b/drivers/video/videomodes.h
@@ -5,6 +5,7 @@
* SPDX-License-Identifier: GPL-2.0+
*/
+#include <edid.h>
#ifndef CONFIG_SYS_DEFAULT_VIDEO_MODE
#define CONFIG_SYS_DEFAULT_VIDEO_MODE 0x301
@@ -35,8 +36,10 @@
struct ctfb_res_modes {
int xres; /* visible resolution */
int yres;
+ int refresh; /* vertical refresh rate in hz */
/* Timing: All values in pixclocks, except pixclock (of course) */
int pixclock; /* pixel clock in ps (pico seconds) */
+ int pixclock_khz; /* pixel clock in kHz */
int left_margin; /* time from sync to picture */
int right_margin; /* time from picture to sync */
int upper_margin; /* time from sync to picture */
@@ -62,7 +65,11 @@ struct ctfb_vesa_modes {
#define RES_MODE_960_720 3
#define RES_MODE_1152x864 4
#define RES_MODE_1280x1024 5
-#define RES_MODES_COUNT 6
+#define RES_MODE_1280x720 6
+#define RES_MODE_1360x768 7
+#define RES_MODE_1920x1080 8
+#define RES_MODE_1920x1200 9
+#define RES_MODES_COUNT 10
#define VESA_MODES_COUNT 19
@@ -73,3 +80,16 @@ int video_get_params (struct ctfb_res_modes *pPar, char *penv);
int video_get_video_mode(unsigned int *xres, unsigned int *yres,
unsigned int *depth, unsigned int *freq, const char **options);
+
+void video_get_ctfb_res_modes(int default_mode, unsigned int default_depth,
+ const struct ctfb_res_modes **mode_ret,
+ unsigned int *depth_ret,
+ const char **options);
+
+void video_get_option_string(const char *options, const char *name,
+ char *dest, int dest_len, const char *def);
+
+int video_get_option_int(const char *options, const char *name, int def);
+
+int video_edid_dtd_to_ctfb_res_modes(struct edid_detailed_timing *t,
+ struct ctfb_res_modes *mode);
diff --git a/drivers/video/x86_fb.c b/drivers/video/x86_fb.c
index 8743a8c..6641033 100644
--- a/drivers/video/x86_fb.c
+++ b/drivers/video/x86_fb.c
@@ -32,6 +32,7 @@ void *video_hw_init(void)
sprintf(gdev->modeIdent, "%dx%dx%d", gdev->winSizeX, gdev->winSizeY,
bits_per_pixel);
printf("%s\n", gdev->modeIdent);
+ debug("Frame buffer at %x\n", gdev->frameAdrs);
return (void *)gdev;
}