diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/gpio/sunxi_gpio.c | 32 | ||||
-rw-r--r-- | drivers/mmc/sunxi_mmc.c | 102 | ||||
-rw-r--r-- | drivers/mtd/nand/nand_base.c | 11 | ||||
-rw-r--r-- | drivers/mtd/nand/nand_util.c | 4 | ||||
-rw-r--r-- | drivers/pci/pci.c | 25 | ||||
-rw-r--r-- | drivers/pci/pci_rom.c | 9 | ||||
-rw-r--r-- | drivers/pci/pci_tegra.c | 3 | ||||
-rw-r--r-- | drivers/power/Kconfig | 54 | ||||
-rw-r--r-- | drivers/power/axp209.c | 72 | ||||
-rw-r--r-- | drivers/power/axp221.c | 245 | ||||
-rw-r--r-- | drivers/serial/ns16550.c | 31 | ||||
-rw-r--r-- | drivers/serial/serial_x86.c | 8 | ||||
-rw-r--r-- | drivers/usb/host/ehci-sunxi.c | 213 | ||||
-rw-r--r-- | drivers/video/Kconfig | 8 | ||||
-rw-r--r-- | drivers/video/Makefile | 2 | ||||
-rw-r--r-- | drivers/video/sunxi_display.c | 825 | ||||
-rw-r--r-- | drivers/video/videomodes.c | 201 | ||||
-rw-r--r-- | drivers/video/videomodes.h | 22 | ||||
-rw-r--r-- | drivers/video/x86_fb.c | 1 |
19 files changed, 1463 insertions, 405 deletions
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/sunxi_mmc.c b/drivers/mmc/sunxi_mmc.c index 231f0a0..6234981 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,72 @@ 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 { + pll = CCM_MMC_CTRL_PLL6; + pll_hz = clock_get_pll6(); + } + + 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; @@ -96,15 +151,7 @@ static int mmc_clk_io_on(int sdc_no) setbits_le32(&ccm->ahb_reset0_cfg, 1 << AHB_RESET_OFFSET_MMC(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 +176,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 +187,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 +207,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 */ @@ -373,7 +417,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 = { diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index 70e780c..63bdf65 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -634,9 +634,10 @@ static int nand_block_checkbad(struct mtd_info *mtd, loff_t ofs, int getchip, { struct nand_chip *chip = mtd->priv; - if (!(chip->options & NAND_BBT_SCANNED)) { - chip->scan_bbt(mtd); + if (!(chip->options & NAND_SKIP_BBTSCAN) && + !(chip->options & NAND_BBT_SCANNED)) { chip->options |= NAND_BBT_SCANNED; + chip->scan_bbt(mtd); } if (!chip->bbt) @@ -2905,7 +2906,7 @@ int nand_erase_nand(struct mtd_info *mtd, struct erase_info *instr, WATCHDOG_RESET(); /* Check if we have a bad block, we do not erase bad blocks! */ - if (nand_block_checkbad(mtd, ((loff_t) page) << + if (!instr->scrub && nand_block_checkbad(mtd, ((loff_t) page) << chip->page_shift, 0, allowbbt)) { pr_warn("%s: attempt to erase a bad block at page 0x%08x\n", __func__, page); @@ -4325,10 +4326,6 @@ int nand_scan_tail(struct mtd_info *mtd) if (!mtd->bitflip_threshold) mtd->bitflip_threshold = mtd->ecc_strength; - /* Check, if we should skip the bad block table scan */ - if (chip->options & NAND_SKIP_BBTSCAN) - chip->options |= NAND_BBT_SCANNED; - return 0; } EXPORT_SYMBOL(nand_scan_tail); diff --git a/drivers/mtd/nand/nand_util.c b/drivers/mtd/nand/nand_util.c index 024f6fb..afdd160 100644 --- a/drivers/mtd/nand/nand_util.c +++ b/drivers/mtd/nand/nand_util.c @@ -91,6 +91,7 @@ int nand_erase_opts(nand_info_t *meminfo, const nand_erase_options_t *opts) kfree(chip->bbt); } chip->bbt = NULL; + chip->options &= ~NAND_BBT_SCANNED; } for (erased_length = 0; @@ -179,9 +180,6 @@ int nand_erase_opts(nand_info_t *meminfo, const nand_erase_options_t *opts) if (!opts->quiet) printf("\n"); - if (opts->scrub) - chip->scan_bbt(meminfo); - return 0; } diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 3daf73c..83fd9a0 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,7 +191,7 @@ 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 @@ -233,7 +243,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 +719,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 +730,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..e68e16b 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -1,47 +1,65 @@ 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 -1 to - disable aldo3. + 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. diff --git a/drivers/power/axp209.c b/drivers/power/axp209.c index 9798e5b..3b1a6a7 100644 --- a/drivers/power/axp209.c +++ b/drivers/power/axp209.c @@ -18,6 +18,11 @@ enum axp209_reg { AXP209_LDO3_VOLTAGE = 0x29, 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 +32,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); @@ -165,3 +179,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..4c86f09 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; @@ -166,21 +304,88 @@ int axp221_set_aldo3(unsigned int mvolt) 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/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/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/video/Kconfig b/drivers/video/Kconfig index e69de29..fdbf3f6 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -0,0 +1,8 @@ +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. diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 00b563f..42b1eaa 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -39,7 +39,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/sunxi_display.c b/drivers/video/sunxi_display.c index d241397..d92dfa8 100644 --- a/drivers/video/sunxi_display.c +++ b/drivers/video/sunxi_display.c @@ -11,26 +11,58 @@ #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" 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 +83,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 +105,171 @@ 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 */ + /* * 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,7 +282,7 @@ static void sunxi_composer_init(void) (struct sunxi_de_be_reg *)SUNXI_DE_BE0_BASE; int i; -#ifdef CONFIG_MACH_SUN6I +#if defined CONFIG_MACH_SUN6I || defined CONFIG_MACH_SUN8I /* Reset off */ setbits_le32(&ccm->ahb_reset1_cfg, 1 << AHB_RESET_OFFSET_DE_BE0); #endif @@ -97,7 +299,7 @@ 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 = @@ -115,23 +317,46 @@ static void sunxi_composer_mode_set(struct fb_videomode *mode, 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; + + 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 +393,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 +417,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 +425,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 +441,183 @@ 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; + + 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"); + /* backlight pwm is inverted, set to 1 to disable backlight */ + gpio_direction_output(pin, 1); + } + + /* 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) { + /* backlight pwm is inverted, set to 0 to enable backlight */ + gpio_direction_output(pin, 0); + } +} + +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); + } + +#ifdef CONFIG_VIDEO_LCD_IF_PARALLEL + val = SUNXI_LCDC_TCON0_IO_POL_DCLK_PHASE0; +#endif +#ifdef CONFIG_VIDEO_LCD_IF_LVDS + val = SUNXI_LCDC_TCON0_IO_POL_DCLK_PHASE60; +#endif + 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, total; + 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 +639,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 +717,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 +759,150 @@ 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; -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); + /* 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); +} - sunxi_composer_mode_set(mode, address); - sunxi_lcdc_mode_set(mode, &clk_div, &clk_double); - sunxi_hdmi_mode_set(mode, clk_div, clk_double); +static void sunxi_vga_enable(void) +{ + struct sunxi_tve_reg * const tve = + (struct sunxi_tve_reg *)SUNXI_TVE0_BASE; - setbits_le32(&de_be->reg_ctrl, SUNXI_DE_BE_REG_CTRL_LOAD_REGS); - setbits_le32(&de_be->mode, SUNXI_DE_BE_MODE_START); + setbits_le32(&tve->gctrl, SUNXI_TVE_GCTRL_ENABLE); +} - udelay(1000000 / mode->refresh + 500); +#endif /* CONFIG_VIDEO_VGA */ - setbits_le32(&lcdc->ctrl, SUNXI_LCDC_CTRL_TCON_ENABLE); +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; + + /* 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); +static void sunxi_engines_init(void) +{ + sunxi_composer_init(); + sunxi_lcdc_init(); + sunxi_drc_init(); +} - /* - * 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"); +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(); + sunxi_composer_mode_set(mode, address); + sunxi_lcdc_tcon0_mode_set(mode); + sunxi_composer_enable(); + sunxi_lcdc_enable(); + 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 +910,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 +1011,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 +1025,33 @@ 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) + switch (sunxi_display.monitor) { + case sunxi_monitor_none: return 0; + case sunxi_monitor_dvi: + case sunxi_monitor_hdmi: + pipeline = "de_be0-lcd0-hdmi"; + break; + case sunxi_monitor_lcd: + pipeline = "de_be0-lcd0"; + break; + case sunxi_monitor_vga: +#ifdef CONFIG_VIDEO_VGA + pipeline = "de_be0-lcd0-tve0"; +#elif defined CONFIG_VIDEO_VGA_VIA_LCD + pipeline = "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; } |