diff options
Diffstat (limited to 'drivers')
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; } |