From 331d74750e69a2202f857d3af9323335d0d6879f Mon Sep 17 00:00:00 2001 From: Ben Dooks Date: Sat, 23 Jun 2007 17:16:28 -0700 Subject: SM501: suspend support This patch adds support for suspending the core (mfd driver) of the SM501. Signed-off-by: Ben Dooks Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/mfd/sm501.c | 113 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 105 insertions(+), 8 deletions(-) (limited to 'drivers/mfd/sm501.c') diff --git a/drivers/mfd/sm501.c b/drivers/mfd/sm501.c index b0b4458ae90..72bbecf4b45 100644 --- a/drivers/mfd/sm501.c +++ b/drivers/mfd/sm501.c @@ -41,6 +41,9 @@ struct sm501_devdata { struct resource *regs_claim; struct sm501_platdata *platdata; + unsigned int in_suspend; + unsigned long pm_misc; + int unit_power[20]; unsigned int pdev_id; unsigned int irq; @@ -169,10 +172,41 @@ x "M %ld.%ld (%ld), MX1 %ld.%ld (%ld)\n", fmt_freq(decode_div(pll2, pm1, 8, 1<<12, 15, misc_div)), fmt_freq(decode_div(pll2, pm1, 0, 1<<4, 15, misc_div))); } -#else -static void sm501_dump_clk(struct sm501_devdata *sm) + +static void sm501_dump_regs(struct sm501_devdata *sm) +{ + void __iomem *regs = sm->regs; + + dev_info(sm->dev, "System Control %08x\n", + readl(regs + SM501_SYSTEM_CONTROL)); + dev_info(sm->dev, "Misc Control %08x\n", + readl(regs + SM501_MISC_CONTROL)); + dev_info(sm->dev, "GPIO Control Low %08x\n", + readl(regs + SM501_GPIO31_0_CONTROL)); + dev_info(sm->dev, "GPIO Control Hi %08x\n", + readl(regs + SM501_GPIO63_32_CONTROL)); + dev_info(sm->dev, "DRAM Control %08x\n", + readl(regs + SM501_DRAM_CONTROL)); + dev_info(sm->dev, "Arbitration Ctrl %08x\n", + readl(regs + SM501_ARBTRTN_CONTROL)); + dev_info(sm->dev, "Misc Timing %08x\n", + readl(regs + SM501_MISC_TIMING)); +} + +static void sm501_dump_gate(struct sm501_devdata *sm) { + dev_info(sm->dev, "CurrentGate %08x\n", + readl(sm->regs + SM501_CURRENT_GATE)); + dev_info(sm->dev, "CurrentClock %08x\n", + readl(sm->regs + SM501_CURRENT_CLOCK)); + dev_info(sm->dev, "PowerModeControl %08x\n", + readl(sm->regs + SM501_POWER_MODE_CONTROL)); } + +#else +static inline void sm501_dump_gate(struct sm501_devdata *sm) { } +static inline void sm501_dump_regs(struct sm501_devdata *sm) { } +static inline void sm501_dump_clk(struct sm501_devdata *sm) { } #endif /* sm501_sync_regs @@ -185,9 +219,21 @@ static void sm501_sync_regs(struct sm501_devdata *sm) readl(sm->regs); } +static inline void sm501_mdelay(struct sm501_devdata *sm, unsigned int delay) +{ + /* during suspend/resume, we are currently not allowed to sleep, + * so change to using mdelay() instead of msleep() if we + * are in one of these paths */ + + if (sm->in_suspend) + mdelay(delay); + else + msleep(delay); +} + /* sm501_misc_control * - * alters the misceleneous control parameters + * alters the miscellaneous control parameters */ int sm501_misc_control(struct device *dev, @@ -368,7 +414,7 @@ int sm501_unit_power(struct device *dev, unsigned int unit, unsigned int to) dev_dbg(sm->dev, "gate %08lx, clock %08lx, mode %08lx\n", gate, clock, mode); - msleep(16); + sm501_mdelay(sm, 16); already: mutex_unlock(&sm->clock_lock); @@ -538,7 +584,7 @@ unsigned long sm501_set_clock(struct device *dev, dev_info(sm->dev, "gate %08lx, clock %08lx, mode %08lx\n", gate, clock, mode); - msleep(16); + sm501_mdelay(sm, 16); mutex_unlock(&sm->clock_lock); sm501_dump_clk(sm); @@ -841,9 +887,7 @@ static int sm501_init_dev(struct sm501_devdata *sm) sm->regs, readl(sm->regs + SM501_DEVICEID), (unsigned long)mem_avail >> 20, sm->irq); - dev_info(sm->dev, "CurrentGate %08x\n", readl(sm->regs+0x38)); - dev_info(sm->dev, "CurrentClock %08x\n", readl(sm->regs+0x3c)); - dev_info(sm->dev, "PowerModeControl %08x\n", readl(sm->regs+0x54)); + sm501_dump_gate(sm); ret = device_create_file(sm->dev, &dev_attr_dbg_regs); if (ret) @@ -933,6 +977,57 @@ static int sm501_plat_probe(struct platform_device *dev) } +#ifdef CONFIG_PM +/* power management support */ + +static int sm501_plat_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct sm501_devdata *sm = platform_get_drvdata(pdev); + + sm->in_suspend = 1; + sm->pm_misc = readl(sm->regs + SM501_MISC_CONTROL); + + sm501_dump_regs(sm); + return 0; +} + +static int sm501_plat_resume(struct platform_device *pdev) +{ + struct sm501_devdata *sm = platform_get_drvdata(pdev); + + sm501_dump_regs(sm); + sm501_dump_gate(sm); + sm501_dump_clk(sm); + + /* check to see if we are in the same state as when suspended */ + + if (readl(sm->regs + SM501_MISC_CONTROL) != sm->pm_misc) { + dev_info(sm->dev, "SM501_MISC_CONTROL changed over sleep\n"); + writel(sm->pm_misc, sm->regs + SM501_MISC_CONTROL); + + /* our suspend causes the controller state to change, + * either by something attempting setup, power loss, + * or an external reset event on power change */ + + if (sm->platdata && sm->platdata->init) { + sm501_init_regs(sm, sm->platdata->init); + } + } + + /* dump our state from resume */ + + sm501_dump_regs(sm); + sm501_dump_clk(sm); + + sm->in_suspend = 0; + + return 0; +} +#else +#define sm501_plat_suspend NULL +#define sm501_plat_resume NULL +#endif + /* Initialisation data for PCI devices */ static struct sm501_initdata sm501_pci_initdata = { @@ -1126,6 +1221,8 @@ static struct platform_driver sm501_plat_drv = { }, .probe = sm501_plat_probe, .remove = sm501_plat_remove, + .suspend = sm501_plat_suspend, + .resume = sm501_plat_resume, }; static int __init sm501_base_init(void) -- cgit v1.2.3-70-g09d2 From b5913bbd2ddebd9c2c6841986c29376e691dd396 Mon Sep 17 00:00:00 2001 From: Ben Dooks Date: Sat, 23 Jun 2007 17:16:29 -0700 Subject: SM501: initialise SDRAM clock before bus clocks This init sequence of setting the SDRAM clock before the bus clock is recommend by Silicon Motion to stop problems with writes not sticking into registers. Signed-off-by: Vincent Sanders Signed-off-by: Ben Dooks Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/mfd/sm501.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'drivers/mfd/sm501.c') diff --git a/drivers/mfd/sm501.c b/drivers/mfd/sm501.c index 72bbecf4b45..4c4412e0de2 100644 --- a/drivers/mfd/sm501.c +++ b/drivers/mfd/sm501.c @@ -843,15 +843,15 @@ static void sm501_init_regs(struct sm501_devdata *sm, sm501_init_reg(sm, SM501_GPIO31_0_CONTROL, &init->gpio_low); sm501_init_reg(sm, SM501_GPIO63_32_CONTROL, &init->gpio_high); - if (init->mclk) { - dev_info(sm->dev, "setting MCLK to %ld\n", init->mclk); - sm501_set_clock(sm->dev, SM501_CLOCK_MCLK, init->mclk); - } - if (init->m1xclk) { dev_info(sm->dev, "setting M1XCLK to %ld\n", init->m1xclk); sm501_set_clock(sm->dev, SM501_CLOCK_M1XCLK, init->m1xclk); } + + if (init->mclk) { + dev_info(sm->dev, "setting MCLK to %ld\n", init->mclk); + sm501_set_clock(sm->dev, SM501_CLOCK_MCLK, init->mclk); + } } static unsigned int sm501_mem_local[] = { -- cgit v1.2.3-70-g09d2 From 5136237bc392413332b02e69ada158c307da658f Mon Sep 17 00:00:00 2001 From: Ben Dooks Date: Sat, 23 Jun 2007 17:16:29 -0700 Subject: SM501: Fix sm501_init_reg() mask/set order The order of the set and mask operation in sm501_init_reg() was setting and then masking the bits set. Correct the order so that we do not end up with 288MHz SDRAM clocks on certain systems. Signed-off-by: Ben Dooks Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/mfd/sm501.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'drivers/mfd/sm501.c') diff --git a/drivers/mfd/sm501.c b/drivers/mfd/sm501.c index 4c4412e0de2..3a0ecfc404e 100644 --- a/drivers/mfd/sm501.c +++ b/drivers/mfd/sm501.c @@ -813,6 +813,9 @@ static DEVICE_ATTR(dbg_regs, 0666, sm501_dbg_regs, NULL); /* sm501_init_reg * * Helper function for the init code to setup a register + * + * clear the bits which are set in r->mask, and then set + * the bits set in r->set. */ static inline void sm501_init_reg(struct sm501_devdata *sm, @@ -822,8 +825,8 @@ static inline void sm501_init_reg(struct sm501_devdata *sm, unsigned long tmp; tmp = readl(sm->regs + reg); - tmp |= r->set; tmp &= ~r->mask; + tmp |= r->set; writel(tmp, sm->regs + reg); } -- cgit v1.2.3-70-g09d2 From 819062219abf8a78e54cad5c1c8716e6c8e7b870 Mon Sep 17 00:00:00 2001 From: Ben Dooks Date: Sat, 23 Jun 2007 17:16:30 -0700 Subject: SM501: Clock updates and checks Ensure that the M1XCLK and MCLK are sourced from the same PLL (and refuse to bind the driver if they are not). Update the PCI to safe initialisation values, as 72MHz is the maximum clock for 33MHz PCI bus mastering. Signed-off-by: Ben Dooks Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/mfd/sm501.c | 33 +++++++++++++++++++++++++++++++-- include/linux/sm501-regs.h | 5 +++++ 2 files changed, 36 insertions(+), 2 deletions(-) (limited to 'drivers/mfd/sm501.c') diff --git a/drivers/mfd/sm501.c b/drivers/mfd/sm501.c index 3a0ecfc404e..e14d70e0741 100644 --- a/drivers/mfd/sm501.c +++ b/drivers/mfd/sm501.c @@ -855,6 +855,24 @@ static void sm501_init_regs(struct sm501_devdata *sm, dev_info(sm->dev, "setting MCLK to %ld\n", init->mclk); sm501_set_clock(sm->dev, SM501_CLOCK_MCLK, init->mclk); } + +} + +/* Check the PLL sources for the M1CLK and M1XCLK + * + * If the M1CLK and M1XCLKs are not sourced from the same PLL, then + * there is a risk (see errata AB-5) that the SM501 will cease proper + * function. If this happens, then it is likely the SM501 will + * hang the system. +*/ + +static int sm501_check_clocks(struct sm501_devdata *sm) +{ + unsigned long pwrmode = readl(sm->regs + SM501_CURRENT_CLOCK); + unsigned long msrc = (pwrmode & SM501_POWERMODE_M_SRC); + unsigned long m1src = (pwrmode & SM501_POWERMODE_M1_SRC); + + return ((msrc == 0 && m1src != 0) || (msrc != 0 && m1src == 0)); } static unsigned int sm501_mem_local[] = { @@ -911,6 +929,13 @@ static int sm501_init_dev(struct sm501_devdata *sm) } } + ret = sm501_check_clocks(sm); + if (ret) { + dev_err(sm->dev, "M1X and M clocks sourced from different " + "PLLs\n"); + return -EINVAL; + } + /* always create a framebuffer */ sm501_register_display(sm, &mem_avail); @@ -1048,8 +1073,12 @@ static struct sm501_initdata sm501_pci_initdata = { }, .devices = SM501_USE_ALL, - .mclk = 100 * MHZ, - .m1xclk = 160 * MHZ, + + /* Errata AB-3 says that 72MHz is the fastest available + * for 33MHZ PCI with proper bus-mastering operation */ + + .mclk = 72 * MHZ, + .m1xclk = 144 * MHZ, }; static struct sm501_platdata_fbsub sm501_pdata_fbsub = { diff --git a/include/linux/sm501-regs.h b/include/linux/sm501-regs.h index cc9be4a1186..8b4ecf02f9d 100644 --- a/include/linux/sm501-regs.h +++ b/include/linux/sm501-regs.h @@ -64,6 +64,11 @@ #define SM501_DEBUG_CONTROL (0x000034) /* power management */ +#define SM501_POWERMODE_P2X_SRC (1<<29) +#define SM501_POWERMODE_V2X_SRC (1<<20) +#define SM501_POWERMODE_M_SRC (1<<12) +#define SM501_POWERMODE_M1_SRC (1<<4) + #define SM501_CURRENT_GATE (0x000038) #define SM501_CURRENT_CLOCK (0x00003C) #define SM501_POWER_MODE_0_GATE (0x000040) -- cgit v1.2.3-70-g09d2 From 1e27dbe7746f3bcbcf1f9a37f31df4b886e36ce3 Mon Sep 17 00:00:00 2001 From: Ben Dooks Date: Sat, 23 Jun 2007 17:16:31 -0700 Subject: SM501: Check SM501 ID register on initialisation When binding the driver, check the ID register for a valid identity, in case the SM501 is not functioning correctly. Signed-off-by: Ben Dooks Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/mfd/sm501.c | 14 ++++++++++---- include/linux/sm501-regs.h | 3 +++ 2 files changed, 13 insertions(+), 4 deletions(-) (limited to 'drivers/mfd/sm501.c') diff --git a/drivers/mfd/sm501.c b/drivers/mfd/sm501.c index e14d70e0741..8135e4c3bf4 100644 --- a/drivers/mfd/sm501.c +++ b/drivers/mfd/sm501.c @@ -893,6 +893,7 @@ static int sm501_init_dev(struct sm501_devdata *sm) { resource_size_t mem_avail; unsigned long dramctrl; + unsigned long devid; int ret; mutex_init(&sm->clock_lock); @@ -900,13 +901,18 @@ static int sm501_init_dev(struct sm501_devdata *sm) INIT_LIST_HEAD(&sm->devices); - dramctrl = readl(sm->regs + SM501_DRAM_CONTROL); + devid = readl(sm->regs + SM501_DEVICEID); + if ((devid & SM501_DEVICEID_IDMASK) != SM501_DEVICEID_SM501) { + dev_err(sm->dev, "incorrect device id %08lx\n", devid); + return -EINVAL; + } + + dramctrl = readl(sm->regs + SM501_DRAM_CONTROL); mem_avail = sm501_mem_local[(dramctrl >> 13) & 0x7]; - dev_info(sm->dev, "SM501 At %p: Version %08x, %ld Mb, IRQ %d\n", - sm->regs, readl(sm->regs + SM501_DEVICEID), - (unsigned long)mem_avail >> 20, sm->irq); + dev_info(sm->dev, "SM501 At %p: Version %08lx, %ld Mb, IRQ %d\n", + sm->regs, devid, (unsigned long)mem_avail >> 20, sm->irq); sm501_dump_gate(sm); diff --git a/include/linux/sm501-regs.h b/include/linux/sm501-regs.h index 8b4ecf02f9d..014e73b31fc 100644 --- a/include/linux/sm501-regs.h +++ b/include/linux/sm501-regs.h @@ -109,6 +109,9 @@ #define SM501_DEVICEID (0x000060) /* 0x050100A0 */ +#define SM501_DEVICEID_SM501 (0x05010000) +#define SM501_DEVICEID_IDMASK (0xffff0000) + #define SM501_PLLCLOCK_COUNT (0x000064) #define SM501_MISC_TIMING (0x000068) #define SM501_CURRENT_SDRAM_CLOCK (0x00006C) -- cgit v1.2.3-70-g09d2