Merge OpenMoko kernel patches git://git.openmoko.org/git/kernel.git#(no lars@lars-laptop Thu May 14 18:33:23 UTC 2009 --- --- /dev/null +++ b/arch/arm/mach-s3c2410/include/mach/mci.h @@ -0,0 +1,13 @@ +#ifndef _ARCH_MCI_H +#define _ARCH_MCI_H + +struct s3c24xx_mci_pdata { + unsigned int gpio_detect; + unsigned int gpio_wprotect; + unsigned long ocr_avail; + unsigned int do_dma; + void (*set_power)(unsigned char power_mode, + unsigned short vdd); +}; + +#endif /* _ARCH_NCI_H */ --- a/arch/arm/mach-s3c2410/include/mach/regs-sdi.h +++ b/arch/arm/mach-s3c2410/include/mach/regs-sdi.h @@ -30,6 +30,7 @@ #define S3C2410_SDIFSTA (0x38) #define S3C2410_SDIDATA (0x3C) +#define S3C2410_SDIDATA_BYTE (0x3C) #define S3C2410_SDIIMSK (0x40) #define S3C2440_SDIDATA (0x40) @@ -37,6 +38,8 @@ #define S3C2440_SDICON_SDRESET (1<<8) #define S3C2440_SDICON_MMCCLOCK (1<<5) +#define S3C2440_SDIDATA_BYTE (0x48) + #define S3C2410_SDICON_BYTEORDER (1<<4) #define S3C2410_SDICON_SDIOIRQ (1<<3) #define S3C2410_SDICON_RWAITEN (1<<2) --- a/arch/arm/mach-s3c2440/s3c2440.c +++ b/arch/arm/mach-s3c2440/s3c2440.c @@ -46,6 +46,9 @@ int __init s3c2440_init(void) s3c_device_wdt.resource[1].start = IRQ_S3C2440_WDT; s3c_device_wdt.resource[1].end = IRQ_S3C2440_WDT; + /* make sure SD/MMC driver can distinguish 2440 from 2410 */ + s3c_device_sdi.name = "s3c2440-sdi"; + /* register our system device for everything else */ return sysdev_register(&s3c2440_sysdev); --- a/arch/arm/mach-s3c2442/s3c2442.c +++ b/arch/arm/mach-s3c2442/s3c2442.c @@ -21,6 +21,7 @@ #include #include +#include static struct sys_device s3c2442_sysdev = { .cls = &s3c2442_sysclass, @@ -30,5 +31,8 @@ int __init s3c2442_init(void) { printk("S3C2442: Initialising architecture\n"); + /* make sure SD/MMC driver can distinguish 2440 from 2410 */ + s3c_device_sdi.name = "s3c2440-sdi"; + return sysdev_register(&s3c2442_sysdev); } --- a/arch/arm/Makefile +++ b/arch/arm/Makefile @@ -55,7 +55,8 @@ ifeq ($(CONFIG_CPU_32v6),y) arch-$(CONFIG_CPU_32v6K) :=-D__LINUX_ARM_ARCH__=6 $(call cc-option,-march=armv6k,-march=armv5t -Wa$(comma)-march=armv6k) endif arch-$(CONFIG_CPU_32v5) :=-D__LINUX_ARM_ARCH__=5 $(call cc-option,-march=armv5te,-march=armv4t) -arch-$(CONFIG_CPU_32v4T) :=-D__LINUX_ARM_ARCH__=4 -march=armv4t +# We can't load armv4t modules, but still need to assemble some armv4t code to be linked in. +arch-$(CONFIG_CPU_32v4T) :=-D__LINUX_ARM_ARCH__=4 -march=armv4 -Wa,-march=armv4t arch-$(CONFIG_CPU_32v4) :=-D__LINUX_ARM_ARCH__=4 -march=armv4 arch-$(CONFIG_CPU_32v3) :=-D__LINUX_ARM_ARCH__=3 -march=armv3 --- /dev/null +++ b/arch/arm/plat-s3c/include/mach/cpu.h @@ -0,0 +1,165 @@ +/* + * arch/arm/plat-s3c/include/mach/cpu.h + * + * S3C cpu type detection + * + * Copyright (C) 2008 Samsung Electronics + * Kyungmin Park + * + * Derived from OMAP cpu.h + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __ASM_ARCH_S3C_CPU_H +#define __ASM_ARCH_S3C_CPU_H + +extern unsigned int system_rev; + +#define S3C_SYSTEM_REV_ATAG (system_rev & 0xffff) +#define S3C_SYSTEM_REV_CPU (system_rev & 0xffff0000) + +/* + * cpu_is_s3c24xx(): True for s3c2400, s3c2410, s3c2440 and so on + * cpu_is_s3c241x(): True fro s3c2410, s3c2412 + * cpu_is_s3c244x(): True fro s3c2440, s3c2442, s3c2443 + * cpu_is_s3c64xx(): True for s3c6400, s3c6410 + */ +#define GET_S3C_CLASS ((system_rev >> 24) & 0xff) + +#define IS_S3C_CLASS(class, id) \ +static inline int is_s3c ##class (void) \ +{ \ + return (GET_S3C_CLASS == (id)) ? 1 : 0; \ +} + +#define GET_S3C_SUBCLASS ((system_rev >> 20) & 0xfff) + +#define IS_S3C_SUBCLASS(subclass, id) \ +static inline int is_s3c ##subclass (void) \ +{ \ + return (GET_S3C_SUBCLASS == (id)) ? 1 : 0; \ +} + +IS_S3C_CLASS(24xx, 0x24) +IS_S3C_CLASS(64xx, 0x64) + +IS_S3C_SUBCLASS(241x, 0x241) +IS_S3C_SUBCLASS(244x, 0x244) + +#define cpu_is_s3c24xx() 0 +#define cpu_is_s3c241x() 0 +#define cpu_is_s3c244x() 0 +#define cpu_is_s3c64xx() 0 + +#if defined(CONFIG_ARCH_S3C2410) +# undef cpu_is_s3c24xx +# undef cpu_is_s3c241x +# undef cpu_is_s3c244x +# define cpu_is_s3c24xx() is_s3c24xx() +# define cpu_is_s3c241x() is_s3c241x() +# define cpu_is_s3c244x() is_s3c244x() +#endif + +#if defined(CONFIG_ARCH_S3C64XX) +# undef cpu_is_s3c64xx +# define cpu_is_s3c64xx() is_s3c64xx() +#endif + +/* + * Macros to detect individual cpu types. + * cpu_is_s3c2410(): True for s3c2410 + * cpu_is_s3c2440(): True for s3c2440 + * cpu_is_s3c6400(): True for s3c6400 + * cpu_is_s3c6410(): True for s3c6410 + * + * Exception: + * Store Revision A to 1 + * s3c2410a -> s3c2411 + * s3c2440a -> s3c2441 + */ + +#define GET_S3C_TYPE ((system_rev >> 16) & 0xffff) + +#define IS_S3C_TYPE(type, id) \ +static inline int is_s3c ##type (void) \ +{ \ + return (GET_S3C_TYPE == (id)) ? 1 : 0; \ +} + +IS_S3C_TYPE(2400, 0x2400) +IS_S3C_TYPE(2410, 0x2410) +IS_S3C_TYPE(2410a, 0x2411) +IS_S3C_TYPE(2412, 0x2412) +IS_S3C_TYPE(2440, 0x2440) +IS_S3C_TYPE(2440a, 0x2441) +IS_S3C_TYPE(2442, 0x2442) +IS_S3C_TYPE(2443, 0x2443) +IS_S3C_TYPE(6400, 0x6400) +IS_S3C_TYPE(6410, 0x6410) + +#define cpu_is_s3c2400() 0 +#define cpu_is_s3c2410() 0 +#define cpu_is_s3c2410a() 0 +#define cpu_is_s3c2412() 0 +#define cpu_is_s3c2440() 0 +#define cpu_is_s3c2440a() 0 +#define cpu_is_s3c2442() 0 +#define cpu_is_s3c2443() 0 +#define cpu_is_s3c6400() 0 +#define cpu_is_s3c6410() 0 + +#if defined(CONFIG_ARCH_S3C2410) +# undef cpu_is_s3c2400 +# define cpu_is_s3c2400() is_s3c2400() +#endif + +#if defined(CONFIG_CPU_S3C2410) +# undef cpu_is_s3c2410 +# undef cpu_is_s3c2410a +# define cpu_is_s3c2410() is_s3c2410() +# define cpu_is_s3c2410a() is_s3c2410a() +#endif + +#if defined(CONFIG_CPU_S3C2412) +# undef cpu_is_s3c2412 +# define cpu_is_s3c2412() is_s3c2412() +#endif + +#if defined(CONFIG_CPU_S3C2440) +# undef cpu_is_s3c2440 +# undef cpu_is_s3c2440a +# define cpu_is_s3c2440() is_s3c2440() +# define cpu_is_s3c2440a() is_s3c2440a() +#endif + +#if defined(CONFIG_CPU_S3C2442) +# undef cpu_is_s3c2442 +# define cpu_is_s3c2442() is_s3c2442() +#endif + +#if defined(CONFIG_CPU_S3C2443) +# undef cpu_is_s3c2443 +# define cpu_is_s3c2443() is_s3c2443() +#endif + +#if defined(CONFIG_ARCH_S3C64XX) +# undef cpu_is_s3c6400 +# undef cpu_is_s3c6410 +# define cpu_is_s3c6400() is_s3c6400() +# define cpu_is_s3c6410() is_s3c6410() +#endif + +#endif --- a/arch/arm/plat-s3c/include/plat/devs.h +++ b/arch/arm/plat-s3c/include/plat/devs.h @@ -16,6 +16,10 @@ struct s3c24xx_uart_resources { unsigned long nr_resources; }; +struct s3c_plat_otg_data { + int phyclk; +}; + extern struct s3c24xx_uart_resources s3c2410_uart_resources[]; extern struct s3c24xx_uart_resources s3c64xx_uart_resources[]; --- a/arch/arm/plat-s3c/include/plat/gpio-core.h +++ b/arch/arm/plat-s3c/include/plat/gpio-core.h @@ -20,6 +20,19 @@ * specific code. */ +struct s3c_gpio_chip; + +/** + * struct s3c_gpio_pm - power management (suspend/resume) information + * @save: Routine to save the state of the GPIO block + * @resume: Routine to resume the GPIO block. + */ +struct s3c_gpio_pm { + void (*save)(struct s3c_gpio_chip *chip); + void (*resume)(struct s3c_gpio_chip *chip); +}; + + struct s3c_gpio_cfg; /** @@ -27,6 +40,7 @@ struct s3c_gpio_cfg; * @chip: The chip structure to be exported via gpiolib. * @base: The base pointer to the gpio configuration registers. * @config: special function and pull-resistor control information. + * @pm_save: Save information for suspend/resume support. * * This wrapper provides the necessary information for the Samsung * specific gpios being registered with gpiolib. @@ -34,7 +48,11 @@ struct s3c_gpio_cfg; struct s3c_gpio_chip { struct gpio_chip chip; struct s3c_gpio_cfg *config; + struct s3c_gpio_pm *pm; void __iomem *base; +#ifdef CONFIG_PM + u32 pm_save[4]; +#endif }; static inline struct s3c_gpio_chip *to_s3c_gpio(struct gpio_chip *gpc) @@ -75,3 +93,16 @@ static inline struct s3c_gpio_chip *s3c_ static inline void s3c_gpiolib_track(struct s3c_gpio_chip *chip) { } #endif + +#ifdef CONFIG_PM +extern struct s3c_gpio_pm s3c_gpio_pm_1bit; +extern struct s3c_gpio_pm s3c_gpio_pm_2bit; +extern struct s3c_gpio_pm s3c_gpio_pm_4bit; +#define __gpio_pm(x) x +#else +#define s3c_gpio_pm_1bit NULL +#define s3c_gpio_pm_2bit NULL +#define s3c_gpio_pm_4bit NULL +#define __gpio_pm(x) NULL + +#endif /* CONFIG_PM */ --- a/arch/arm/plat-s3c/include/plat/map-base.h +++ b/arch/arm/plat-s3c/include/plat/map-base.h @@ -36,5 +36,7 @@ #define S3C_VA_TIMER S3C_ADDR(0x00300000) /* timer block */ #define S3C_VA_WATCHDOG S3C_ADDR(0x00400000) /* watchdog */ #define S3C_VA_UART S3C_ADDR(0x01000000) /* UART */ +#define S3C_VA_OTG S3C_ADDR(0x03900000) /* OTG */ +#define S3C_VA_OTGSFR S3C_ADDR(0x03a00000) /* OTGSFR */ #endif /* __ASM_PLAT_MAP_H */ --- a/arch/arm/plat-s3c/include/plat/nand.h +++ b/arch/arm/plat-s3c/include/plat/nand.h @@ -21,11 +21,14 @@ * partitions = mtd partition list */ +#define S3C2410_NAND_BBT 0x0001 + struct s3c2410_nand_set { unsigned int disable_ecc : 1; int nr_chips; int nr_partitions; + unsigned int flags; char *name; int *nr_map; struct mtd_partition *partitions; @@ -44,6 +47,9 @@ struct s3c2410_platform_nand { int nr_sets; struct s3c2410_nand_set *sets; + /* force software_ecc at runtime */ + int software_ecc; + void (*select_chip)(struct s3c2410_nand_set *, int chip); }; --- a/arch/arm/plat-s3c/include/plat/pm.h +++ b/arch/arm/plat-s3c/include/plat/pm.h @@ -9,6 +9,8 @@ * published by the Free Software Foundation. */ +#include + /* s3c_pm_init * * called from board at initialisation time to setup the power @@ -44,6 +46,8 @@ extern void (*pm_cpu_sleep)(void); extern unsigned long s3c_pm_flags; +extern unsigned char pm_uart_udivslot; /* true to save UART UDIVSLOT */ + /* from sleep.S */ extern int s3c_cpu_save(unsigned long *saveblk); @@ -88,6 +92,7 @@ struct pm_uart_save { u32 ufcon; u32 umcon; u32 ubrdiv; + u32 udivslot; }; /* helper functions to save/restore lists of registers. */ --- a/arch/arm/plat-s3c/include/plat/sdhci.h +++ b/arch/arm/plat-s3c/include/plat/sdhci.h @@ -29,6 +29,7 @@ struct mmc_ios; * is necessary the controllers and/or GPIO blocks require the * changing of driver-strength and other controls dependant on * the card and speed of operation. + * sdhci_host: Pointer kept during init, allows presence change notification * * Initialisation data specific to either the machine or the platform * for the device driver to use or call-back when configuring gpio or @@ -45,8 +46,11 @@ struct s3c_sdhci_platdata { void __iomem *regbase, struct mmc_ios *ios, struct mmc_card *card); + struct sdhci_host * sdhci_host; }; +extern void sdhci_s3c_force_presence_change(struct platform_device *pdev); + /** * s3c_sdhci0_set_platdata - Set platform data for S3C SDHCI device. * @pd: Platform data to register to device. --- a/arch/arm/plat-s3c/init.c +++ b/arch/arm/plat-s3c/init.c @@ -31,6 +31,34 @@ static struct cpu_table *cpu; +static void __init set_system_rev(unsigned int idcode) +{ + /* + * system_rev encoding is as follows + * system_rev & 0xff000000 -> S3C Class (24xx/64xx) + * system_rev & 0xfff00000 -> S3C Sub Class (241x/244x) + * system_rev & 0xffff0000 -> S3C Type (2410/2440/6400/6410) + * + * Remaining[15:0] are preserved from the value set by ATAG + * + * Exception: + * Store Revision A to 1 such as + * s3c2410A to s3c2411 + * s3c2440A to s3c2441 + */ + + system_rev &= 0xffff; + system_rev |= (idcode & 0x0ffff000) << 4; + + if (idcode == 0x32410002 || idcode == 0x32440001) + system_rev |= (0x1 << 16); + if (idcode == 0x32440aaa /* s3c2442 */ + || idcode == 0x32440aab) /* s3c2442b */ + system_rev |= (0x2 << 16); + if (idcode == 0x0) /* s3c2400 */ + system_rev |= (0x2400 << 16); +} + static struct cpu_table * __init s3c_lookup_cpu(unsigned long idcode, struct cpu_table *tab, unsigned int count) @@ -53,6 +81,8 @@ void __init s3c_init_cpu(unsigned long i panic("Unknown S3C24XX CPU"); } + set_system_rev(idcode); + printk("CPU %s (id 0x%08lx)\n", cpu->name, idcode); if (cpu->map_io == NULL || cpu->init == NULL) { --- a/arch/arm/plat-s3c/Makefile +++ b/arch/arm/plat-s3c/Makefile @@ -21,6 +21,7 @@ obj-y += gpio-config.o # PM support obj-$(CONFIG_PM) += pm.o +obj-$(CONFIG_PM) += pm-gpio.o obj-$(CONFIG_S3C2410_PM_CHECK) += pm-check.o # devices --- a/arch/arm/plat-s3c/pm.c +++ b/arch/arm/plat-s3c/pm.c @@ -21,11 +21,10 @@ #include #include +#include #include #include -#include -#include #include #include @@ -70,6 +69,8 @@ static inline void s3c_pm_debug_init(voi /* Save the UART configurations if we are configured for debug. */ +unsigned char pm_uart_udivslot; + #ifdef CONFIG_S3C2410_PM_DEBUG struct pm_uart_save uart_save[CONFIG_SERIAL_SAMSUNG_UARTS]; @@ -83,6 +84,12 @@ static void s3c_pm_save_uart(unsigned in save->ufcon = __raw_readl(regs + S3C2410_UFCON); save->umcon = __raw_readl(regs + S3C2410_UMCON); save->ubrdiv = __raw_readl(regs + S3C2410_UBRDIV); + + if (pm_uart_udivslot) + save->udivslot = __raw_readl(regs + S3C2443_DIVSLOT); + + S3C_PMDBG("UART[%d]: ULCON=%04x, UCON=%04x, UFCON=%04x, UBRDIV=%04x\n", + uart, save->ulcon, save->ucon, save->ufcon, save->ubrdiv); } static void s3c_pm_save_uarts(void) @@ -98,11 +105,16 @@ static void s3c_pm_restore_uart(unsigned { void __iomem *regs = S3C_VA_UARTx(uart); + s3c_pm_arch_update_uart(regs, save); + __raw_writel(save->ulcon, regs + S3C2410_ULCON); __raw_writel(save->ucon, regs + S3C2410_UCON); __raw_writel(save->ufcon, regs + S3C2410_UFCON); __raw_writel(save->umcon, regs + S3C2410_UMCON); __raw_writel(save->ubrdiv, regs + S3C2410_UBRDIV); + + if (pm_uart_udivslot) + __raw_writel(save->udivslot, regs + S3C2443_DIVSLOT); } static void s3c_pm_restore_uarts(void) @@ -289,11 +301,14 @@ static int s3c_pm_enter(suspend_state_t s3c_pm_arch_stop_clocks(); - /* s3c_cpu_save will also act as our return point from when - * we resume as it saves its own register state and restores it - * during the resume. */ - - s3c_cpu_save(regs_save); + /* s3c2410_cpu_save will also act as our return point from when + * we resume as it saves its own register state, so use the return + * code to differentiate return from save and return from sleep */ + + if (s3c_cpu_save(regs_save) == 0) { + flush_cache_all(); + pm_cpu_sleep(); + } /* restore the cpu state using the kernel's cpu init code. */ --- /dev/null +++ b/arch/arm/plat-s3c/pm-gpio.c @@ -0,0 +1,378 @@ +/* linux/arch/arm/plat-s3c/pm-gpio.c + * + * Copyright 2008 Openmoko, Inc. + * Copyright 2008 Simtec Electronics + * Ben Dooks + * http://armlinux.simtec.co.uk/ + * + * S3C series GPIO PM code + * + * This program 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 +#include +#include +#include + +#include +#include + +/* PM GPIO helpers */ + +#define OFFS_CON (0x00) +#define OFFS_DAT (0X04) +#define OFFS_UP (0X08) + +static void s3c_gpio_pm_1bit_save(struct s3c_gpio_chip *chip) +{ + chip->pm_save[0] = __raw_readl(chip->base + OFFS_CON); + chip->pm_save[1] = __raw_readl(chip->base + OFFS_DAT); +} + +static void s3c_gpio_pm_1bit_resume(struct s3c_gpio_chip *chip) +{ + void __iomem *base = chip->base; + u32 old_gpcon = __raw_readl(base + OFFS_CON); + u32 old_gpdat = __raw_readl(base + OFFS_DAT); + u32 gps_gpcon = chip->pm_save[0]; + u32 gps_gpdat = chip->pm_save[1]; + u32 gpcon; + + /* GPACON only has one bit per control / data and no PULLUPs. + * GPACON[x] = 0 => Output, 1 => SFN */ + + /* first set all SFN bits to SFN */ + + gpcon = old_gpcon | gps_gpcon; + __raw_writel(gpcon, base + OFFS_CON); + + /* now set all the other bits */ + + __raw_writel(gps_gpdat, base + OFFS_DAT); + __raw_writel(gps_gpcon, base + OFFS_CON); + + S3C_PMDBG("%s: CON %08x => %08x, DAT %08x => %08x\n", + chip->chip.label, old_gpcon, gps_gpcon, old_gpdat, gps_gpdat); +} + +struct s3c_gpio_pm s3c_gpio_pm_1bit = { + .save = s3c_gpio_pm_1bit_save, + .resume = s3c_gpio_pm_1bit_resume, +}; + +static void s3c_gpio_pm_2bit_save(struct s3c_gpio_chip *chip) +{ + chip->pm_save[0] = __raw_readl(chip->base + OFFS_CON); + chip->pm_save[1] = __raw_readl(chip->base + OFFS_DAT); + chip->pm_save[2] = __raw_readl(chip->base + OFFS_UP); +} + +/* Test whether the given masked+shifted bits of an GPIO configuration + * are one of the SFN (special function) modes. */ + +static inline int is_sfn(unsigned long con) +{ + return con >= 2; +} + +/* Test if the given masked+shifted GPIO configuration is an input */ + +static inline int is_in(unsigned long con) +{ + return con == 0; +} + +/* Test if the given masked+shifted GPIO configuration is an output */ + +static inline int is_out(unsigned long con) +{ + return con == 1; +} + +/** + * s3c_gpio_pm_2bit_resume() - restore the given GPIO bank + * @chip: The chip information to resume. + * + * Restore one of the GPIO banks that was saved during suspend. This is + * not as simple as once thought, due to the possibility of glitches + * from the order that the CON and DAT registers are set in. + * + * The three states the pin can be are {IN,OUT,SFN} which gives us 9 + * combinations of changes to check. Three of these, if the pin stays + * in the same configuration can be discounted. This leaves us with + * the following: + * + * { IN => OUT } Change DAT first + * { IN => SFN } Change CON first + * { OUT => SFN } Change CON first, so new data will not glitch + * { OUT => IN } Change CON first, so new data will not glitch + * { SFN => IN } Change CON first + * { SFN => OUT } Change DAT first, so new data will not glitch [1] + * + * We do not currently deal with the UP registers as these control + * weak resistors, so a small delay in change should not need to bring + * these into the calculations. + * + * [1] this assumes that writing to a pin DAT whilst in SFN will set the + * state for when it is next output. + */ +static void s3c_gpio_pm_2bit_resume(struct s3c_gpio_chip *chip) +{ + void __iomem *base = chip->base; + u32 old_gpcon = __raw_readl(base + OFFS_CON); + u32 old_gpdat = __raw_readl(base + OFFS_DAT); + u32 gps_gpcon = chip->pm_save[0]; + u32 gps_gpdat = chip->pm_save[1]; + u32 gpcon, old, new, mask; + u32 change_mask = 0x0; + int nr; + + /* restore GPIO pull-up settings */ + __raw_writel(chip->pm_save[2], base + OFFS_UP); + + /* Create a change_mask of all the items that need to have + * their CON value changed before their DAT value, so that + * we minimise the work between the two settings. + */ + + for (nr = 0, mask = 0x03; nr < 32; nr += 2, mask <<= 2) { + old = (old_gpcon & mask) >> nr; + new = (gps_gpcon & mask) >> nr; + + /* If there is no change, then skip */ + + if (old == new) + continue; + + /* If both are special function, then skip */ + + if (is_sfn(old) && is_sfn(new)) + continue; + + /* Change is IN => OUT, do not change now */ + + if (is_in(old) && is_out(new)) + continue; + + /* Change is SFN => OUT, do not change now */ + + if (is_sfn(old) && is_out(new)) + continue; + + /* We should now be at the case of IN=>SFN, + * OUT=>SFN, OUT=>IN, SFN=>IN. */ + + change_mask |= mask; + } + + + /* Write the new CON settings */ + + gpcon = old_gpcon & ~change_mask; + gpcon |= gps_gpcon & change_mask; + + __raw_writel(gpcon, base + OFFS_CON); + + /* Now change any items that require DAT,CON */ + + __raw_writel(gps_gpdat, base + OFFS_DAT); + __raw_writel(gps_gpcon, base + OFFS_CON); + + S3C_PMDBG("%s: CON %08x => %08x, DAT %08x => %08x\n", + chip->chip.label, old_gpcon, gps_gpcon, old_gpdat, gps_gpdat); +} + +struct s3c_gpio_pm s3c_gpio_pm_2bit = { + .save = s3c_gpio_pm_2bit_save, + .resume = s3c_gpio_pm_2bit_resume, +}; + +#ifdef CONFIG_ARCH_S3C64XX +static void s3c_gpio_pm_4bit_save(struct s3c_gpio_chip *chip) +{ + chip->pm_save[1] = __raw_readl(chip->base + OFFS_CON); + chip->pm_save[2] = __raw_readl(chip->base + OFFS_DAT); + chip->pm_save[3] = __raw_readl(chip->base + OFFS_UP); + + if (chip->chip.ngpio > 8) + chip->pm_save[0] = __raw_readl(chip->base - 4); +} + +static u32 s3c_gpio_pm_4bit_mask(u32 old_gpcon, u32 gps_gpcon) +{ + u32 old, new, mask; + u32 change_mask = 0x0; + int nr; + + for (nr = 0, mask = 0x0f; nr < 16; nr += 4, mask <<= 4) { + old = (old_gpcon & mask) >> nr; + new = (gps_gpcon & mask) >> nr; + + /* If there is no change, then skip */ + + if (old == new) + continue; + + /* If both are special function, then skip */ + + if (is_sfn(old) && is_sfn(new)) + continue; + + /* Change is IN => OUT, do not change now */ + + if (is_in(old) && is_out(new)) + continue; + + /* Change is SFN => OUT, do not change now */ + + if (is_sfn(old) && is_out(new)) + continue; + + /* We should now be at the case of IN=>SFN, + * OUT=>SFN, OUT=>IN, SFN=>IN. */ + + change_mask |= mask; + } + + return change_mask; +} + +static void s3c_gpio_pm_4bit_con(struct s3c_gpio_chip *chip, int index) +{ + void __iomem *con = chip->base + (index * 4); + u32 old_gpcon = __raw_readl(con); + u32 gps_gpcon = chip->pm_save[index + 1]; + u32 gpcon, mask; + + mask = s3c_gpio_pm_4bit_mask(old_gpcon, gps_gpcon); + + gpcon = old_gpcon & ~mask; + gpcon |= gps_gpcon & mask; + + __raw_writel(gpcon, con); +} + +static void s3c_gpio_pm_4bit_resume(struct s3c_gpio_chip *chip) +{ + void __iomem *base = chip->base; + u32 old_gpcon[2]; + u32 old_gpdat = __raw_readl(base + OFFS_DAT); + u32 gps_gpdat = chip->pm_save[2]; + + /* First, modify the CON settings */ + + old_gpcon[0] = 0; + old_gpcon[1] = __raw_readl(base + OFFS_CON); + + s3c_gpio_pm_4bit_con(chip, 0); + if (chip->chip.ngpio > 8) { + old_gpcon[0] = __raw_readl(base - 4); + s3c_gpio_pm_4bit_con(chip, -1); + } + + /* Now change the configurations that require DAT,CON */ + + __raw_writel(chip->pm_save[2], base + OFFS_DAT); + __raw_writel(chip->pm_save[1], base + OFFS_CON); + if (chip->chip.ngpio > 8) + __raw_writel(chip->pm_save[0], base - 4); + + __raw_writel(chip->pm_save[2], base + OFFS_DAT); + __raw_writel(chip->pm_save[3], base + OFFS_UP); + + if (chip->chip.ngpio > 8) { + S3C_PMDBG("%s: CON4 %08x,%08x => %08x,%08x, DAT %08x => %08x\n", + chip->chip.label, old_gpcon[0], old_gpcon[1], + __raw_readl(base - 4), + __raw_readl(base + OFFS_CON), + old_gpdat, gps_gpdat); + } else + S3C_PMDBG("%s: CON4 %08x => %08x, DAT %08x => %08x\n", + chip->chip.label, old_gpcon[1], + __raw_readl(base + OFFS_CON), + old_gpdat, gps_gpdat); +} + +struct s3c_gpio_pm s3c_gpio_pm_4bit = { + .save = s3c_gpio_pm_4bit_save, + .resume = s3c_gpio_pm_4bit_resume, +}; +#endif /* CONFIG_ARCH_S3C64XX */ + +/** + * s3c_pm_save_gpio() - save gpio chip data for suspend + * @ourchip: The chip for suspend. + */ +static void s3c_pm_save_gpio(struct s3c_gpio_chip *ourchip) +{ + struct s3c_gpio_pm *pm = ourchip->pm; + + if (pm == NULL || pm->save == NULL) + S3C_PMDBG("%s: no pm for %s\n", __func__, ourchip->chip.label); + else + pm->save(ourchip); +} + +/** + * s3c_pm_save_gpios() - Save the state of the GPIO banks. + * + * For all the GPIO banks, save the state of each one ready for going + * into a suspend mode. + */ +void s3c_pm_save_gpios(void) +{ + struct s3c_gpio_chip *ourchip; + unsigned int gpio_nr; + + for (gpio_nr = 0; gpio_nr < S3C_GPIO_END; gpio_nr++) { + ourchip = s3c_gpiolib_getchip(gpio_nr); + if (!ourchip) + continue; + + s3c_pm_save_gpio(ourchip); + + S3C_PMDBG("%s: save %08x,%08x,%08x,%08x\n", + ourchip->chip.label, + ourchip->pm_save[0], + ourchip->pm_save[1], + ourchip->pm_save[2], + ourchip->pm_save[3]); + + gpio_nr += ourchip->chip.ngpio; + gpio_nr += CONFIG_S3C_GPIO_SPACE; + } +} + +/** + * s3c_pm_resume_gpio() - restore gpio chip data after suspend + * @ourchip: The suspended chip. + */ +static void s3c_pm_resume_gpio(struct s3c_gpio_chip *ourchip) +{ + struct s3c_gpio_pm *pm = ourchip->pm; + + if (pm == NULL || pm->resume == NULL) + S3C_PMDBG("%s: no pm for %s\n", __func__, ourchip->chip.label); + else + pm->resume(ourchip); +} + +void s3c_pm_restore_gpios(void) +{ + struct s3c_gpio_chip *ourchip; + unsigned int gpio_nr; + + for (gpio_nr = 0; gpio_nr < S3C_GPIO_END; gpio_nr++) { + ourchip = s3c_gpiolib_getchip(gpio_nr); + if (!ourchip) + continue; + + s3c_pm_resume_gpio(ourchip); + + gpio_nr += ourchip->chip.ngpio; + gpio_nr += CONFIG_S3C_GPIO_SPACE; + } +} --- a/arch/arm/plat-s3c24xx/clock-dclk.c +++ b/arch/arm/plat-s3c24xx/clock-dclk.c @@ -18,6 +18,7 @@ #include #include +#include #include #include --- a/arch/arm/plat-s3c24xx/cpu.c +++ b/arch/arm/plat-s3c24xx/cpu.c @@ -61,6 +61,7 @@ static const char name_s3c2410[] = "S3C static const char name_s3c2412[] = "S3C2412"; static const char name_s3c2440[] = "S3C2440"; static const char name_s3c2442[] = "S3C2442"; +static const char name_s3c2442b[] = "S3C2442B"; static const char name_s3c2443[] = "S3C2443"; static const char name_s3c2410a[] = "S3C2410A"; static const char name_s3c2440a[] = "S3C2440A"; @@ -112,6 +113,15 @@ static struct cpu_table cpu_ids[] __init .name = name_s3c2442 }, { + .idcode = 0x32440aab, + .idmask = 0xffffffff, + .map_io = s3c244x_map_io, + .init_clocks = s3c244x_init_clocks, + .init_uarts = s3c244x_init_uarts, + .init = s3c2442_init, + .name = name_s3c2442b + }, + { .idcode = 0x32412001, .idmask = 0xffffffff, .map_io = s3c2412_map_io, --- a/arch/arm/plat-s3c24xx/gpiolib.c +++ b/arch/arm/plat-s3c24xx/gpiolib.c @@ -19,9 +19,10 @@ #include #include -#include +#include #include #include +#include #include @@ -78,6 +79,7 @@ static int s3c24xx_gpiolib_bankg_toirq(s struct s3c_gpio_chip s3c24xx_gpios[] = { [0] = { .base = S3C24XX_GPIO_BASE(S3C2410_GPA0), + .pm = __gpio_pm(&s3c_gpio_pm_1bit), .chip = { .base = S3C2410_GPA0, .owner = THIS_MODULE, @@ -89,6 +91,7 @@ struct s3c_gpio_chip s3c24xx_gpios[] = { }, [1] = { .base = S3C24XX_GPIO_BASE(S3C2410_GPB0), + .pm = __gpio_pm(&s3c_gpio_pm_2bit), .chip = { .base = S3C2410_GPB0, .owner = THIS_MODULE, @@ -98,6 +101,7 @@ struct s3c_gpio_chip s3c24xx_gpios[] = { }, [2] = { .base = S3C24XX_GPIO_BASE(S3C2410_GPC0), + .pm = __gpio_pm(&s3c_gpio_pm_2bit), .chip = { .base = S3C2410_GPC0, .owner = THIS_MODULE, @@ -107,6 +111,7 @@ struct s3c_gpio_chip s3c24xx_gpios[] = { }, [3] = { .base = S3C24XX_GPIO_BASE(S3C2410_GPD0), + .pm = __gpio_pm(&s3c_gpio_pm_2bit), .chip = { .base = S3C2410_GPD0, .owner = THIS_MODULE, @@ -116,6 +121,7 @@ struct s3c_gpio_chip s3c24xx_gpios[] = { }, [4] = { .base = S3C24XX_GPIO_BASE(S3C2410_GPE0), + .pm = __gpio_pm(&s3c_gpio_pm_2bit), .chip = { .base = S3C2410_GPE0, .label = "GPIOE", @@ -125,6 +131,7 @@ struct s3c_gpio_chip s3c24xx_gpios[] = { }, [5] = { .base = S3C24XX_GPIO_BASE(S3C2410_GPF0), + .pm = __gpio_pm(&s3c_gpio_pm_2bit), .chip = { .base = S3C2410_GPF0, .owner = THIS_MODULE, @@ -135,12 +142,23 @@ struct s3c_gpio_chip s3c24xx_gpios[] = { }, [6] = { .base = S3C24XX_GPIO_BASE(S3C2410_GPG0), + .pm = __gpio_pm(&s3c_gpio_pm_2bit), .chip = { .base = S3C2410_GPG0, .owner = THIS_MODULE, .label = "GPIOG", - .ngpio = 10, .to_irq = s3c24xx_gpiolib_bankg_toirq, + .ngpio = 16, + }, + }, + [7] = { + .base = S3C24XX_GPIO_BASE(S3C2410_GPH0), + .pm = __gpio_pm(&s3c_gpio_pm_2bit), + .chip = { + .base = S3C2410_GPH0, + .owner = THIS_MODULE, + .label = "GPIOH", + .ngpio = 11, }, }, }; --- a/arch/arm/plat-s3c24xx/include/plat/pm-core.h +++ b/arch/arm/plat-s3c24xx/include/plat/pm-core.h @@ -57,3 +57,8 @@ static inline void s3c_pm_arch_show_resu s3c_pm_show_resume_irqs(IRQ_EINT4-4, __raw_readl(S3C2410_EINTPEND), s3c_irqwake_eintmask); } + +static inline void s3c_pm_arch_update_uart(void __iomem *regs, + struct pm_uart_save *save) +{ +} --- a/arch/arm/plat-s3c24xx/irq-pm.c +++ b/arch/arm/plat-s3c24xx/irq-pm.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -80,7 +81,9 @@ int s3c24xx_irq_suspend(struct sys_devic int s3c24xx_irq_resume(struct sys_device *dev) { - unsigned int i; + unsigned int i, irq; + unsigned long eintpnd; + struct irq_desc *desc; for (i = 0; i < ARRAY_SIZE(save_extint); i++) __raw_writel(save_extint[i], S3C24XX_EXTINT0 + (i*4)); @@ -91,5 +94,25 @@ int s3c24xx_irq_resume(struct sys_device s3c_pm_do_restore(irq_save, ARRAY_SIZE(irq_save)); __raw_writel(save_eintmask, S3C24XX_EINTMASK); + /* + * ACK those interrupts which are now masked and pending. + * Level interrupts if not ACKed here, create an interrupt storm + * because they are not handled at all. + */ + + eintpnd = __raw_readl(S3C24XX_EINTPEND); + + eintpnd &= save_eintmask; + eintpnd &= ~0xff; /* ignore lower irqs */ + + while (eintpnd) { + irq = __ffs(eintpnd); + eintpnd &= ~(1 << irq); + + irq += (IRQ_EINT4 - 4); + desc = irq_to_desc(irq); + desc->chip->ack(irq); + } + return 0; } --- a/arch/arm/plat-s3c24xx/pm.c +++ b/arch/arm/plat-s3c24xx/pm.c @@ -39,6 +39,7 @@ #include #include #include +#include #include @@ -75,43 +76,10 @@ static struct sleep_save core_save[] = { SAVE_ITEM(S3C2410_CLKSLOW), }; -static struct gpio_sleep { - void __iomem *base; - unsigned int gpcon; - unsigned int gpdat; - unsigned int gpup; -} gpio_save[] = { - [0] = { - .base = S3C2410_GPACON, - }, - [1] = { - .base = S3C2410_GPBCON, - }, - [2] = { - .base = S3C2410_GPCCON, - }, - [3] = { - .base = S3C2410_GPDCON, - }, - [4] = { - .base = S3C2410_GPECON, - }, - [5] = { - .base = S3C2410_GPFCON, - }, - [6] = { - .base = S3C2410_GPGCON, - }, - [7] = { - .base = S3C2410_GPHCON, - }, -}; - static struct sleep_save misc_save[] = { SAVE_ITEM(S3C2410_DCLKCON), }; - /* s3c_pm_check_resume_pin * * check to see if the pin is configured correctly for sleep mode, and @@ -165,186 +133,6 @@ void s3c_pm_configure_extint(void) } } -/* offsets for CON/DAT/UP registers */ - -#define OFFS_CON (S3C2410_GPACON - S3C2410_GPACON) -#define OFFS_DAT (S3C2410_GPADAT - S3C2410_GPACON) -#define OFFS_UP (S3C2410_GPBUP - S3C2410_GPBCON) - -/* s3c_pm_save_gpios() - * - * Save the state of the GPIOs - */ - -void s3c_pm_save_gpios(void) -{ - struct gpio_sleep *gps = gpio_save; - unsigned int gpio; - - for (gpio = 0; gpio < ARRAY_SIZE(gpio_save); gpio++, gps++) { - void __iomem *base = gps->base; - - gps->gpcon = __raw_readl(base + OFFS_CON); - gps->gpdat = __raw_readl(base + OFFS_DAT); - - if (gpio > 0) - gps->gpup = __raw_readl(base + OFFS_UP); - - } -} - -/* Test whether the given masked+shifted bits of an GPIO configuration - * are one of the SFN (special function) modes. */ - -static inline int is_sfn(unsigned long con) -{ - return (con == 2 || con == 3); -} - -/* Test if the given masked+shifted GPIO configuration is an input */ - -static inline int is_in(unsigned long con) -{ - return con == 0; -} - -/* Test if the given masked+shifted GPIO configuration is an output */ - -static inline int is_out(unsigned long con) -{ - return con == 1; -} - -/** - * s3c2410_pm_restore_gpio() - restore the given GPIO bank - * @index: The number of the GPIO bank being resumed. - * @gps: The sleep confgiuration for the bank. - * - * Restore one of the GPIO banks that was saved during suspend. This is - * not as simple as once thought, due to the possibility of glitches - * from the order that the CON and DAT registers are set in. - * - * The three states the pin can be are {IN,OUT,SFN} which gives us 9 - * combinations of changes to check. Three of these, if the pin stays - * in the same configuration can be discounted. This leaves us with - * the following: - * - * { IN => OUT } Change DAT first - * { IN => SFN } Change CON first - * { OUT => SFN } Change CON first, so new data will not glitch - * { OUT => IN } Change CON first, so new data will not glitch - * { SFN => IN } Change CON first - * { SFN => OUT } Change DAT first, so new data will not glitch [1] - * - * We do not currently deal with the UP registers as these control - * weak resistors, so a small delay in change should not need to bring - * these into the calculations. - * - * [1] this assumes that writing to a pin DAT whilst in SFN will set the - * state for when it is next output. - */ - -static void s3c2410_pm_restore_gpio(int index, struct gpio_sleep *gps) -{ - void __iomem *base = gps->base; - unsigned long gps_gpcon = gps->gpcon; - unsigned long gps_gpdat = gps->gpdat; - unsigned long old_gpcon; - unsigned long old_gpdat; - unsigned long old_gpup = 0x0; - unsigned long gpcon; - int nr; - - old_gpcon = __raw_readl(base + OFFS_CON); - old_gpdat = __raw_readl(base + OFFS_DAT); - - if (base == S3C2410_GPACON) { - /* GPACON only has one bit per control / data and no PULLUPs. - * GPACON[x] = 0 => Output, 1 => SFN */ - - /* first set all SFN bits to SFN */ - - gpcon = old_gpcon | gps->gpcon; - __raw_writel(gpcon, base + OFFS_CON); - - /* now set all the other bits */ - - __raw_writel(gps_gpdat, base + OFFS_DAT); - __raw_writel(gps_gpcon, base + OFFS_CON); - } else { - unsigned long old, new, mask; - unsigned long change_mask = 0x0; - - old_gpup = __raw_readl(base + OFFS_UP); - - /* Create a change_mask of all the items that need to have - * their CON value changed before their DAT value, so that - * we minimise the work between the two settings. - */ - - for (nr = 0, mask = 0x03; nr < 32; nr += 2, mask <<= 2) { - old = (old_gpcon & mask) >> nr; - new = (gps_gpcon & mask) >> nr; - - /* If there is no change, then skip */ - - if (old == new) - continue; - - /* If both are special function, then skip */ - - if (is_sfn(old) && is_sfn(new)) - continue; - - /* Change is IN => OUT, do not change now */ - - if (is_in(old) && is_out(new)) - continue; - - /* Change is SFN => OUT, do not change now */ - - if (is_sfn(old) && is_out(new)) - continue; - - /* We should now be at the case of IN=>SFN, - * OUT=>SFN, OUT=>IN, SFN=>IN. */ - - change_mask |= mask; - } - - /* Write the new CON settings */ - - gpcon = old_gpcon & ~change_mask; - gpcon |= gps_gpcon & change_mask; - - __raw_writel(gpcon, base + OFFS_CON); - - /* Now change any items that require DAT,CON */ - - __raw_writel(gps_gpdat, base + OFFS_DAT); - __raw_writel(gps_gpcon, base + OFFS_CON); - __raw_writel(gps->gpup, base + OFFS_UP); - } - - S3C_PMDBG("GPIO[%d] CON %08lx => %08lx, DAT %08lx => %08lx\n", - index, old_gpcon, gps_gpcon, old_gpdat, gps_gpdat); -} - - -/** s3c2410_pm_restore_gpios() - * - * Restore the state of the GPIOs - */ - -void s3c_pm_restore_gpios(void) -{ - struct gpio_sleep *gps = gpio_save; - int gpio; - - for (gpio = 0; gpio < ARRAY_SIZE(gpio_save); gpio++, gps++) { - s3c2410_pm_restore_gpio(gpio, gps); - } -} void s3c_pm_restore_core(void) { --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -37,13 +37,6 @@ config MMC_SDHCI If unsure, say N. -config MMC_SDHCI_IO_ACCESSORS - bool - depends on MMC_SDHCI - help - This is silent Kconfig symbol that is selected by the drivers that - need to overwrite SDHCI IO memory accessors. - config MMC_SDHCI_PCI tristate "SDHCI support on PCI bus" depends on MMC_SDHCI && PCI @@ -55,6 +48,18 @@ config MMC_SDHCI_PCI If unsure, say N. +config MMC_SDHCI_S3C + tristate "SDHCI support on Samsung S3C SoC" + depends on MMC_SDHCI && PLAT_S3C24XX + help + This selects the Secure Digital Host Controller Interface (SDHCI) + often referrered to as the HSMMC block in some of the Samsung S3C + range of SoC. + + If you have a controller with this interface, say Y or M here. + + If unsure, say N. + config MMC_RICOH_MMC tristate "Ricoh MMC Controller Disabler (EXPERIMENTAL)" depends on MMC_SDHCI_PCI @@ -72,17 +77,6 @@ config MMC_RICOH_MMC If unsure, say Y. -config MMC_SDHCI_OF - tristate "SDHCI support on OpenFirmware platforms" - depends on MMC_SDHCI && PPC_OF - select MMC_SDHCI_IO_ACCESSORS - help - This selects the OF support for Secure Digital Host Controller - Interfaces. So far, only the Freescale eSDHC controller is known - to exist on OF platforms. - - If unsure, say N. - config MMC_OMAP tristate "TI OMAP Multimedia Card Interface support" depends on ARCH_OMAP @@ -163,16 +157,6 @@ config MMC_IMX If unsure, say N. -config MMC_MXC - tristate "Freescale i.MX2/3 Multimedia Card Interface support" - depends on ARCH_MXC - help - This selects the Freescale i.MX2/3 Multimedia card Interface. - If you have a i.MX platform with a Multimedia Card slot, - say Y or M here. - - If unsure, say N. - config MMC_TIFM_SD tristate "TI Flash Media MMC/SD Interface support (EXPERIMENTAL)" depends on EXPERIMENTAL && PCI --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -9,11 +9,10 @@ endif obj-$(CONFIG_MMC_ARMMMCI) += mmci.o obj-$(CONFIG_MMC_PXA) += pxamci.o obj-$(CONFIG_MMC_IMX) += imxmmc.o -obj-$(CONFIG_MMC_MXC) += mxcmmc.o obj-$(CONFIG_MMC_SDHCI) += sdhci.o obj-$(CONFIG_MMC_SDHCI_PCI) += sdhci-pci.o +obj-$(CONFIG_MMC_SDHCI_S3C) += sdhci-s3c.o obj-$(CONFIG_MMC_RICOH_MMC) += ricoh_mmc.o -obj-$(CONFIG_MMC_SDHCI_OF) += sdhci-of.o obj-$(CONFIG_MMC_WBSD) += wbsd.o obj-$(CONFIG_MMC_AU1X) += au1xmmc.o obj-$(CONFIG_MMC_OMAP) += omap.o @@ -21,7 +20,6 @@ obj-$(CONFIG_MMC_OMAP_HS) += omap_hsmmc. obj-$(CONFIG_MMC_AT91) += at91_mci.o obj-$(CONFIG_MMC_ATMELMCI) += atmel-mci.o obj-$(CONFIG_MMC_TIFM_SD) += tifm_sd.o -obj-$(CONFIG_MMC_MVSDIO) += mvsdio.o obj-$(CONFIG_MMC_SPI) += mmc_spi.o ifeq ($(CONFIG_OF),y) obj-$(CONFIG_MMC_SPI) += of_mmc_spi.o --- a/drivers/mmc/host/s3cmci.c +++ b/drivers/mmc/host/s3cmci.c @@ -2,6 +2,7 @@ * linux/drivers/mmc/s3cmci.h - Samsung S3C MCI driver * * Copyright (C) 2004-2006 maintech GmbH, Thomas Kleffel + * Copyright (C) 2007 Harald Welte * * Current driver maintained by Ben Dooks and Simtec Electronics * Copyright (C) 2008 Simtec Electronics @@ -24,9 +25,18 @@ #include #include +#include #include +#include +#include + +#include +#include +#include +#include + #include "s3cmci.h" #define DRIVER_NAME "s3c-mci" @@ -47,6 +57,9 @@ static const int dbgmap_err = dbg_fail static const int dbgmap_info = dbg_info | dbg_conf; static const int dbgmap_debug = dbg_err | dbg_debug; +static int f_max = -1; /* override maximum frequency limit */ +static int persist; /* keep interface alive across suspend/resume */ + #define dbg(host, channels, args...) \ do { \ if (dbgmap_err & channels) \ @@ -280,8 +293,11 @@ static void do_pio_read(struct s3cmci_ho * an even multiple of 4. */ if (fifo >= host->pio_bytes) fifo = host->pio_bytes; - else + else { fifo -= fifo & 3; + if (!fifo) + break; + } host->pio_bytes -= fifo; host->pio_count += fifo; @@ -329,7 +345,7 @@ static void do_pio_write(struct s3cmci_h to_ptr = host->base + host->sdidata; - while ((fifo = fifo_free(host)) > 3) { + while ((fifo = fifo_free(host))) { if (!host->pio_bytes) { res = get_data_buffer(host, &host->pio_bytes, &host->pio_ptr); @@ -353,8 +369,11 @@ static void do_pio_write(struct s3cmci_h * words, so round down to an even multiple of 4. */ if (fifo >= host->pio_bytes) fifo = host->pio_bytes; - else + else { fifo -= fifo & 3; + if (!fifo) + break; + } host->pio_bytes -= fifo; host->pio_count += fifo; @@ -373,7 +392,6 @@ static void pio_tasklet(unsigned long da { struct s3cmci_host *host = (struct s3cmci_host *) data; - disable_irq(host->irq); if (host->pio_active == XFER_WRITE) @@ -614,7 +632,6 @@ irq_out: spin_unlock_irqrestore(&host->complete_lock, iflags); return IRQ_HANDLED; - } /* @@ -789,11 +806,11 @@ static void s3cmci_dma_setup(struct s3cm last_source = source; - s3c2410_dma_devconfig(host->dma, source, 3, + s3c2410_dma_devconfig(host->dma, source, host->mem->start + host->sdidata); if (!setup_ok) { - s3c2410_dma_config(host->dma, 4, 0); + s3c2410_dma_config(host->dma, 4); s3c2410_dma_set_buffdone_fn(host->dma, s3cmci_dma_done_callback); s3c2410_dma_setflags(host->dma, S3C2410_DMAF_AUTOSTART); @@ -1026,6 +1043,7 @@ static void s3cmci_send_request(struct m dbg(host, dbg_err, "data prepare error %d\n", res); cmd->error = res; cmd->data->error = res; + cmd->data->error = -EIO; mmc_request_done(mmc, mrq); return; @@ -1263,10 +1281,8 @@ static int __devinit s3cmci_probe(struct host->is2440 = is2440; host->pdata = pdev->dev.platform_data; - if (!host->pdata) { - pdev->dev.platform_data = &s3cmci_def_pdata; + if (!host->pdata) host->pdata = &s3cmci_def_pdata; - } spin_lock_init(&host->complete_lock); tasklet_init(&host->pio_tasklet, pio_tasklet, (unsigned long) host); @@ -1379,6 +1395,18 @@ static int __devinit s3cmci_probe(struct mmc->f_min = host->clk_rate / (host->clk_div * 256); mmc->f_max = host->clk_rate / host->clk_div; + if (f_max >= 0) { + unsigned f = f_max; + + if (f < mmc->f_min) + f = mmc->f_min; + if (mmc->f_max > f) { + dev_info(&pdev->dev, "f_max lowered from %u to %u Hz\n", + mmc->f_max, f); + mmc->f_max = f; + } + } + if (host->pdata->ocr_avail) mmc->ocr_avail = host->pdata->ocr_avail; @@ -1491,18 +1519,60 @@ static int __devinit s3cmci_2440_probe(s #ifdef CONFIG_PM +static int save_regs(struct mmc_host *mmc) +{ + struct s3cmci_host *host = mmc_priv(mmc); + unsigned long flags; + unsigned from; + u32 *to = host->saved; + + mmc_flush_scheduled_work(); + + local_irq_save(flags); + for (from = S3C2410_SDICON; from != S3C2410_SDIIMSK+4; from += 4) + if (from != host->sdidata) + *to++ = readl(host->base + from); + BUG_ON(to-host->saved != ARRAY_SIZE(host->saved)); + local_irq_restore(flags); + + return 0; +} + +static int restore_regs(struct mmc_host *mmc) +{ + struct s3cmci_host *host = mmc_priv(mmc); + unsigned long flags; + unsigned to; + u32 *from = host->saved; + + /* + * Before we begin with the necromancy, make sure we don't + * inadvertently start something we'll regret microseconds later. + */ + from[S3C2410_SDICMDCON - S3C2410_SDICON] = 0; + + local_irq_save(flags); + for (to = S3C2410_SDICON; to != S3C2410_SDIIMSK+4; to += 4) + if (to != host->sdidata) + writel(*from++, host->base + to); + BUG_ON(from-host->saved != ARRAY_SIZE(host->saved)); + local_irq_restore(flags); + + return 0; +} + static int s3cmci_suspend(struct platform_device *dev, pm_message_t state) { struct mmc_host *mmc = platform_get_drvdata(dev); - return mmc_suspend_host(mmc, state); + return persist ? save_regs(mmc) : mmc_suspend_host(mmc, state); } static int s3cmci_resume(struct platform_device *dev) { struct mmc_host *mmc = platform_get_drvdata(dev); - return mmc_resume_host(mmc); + return persist ? restore_regs(mmc) : mmc_resume_host(mmc); } #else /* CONFIG_PM */ @@ -1560,9 +1630,13 @@ static void __exit s3cmci_exit(void) module_init(s3cmci_init); module_exit(s3cmci_exit); +module_param(f_max, int, 0644); +module_param(persist, int, 0644); + MODULE_DESCRIPTION("Samsung S3C MMC/SD Card Interface driver"); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Thomas Kleffel , Ben Dooks "); MODULE_ALIAS("platform:s3c2410-sdi"); MODULE_ALIAS("platform:s3c2412-sdi"); MODULE_ALIAS("platform:s3c2440-sdi"); + --- a/drivers/mmc/host/s3cmci.h +++ b/drivers/mmc/host/s3cmci.h @@ -8,6 +8,10 @@ * published by the Free Software Foundation. */ + +#include +#include + /* FIXME: DMA Resource management ?! */ #define S3CMCI_DMA 0 @@ -68,7 +72,16 @@ struct s3cmci_host { unsigned int ccnt, dcnt; struct tasklet_struct pio_tasklet; + /* + * Here's where we save the registers during suspend. Note that we skip + * SDIDATA, which is at different positions on 2410 and 2440, so + * there's no "+1" in the array size. + */ + u32 saved[(S3C2410_SDIIMSK-S3C2410_SDICON)/4]; + #ifdef CONFIG_CPU_FREQ struct notifier_block freq_transition; #endif + + struct regulator *regulator; }; --- /dev/null +++ b/drivers/mmc/host/sdhci-s3c.c @@ -0,0 +1,419 @@ +/* linux/drivers/mmc/host/sdhci-s3c.c + * + * Copyright 2008 Openmoko Inc. + * Copyright 2008 Simtec Electronics + * Ben Dooks + * http://armlinux.simtec.co.uk/ + * + * SDHCI (HSMMC) support for Samsung SoC + * + * This program 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 +#include +#include +#include +#include + +#include + +#include +#include + +#include "sdhci.h" + +#define MAX_BUS_CLK (4) + +struct sdhci_s3c { + struct sdhci_host *host; + struct platform_device *pdev; + struct resource *ioarea; + struct s3c_sdhci_platdata *pdata; + unsigned int cur_clk; + + struct clk *clk_io; /* clock for io bus */ + struct clk *clk_bus[MAX_BUS_CLK]; +}; + +static inline struct sdhci_s3c *to_s3c(struct sdhci_host *host) +{ + return sdhci_priv(host); +} + +static u32 get_curclk(u32 ctrl2) +{ + ctrl2 &= S3C_SDHCI_CTRL2_SELBASECLK_MASK; + ctrl2 >>= S3C_SDHCI_CTRL2_SELBASECLK_SHIFT; + + return ctrl2; +} + +static void sdhci_s3c_check_sclk(struct sdhci_host *host) +{ + struct sdhci_s3c *ourhost = to_s3c(host); + u32 tmp = readl(host->ioaddr + S3C_SDHCI_CONTROL2); + + if (get_curclk(tmp) != ourhost->cur_clk) { + dev_dbg(&ourhost->pdev->dev, "restored ctrl2 clock setting\n"); + + tmp &= ~S3C_SDHCI_CTRL2_SELBASECLK_MASK; + tmp |= ourhost->cur_clk << S3C_SDHCI_CTRL2_SELBASECLK_SHIFT; + writel(tmp, host->ioaddr + 0x80); + } +} + +static unsigned int sdhci_s3c_get_max_clk(struct sdhci_host *host) +{ + struct sdhci_s3c *ourhost = to_s3c(host); + struct clk *busclk; + unsigned int rate, max; + int clk; + + /* note, a reset will reset the clock source */ + + sdhci_s3c_check_sclk(host); + + for (max = 0, clk = 0; clk < MAX_BUS_CLK; clk++) { + busclk = ourhost->clk_bus[clk]; + if (!busclk) + continue; + + rate = clk_get_rate(busclk); + if (rate > max) + max = rate; + } + + return max; +} + +static unsigned int sdhci_s3c_get_timeout_clk(struct sdhci_host *host) +{ + return sdhci_s3c_get_max_clk(host) / 1000000; +} + +static void sdhci_s3c_set_ios(struct sdhci_host *host, + struct mmc_ios *ios) +{ + struct sdhci_s3c *ourhost = to_s3c(host); + struct s3c_sdhci_platdata *pdata = ourhost->pdata; + int width; + + sdhci_s3c_check_sclk(host); + + if (ios->power_mode != MMC_POWER_OFF) { + switch (ios->bus_width) { + case MMC_BUS_WIDTH_4: + width = 4; + break; + case MMC_BUS_WIDTH_1: + width = 1; + break; + default: + BUG(); + } + + if (pdata->cfg_gpio) + pdata->cfg_gpio(ourhost->pdev, width); + } + + if (pdata->cfg_card) + pdata->cfg_card(ourhost->pdev, host->ioaddr, + ios, host->mmc->card); +} + +static unsigned int sdhci_s3c_consider_clock(struct sdhci_s3c *ourhost, + unsigned int src, + unsigned int wanted) +{ + unsigned long rate; + struct clk *clksrc = ourhost->clk_bus[src]; + int div; + + if (!clksrc) + return UINT_MAX; + + rate = clk_get_rate(clksrc); + + for (div = 1; div < 256; div *= 2) { + if ((rate / div) <= wanted) + break; + } + + dev_dbg(&ourhost->pdev->dev, "clk %d: rate %ld, want %d, got %ld\n", + src, rate, wanted, rate / div); + + return (wanted - (rate / div)); +} + +static void sdhci_s3c_change_clock(struct sdhci_host *host, unsigned int clock) +{ + struct sdhci_s3c *ourhost = to_s3c(host); + unsigned int best = UINT_MAX; + unsigned int delta; + int best_src = 0; + int src; + u32 ctrl; + + for (src = 0; src < MAX_BUS_CLK; src++) { + delta = sdhci_s3c_consider_clock(ourhost, src, clock); + if (delta < best) { + best = delta; + best_src = src; + } + } + + dev_dbg(&ourhost->pdev->dev, + "selected source %d, clock %d, delta %d\n", + best_src, clock, best); + + /* turn clock off to card before changing clock source */ + writew(0, host->ioaddr + SDHCI_CLOCK_CONTROL); + + /* select the new clock source */ + + if (ourhost->cur_clk != best_src) { + struct clk *clk = ourhost->clk_bus[best_src]; + + ourhost->cur_clk = best_src; + host->max_clk = clk_get_rate(clk); + host->timeout_clk = host->max_clk / 1000000; + + ctrl = readl(host->ioaddr + S3C_SDHCI_CONTROL2); + ctrl &= ~S3C_SDHCI_CTRL2_SELBASECLK_MASK; + ctrl |= best_src << S3C_SDHCI_CTRL2_SELBASECLK_SHIFT; + writel(ctrl, host->ioaddr + S3C_SDHCI_CONTROL2); + } + + sdhci_change_clock(host, clock); +} + +static struct sdhci_ops sdhci_s3c_ops = { + .get_max_clock = sdhci_s3c_get_max_clk, + .get_timeout_clock = sdhci_s3c_get_timeout_clk, + .change_clock = sdhci_s3c_change_clock, + .set_ios = sdhci_s3c_set_ios, +}; + +/* + * call this when you need sd stack to recognize insertion or removal of card + * that can't be told by SDHCI regs + */ + +void sdhci_s3c_force_presence_change(struct platform_device *pdev) +{ + struct s3c_sdhci_platdata *pdata = pdev->dev.platform_data; + + dev_info(&pdev->dev, "sdhci_s3c_force_presence_change called\n"); + mmc_detect_change(pdata->sdhci_host->mmc, msecs_to_jiffies(200)); +} +EXPORT_SYMBOL_GPL(sdhci_s3c_force_presence_change); + + +static int __devinit sdhci_s3c_probe(struct platform_device *pdev) +{ + struct s3c_sdhci_platdata *pdata = pdev->dev.platform_data; + struct device *dev = &pdev->dev; + struct sdhci_host *host; + struct sdhci_s3c *sc; + struct resource *res; + int ret, irq, ptr, clks; + + if (!pdata) { + dev_err(dev, "no device data specified\n"); + return -ENOENT; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(dev, "no irq specified\n"); + return irq; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "no memory specified\n"); + return -ENOENT; + } + + host = sdhci_alloc_host(dev, sizeof(struct sdhci_s3c)); + if (IS_ERR(host)) { + dev_err(dev, "sdhci_alloc_host() failed\n"); + return PTR_ERR(host); + } + + pdata->sdhci_host = host; + + sc = sdhci_priv(host); + + sc->host = host; + sc->pdev = pdev; + sc->pdata = pdata; + + platform_set_drvdata(pdev, host); + + sc->clk_io = clk_get(dev, "hsmmc"); + if (IS_ERR(sc->clk_io)) { + dev_err(dev, "failed to get io clock\n"); + ret = PTR_ERR(sc->clk_io); + goto err_io_clk; + } + + /* enable the local io clock and keep it running for the moment. */ + clk_enable(sc->clk_io); + + for (clks = 0, ptr = 0; ptr < MAX_BUS_CLK; ptr++) { + struct clk *clk; + char *name = pdata->clocks[ptr]; + + if (name == NULL) + continue; + + clk = clk_get(dev, name); + if (IS_ERR(clk)) { + dev_err(dev, "failed to get clock %s\n", name); + continue; + } + + clks++; + sc->clk_bus[ptr] = clk; + clk_enable(clk); + + dev_info(dev, "clock source %d: %s (%ld Hz)\n", + ptr, name, clk_get_rate(clk)); + } + + if (clks == 0) { + dev_err(dev, "failed to find any bus clocks\n"); + ret = -ENOENT; + goto err_no_busclks; + } + + sc->ioarea = request_mem_region(res->start, resource_size(res), + mmc_hostname(host->mmc)); + if (!sc->ioarea) { + dev_err(dev, "failed to reserve register area\n"); + ret = -ENXIO; + goto err_req_regs; + } + + host->ioaddr = ioremap_nocache(res->start, resource_size(res)); + if (!host->ioaddr) { + dev_err(dev, "failed to map registers\n"); + ret = -ENXIO; + goto err_req_regs; + } + + /* Ensure we have minimal gpio selected CMD/CLK/Detect */ + if (pdata->cfg_gpio) + pdata->cfg_gpio(pdev, 0); + + sdhci_s3c_check_sclk(host); + + host->hw_name = "samsung-hsmmc"; + host->ops = &sdhci_s3c_ops; + host->quirks = 0; + host->irq = irq; + + /* Setup quirks for the controller */ + + /* Currently with ADMA enabled we are getting some length + * interrupts that are not being dealt with, do disable + * ADMA until this is sorted out. */ + host->quirks |= SDHCI_QUIRK_BROKEN_ADMA; + host->quirks |= SDHCI_QUIRK_32BIT_ADMA_SIZE; + + /* It seems we do not get an DATA transfer complete on non-busy + * transfers, not sure if this is a problem with this specific + * SDHCI block, or a missing configuration that needs to be set. */ + host->quirks |= SDHCI_QUIRK_NO_TCIRQ_ON_NOT_BUSY; + + host->quirks |= (SDHCI_QUIRK_32BIT_DMA_ADDR | + SDHCI_QUIRK_32BIT_DMA_SIZE); + + ret = sdhci_add_host(host); + if (ret) { + dev_err(dev, "sdhci_add_host() failed\n"); + goto err_add_host; + } + + return 0; + + err_add_host: + release_resource(sc->ioarea); + kfree(sc->ioarea); + + err_req_regs: + for (ptr = 0; ptr < MAX_BUS_CLK; ptr++) { + clk_disable(sc->clk_bus[ptr]); + clk_put(sc->clk_bus[ptr]); + } + + err_no_busclks: + clk_disable(sc->clk_io); + clk_put(sc->clk_io); + + err_io_clk: + sdhci_free_host(host); + + return ret; +} + +static int __devexit sdhci_s3c_remove(struct platform_device *pdev) +{ + return 0; +} + +#ifdef CONFIG_PM + +static int sdhci_s3c_suspend(struct platform_device *dev, pm_message_t pm) +{ + struct sdhci_host *host = platform_get_drvdata(dev); + + sdhci_suspend_host(host, pm); + return 0; +} + +static int sdhci_s3c_resume(struct platform_device *dev) +{ + struct sdhci_host *host = platform_get_drvdata(dev); + + sdhci_resume_host(host); + return 0; +} + +#else +#define sdhci_s3c_suspend NULL +#define sdhci_s3c_resume NULL +#endif + +static struct platform_driver sdhci_s3c_driver = { + .probe = sdhci_s3c_probe, + .remove = __devexit_p(sdhci_s3c_remove), + .suspend = sdhci_s3c_suspend, + .resume = sdhci_s3c_resume, + .driver = { + .owner = THIS_MODULE, + .name = "s3c-sdhci", + }, +}; + +static int __init sdhci_s3c_init(void) +{ + return platform_driver_register(&sdhci_s3c_driver); +} + +static void __exit sdhci_s3c_exit(void) +{ + platform_driver_unregister(&sdhci_s3c_driver); +} + +module_init(sdhci_s3c_init); +module_exit(sdhci_s3c_exit); + +MODULE_DESCRIPTION("Samsung SDHCI (HSMMC) glue"); +MODULE_AUTHOR("Ben Dooks, "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:s3c-sdhci"); --- a/drivers/mtd/nand/s3c2410.c +++ b/drivers/mtd/nand/s3c2410.c @@ -438,7 +438,7 @@ static int s3c2410_nand_correct_data(str if ((diff0 & ~(1<regs + S3C2440_NFDATA, buf, len / 4); + for (i = 0; i != (len & 3); i++) + ptr[i] = readb(info->regs + S3C2440_NFDATA); } static void s3c2410_nand_write_buf(struct mtd_info *mtd, const u_char *buf, int len) @@ -645,17 +650,31 @@ static int s3c2410_nand_remove(struct pl } #ifdef CONFIG_MTD_PARTITIONS +const char *part_probes[] = { "cmdlinepart", NULL }; static int s3c2410_nand_add_partition(struct s3c2410_nand_info *info, struct s3c2410_nand_mtd *mtd, struct s3c2410_nand_set *set) { + struct mtd_partition *part_info; + int nr_part = 0; + if (set == NULL) return add_mtd_device(&mtd->mtd); - if (set->nr_partitions > 0 && set->partitions != NULL) { - return add_mtd_partitions(&mtd->mtd, set->partitions, set->nr_partitions); + if (set->nr_partitions == 0) { + mtd->mtd.name = set->name; + nr_part = parse_mtd_partitions(&mtd->mtd, part_probes, + &part_info, 0); + } else { + if (set->nr_partitions > 0 && set->partitions != NULL) { + nr_part = set->nr_partitions; + part_info = set->partitions; + } } + if (nr_part > 0 && part_info) + return add_mtd_partitions(&mtd->mtd, part_info, nr_part); + return add_mtd_device(&mtd->mtd); } #else @@ -684,9 +703,13 @@ static void s3c2410_nand_init_chip(struc chip->select_chip = s3c2410_nand_select_chip; chip->chip_delay = 50; chip->priv = nmtd; - chip->options = 0; chip->controller = &info->controller; + if (set->flags & S3C2410_NAND_BBT) + chip->options = NAND_USE_FLASH_BBT; + else + chip->options = 0; + switch (info->cpu_type) { case TYPE_S3C2410: chip->IO_ADDR_W = regs + S3C2410_NFDATA; @@ -726,7 +749,7 @@ static void s3c2410_nand_init_chip(struc nmtd->mtd.owner = THIS_MODULE; nmtd->set = set; - if (hardware_ecc) { + if (!info->platform->software_ecc && hardware_ecc) { chip->ecc.calculate = s3c2410_nand_calculate_ecc; chip->ecc.correct = s3c2410_nand_correct_data; chip->ecc.mode = NAND_ECC_HW; --- a/drivers/mmc/core/core.c +++ b/drivers/mmc/core/core.c @@ -59,10 +59,11 @@ static int mmc_schedule_delayed_work(str /* * Internal function. Flush all scheduled work from the MMC work queue. */ -static void mmc_flush_scheduled_work(void) +void mmc_flush_scheduled_work(void) { flush_workqueue(workqueue); } +EXPORT_SYMBOL_GPL(mmc_flush_scheduled_work); /** * mmc_request_done - finish processing an MMC request