diff options
137 files changed, 5820 insertions, 604 deletions
diff --git a/arch/arm/common/dmabounce.c b/arch/arm/common/dmabounce.c index 5a375e5fef21..bc90364a96c7 100644 --- a/arch/arm/common/dmabounce.c +++ b/arch/arm/common/dmabounce.c @@ -308,15 +308,11 @@ static inline void unmap_single(struct device *dev, dma_addr_t dma_addr, memcpy(ptr, buf->safe, size); /* - * DMA buffers must have the same cache properties - * as if they were really used for DMA - which means - * data must be written back to RAM. Note that - * we don't use dmac_flush_range() here for the - * bidirectional case because we know the cache - * lines will be coherent with the data written. + * Since we may have written to a page cache page, + * we need to ensure that the data will be coherent + * with user mappings. */ - dmac_clean_range(ptr, ptr + size); - outer_clean_range(__pa(ptr), __pa(ptr) + size); + __cpuc_flush_kernel_dcache_area(ptr, size); } free_safe_buffer(dev->archdata.dmabounce, buf); } diff --git a/arch/arm/include/asm/cacheflush.h b/arch/arm/include/asm/cacheflush.h index 73eceb87e588..730aefcfbee3 100644 --- a/arch/arm/include/asm/cacheflush.h +++ b/arch/arm/include/asm/cacheflush.h @@ -211,7 +211,7 @@ struct cpu_cache_fns { void (*coherent_kern_range)(unsigned long, unsigned long); void (*coherent_user_range)(unsigned long, unsigned long); - void (*flush_kern_dcache_page)(void *); + void (*flush_kern_dcache_area)(void *, size_t); void (*dma_inv_range)(const void *, const void *); void (*dma_clean_range)(const void *, const void *); @@ -236,7 +236,7 @@ extern struct cpu_cache_fns cpu_cache; #define __cpuc_flush_user_range cpu_cache.flush_user_range #define __cpuc_coherent_kern_range cpu_cache.coherent_kern_range #define __cpuc_coherent_user_range cpu_cache.coherent_user_range -#define __cpuc_flush_dcache_page cpu_cache.flush_kern_dcache_page +#define __cpuc_flush_dcache_area cpu_cache.flush_kern_dcache_area /* * These are private to the dma-mapping API. Do not use directly. @@ -255,14 +255,14 @@ extern struct cpu_cache_fns cpu_cache; #define __cpuc_flush_user_range __glue(_CACHE,_flush_user_cache_range) #define __cpuc_coherent_kern_range __glue(_CACHE,_coherent_kern_range) #define __cpuc_coherent_user_range __glue(_CACHE,_coherent_user_range) -#define __cpuc_flush_dcache_page __glue(_CACHE,_flush_kern_dcache_page) +#define __cpuc_flush_dcache_area __glue(_CACHE,_flush_kern_dcache_area) extern void __cpuc_flush_kern_all(void); extern void __cpuc_flush_user_all(void); extern void __cpuc_flush_user_range(unsigned long, unsigned long, unsigned int); extern void __cpuc_coherent_kern_range(unsigned long, unsigned long); extern void __cpuc_coherent_user_range(unsigned long, unsigned long); -extern void __cpuc_flush_dcache_page(void *); +extern void __cpuc_flush_dcache_area(void *, size_t); /* * These are private to the dma-mapping API. Do not use directly. @@ -448,7 +448,7 @@ static inline void flush_kernel_dcache_page(struct page *page) { /* highmem pages are always flushed upon kunmap already */ if ((cache_is_vivt() || cache_is_vipt_aliasing()) && !PageHighMem(page)) - __cpuc_flush_dcache_page(page_address(page)); + __cpuc_flush_dcache_area(page_address(page), PAGE_SIZE); } #define flush_dcache_mmap_lock(mapping) \ @@ -465,13 +465,6 @@ static inline void flush_kernel_dcache_page(struct page *page) */ #define flush_icache_page(vma,page) do { } while (0) -static inline void flush_ioremap_region(unsigned long phys, void __iomem *virt, - unsigned offset, size_t size) -{ - const void *start = (void __force *)virt + offset; - dmac_inv_range(start, start + size); -} - /* * flush_cache_vmap() is used when creating mappings (eg, via vmap, * vmalloc, ioremap etc) in kernel space for pages. On non-VIPT diff --git a/arch/arm/mach-kirkwood/Kconfig b/arch/arm/mach-kirkwood/Kconfig index 8bf09ae5b347..f6c6196a51fa 100644 --- a/arch/arm/mach-kirkwood/Kconfig +++ b/arch/arm/mach-kirkwood/Kconfig @@ -52,6 +52,12 @@ config MACH_OPENRD_BASE Say 'Y' here if you want your kernel to support the Marvell OpenRD Base Board. +config MACH_NETSPACE_V2 + bool "LaCie Network Space v2 NAS Board" + help + Say 'Y' here if you want your kernel to support the + LaCie Network Space v2 NAS. + endmenu endif diff --git a/arch/arm/mach-kirkwood/Makefile b/arch/arm/mach-kirkwood/Makefile index 9f2f67b2b63d..d4d7f53b0fb9 100644 --- a/arch/arm/mach-kirkwood/Makefile +++ b/arch/arm/mach-kirkwood/Makefile @@ -8,5 +8,6 @@ obj-$(CONFIG_MACH_SHEEVAPLUG) += sheevaplug-setup.o obj-$(CONFIG_MACH_TS219) += ts219-setup.o tsx1x-common.o obj-$(CONFIG_MACH_TS41X) += ts41x-setup.o tsx1x-common.o obj-$(CONFIG_MACH_OPENRD_BASE) += openrd_base-setup.o +obj-$(CONFIG_MACH_NETSPACE_V2) += netspace_v2-setup.o obj-$(CONFIG_CPU_IDLE) += cpuidle.o diff --git a/arch/arm/mach-kirkwood/netspace_v2-setup.c b/arch/arm/mach-kirkwood/netspace_v2-setup.c new file mode 100644 index 000000000000..9a064065bebe --- /dev/null +++ b/arch/arm/mach-kirkwood/netspace_v2-setup.c @@ -0,0 +1,325 @@ +/* + * arch/arm/mach-kirkwood/netspace_v2-setup.c + * + * LaCie Network Space v2 board setup + * + * Copyright (C) 2009 Simon Guinot <sguinot@lacie.com> + * Copyright (C) 2009 Benoît Canet <benoit.canet@gmail.com> + * + * 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 + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/mtd/physmap.h> +#include <linux/spi/flash.h> +#include <linux/spi/spi.h> +#include <linux/ata_platform.h> +#include <linux/mv643xx_eth.h> +#include <linux/i2c.h> +#include <linux/i2c/at24.h> +#include <linux/input.h> +#include <linux/gpio.h> +#include <linux/gpio_keys.h> +#include <linux/leds.h> +#include <asm/mach-types.h> +#include <asm/mach/arch.h> +#include <asm/mach/time.h> +#include <mach/kirkwood.h> +#include <plat/time.h> +#include "common.h" +#include "mpp.h" + +/***************************************************************************** + * 512KB SPI Flash on Boot Device (MACRONIX MX25L4005) + ****************************************************************************/ + +static struct mtd_partition netspace_v2_flash_parts[] = { + { + .name = "u-boot", + .size = MTDPART_SIZ_FULL, + .offset = 0, + .mask_flags = MTD_WRITEABLE, /* force read-only */ + }, +}; + +static const struct flash_platform_data netspace_v2_flash = { + .type = "mx25l4005a", + .name = "spi_flash", + .parts = netspace_v2_flash_parts, + .nr_parts = ARRAY_SIZE(netspace_v2_flash_parts), +}; + +static struct spi_board_info __initdata netspace_v2_spi_slave_info[] = { + { + .modalias = "m25p80", + .platform_data = &netspace_v2_flash, + .irq = -1, + .max_speed_hz = 20000000, + .bus_num = 0, + .chip_select = 0, + }, +}; + +/***************************************************************************** + * Ethernet + ****************************************************************************/ + +static struct mv643xx_eth_platform_data netspace_v2_ge00_data = { + .phy_addr = MV643XX_ETH_PHY_ADDR(8), +}; + +/***************************************************************************** + * I2C devices + ****************************************************************************/ + +static struct at24_platform_data at24c04 = { + .byte_len = SZ_4K / 8, + .page_size = 16, +}; + +/* + * i2c addr | chip | description + * 0x50 | HT24LC04 | eeprom (512B) + */ + +static struct i2c_board_info __initdata netspace_v2_i2c_info[] = { + { + I2C_BOARD_INFO("24c04", 0x50), + .platform_data = &at24c04, + } +}; + +/***************************************************************************** + * SATA + ****************************************************************************/ + +static struct mv_sata_platform_data netspace_v2_sata_data = { + .n_ports = 2, +}; + +#define NETSPACE_V2_GPIO_SATA0_POWER 16 +#define NETSPACE_V2_GPIO_SATA1_POWER 17 + +static void __init netspace_v2_sata_power_init(void) +{ + int err; + + err = gpio_request(NETSPACE_V2_GPIO_SATA0_POWER, "SATA0 power"); + if (err == 0) { + err = gpio_direction_output(NETSPACE_V2_GPIO_SATA0_POWER, 1); + if (err) + gpio_free(NETSPACE_V2_GPIO_SATA0_POWER); + } + if (err) + pr_err("netspace_v2: failed to setup SATA0 power\n"); +} + +/***************************************************************************** + * GPIO keys + ****************************************************************************/ + +#define NETSPACE_V2_PUSH_BUTTON 32 + +static struct gpio_keys_button netspace_v2_buttons[] = { + [0] = { + .code = KEY_POWER, + .gpio = NETSPACE_V2_PUSH_BUTTON, + .desc = "Power push button", + .active_low = 0, + }, +}; + +static struct gpio_keys_platform_data netspace_v2_button_data = { + .buttons = netspace_v2_buttons, + .nbuttons = ARRAY_SIZE(netspace_v2_buttons), +}; + +static struct platform_device netspace_v2_gpio_buttons = { + .name = "gpio-keys", + .id = -1, + .dev = { + .platform_data = &netspace_v2_button_data, + }, +}; + +/***************************************************************************** + * GPIO LEDs + ****************************************************************************/ + +/* + * The blue front LED is wired to a CPLD and can blink in relation with the + * SATA activity. + * + * The following array detail the different LED registers and the combination + * of their possible values: + * + * cmd_led | slow_led | /SATA active | LED state + * | | | + * 1 | 0 | x | off + * - | 1 | x | on + * 0 | 0 | 1 | on + * 0 | 0 | 0 | blink (rate 300ms) + */ + +#define NETSPACE_V2_GPIO_RED_LED 12 +#define NETSPACE_V2_GPIO_BLUE_LED_SLOW 29 +#define NETSPACE_V2_GPIO_BLUE_LED_CMD 30 + + +static struct gpio_led netspace_v2_gpio_led_pins[] = { + { + .name = "ns_v2:red:fail", + .gpio = NETSPACE_V2_GPIO_RED_LED, + }, +}; + +static struct gpio_led_platform_data netspace_v2_gpio_leds_data = { + .num_leds = ARRAY_SIZE(netspace_v2_gpio_led_pins), + .leds = netspace_v2_gpio_led_pins, +}; + +static struct platform_device netspace_v2_gpio_leds = { + .name = "leds-gpio", + .id = -1, + .dev = { + .platform_data = &netspace_v2_gpio_leds_data, + }, +}; + +static void __init netspace_v2_gpio_leds_init(void) +{ + platform_device_register(&netspace_v2_gpio_leds); + + /* + * Configure the front blue LED to blink in relation with the SATA + * activity. + */ + if (gpio_request(NETSPACE_V2_GPIO_BLUE_LED_SLOW, + "SATA blue LED slow") != 0) + return; + if (gpio_direction_output(NETSPACE_V2_GPIO_BLUE_LED_SLOW, 0) != 0) + goto err_free_1; + if (gpio_request(NETSPACE_V2_GPIO_BLUE_LED_CMD, + "SATA blue LED command") != 0) + goto err_free_1; + if (gpio_direction_output(NETSPACE_V2_GPIO_BLUE_LED_CMD, 0) != 0) + goto err_free_2; + + return; + +err_free_2: + gpio_free(NETSPACE_V2_GPIO_BLUE_LED_CMD); +err_free_1: + gpio_free(NETSPACE_V2_GPIO_BLUE_LED_SLOW); + pr_err("netspace_v2: failed to configure SATA blue LED\n"); +} + +/***************************************************************************** + * Timer + ****************************************************************************/ + +static void netspace_v2_timer_init(void) +{ + kirkwood_tclk = 166666667; + orion_time_init(IRQ_KIRKWOOD_BRIDGE, kirkwood_tclk); +} + +struct sys_timer netspace_v2_timer = { + .init = netspace_v2_timer_init, +}; + +/***************************************************************************** + * General Setup + ****************************************************************************/ + +static unsigned int netspace_v2_mpp_config[] __initdata = { + MPP0_SPI_SCn, + MPP1_SPI_MOSI, + MPP2_SPI_SCK, + MPP3_SPI_MISO, + MPP4_NF_IO6, + MPP5_NF_IO7, + MPP6_SYSRST_OUTn, + MPP8_TW_SDA, + MPP9_TW_SCK, + MPP10_UART0_TXD, + MPP11_UART0_RXD, + MPP12_GPO, /* Red led */ + MPP14_GPIO, /* USB fuse */ + MPP16_GPIO, /* SATA 0 power */ + MPP18_NF_IO0, + MPP19_NF_IO1, + MPP20_SATA1_ACTn, + MPP21_SATA0_ACTn, + MPP24_GPIO, /* USB mode select */ + MPP25_GPIO, /* Fan rotation fail */ + MPP26_GPIO, /* USB device vbus */ + MPP28_GPIO, /* USB enable host vbus */ + MPP29_GPIO, /* Blue led (slow register) */ + MPP30_GPIO, /* Blue led (command register) */ + MPP31_GPIO, /* Board power off */ + MPP32_GPIO, /* Power button (0 = Released, 1 = Pushed) */ + 0 +}; + +#define NETSPACE_V2_GPIO_POWER_OFF 31 + +static void netspace_v2_power_off(void) +{ + gpio_set_value(NETSPACE_V2_GPIO_POWER_OFF, 1); +} + +static void __init netspace_v2_init(void) +{ + /* + * Basic setup. Needs to be called early. + */ + kirkwood_init(); + kirkwood_mpp_conf(netspace_v2_mpp_config); + + netspace_v2_sata_power_init(); + + kirkwood_ehci_init(); + kirkwood_ge00_init(&netspace_v2_ge00_data); + kirkwood_sata_init(&netspace_v2_sata_data); + kirkwood_uart0_init(); + spi_register_board_info(netspace_v2_spi_slave_info, + ARRAY_SIZE(netspace_v2_spi_slave_info)); + kirkwood_spi_init(); + kirkwood_i2c_init(); + i2c_register_board_info(0, netspace_v2_i2c_info, + ARRAY_SIZE(netspace_v2_i2c_info)); + + netspace_v2_gpio_leds_init(); + platform_device_register(&netspace_v2_gpio_buttons); + + if (gpio_request(NETSPACE_V2_GPIO_POWER_OFF, "power-off") == 0 && + gpio_direction_output(NETSPACE_V2_GPIO_POWER_OFF, 0) == 0) + pm_power_off = netspace_v2_power_off; + else + pr_err("netspace_v2: failed to configure power-off GPIO\n"); +} + +MACHINE_START(NETSPACE_V2, "LaCie Network Space v2") + .phys_io = KIRKWOOD_REGS_PHYS_BASE, + .io_pg_offst = ((KIRKWOOD_REGS_VIRT_BASE) >> 18) & 0xfffc, + .boot_params = 0x00000100, + .init_machine = netspace_v2_init, + .map_io = kirkwood_map_io, + .init_irq = kirkwood_init_irq, + .timer = &netspace_v2_timer, +MACHINE_END diff --git a/arch/arm/mach-pxa/Kconfig b/arch/arm/mach-pxa/Kconfig index e6d8e10ae5d1..8a0837ea0294 100644 --- a/arch/arm/mach-pxa/Kconfig +++ b/arch/arm/mach-pxa/Kconfig @@ -110,6 +110,8 @@ config MACH_CM_X300 bool "CompuLab CM-X300 modules" select PXA3xx select CPU_PXA300 + select CPU_PXA310 + select HAVE_PWM config ARCH_GUMSTIX bool "Gumstix XScale 255 boards" @@ -240,7 +242,6 @@ config MACH_COLIBRI300 select PXA3xx select CPU_PXA300 select CPU_PXA310 - select HAVE_PWM config MACH_COLIBRI320 bool "Toradex Colibri PXA320" diff --git a/arch/arm/mach-pxa/devices.c b/arch/arm/mach-pxa/devices.c index 3395463bb5a6..8e10db148f1b 100644 --- a/arch/arm/mach-pxa/devices.c +++ b/arch/arm/mach-pxa/devices.c @@ -4,7 +4,6 @@ #include <linux/platform_device.h> #include <linux/dma-mapping.h> -#include <mach/hardware.h> #include <mach/udc.h> #include <mach/pxafb.h> #include <mach/mmc.h> @@ -14,6 +13,7 @@ #include <mach/pxa2xx_spi.h> #include <mach/camera.h> #include <mach/audio.h> +#include <mach/hardware.h> #include <plat/i2c.h> #include <plat/pxa3xx_nand.h> diff --git a/arch/arm/mach-s3c2410/include/mach/spi.h b/arch/arm/mach-s3c2410/include/mach/spi.h index 193b39d654ed..4d9588373aa5 100644 --- a/arch/arm/mach-s3c2410/include/mach/spi.h +++ b/arch/arm/mach-s3c2410/include/mach/spi.h @@ -18,6 +18,8 @@ struct s3c2410_spi_info { unsigned int num_cs; /* total chipselects */ int bus_num; /* bus number to use. */ + unsigned int use_fiq:1; /* use fiq */ + void (*gpio_setup)(struct s3c2410_spi_info *spi, int enable); void (*set_cs)(struct s3c2410_spi_info *spi, int cs, int pol); }; diff --git a/arch/arm/mm/cache-fa.S b/arch/arm/mm/cache-fa.S index b63a8f7b95cf..a89444a3c016 100644 --- a/arch/arm/mm/cache-fa.S +++ b/arch/arm/mm/cache-fa.S @@ -127,15 +127,16 @@ ENTRY(fa_coherent_user_range) mov pc, lr /* - * flush_kern_dcache_page(kaddr) + * flush_kern_dcache_area(void *addr, size_t size) * * Ensure that the data held in the page kaddr is written back * to the page in question. * - * - kaddr - kernel address (guaranteed to be page aligned) + * - addr - kernel address + * - size - size of region */ -ENTRY(fa_flush_kern_dcache_page) - add r1, r0, #PAGE_SZ +ENTRY(fa_flush_kern_dcache_area) + add r1, r0, r1 1: mcr p15, 0, r0, c7, c14, 1 @ clean & invalidate D line add r0, r0, #CACHE_DLINESIZE cmp r0, r1 @@ -213,7 +214,7 @@ ENTRY(fa_cache_fns) .long fa_flush_user_cache_range .long fa_coherent_kern_range .long fa_coherent_user_range - .long fa_flush_kern_dcache_page + .long fa_flush_kern_dcache_area .long fa_dma_inv_range .long fa_dma_clean_range .long fa_dma_flush_range diff --git a/arch/arm/mm/cache-l2x0.c b/arch/arm/mm/cache-l2x0.c index 747f9a9021bb..cb8fc6573b1b 100644 --- a/arch/arm/mm/cache-l2x0.c +++ b/arch/arm/mm/cache-l2x0.c @@ -28,69 +28,120 @@ static void __iomem *l2x0_base; static DEFINE_SPINLOCK(l2x0_lock); -static inline void sync_writel(unsigned long val, unsigned long reg, - unsigned long complete_mask) +static inline void cache_wait(void __iomem *reg, unsigned long mask) { - unsigned long flags; - - spin_lock_irqsave(&l2x0_lock, flags); - writel(val, l2x0_base + reg); /* wait for the operation to complete */ - while (readl(l2x0_base + reg) & complete_mask) + while (readl(reg) & mask) ; - spin_unlock_irqrestore(&l2x0_lock, flags); } static inline void cache_sync(void) { - sync_writel(0, L2X0_CACHE_SYNC, 1); + void __iomem *base = l2x0_base; + writel(0, base + L2X0_CACHE_SYNC); + cache_wait(base + L2X0_CACHE_SYNC, 1); } static inline void l2x0_inv_all(void) { + unsigned long flags; + /* invalidate all ways */ - sync_writel(0xff, L2X0_INV_WAY, 0xff); + spin_lock_irqsave(&l2x0_lock, flags); + writel(0xff, l2x0_base + L2X0_INV_WAY); + cache_wait(l2x0_base + L2X0_INV_WAY, 0xff); cache_sync(); + spin_unlock_irqrestore(&l2x0_lock, flags); } static void l2x0_inv_range(unsigned long start, unsigned long end) { - unsigned long addr; + void __iomem *base = l2x0_base; + unsigned long flags; + spin_lock_irqsave(&l2x0_lock, flags); if (start & (CACHE_LINE_SIZE - 1)) { start &= ~(CACHE_LINE_SIZE - 1); - sync_writel(start, L2X0_CLEAN_INV_LINE_PA, 1); + cache_wait(base + L2X0_CLEAN_INV_LINE_PA, 1); + writel(start, base + L2X0_CLEAN_INV_LINE_PA); start += CACHE_LINE_SIZE; } if (end & (CACHE_LINE_SIZE - 1)) { end &= ~(CACHE_LINE_SIZE - 1); - sync_writel(end, L2X0_CLEAN_INV_LINE_PA, 1); + cache_wait(base + L2X0_CLEAN_INV_LINE_PA, 1); + writel(end, base + L2X0_CLEAN_INV_LINE_PA); } - for (addr = start; addr < end; addr += CACHE_LINE_SIZE) - sync_writel(addr, L2X0_INV_LINE_PA, 1); + while (start < end) { + unsigned long blk_end = start + min(end - start, 4096UL); + + while (start < blk_end) { + cache_wait(base + L2X0_INV_LINE_PA, 1); + writel(start, base + L2X0_INV_LINE_PA); + start += CACHE_LINE_SIZE; + } + + if (blk_end < end) { + spin_unlock_irqrestore(&l2x0_lock, flags); + spin_lock_irqsave(&l2x0_lock, flags); + } + } + cache_wait(base + L2X0_INV_LINE_PA, 1); cache_sync(); + spin_unlock_irqrestore(&l2x0_lock, flags); } static void l2x0_clean_range(unsigned long start, unsigned long end) { - unsigned long addr; + void __iomem *base = l2x0_base; + unsigned long flags; + spin_lock_irqsave(&l2x0_lock, flags); start &= ~(CACHE_LINE_SIZE - 1); - for (addr = start; addr < end; addr += CACHE_LINE_SIZE) - sync_writel(addr, L2X0_CLEAN_LINE_PA, 1); + while (start < end) { + unsigned long blk_end = start + min(end - start, 4096UL); + + while (start < blk_end) { + cache_wait(base + L2X0_CLEAN_LINE_PA, 1); + writel(start, base + L2X0_CLEAN_LINE_PA); + start += CACHE_LINE_SIZE; + } + + if (blk_end < end) { + spin_unlock_irqrestore(&l2x0_lock, flags); + spin_lock_irqsave(&l2x0_lock, flags); + } + } + cache_wait(base + L2X0_CLEAN_LINE_PA, 1); cache_sync(); + spin_unlock_irqrestore(&l2x0_lock, flags); } static void l2x0_flush_range(unsigned long start, unsigned long end) { - unsigned long addr; + void __iomem *base = l2x0_base; + unsigned long flags; + spin_lock_irqsave(&l2x0_lock, flags); start &= ~(CACHE_LINE_SIZE - 1); - for (addr = start; addr < end; addr += CACHE_LINE_SIZE) - sync_writel(addr, L2X0_CLEAN_INV_LINE_PA, 1); + while (start < end) { + unsigned long blk_end = start + min(end - start, 4096UL); + + while (start < blk_end) { + cache_wait(base + L2X0_CLEAN_INV_LINE_PA, 1); + writel(start, base + L2X0_CLEAN_INV_LINE_PA); + start += CACHE_LINE_SIZE; + } + + if (blk_end < end) { + spin_unlock_irqrestore(&l2x0_lock, flags); + spin_lock_irqsave(&l2x0_lock, flags); + } + } + cache_wait(base + L2X0_CLEAN_INV_LINE_PA, 1); cache_sync(); + spin_unlock_irqrestore(&l2x0_lock, flags); } void __init l2x0_init(void __iomem *base, __u32 aux_val, __u32 aux_mask) diff --git a/arch/arm/mm/cache-v3.S b/arch/arm/mm/cache-v3.S index 8a4abebc478a..2a482731ea36 100644 --- a/arch/arm/mm/cache-v3.S +++ b/arch/arm/mm/cache-v3.S @@ -72,14 +72,15 @@ ENTRY(v3_coherent_user_range) mov pc, lr /* - * flush_kern_dcache_page(void *page) + * flush_kern_dcache_area(void *page, size_t size) * * Ensure no D cache aliasing occurs, either with itself or * the I cache * - * - addr - page aligned address + * - addr - kernel address + * - size - region size */ -ENTRY(v3_flush_kern_dcache_page) +ENTRY(v3_flush_kern_dcache_area) /* FALLTHROUGH */ /* @@ -129,7 +130,7 @@ ENTRY(v3_cache_fns) .long v3_flush_user_cache_range .long v3_coherent_kern_range .long v3_coherent_user_range - .long v3_flush_kern_dcache_page + .long v3_flush_kern_dcache_area .long v3_dma_inv_range .long v3_dma_clean_range .long v3_dma_flush_range diff --git a/arch/arm/mm/cache-v4.S b/arch/arm/mm/cache-v4.S index 3668611cb400..5c7da3e372e9 100644 --- a/arch/arm/mm/cache-v4.S +++ b/arch/arm/mm/cache-v4.S @@ -82,14 +82,15 @@ ENTRY(v4_coherent_user_range) mov pc, lr /* - * flush_kern_dcache_page(void *page) + * flush_kern_dcache_area(void *addr, size_t size) * * Ensure no D cache aliasing occurs, either with itself or * the I cache * - * - addr - page aligned address + * - addr - kernel address + * - size - region size */ -ENTRY(v4_flush_kern_dcache_page) +ENTRY(v4_flush_kern_dcache_area) /* FALLTHROUGH */ /* @@ -141,7 +142,7 @@ ENTRY(v4_cache_fns) .long v4_flush_user_cache_range .long v4_coherent_kern_range .long v4_coherent_user_range - .long v4_flush_kern_dcache_page + .long v4_flush_kern_dcache_area .long v4_dma_inv_range .long v4_dma_clean_range .long v4_dma_flush_range diff --git a/arch/arm/mm/cache-v4wb.S b/arch/arm/mm/cache-v4wb.S index 2ebc1b3bf856..3dbedf1ec0e7 100644 --- a/arch/arm/mm/cache-v4wb.S +++ b/arch/arm/mm/cache-v4wb.S @@ -114,15 +114,16 @@ ENTRY(v4wb_flush_user_cache_range) mov pc, lr /* - * flush_kern_dcache_page(void *page) + * flush_kern_dcache_area(void *addr, size_t size) * * Ensure no D cache aliasing occurs, either with itself or * the I cache * - * - addr - page aligned address + * - addr - kernel address + * - size - region size */ -ENTRY(v4wb_flush_kern_dcache_page) - add r1, r0, #PAGE_SZ +ENTRY(v4wb_flush_kern_dcache_area) + add r1, r0, r1 /* fall through */ /* @@ -224,7 +225,7 @@ ENTRY(v4wb_cache_fns) .long v4wb_flush_user_cache_range .long v4wb_coherent_kern_range .long v4wb_coherent_user_range - .long v4wb_flush_kern_dcache_page + .long v4wb_flush_kern_dcache_area .long v4wb_dma_inv_range .long v4wb_dma_clean_range .long v4wb_dma_flush_range diff --git a/arch/arm/mm/cache-v4wt.S b/arch/arm/mm/cache-v4wt.S index c54fa2cc40e6..b3b7410270b4 100644 --- a/arch/arm/mm/cache-v4wt.S +++ b/arch/arm/mm/cache-v4wt.S @@ -117,17 +117,18 @@ ENTRY(v4wt_coherent_user_range) mov pc, lr /* - * flush_kern_dcache_page(void *page) + * flush_kern_dcache_area(void *addr, size_t size) * * Ensure no D cache aliasing occurs, either with itself or * the I cache * - * - addr - page aligned address + * - addr - kernel address + * - size - region size */ -ENTRY(v4wt_flush_kern_dcache_page) +ENTRY(v4wt_flush_kern_dcache_area) mov r2, #0 mcr p15, 0, r2, c7, c5, 0 @ invalidate I cache - add r1, r0, #PAGE_SZ + add r1, r0, r1 /* fallthrough */ /* @@ -180,7 +181,7 @@ ENTRY(v4wt_cache_fns) .long v4wt_flush_user_cache_range .long v4wt_coherent_kern_range .long v4wt_coherent_user_range - .long v4wt_flush_kern_dcache_page + .long v4wt_flush_kern_dcache_area .long v4wt_dma_inv_range .long v4wt_dma_clean_range .long v4wt_dma_flush_range diff --git a/arch/arm/mm/cache-v6.S b/arch/arm/mm/cache-v6.S index 295e25dd6381..4ba0a24ce6f5 100644 --- a/arch/arm/mm/cache-v6.S +++ b/arch/arm/mm/cache-v6.S @@ -159,15 +159,16 @@ ENDPROC(v6_coherent_user_range) ENDPROC(v6_coherent_kern_range) /* - * v6_flush_kern_dcache_page(kaddr) + * v6_flush_kern_dcache_area(void *addr, size_t size) * * Ensure that the data held in the page kaddr is written back * to the page in question. * - * - kaddr - kernel address (guaranteed to be page aligned) + * - addr - kernel address + * - size - region size */ -ENTRY(v6_flush_kern_dcache_page) - add r1, r0, #PAGE_SZ +ENTRY(v6_flush_kern_dcache_area) + add r1, r0, r1 1: #ifdef HARVARD_CACHE mcr p15, 0, r0, c7, c14, 1 @ clean & invalidate D line @@ -271,7 +272,7 @@ ENTRY(v6_cache_fns) .long v6_flush_user_cache_range .long v6_coherent_kern_range .long v6_coherent_user_range - .long v6_flush_kern_dcache_page + .long v6_flush_kern_dcache_area .long v6_dma_inv_range .long v6_dma_clean_range .long v6_dma_flush_range diff --git a/arch/arm/mm/cache-v7.S b/arch/arm/mm/cache-v7.S index e1bd9759617f..9073db849fb4 100644 --- a/arch/arm/mm/cache-v7.S +++ b/arch/arm/mm/cache-v7.S @@ -186,16 +186,17 @@ ENDPROC(v7_coherent_kern_range) ENDPROC(v7_coherent_user_range) /* - * v7_flush_kern_dcache_page(kaddr) + * v7_flush_kern_dcache_area(void *addr, size_t size) * * Ensure that the data held in the page kaddr is written back * to the page in question. * - * - kaddr - kernel address (guaranteed to be page aligned) + * - addr - kernel address + * - size - region size */ -ENTRY(v7_flush_kern_dcache_page) +ENTRY(v7_flush_kern_dcache_area) dcache_line_size r2, r3 - add r1, r0, #PAGE_SZ + add r1, r0, r1 1: mcr p15, 0, r0, c7, c14, 1 @ clean & invalidate D line / unified line add r0, r0, r2 @@ -203,7 +204,7 @@ ENTRY(v7_flush_kern_dcache_page) blo 1b dsb mov pc, lr -ENDPROC(v7_flush_kern_dcache_page) +ENDPROC(v7_flush_kern_dcache_area) /* * v7_dma_inv_range(start,end) @@ -279,7 +280,7 @@ ENTRY(v7_cache_fns) .long v7_flush_user_cache_range .long v7_coherent_kern_range .long v7_coherent_user_range - .long v7_flush_kern_dcache_page + .long v7_flush_kern_dcache_area .long v7_dma_inv_range .long v7_dma_clean_range .long v7_dma_flush_range diff --git a/arch/arm/mm/flush.c b/arch/arm/mm/flush.c index 329594e760cd..6f3a4b7a3b82 100644 --- a/arch/arm/mm/flush.c +++ b/arch/arm/mm/flush.c @@ -131,7 +131,7 @@ void __flush_dcache_page(struct address_space *mapping, struct page *page) */ if (addr) #endif - __cpuc_flush_dcache_page(addr); + __cpuc_flush_dcache_area(addr, PAGE_SIZE); /* * If this is a page cache page, and we have an aliasing VIPT cache, @@ -258,5 +258,5 @@ void __flush_anon_page(struct vm_area_struct *vma, struct page *page, unsigned l * in this mapping of the page. FIXME: this is overkill * since we actually ask for a write-back and invalidate. */ - __cpuc_flush_dcache_page(page_address(page)); + __cpuc_flush_dcache_area(page_address(page), PAGE_SIZE); } diff --git a/arch/arm/mm/highmem.c b/arch/arm/mm/highmem.c index 30f82fb5918c..2be1ec7c1b41 100644 --- a/arch/arm/mm/highmem.c +++ b/arch/arm/mm/highmem.c @@ -79,7 +79,7 @@ void kunmap_atomic(void *kvaddr, enum km_type type) unsigned int idx = type + KM_TYPE_NR * smp_processor_id(); if (kvaddr >= (void *)FIXADDR_START) { - __cpuc_flush_dcache_page((void *)vaddr); + __cpuc_flush_dcache_area((void *)vaddr, PAGE_SIZE); #ifdef CONFIG_DEBUG_HIGHMEM BUG_ON(vaddr != __fix_to_virt(FIX_KMAP_BEGIN + idx)); set_pte_ext(TOP_PTE(vaddr), __pte(0), 0); diff --git a/arch/arm/mm/nommu.c b/arch/arm/mm/nommu.c index 900811cc9130..374a8311bc84 100644 --- a/arch/arm/mm/nommu.c +++ b/arch/arm/mm/nommu.c @@ -61,7 +61,7 @@ void setup_mm_for_reboot(char mode) void flush_dcache_page(struct page *page) { - __cpuc_flush_dcache_page(page_address(page)); + __cpuc_flush_dcache_area(page_address(page), PAGE_SIZE); } EXPORT_SYMBOL(flush_dcache_page); diff --git a/arch/arm/mm/proc-arm1020.S b/arch/arm/mm/proc-arm1020.S index d9fb4b98c49f..8012e24282b2 100644 --- a/arch/arm/mm/proc-arm1020.S +++ b/arch/arm/mm/proc-arm1020.S @@ -231,17 +231,18 @@ ENTRY(arm1020_coherent_user_range) mov pc, lr /* - * flush_kern_dcache_page(void *page) + * flush_kern_dcache_area(void *addr, size_t size) * * Ensure no D cache aliasing occurs, either with itself or * the I cache * - * - page - page aligned address + * - addr - kernel address + * - size - region size */ -ENTRY(arm1020_flush_kern_dcache_page) +ENTRY(arm1020_flush_kern_dcache_area) mov ip, #0 #ifndef CONFIG_CPU_DCACHE_DISABLE - add r1, r0, #PAGE_SZ + add r1, r0, r1 1: mcr p15, 0, r0, c7, c14, 1 @ clean+invalidate D entry mcr p15, 0, ip, c7, c10, 4 @ drain WB add r0, r0, #CACHE_DLINESIZE @@ -335,7 +336,7 @@ ENTRY(arm1020_cache_fns) .long arm1020_flush_user_cache_range .long arm1020_coherent_kern_range .long arm1020_coherent_user_range - .long arm1020_flush_kern_dcache_page + .long arm1020_flush_kern_dcache_area .long arm1020_dma_inv_range .long arm1020_dma_clean_range .long arm1020_dma_flush_range diff --git a/arch/arm/mm/proc-arm1020e.S b/arch/arm/mm/proc-arm1020e.S index 7453b75dcea5..41fe25d234f5 100644 --- a/arch/arm/mm/proc-arm1020e.S +++ b/arch/arm/mm/proc-arm1020e.S @@ -225,17 +225,18 @@ ENTRY(arm1020e_coherent_user_range) mov pc, lr /* - * flush_kern_dcache_page(void *page) + * flush_kern_dcache_area(void *addr, size_t size) * * Ensure no D cache aliasing occurs, either with itself or * the I cache * - * - page - page aligned address + * - addr - kernel address + * - size - region size */ -ENTRY(arm1020e_flush_kern_dcache_page) +ENTRY(arm1020e_flush_kern_dcache_area) mov ip, #0 #ifndef CONFIG_CPU_DCACHE_DISABLE - add r1, r0, #PAGE_SZ + add r1, r0, r1 1: mcr p15, 0, r0, c7, c14, 1 @ clean+invalidate D entry add r0, r0, #CACHE_DLINESIZE cmp r0, r1 @@ -321,7 +322,7 @@ ENTRY(arm1020e_cache_fns) .long arm1020e_flush_user_cache_range .long arm1020e_coherent_kern_range .long arm1020e_coherent_user_range - .long arm1020e_flush_kern_dcache_page + .long arm1020e_flush_kern_dcache_area .long arm1020e_dma_inv_range .long arm1020e_dma_clean_range .long arm1020e_dma_flush_range diff --git a/arch/arm/mm/proc-arm1022.S b/arch/arm/mm/proc-arm1022.S index 8eb72d75a8b6..20a5b1b31a70 100644 --- a/arch/arm/mm/proc-arm1022.S +++ b/arch/arm/mm/proc-arm1022.S @@ -214,17 +214,18 @@ ENTRY(arm1022_coherent_user_range) mov pc, lr /* - * flush_kern_dcache_page(void *page) + * flush_kern_dcache_area(void *addr, size_t size) * * Ensure no D cache aliasing occurs, either with itself or * the I cache * - * - page - page aligned address + * - addr - kernel address + * - size - region size */ -ENTRY(arm1022_flush_kern_dcache_page) +ENTRY(arm1022_flush_kern_dcache_area) mov ip, #0 #ifndef CONFIG_CPU_DCACHE_DISABLE - add r1, r0, #PAGE_SZ + add r1, r0, r1 1: mcr p15, 0, r0, c7, c14, 1 @ clean+invalidate D entry add r0, r0, #CACHE_DLINESIZE cmp r0, r1 @@ -310,7 +311,7 @@ ENTRY(arm1022_cache_fns) .long arm1022_flush_user_cache_range .long arm1022_coherent_kern_range .long arm1022_coherent_user_range - .long arm1022_flush_kern_dcache_page + .long arm1022_flush_kern_dcache_area .long arm1022_dma_inv_range .long arm1022_dma_clean_range .long arm1022_dma_flush_range diff --git a/arch/arm/mm/proc-arm1026.S b/arch/arm/mm/proc-arm1026.S index 3b59f0d67139..96aedb10fcc4 100644 --- a/arch/arm/mm/proc-arm1026.S +++ b/arch/arm/mm/proc-arm1026.S @@ -208,17 +208,18 @@ ENTRY(arm1026_coherent_user_range) mov pc, lr /* - * flush_kern_dcache_page(void *page) + * flush_kern_dcache_area(void *addr, size_t size) * * Ensure no D cache aliasing occurs, either with itself or * the I cache * - * - page - page aligned address + * - addr - kernel address + * - size - region size */ -ENTRY(arm1026_flush_kern_dcache_page) +ENTRY(arm1026_flush_kern_dcache_area) mov ip, #0 #ifndef CONFIG_CPU_DCACHE_DISABLE - add r1, r0, #PAGE_SZ + add r1, r0, r1 1: mcr p15, 0, r0, c7, c14, 1 @ clean+invalidate D entry add r0, r0, #CACHE_DLINESIZE cmp r0, r1 @@ -304,7 +305,7 @@ ENTRY(arm1026_cache_fns) .long arm1026_flush_user_cache_range .long arm1026_coherent_kern_range .long arm1026_coherent_user_range - .long arm1026_flush_kern_dcache_page + .long arm1026_flush_kern_dcache_area .long arm1026_dma_inv_range .long arm1026_dma_clean_range .long arm1026_dma_flush_range diff --git a/arch/arm/mm/proc-arm920.S b/arch/arm/mm/proc-arm920.S index 2b7c197cc58d..471669e2d7cb 100644 --- a/arch/arm/mm/proc-arm920.S +++ b/arch/arm/mm/proc-arm920.S @@ -207,15 +207,16 @@ ENTRY(arm920_coherent_user_range) mov pc, lr /* - * flush_kern_dcache_page(void *page) + * flush_kern_dcache_area(void *addr, size_t size) * * Ensure no D cache aliasing occurs, either with itself or * the I cache * - * - addr - page aligned address + * - addr - kernel address + * - size - region size */ -ENTRY(arm920_flush_kern_dcache_page) - add r1, r0, #PAGE_SZ +ENTRY(arm920_flush_kern_dcache_area) + add r1, r0, r1 1: mcr p15, 0, r0, c7, c14, 1 @ clean+invalidate D entry add r0, r0, #CACHE_DLINESIZE cmp r0, r1 @@ -293,7 +294,7 @@ ENTRY(arm920_cache_fns) .long arm920_flush_user_cache_range .long arm920_coherent_kern_range .long arm920_coherent_user_range - .long arm920_flush_kern_dcache_page + .long arm920_flush_kern_dcache_area .long arm920_dma_inv_range .long arm920_dma_clean_range .long arm920_dma_flush_range diff --git a/arch/arm/mm/proc-arm922.S b/arch/arm/mm/proc-arm922.S index 06a1aa4e3398..ee111b00fa41 100644 --- a/arch/arm/mm/proc-arm922.S +++ b/arch/arm/mm/proc-arm922.S @@ -209,15 +209,16 @@ ENTRY(arm922_coherent_user_range) mov pc, lr /* - * flush_kern_dcache_page(void *page) + * flush_kern_dcache_area(void *addr, size_t size) * * Ensure no D cache aliasing occurs, either with itself or * the I cache * - * - addr - page aligned address + * - addr - kernel address + * - size - region size */ -ENTRY(arm922_flush_kern_dcache_page) - add r1, r0, #PAGE_SZ +ENTRY(arm922_flush_kern_dcache_area) + add r1, r0, r1 1: mcr p15, 0, r0, c7, c14, 1 @ clean+invalidate D entry add r0, r0, #CACHE_DLINESIZE cmp r0, r1 @@ -295,7 +296,7 @@ ENTRY(arm922_cache_fns) .long arm922_flush_user_cache_range .long arm922_coherent_kern_range .long arm922_coherent_user_range - .long arm922_flush_kern_dcache_page + .long arm922_flush_kern_dcache_area .long arm922_dma_inv_range .long arm922_dma_clean_range .long arm922_dma_flush_range diff --git a/arch/arm/mm/proc-arm925.S b/arch/arm/mm/proc-arm925.S index cb53435a85ae..8deb5bde58e4 100644 --- a/arch/arm/mm/proc-arm925.S +++ b/arch/arm/mm/proc-arm925.S @@ -251,15 +251,16 @@ ENTRY(arm925_coherent_user_range) mov pc, lr /* - * flush_kern_dcache_page(void *page) + * flush_kern_dcache_area(void *addr, size_t size) * * Ensure no D cache aliasing occurs, either with itself or * the I cache * - * - addr - page aligned address + * - addr - kernel address + * - size - region size */ -ENTRY(arm925_flush_kern_dcache_page) - add r1, r0, #PAGE_SZ +ENTRY(arm925_flush_kern_dcache_area) + add r1, r0, r1 1: mcr p15, 0, r0, c7, c14, 1 @ clean+invalidate D entry add r0, r0, #CACHE_DLINESIZE cmp r0, r1 @@ -346,7 +347,7 @@ ENTRY(arm925_cache_fns) .long arm925_flush_user_cache_range .long arm925_coherent_kern_range .long arm925_coherent_user_range - .long arm925_flush_kern_dcache_page + .long arm925_flush_kern_dcache_area .long arm925_dma_inv_range .long arm925_dma_clean_range .long arm925_dma_flush_range diff --git a/arch/arm/mm/proc-arm926.S b/arch/arm/mm/proc-arm926.S index 1c4848704bb3..64db6e275a44 100644 --- a/arch/arm/mm/proc-arm926.S +++ b/arch/arm/mm/proc-arm926.S @@ -214,15 +214,16 @@ ENTRY(arm926_coherent_user_range) mov pc, lr /* - * flush_kern_dcache_page(void *page) + * flush_kern_dcache_area(void *addr, size_t size) * * Ensure no D cache aliasing occurs, either with itself or * the I cache * - * - addr - page aligned address + * - addr - kernel address + * - size - region size */ -ENTRY(arm926_flush_kern_dcache_page) - add r1, r0, #PAGE_SZ +ENTRY(arm926_flush_kern_dcache_area) + add r1, r0, r1 1: mcr p15, 0, r0, c7, c14, 1 @ clean+invalidate D entry add r0, r0, #CACHE_DLINESIZE cmp r0, r1 @@ -309,7 +310,7 @@ ENTRY(arm926_cache_fns) .long arm926_flush_user_cache_range .long arm926_coherent_kern_range .long arm926_coherent_user_range - .long arm926_flush_kern_dcache_page + .long arm926_flush_kern_dcache_area .long arm926_dma_inv_range .long arm926_dma_clean_range .long arm926_dma_flush_range diff --git a/arch/arm/mm/proc-arm940.S b/arch/arm/mm/proc-arm940.S index 5b0f8464c8f2..8196b9f401fb 100644 --- a/arch/arm/mm/proc-arm940.S +++ b/arch/arm/mm/proc-arm940.S @@ -141,14 +141,15 @@ ENTRY(arm940_coherent_user_range) /* FALLTHROUGH */ /* - * flush_kern_dcache_page(void *page) + * flush_kern_dcache_area(void *addr, size_t size) * * Ensure no D cache aliasing occurs, either with itself or * the I cache * - * - addr - page aligned address + * - addr - kernel address + * - size - region size */ -ENTRY(arm940_flush_kern_dcache_page) +ENTRY(arm940_flush_kern_dcache_area) mov ip, #0 mov r1, #(CACHE_DSEGMENTS - 1) << 4 @ 4 segments 1: orr r3, r1, #(CACHE_DENTRIES - 1) << 26 @ 64 entries @@ -238,7 +239,7 @@ ENTRY(arm940_cache_fns) .long arm940_flush_user_cache_range .long arm940_coherent_kern_range .long arm940_coherent_user_range - .long arm940_flush_kern_dcache_page + .long arm940_flush_kern_dcache_area .long arm940_dma_inv_range .long arm940_dma_clean_range .long arm940_dma_flush_range diff --git a/arch/arm/mm/proc-arm946.S b/arch/arm/mm/proc-arm946.S index 40c0449a139b..9a951239c86c 100644 --- a/arch/arm/mm/proc-arm946.S +++ b/arch/arm/mm/proc-arm946.S @@ -183,16 +183,17 @@ ENTRY(arm946_coherent_user_range) mov pc, lr /* - * flush_kern_dcache_page(void *page) + * flush_kern_dcache_area(void *addr, size_t size) * * Ensure no D cache aliasing occurs, either with itself or * the I cache * - * - addr - page aligned address + * - addr - kernel address + * - size - region size * (same as arm926) */ -ENTRY(arm946_flush_kern_dcache_page) - add r1, r0, #PAGE_SZ +ENTRY(arm946_flush_kern_dcache_area) + add r1, r0, r1 1: mcr p15, 0, r0, c7, c14, 1 @ clean+invalidate D entry add r0, r0, #CACHE_DLINESIZE cmp r0, r1 @@ -280,7 +281,7 @@ ENTRY(arm946_cache_fns) .long arm946_flush_user_cache_range .long arm946_coherent_kern_range .long arm946_coherent_user_range - .long arm946_flush_kern_dcache_page + .long arm946_flush_kern_dcache_area .long arm946_dma_inv_range .long arm946_dma_clean_range .long arm946_dma_flush_range diff --git a/arch/arm/mm/proc-feroceon.S b/arch/arm/mm/proc-feroceon.S index d0d7795200fc..dbc39383e66a 100644 --- a/arch/arm/mm/proc-feroceon.S +++ b/arch/arm/mm/proc-feroceon.S @@ -226,16 +226,17 @@ ENTRY(feroceon_coherent_user_range) mov pc, lr /* - * flush_kern_dcache_page(void *page) + * flush_kern_dcache_area(void *addr, size_t size) * * Ensure no D cache aliasing occurs, either with itself or * the I cache * - * - addr - page aligned address + * - addr - kernel address + * - size - region size */ .align 5 -ENTRY(feroceon_flush_kern_dcache_page) - add r1, r0, #PAGE_SZ +ENTRY(feroceon_flush_kern_dcache_area) + add r1, r0, r1 1: mcr p15, 0, r0, c7, c14, 1 @ clean+invalidate D entry add r0, r0, #CACHE_DLINESIZE cmp r0, r1 @@ -246,7 +247,7 @@ ENTRY(feroceon_flush_kern_dcache_page) mov pc, lr .align 5 -ENTRY(feroceon_range_flush_kern_dcache_page) +ENTRY(feroceon_range_flush_kern_dcache_area) mrs r2, cpsr add r1, r0, #PAGE_SZ - CACHE_DLINESIZE @ top addr is inclusive orr r3, r2, #PSR_I_BIT @@ -372,7 +373,7 @@ ENTRY(feroceon_cache_fns) .long feroceon_flush_user_cache_range .long feroceon_coherent_kern_range .long feroceon_coherent_user_range - .long feroceon_flush_kern_dcache_page + .long feroceon_flush_kern_dcache_area .long feroceon_dma_inv_range .long feroceon_dma_clean_range .long feroceon_dma_flush_range @@ -383,7 +384,7 @@ ENTRY(feroceon_range_cache_fns) .long feroceon_flush_user_cache_range .long feroceon_coherent_kern_range .long feroceon_coherent_user_range - .long feroceon_range_flush_kern_dcache_page + .long feroceon_range_flush_kern_dcache_area .long feroceon_range_dma_inv_range .long feroceon_range_dma_clean_range .long feroceon_range_dma_flush_range diff --git a/arch/arm/mm/proc-mohawk.S b/arch/arm/mm/proc-mohawk.S index 52b5fd74fbb3..9674d36cc97d 100644 --- a/arch/arm/mm/proc-mohawk.S +++ b/arch/arm/mm/proc-mohawk.S @@ -186,15 +186,16 @@ ENTRY(mohawk_coherent_user_range) mov pc, lr /* - * flush_kern_dcache_page(void *page) + * flush_kern_dcache_area(void *addr, size_t size) * * Ensure no D cache aliasing occurs, either with itself or * the I cache * - * - addr - page aligned address + * - addr - kernel address + * - size - region size */ -ENTRY(mohawk_flush_kern_dcache_page) - add r1, r0, #PAGE_SZ +ENTRY(mohawk_flush_kern_dcache_area) + add r1, r0, r1 1: mcr p15, 0, r0, c7, c14, 1 @ clean+invalidate D entry add r0, r0, #CACHE_DLINESIZE cmp r0, r1 @@ -273,7 +274,7 @@ ENTRY(mohawk_cache_fns) .long mohawk_flush_user_cache_range .long mohawk_coherent_kern_range .long mohawk_coherent_user_range - .long mohawk_flush_kern_dcache_page + .long mohawk_flush_kern_dcache_area .long mohawk_dma_inv_range .long mohawk_dma_clean_range .long mohawk_dma_flush_range diff --git a/arch/arm/mm/proc-syms.c b/arch/arm/mm/proc-syms.c index ac5c80062b70..3e6210b4d6d4 100644 --- a/arch/arm/mm/proc-syms.c +++ b/arch/arm/mm/proc-syms.c @@ -27,8 +27,7 @@ EXPORT_SYMBOL(__cpuc_flush_kern_all); EXPORT_SYMBOL(__cpuc_flush_user_all); EXPORT_SYMBOL(__cpuc_flush_user_range); EXPORT_SYMBOL(__cpuc_coherent_kern_range); -EXPORT_SYMBOL(__cpuc_flush_dcache_page); -EXPORT_SYMBOL(dmac_inv_range); /* because of flush_ioremap_region() */ +EXPORT_SYMBOL(__cpuc_flush_dcache_area); #else EXPORT_SYMBOL(cpu_cache); #endif diff --git a/arch/arm/mm/proc-v6.S b/arch/arm/mm/proc-v6.S index 5485c821101c..395cc90c6613 100644 --- a/arch/arm/mm/proc-v6.S +++ b/arch/arm/mm/proc-v6.S @@ -254,10 +254,9 @@ __pj4_v6_proc_info: .long 0x560f5810 .long 0xff0ffff0 .long PMD_TYPE_SECT | \ - PMD_SECT_BUFFERABLE | \ - PMD_SECT_CACHEABLE | \ PMD_SECT_AP_WRITE | \ - PMD_SECT_AP_READ + PMD_SECT_AP_READ | \ + PMD_FLAGS .long PMD_TYPE_SECT | \ PMD_SECT_XN | \ PMD_SECT_AP_WRITE | \ diff --git a/arch/arm/mm/proc-xsc3.S b/arch/arm/mm/proc-xsc3.S index fab134e29826..96456f548798 100644 --- a/arch/arm/mm/proc-xsc3.S +++ b/arch/arm/mm/proc-xsc3.S @@ -226,15 +226,16 @@ ENTRY(xsc3_coherent_user_range) mov pc, lr /* - * flush_kern_dcache_page(void *page) + * flush_kern_dcache_area(void *addr, size_t size) * * Ensure no D cache aliasing occurs, either with itself or * the I cache. * - * - addr - page aligned address + * - addr - kernel address + * - size - region size */ -ENTRY(xsc3_flush_kern_dcache_page) - add r1, r0, #PAGE_SZ +ENTRY(xsc3_flush_kern_dcache_area) + add r1, r0, r1 1: mcr p15, 0, r0, c7, c14, 1 @ clean/invalidate L1 D line add r0, r0, #CACHELINESIZE cmp r0, r1 @@ -309,7 +310,7 @@ ENTRY(xsc3_cache_fns) .long xsc3_flush_user_cache_range .long xsc3_coherent_kern_range .long xsc3_coherent_user_range - .long xsc3_flush_kern_dcache_page + .long xsc3_flush_kern_dcache_area .long xsc3_dma_inv_range .long xsc3_dma_clean_range .long xsc3_dma_flush_range diff --git a/arch/arm/mm/proc-xscale.S b/arch/arm/mm/proc-xscale.S index f056c283682d..93df47265f2d 100644 --- a/arch/arm/mm/proc-xscale.S +++ b/arch/arm/mm/proc-xscale.S @@ -284,15 +284,16 @@ ENTRY(xscale_coherent_user_range) mov pc, lr /* - * flush_kern_dcache_page(void *page) + * flush_kern_dcache_area(void *addr, size_t size) * * Ensure no D cache aliasing occurs, either with itself or * the I cache * - * - addr - page aligned address + * - addr - kernel address + * - size - region size */ -ENTRY(xscale_flush_kern_dcache_page) - add r1, r0, #PAGE_SZ +ENTRY(xscale_flush_kern_dcache_area) + add r1, r0, r1 1: mcr p15, 0, r0, c7, c10, 1 @ clean D entry mcr p15, 0, r0, c7, c6, 1 @ invalidate D entry add r0, r0, #CACHELINESIZE @@ -368,7 +369,7 @@ ENTRY(xscale_cache_fns) .long xscale_flush_user_cache_range .long xscale_coherent_kern_range .long xscale_coherent_user_range - .long xscale_flush_kern_dcache_page + .long xscale_flush_kern_dcache_area .long xscale_dma_inv_range .long xscale_dma_clean_range .long xscale_dma_flush_range @@ -392,7 +393,7 @@ ENTRY(xscale_80200_A0_A1_cache_fns) .long xscale_flush_user_cache_range .long xscale_coherent_kern_range .long xscale_coherent_user_range - .long xscale_flush_kern_dcache_page + .long xscale_flush_kern_dcache_area .long xscale_dma_flush_range .long xscale_dma_clean_range .long xscale_dma_flush_range diff --git a/arch/arm/tools/mach-types b/arch/arm/tools/mach-types index 07b976da6174..c3a74ce24ef6 100644 --- a/arch/arm/tools/mach-types +++ b/arch/arm/tools/mach-types @@ -12,7 +12,7 @@ # # http://www.arm.linux.org.uk/developer/machines/?action=new # -# Last update: Wed Nov 25 22:14:58 2009 +# Last update: Wed Dec 16 20:06:34 2009 # # machine_is_xxx CONFIG_xxxx MACH_TYPE_xxx number # @@ -1776,6 +1776,7 @@ cybook3 MACH_CYBOOK3 CYBOOK3 1784 wdg002 MACH_WDG002 WDG002 1785 sg560adsl MACH_SG560ADSL SG560ADSL 1786 nextio_n2800_ica MACH_NEXTIO_N2800_ICA NEXTIO_N2800_ICA 1787 +dove_db MACH_DOVE_DB DOVE_DB 1788 marvell_newdb MACH_MARVELL_NEWDB MARVELL_NEWDB 1789 vandihud MACH_VANDIHUD VANDIHUD 1790 magx_e8 MACH_MAGX_E8 MAGX_E8 1791 @@ -2536,3 +2537,44 @@ c3ax03 MACH_C3AX03 C3AX03 2549 mxt_td60 MACH_MXT_TD60 MXT_TD60 2550 esyx MACH_ESYX ESYX 2551 bulldog MACH_BULLDOG BULLDOG 2553 +derell_me2000 MACH_DERELL_ME2000 DERELL_ME2000 2554 +bcmring_base MACH_BCMRING_BASE BCMRING_BASE 2555 +bcmring_evm MACH_BCMRING_EVM BCMRING_EVM 2556 +bcmring_evm_jazz MACH_BCMRING_EVM_JAZZ BCMRING_EVM_JAZZ 2557 +bcmring_sp MACH_BCMRING_SP BCMRING_SP 2558 +bcmring_sv MACH_BCMRING_SV BCMRING_SV 2559 +bcmring_sv_jazz MACH_BCMRING_SV_JAZZ BCMRING_SV_JAZZ 2560 +bcmring_tablet MACH_BCMRING_TABLET BCMRING_TABLET 2561 +bcmring_vp MACH_BCMRING_VP BCMRING_VP 2562 +bcmring_evm_seikor MACH_BCMRING_EVM_SEIKOR BCMRING_EVM_SEIKOR 2563 +bcmring_sp_wqvga MACH_BCMRING_SP_WQVGA BCMRING_SP_WQVGA 2564 +bcmring_custom MACH_BCMRING_CUSTOM BCMRING_CUSTOM 2565 +acer_s200 MACH_ACER_S200 ACER_S200 2566 +bt270 MACH_BT270 BT270 2567 +iseo MACH_ISEO ISEO 2568 +cezanne MACH_CEZANNE CEZANNE 2569 +lucca MACH_LUCCA LUCCA 2570 +supersmart MACH_SUPERSMART SUPERSMART 2571 +magnolia2 MACH_MAGNOLIA2 MAGNOLIA2 2573 +emxx MACH_EMXX EMXX 2574 +outlaw MACH_OUTLAW OUTLAW 2575 +riot_bei2 MACH_RIOT_BEI2 RIOT_BEI2 2576 +riot_vox MACH_RIOT_VOX RIOT_VOX 2577 +riot_x37 MACH_RIOT_X37 RIOT_X37 2578 +mega25mx MACH_MEGA25MX MEGA25MX 2579 +benzina2 MACH_BENZINA2 BENZINA2 2580 +ignite MACH_IGNITE IGNITE 2581 +foggia MACH_FOGGIA FOGGIA 2582 +arezzo MACH_AREZZO AREZZO 2583 +leica_skywalker MACH_LEICA_SKYWALKER LEICA_SKYWALKER 2584 +jacinto2_jamr MACH_JACINTO2_JAMR JACINTO2_JAMR 2585 +gts_nova MACH_GTS_NOVA GTS_NOVA 2586 +p3600 MACH_P3600 P3600 2587 +dlt2 MACH_DLT2 DLT2 2588 +df3120 MACH_DF3120 DF3120 2589 +ecucore_9g20 MACH_ECUCORE_9G20 ECUCORE_9G20 2590 +nautel_lpc3240 MACH_NAUTEL_LPC3240 NAUTEL_LPC3240 2591 +glacier MACH_GLACIER GLACIER 2592 +phrazer_bulldog MACH_PHRAZER_BULLDOG PHRAZER_BULLDOG 2593 +omap3_bulldog MACH_OMAP3_BULLDOG OMAP3_BULLDOG 2594 +pca101 MACH_PCA101 PCA101 2595 diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 95ccbe377f9c..bf28945c610d 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -998,6 +998,23 @@ config SENSORS_LIS3_SPI will be called lis3lv02d and a specific module for the SPI transport is called lis3lv02d_spi. +config SENSORS_LIS3_I2C + tristate "STMicroeletronics LIS3LV02Dx three-axis digital accelerometer (I2C)" + depends on I2C && INPUT + select INPUT_POLLDEV + default n + help + This driver provides support for the LIS3LV02Dx accelerometer connected + via I2C. The accelerometer data is readable via + /sys/devices/platform/lis3lv02d. + + This driver also provides an absolute input class device, allowing + the device to act as a pinball machine-esque joystick. + + This driver can also be built as modules. If so, the core module + will be called lis3lv02d and a specific module for the I2C transport + is called lis3lv02d_i2c. + config SENSORS_APPLESMC tristate "Apple SMC (Motion sensor, light sensor, keyboard backlight)" depends on INPUT && X86 diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 33c2ee105284..4131e253f96a 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -55,6 +55,7 @@ obj-$(CONFIG_SENSORS_IT87) += it87.o obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o obj-$(CONFIG_SENSORS_LIS3LV02D) += lis3lv02d.o hp_accel.o obj-$(CONFIG_SENSORS_LIS3_SPI) += lis3lv02d.o lis3lv02d_spi.o +obj-$(CONFIG_SENSORS_LIS3_I2C) += lis3lv02d.o lis3lv02d_i2c.o obj-$(CONFIG_SENSORS_LM63) += lm63.o obj-$(CONFIG_SENSORS_LM70) += lm70.o obj-$(CONFIG_SENSORS_LM73) += lm73.o diff --git a/drivers/hwmon/lis3lv02d_i2c.c b/drivers/hwmon/lis3lv02d_i2c.c new file mode 100644 index 000000000000..dc1f5402c1d7 --- /dev/null +++ b/drivers/hwmon/lis3lv02d_i2c.c @@ -0,0 +1,183 @@ +/* + * drivers/hwmon/lis3lv02d_i2c.c + * + * Implements I2C interface for lis3lv02d (STMicroelectronics) accelerometer. + * Driver is based on corresponding SPI driver written by Daniel Mack + * (lis3lv02d_spi.c (C) 2009 Daniel Mack <daniel@caiaq.de> ). + * + * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + * + * Contact: Samu Onkalo <samu.p.onkalo@nokia.com> + * + * 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. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include "lis3lv02d.h" + +#define DRV_NAME "lis3lv02d_i2c" + +static inline s32 lis3_i2c_write(struct lis3lv02d *lis3, int reg, u8 value) +{ + struct i2c_client *c = lis3->bus_priv; + return i2c_smbus_write_byte_data(c, reg, value); +} + +static inline s32 lis3_i2c_read(struct lis3lv02d *lis3, int reg, u8 *v) +{ + struct i2c_client *c = lis3->bus_priv; + *v = i2c_smbus_read_byte_data(c, reg); + return 0; +} + +static int lis3_i2c_init(struct lis3lv02d *lis3) +{ + u8 reg; + int ret; + + /* power up the device */ + ret = lis3->read(lis3, CTRL_REG1, ®); + if (ret < 0) + return ret; + + reg |= CTRL1_PD0; + return lis3->write(lis3, CTRL_REG1, reg); +} + +/* Default axis mapping but it can be overwritten by platform data */ +static struct axis_conversion lis3lv02d_axis_map = { LIS3_DEV_X, + LIS3_DEV_Y, + LIS3_DEV_Z }; + +static int __devinit lis3lv02d_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret = 0; + struct lis3lv02d_platform_data *pdata = client->dev.platform_data; + + if (pdata) { + if (pdata->axis_x) + lis3lv02d_axis_map.x = pdata->axis_x; + + if (pdata->axis_y) + lis3lv02d_axis_map.y = pdata->axis_y; + + if (pdata->axis_z) + lis3lv02d_axis_map.z = pdata->axis_z; + + if (pdata->setup_resources) + ret = pdata->setup_resources(); + + if (ret) + goto fail; + } + + lis3_dev.pdata = pdata; + lis3_dev.bus_priv = client; + lis3_dev.init = lis3_i2c_init; + lis3_dev.read = lis3_i2c_read; + lis3_dev.write = lis3_i2c_write; + lis3_dev.irq = client->irq; + lis3_dev.ac = lis3lv02d_axis_map; + + i2c_set_clientdata(client, &lis3_dev); + ret = lis3lv02d_init_device(&lis3_dev); +fail: + return ret; +} + +static int __devexit lis3lv02d_i2c_remove(struct i2c_client *client) +{ + struct lis3lv02d *lis3 = i2c_get_clientdata(client); + struct lis3lv02d_platform_data *pdata = client->dev.platform_data; + + if (pdata && pdata->release_resources) + pdata->release_resources(); + + lis3lv02d_joystick_disable(); + lis3lv02d_poweroff(lis3); + + return lis3lv02d_remove_fs(&lis3_dev); +} + +#ifdef CONFIG_PM +static int lis3lv02d_i2c_suspend(struct i2c_client *client, pm_message_t mesg) +{ + struct lis3lv02d *lis3 = i2c_get_clientdata(client); + + if (!lis3->pdata->wakeup_flags) + lis3lv02d_poweroff(lis3); + return 0; +} + +static int lis3lv02d_i2c_resume(struct i2c_client *client) +{ + struct lis3lv02d *lis3 = i2c_get_clientdata(client); + + if (!lis3->pdata->wakeup_flags) + lis3lv02d_poweron(lis3); + return 0; +} + +static void lis3lv02d_i2c_shutdown(struct i2c_client *client) +{ + lis3lv02d_i2c_suspend(client, PMSG_SUSPEND); +} +#else +#define lis3lv02d_i2c_suspend NULL +#define lis3lv02d_i2c_resume NULL +#define lis3lv02d_i2c_shutdown NULL +#endif + +static const struct i2c_device_id lis3lv02d_id[] = { + {"lis3lv02d", 0 }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, lis3lv02d_id); + +static struct i2c_driver lis3lv02d_i2c_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, + .suspend = lis3lv02d_i2c_suspend, + .shutdown = lis3lv02d_i2c_shutdown, + .resume = lis3lv02d_i2c_resume, + .probe = lis3lv02d_i2c_probe, + .remove = __devexit_p(lis3lv02d_i2c_remove), + .id_table = lis3lv02d_id, +}; + +static int __init lis3lv02d_init(void) +{ + return i2c_add_driver(&lis3lv02d_i2c_driver); +} + +static void __exit lis3lv02d_exit(void) +{ + i2c_del_driver(&lis3lv02d_i2c_driver); +} + +MODULE_AUTHOR("Nokia Corporation"); +MODULE_DESCRIPTION("lis3lv02d I2C interface"); +MODULE_LICENSE("GPL"); + +module_init(lis3lv02d_init); +module_exit(lis3lv02d_exit); diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index e4f599f20e38..8a0e1ec95e4a 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -229,6 +229,12 @@ config LEDS_PWM help This option enables support for pwm driven LEDs +config LEDS_REGULATOR + tristate "REGULATOR driven LED support" + depends on LEDS_CLASS && REGULATOR + help + This option enables support for regulator driven LEDs. + config LEDS_BD2802 tristate "LED driver for BD2802 RGB LED" depends on LEDS_CLASS && I2C @@ -236,6 +242,33 @@ config LEDS_BD2802 This option enables support for BD2802GU RGB LED driver chips accessed via the I2C bus. +config LEDS_INTEL_SS4200 + tristate "LED driver for Intel NAS SS4200 series" + depends on LEDS_CLASS && PCI && DMI + help + This option enables support for the Intel SS4200 series of + Network Attached Storage servers. You may control the hard + drive or power LEDs on the front panel. Using this driver + can stop the front LED from blinking after startup. + +config LEDS_LT3593 + tristate "LED driver for LT3593 controllers" + depends on LEDS_CLASS && GENERIC_GPIO + help + This option enables support for LEDs driven by a Linear Technology + LT3593 controller. This controller uses a special one-wire pulse + coding protocol to set the brightness. + +config LEDS_ADP5520 + tristate "LED Support for ADP5520/ADP5501 PMIC" + depends on LEDS_CLASS && PMIC_ADP5520 + help + This option enables support for on-chip LED drivers found + on Analog Devices ADP5520/ADP5501 PMICs. + + To compile this driver as a module, choose M here: the module will + be called leds-adp5520. + comment "LED Triggers" config LEDS_TRIGGERS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 46d72704d606..9e63869d7c0d 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -29,6 +29,10 @@ obj-$(CONFIG_LEDS_DA903X) += leds-da903x.o obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o obj-$(CONFIG_LEDS_WM8350) += leds-wm8350.o obj-$(CONFIG_LEDS_PWM) += leds-pwm.o +obj-$(CONFIG_LEDS_REGULATOR) += leds-regulator.o +obj-$(CONFIG_LEDS_INTEL_SS4200) += leds-ss4200.o +obj-$(CONFIG_LEDS_LT3593) += leds-lt3593.o +obj-$(CONFIG_LEDS_ADP5520) += leds-adp5520.o # LED SPI Drivers obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o diff --git a/drivers/leds/leds-adp5520.c b/drivers/leds/leds-adp5520.c new file mode 100644 index 000000000000..a8f315902131 --- /dev/null +++ b/drivers/leds/leds-adp5520.c @@ -0,0 +1,230 @@ +/* + * LEDs driver for Analog Devices ADP5520/ADP5501 MFD PMICs + * + * Copyright 2009 Analog Devices Inc. + * + * Loosely derived from leds-da903x: + * Copyright (C) 2008 Compulab, Ltd. + * Mike Rapoport <mike@compulab.co.il> + * + * Copyright (C) 2006-2008 Marvell International Ltd. + * Eric Miao <eric.miao@marvell.com> + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/leds.h> +#include <linux/workqueue.h> +#include <linux/mfd/adp5520.h> + +struct adp5520_led { + struct led_classdev cdev; + struct work_struct work; + struct device *master; + enum led_brightness new_brightness; + int id; + int flags; +}; + +static void adp5520_led_work(struct work_struct *work) +{ + struct adp5520_led *led = container_of(work, struct adp5520_led, work); + adp5520_write(led->master, ADP5520_LED1_CURRENT + led->id - 1, + led->new_brightness >> 2); +} + +static void adp5520_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct adp5520_led *led; + + led = container_of(led_cdev, struct adp5520_led, cdev); + led->new_brightness = value; + schedule_work(&led->work); +} + +static int adp5520_led_setup(struct adp5520_led *led) +{ + struct device *dev = led->master; + int flags = led->flags; + int ret = 0; + + switch (led->id) { + case FLAG_ID_ADP5520_LED1_ADP5501_LED0: + ret |= adp5520_set_bits(dev, ADP5520_LED_TIME, + (flags >> ADP5520_FLAG_OFFT_SHIFT) & + ADP5520_FLAG_OFFT_MASK); + ret |= adp5520_set_bits(dev, ADP5520_LED_CONTROL, + ADP5520_LED1_EN); + break; + case FLAG_ID_ADP5520_LED2_ADP5501_LED1: + ret |= adp5520_set_bits(dev, ADP5520_LED_TIME, + ((flags >> ADP5520_FLAG_OFFT_SHIFT) & + ADP5520_FLAG_OFFT_MASK) << 2); + ret |= adp5520_clr_bits(dev, ADP5520_LED_CONTROL, + ADP5520_R3_MODE); + ret |= adp5520_set_bits(dev, ADP5520_LED_CONTROL, + ADP5520_LED2_EN); + break; + case FLAG_ID_ADP5520_LED3_ADP5501_LED2: + ret |= adp5520_set_bits(dev, ADP5520_LED_TIME, + ((flags >> ADP5520_FLAG_OFFT_SHIFT) & + ADP5520_FLAG_OFFT_MASK) << 4); + ret |= adp5520_clr_bits(dev, ADP5520_LED_CONTROL, + ADP5520_C3_MODE); + ret |= adp5520_set_bits(dev, ADP5520_LED_CONTROL, + ADP5520_LED3_EN); + break; + } + + return ret; +} + +static int __devinit adp5520_led_prepare(struct platform_device *pdev) +{ + struct adp5520_leds_platform_data *pdata = pdev->dev.platform_data; + struct device *dev = pdev->dev.parent; + int ret = 0; + + ret |= adp5520_write(dev, ADP5520_LED1_CURRENT, 0); + ret |= adp5520_write(dev, ADP5520_LED2_CURRENT, 0); + ret |= adp5520_write(dev, ADP5520_LED3_CURRENT, 0); + ret |= adp5520_write(dev, ADP5520_LED_TIME, pdata->led_on_time << 6); + ret |= adp5520_write(dev, ADP5520_LED_FADE, FADE_VAL(pdata->fade_in, + pdata->fade_out)); + + return ret; +} + +static int __devinit adp5520_led_probe(struct platform_device *pdev) +{ + struct adp5520_leds_platform_data *pdata = pdev->dev.platform_data; + struct adp5520_led *led, *led_dat; + struct led_info *cur_led; + int ret, i; + + if (pdata == NULL) { + dev_err(&pdev->dev, "missing platform data\n"); + return -ENODEV; + } + + if (pdata->num_leds > ADP5520_01_MAXLEDS) { + dev_err(&pdev->dev, "can't handle more than %d LEDS\n", + ADP5520_01_MAXLEDS); + return -EFAULT; + } + + led = kzalloc(sizeof(*led) * pdata->num_leds, GFP_KERNEL); + if (led == NULL) { + dev_err(&pdev->dev, "failed to alloc memory\n"); + return -ENOMEM; + } + + ret = adp5520_led_prepare(pdev); + + if (ret) { + dev_err(&pdev->dev, "failed to write\n"); + goto err_free; + } + + for (i = 0; i < pdata->num_leds; ++i) { + cur_led = &pdata->leds[i]; + led_dat = &led[i]; + + led_dat->cdev.name = cur_led->name; + led_dat->cdev.default_trigger = cur_led->default_trigger; + led_dat->cdev.brightness_set = adp5520_led_set; + led_dat->cdev.brightness = LED_OFF; + + if (cur_led->flags & ADP5520_FLAG_LED_MASK) + led_dat->flags = cur_led->flags; + else + led_dat->flags = i + 1; + + led_dat->id = led_dat->flags & ADP5520_FLAG_LED_MASK; + + led_dat->master = pdev->dev.parent; + led_dat->new_brightness = LED_OFF; + + INIT_WORK(&led_dat->work, adp5520_led_work); + + ret = led_classdev_register(led_dat->master, &led_dat->cdev); + if (ret) { + dev_err(&pdev->dev, "failed to register LED %d\n", + led_dat->id); + goto err; + } + + ret = adp5520_led_setup(led_dat); + if (ret) { + dev_err(&pdev->dev, "failed to write\n"); + i++; + goto err; + } + } + + platform_set_drvdata(pdev, led); + return 0; + +err: + if (i > 0) { + for (i = i - 1; i >= 0; i--) { + led_classdev_unregister(&led[i].cdev); + cancel_work_sync(&led[i].work); + } + } + +err_free: + kfree(led); + return ret; +} + +static int __devexit adp5520_led_remove(struct platform_device *pdev) +{ + struct adp5520_leds_platform_data *pdata = pdev->dev.platform_data; + struct adp5520_led *led; + int i; + + led = platform_get_drvdata(pdev); + + adp5520_clr_bits(led->master, ADP5520_LED_CONTROL, + ADP5520_LED1_EN | ADP5520_LED2_EN | ADP5520_LED3_EN); + + for (i = 0; i < pdata->num_leds; i++) { + led_classdev_unregister(&led[i].cdev); + cancel_work_sync(&led[i].work); + } + + kfree(led); + return 0; +} + +static struct platform_driver adp5520_led_driver = { + .driver = { + .name = "adp5520-led", + .owner = THIS_MODULE, + }, + .probe = adp5520_led_probe, + .remove = __devexit_p(adp5520_led_remove), +}; + +static int __init adp5520_led_init(void) +{ + return platform_driver_register(&adp5520_led_driver); +} +module_init(adp5520_led_init); + +static void __exit adp5520_led_exit(void) +{ + platform_driver_unregister(&adp5520_led_driver); +} +module_exit(adp5520_led_exit); + +MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); +MODULE_DESCRIPTION("LEDS ADP5520(01) Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:adp5520-led"); diff --git a/drivers/leds/leds-alix2.c b/drivers/leds/leds-alix2.c index 731d4eef3425..f59ffadf5125 100644 --- a/drivers/leds/leds-alix2.c +++ b/drivers/leds/leds-alix2.c @@ -11,11 +11,24 @@ #include <linux/module.h> #include <linux/platform_device.h> #include <linux/string.h> +#include <linux/pci.h> static int force = 0; module_param(force, bool, 0444); MODULE_PARM_DESC(force, "Assume system has ALIX.2/ALIX.3 style LEDs"); +#define MSR_LBAR_GPIO 0x5140000C +#define CS5535_GPIO_SIZE 256 + +static u32 gpio_base; + +static struct pci_device_id divil_pci[] = { + { PCI_DEVICE(PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_CS5535_ISA) }, + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_CS5536_ISA) }, + { } /* NULL entry */ +}; +MODULE_DEVICE_TABLE(pci, divil_pci); + struct alix_led { struct led_classdev cdev; unsigned short port; @@ -30,9 +43,9 @@ static void alix_led_set(struct led_classdev *led_cdev, container_of(led_cdev, struct alix_led, cdev); if (brightness) - outl(led_dev->on_value, led_dev->port); + outl(led_dev->on_value, gpio_base + led_dev->port); else - outl(led_dev->off_value, led_dev->port); + outl(led_dev->off_value, gpio_base + led_dev->port); } static struct alix_led alix_leds[] = { @@ -41,7 +54,7 @@ static struct alix_led alix_leds[] = { .name = "alix:1", .brightness_set = alix_led_set, }, - .port = 0x6100, + .port = 0x00, .on_value = 1 << 22, .off_value = 1 << 6, }, @@ -50,7 +63,7 @@ static struct alix_led alix_leds[] = { .name = "alix:2", .brightness_set = alix_led_set, }, - .port = 0x6180, + .port = 0x80, .on_value = 1 << 25, .off_value = 1 << 9, }, @@ -59,7 +72,7 @@ static struct alix_led alix_leds[] = { .name = "alix:3", .brightness_set = alix_led_set, }, - .port = 0x6180, + .port = 0x80, .on_value = 1 << 27, .off_value = 1 << 11, }, @@ -101,64 +114,104 @@ static struct platform_driver alix_led_driver = { }, }; -static int __init alix_present(void) +static int __init alix_present(unsigned long bios_phys, + const char *alix_sig, + size_t alix_sig_len) { - const unsigned long bios_phys = 0x000f0000; const size_t bios_len = 0x00010000; - const char alix_sig[] = "PC Engines ALIX."; - const size_t alix_sig_len = sizeof(alix_sig) - 1; - const char *bios_virt; const char *scan_end; const char *p; - int ret = 0; + char name[64]; if (force) { printk(KERN_NOTICE "%s: forced to skip BIOS test, " "assume system has ALIX.2 style LEDs\n", KBUILD_MODNAME); - ret = 1; - goto out; + return 1; } bios_virt = phys_to_virt(bios_phys); scan_end = bios_virt + bios_len - (alix_sig_len + 2); for (p = bios_virt; p < scan_end; p++) { const char *tail; + char *a; - if (memcmp(p, alix_sig, alix_sig_len) != 0) { + if (memcmp(p, alix_sig, alix_sig_len) != 0) continue; - } + + memcpy(name, p, sizeof(name)); + + /* remove the first \0 character from string */ + a = strchr(name, '\0'); + if (a) + *a = ' '; + + /* cut the string at a newline */ + a = strchr(name, '\r'); + if (a) + *a = '\0'; tail = p + alix_sig_len; - if ((tail[0] == '2' || tail[0] == '3') && tail[1] == '\0') { + if ((tail[0] == '2' || tail[0] == '3')) { printk(KERN_INFO "%s: system is recognized as \"%s\"\n", - KBUILD_MODNAME, p); - ret = 1; - break; + KBUILD_MODNAME, name); + return 1; } } -out: - return ret; + return 0; } static struct platform_device *pdev; -static int __init alix_led_init(void) +static int __init alix_pci_led_init(void) { - int ret; + u32 low, hi; - if (!alix_present()) { - ret = -ENODEV; - goto out; + if (pci_dev_present(divil_pci) == 0) { + printk(KERN_WARNING KBUILD_MODNAME": DIVIL not found\n"); + return -ENODEV; } - /* enable output on GPIO for LED 1,2,3 */ - outl(1 << 6, 0x6104); - outl(1 << 9, 0x6184); - outl(1 << 11, 0x6184); + /* Grab the GPIO I/O range */ + rdmsr(MSR_LBAR_GPIO, low, hi); + + /* Check the mask and whether GPIO is enabled (sanity check) */ + if (hi != 0x0000f001) { + printk(KERN_WARNING KBUILD_MODNAME": GPIO not enabled\n"); + return -ENODEV; + } + + /* Mask off the IO base address */ + gpio_base = low & 0x0000ff00; + + if (!request_region(gpio_base, CS5535_GPIO_SIZE, KBUILD_MODNAME)) { + printk(KERN_ERR KBUILD_MODNAME": can't allocate I/O for GPIO\n"); + return -ENODEV; + } + + /* Set GPIO function to output */ + outl(1 << 6, gpio_base + 0x04); + outl(1 << 9, gpio_base + 0x84); + outl(1 << 11, gpio_base + 0x84); + + return 0; +} + +static int __init alix_led_init(void) +{ + int ret = -ENODEV; + const char tinybios_sig[] = "PC Engines ALIX."; + const char coreboot_sig[] = "PC Engines\0ALIX."; + + if (alix_present(0xf0000, tinybios_sig, sizeof(tinybios_sig) - 1) || + alix_present(0x500, coreboot_sig, sizeof(coreboot_sig) - 1)) + ret = alix_pci_led_init(); + + if (ret < 0) + return ret; pdev = platform_device_register_simple(KBUILD_MODNAME, -1, NULL, 0); if (!IS_ERR(pdev)) { @@ -168,7 +221,6 @@ static int __init alix_led_init(void) } else ret = PTR_ERR(pdev); -out: return ret; } @@ -176,6 +228,7 @@ static void __exit alix_led_exit(void) { platform_device_unregister(pdev); platform_driver_unregister(&alix_led_driver); + release_region(gpio_base, CS5535_GPIO_SIZE); } module_init(alix_led_init); diff --git a/drivers/leds/leds-cobalt-qube.c b/drivers/leds/leds-cobalt-qube.c index 8816806accd2..da5fb016b1a5 100644 --- a/drivers/leds/leds-cobalt-qube.c +++ b/drivers/leds/leds-cobalt-qube.c @@ -31,7 +31,7 @@ static struct led_classdev qube_front_led = { .name = "qube::front", .brightness = LED_FULL, .brightness_set = qube_front_led_set, - .default_trigger = "ide-disk", + .default_trigger = "default-on", }; static int __devinit cobalt_qube_led_probe(struct platform_device *pdev) @@ -43,7 +43,7 @@ static int __devinit cobalt_qube_led_probe(struct platform_device *pdev) if (!res) return -EBUSY; - led_port = ioremap(res->start, res->end - res->start + 1); + led_port = ioremap(res->start, resource_size(res)); if (!led_port) return -ENOMEM; diff --git a/drivers/leds/leds-cobalt-raq.c b/drivers/leds/leds-cobalt-raq.c index defc212105f3..438d48384636 100644 --- a/drivers/leds/leds-cobalt-raq.c +++ b/drivers/leds/leds-cobalt-raq.c @@ -84,7 +84,7 @@ static int __devinit cobalt_raq_led_probe(struct platform_device *pdev) if (!res) return -EBUSY; - led_port = ioremap(res->start, res->end - res->start + 1); + led_port = ioremap(res->start, resource_size(res)); if (!led_port) return -ENOMEM; diff --git a/drivers/leds/leds-lt3593.c b/drivers/leds/leds-lt3593.c new file mode 100644 index 000000000000..fee40a841959 --- /dev/null +++ b/drivers/leds/leds-lt3593.c @@ -0,0 +1,217 @@ +/* + * LEDs driver for LT3593 controllers + * + * See the datasheet at http://cds.linear.com/docs/Datasheet/3593f.pdf + * + * Copyright (c) 2009 Daniel Mack <daniel@caiaq.de> + * + * Based on leds-gpio.c, + * + * Copyright (C) 2007 8D Technologies inc. + * Raphael Assenat <raph@8d.com> + * Copyright (C) 2008 Freescale Semiconductor, Inc. + * + * 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 <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/leds.h> +#include <linux/workqueue.h> +#include <linux/delay.h> +#include <linux/gpio.h> + +struct lt3593_led_data { + struct led_classdev cdev; + unsigned gpio; + struct work_struct work; + u8 new_level; +}; + +static void lt3593_led_work(struct work_struct *work) +{ + int pulses; + struct lt3593_led_data *led_dat = + container_of(work, struct lt3593_led_data, work); + + /* + * The LT3593 resets its internal current level register to the maximum + * level on the first falling edge on the control pin. Each following + * falling edge decreases the current level by 625uA. Up to 32 pulses + * can be sent, so the maximum power reduction is 20mA. + * After a timeout of 128us, the value is taken from the register and + * applied is to the output driver. + */ + + if (led_dat->new_level == 0) { + gpio_set_value_cansleep(led_dat->gpio, 0); + return; + } + + pulses = 32 - (led_dat->new_level * 32) / 255; + + if (pulses == 0) { + gpio_set_value_cansleep(led_dat->gpio, 0); + mdelay(1); + gpio_set_value_cansleep(led_dat->gpio, 1); + return; + } + + gpio_set_value_cansleep(led_dat->gpio, 1); + + while (pulses--) { + gpio_set_value_cansleep(led_dat->gpio, 0); + udelay(1); + gpio_set_value_cansleep(led_dat->gpio, 1); + udelay(1); + } +} + +static void lt3593_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct lt3593_led_data *led_dat = + container_of(led_cdev, struct lt3593_led_data, cdev); + + led_dat->new_level = value; + schedule_work(&led_dat->work); +} + +static int __devinit create_lt3593_led(const struct gpio_led *template, + struct lt3593_led_data *led_dat, struct device *parent) +{ + int ret, state; + + /* skip leds on GPIOs that aren't available */ + if (!gpio_is_valid(template->gpio)) { + printk(KERN_INFO "%s: skipping unavailable LT3593 LED at gpio %d (%s)\n", + KBUILD_MODNAME, template->gpio, template->name); + return 0; + } + + ret = gpio_request(template->gpio, template->name); + if (ret < 0) + return ret; + + led_dat->cdev.name = template->name; + led_dat->cdev.default_trigger = template->default_trigger; + led_dat->gpio = template->gpio; + + led_dat->cdev.brightness_set = lt3593_led_set; + + state = (template->default_state == LEDS_GPIO_DEFSTATE_ON); + led_dat->cdev.brightness = state ? LED_FULL : LED_OFF; + + if (!template->retain_state_suspended) + led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME; + + ret = gpio_direction_output(led_dat->gpio, state); + if (ret < 0) + goto err; + + INIT_WORK(&led_dat->work, lt3593_led_work); + + ret = led_classdev_register(parent, &led_dat->cdev); + if (ret < 0) + goto err; + + printk(KERN_INFO "%s: registered LT3593 LED '%s' at GPIO %d\n", + KBUILD_MODNAME, template->name, template->gpio); + + return 0; + +err: + gpio_free(led_dat->gpio); + return ret; +} + +static void delete_lt3593_led(struct lt3593_led_data *led) +{ + if (!gpio_is_valid(led->gpio)) + return; + + led_classdev_unregister(&led->cdev); + cancel_work_sync(&led->work); + gpio_free(led->gpio); +} + +static int __devinit lt3593_led_probe(struct platform_device *pdev) +{ + struct gpio_led_platform_data *pdata = pdev->dev.platform_data; + struct lt3593_led_data *leds_data; + int i, ret = 0; + + if (!pdata) + return -EBUSY; + + leds_data = kzalloc(sizeof(struct lt3593_led_data) * pdata->num_leds, + GFP_KERNEL); + if (!leds_data) + return -ENOMEM; + + for (i = 0; i < pdata->num_leds; i++) { + ret = create_lt3593_led(&pdata->leds[i], &leds_data[i], + &pdev->dev); + if (ret < 0) + goto err; + } + + platform_set_drvdata(pdev, leds_data); + + return 0; + +err: + for (i = i - 1; i >= 0; i--) + delete_lt3593_led(&leds_data[i]); + + kfree(leds_data); + + return ret; +} + +static int __devexit lt3593_led_remove(struct platform_device *pdev) +{ + int i; + struct gpio_led_platform_data *pdata = pdev->dev.platform_data; + struct lt3593_led_data *leds_data; + + leds_data = platform_get_drvdata(pdev); + + for (i = 0; i < pdata->num_leds; i++) + delete_lt3593_led(&leds_data[i]); + + kfree(leds_data); + + return 0; +} + +static struct platform_driver lt3593_led_driver = { + .probe = lt3593_led_probe, + .remove = __devexit_p(lt3593_led_remove), + .driver = { + .name = "leds-lt3593", + .owner = THIS_MODULE, + }, +}; + +MODULE_ALIAS("platform:leds-lt3593"); + +static int __init lt3593_led_init(void) +{ + return platform_driver_register(<3593_led_driver); +} + +static void __exit lt3593_led_exit(void) +{ + platform_driver_unregister(<3593_led_driver); +} + +module_init(lt3593_led_init); +module_exit(lt3593_led_exit); + +MODULE_AUTHOR("Daniel Mack <daniel@caiaq.de>"); +MODULE_DESCRIPTION("LED driver for LT3593 controllers"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/leds-pwm.c b/drivers/leds/leds-pwm.c index cdfdc8714e10..88b1dd091cfb 100644 --- a/drivers/leds/leds-pwm.c +++ b/drivers/leds/leds-pwm.c @@ -27,7 +27,6 @@ struct led_pwm_data { struct pwm_device *pwm; unsigned int active_low; unsigned int period; - unsigned int max_brightness; }; static void led_pwm_set(struct led_classdev *led_cdev, @@ -35,7 +34,7 @@ static void led_pwm_set(struct led_classdev *led_cdev, { struct led_pwm_data *led_dat = container_of(led_cdev, struct led_pwm_data, cdev); - unsigned int max = led_dat->max_brightness; + unsigned int max = led_dat->cdev.max_brightness; unsigned int period = led_dat->period; if (brightness == 0) { @@ -77,10 +76,10 @@ static int led_pwm_probe(struct platform_device *pdev) led_dat->cdev.name = cur_led->name; led_dat->cdev.default_trigger = cur_led->default_trigger; led_dat->active_low = cur_led->active_low; - led_dat->max_brightness = cur_led->max_brightness; led_dat->period = cur_led->pwm_period_ns; led_dat->cdev.brightness_set = led_pwm_set; led_dat->cdev.brightness = LED_OFF; + led_dat->cdev.max_brightness = cur_led->max_brightness; led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME; ret = led_classdev_register(&pdev->dev, &led_dat->cdev); diff --git a/drivers/leds/leds-regulator.c b/drivers/leds/leds-regulator.c new file mode 100644 index 000000000000..7f00de3ef922 --- /dev/null +++ b/drivers/leds/leds-regulator.c @@ -0,0 +1,242 @@ +/* + * leds-regulator.c - LED class driver for regulator driven LEDs. + * + * Copyright (C) 2009 Antonio Ospite <ospite@studenti.unina.it> + * + * Inspired by leds-wm8350 driver. + * + * 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 <linux/module.h> +#include <linux/err.h> +#include <linux/workqueue.h> +#include <linux/leds.h> +#include <linux/leds-regulator.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> + +#define to_regulator_led(led_cdev) \ + container_of(led_cdev, struct regulator_led, cdev) + +struct regulator_led { + struct led_classdev cdev; + enum led_brightness value; + int enabled; + struct mutex mutex; + struct work_struct work; + + struct regulator *vcc; +}; + +static inline int led_regulator_get_max_brightness(struct regulator *supply) +{ + int ret; + int voltage = regulator_list_voltage(supply, 0); + + if (voltage <= 0) + return 1; + + /* even if regulator can't change voltages, + * we still assume it can change status + * and the LED can be turned on and off. + */ + ret = regulator_set_voltage(supply, voltage, voltage); + if (ret < 0) + return 1; + + return regulator_count_voltages(supply); +} + +static int led_regulator_get_voltage(struct regulator *supply, + enum led_brightness brightness) +{ + if (brightness == 0) + return -EINVAL; + + return regulator_list_voltage(supply, brightness - 1); +} + + +static void regulator_led_enable(struct regulator_led *led) +{ + int ret; + + if (led->enabled) + return; + + ret = regulator_enable(led->vcc); + if (ret != 0) { + dev_err(led->cdev.dev, "Failed to enable vcc: %d\n", ret); + return; + } + + led->enabled = 1; +} + +static void regulator_led_disable(struct regulator_led *led) +{ + int ret; + + if (!led->enabled) + return; + + ret = regulator_disable(led->vcc); + if (ret != 0) { + dev_err(led->cdev.dev, "Failed to disable vcc: %d\n", ret); + return; + } + + led->enabled = 0; +} + +static void regulator_led_set_value(struct regulator_led *led) +{ + int voltage; + int ret; + + mutex_lock(&led->mutex); + + if (led->value == LED_OFF) { + regulator_led_disable(led); + goto out; + } + + if (led->cdev.max_brightness > 1) { + voltage = led_regulator_get_voltage(led->vcc, led->value); + dev_dbg(led->cdev.dev, "brightness: %d voltage: %d\n", + led->value, voltage); + + ret = regulator_set_voltage(led->vcc, voltage, voltage); + if (ret != 0) + dev_err(led->cdev.dev, "Failed to set voltage %d: %d\n", + voltage, ret); + } + + regulator_led_enable(led); + +out: + mutex_unlock(&led->mutex); +} + +static void led_work(struct work_struct *work) +{ + struct regulator_led *led; + + led = container_of(work, struct regulator_led, work); + regulator_led_set_value(led); +} + +static void regulator_led_brightness_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct regulator_led *led = to_regulator_led(led_cdev); + + led->value = value; + schedule_work(&led->work); +} + +static int __devinit regulator_led_probe(struct platform_device *pdev) +{ + struct led_regulator_platform_data *pdata = pdev->dev.platform_data; + struct regulator_led *led; + struct regulator *vcc; + int ret = 0; + + if (pdata == NULL) { + dev_err(&pdev->dev, "no platform data\n"); + return -ENODEV; + } + + vcc = regulator_get_exclusive(&pdev->dev, "vled"); + if (IS_ERR(vcc)) { + dev_err(&pdev->dev, "Cannot get vcc for %s\n", pdata->name); + return PTR_ERR(vcc); + } + + led = kzalloc(sizeof(*led), GFP_KERNEL); + if (led == NULL) { + ret = -ENOMEM; + goto err_vcc; + } + + led->cdev.max_brightness = led_regulator_get_max_brightness(vcc); + if (pdata->brightness > led->cdev.max_brightness) { + dev_err(&pdev->dev, "Invalid default brightness %d\n", + pdata->brightness); + ret = -EINVAL; + goto err_led; + } + led->value = pdata->brightness; + + led->cdev.brightness_set = regulator_led_brightness_set; + led->cdev.name = pdata->name; + led->cdev.flags |= LED_CORE_SUSPENDRESUME; + led->vcc = vcc; + + mutex_init(&led->mutex); + INIT_WORK(&led->work, led_work); + + platform_set_drvdata(pdev, led); + + ret = led_classdev_register(&pdev->dev, &led->cdev); + if (ret < 0) { + cancel_work_sync(&led->work); + goto err_led; + } + + /* to expose the default value to userspace */ + led->cdev.brightness = led->value; + + /* Set the default led status */ + regulator_led_set_value(led); + + return 0; + +err_led: + kfree(led); +err_vcc: + regulator_put(vcc); + return ret; +} + +static int __devexit regulator_led_remove(struct platform_device *pdev) +{ + struct regulator_led *led = platform_get_drvdata(pdev); + + led_classdev_unregister(&led->cdev); + cancel_work_sync(&led->work); + regulator_led_disable(led); + regulator_put(led->vcc); + kfree(led); + return 0; +} + +static struct platform_driver regulator_led_driver = { + .driver = { + .name = "leds-regulator", + .owner = THIS_MODULE, + }, + .probe = regulator_led_probe, + .remove = __devexit_p(regulator_led_remove), +}; + +static int __init regulator_led_init(void) +{ + return platform_driver_register(®ulator_led_driver); +} +module_init(regulator_led_init); + +static void __exit regulator_led_exit(void) +{ + platform_driver_unregister(®ulator_led_driver); +} +module_exit(regulator_led_exit); + +MODULE_AUTHOR("Antonio Ospite <ospite@studenti.unina.it>"); +MODULE_DESCRIPTION("Regulator driven LED driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:leds-regulator"); diff --git a/drivers/leds/leds-ss4200.c b/drivers/leds/leds-ss4200.c new file mode 100644 index 000000000000..97f04984c1ca --- /dev/null +++ b/drivers/leds/leds-ss4200.c @@ -0,0 +1,556 @@ +/* + * SS4200-E Hardware API + * Copyright (c) 2009, Intel Corporation. + * Copyright IBM Corporation, 2009 + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Author: Dave Hansen <dave@sr71.net> + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/dmi.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/kernel.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/types.h> +#include <linux/uaccess.h> + +MODULE_AUTHOR("Rodney Girod <rgirod@confocus.com>, Dave Hansen <dave@sr71.net>"); +MODULE_DESCRIPTION("Intel NAS/Home Server ICH7 GPIO Driver"); +MODULE_LICENSE("GPL"); + +/* + * ICH7 LPC/GPIO PCI Config register offsets + */ +#define PMBASE 0x040 +#define GPIO_BASE 0x048 +#define GPIO_CTRL 0x04c +#define GPIO_EN 0x010 + +/* + * The ICH7 GPIO register block is 64 bytes in size. + */ +#define ICH7_GPIO_SIZE 64 + +/* + * Define register offsets within the ICH7 register block. + */ +#define GPIO_USE_SEL 0x000 +#define GP_IO_SEL 0x004 +#define GP_LVL 0x00c +#define GPO_BLINK 0x018 +#define GPI_INV 0x030 +#define GPIO_USE_SEL2 0x034 +#define GP_IO_SEL2 0x038 +#define GP_LVL2 0x03c + +/* + * PCI ID of the Intel ICH7 LPC Device within which the GPIO block lives. + */ +static struct pci_device_id ich7_lpc_pci_id[] = +{ + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_0) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_1) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_30) }, + { } /* NULL entry */ +}; + +MODULE_DEVICE_TABLE(pci, ich7_lpc_pci_id); + +static int __init ss4200_led_dmi_callback(const struct dmi_system_id *id) +{ + pr_info("detected '%s'\n", id->ident); + return 1; +} + +static unsigned int __initdata nodetect; +module_param_named(nodetect, nodetect, bool, 0); +MODULE_PARM_DESC(nodetect, "Skip DMI-based hardware detection"); + +/* + * struct nas_led_whitelist - List of known good models + * + * Contains the known good models this driver is compatible with. + * When adding a new model try to be as strict as possible. This + * makes it possible to keep the false positives (the model is + * detected as working, but in reality it is not) as low as + * possible. + */ +static struct dmi_system_id __initdata nas_led_whitelist[] = { + { + .callback = ss4200_led_dmi_callback, + .ident = "Intel SS4200-E", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Intel"), + DMI_MATCH(DMI_PRODUCT_NAME, "SS4200-E"), + DMI_MATCH(DMI_PRODUCT_VERSION, "1.00.00") + } + }, +}; + +/* + * Base I/O address assigned to the Power Management register block + */ +static u32 g_pm_io_base; + +/* + * Base I/O address assigned to the ICH7 GPIO register block + */ +static u32 nas_gpio_io_base; + +/* + * When we successfully register a region, we are returned a resource. + * We use these to identify which regions we need to release on our way + * back out. + */ +static struct resource *gp_gpio_resource; + +struct nasgpio_led { + char *name; + u32 gpio_bit; + struct led_classdev led_cdev; +}; + +/* + * gpio_bit(s) are the ICH7 GPIO bit assignments + */ +static struct nasgpio_led nasgpio_leds[] = { + { .name = "hdd1:blue:sata", .gpio_bit = 0 }, + { .name = "hdd1:amber:sata", .gpio_bit = 1 }, + { .name = "hdd2:blue:sata", .gpio_bit = 2 }, + { .name = "hdd2:amber:sata", .gpio_bit = 3 }, + { .name = "hdd3:blue:sata", .gpio_bit = 4 }, + { .name = "hdd3:amber:sata", .gpio_bit = 5 }, + { .name = "hdd4:blue:sata", .gpio_bit = 6 }, + { .name = "hdd4:amber:sata", .gpio_bit = 7 }, + { .name = "power:blue:power", .gpio_bit = 27}, + { .name = "power:amber:power", .gpio_bit = 28}, +}; + +#define NAS_RECOVERY 0x00000400 /* GPIO10 */ + +static struct nasgpio_led * +led_classdev_to_nasgpio_led(struct led_classdev *led_cdev) +{ + return container_of(led_cdev, struct nasgpio_led, led_cdev); +} + +static struct nasgpio_led *get_led_named(char *name) +{ + int i; + for (i = 0; i < ARRAY_SIZE(nasgpio_leds); i++) { + if (strcmp(nasgpio_leds[i].name, name)) + continue; + return &nasgpio_leds[i]; + } + return NULL; +} + +/* + * This protects access to the gpio ports. + */ +static DEFINE_SPINLOCK(nasgpio_gpio_lock); + +/* + * There are two gpio ports, one for blinking and the other + * for power. @port tells us if we're doing blinking or + * power control. + * + * Caller must hold nasgpio_gpio_lock + */ +static void __nasgpio_led_set_attr(struct led_classdev *led_cdev, + u32 port, u32 value) +{ + struct nasgpio_led *led = led_classdev_to_nasgpio_led(led_cdev); + u32 gpio_out; + + gpio_out = inl(nas_gpio_io_base + port); + if (value) + gpio_out |= (1<<led->gpio_bit); + else + gpio_out &= ~(1<<led->gpio_bit); + + outl(gpio_out, nas_gpio_io_base + port); +} + +static void nasgpio_led_set_attr(struct led_classdev *led_cdev, + u32 port, u32 value) +{ + spin_lock(&nasgpio_gpio_lock); + __nasgpio_led_set_attr(led_cdev, port, value); + spin_unlock(&nasgpio_gpio_lock); +} + +u32 nasgpio_led_get_attr(struct led_classdev *led_cdev, u32 port) +{ + struct nasgpio_led *led = led_classdev_to_nasgpio_led(led_cdev); + u32 gpio_in; + + spin_lock(&nasgpio_gpio_lock); + gpio_in = inl(nas_gpio_io_base + port); + spin_unlock(&nasgpio_gpio_lock); + if (gpio_in & (1<<led->gpio_bit)) + return 1; + return 0; +} + +/* + * There is actual brightness control in the hardware, + * but it is via smbus commands and not implemented + * in this driver. + */ +static void nasgpio_led_set_brightness(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + u32 setting = 0; + if (brightness >= LED_HALF) + setting = 1; + /* + * Hold the lock across both operations. This ensures + * consistency so that both the "turn off blinking" + * and "turn light off" operations complete as a set. + */ + spin_lock(&nasgpio_gpio_lock); + /* + * LED class documentation asks that past blink state + * be disabled when brightness is turned to zero. + */ + if (brightness == 0) + __nasgpio_led_set_attr(led_cdev, GPO_BLINK, 0); + __nasgpio_led_set_attr(led_cdev, GP_LVL, setting); + spin_unlock(&nasgpio_gpio_lock); +} + +static int nasgpio_led_set_blink(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + u32 setting = 1; + if (!(*delay_on == 0 && *delay_off == 0) && + !(*delay_on == 500 && *delay_off == 500)) + return -EINVAL; + /* + * These are very approximate. + */ + *delay_on = 500; + *delay_off = 500; + + nasgpio_led_set_attr(led_cdev, GPO_BLINK, setting); + + return 0; +} + + +/* + * Initialize the ICH7 GPIO registers for NAS usage. The BIOS should have + * already taken care of this, but we will do so in a non destructive manner + * so that we have what we need whether the BIOS did it or not. + */ +static int __devinit ich7_gpio_init(struct device *dev) +{ + int i; + u32 config_data = 0; + u32 all_nas_led = 0; + + for (i = 0; i < ARRAY_SIZE(nasgpio_leds); i++) + all_nas_led |= (1<<nasgpio_leds[i].gpio_bit); + + spin_lock(&nasgpio_gpio_lock); + /* + * We need to enable all of the GPIO lines used by the NAS box, + * so we will read the current Use Selection and add our usage + * to it. This should be benign with regard to the original + * BIOS configuration. + */ + config_data = inl(nas_gpio_io_base + GPIO_USE_SEL); + dev_dbg(dev, ": Data read from GPIO_USE_SEL = 0x%08x\n", config_data); + config_data |= all_nas_led + NAS_RECOVERY; + outl(config_data, nas_gpio_io_base + GPIO_USE_SEL); + config_data = inl(nas_gpio_io_base + GPIO_USE_SEL); + dev_dbg(dev, ": GPIO_USE_SEL = 0x%08x\n\n", config_data); + + /* + * The LED GPIO outputs need to be configured for output, so we + * will ensure that all LED lines are cleared for output and the + * RECOVERY line ready for input. This too should be benign with + * regard to BIOS configuration. + */ + config_data = inl(nas_gpio_io_base + GP_IO_SEL); + dev_dbg(dev, ": Data read from GP_IO_SEL = 0x%08x\n", + config_data); + config_data &= ~all_nas_led; + config_data |= NAS_RECOVERY; + outl(config_data, nas_gpio_io_base + GP_IO_SEL); + config_data = inl(nas_gpio_io_base + GP_IO_SEL); + dev_dbg(dev, ": GP_IO_SEL = 0x%08x\n", config_data); + + /* + * In our final system, the BIOS will initialize the state of all + * of the LEDs. For now, we turn them all off (or Low). + */ + config_data = inl(nas_gpio_io_base + GP_LVL); + dev_dbg(dev, ": Data read from GP_LVL = 0x%08x\n", config_data); + /* + * In our final system, the BIOS will initialize the blink state of all + * of the LEDs. For now, we turn blink off for all of them. + */ + config_data = inl(nas_gpio_io_base + GPO_BLINK); + dev_dbg(dev, ": Data read from GPO_BLINK = 0x%08x\n", config_data); + + /* + * At this moment, I am unsure if anything needs to happen with GPI_INV + */ + config_data = inl(nas_gpio_io_base + GPI_INV); + dev_dbg(dev, ": Data read from GPI_INV = 0x%08x\n", config_data); + + spin_unlock(&nasgpio_gpio_lock); + return 0; +} + +static void ich7_lpc_cleanup(struct device *dev) +{ + /* + * If we were given exclusive use of the GPIO + * I/O Address range, we must return it. + */ + if (gp_gpio_resource) { + dev_dbg(dev, ": Releasing GPIO I/O addresses\n"); + release_region(nas_gpio_io_base, ICH7_GPIO_SIZE); + gp_gpio_resource = NULL; + } +} + +/* + * The OS has determined that the LPC of the Intel ICH7 Southbridge is present + * so we can retrive the required operational information and prepare the GPIO. + */ +static struct pci_dev *nas_gpio_pci_dev; +static int __devinit ich7_lpc_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + int status; + u32 gc = 0; + + status = pci_enable_device(dev); + if (status) { + dev_err(&dev->dev, "pci_enable_device failed\n"); + return -EIO; + } + + nas_gpio_pci_dev = dev; + status = pci_read_config_dword(dev, PMBASE, &g_pm_io_base); + if (status) + goto out; + g_pm_io_base &= 0x00000ff80; + + status = pci_read_config_dword(dev, GPIO_CTRL, &gc); + if (!(GPIO_EN & gc)) { + status = -EEXIST; + dev_info(&dev->dev, + "ERROR: The LPC GPIO Block has not been enabled.\n"); + goto out; + } + + status = pci_read_config_dword(dev, GPIO_BASE, &nas_gpio_io_base); + if (0 > status) { + dev_info(&dev->dev, "Unable to read GPIOBASE.\n"); + goto out; + } + dev_dbg(&dev->dev, ": GPIOBASE = 0x%08x\n", nas_gpio_io_base); + nas_gpio_io_base &= 0x00000ffc0; + + /* + * Insure that we have exclusive access to the GPIO I/O address range. + */ + gp_gpio_resource = request_region(nas_gpio_io_base, ICH7_GPIO_SIZE, + KBUILD_MODNAME); + if (NULL == gp_gpio_resource) { + dev_info(&dev->dev, + "ERROR Unable to register GPIO I/O addresses.\n"); + status = -1; + goto out; + } + + /* + * Initialize the GPIO for NAS/Home Server Use + */ + ich7_gpio_init(&dev->dev); + +out: + if (status) { + ich7_lpc_cleanup(&dev->dev); + pci_disable_device(dev); + } + return status; +} + +static void ich7_lpc_remove(struct pci_dev *dev) +{ + ich7_lpc_cleanup(&dev->dev); + pci_disable_device(dev); +} + +/* + * pci_driver structure passed to the PCI modules + */ +static struct pci_driver nas_gpio_pci_driver = { + .name = KBUILD_MODNAME, + .id_table = ich7_lpc_pci_id, + .probe = ich7_lpc_probe, + .remove = ich7_lpc_remove, +}; + +static struct led_classdev *get_classdev_for_led_nr(int nr) +{ + struct nasgpio_led *nas_led = &nasgpio_leds[nr]; + struct led_classdev *led = &nas_led->led_cdev; + return led; +} + + +static void set_power_light_amber_noblink(void) +{ + struct nasgpio_led *amber = get_led_named("power:amber:power"); + struct nasgpio_led *blue = get_led_named("power:blue:power"); + + if (!amber || !blue) + return; + /* + * LED_OFF implies disabling future blinking + */ + pr_debug("setting blue off and amber on\n"); + + nasgpio_led_set_brightness(&blue->led_cdev, LED_OFF); + nasgpio_led_set_brightness(&amber->led_cdev, LED_FULL); +} + +static ssize_t nas_led_blink_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led = dev_get_drvdata(dev); + int blinking = 0; + if (nasgpio_led_get_attr(led, GPO_BLINK)) + blinking = 1; + return sprintf(buf, "%u\n", blinking); +} + +static ssize_t nas_led_blink_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int ret; + struct led_classdev *led = dev_get_drvdata(dev); + unsigned long blink_state; + + ret = strict_strtoul(buf, 10, &blink_state); + if (ret) + return ret; + + nasgpio_led_set_attr(led, GPO_BLINK, blink_state); + + return size; +} + +static DEVICE_ATTR(blink, 0644, nas_led_blink_show, nas_led_blink_store); + +static int register_nasgpio_led(int led_nr) +{ + int ret; + struct nasgpio_led *nas_led = &nasgpio_leds[led_nr]; + struct led_classdev *led = get_classdev_for_led_nr(led_nr); + + led->name = nas_led->name; + led->brightness = LED_OFF; + if (nasgpio_led_get_attr(led, GP_LVL)) + led->brightness = LED_FULL; + led->brightness_set = nasgpio_led_set_brightness; + led->blink_set = nasgpio_led_set_blink; + ret = led_classdev_register(&nas_gpio_pci_dev->dev, led); + if (ret) + return ret; + ret = device_create_file(led->dev, &dev_attr_blink); + if (ret) + led_classdev_unregister(led); + return ret; +} + +static void unregister_nasgpio_led(int led_nr) +{ + struct led_classdev *led = get_classdev_for_led_nr(led_nr); + led_classdev_unregister(led); + device_remove_file(led->dev, &dev_attr_blink); +} +/* + * module load/initialization + */ +static int __init nas_gpio_init(void) +{ + int i; + int ret = 0; + int nr_devices = 0; + + nr_devices = dmi_check_system(nas_led_whitelist); + if (nodetect) { + pr_info("skipping hardware autodetection\n"); + pr_info("Please send 'dmidecode' output to dave@sr71.net\n"); + nr_devices++; + } + + if (nr_devices <= 0) { + pr_info("no LED devices found\n"); + return -ENODEV; + } + + pr_info("registering PCI driver\n"); + ret = pci_register_driver(&nas_gpio_pci_driver); + if (ret) + return ret; + for (i = 0; i < ARRAY_SIZE(nasgpio_leds); i++) { + ret = register_nasgpio_led(i); + if (ret) + goto out_err; + } + /* + * When the system powers on, the BIOS leaves the power + * light blue and blinking. This will turn it solid + * amber once the driver is loaded. + */ + set_power_light_amber_noblink(); + return 0; +out_err: + for (; i >= 0; i--) + unregister_nasgpio_led(i); + pci_unregister_driver(&nas_gpio_pci_driver); + return ret; +} + +/* + * module unload + */ +static void __exit nas_gpio_exit(void) +{ + int i; + pr_info("Unregistering driver\n"); + for (i = 0; i < ARRAY_SIZE(nasgpio_leds); i++) + unregister_nasgpio_led(i); + pci_unregister_driver(&nas_gpio_pci_driver); +} + +module_init(nas_gpio_init); +module_exit(nas_gpio_exit); diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 1a7a9fc50ea1..e3551d20464f 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -203,6 +203,7 @@ config CS5535_MFGPT config CS5535_MFGPT_DEFAULT_IRQ int + depends on CS5535_MFGPT default 7 help MFGPTs on the CS5535 require an interrupt. The selected IRQ diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c index cdb845b68ab5..06b64085a355 100644 --- a/drivers/mmc/core/sdio.c +++ b/drivers/mmc/core/sdio.c @@ -516,7 +516,8 @@ int mmc_attach_sdio(struct mmc_host *host, u32 ocr) * The number of functions on the card is encoded inside * the ocr. */ - card->sdio_funcs = funcs = (ocr & 0x70000000) >> 28; + funcs = (ocr & 0x70000000) >> 28; + card->sdio_funcs = 0; /* * If needed, disconnect card detection pull-up resistor. @@ -528,7 +529,7 @@ int mmc_attach_sdio(struct mmc_host *host, u32 ocr) /* * Initialize (but don't add) all present functions. */ - for (i = 0;i < funcs;i++) { + for (i = 0; i < funcs; i++, card->sdio_funcs++) { err = sdio_init_func(host->card, i + 1); if (err) goto remove; diff --git a/drivers/mmc/core/sdio_bus.c b/drivers/mmc/core/sdio_bus.c index d37464e296a5..9e060c87e64d 100644 --- a/drivers/mmc/core/sdio_bus.c +++ b/drivers/mmc/core/sdio_bus.c @@ -248,12 +248,15 @@ int sdio_add_func(struct sdio_func *func) /* * Unregister a SDIO function with the driver model, and * (eventually) free it. + * This function can be called through error paths where sdio_add_func() was + * never executed (because a failure occurred at an earlier point). */ void sdio_remove_func(struct sdio_func *func) { - if (sdio_func_present(func)) - device_del(&func->dev); + if (!sdio_func_present(func)) + return; + device_del(&func->dev); put_device(&func->dev); } diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 9d405b181781..ce1d28884e29 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -44,6 +44,19 @@ config MMC_SDHCI_IO_ACCESSORS This is silent Kconfig symbol that is selected by the drivers that need to overwrite SDHCI IO memory accessors. +config MMC_SDHCI_BIG_ENDIAN_32BIT_BYTE_SWAPPER + bool + select MMC_SDHCI_IO_ACCESSORS + help + This option is selected by drivers running on big endian hosts + and performing I/O to a SDHCI controller through a bus that + implements a hardware byte swapper using a 32-bit datum. + This endian mapping mode is called "data invariance" and + has the effect of scrambling the addresses and formats of data + accessed in sizes other than the datum size. + + This is the case for the Freescale eSDHC and Nintendo Wii SDHCI. + config MMC_SDHCI_PCI tristate "SDHCI support on PCI bus" depends on MMC_SDHCI && PCI @@ -75,11 +88,29 @@ config MMC_RICOH_MMC 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. + Interfaces. + + If unsure, say N. + +config MMC_SDHCI_OF_ESDHC + bool "SDHCI OF support for the Freescale eSDHC controller" + depends on MMC_SDHCI_OF + select MMC_SDHCI_BIG_ENDIAN_32BIT_BYTE_SWAPPER + help + This selects the Freescale eSDHC controller support. + + If unsure, say N. + +config MMC_SDHCI_OF_HLWD + bool "SDHCI OF support for the Nintendo Wii SDHCI controllers" + depends on MMC_SDHCI_OF + select MMC_SDHCI_BIG_ENDIAN_32BIT_BYTE_SWAPPER + help + This selects the Secure Digital Host Controller Interface (SDHCI) + found in the "Hollywood" chipset of the Nintendo Wii video game + console. If unsure, say N. diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index ded4d8cdd9d7..3d253dd4240f 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -13,7 +13,6 @@ obj-$(CONFIG_MMC_MXC) += mxcmmc.o obj-$(CONFIG_MMC_SDHCI) += sdhci.o obj-$(CONFIG_MMC_SDHCI_PCI) += sdhci-pci.o obj-$(CONFIG_MMC_RICOH_MMC) += ricoh_mmc.o -obj-$(CONFIG_MMC_SDHCI_OF) += sdhci-of.o obj-$(CONFIG_MMC_SDHCI_PLTFM) += sdhci-pltfm.o obj-$(CONFIG_MMC_SDHCI_S3C) += sdhci-s3c.o obj-$(CONFIG_MMC_WBSD) += wbsd.o @@ -37,6 +36,11 @@ obj-$(CONFIG_MMC_CB710) += cb710-mmc.o obj-$(CONFIG_MMC_VIA_SDMMC) += via-sdmmc.o obj-$(CONFIG_SDH_BFIN) += bfin_sdh.o +obj-$(CONFIG_MMC_SDHCI_OF) += sdhci-of.o +sdhci-of-y := sdhci-of-core.o +sdhci-of-$(CONFIG_MMC_SDHCI_OF_ESDHC) += sdhci-of-esdhc.o +sdhci-of-$(CONFIG_MMC_SDHCI_OF_HLWD) += sdhci-of-hlwd.o + ifeq ($(CONFIG_CB710_DEBUG),y) CFLAGS-cb710-mmc += -DDEBUG endif diff --git a/drivers/mmc/host/sdhci-of.c b/drivers/mmc/host/sdhci-of-core.c index 01ab916c2802..55e33135edb4 100644 --- a/drivers/mmc/host/sdhci-of.c +++ b/drivers/mmc/host/sdhci-of-core.c @@ -22,62 +22,37 @@ #include <linux/of_platform.h> #include <linux/mmc/host.h> #include <asm/machdep.h> +#include "sdhci-of.h" #include "sdhci.h" -struct sdhci_of_data { - unsigned int quirks; - struct sdhci_ops ops; -}; - -struct sdhci_of_host { - unsigned int clock; - u16 xfer_mode_shadow; -}; +#ifdef CONFIG_MMC_SDHCI_BIG_ENDIAN_32BIT_BYTE_SWAPPER /* - * Ops and quirks for the Freescale eSDHC controller. + * These accessors are designed for big endian hosts doing I/O to + * little endian controllers incorporating a 32-bit hardware byte swapper. */ -#define ESDHC_DMA_SYSCTL 0x40c -#define ESDHC_DMA_SNOOP 0x00000040 - -#define ESDHC_SYSTEM_CONTROL 0x2c -#define ESDHC_CLOCK_MASK 0x0000fff0 -#define ESDHC_PREDIV_SHIFT 8 -#define ESDHC_DIVIDER_SHIFT 4 -#define ESDHC_CLOCK_PEREN 0x00000004 -#define ESDHC_CLOCK_HCKEN 0x00000002 -#define ESDHC_CLOCK_IPGEN 0x00000001 - -#define ESDHC_HOST_CONTROL_RES 0x05 - -static u32 esdhc_readl(struct sdhci_host *host, int reg) +u32 sdhci_be32bs_readl(struct sdhci_host *host, int reg) { return in_be32(host->ioaddr + reg); } -static u16 esdhc_readw(struct sdhci_host *host, int reg) +u16 sdhci_be32bs_readw(struct sdhci_host *host, int reg) { - u16 ret; - - if (unlikely(reg == SDHCI_HOST_VERSION)) - ret = in_be16(host->ioaddr + reg); - else - ret = in_be16(host->ioaddr + (reg ^ 0x2)); - return ret; + return in_be16(host->ioaddr + (reg ^ 0x2)); } -static u8 esdhc_readb(struct sdhci_host *host, int reg) +u8 sdhci_be32bs_readb(struct sdhci_host *host, int reg) { return in_8(host->ioaddr + (reg ^ 0x3)); } -static void esdhc_writel(struct sdhci_host *host, u32 val, int reg) +void sdhci_be32bs_writel(struct sdhci_host *host, u32 val, int reg) { out_be32(host->ioaddr + reg, val); } -static void esdhc_writew(struct sdhci_host *host, u16 val, int reg) +void sdhci_be32bs_writew(struct sdhci_host *host, u16 val, int reg) { struct sdhci_of_host *of_host = sdhci_priv(host); int base = reg & ~0x3; @@ -92,106 +67,21 @@ static void esdhc_writew(struct sdhci_host *host, u16 val, int reg) of_host->xfer_mode_shadow = val; return; case SDHCI_COMMAND: - esdhc_writel(host, val << 16 | of_host->xfer_mode_shadow, - SDHCI_TRANSFER_MODE); + sdhci_be32bs_writel(host, val << 16 | of_host->xfer_mode_shadow, + SDHCI_TRANSFER_MODE); return; - case SDHCI_BLOCK_SIZE: - /* - * Two last DMA bits are reserved, and first one is used for - * non-standard blksz of 4096 bytes that we don't support - * yet. So clear the DMA boundary bits. - */ - val &= ~SDHCI_MAKE_BLKSZ(0x7, 0); - /* fall through */ } clrsetbits_be32(host->ioaddr + base, 0xffff << shift, val << shift); } -static void esdhc_writeb(struct sdhci_host *host, u8 val, int reg) +void sdhci_be32bs_writeb(struct sdhci_host *host, u8 val, int reg) { int base = reg & ~0x3; int shift = (reg & 0x3) * 8; - /* Prevent SDHCI core from writing reserved bits (e.g. HISPD). */ - if (reg == SDHCI_HOST_CONTROL) - val &= ~ESDHC_HOST_CONTROL_RES; - clrsetbits_be32(host->ioaddr + base , 0xff << shift, val << shift); } - -static void esdhc_set_clock(struct sdhci_host *host, unsigned int clock) -{ - int pre_div = 2; - int div = 1; - - clrbits32(host->ioaddr + ESDHC_SYSTEM_CONTROL, ESDHC_CLOCK_IPGEN | - ESDHC_CLOCK_HCKEN | ESDHC_CLOCK_PEREN | ESDHC_CLOCK_MASK); - - if (clock == 0) - goto out; - - while (host->max_clk / pre_div / 16 > clock && pre_div < 256) - pre_div *= 2; - - while (host->max_clk / pre_div / div > clock && div < 16) - div++; - - dev_dbg(mmc_dev(host->mmc), "desired SD clock: %d, actual: %d\n", - clock, host->max_clk / pre_div / div); - - pre_div >>= 1; - div--; - - setbits32(host->ioaddr + ESDHC_SYSTEM_CONTROL, ESDHC_CLOCK_IPGEN | - ESDHC_CLOCK_HCKEN | ESDHC_CLOCK_PEREN | - div << ESDHC_DIVIDER_SHIFT | pre_div << ESDHC_PREDIV_SHIFT); - mdelay(100); -out: - host->clock = clock; -} - -static int esdhc_enable_dma(struct sdhci_host *host) -{ - setbits32(host->ioaddr + ESDHC_DMA_SYSCTL, ESDHC_DMA_SNOOP); - return 0; -} - -static unsigned int esdhc_get_max_clock(struct sdhci_host *host) -{ - struct sdhci_of_host *of_host = sdhci_priv(host); - - return of_host->clock; -} - -static unsigned int esdhc_get_min_clock(struct sdhci_host *host) -{ - struct sdhci_of_host *of_host = sdhci_priv(host); - - return of_host->clock / 256 / 16; -} - -static struct sdhci_of_data sdhci_esdhc = { - .quirks = SDHCI_QUIRK_FORCE_BLK_SZ_2048 | - SDHCI_QUIRK_BROKEN_CARD_DETECTION | - SDHCI_QUIRK_NO_BUSY_IRQ | - SDHCI_QUIRK_NONSTANDARD_CLOCK | - SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK | - SDHCI_QUIRK_PIO_NEEDS_DELAY | - SDHCI_QUIRK_RESTORE_IRQS_AFTER_RESET | - SDHCI_QUIRK_NO_CARD_NO_RESET, - .ops = { - .readl = esdhc_readl, - .readw = esdhc_readw, - .readb = esdhc_readb, - .writel = esdhc_writel, - .writew = esdhc_writew, - .writeb = esdhc_writeb, - .set_clock = esdhc_set_clock, - .enable_dma = esdhc_enable_dma, - .get_max_clock = esdhc_get_max_clock, - .get_min_clock = esdhc_get_min_clock, - }, -}; +#endif /* CONFIG_MMC_SDHCI_BIG_ENDIAN_32BIT_BYTE_SWAPPER */ #ifdef CONFIG_PM @@ -301,9 +191,14 @@ static int __devexit sdhci_of_remove(struct of_device *ofdev) } static const struct of_device_id sdhci_of_match[] = { +#ifdef CONFIG_MMC_SDHCI_OF_ESDHC { .compatible = "fsl,mpc8379-esdhc", .data = &sdhci_esdhc, }, { .compatible = "fsl,mpc8536-esdhc", .data = &sdhci_esdhc, }, { .compatible = "fsl,esdhc", .data = &sdhci_esdhc, }, +#endif +#ifdef CONFIG_MMC_SDHCI_OF_HLWD + { .compatible = "nintendo,hollywood-sdhci", .data = &sdhci_hlwd, }, +#endif { .compatible = "generic-sdhci", }, {}, }; diff --git a/drivers/mmc/host/sdhci-of-esdhc.c b/drivers/mmc/host/sdhci-of-esdhc.c new file mode 100644 index 000000000000..d5b11a17e648 --- /dev/null +++ b/drivers/mmc/host/sdhci-of-esdhc.c @@ -0,0 +1,143 @@ +/* + * Freescale eSDHC controller driver. + * + * Copyright (c) 2007 Freescale Semiconductor, Inc. + * Copyright (c) 2009 MontaVista Software, Inc. + * + * Authors: Xiaobo Xie <X.Xie@freescale.com> + * Anton Vorontsov <avorontsov@ru.mvista.com> + * + * 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. + */ + +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/mmc/host.h> +#include "sdhci-of.h" +#include "sdhci.h" + +/* + * Ops and quirks for the Freescale eSDHC controller. + */ + +#define ESDHC_DMA_SYSCTL 0x40c +#define ESDHC_DMA_SNOOP 0x00000040 + +#define ESDHC_SYSTEM_CONTROL 0x2c +#define ESDHC_CLOCK_MASK 0x0000fff0 +#define ESDHC_PREDIV_SHIFT 8 +#define ESDHC_DIVIDER_SHIFT 4 +#define ESDHC_CLOCK_PEREN 0x00000004 +#define ESDHC_CLOCK_HCKEN 0x00000002 +#define ESDHC_CLOCK_IPGEN 0x00000001 + +#define ESDHC_HOST_CONTROL_RES 0x05 + +static u16 esdhc_readw(struct sdhci_host *host, int reg) +{ + u16 ret; + + if (unlikely(reg == SDHCI_HOST_VERSION)) + ret = in_be16(host->ioaddr + reg); + else + ret = sdhci_be32bs_readw(host, reg); + return ret; +} + +static void esdhc_writew(struct sdhci_host *host, u16 val, int reg) +{ + if (reg == SDHCI_BLOCK_SIZE) { + /* + * Two last DMA bits are reserved, and first one is used for + * non-standard blksz of 4096 bytes that we don't support + * yet. So clear the DMA boundary bits. + */ + val &= ~SDHCI_MAKE_BLKSZ(0x7, 0); + } + sdhci_be32bs_writew(host, val, reg); +} + +static void esdhc_writeb(struct sdhci_host *host, u8 val, int reg) +{ + /* Prevent SDHCI core from writing reserved bits (e.g. HISPD). */ + if (reg == SDHCI_HOST_CONTROL) + val &= ~ESDHC_HOST_CONTROL_RES; + sdhci_be32bs_writeb(host, val, reg); +} + +static void esdhc_set_clock(struct sdhci_host *host, unsigned int clock) +{ + int pre_div = 2; + int div = 1; + + clrbits32(host->ioaddr + ESDHC_SYSTEM_CONTROL, ESDHC_CLOCK_IPGEN | + ESDHC_CLOCK_HCKEN | ESDHC_CLOCK_PEREN | ESDHC_CLOCK_MASK); + + if (clock == 0) + goto out; + + while (host->max_clk / pre_div / 16 > clock && pre_div < 256) + pre_div *= 2; + + while (host->max_clk / pre_div / div > clock && div < 16) + div++; + + dev_dbg(mmc_dev(host->mmc), "desired SD clock: %d, actual: %d\n", + clock, host->max_clk / pre_div / div); + + pre_div >>= 1; + div--; + + setbits32(host->ioaddr + ESDHC_SYSTEM_CONTROL, ESDHC_CLOCK_IPGEN | + ESDHC_CLOCK_HCKEN | ESDHC_CLOCK_PEREN | + div << ESDHC_DIVIDER_SHIFT | pre_div << ESDHC_PREDIV_SHIFT); + mdelay(100); +out: + host->clock = clock; +} + +static int esdhc_enable_dma(struct sdhci_host *host) +{ + setbits32(host->ioaddr + ESDHC_DMA_SYSCTL, ESDHC_DMA_SNOOP); + return 0; +} + +static unsigned int esdhc_get_max_clock(struct sdhci_host *host) +{ + struct sdhci_of_host *of_host = sdhci_priv(host); + + return of_host->clock; +} + +static unsigned int esdhc_get_min_clock(struct sdhci_host *host) +{ + struct sdhci_of_host *of_host = sdhci_priv(host); + + return of_host->clock / 256 / 16; +} + +struct sdhci_of_data sdhci_esdhc = { + .quirks = SDHCI_QUIRK_FORCE_BLK_SZ_2048 | + SDHCI_QUIRK_BROKEN_CARD_DETECTION | + SDHCI_QUIRK_NO_BUSY_IRQ | + SDHCI_QUIRK_NONSTANDARD_CLOCK | + SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK | + SDHCI_QUIRK_PIO_NEEDS_DELAY | + SDHCI_QUIRK_RESTORE_IRQS_AFTER_RESET | + SDHCI_QUIRK_NO_CARD_NO_RESET, + .ops = { + .readl = sdhci_be32bs_readl, + .readw = esdhc_readw, + .readb = sdhci_be32bs_readb, + .writel = sdhci_be32bs_writel, + .writew = esdhc_writew, + .writeb = esdhc_writeb, + .set_clock = esdhc_set_clock, + .enable_dma = esdhc_enable_dma, + .get_max_clock = esdhc_get_max_clock, + .get_min_clock = esdhc_get_min_clock, + }, +}; diff --git a/drivers/mmc/host/sdhci-of-hlwd.c b/drivers/mmc/host/sdhci-of-hlwd.c new file mode 100644 index 000000000000..35117f3ed757 --- /dev/null +++ b/drivers/mmc/host/sdhci-of-hlwd.c @@ -0,0 +1,65 @@ +/* + * drivers/mmc/host/sdhci-of-hlwd.c + * + * Nintendo Wii Secure Digital Host Controller Interface. + * Copyright (C) 2009 The GameCube Linux Team + * Copyright (C) 2009 Albert Herranz + * + * Based on sdhci-of-esdhc.c + * + * Copyright (c) 2007 Freescale Semiconductor, Inc. + * Copyright (c) 2009 MontaVista Software, Inc. + * + * Authors: Xiaobo Xie <X.Xie@freescale.com> + * Anton Vorontsov <avorontsov@ru.mvista.com> + * + * 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. + */ + +#include <linux/delay.h> +#include <linux/mmc/host.h> +#include "sdhci-of.h" +#include "sdhci.h" + +/* + * Ops and quirks for the Nintendo Wii SDHCI controllers. + */ + +/* + * We need a small delay after each write, or things go horribly wrong. + */ +#define SDHCI_HLWD_WRITE_DELAY 5 /* usecs */ + +static void sdhci_hlwd_writel(struct sdhci_host *host, u32 val, int reg) +{ + sdhci_be32bs_writel(host, val, reg); + udelay(SDHCI_HLWD_WRITE_DELAY); +} + +static void sdhci_hlwd_writew(struct sdhci_host *host, u16 val, int reg) +{ + sdhci_be32bs_writew(host, val, reg); + udelay(SDHCI_HLWD_WRITE_DELAY); +} + +static void sdhci_hlwd_writeb(struct sdhci_host *host, u8 val, int reg) +{ + sdhci_be32bs_writeb(host, val, reg); + udelay(SDHCI_HLWD_WRITE_DELAY); +} + +struct sdhci_of_data sdhci_hlwd = { + .quirks = SDHCI_QUIRK_32BIT_DMA_ADDR | + SDHCI_QUIRK_32BIT_DMA_SIZE, + .ops = { + .readl = sdhci_be32bs_readl, + .readw = sdhci_be32bs_readw, + .readb = sdhci_be32bs_readb, + .writel = sdhci_hlwd_writel, + .writew = sdhci_hlwd_writew, + .writeb = sdhci_hlwd_writeb, + }, +}; diff --git a/drivers/mmc/host/sdhci-of.h b/drivers/mmc/host/sdhci-of.h new file mode 100644 index 000000000000..ad09ad9915d8 --- /dev/null +++ b/drivers/mmc/host/sdhci-of.h @@ -0,0 +1,42 @@ +/* + * OpenFirmware bindings for Secure Digital Host Controller Interface. + * + * Copyright (c) 2007 Freescale Semiconductor, Inc. + * Copyright (c) 2009 MontaVista Software, Inc. + * + * Authors: Xiaobo Xie <X.Xie@freescale.com> + * Anton Vorontsov <avorontsov@ru.mvista.com> + * + * 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. + */ + +#ifndef __SDHCI_OF_H +#define __SDHCI_OF_H + +#include <linux/types.h> +#include "sdhci.h" + +struct sdhci_of_data { + unsigned int quirks; + struct sdhci_ops ops; +}; + +struct sdhci_of_host { + unsigned int clock; + u16 xfer_mode_shadow; +}; + +extern u32 sdhci_be32bs_readl(struct sdhci_host *host, int reg); +extern u16 sdhci_be32bs_readw(struct sdhci_host *host, int reg); +extern u8 sdhci_be32bs_readb(struct sdhci_host *host, int reg); +extern void sdhci_be32bs_writel(struct sdhci_host *host, u32 val, int reg); +extern void sdhci_be32bs_writew(struct sdhci_host *host, u16 val, int reg); +extern void sdhci_be32bs_writeb(struct sdhci_host *host, u8 val, int reg); + +extern struct sdhci_of_data sdhci_esdhc; +extern struct sdhci_of_data sdhci_hlwd; + +#endif /* __SDHCI_OF_H */ diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index ce5f1d73dc04..842f46f94284 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -8,6 +8,8 @@ * the Free Software Foundation; either version 2 of the License, or (at * your option) any later version. */ +#ifndef __SDHCI_H +#define __SDHCI_H #include <linux/scatterlist.h> #include <linux/compiler.h> @@ -408,3 +410,5 @@ extern void sdhci_remove_host(struct sdhci_host *host, int dead); extern int sdhci_suspend_host(struct sdhci_host *host, pm_message_t state); extern int sdhci_resume_host(struct sdhci_host *host); #endif + +#endif /* __SDHCI_H */ diff --git a/drivers/mtd/maps/pxa2xx-flash.c b/drivers/mtd/maps/pxa2xx-flash.c index 74fa075c838a..b13f6417b5b2 100644 --- a/drivers/mtd/maps/pxa2xx-flash.c +++ b/drivers/mtd/maps/pxa2xx-flash.c @@ -20,14 +20,23 @@ #include <asm/io.h> #include <mach/hardware.h> -#include <asm/cacheflush.h> #include <asm/mach/flash.h> +#define CACHELINESIZE 32 + static void pxa2xx_map_inval_cache(struct map_info *map, unsigned long from, ssize_t len) { - flush_ioremap_region(map->phys, map->cached, from, len); + unsigned long start = (unsigned long)map->cached + from; + unsigned long end = start + len; + + start &= ~(CACHELINESIZE - 1); + while (start < end) { + /* invalidate D cache line */ + asm volatile ("mcr p15, 0, %0, c7, c6, 1" : : "r" (start)); + start += CACHELINESIZE; + } } struct pxa2xx_flash_info { diff --git a/drivers/pcmcia/pxa2xx_base.c b/drivers/pcmcia/pxa2xx_base.c index 3aabf1e37988..76e640bccde8 100644 --- a/drivers/pcmcia/pxa2xx_base.c +++ b/drivers/pcmcia/pxa2xx_base.c @@ -291,7 +291,7 @@ static int pxa2xx_drv_pcmcia_probe(struct platform_device *dev) skt->nr = ops->first + i; skt->ops = ops; skt->socket.owner = ops->owner; - skt->socket.dev.parent = dev; + skt->socket.dev.parent = &dev->dev; skt->socket.pci_irq = NO_IRQ; ret = pxa2xx_drv_pcmcia_add_one(skt); @@ -304,8 +304,8 @@ static int pxa2xx_drv_pcmcia_probe(struct platform_device *dev) soc_pcmcia_remove_one(&sinfo->skt[i]); kfree(sinfo); } else { - pxa2xx_configure_sockets(dev); - dev_set_drvdata(dev, sinfo); + pxa2xx_configure_sockets(&dev->dev); + dev_set_drvdata(&dev->dev, sinfo); } return ret; diff --git a/drivers/rtc/rtc-ds1305.c b/drivers/rtc/rtc-ds1305.c index 259db7f3535b..9630e7d3314e 100644 --- a/drivers/rtc/rtc-ds1305.c +++ b/drivers/rtc/rtc-ds1305.c @@ -778,6 +778,8 @@ static int __devinit ds1305_probe(struct spi_device *spi) spi->irq, status); goto fail1; } + + device_set_wakeup_capable(&spi->dev, 1); } /* export NVRAM */ diff --git a/drivers/rtc/rtc-ds1307.c b/drivers/rtc/rtc-ds1307.c index 8a99da6f2f24..c4ec5c158aa1 100644 --- a/drivers/rtc/rtc-ds1307.c +++ b/drivers/rtc/rtc-ds1307.c @@ -881,6 +881,8 @@ read_rtc: "unable to request IRQ!\n"); goto exit_irq; } + + device_set_wakeup_capable(&client->dev, 1); set_bit(HAS_ALARM, &ds1307->flags); dev_dbg(&client->dev, "got IRQ %d\n", client->irq); } diff --git a/drivers/rtc/rtc-ds1374.c b/drivers/rtc/rtc-ds1374.c index 713f7bf5afb3..5317bbcbc7a0 100644 --- a/drivers/rtc/rtc-ds1374.c +++ b/drivers/rtc/rtc-ds1374.c @@ -383,6 +383,8 @@ static int ds1374_probe(struct i2c_client *client, dev_err(&client->dev, "unable to request IRQ\n"); goto out_free; } + + device_set_wakeup_capable(&client->dev, 1); } ds1374->rtc = rtc_device_register(client->name, &client->dev, diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 2d9d70359360..f55eb0107336 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -216,6 +216,17 @@ config SPI_S3C24XX help SPI driver for Samsung S3C24XX series ARM SoCs +config SPI_S3C24XX_FIQ + bool "S3C24XX driver with FIQ pseudo-DMA" + depends on SPI_S3C24XX + select FIQ + help + Enable FIQ support for the S3C24XX SPI driver to provide pseudo + DMA by using the fast-interrupt request framework, This allows + the driver to get DMA-like performance when there are either + no free DMA channels, or when doing transfers that required both + TX and RX data paths. + config SPI_S3C24XX_GPIO tristate "Samsung S3C24XX series SPI by GPIO" depends on ARCH_S3C2410 && EXPERIMENTAL @@ -226,6 +237,13 @@ config SPI_S3C24XX_GPIO the inbuilt hardware cannot provide the transfer mode, or where the board is using non hardware connected pins. +config SPI_S3C64XX + tristate "Samsung S3C64XX series type SPI" + depends on ARCH_S3C64XX && EXPERIMENTAL + select S3C64XX_DMA + help + SPI driver for Samsung S3C64XX and newer SoCs. + config SPI_SH_MSIOF tristate "SuperH MSIOF SPI controller" depends on SUPERH && HAVE_CLK @@ -289,6 +307,16 @@ config SPI_NUC900 # Add new SPI master controllers in alphabetical order above this line # +config SPI_DESIGNWARE + bool "DesignWare SPI controller core support" + depends on SPI_MASTER + help + general driver for SPI controller core from DesignWare + +config SPI_DW_PCI + tristate "PCI interface driver for DW SPI core" + depends on SPI_DESIGNWARE && PCI + # # There are lots of SPI device types, with sensors and memory # being probably the most widely used ones. diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index ed8c1675b52f..f3d2810ba11c 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -16,6 +16,8 @@ obj-$(CONFIG_SPI_BFIN) += spi_bfin5xx.o obj-$(CONFIG_SPI_BITBANG) += spi_bitbang.o obj-$(CONFIG_SPI_AU1550) += au1550_spi.o obj-$(CONFIG_SPI_BUTTERFLY) += spi_butterfly.o +obj-$(CONFIG_SPI_DESIGNWARE) += dw_spi.o +obj-$(CONFIG_SPI_DW_PCI) += dw_spi_pci.o obj-$(CONFIG_SPI_GPIO) += spi_gpio.o obj-$(CONFIG_SPI_IMX) += spi_imx.o obj-$(CONFIG_SPI_LM70_LLP) += spi_lm70llp.o @@ -30,7 +32,8 @@ obj-$(CONFIG_SPI_MPC52xx) += mpc52xx_spi.o obj-$(CONFIG_SPI_MPC8xxx) += spi_mpc8xxx.o obj-$(CONFIG_SPI_PPC4xx) += spi_ppc4xx.o obj-$(CONFIG_SPI_S3C24XX_GPIO) += spi_s3c24xx_gpio.o -obj-$(CONFIG_SPI_S3C24XX) += spi_s3c24xx.o +obj-$(CONFIG_SPI_S3C24XX) += spi_s3c24xx_hw.o +obj-$(CONFIG_SPI_S3C64XX) += spi_s3c64xx.o obj-$(CONFIG_SPI_TXX9) += spi_txx9.o obj-$(CONFIG_SPI_XILINX) += xilinx_spi.o obj-$(CONFIG_SPI_XILINX_OF) += xilinx_spi_of.o @@ -39,6 +42,11 @@ obj-$(CONFIG_SPI_SH_SCI) += spi_sh_sci.o obj-$(CONFIG_SPI_SH_MSIOF) += spi_sh_msiof.o obj-$(CONFIG_SPI_STMP3XXX) += spi_stmp.o obj-$(CONFIG_SPI_NUC900) += spi_nuc900.o + +# special build for s3c24xx spi driver with fiq support +spi_s3c24xx_hw-y := spi_s3c24xx.o +spi_s3c24xx_hw-$(CONFIG_SPI_S3C24XX_FIQ) += spi_s3c24xx_fiq.o + # ... add above this line ... # SPI protocol drivers (device/link on bus) diff --git a/drivers/spi/atmel_spi.c b/drivers/spi/atmel_spi.c index f5b3fdbb1e27..d21c24eaf0a9 100644 --- a/drivers/spi/atmel_spi.c +++ b/drivers/spi/atmel_spi.c @@ -189,14 +189,14 @@ static void atmel_spi_next_xfer_data(struct spi_master *master, /* use scratch buffer only when rx or tx data is unspecified */ if (xfer->rx_buf) - *rx_dma = xfer->rx_dma + xfer->len - len; + *rx_dma = xfer->rx_dma + xfer->len - *plen; else { *rx_dma = as->buffer_dma; if (len > BUFFER_SIZE) len = BUFFER_SIZE; } if (xfer->tx_buf) - *tx_dma = xfer->tx_dma + xfer->len - len; + *tx_dma = xfer->tx_dma + xfer->len - *plen; else { *tx_dma = as->buffer_dma; if (len > BUFFER_SIZE) @@ -788,7 +788,7 @@ static int __init atmel_spi_probe(struct platform_device *pdev) spin_lock_init(&as->lock); INIT_LIST_HEAD(&as->queue); as->pdev = pdev; - as->regs = ioremap(regs->start, (regs->end - regs->start) + 1); + as->regs = ioremap(regs->start, resource_size(regs)); if (!as->regs) goto out_free_buffer; as->irq = irq; diff --git a/drivers/spi/dw_spi.c b/drivers/spi/dw_spi.c new file mode 100644 index 000000000000..31620fae77be --- /dev/null +++ b/drivers/spi/dw_spi.c @@ -0,0 +1,944 @@ +/* + * dw_spi.c - Designware SPI core controller driver (refer pxa2xx_spi.c) + * + * Copyright (c) 2009, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/highmem.h> +#include <linux/delay.h> + +#include <linux/spi/dw_spi.h> +#include <linux/spi/spi.h> + +#ifdef CONFIG_DEBUG_FS +#include <linux/debugfs.h> +#endif + +#define START_STATE ((void *)0) +#define RUNNING_STATE ((void *)1) +#define DONE_STATE ((void *)2) +#define ERROR_STATE ((void *)-1) + +#define QUEUE_RUNNING 0 +#define QUEUE_STOPPED 1 + +#define MRST_SPI_DEASSERT 0 +#define MRST_SPI_ASSERT 1 + +/* Slave spi_dev related */ +struct chip_data { + u16 cr0; + u8 cs; /* chip select pin */ + u8 n_bytes; /* current is a 1/2/4 byte op */ + u8 tmode; /* TR/TO/RO/EEPROM */ + u8 type; /* SPI/SSP/MicroWire */ + + u8 poll_mode; /* 1 means use poll mode */ + + u32 dma_width; + u32 rx_threshold; + u32 tx_threshold; + u8 enable_dma; + u8 bits_per_word; + u16 clk_div; /* baud rate divider */ + u32 speed_hz; /* baud rate */ + int (*write)(struct dw_spi *dws); + int (*read)(struct dw_spi *dws); + void (*cs_control)(u32 command); +}; + +#ifdef CONFIG_DEBUG_FS +static int spi_show_regs_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +#define SPI_REGS_BUFSIZE 1024 +static ssize_t spi_show_regs(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct dw_spi *dws; + char *buf; + u32 len = 0; + ssize_t ret; + + dws = file->private_data; + + buf = kzalloc(SPI_REGS_BUFSIZE, GFP_KERNEL); + if (!buf) + return 0; + + len += snprintf(buf + len, SPI_REGS_BUFSIZE - len, + "MRST SPI0 registers:\n"); + len += snprintf(buf + len, SPI_REGS_BUFSIZE - len, + "=================================\n"); + len += snprintf(buf + len, SPI_REGS_BUFSIZE - len, + "CTRL0: \t\t0x%08x\n", dw_readl(dws, ctrl0)); + len += snprintf(buf + len, SPI_REGS_BUFSIZE - len, + "CTRL1: \t\t0x%08x\n", dw_readl(dws, ctrl1)); + len += snprintf(buf + len, SPI_REGS_BUFSIZE - len, + "SSIENR: \t0x%08x\n", dw_readl(dws, ssienr)); + len += snprintf(buf + len, SPI_REGS_BUFSIZE - len, + "SER: \t\t0x%08x\n", dw_readl(dws, ser)); + len += snprintf(buf + len, SPI_REGS_BUFSIZE - len, + "BAUDR: \t\t0x%08x\n", dw_readl(dws, baudr)); + len += snprintf(buf + len, SPI_REGS_BUFSIZE - len, + "TXFTLR: \t0x%08x\n", dw_readl(dws, txfltr)); + len += snprintf(buf + len, SPI_REGS_BUFSIZE - len, + "RXFTLR: \t0x%08x\n", dw_readl(dws, rxfltr)); + len += snprintf(buf + len, SPI_REGS_BUFSIZE - len, + "TXFLR: \t\t0x%08x\n", dw_readl(dws, txflr)); + len += snprintf(buf + len, SPI_REGS_BUFSIZE - len, + "RXFLR: \t\t0x%08x\n", dw_readl(dws, rxflr)); + len += snprintf(buf + len, SPI_REGS_BUFSIZE - len, + "SR: \t\t0x%08x\n", dw_readl(dws, sr)); + len += snprintf(buf + len, SPI_REGS_BUFSIZE - len, + "IMR: \t\t0x%08x\n", dw_readl(dws, imr)); + len += snprintf(buf + len, SPI_REGS_BUFSIZE - len, + "ISR: \t\t0x%08x\n", dw_readl(dws, isr)); + len += snprintf(buf + len, SPI_REGS_BUFSIZE - len, + "DMACR: \t\t0x%08x\n", dw_readl(dws, dmacr)); + len += snprintf(buf + len, SPI_REGS_BUFSIZE - len, + "DMATDLR: \t0x%08x\n", dw_readl(dws, dmatdlr)); + len += snprintf(buf + len, SPI_REGS_BUFSIZE - len, + "DMARDLR: \t0x%08x\n", dw_readl(dws, dmardlr)); + len += snprintf(buf + len, SPI_REGS_BUFSIZE - len, + "=================================\n"); + + ret = simple_read_from_buffer(user_buf, count, ppos, buf, len); + kfree(buf); + return ret; +} + +static const struct file_operations mrst_spi_regs_ops = { + .owner = THIS_MODULE, + .open = spi_show_regs_open, + .read = spi_show_regs, +}; + +static int mrst_spi_debugfs_init(struct dw_spi *dws) +{ + dws->debugfs = debugfs_create_dir("mrst_spi", NULL); + if (!dws->debugfs) + return -ENOMEM; + + debugfs_create_file("registers", S_IFREG | S_IRUGO, + dws->debugfs, (void *)dws, &mrst_spi_regs_ops); + return 0; +} + +static void mrst_spi_debugfs_remove(struct dw_spi *dws) +{ + if (dws->debugfs) + debugfs_remove_recursive(dws->debugfs); +} + +#else +static inline int mrst_spi_debugfs_init(struct dw_spi *dws) +{ +} + +static inline void mrst_spi_debugfs_remove(struct dw_spi *dws) +{ +} +#endif /* CONFIG_DEBUG_FS */ + +static void wait_till_not_busy(struct dw_spi *dws) +{ + unsigned long end = jiffies + usecs_to_jiffies(1000); + + while (time_before(jiffies, end)) { + if (!(dw_readw(dws, sr) & SR_BUSY)) + return; + } + dev_err(&dws->master->dev, + "DW SPI: Stutus keeps busy for 1000us after a read/write!\n"); +} + +static void flush(struct dw_spi *dws) +{ + while (dw_readw(dws, sr) & SR_RF_NOT_EMPT) + dw_readw(dws, dr); + + wait_till_not_busy(dws); +} + +static void null_cs_control(u32 command) +{ +} + +static int null_writer(struct dw_spi *dws) +{ + u8 n_bytes = dws->n_bytes; + + if (!(dw_readw(dws, sr) & SR_TF_NOT_FULL) + || (dws->tx == dws->tx_end)) + return 0; + dw_writew(dws, dr, 0); + dws->tx += n_bytes; + + wait_till_not_busy(dws); + return 1; +} + +static int null_reader(struct dw_spi *dws) +{ + u8 n_bytes = dws->n_bytes; + + while ((dw_readw(dws, sr) & SR_RF_NOT_EMPT) + && (dws->rx < dws->rx_end)) { + dw_readw(dws, dr); + dws->rx += n_bytes; + } + wait_till_not_busy(dws); + return dws->rx == dws->rx_end; +} + +static int u8_writer(struct dw_spi *dws) +{ + if (!(dw_readw(dws, sr) & SR_TF_NOT_FULL) + || (dws->tx == dws->tx_end)) + return 0; + + dw_writew(dws, dr, *(u8 *)(dws->tx)); + ++dws->tx; + + wait_till_not_busy(dws); + return 1; +} + +static int u8_reader(struct dw_spi *dws) +{ + while ((dw_readw(dws, sr) & SR_RF_NOT_EMPT) + && (dws->rx < dws->rx_end)) { + *(u8 *)(dws->rx) = dw_readw(dws, dr); + ++dws->rx; + } + + wait_till_not_busy(dws); + return dws->rx == dws->rx_end; +} + +static int u16_writer(struct dw_spi *dws) +{ + if (!(dw_readw(dws, sr) & SR_TF_NOT_FULL) + || (dws->tx == dws->tx_end)) + return 0; + + dw_writew(dws, dr, *(u16 *)(dws->tx)); + dws->tx += 2; + + wait_till_not_busy(dws); + return 1; +} + +static int u16_reader(struct dw_spi *dws) +{ + u16 temp; + + while ((dw_readw(dws, sr) & SR_RF_NOT_EMPT) + && (dws->rx < dws->rx_end)) { + temp = dw_readw(dws, dr); + *(u16 *)(dws->rx) = temp; + dws->rx += 2; + } + + wait_till_not_busy(dws); + return dws->rx == dws->rx_end; +} + +static void *next_transfer(struct dw_spi *dws) +{ + struct spi_message *msg = dws->cur_msg; + struct spi_transfer *trans = dws->cur_transfer; + + /* Move to next transfer */ + if (trans->transfer_list.next != &msg->transfers) { + dws->cur_transfer = + list_entry(trans->transfer_list.next, + struct spi_transfer, + transfer_list); + return RUNNING_STATE; + } else + return DONE_STATE; +} + +/* + * Note: first step is the protocol driver prepares + * a dma-capable memory, and this func just need translate + * the virt addr to physical + */ +static int map_dma_buffers(struct dw_spi *dws) +{ + if (!dws->cur_msg->is_dma_mapped || !dws->dma_inited + || !dws->cur_chip->enable_dma) + return 0; + + if (dws->cur_transfer->tx_dma) + dws->tx_dma = dws->cur_transfer->tx_dma; + + if (dws->cur_transfer->rx_dma) + dws->rx_dma = dws->cur_transfer->rx_dma; + + return 1; +} + +/* Caller already set message->status; dma and pio irqs are blocked */ +static void giveback(struct dw_spi *dws) +{ + struct spi_transfer *last_transfer; + unsigned long flags; + struct spi_message *msg; + + spin_lock_irqsave(&dws->lock, flags); + msg = dws->cur_msg; + dws->cur_msg = NULL; + dws->cur_transfer = NULL; + dws->prev_chip = dws->cur_chip; + dws->cur_chip = NULL; + dws->dma_mapped = 0; + queue_work(dws->workqueue, &dws->pump_messages); + spin_unlock_irqrestore(&dws->lock, flags); + + last_transfer = list_entry(msg->transfers.prev, + struct spi_transfer, + transfer_list); + + if (!last_transfer->cs_change) + dws->cs_control(MRST_SPI_DEASSERT); + + msg->state = NULL; + if (msg->complete) + msg->complete(msg->context); +} + +static void int_error_stop(struct dw_spi *dws, const char *msg) +{ + /* Stop and reset hw */ + flush(dws); + spi_enable_chip(dws, 0); + + dev_err(&dws->master->dev, "%s\n", msg); + dws->cur_msg->state = ERROR_STATE; + tasklet_schedule(&dws->pump_transfers); +} + +static void transfer_complete(struct dw_spi *dws) +{ + /* Update total byte transfered return count actual bytes read */ + dws->cur_msg->actual_length += dws->len; + + /* Move to next transfer */ + dws->cur_msg->state = next_transfer(dws); + + /* Handle end of message */ + if (dws->cur_msg->state == DONE_STATE) { + dws->cur_msg->status = 0; + giveback(dws); + } else + tasklet_schedule(&dws->pump_transfers); +} + +static irqreturn_t interrupt_transfer(struct dw_spi *dws) +{ + u16 irq_status, irq_mask = 0x3f; + + irq_status = dw_readw(dws, isr) & irq_mask; + /* Error handling */ + if (irq_status & (SPI_INT_TXOI | SPI_INT_RXOI | SPI_INT_RXUI)) { + dw_readw(dws, txoicr); + dw_readw(dws, rxoicr); + dw_readw(dws, rxuicr); + int_error_stop(dws, "interrupt_transfer: fifo overrun"); + return IRQ_HANDLED; + } + + /* INT comes from tx */ + if (dws->tx && (irq_status & SPI_INT_TXEI)) { + while (dws->tx < dws->tx_end) + dws->write(dws); + + if (dws->tx == dws->tx_end) { + spi_mask_intr(dws, SPI_INT_TXEI); + transfer_complete(dws); + } + } + + /* INT comes from rx */ + if (dws->rx && (irq_status & SPI_INT_RXFI)) { + if (dws->read(dws)) + transfer_complete(dws); + } + return IRQ_HANDLED; +} + +static irqreturn_t dw_spi_irq(int irq, void *dev_id) +{ + struct dw_spi *dws = dev_id; + + if (!dws->cur_msg) { + spi_mask_intr(dws, SPI_INT_TXEI); + /* Never fail */ + return IRQ_HANDLED; + } + + return dws->transfer_handler(dws); +} + +/* Must be called inside pump_transfers() */ +static void poll_transfer(struct dw_spi *dws) +{ + if (dws->tx) { + while (dws->write(dws)) + dws->read(dws); + } + + dws->read(dws); + transfer_complete(dws); +} + +static void dma_transfer(struct dw_spi *dws, int cs_change) +{ +} + +static void pump_transfers(unsigned long data) +{ + struct dw_spi *dws = (struct dw_spi *)data; + struct spi_message *message = NULL; + struct spi_transfer *transfer = NULL; + struct spi_transfer *previous = NULL; + struct spi_device *spi = NULL; + struct chip_data *chip = NULL; + u8 bits = 0; + u8 imask = 0; + u8 cs_change = 0; + u16 clk_div = 0; + u32 speed = 0; + u32 cr0 = 0; + + /* Get current state information */ + message = dws->cur_msg; + transfer = dws->cur_transfer; + chip = dws->cur_chip; + spi = message->spi; + + if (message->state == ERROR_STATE) { + message->status = -EIO; + goto early_exit; + } + + /* Handle end of message */ + if (message->state == DONE_STATE) { + message->status = 0; + goto early_exit; + } + + /* Delay if requested at end of transfer*/ + if (message->state == RUNNING_STATE) { + previous = list_entry(transfer->transfer_list.prev, + struct spi_transfer, + transfer_list); + if (previous->delay_usecs) + udelay(previous->delay_usecs); + } + + dws->n_bytes = chip->n_bytes; + dws->dma_width = chip->dma_width; + dws->cs_control = chip->cs_control; + + dws->rx_dma = transfer->rx_dma; + dws->tx_dma = transfer->tx_dma; + dws->tx = (void *)transfer->tx_buf; + dws->tx_end = dws->tx + transfer->len; + dws->rx = transfer->rx_buf; + dws->rx_end = dws->rx + transfer->len; + dws->write = dws->tx ? chip->write : null_writer; + dws->read = dws->rx ? chip->read : null_reader; + dws->cs_change = transfer->cs_change; + dws->len = dws->cur_transfer->len; + if (chip != dws->prev_chip) + cs_change = 1; + + cr0 = chip->cr0; + + /* Handle per transfer options for bpw and speed */ + if (transfer->speed_hz) { + speed = chip->speed_hz; + + if (transfer->speed_hz != speed) { + speed = transfer->speed_hz; + if (speed > dws->max_freq) { + printk(KERN_ERR "MRST SPI0: unsupported" + "freq: %dHz\n", speed); + message->status = -EIO; + goto early_exit; + } + + /* clk_div doesn't support odd number */ + clk_div = dws->max_freq / speed; + clk_div = (clk_div >> 1) << 1; + + chip->speed_hz = speed; + chip->clk_div = clk_div; + } + } + if (transfer->bits_per_word) { + bits = transfer->bits_per_word; + + switch (bits) { + case 8: + dws->n_bytes = 1; + dws->dma_width = 1; + dws->read = (dws->read != null_reader) ? + u8_reader : null_reader; + dws->write = (dws->write != null_writer) ? + u8_writer : null_writer; + break; + case 16: + dws->n_bytes = 2; + dws->dma_width = 2; + dws->read = (dws->read != null_reader) ? + u16_reader : null_reader; + dws->write = (dws->write != null_writer) ? + u16_writer : null_writer; + break; + default: + printk(KERN_ERR "MRST SPI0: unsupported bits:" + "%db\n", bits); + message->status = -EIO; + goto early_exit; + } + + cr0 = (bits - 1) + | (chip->type << SPI_FRF_OFFSET) + | (spi->mode << SPI_MODE_OFFSET) + | (chip->tmode << SPI_TMOD_OFFSET); + } + message->state = RUNNING_STATE; + + /* Check if current transfer is a DMA transaction */ + dws->dma_mapped = map_dma_buffers(dws); + + if (!dws->dma_mapped && !chip->poll_mode) { + if (dws->rx) + imask |= SPI_INT_RXFI; + if (dws->tx) + imask |= SPI_INT_TXEI; + dws->transfer_handler = interrupt_transfer; + } + + /* + * Reprogram registers only if + * 1. chip select changes + * 2. clk_div is changed + * 3. control value changes + */ + if (dw_readw(dws, ctrl0) != cr0 || cs_change || clk_div) { + spi_enable_chip(dws, 0); + + if (dw_readw(dws, ctrl0) != cr0) + dw_writew(dws, ctrl0, cr0); + + /* Set the interrupt mask, for poll mode just diable all int */ + spi_mask_intr(dws, 0xff); + if (!chip->poll_mode) + spi_umask_intr(dws, imask); + + spi_set_clk(dws, clk_div ? clk_div : chip->clk_div); + spi_chip_sel(dws, spi->chip_select); + spi_enable_chip(dws, 1); + + if (cs_change) + dws->prev_chip = chip; + } + + if (dws->dma_mapped) + dma_transfer(dws, cs_change); + + if (chip->poll_mode) + poll_transfer(dws); + + return; + +early_exit: + giveback(dws); + return; +} + +static void pump_messages(struct work_struct *work) +{ + struct dw_spi *dws = + container_of(work, struct dw_spi, pump_messages); + unsigned long flags; + + /* Lock queue and check for queue work */ + spin_lock_irqsave(&dws->lock, flags); + if (list_empty(&dws->queue) || dws->run == QUEUE_STOPPED) { + dws->busy = 0; + spin_unlock_irqrestore(&dws->lock, flags); + return; + } + + /* Make sure we are not already running a message */ + if (dws->cur_msg) { + spin_unlock_irqrestore(&dws->lock, flags); + return; + } + + /* Extract head of queue */ + dws->cur_msg = list_entry(dws->queue.next, struct spi_message, queue); + list_del_init(&dws->cur_msg->queue); + + /* Initial message state*/ + dws->cur_msg->state = START_STATE; + dws->cur_transfer = list_entry(dws->cur_msg->transfers.next, + struct spi_transfer, + transfer_list); + dws->cur_chip = spi_get_ctldata(dws->cur_msg->spi); + + /* Mark as busy and launch transfers */ + tasklet_schedule(&dws->pump_transfers); + + dws->busy = 1; + spin_unlock_irqrestore(&dws->lock, flags); +} + +/* spi_device use this to queue in their spi_msg */ +static int dw_spi_transfer(struct spi_device *spi, struct spi_message *msg) +{ + struct dw_spi *dws = spi_master_get_devdata(spi->master); + unsigned long flags; + + spin_lock_irqsave(&dws->lock, flags); + + if (dws->run == QUEUE_STOPPED) { + spin_unlock_irqrestore(&dws->lock, flags); + return -ESHUTDOWN; + } + + msg->actual_length = 0; + msg->status = -EINPROGRESS; + msg->state = START_STATE; + + list_add_tail(&msg->queue, &dws->queue); + + if (dws->run == QUEUE_RUNNING && !dws->busy) { + + if (dws->cur_transfer || dws->cur_msg) + queue_work(dws->workqueue, + &dws->pump_messages); + else { + /* If no other data transaction in air, just go */ + spin_unlock_irqrestore(&dws->lock, flags); + pump_messages(&dws->pump_messages); + return 0; + } + } + + spin_unlock_irqrestore(&dws->lock, flags); + return 0; +} + +/* This may be called twice for each spi dev */ +static int dw_spi_setup(struct spi_device *spi) +{ + struct dw_spi_chip *chip_info = NULL; + struct chip_data *chip; + + if (spi->bits_per_word != 8 && spi->bits_per_word != 16) + return -EINVAL; + + /* Only alloc on first setup */ + chip = spi_get_ctldata(spi); + if (!chip) { + chip = kzalloc(sizeof(struct chip_data), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->cs_control = null_cs_control; + chip->enable_dma = 0; + } + + /* + * Protocol drivers may change the chip settings, so... + * if chip_info exists, use it + */ + chip_info = spi->controller_data; + + /* chip_info doesn't always exist */ + if (chip_info) { + if (chip_info->cs_control) + chip->cs_control = chip_info->cs_control; + + chip->poll_mode = chip_info->poll_mode; + chip->type = chip_info->type; + + chip->rx_threshold = 0; + chip->tx_threshold = 0; + + chip->enable_dma = chip_info->enable_dma; + } + + if (spi->bits_per_word <= 8) { + chip->n_bytes = 1; + chip->dma_width = 1; + chip->read = u8_reader; + chip->write = u8_writer; + } else if (spi->bits_per_word <= 16) { + chip->n_bytes = 2; + chip->dma_width = 2; + chip->read = u16_reader; + chip->write = u16_writer; + } else { + /* Never take >16b case for MRST SPIC */ + dev_err(&spi->dev, "invalid wordsize\n"); + return -EINVAL; + } + chip->bits_per_word = spi->bits_per_word; + + chip->speed_hz = spi->max_speed_hz; + if (chip->speed_hz) + chip->clk_div = 25000000 / chip->speed_hz; + else + chip->clk_div = 8; /* default value */ + + chip->tmode = 0; /* Tx & Rx */ + /* Default SPI mode is SCPOL = 0, SCPH = 0 */ + chip->cr0 = (chip->bits_per_word - 1) + | (chip->type << SPI_FRF_OFFSET) + | (spi->mode << SPI_MODE_OFFSET) + | (chip->tmode << SPI_TMOD_OFFSET); + + spi_set_ctldata(spi, chip); + return 0; +} + +static void dw_spi_cleanup(struct spi_device *spi) +{ + struct chip_data *chip = spi_get_ctldata(spi); + kfree(chip); +} + +static int __init init_queue(struct dw_spi *dws) +{ + INIT_LIST_HEAD(&dws->queue); + spin_lock_init(&dws->lock); + + dws->run = QUEUE_STOPPED; + dws->busy = 0; + + tasklet_init(&dws->pump_transfers, + pump_transfers, (unsigned long)dws); + + INIT_WORK(&dws->pump_messages, pump_messages); + dws->workqueue = create_singlethread_workqueue( + dev_name(dws->master->dev.parent)); + if (dws->workqueue == NULL) + return -EBUSY; + + return 0; +} + +static int start_queue(struct dw_spi *dws) +{ + unsigned long flags; + + spin_lock_irqsave(&dws->lock, flags); + + if (dws->run == QUEUE_RUNNING || dws->busy) { + spin_unlock_irqrestore(&dws->lock, flags); + return -EBUSY; + } + + dws->run = QUEUE_RUNNING; + dws->cur_msg = NULL; + dws->cur_transfer = NULL; + dws->cur_chip = NULL; + dws->prev_chip = NULL; + spin_unlock_irqrestore(&dws->lock, flags); + + queue_work(dws->workqueue, &dws->pump_messages); + + return 0; +} + +static int stop_queue(struct dw_spi *dws) +{ + unsigned long flags; + unsigned limit = 50; + int status = 0; + + spin_lock_irqsave(&dws->lock, flags); + dws->run = QUEUE_STOPPED; + while (!list_empty(&dws->queue) && dws->busy && limit--) { + spin_unlock_irqrestore(&dws->lock, flags); + msleep(10); + spin_lock_irqsave(&dws->lock, flags); + } + + if (!list_empty(&dws->queue) || dws->busy) + status = -EBUSY; + spin_unlock_irqrestore(&dws->lock, flags); + + return status; +} + +static int destroy_queue(struct dw_spi *dws) +{ + int status; + + status = stop_queue(dws); + if (status != 0) + return status; + destroy_workqueue(dws->workqueue); + return 0; +} + +/* Restart the controller, disable all interrupts, clean rx fifo */ +static void spi_hw_init(struct dw_spi *dws) +{ + spi_enable_chip(dws, 0); + spi_mask_intr(dws, 0xff); + spi_enable_chip(dws, 1); + flush(dws); +} + +int __devinit dw_spi_add_host(struct dw_spi *dws) +{ + struct spi_master *master; + int ret; + + BUG_ON(dws == NULL); + + master = spi_alloc_master(dws->parent_dev, 0); + if (!master) { + ret = -ENOMEM; + goto exit; + } + + dws->master = master; + dws->type = SSI_MOTO_SPI; + dws->prev_chip = NULL; + dws->dma_inited = 0; + dws->dma_addr = (dma_addr_t)(dws->paddr + 0x60); + + ret = request_irq(dws->irq, dw_spi_irq, 0, + "dw_spi", dws); + if (ret < 0) { + dev_err(&master->dev, "can not get IRQ\n"); + goto err_free_master; + } + + master->mode_bits = SPI_CPOL | SPI_CPHA; + master->bus_num = dws->bus_num; + master->num_chipselect = dws->num_cs; + master->cleanup = dw_spi_cleanup; + master->setup = dw_spi_setup; + master->transfer = dw_spi_transfer; + + dws->dma_inited = 0; + + /* Basic HW init */ + spi_hw_init(dws); + + /* Initial and start queue */ + ret = init_queue(dws); + if (ret) { + dev_err(&master->dev, "problem initializing queue\n"); + goto err_diable_hw; + } + ret = start_queue(dws); + if (ret) { + dev_err(&master->dev, "problem starting queue\n"); + goto err_diable_hw; + } + + spi_master_set_devdata(master, dws); + ret = spi_register_master(master); + if (ret) { + dev_err(&master->dev, "problem registering spi master\n"); + goto err_queue_alloc; + } + + mrst_spi_debugfs_init(dws); + return 0; + +err_queue_alloc: + destroy_queue(dws); +err_diable_hw: + spi_enable_chip(dws, 0); + free_irq(dws->irq, dws); +err_free_master: + spi_master_put(master); +exit: + return ret; +} +EXPORT_SYMBOL(dw_spi_add_host); + +void __devexit dw_spi_remove_host(struct dw_spi *dws) +{ + int status = 0; + + if (!dws) + return; + mrst_spi_debugfs_remove(dws); + + /* Remove the queue */ + status = destroy_queue(dws); + if (status != 0) + dev_err(&dws->master->dev, "dw_spi_remove: workqueue will not " + "complete, message memory not freed\n"); + + spi_enable_chip(dws, 0); + /* Disable clk */ + spi_set_clk(dws, 0); + free_irq(dws->irq, dws); + + /* Disconnect from the SPI framework */ + spi_unregister_master(dws->master); +} + +int dw_spi_suspend_host(struct dw_spi *dws) +{ + int ret = 0; + + ret = stop_queue(dws); + if (ret) + return ret; + spi_enable_chip(dws, 0); + spi_set_clk(dws, 0); + return ret; +} +EXPORT_SYMBOL(dw_spi_suspend_host); + +int dw_spi_resume_host(struct dw_spi *dws) +{ + int ret; + + spi_hw_init(dws); + ret = start_queue(dws); + if (ret) + dev_err(&dws->master->dev, "fail to start queue (%d)\n", ret); + return ret; +} +EXPORT_SYMBOL(dw_spi_resume_host); + +MODULE_AUTHOR("Feng Tang <feng.tang@intel.com>"); +MODULE_DESCRIPTION("Driver for DesignWare SPI controller core"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/spi/dw_spi_pci.c b/drivers/spi/dw_spi_pci.c new file mode 100644 index 000000000000..34ba69161734 --- /dev/null +++ b/drivers/spi/dw_spi_pci.c @@ -0,0 +1,169 @@ +/* + * mrst_spi_pci.c - PCI interface driver for DW SPI Core + * + * Copyright (c) 2009, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/spi/dw_spi.h> +#include <linux/spi/spi.h> + +#define DRIVER_NAME "dw_spi_pci" + +struct dw_spi_pci { + struct pci_dev *pdev; + struct dw_spi dws; +}; + +static int __devinit spi_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + struct dw_spi_pci *dwpci; + struct dw_spi *dws; + int pci_bar = 0; + int ret; + + printk(KERN_INFO "DW: found PCI SPI controller(ID: %04x:%04x)\n", + pdev->vendor, pdev->device); + + ret = pci_enable_device(pdev); + if (ret) + return ret; + + dwpci = kzalloc(sizeof(struct dw_spi_pci), GFP_KERNEL); + if (!dwpci) { + ret = -ENOMEM; + goto err_disable; + } + + dwpci->pdev = pdev; + dws = &dwpci->dws; + + /* Get basic io resource and map it */ + dws->paddr = pci_resource_start(pdev, pci_bar); + dws->iolen = pci_resource_len(pdev, pci_bar); + + ret = pci_request_region(pdev, pci_bar, dev_name(&pdev->dev)); + if (ret) + goto err_kfree; + + dws->regs = ioremap_nocache((unsigned long)dws->paddr, + pci_resource_len(pdev, pci_bar)); + if (!dws->regs) { + ret = -ENOMEM; + goto err_release_reg; + } + + dws->parent_dev = &pdev->dev; + dws->bus_num = 0; + dws->num_cs = 4; + dws->max_freq = 25000000; /* for Moorestwon */ + dws->irq = pdev->irq; + + ret = dw_spi_add_host(dws); + if (ret) + goto err_unmap; + + /* PCI hook and SPI hook use the same drv data */ + pci_set_drvdata(pdev, dwpci); + return 0; + +err_unmap: + iounmap(dws->regs); +err_release_reg: + pci_release_region(pdev, pci_bar); +err_kfree: + kfree(dwpci); +err_disable: + pci_disable_device(pdev); + return ret; +} + +static void __devexit spi_pci_remove(struct pci_dev *pdev) +{ + struct dw_spi_pci *dwpci = pci_get_drvdata(pdev); + + pci_set_drvdata(pdev, NULL); + iounmap(dwpci->dws.regs); + pci_release_region(pdev, 0); + kfree(dwpci); + pci_disable_device(pdev); +} + +#ifdef CONFIG_PM +static int spi_suspend(struct pci_dev *pdev, pm_message_t state) +{ + struct dw_spi_pci *dwpci = pci_get_drvdata(pdev); + int ret; + + ret = dw_spi_suspend_host(&dwpci->dws); + if (ret) + return ret; + pci_save_state(pdev); + pci_disable_device(pdev); + pci_set_power_state(pdev, pci_choose_state(pdev, state)); + return ret; +} + +static int spi_resume(struct pci_dev *pdev) +{ + struct dw_spi_pci *dwpci = pci_get_drvdata(pdev); + int ret; + + pci_set_power_state(pdev, PCI_D0); + pci_restore_state(pdev); + ret = pci_enable_device(pdev); + if (ret) + return ret; + return dw_spi_resume_host(&dwpci->dws); +} +#else +#define spi_suspend NULL +#define spi_resume NULL +#endif + +static const struct pci_device_id pci_ids[] __devinitdata = { + /* Intel Moorestown platform SPI controller 0 */ + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x0800) }, + {}, +}; + +static struct pci_driver dw_spi_driver = { + .name = DRIVER_NAME, + .id_table = pci_ids, + .probe = spi_pci_probe, + .remove = __devexit_p(spi_pci_remove), + .suspend = spi_suspend, + .resume = spi_resume, +}; + +static int __init mrst_spi_init(void) +{ + return pci_register_driver(&dw_spi_driver); +} + +static void __exit mrst_spi_exit(void) +{ + pci_unregister_driver(&dw_spi_driver); +} + +module_init(mrst_spi_init); +module_exit(mrst_spi_exit); + +MODULE_AUTHOR("Feng Tang <feng.tang@intel.com>"); +MODULE_DESCRIPTION("PCI interface driver for DW SPI Core"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/spi/spi_bfin5xx.c b/drivers/spi/spi_bfin5xx.c index 73e24ef5a2f9..1d41058bbab2 100644 --- a/drivers/spi/spi_bfin5xx.c +++ b/drivers/spi/spi_bfin5xx.c @@ -1294,7 +1294,7 @@ static int __init bfin_spi_probe(struct platform_device *pdev) goto out_error_get_res; } - drv_data->regs_base = ioremap(res->start, (res->end - res->start + 1)); + drv_data->regs_base = ioremap(res->start, resource_size(res)); if (drv_data->regs_base == NULL) { dev_err(dev, "Cannot map IO\n"); status = -ENXIO; diff --git a/drivers/spi/spi_mpc8xxx.c b/drivers/spi/spi_mpc8xxx.c index e9390d747bfc..1fb2a6ea328c 100644 --- a/drivers/spi/spi_mpc8xxx.c +++ b/drivers/spi/spi_mpc8xxx.c @@ -1013,7 +1013,7 @@ mpc8xxx_spi_probe(struct device *dev, struct resource *mem, unsigned int irq) init_completion(&mpc8xxx_spi->done); - mpc8xxx_spi->base = ioremap(mem->start, mem->end - mem->start + 1); + mpc8xxx_spi->base = ioremap(mem->start, resource_size(mem)); if (mpc8xxx_spi->base == NULL) { ret = -ENOMEM; goto err_ioremap; diff --git a/drivers/spi/spi_s3c24xx.c b/drivers/spi/spi_s3c24xx.c index 276591569c8b..c010733877ae 100644 --- a/drivers/spi/spi_s3c24xx.c +++ b/drivers/spi/spi_s3c24xx.c @@ -1,7 +1,7 @@ /* linux/drivers/spi/spi_s3c24xx.c * * Copyright (c) 2006 Ben Dooks - * Copyright (c) 2006 Simtec Electronics + * Copyright 2006-2009 Simtec Electronics * Ben Dooks <ben@simtec.co.uk> * * This program is free software; you can redistribute it and/or modify @@ -28,6 +28,11 @@ #include <plat/regs-spi.h> #include <mach/spi.h> +#include <plat/fiq.h> +#include <asm/fiq.h> + +#include "spi_s3c24xx_fiq.h" + /** * s3c24xx_spi_devstate - per device data * @hz: Last frequency calculated for @sppre field. @@ -42,6 +47,13 @@ struct s3c24xx_spi_devstate { u8 sppre; }; +enum spi_fiq_mode { + FIQ_MODE_NONE = 0, + FIQ_MODE_TX = 1, + FIQ_MODE_RX = 2, + FIQ_MODE_TXRX = 3, +}; + struct s3c24xx_spi { /* bitbang has to be first */ struct spi_bitbang bitbang; @@ -52,6 +64,11 @@ struct s3c24xx_spi { int len; int count; + struct fiq_handler fiq_handler; + enum spi_fiq_mode fiq_mode; + unsigned char fiq_inuse; + unsigned char fiq_claimed; + void (*set_cs)(struct s3c2410_spi_info *spi, int cs, int pol); @@ -67,6 +84,7 @@ struct s3c24xx_spi { struct s3c2410_spi_info *pdata; }; + #define SPCON_DEFAULT (S3C2410_SPCON_MSTR | S3C2410_SPCON_SMOD_INT) #define SPPIN_DEFAULT (S3C2410_SPPIN_KEEP) @@ -127,7 +145,7 @@ static int s3c24xx_spi_update_state(struct spi_device *spi, } if (spi->mode != cs->mode) { - u8 spcon = SPCON_DEFAULT; + u8 spcon = SPCON_DEFAULT | S3C2410_SPCON_ENSCK; if (spi->mode & SPI_CPHA) spcon |= S3C2410_SPCON_CPHA_FMTB; @@ -214,13 +232,196 @@ static inline unsigned int hw_txbyte(struct s3c24xx_spi *hw, int count) return hw->tx ? hw->tx[count] : 0; } +#ifdef CONFIG_SPI_S3C24XX_FIQ +/* Support for FIQ based pseudo-DMA to improve the transfer speed. + * + * This code uses the assembly helper in spi_s3c24xx_spi.S which is + * used by the FIQ core to move data between main memory and the peripheral + * block. Since this is code running on the processor, there is no problem + * with cache coherency of the buffers, so we can use any buffer we like. + */ + +/** + * struct spi_fiq_code - FIQ code and header + * @length: The length of the code fragment, excluding this header. + * @ack_offset: The offset from @data to the word to place the IRQ ACK bit at. + * @data: The code itself to install as a FIQ handler. + */ +struct spi_fiq_code { + u32 length; + u32 ack_offset; + u8 data[0]; +}; + +extern struct spi_fiq_code s3c24xx_spi_fiq_txrx; +extern struct spi_fiq_code s3c24xx_spi_fiq_tx; +extern struct spi_fiq_code s3c24xx_spi_fiq_rx; + +/** + * ack_bit - turn IRQ into IRQ acknowledgement bit + * @irq: The interrupt number + * + * Returns the bit to write to the interrupt acknowledge register. + */ +static inline u32 ack_bit(unsigned int irq) +{ + return 1 << (irq - IRQ_EINT0); +} + +/** + * s3c24xx_spi_tryfiq - attempt to claim and setup FIQ for transfer + * @hw: The hardware state. + * + * Claim the FIQ handler (only one can be active at any one time) and + * then setup the correct transfer code for this transfer. + * + * This call updates all the necessary state information if sucessful, + * so the caller does not need to do anything more than start the transfer + * as normal, since the IRQ will have been re-routed to the FIQ handler. +*/ +void s3c24xx_spi_tryfiq(struct s3c24xx_spi *hw) +{ + struct pt_regs regs; + enum spi_fiq_mode mode; + struct spi_fiq_code *code; + int ret; + + if (!hw->fiq_claimed) { + /* try and claim fiq if we haven't got it, and if not + * then return and simply use another transfer method */ + + ret = claim_fiq(&hw->fiq_handler); + if (ret) + return; + } + + if (hw->tx && !hw->rx) + mode = FIQ_MODE_TX; + else if (hw->rx && !hw->tx) + mode = FIQ_MODE_RX; + else + mode = FIQ_MODE_TXRX; + + regs.uregs[fiq_rspi] = (long)hw->regs; + regs.uregs[fiq_rrx] = (long)hw->rx; + regs.uregs[fiq_rtx] = (long)hw->tx + 1; + regs.uregs[fiq_rcount] = hw->len - 1; + regs.uregs[fiq_rirq] = (long)S3C24XX_VA_IRQ; + + set_fiq_regs(®s); + + if (hw->fiq_mode != mode) { + u32 *ack_ptr; + + hw->fiq_mode = mode; + + switch (mode) { + case FIQ_MODE_TX: + code = &s3c24xx_spi_fiq_tx; + break; + case FIQ_MODE_RX: + code = &s3c24xx_spi_fiq_rx; + break; + case FIQ_MODE_TXRX: + code = &s3c24xx_spi_fiq_txrx; + break; + default: + code = NULL; + } + + BUG_ON(!code); + + ack_ptr = (u32 *)&code->data[code->ack_offset]; + *ack_ptr = ack_bit(hw->irq); + + set_fiq_handler(&code->data, code->length); + } + + s3c24xx_set_fiq(hw->irq, true); + + hw->fiq_mode = mode; + hw->fiq_inuse = 1; +} + +/** + * s3c24xx_spi_fiqop - FIQ core code callback + * @pw: Data registered with the handler + * @release: Whether this is a release or a return. + * + * Called by the FIQ code when another module wants to use the FIQ, so + * return whether we are currently using this or not and then update our + * internal state. + */ +static int s3c24xx_spi_fiqop(void *pw, int release) +{ + struct s3c24xx_spi *hw = pw; + int ret = 0; + + if (release) { + if (hw->fiq_inuse) + ret = -EBUSY; + + /* note, we do not need to unroute the FIQ, as the FIQ + * vector code de-routes it to signal the end of transfer */ + + hw->fiq_mode = FIQ_MODE_NONE; + hw->fiq_claimed = 0; + } else { + hw->fiq_claimed = 1; + } + + return ret; +} + +/** + * s3c24xx_spi_initfiq - setup the information for the FIQ core + * @hw: The hardware state. + * + * Setup the fiq_handler block to pass to the FIQ core. + */ +static inline void s3c24xx_spi_initfiq(struct s3c24xx_spi *hw) +{ + hw->fiq_handler.dev_id = hw; + hw->fiq_handler.name = dev_name(hw->dev); + hw->fiq_handler.fiq_op = s3c24xx_spi_fiqop; +} + +/** + * s3c24xx_spi_usefiq - return if we should be using FIQ. + * @hw: The hardware state. + * + * Return true if the platform data specifies whether this channel is + * allowed to use the FIQ. + */ +static inline bool s3c24xx_spi_usefiq(struct s3c24xx_spi *hw) +{ + return hw->pdata->use_fiq; +} + +/** + * s3c24xx_spi_usingfiq - return if channel is using FIQ + * @spi: The hardware state. + * + * Return whether the channel is currently using the FIQ (separate from + * whether the FIQ is claimed). + */ +static inline bool s3c24xx_spi_usingfiq(struct s3c24xx_spi *spi) +{ + return spi->fiq_inuse; +} +#else + +static inline void s3c24xx_spi_initfiq(struct s3c24xx_spi *s) { } +static inline void s3c24xx_spi_tryfiq(struct s3c24xx_spi *s) { } +static inline bool s3c24xx_spi_usefiq(struct s3c24xx_spi *s) { return false; } +static inline bool s3c24xx_spi_usingfiq(struct s3c24xx_spi *s) { return false; } + +#endif /* CONFIG_SPI_S3C24XX_FIQ */ + static int s3c24xx_spi_txrx(struct spi_device *spi, struct spi_transfer *t) { struct s3c24xx_spi *hw = to_hw(spi); - dev_dbg(&spi->dev, "txrx: tx %p, rx %p, len %d\n", - t->tx_buf, t->rx_buf, t->len); - hw->tx = t->tx_buf; hw->rx = t->rx_buf; hw->len = t->len; @@ -228,11 +429,14 @@ static int s3c24xx_spi_txrx(struct spi_device *spi, struct spi_transfer *t) init_completion(&hw->done); + hw->fiq_inuse = 0; + if (s3c24xx_spi_usefiq(hw) && t->len >= 3) + s3c24xx_spi_tryfiq(hw); + /* send the first byte */ writeb(hw_txbyte(hw, 0), hw->regs + S3C2410_SPTDAT); wait_for_completion(&hw->done); - return hw->count; } @@ -254,17 +458,27 @@ static irqreturn_t s3c24xx_spi_irq(int irq, void *dev) goto irq_done; } - hw->count++; + if (!s3c24xx_spi_usingfiq(hw)) { + hw->count++; - if (hw->rx) - hw->rx[count] = readb(hw->regs + S3C2410_SPRDAT); + if (hw->rx) + hw->rx[count] = readb(hw->regs + S3C2410_SPRDAT); - count++; + count++; + + if (count < hw->len) + writeb(hw_txbyte(hw, count), hw->regs + S3C2410_SPTDAT); + else + complete(&hw->done); + } else { + hw->count = hw->len; + hw->fiq_inuse = 0; + + if (hw->rx) + hw->rx[hw->len-1] = readb(hw->regs + S3C2410_SPRDAT); - if (count < hw->len) - writeb(hw_txbyte(hw, count), hw->regs + S3C2410_SPTDAT); - else complete(&hw->done); + } irq_done: return IRQ_HANDLED; @@ -322,6 +536,10 @@ static int __init s3c24xx_spi_probe(struct platform_device *pdev) platform_set_drvdata(pdev, hw); init_completion(&hw->done); + /* initialise fiq handler */ + + s3c24xx_spi_initfiq(hw); + /* setup the master state. */ /* the spi->mode bits understood by this driver: */ diff --git a/drivers/spi/spi_s3c24xx_fiq.S b/drivers/spi/spi_s3c24xx_fiq.S new file mode 100644 index 000000000000..3793cae361db --- /dev/null +++ b/drivers/spi/spi_s3c24xx_fiq.S @@ -0,0 +1,116 @@ +/* linux/drivers/spi/spi_s3c24xx_fiq.S + * + * Copyright 2009 Simtec Electronics + * Ben Dooks <ben@simtec.co.uk> + * + * S3C24XX SPI - FIQ pseudo-DMA transfer 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 <linux/linkage.h> +#include <asm/assembler.h> + +#include <mach/map.h> +#include <mach/regs-irq.h> +#include <plat/regs-spi.h> + +#include "spi_s3c24xx_fiq.h" + + .text + + @ entry to these routines is as follows, with the register names + @ defined in fiq.h so that they can be shared with the C files which + @ setup the calling registers. + @ + @ fiq_rirq The base of the IRQ registers to find S3C2410_SRCPND + @ fiq_rtmp Temporary register to hold tx/rx data + @ fiq_rspi The base of the SPI register block + @ fiq_rtx The tx buffer pointer + @ fiq_rrx The rx buffer pointer + @ fiq_rcount The number of bytes to move + + @ each entry starts with a word entry of how long it is + @ and an offset to the irq acknowledgment word + +ENTRY(s3c24xx_spi_fiq_rx) +s3c24xx_spi_fix_rx: + .word fiq_rx_end - fiq_rx_start + .word fiq_rx_irq_ack - fiq_rx_start +fiq_rx_start: + ldr fiq_rtmp, fiq_rx_irq_ack + str fiq_rtmp, [ fiq_rirq, # S3C2410_SRCPND - S3C24XX_VA_IRQ ] + + ldrb fiq_rtmp, [ fiq_rspi, # S3C2410_SPRDAT ] + strb fiq_rtmp, [ fiq_rrx ], #1 + + mov fiq_rtmp, #0xff + strb fiq_rtmp, [ fiq_rspi, # S3C2410_SPTDAT ] + + subs fiq_rcount, fiq_rcount, #1 + subnes pc, lr, #4 @@ return, still have work to do + + @@ set IRQ controller so that next op will trigger IRQ + mov fiq_rtmp, #0 + str fiq_rtmp, [ fiq_rirq, # S3C2410_INTMOD - S3C24XX_VA_IRQ ] + subs pc, lr, #4 + +fiq_rx_irq_ack: + .word 0 +fiq_rx_end: + +ENTRY(s3c24xx_spi_fiq_txrx) +s3c24xx_spi_fiq_txrx: + .word fiq_txrx_end - fiq_txrx_start + .word fiq_txrx_irq_ack - fiq_txrx_start +fiq_txrx_start: + + ldrb fiq_rtmp, [ fiq_rspi, # S3C2410_SPRDAT ] + strb fiq_rtmp, [ fiq_rrx ], #1 + + ldr fiq_rtmp, fiq_txrx_irq_ack + str fiq_rtmp, [ fiq_rirq, # S3C2410_SRCPND - S3C24XX_VA_IRQ ] + + ldrb fiq_rtmp, [ fiq_rtx ], #1 + strb fiq_rtmp, [ fiq_rspi, # S3C2410_SPTDAT ] + + subs fiq_rcount, fiq_rcount, #1 + subnes pc, lr, #4 @@ return, still have work to do + + mov fiq_rtmp, #0 + str fiq_rtmp, [ fiq_rirq, # S3C2410_INTMOD - S3C24XX_VA_IRQ ] + subs pc, lr, #4 + +fiq_txrx_irq_ack: + .word 0 + +fiq_txrx_end: + +ENTRY(s3c24xx_spi_fiq_tx) +s3c24xx_spi_fix_tx: + .word fiq_tx_end - fiq_tx_start + .word fiq_tx_irq_ack - fiq_tx_start +fiq_tx_start: + ldrb fiq_rtmp, [ fiq_rspi, # S3C2410_SPRDAT ] + + ldr fiq_rtmp, fiq_tx_irq_ack + str fiq_rtmp, [ fiq_rirq, # S3C2410_SRCPND - S3C24XX_VA_IRQ ] + + ldrb fiq_rtmp, [ fiq_rtx ], #1 + strb fiq_rtmp, [ fiq_rspi, # S3C2410_SPTDAT ] + + subs fiq_rcount, fiq_rcount, #1 + subnes pc, lr, #4 @@ return, still have work to do + + mov fiq_rtmp, #0 + str fiq_rtmp, [ fiq_rirq, # S3C2410_INTMOD - S3C24XX_VA_IRQ ] + subs pc, lr, #4 + +fiq_tx_irq_ack: + .word 0 + +fiq_tx_end: + + .end diff --git a/drivers/spi/spi_s3c24xx_fiq.h b/drivers/spi/spi_s3c24xx_fiq.h new file mode 100644 index 000000000000..a5950bb25b51 --- /dev/null +++ b/drivers/spi/spi_s3c24xx_fiq.h @@ -0,0 +1,26 @@ +/* linux/drivers/spi/spi_s3c24xx_fiq.h + * + * Copyright 2009 Simtec Electronics + * Ben Dooks <ben@simtec.co.uk> + * + * S3C24XX SPI - FIQ pseudo-DMA transfer support + * + * 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. +*/ + +/* We have R8 through R13 to play with */ + +#ifdef __ASSEMBLY__ +#define __REG_NR(x) r##x +#else +#define __REG_NR(x) (x) +#endif + +#define fiq_rspi __REG_NR(8) +#define fiq_rtmp __REG_NR(9) +#define fiq_rrx __REG_NR(10) +#define fiq_rtx __REG_NR(11) +#define fiq_rcount __REG_NR(12) +#define fiq_rirq __REG_NR(13) diff --git a/drivers/spi/spi_s3c64xx.c b/drivers/spi/spi_s3c64xx.c new file mode 100644 index 000000000000..88a456dba967 --- /dev/null +++ b/drivers/spi/spi_s3c64xx.c @@ -0,0 +1,1196 @@ +/* linux/drivers/spi/spi_s3c64xx.c + * + * Copyright (C) 2009 Samsung Electronics Ltd. + * Jaswinder Singh <jassi.brar@samsung.com> + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/workqueue.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/dma-mapping.h> +#include <linux/platform_device.h> +#include <linux/spi/spi.h> + +#include <mach/dma.h> +#include <plat/spi.h> + +/* Registers and bit-fields */ + +#define S3C64XX_SPI_CH_CFG 0x00 +#define S3C64XX_SPI_CLK_CFG 0x04 +#define S3C64XX_SPI_MODE_CFG 0x08 +#define S3C64XX_SPI_SLAVE_SEL 0x0C +#define S3C64XX_SPI_INT_EN 0x10 +#define S3C64XX_SPI_STATUS 0x14 +#define S3C64XX_SPI_TX_DATA 0x18 +#define S3C64XX_SPI_RX_DATA 0x1C +#define S3C64XX_SPI_PACKET_CNT 0x20 +#define S3C64XX_SPI_PENDING_CLR 0x24 +#define S3C64XX_SPI_SWAP_CFG 0x28 +#define S3C64XX_SPI_FB_CLK 0x2C + +#define S3C64XX_SPI_CH_HS_EN (1<<6) /* High Speed Enable */ +#define S3C64XX_SPI_CH_SW_RST (1<<5) +#define S3C64XX_SPI_CH_SLAVE (1<<4) +#define S3C64XX_SPI_CPOL_L (1<<3) +#define S3C64XX_SPI_CPHA_B (1<<2) +#define S3C64XX_SPI_CH_RXCH_ON (1<<1) +#define S3C64XX_SPI_CH_TXCH_ON (1<<0) + +#define S3C64XX_SPI_CLKSEL_SRCMSK (3<<9) +#define S3C64XX_SPI_CLKSEL_SRCSHFT 9 +#define S3C64XX_SPI_ENCLK_ENABLE (1<<8) +#define S3C64XX_SPI_PSR_MASK 0xff + +#define S3C64XX_SPI_MODE_CH_TSZ_BYTE (0<<29) +#define S3C64XX_SPI_MODE_CH_TSZ_HALFWORD (1<<29) +#define S3C64XX_SPI_MODE_CH_TSZ_WORD (2<<29) +#define S3C64XX_SPI_MODE_CH_TSZ_MASK (3<<29) +#define S3C64XX_SPI_MODE_BUS_TSZ_BYTE (0<<17) +#define S3C64XX_SPI_MODE_BUS_TSZ_HALFWORD (1<<17) +#define S3C64XX_SPI_MODE_BUS_TSZ_WORD (2<<17) +#define S3C64XX_SPI_MODE_BUS_TSZ_MASK (3<<17) +#define S3C64XX_SPI_MODE_RXDMA_ON (1<<2) +#define S3C64XX_SPI_MODE_TXDMA_ON (1<<1) +#define S3C64XX_SPI_MODE_4BURST (1<<0) + +#define S3C64XX_SPI_SLAVE_AUTO (1<<1) +#define S3C64XX_SPI_SLAVE_SIG_INACT (1<<0) + +#define S3C64XX_SPI_ACT(c) writel(0, (c)->regs + S3C64XX_SPI_SLAVE_SEL) + +#define S3C64XX_SPI_DEACT(c) writel(S3C64XX_SPI_SLAVE_SIG_INACT, \ + (c)->regs + S3C64XX_SPI_SLAVE_SEL) + +#define S3C64XX_SPI_INT_TRAILING_EN (1<<6) +#define S3C64XX_SPI_INT_RX_OVERRUN_EN (1<<5) +#define S3C64XX_SPI_INT_RX_UNDERRUN_EN (1<<4) +#define S3C64XX_SPI_INT_TX_OVERRUN_EN (1<<3) +#define S3C64XX_SPI_INT_TX_UNDERRUN_EN (1<<2) +#define S3C64XX_SPI_INT_RX_FIFORDY_EN (1<<1) +#define S3C64XX_SPI_INT_TX_FIFORDY_EN (1<<0) + +#define S3C64XX_SPI_ST_RX_OVERRUN_ERR (1<<5) +#define S3C64XX_SPI_ST_RX_UNDERRUN_ERR (1<<4) +#define S3C64XX_SPI_ST_TX_OVERRUN_ERR (1<<3) +#define S3C64XX_SPI_ST_TX_UNDERRUN_ERR (1<<2) +#define S3C64XX_SPI_ST_RX_FIFORDY (1<<1) +#define S3C64XX_SPI_ST_TX_FIFORDY (1<<0) + +#define S3C64XX_SPI_PACKET_CNT_EN (1<<16) + +#define S3C64XX_SPI_PND_TX_UNDERRUN_CLR (1<<4) +#define S3C64XX_SPI_PND_TX_OVERRUN_CLR (1<<3) +#define S3C64XX_SPI_PND_RX_UNDERRUN_CLR (1<<2) +#define S3C64XX_SPI_PND_RX_OVERRUN_CLR (1<<1) +#define S3C64XX_SPI_PND_TRAILING_CLR (1<<0) + +#define S3C64XX_SPI_SWAP_RX_HALF_WORD (1<<7) +#define S3C64XX_SPI_SWAP_RX_BYTE (1<<6) +#define S3C64XX_SPI_SWAP_RX_BIT (1<<5) +#define S3C64XX_SPI_SWAP_RX_EN (1<<4) +#define S3C64XX_SPI_SWAP_TX_HALF_WORD (1<<3) +#define S3C64XX_SPI_SWAP_TX_BYTE (1<<2) +#define S3C64XX_SPI_SWAP_TX_BIT (1<<1) +#define S3C64XX_SPI_SWAP_TX_EN (1<<0) + +#define S3C64XX_SPI_FBCLK_MSK (3<<0) + +#define S3C64XX_SPI_ST_TRLCNTZ(v, i) ((((v) >> (i)->rx_lvl_offset) & \ + (((i)->fifo_lvl_mask + 1))) \ + ? 1 : 0) + +#define S3C64XX_SPI_ST_TX_DONE(v, i) ((((v) >> (i)->rx_lvl_offset) & \ + (((i)->fifo_lvl_mask + 1) << 1)) \ + ? 1 : 0) +#define TX_FIFO_LVL(v, i) (((v) >> 6) & (i)->fifo_lvl_mask) +#define RX_FIFO_LVL(v, i) (((v) >> (i)->rx_lvl_offset) & (i)->fifo_lvl_mask) + +#define S3C64XX_SPI_MAX_TRAILCNT 0x3ff +#define S3C64XX_SPI_TRAILCNT_OFF 19 + +#define S3C64XX_SPI_TRAILCNT S3C64XX_SPI_MAX_TRAILCNT + +#define msecs_to_loops(t) (loops_per_jiffy / 1000 * HZ * t) + +#define SUSPND (1<<0) +#define SPIBUSY (1<<1) +#define RXBUSY (1<<2) +#define TXBUSY (1<<3) + +/** + * struct s3c64xx_spi_driver_data - Runtime info holder for SPI driver. + * @clk: Pointer to the spi clock. + * @master: Pointer to the SPI Protocol master. + * @workqueue: Work queue for the SPI xfer requests. + * @cntrlr_info: Platform specific data for the controller this driver manages. + * @tgl_spi: Pointer to the last CS left untoggled by the cs_change hint. + * @work: Work + * @queue: To log SPI xfer requests. + * @lock: Controller specific lock. + * @state: Set of FLAGS to indicate status. + * @rx_dmach: Controller's DMA channel for Rx. + * @tx_dmach: Controller's DMA channel for Tx. + * @sfr_start: BUS address of SPI controller regs. + * @regs: Pointer to ioremap'ed controller registers. + * @xfer_completion: To indicate completion of xfer task. + * @cur_mode: Stores the active configuration of the controller. + * @cur_bpw: Stores the active bits per word settings. + * @cur_speed: Stores the active xfer clock speed. + */ +struct s3c64xx_spi_driver_data { + void __iomem *regs; + struct clk *clk; + struct platform_device *pdev; + struct spi_master *master; + struct workqueue_struct *workqueue; + struct s3c64xx_spi_cntrlr_info *cntrlr_info; + struct spi_device *tgl_spi; + struct work_struct work; + struct list_head queue; + spinlock_t lock; + enum dma_ch rx_dmach; + enum dma_ch tx_dmach; + unsigned long sfr_start; + struct completion xfer_completion; + unsigned state; + unsigned cur_mode, cur_bpw; + unsigned cur_speed; +}; + +static struct s3c2410_dma_client s3c64xx_spi_dma_client = { + .name = "samsung-spi-dma", +}; + +static void flush_fifo(struct s3c64xx_spi_driver_data *sdd) +{ + struct s3c64xx_spi_cntrlr_info *sci = sdd->cntrlr_info; + void __iomem *regs = sdd->regs; + unsigned long loops; + u32 val; + + writel(0, regs + S3C64XX_SPI_PACKET_CNT); + + val = readl(regs + S3C64XX_SPI_CH_CFG); + val |= S3C64XX_SPI_CH_SW_RST; + val &= ~S3C64XX_SPI_CH_HS_EN; + writel(val, regs + S3C64XX_SPI_CH_CFG); + + /* Flush TxFIFO*/ + loops = msecs_to_loops(1); + do { + val = readl(regs + S3C64XX_SPI_STATUS); + } while (TX_FIFO_LVL(val, sci) && loops--); + + /* Flush RxFIFO*/ + loops = msecs_to_loops(1); + do { + val = readl(regs + S3C64XX_SPI_STATUS); + if (RX_FIFO_LVL(val, sci)) + readl(regs + S3C64XX_SPI_RX_DATA); + else + break; + } while (loops--); + + val = readl(regs + S3C64XX_SPI_CH_CFG); + val &= ~S3C64XX_SPI_CH_SW_RST; + writel(val, regs + S3C64XX_SPI_CH_CFG); + + val = readl(regs + S3C64XX_SPI_MODE_CFG); + val &= ~(S3C64XX_SPI_MODE_TXDMA_ON | S3C64XX_SPI_MODE_RXDMA_ON); + writel(val, regs + S3C64XX_SPI_MODE_CFG); + + val = readl(regs + S3C64XX_SPI_CH_CFG); + val &= ~(S3C64XX_SPI_CH_RXCH_ON | S3C64XX_SPI_CH_TXCH_ON); + writel(val, regs + S3C64XX_SPI_CH_CFG); +} + +static void enable_datapath(struct s3c64xx_spi_driver_data *sdd, + struct spi_device *spi, + struct spi_transfer *xfer, int dma_mode) +{ + struct s3c64xx_spi_cntrlr_info *sci = sdd->cntrlr_info; + void __iomem *regs = sdd->regs; + u32 modecfg, chcfg; + + modecfg = readl(regs + S3C64XX_SPI_MODE_CFG); + modecfg &= ~(S3C64XX_SPI_MODE_TXDMA_ON | S3C64XX_SPI_MODE_RXDMA_ON); + + chcfg = readl(regs + S3C64XX_SPI_CH_CFG); + chcfg &= ~S3C64XX_SPI_CH_TXCH_ON; + + if (dma_mode) { + chcfg &= ~S3C64XX_SPI_CH_RXCH_ON; + } else { + /* Always shift in data in FIFO, even if xfer is Tx only, + * this helps setting PCKT_CNT value for generating clocks + * as exactly needed. + */ + chcfg |= S3C64XX_SPI_CH_RXCH_ON; + writel(((xfer->len * 8 / sdd->cur_bpw) & 0xffff) + | S3C64XX_SPI_PACKET_CNT_EN, + regs + S3C64XX_SPI_PACKET_CNT); + } + + if (xfer->tx_buf != NULL) { + sdd->state |= TXBUSY; + chcfg |= S3C64XX_SPI_CH_TXCH_ON; + if (dma_mode) { + modecfg |= S3C64XX_SPI_MODE_TXDMA_ON; + s3c2410_dma_config(sdd->tx_dmach, 1); + s3c2410_dma_enqueue(sdd->tx_dmach, (void *)sdd, + xfer->tx_dma, xfer->len); + s3c2410_dma_ctrl(sdd->tx_dmach, S3C2410_DMAOP_START); + } else { + unsigned char *buf = (unsigned char *) xfer->tx_buf; + int i = 0; + while (i < xfer->len) + writeb(buf[i++], regs + S3C64XX_SPI_TX_DATA); + } + } + + if (xfer->rx_buf != NULL) { + sdd->state |= RXBUSY; + + if (sci->high_speed && sdd->cur_speed >= 30000000UL + && !(sdd->cur_mode & SPI_CPHA)) + chcfg |= S3C64XX_SPI_CH_HS_EN; + + if (dma_mode) { + modecfg |= S3C64XX_SPI_MODE_RXDMA_ON; + chcfg |= S3C64XX_SPI_CH_RXCH_ON; + writel(((xfer->len * 8 / sdd->cur_bpw) & 0xffff) + | S3C64XX_SPI_PACKET_CNT_EN, + regs + S3C64XX_SPI_PACKET_CNT); + s3c2410_dma_config(sdd->rx_dmach, 1); + s3c2410_dma_enqueue(sdd->rx_dmach, (void *)sdd, + xfer->rx_dma, xfer->len); + s3c2410_dma_ctrl(sdd->rx_dmach, S3C2410_DMAOP_START); + } + } + + writel(modecfg, regs + S3C64XX_SPI_MODE_CFG); + writel(chcfg, regs + S3C64XX_SPI_CH_CFG); +} + +static inline void enable_cs(struct s3c64xx_spi_driver_data *sdd, + struct spi_device *spi) +{ + struct s3c64xx_spi_csinfo *cs; + + if (sdd->tgl_spi != NULL) { /* If last device toggled after mssg */ + if (sdd->tgl_spi != spi) { /* if last mssg on diff device */ + /* Deselect the last toggled device */ + cs = sdd->tgl_spi->controller_data; + cs->set_level(spi->mode & SPI_CS_HIGH ? 0 : 1); + } + sdd->tgl_spi = NULL; + } + + cs = spi->controller_data; + cs->set_level(spi->mode & SPI_CS_HIGH ? 1 : 0); +} + +static int wait_for_xfer(struct s3c64xx_spi_driver_data *sdd, + struct spi_transfer *xfer, int dma_mode) +{ + struct s3c64xx_spi_cntrlr_info *sci = sdd->cntrlr_info; + void __iomem *regs = sdd->regs; + unsigned long val; + int ms; + + /* millisecs to xfer 'len' bytes @ 'cur_speed' */ + ms = xfer->len * 8 * 1000 / sdd->cur_speed; + ms += 5; /* some tolerance */ + + if (dma_mode) { + val = msecs_to_jiffies(ms) + 10; + val = wait_for_completion_timeout(&sdd->xfer_completion, val); + } else { + val = msecs_to_loops(ms); + do { + val = readl(regs + S3C64XX_SPI_STATUS); + } while (RX_FIFO_LVL(val, sci) < xfer->len && --val); + } + + if (!val) + return -EIO; + + if (dma_mode) { + u32 status; + + /* + * DmaTx returns after simply writing data in the FIFO, + * w/o waiting for real transmission on the bus to finish. + * DmaRx returns only after Dma read data from FIFO which + * needs bus transmission to finish, so we don't worry if + * Xfer involved Rx(with or without Tx). + */ + if (xfer->rx_buf == NULL) { + val = msecs_to_loops(10); + status = readl(regs + S3C64XX_SPI_STATUS); + while ((TX_FIFO_LVL(status, sci) + || !S3C64XX_SPI_ST_TX_DONE(status, sci)) + && --val) { + cpu_relax(); + status = readl(regs + S3C64XX_SPI_STATUS); + } + + if (!val) + return -EIO; + } + } else { + unsigned char *buf; + int i; + + /* If it was only Tx */ + if (xfer->rx_buf == NULL) { + sdd->state &= ~TXBUSY; + return 0; + } + + i = 0; + buf = xfer->rx_buf; + while (i < xfer->len) + buf[i++] = readb(regs + S3C64XX_SPI_RX_DATA); + + sdd->state &= ~RXBUSY; + } + + return 0; +} + +static inline void disable_cs(struct s3c64xx_spi_driver_data *sdd, + struct spi_device *spi) +{ + struct s3c64xx_spi_csinfo *cs = spi->controller_data; + + if (sdd->tgl_spi == spi) + sdd->tgl_spi = NULL; + + cs->set_level(spi->mode & SPI_CS_HIGH ? 0 : 1); +} + +static void s3c64xx_spi_config(struct s3c64xx_spi_driver_data *sdd) +{ + struct s3c64xx_spi_cntrlr_info *sci = sdd->cntrlr_info; + void __iomem *regs = sdd->regs; + u32 val; + + /* Disable Clock */ + val = readl(regs + S3C64XX_SPI_CLK_CFG); + val &= ~S3C64XX_SPI_ENCLK_ENABLE; + writel(val, regs + S3C64XX_SPI_CLK_CFG); + + /* Set Polarity and Phase */ + val = readl(regs + S3C64XX_SPI_CH_CFG); + val &= ~(S3C64XX_SPI_CH_SLAVE | + S3C64XX_SPI_CPOL_L | + S3C64XX_SPI_CPHA_B); + + if (sdd->cur_mode & SPI_CPOL) + val |= S3C64XX_SPI_CPOL_L; + + if (sdd->cur_mode & SPI_CPHA) + val |= S3C64XX_SPI_CPHA_B; + + writel(val, regs + S3C64XX_SPI_CH_CFG); + + /* Set Channel & DMA Mode */ + val = readl(regs + S3C64XX_SPI_MODE_CFG); + val &= ~(S3C64XX_SPI_MODE_BUS_TSZ_MASK + | S3C64XX_SPI_MODE_CH_TSZ_MASK); + + switch (sdd->cur_bpw) { + case 32: + val |= S3C64XX_SPI_MODE_BUS_TSZ_WORD; + break; + case 16: + val |= S3C64XX_SPI_MODE_BUS_TSZ_HALFWORD; + break; + default: + val |= S3C64XX_SPI_MODE_BUS_TSZ_BYTE; + break; + } + val |= S3C64XX_SPI_MODE_CH_TSZ_BYTE; /* Always 8bits wide */ + + writel(val, regs + S3C64XX_SPI_MODE_CFG); + + /* Configure Clock */ + val = readl(regs + S3C64XX_SPI_CLK_CFG); + val &= ~S3C64XX_SPI_PSR_MASK; + val |= ((clk_get_rate(sci->src_clk) / sdd->cur_speed / 2 - 1) + & S3C64XX_SPI_PSR_MASK); + writel(val, regs + S3C64XX_SPI_CLK_CFG); + + /* Enable Clock */ + val = readl(regs + S3C64XX_SPI_CLK_CFG); + val |= S3C64XX_SPI_ENCLK_ENABLE; + writel(val, regs + S3C64XX_SPI_CLK_CFG); +} + +void s3c64xx_spi_dma_rxcb(struct s3c2410_dma_chan *chan, void *buf_id, + int size, enum s3c2410_dma_buffresult res) +{ + struct s3c64xx_spi_driver_data *sdd = buf_id; + unsigned long flags; + + spin_lock_irqsave(&sdd->lock, flags); + + if (res == S3C2410_RES_OK) + sdd->state &= ~RXBUSY; + else + dev_err(&sdd->pdev->dev, "DmaAbrtRx-%d\n", size); + + /* If the other done */ + if (!(sdd->state & TXBUSY)) + complete(&sdd->xfer_completion); + + spin_unlock_irqrestore(&sdd->lock, flags); +} + +void s3c64xx_spi_dma_txcb(struct s3c2410_dma_chan *chan, void *buf_id, + int size, enum s3c2410_dma_buffresult res) +{ + struct s3c64xx_spi_driver_data *sdd = buf_id; + unsigned long flags; + + spin_lock_irqsave(&sdd->lock, flags); + + if (res == S3C2410_RES_OK) + sdd->state &= ~TXBUSY; + else + dev_err(&sdd->pdev->dev, "DmaAbrtTx-%d \n", size); + + /* If the other done */ + if (!(sdd->state & RXBUSY)) + complete(&sdd->xfer_completion); + + spin_unlock_irqrestore(&sdd->lock, flags); +} + +#define XFER_DMAADDR_INVALID DMA_BIT_MASK(32) + +static int s3c64xx_spi_map_mssg(struct s3c64xx_spi_driver_data *sdd, + struct spi_message *msg) +{ + struct device *dev = &sdd->pdev->dev; + struct spi_transfer *xfer; + + if (msg->is_dma_mapped) + return 0; + + /* First mark all xfer unmapped */ + list_for_each_entry(xfer, &msg->transfers, transfer_list) { + xfer->rx_dma = XFER_DMAADDR_INVALID; + xfer->tx_dma = XFER_DMAADDR_INVALID; + } + + /* Map until end or first fail */ + list_for_each_entry(xfer, &msg->transfers, transfer_list) { + + if (xfer->tx_buf != NULL) { + xfer->tx_dma = dma_map_single(dev, xfer->tx_buf, + xfer->len, DMA_TO_DEVICE); + if (dma_mapping_error(dev, xfer->tx_dma)) { + dev_err(dev, "dma_map_single Tx failed\n"); + xfer->tx_dma = XFER_DMAADDR_INVALID; + return -ENOMEM; + } + } + + if (xfer->rx_buf != NULL) { + xfer->rx_dma = dma_map_single(dev, xfer->rx_buf, + xfer->len, DMA_FROM_DEVICE); + if (dma_mapping_error(dev, xfer->rx_dma)) { + dev_err(dev, "dma_map_single Rx failed\n"); + dma_unmap_single(dev, xfer->tx_dma, + xfer->len, DMA_TO_DEVICE); + xfer->tx_dma = XFER_DMAADDR_INVALID; + xfer->rx_dma = XFER_DMAADDR_INVALID; + return -ENOMEM; + } + } + } + + return 0; +} + +static void s3c64xx_spi_unmap_mssg(struct s3c64xx_spi_driver_data *sdd, + struct spi_message *msg) +{ + struct device *dev = &sdd->pdev->dev; + struct spi_transfer *xfer; + + if (msg->is_dma_mapped) + return; + + list_for_each_entry(xfer, &msg->transfers, transfer_list) { + + if (xfer->rx_buf != NULL + && xfer->rx_dma != XFER_DMAADDR_INVALID) + dma_unmap_single(dev, xfer->rx_dma, + xfer->len, DMA_FROM_DEVICE); + + if (xfer->tx_buf != NULL + && xfer->tx_dma != XFER_DMAADDR_INVALID) + dma_unmap_single(dev, xfer->tx_dma, + xfer->len, DMA_TO_DEVICE); + } +} + +static void handle_msg(struct s3c64xx_spi_driver_data *sdd, + struct spi_message *msg) +{ + struct s3c64xx_spi_cntrlr_info *sci = sdd->cntrlr_info; + struct spi_device *spi = msg->spi; + struct s3c64xx_spi_csinfo *cs = spi->controller_data; + struct spi_transfer *xfer; + int status = 0, cs_toggle = 0; + u32 speed; + u8 bpw; + + /* If Master's(controller) state differs from that needed by Slave */ + if (sdd->cur_speed != spi->max_speed_hz + || sdd->cur_mode != spi->mode + || sdd->cur_bpw != spi->bits_per_word) { + sdd->cur_bpw = spi->bits_per_word; + sdd->cur_speed = spi->max_speed_hz; + sdd->cur_mode = spi->mode; + s3c64xx_spi_config(sdd); + } + + /* Map all the transfers if needed */ + if (s3c64xx_spi_map_mssg(sdd, msg)) { + dev_err(&spi->dev, + "Xfer: Unable to map message buffers!\n"); + status = -ENOMEM; + goto out; + } + + /* Configure feedback delay */ + writel(cs->fb_delay & 0x3, sdd->regs + S3C64XX_SPI_FB_CLK); + + list_for_each_entry(xfer, &msg->transfers, transfer_list) { + + unsigned long flags; + int use_dma; + + INIT_COMPLETION(sdd->xfer_completion); + + /* Only BPW and Speed may change across transfers */ + bpw = xfer->bits_per_word ? : spi->bits_per_word; + speed = xfer->speed_hz ? : spi->max_speed_hz; + + if (bpw != sdd->cur_bpw || speed != sdd->cur_speed) { + sdd->cur_bpw = bpw; + sdd->cur_speed = speed; + s3c64xx_spi_config(sdd); + } + + /* Polling method for xfers not bigger than FIFO capacity */ + if (xfer->len <= ((sci->fifo_lvl_mask >> 1) + 1)) + use_dma = 0; + else + use_dma = 1; + + spin_lock_irqsave(&sdd->lock, flags); + + /* Pending only which is to be done */ + sdd->state &= ~RXBUSY; + sdd->state &= ~TXBUSY; + + enable_datapath(sdd, spi, xfer, use_dma); + + /* Slave Select */ + enable_cs(sdd, spi); + + /* Start the signals */ + S3C64XX_SPI_ACT(sdd); + + spin_unlock_irqrestore(&sdd->lock, flags); + + status = wait_for_xfer(sdd, xfer, use_dma); + + /* Quiese the signals */ + S3C64XX_SPI_DEACT(sdd); + + if (status) { + dev_err(&spi->dev, "I/O Error: \ + rx-%d tx-%d res:rx-%c tx-%c len-%d\n", + xfer->rx_buf ? 1 : 0, xfer->tx_buf ? 1 : 0, + (sdd->state & RXBUSY) ? 'f' : 'p', + (sdd->state & TXBUSY) ? 'f' : 'p', + xfer->len); + + if (use_dma) { + if (xfer->tx_buf != NULL + && (sdd->state & TXBUSY)) + s3c2410_dma_ctrl(sdd->tx_dmach, + S3C2410_DMAOP_FLUSH); + if (xfer->rx_buf != NULL + && (sdd->state & RXBUSY)) + s3c2410_dma_ctrl(sdd->rx_dmach, + S3C2410_DMAOP_FLUSH); + } + + goto out; + } + + if (xfer->delay_usecs) + udelay(xfer->delay_usecs); + + if (xfer->cs_change) { + /* Hint that the next mssg is gonna be + for the same device */ + if (list_is_last(&xfer->transfer_list, + &msg->transfers)) + cs_toggle = 1; + else + disable_cs(sdd, spi); + } + + msg->actual_length += xfer->len; + + flush_fifo(sdd); + } + +out: + if (!cs_toggle || status) + disable_cs(sdd, spi); + else + sdd->tgl_spi = spi; + + s3c64xx_spi_unmap_mssg(sdd, msg); + + msg->status = status; + + if (msg->complete) + msg->complete(msg->context); +} + +static int acquire_dma(struct s3c64xx_spi_driver_data *sdd) +{ + if (s3c2410_dma_request(sdd->rx_dmach, + &s3c64xx_spi_dma_client, NULL) < 0) { + dev_err(&sdd->pdev->dev, "cannot get RxDMA\n"); + return 0; + } + s3c2410_dma_set_buffdone_fn(sdd->rx_dmach, s3c64xx_spi_dma_rxcb); + s3c2410_dma_devconfig(sdd->rx_dmach, S3C2410_DMASRC_HW, + sdd->sfr_start + S3C64XX_SPI_RX_DATA); + + if (s3c2410_dma_request(sdd->tx_dmach, + &s3c64xx_spi_dma_client, NULL) < 0) { + dev_err(&sdd->pdev->dev, "cannot get TxDMA\n"); + s3c2410_dma_free(sdd->rx_dmach, &s3c64xx_spi_dma_client); + return 0; + } + s3c2410_dma_set_buffdone_fn(sdd->tx_dmach, s3c64xx_spi_dma_txcb); + s3c2410_dma_devconfig(sdd->tx_dmach, S3C2410_DMASRC_MEM, + sdd->sfr_start + S3C64XX_SPI_TX_DATA); + + return 1; +} + +static void s3c64xx_spi_work(struct work_struct *work) +{ + struct s3c64xx_spi_driver_data *sdd = container_of(work, + struct s3c64xx_spi_driver_data, work); + unsigned long flags; + + /* Acquire DMA channels */ + while (!acquire_dma(sdd)) + msleep(10); + + spin_lock_irqsave(&sdd->lock, flags); + + while (!list_empty(&sdd->queue) + && !(sdd->state & SUSPND)) { + + struct spi_message *msg; + + msg = container_of(sdd->queue.next, struct spi_message, queue); + + list_del_init(&msg->queue); + + /* Set Xfer busy flag */ + sdd->state |= SPIBUSY; + + spin_unlock_irqrestore(&sdd->lock, flags); + + handle_msg(sdd, msg); + + spin_lock_irqsave(&sdd->lock, flags); + + sdd->state &= ~SPIBUSY; + } + + spin_unlock_irqrestore(&sdd->lock, flags); + + /* Free DMA channels */ + s3c2410_dma_free(sdd->tx_dmach, &s3c64xx_spi_dma_client); + s3c2410_dma_free(sdd->rx_dmach, &s3c64xx_spi_dma_client); +} + +static int s3c64xx_spi_transfer(struct spi_device *spi, + struct spi_message *msg) +{ + struct s3c64xx_spi_driver_data *sdd; + unsigned long flags; + + sdd = spi_master_get_devdata(spi->master); + + spin_lock_irqsave(&sdd->lock, flags); + + if (sdd->state & SUSPND) { + spin_unlock_irqrestore(&sdd->lock, flags); + return -ESHUTDOWN; + } + + msg->status = -EINPROGRESS; + msg->actual_length = 0; + + list_add_tail(&msg->queue, &sdd->queue); + + queue_work(sdd->workqueue, &sdd->work); + + spin_unlock_irqrestore(&sdd->lock, flags); + + return 0; +} + +/* + * Here we only check the validity of requested configuration + * and save the configuration in a local data-structure. + * The controller is actually configured only just before we + * get a message to transfer. + */ +static int s3c64xx_spi_setup(struct spi_device *spi) +{ + struct s3c64xx_spi_csinfo *cs = spi->controller_data; + struct s3c64xx_spi_driver_data *sdd; + struct s3c64xx_spi_cntrlr_info *sci; + struct spi_message *msg; + u32 psr, speed; + unsigned long flags; + int err = 0; + + if (cs == NULL || cs->set_level == NULL) { + dev_err(&spi->dev, "No CS for SPI(%d)\n", spi->chip_select); + return -ENODEV; + } + + sdd = spi_master_get_devdata(spi->master); + sci = sdd->cntrlr_info; + + spin_lock_irqsave(&sdd->lock, flags); + + list_for_each_entry(msg, &sdd->queue, queue) { + /* Is some mssg is already queued for this device */ + if (msg->spi == spi) { + dev_err(&spi->dev, + "setup: attempt while mssg in queue!\n"); + spin_unlock_irqrestore(&sdd->lock, flags); + return -EBUSY; + } + } + + if (sdd->state & SUSPND) { + spin_unlock_irqrestore(&sdd->lock, flags); + dev_err(&spi->dev, + "setup: SPI-%d not active!\n", spi->master->bus_num); + return -ESHUTDOWN; + } + + spin_unlock_irqrestore(&sdd->lock, flags); + + if (spi->bits_per_word != 8 + && spi->bits_per_word != 16 + && spi->bits_per_word != 32) { + dev_err(&spi->dev, "setup: %dbits/wrd not supported!\n", + spi->bits_per_word); + err = -EINVAL; + goto setup_exit; + } + + /* Check if we can provide the requested rate */ + speed = clk_get_rate(sci->src_clk) / 2 / (0 + 1); /* Max possible */ + + if (spi->max_speed_hz > speed) + spi->max_speed_hz = speed; + + psr = clk_get_rate(sci->src_clk) / 2 / spi->max_speed_hz - 1; + psr &= S3C64XX_SPI_PSR_MASK; + if (psr == S3C64XX_SPI_PSR_MASK) + psr--; + + speed = clk_get_rate(sci->src_clk) / 2 / (psr + 1); + if (spi->max_speed_hz < speed) { + if (psr+1 < S3C64XX_SPI_PSR_MASK) { + psr++; + } else { + err = -EINVAL; + goto setup_exit; + } + } + + speed = clk_get_rate(sci->src_clk) / 2 / (psr + 1); + if (spi->max_speed_hz >= speed) + spi->max_speed_hz = speed; + else + err = -EINVAL; + +setup_exit: + + /* setup() returns with device de-selected */ + disable_cs(sdd, spi); + + return err; +} + +static void s3c64xx_spi_hwinit(struct s3c64xx_spi_driver_data *sdd, int channel) +{ + struct s3c64xx_spi_cntrlr_info *sci = sdd->cntrlr_info; + void __iomem *regs = sdd->regs; + unsigned int val; + + sdd->cur_speed = 0; + + S3C64XX_SPI_DEACT(sdd); + + /* Disable Interrupts - we use Polling if not DMA mode */ + writel(0, regs + S3C64XX_SPI_INT_EN); + + writel(sci->src_clk_nr << S3C64XX_SPI_CLKSEL_SRCSHFT, + regs + S3C64XX_SPI_CLK_CFG); + writel(0, regs + S3C64XX_SPI_MODE_CFG); + writel(0, regs + S3C64XX_SPI_PACKET_CNT); + + /* Clear any irq pending bits */ + writel(readl(regs + S3C64XX_SPI_PENDING_CLR), + regs + S3C64XX_SPI_PENDING_CLR); + + writel(0, regs + S3C64XX_SPI_SWAP_CFG); + + val = readl(regs + S3C64XX_SPI_MODE_CFG); + val &= ~S3C64XX_SPI_MODE_4BURST; + val &= ~(S3C64XX_SPI_MAX_TRAILCNT << S3C64XX_SPI_TRAILCNT_OFF); + val |= (S3C64XX_SPI_TRAILCNT << S3C64XX_SPI_TRAILCNT_OFF); + writel(val, regs + S3C64XX_SPI_MODE_CFG); + + flush_fifo(sdd); +} + +static int __init s3c64xx_spi_probe(struct platform_device *pdev) +{ + struct resource *mem_res, *dmatx_res, *dmarx_res; + struct s3c64xx_spi_driver_data *sdd; + struct s3c64xx_spi_cntrlr_info *sci; + struct spi_master *master; + int ret; + + if (pdev->id < 0) { + dev_err(&pdev->dev, + "Invalid platform device id-%d\n", pdev->id); + return -ENODEV; + } + + if (pdev->dev.platform_data == NULL) { + dev_err(&pdev->dev, "platform_data missing!\n"); + return -ENODEV; + } + + /* Check for availability of necessary resource */ + + dmatx_res = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (dmatx_res == NULL) { + dev_err(&pdev->dev, "Unable to get SPI-Tx dma resource\n"); + return -ENXIO; + } + + dmarx_res = platform_get_resource(pdev, IORESOURCE_DMA, 1); + if (dmarx_res == NULL) { + dev_err(&pdev->dev, "Unable to get SPI-Rx dma resource\n"); + return -ENXIO; + } + + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (mem_res == NULL) { + dev_err(&pdev->dev, "Unable to get SPI MEM resource\n"); + return -ENXIO; + } + + master = spi_alloc_master(&pdev->dev, + sizeof(struct s3c64xx_spi_driver_data)); + if (master == NULL) { + dev_err(&pdev->dev, "Unable to allocate SPI Master\n"); + return -ENOMEM; + } + + sci = pdev->dev.platform_data; + + platform_set_drvdata(pdev, master); + + sdd = spi_master_get_devdata(master); + sdd->master = master; + sdd->cntrlr_info = sci; + sdd->pdev = pdev; + sdd->sfr_start = mem_res->start; + sdd->tx_dmach = dmatx_res->start; + sdd->rx_dmach = dmarx_res->start; + + sdd->cur_bpw = 8; + + master->bus_num = pdev->id; + master->setup = s3c64xx_spi_setup; + master->transfer = s3c64xx_spi_transfer; + master->num_chipselect = sci->num_cs; + master->dma_alignment = 8; + /* the spi->mode bits understood by this driver: */ + master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; + + if (request_mem_region(mem_res->start, + resource_size(mem_res), pdev->name) == NULL) { + dev_err(&pdev->dev, "Req mem region failed\n"); + ret = -ENXIO; + goto err0; + } + + sdd->regs = ioremap(mem_res->start, resource_size(mem_res)); + if (sdd->regs == NULL) { + dev_err(&pdev->dev, "Unable to remap IO\n"); + ret = -ENXIO; + goto err1; + } + + if (sci->cfg_gpio == NULL || sci->cfg_gpio(pdev)) { + dev_err(&pdev->dev, "Unable to config gpio\n"); + ret = -EBUSY; + goto err2; + } + + /* Setup clocks */ + sdd->clk = clk_get(&pdev->dev, "spi"); + if (IS_ERR(sdd->clk)) { + dev_err(&pdev->dev, "Unable to acquire clock 'spi'\n"); + ret = PTR_ERR(sdd->clk); + goto err3; + } + + if (clk_enable(sdd->clk)) { + dev_err(&pdev->dev, "Couldn't enable clock 'spi'\n"); + ret = -EBUSY; + goto err4; + } + + if (sci->src_clk_nr == S3C64XX_SPI_SRCCLK_PCLK) + sci->src_clk = sdd->clk; + else + sci->src_clk = clk_get(&pdev->dev, sci->src_clk_name); + if (IS_ERR(sci->src_clk)) { + dev_err(&pdev->dev, + "Unable to acquire clock '%s'\n", sci->src_clk_name); + ret = PTR_ERR(sci->src_clk); + goto err5; + } + + if (sci->src_clk != sdd->clk && clk_enable(sci->src_clk)) { + dev_err(&pdev->dev, "Couldn't enable clock '%s'\n", + sci->src_clk_name); + ret = -EBUSY; + goto err6; + } + + sdd->workqueue = create_singlethread_workqueue( + dev_name(master->dev.parent)); + if (sdd->workqueue == NULL) { + dev_err(&pdev->dev, "Unable to create workqueue\n"); + ret = -ENOMEM; + goto err7; + } + + /* Setup Deufult Mode */ + s3c64xx_spi_hwinit(sdd, pdev->id); + + spin_lock_init(&sdd->lock); + init_completion(&sdd->xfer_completion); + INIT_WORK(&sdd->work, s3c64xx_spi_work); + INIT_LIST_HEAD(&sdd->queue); + + if (spi_register_master(master)) { + dev_err(&pdev->dev, "cannot register SPI master\n"); + ret = -EBUSY; + goto err8; + } + + dev_dbg(&pdev->dev, "Samsung SoC SPI Driver loaded for Bus SPI-%d \ + with %d Slaves attached\n", + pdev->id, master->num_chipselect); + dev_dbg(&pdev->dev, "\tIOmem=[0x%x-0x%x]\ + \tDMA=[Rx-%d, Tx-%d]\n", + mem_res->end, mem_res->start, + sdd->rx_dmach, sdd->tx_dmach); + + return 0; + +err8: + destroy_workqueue(sdd->workqueue); +err7: + if (sci->src_clk != sdd->clk) + clk_disable(sci->src_clk); +err6: + if (sci->src_clk != sdd->clk) + clk_put(sci->src_clk); +err5: + clk_disable(sdd->clk); +err4: + clk_put(sdd->clk); +err3: +err2: + iounmap((void *) sdd->regs); +err1: + release_mem_region(mem_res->start, resource_size(mem_res)); +err0: + platform_set_drvdata(pdev, NULL); + spi_master_put(master); + + return ret; +} + +static int s3c64xx_spi_remove(struct platform_device *pdev) +{ + struct spi_master *master = spi_master_get(platform_get_drvdata(pdev)); + struct s3c64xx_spi_driver_data *sdd = spi_master_get_devdata(master); + struct s3c64xx_spi_cntrlr_info *sci = sdd->cntrlr_info; + struct resource *mem_res; + unsigned long flags; + + spin_lock_irqsave(&sdd->lock, flags); + sdd->state |= SUSPND; + spin_unlock_irqrestore(&sdd->lock, flags); + + while (sdd->state & SPIBUSY) + msleep(10); + + spi_unregister_master(master); + + destroy_workqueue(sdd->workqueue); + + if (sci->src_clk != sdd->clk) + clk_disable(sci->src_clk); + + if (sci->src_clk != sdd->clk) + clk_put(sci->src_clk); + + clk_disable(sdd->clk); + clk_put(sdd->clk); + + iounmap((void *) sdd->regs); + + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(mem_res->start, resource_size(mem_res)); + + platform_set_drvdata(pdev, NULL); + spi_master_put(master); + + return 0; +} + +#ifdef CONFIG_PM +static int s3c64xx_spi_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct spi_master *master = spi_master_get(platform_get_drvdata(pdev)); + struct s3c64xx_spi_driver_data *sdd = spi_master_get_devdata(master); + struct s3c64xx_spi_cntrlr_info *sci = sdd->cntrlr_info; + struct s3c64xx_spi_csinfo *cs; + unsigned long flags; + + spin_lock_irqsave(&sdd->lock, flags); + sdd->state |= SUSPND; + spin_unlock_irqrestore(&sdd->lock, flags); + + while (sdd->state & SPIBUSY) + msleep(10); + + /* Disable the clock */ + if (sci->src_clk != sdd->clk) + clk_disable(sci->src_clk); + + clk_disable(sdd->clk); + + sdd->cur_speed = 0; /* Output Clock is stopped */ + + return 0; +} + +static int s3c64xx_spi_resume(struct platform_device *pdev) +{ + struct spi_master *master = spi_master_get(platform_get_drvdata(pdev)); + struct s3c64xx_spi_driver_data *sdd = spi_master_get_devdata(master); + struct s3c64xx_spi_cntrlr_info *sci = sdd->cntrlr_info; + unsigned long flags; + + sci->cfg_gpio(pdev); + + /* Enable the clock */ + if (sci->src_clk != sdd->clk) + clk_enable(sci->src_clk); + + clk_enable(sdd->clk); + + s3c64xx_spi_hwinit(sdd, pdev->id); + + spin_lock_irqsave(&sdd->lock, flags); + sdd->state &= ~SUSPND; + spin_unlock_irqrestore(&sdd->lock, flags); + + return 0; +} +#else +#define s3c64xx_spi_suspend NULL +#define s3c64xx_spi_resume NULL +#endif /* CONFIG_PM */ + +static struct platform_driver s3c64xx_spi_driver = { + .driver = { + .name = "s3c64xx-spi", + .owner = THIS_MODULE, + }, + .remove = s3c64xx_spi_remove, + .suspend = s3c64xx_spi_suspend, + .resume = s3c64xx_spi_resume, +}; +MODULE_ALIAS("platform:s3c64xx-spi"); + +static int __init s3c64xx_spi_init(void) +{ + return platform_driver_probe(&s3c64xx_spi_driver, s3c64xx_spi_probe); +} +module_init(s3c64xx_spi_init); + +static void __exit s3c64xx_spi_exit(void) +{ + platform_driver_unregister(&s3c64xx_spi_driver); +} +module_exit(s3c64xx_spi_exit); + +MODULE_AUTHOR("Jaswinder Singh <jassi.brar@samsung.com>"); +MODULE_DESCRIPTION("S3C64XX SPI Controller Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/spi/spi_sh_sci.c b/drivers/spi/spi_sh_sci.c index 7d36720eb982..a65c12ffa733 100644 --- a/drivers/spi/spi_sh_sci.c +++ b/drivers/spi/spi_sh_sci.c @@ -148,7 +148,7 @@ static int sh_sci_spi_probe(struct platform_device *dev) ret = -ENOENT; goto err1; } - sp->membase = ioremap(r->start, r->end - r->start + 1); + sp->membase = ioremap(r->start, resource_size(r)); if (!sp->membase) { ret = -ENXIO; goto err1; diff --git a/drivers/spi/spi_txx9.c b/drivers/spi/spi_txx9.c index 19f75627c3de..dfa024b633e1 100644 --- a/drivers/spi/spi_txx9.c +++ b/drivers/spi/spi_txx9.c @@ -375,12 +375,10 @@ static int __init txx9spi_probe(struct platform_device *dev) res = platform_get_resource(dev, IORESOURCE_MEM, 0); if (!res) goto exit_busy; - if (!devm_request_mem_region(&dev->dev, - res->start, res->end - res->start + 1, + if (!devm_request_mem_region(&dev->dev, res->start, resource_size(res), "spi_txx9")) goto exit_busy; - c->membase = devm_ioremap(&dev->dev, - res->start, res->end - res->start + 1); + c->membase = devm_ioremap(&dev->dev, res->start, resource_size(res)); if (!c->membase) goto exit_busy; diff --git a/drivers/spi/spidev.c b/drivers/spi/spidev.c index 9c446e6003d5..ea1bec3c9a13 100644 --- a/drivers/spi/spidev.c +++ b/drivers/spi/spidev.c @@ -53,7 +53,7 @@ #define SPIDEV_MAJOR 153 /* assigned */ #define N_SPI_MINORS 32 /* ... up to 256 */ -static unsigned long minors[N_SPI_MINORS / BITS_PER_LONG]; +static DECLARE_BITMAP(minors, N_SPI_MINORS); /* Bit masks for spi_device.mode management. Note that incorrect @@ -558,7 +558,7 @@ static struct class *spidev_class; /*-------------------------------------------------------------------------*/ -static int spidev_probe(struct spi_device *spi) +static int __devinit spidev_probe(struct spi_device *spi) { struct spidev_data *spidev; int status; @@ -607,7 +607,7 @@ static int spidev_probe(struct spi_device *spi) return status; } -static int spidev_remove(struct spi_device *spi) +static int __devexit spidev_remove(struct spi_device *spi) { struct spidev_data *spidev = spi_get_drvdata(spi); @@ -629,7 +629,7 @@ static int spidev_remove(struct spi_device *spi) return 0; } -static struct spi_driver spidev_spi = { +static struct spi_driver spidev_spi_driver = { .driver = { .name = "spidev", .owner = THIS_MODULE, @@ -661,14 +661,14 @@ static int __init spidev_init(void) spidev_class = class_create(THIS_MODULE, "spidev"); if (IS_ERR(spidev_class)) { - unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name); + unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name); return PTR_ERR(spidev_class); } - status = spi_register_driver(&spidev_spi); + status = spi_register_driver(&spidev_spi_driver); if (status < 0) { class_destroy(spidev_class); - unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name); + unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name); } return status; } @@ -676,9 +676,9 @@ module_init(spidev_init); static void __exit spidev_exit(void) { - spi_unregister_driver(&spidev_spi); + spi_unregister_driver(&spidev_spi_driver); class_destroy(spidev_class); - unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name); + unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name); } module_exit(spidev_exit); diff --git a/drivers/video/backlight/adp5520_bl.c b/drivers/video/backlight/adp5520_bl.c index 4c10edecfb66..86d95c228adb 100644 --- a/drivers/video/backlight/adp5520_bl.c +++ b/drivers/video/backlight/adp5520_bl.c @@ -85,7 +85,7 @@ static int adp5520_bl_get_brightness(struct backlight_device *bl) return error ? data->current_brightness : reg_val; } -static struct backlight_ops adp5520_bl_ops = { +static const struct backlight_ops adp5520_bl_ops = { .update_status = adp5520_bl_update_status, .get_brightness = adp5520_bl_get_brightness, }; diff --git a/drivers/video/backlight/adx_bl.c b/drivers/video/backlight/adx_bl.c index 2c3bdfc620b7..d769b0bab21a 100644 --- a/drivers/video/backlight/adx_bl.c +++ b/drivers/video/backlight/adx_bl.c @@ -61,7 +61,7 @@ static int adx_backlight_check_fb(struct fb_info *fb) return 1; } -static struct backlight_ops adx_backlight_ops = { +static const struct backlight_ops adx_backlight_ops = { .options = 0, .update_status = adx_backlight_update_status, .get_brightness = adx_backlight_get_brightness, diff --git a/drivers/video/backlight/atmel-pwm-bl.c b/drivers/video/backlight/atmel-pwm-bl.c index 2cf7ba52f67c..f625ffc69ad3 100644 --- a/drivers/video/backlight/atmel-pwm-bl.c +++ b/drivers/video/backlight/atmel-pwm-bl.c @@ -113,7 +113,7 @@ static int atmel_pwm_bl_init_pwm(struct atmel_pwm_bl *pwmbl) return pwm_channel_enable(&pwmbl->pwmc); } -static struct backlight_ops atmel_pwm_bl_ops = { +static const struct backlight_ops atmel_pwm_bl_ops = { .get_brightness = atmel_pwm_bl_get_intensity, .update_status = atmel_pwm_bl_set_intensity, }; diff --git a/drivers/video/backlight/backlight.c b/drivers/video/backlight/backlight.c index 6615ac7fa60a..18829cf68b1b 100644 --- a/drivers/video/backlight/backlight.c +++ b/drivers/video/backlight/backlight.c @@ -269,7 +269,7 @@ EXPORT_SYMBOL(backlight_force_update); * ERR_PTR() or a pointer to the newly allocated device. */ struct backlight_device *backlight_device_register(const char *name, - struct device *parent, void *devdata, struct backlight_ops *ops) + struct device *parent, void *devdata, const struct backlight_ops *ops) { struct backlight_device *new_bd; int rc; diff --git a/drivers/video/backlight/corgi_lcd.c b/drivers/video/backlight/corgi_lcd.c index 96774949cd30..b4bcf8043797 100644 --- a/drivers/video/backlight/corgi_lcd.c +++ b/drivers/video/backlight/corgi_lcd.c @@ -451,7 +451,7 @@ void corgi_lcd_limit_intensity(int limit) } EXPORT_SYMBOL(corgi_lcd_limit_intensity); -static struct backlight_ops corgi_bl_ops = { +static const struct backlight_ops corgi_bl_ops = { .get_brightness = corgi_bl_get_intensity, .update_status = corgi_bl_update_status, }; diff --git a/drivers/video/backlight/cr_bllcd.c b/drivers/video/backlight/cr_bllcd.c index b9fe62b475c6..da86db4374a0 100644 --- a/drivers/video/backlight/cr_bllcd.c +++ b/drivers/video/backlight/cr_bllcd.c @@ -108,7 +108,7 @@ static int cr_backlight_get_intensity(struct backlight_device *bd) return intensity; } -static struct backlight_ops cr_backlight_ops = { +static const struct backlight_ops cr_backlight_ops = { .get_brightness = cr_backlight_get_intensity, .update_status = cr_backlight_set_intensity, }; @@ -201,7 +201,7 @@ static int cr_backlight_probe(struct platform_device *pdev) if (IS_ERR(ldp)) { backlight_device_unregister(bdp); pci_dev_put(lpc_dev); - return PTR_ERR(bdp); + return PTR_ERR(ldp); } pci_read_config_dword(lpc_dev, CRVML_REG_GPIOBAR, diff --git a/drivers/video/backlight/da903x_bl.c b/drivers/video/backlight/da903x_bl.c index f2d76dae1eb3..74cdc640173d 100644 --- a/drivers/video/backlight/da903x_bl.c +++ b/drivers/video/backlight/da903x_bl.c @@ -95,7 +95,7 @@ static int da903x_backlight_get_brightness(struct backlight_device *bl) return data->current_brightness; } -static struct backlight_ops da903x_backlight_ops = { +static const struct backlight_ops da903x_backlight_ops = { .update_status = da903x_backlight_update_status, .get_brightness = da903x_backlight_get_brightness, }; diff --git a/drivers/video/backlight/generic_bl.c b/drivers/video/backlight/generic_bl.c index 6d27f62fdcd0..e6d348e63596 100644 --- a/drivers/video/backlight/generic_bl.c +++ b/drivers/video/backlight/generic_bl.c @@ -70,7 +70,7 @@ void corgibl_limit_intensity(int limit) } EXPORT_SYMBOL(corgibl_limit_intensity); -static struct backlight_ops genericbl_ops = { +static const struct backlight_ops genericbl_ops = { .options = BL_CORE_SUSPENDRESUME, .get_brightness = genericbl_get_intensity, .update_status = genericbl_send_intensity, diff --git a/drivers/video/backlight/hp680_bl.c b/drivers/video/backlight/hp680_bl.c index 7fb4eefff80d..f7cc528d5be7 100644 --- a/drivers/video/backlight/hp680_bl.c +++ b/drivers/video/backlight/hp680_bl.c @@ -98,7 +98,7 @@ static int hp680bl_get_intensity(struct backlight_device *bd) return current_intensity; } -static struct backlight_ops hp680bl_ops = { +static const struct backlight_ops hp680bl_ops = { .get_brightness = hp680bl_get_intensity, .update_status = hp680bl_set_intensity, }; diff --git a/drivers/video/backlight/jornada720_bl.c b/drivers/video/backlight/jornada720_bl.c index 7aed2565c1bd..db9071fc5665 100644 --- a/drivers/video/backlight/jornada720_bl.c +++ b/drivers/video/backlight/jornada720_bl.c @@ -93,7 +93,7 @@ out: return ret; } -static struct backlight_ops jornada_bl_ops = { +static const struct backlight_ops jornada_bl_ops = { .get_brightness = jornada_bl_get_brightness, .update_status = jornada_bl_update_status, .options = BL_CORE_SUSPENDRESUME, diff --git a/drivers/video/backlight/kb3886_bl.c b/drivers/video/backlight/kb3886_bl.c index a38fda1742dd..939e7b830cf3 100644 --- a/drivers/video/backlight/kb3886_bl.c +++ b/drivers/video/backlight/kb3886_bl.c @@ -134,7 +134,7 @@ static int kb3886bl_get_intensity(struct backlight_device *bd) return kb3886bl_intensity; } -static struct backlight_ops kb3886bl_ops = { +static const struct backlight_ops kb3886bl_ops = { .get_brightness = kb3886bl_get_intensity, .update_status = kb3886bl_send_intensity, }; diff --git a/drivers/video/backlight/locomolcd.c b/drivers/video/backlight/locomolcd.c index 6b488b8a7eee..00a9591b0003 100644 --- a/drivers/video/backlight/locomolcd.c +++ b/drivers/video/backlight/locomolcd.c @@ -141,7 +141,7 @@ static int locomolcd_get_intensity(struct backlight_device *bd) return current_intensity; } -static struct backlight_ops locomobl_data = { +static const struct backlight_ops locomobl_data = { .get_brightness = locomolcd_get_intensity, .update_status = locomolcd_set_intensity, }; diff --git a/drivers/video/backlight/mbp_nvidia_bl.c b/drivers/video/backlight/mbp_nvidia_bl.c index 9edb8d7c295f..2e78b0784bdc 100644 --- a/drivers/video/backlight/mbp_nvidia_bl.c +++ b/drivers/video/backlight/mbp_nvidia_bl.c @@ -33,7 +33,7 @@ struct dmi_match_data { unsigned long iostart; unsigned long iolen; /* Backlight operations structure. */ - struct backlight_ops backlight_ops; + const struct backlight_ops backlight_ops; }; /* Module parameters. */ @@ -220,6 +220,24 @@ static const struct dmi_system_id __initdata mbp_device_table[] = { }, { .callback = mbp_dmi_match, + .ident = "MacBookPro 5,3", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Apple Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "MacBookPro5,3"), + }, + .driver_data = (void *)&nvidia_chipset_data, + }, + { + .callback = mbp_dmi_match, + .ident = "MacBookPro 5,4", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Apple Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "MacBookPro5,4"), + }, + .driver_data = (void *)&nvidia_chipset_data, + }, + { + .callback = mbp_dmi_match, .ident = "MacBookPro 5,5", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Apple Inc."), diff --git a/drivers/video/backlight/omap1_bl.c b/drivers/video/backlight/omap1_bl.c index 8693e5fcd2eb..409ca9643528 100644 --- a/drivers/video/backlight/omap1_bl.c +++ b/drivers/video/backlight/omap1_bl.c @@ -125,7 +125,7 @@ static int omapbl_get_intensity(struct backlight_device *dev) return bl->current_intensity; } -static struct backlight_ops omapbl_ops = { +static const struct backlight_ops omapbl_ops = { .get_brightness = omapbl_get_intensity, .update_status = omapbl_update_status, }; diff --git a/drivers/video/backlight/progear_bl.c b/drivers/video/backlight/progear_bl.c index 9edaf24fd82d..075786e05034 100644 --- a/drivers/video/backlight/progear_bl.c +++ b/drivers/video/backlight/progear_bl.c @@ -54,7 +54,7 @@ static int progearbl_get_intensity(struct backlight_device *bd) return intensity - HW_LEVEL_MIN; } -static struct backlight_ops progearbl_ops = { +static const struct backlight_ops progearbl_ops = { .get_brightness = progearbl_get_intensity, .update_status = progearbl_set_intensity, }; diff --git a/drivers/video/backlight/pwm_bl.c b/drivers/video/backlight/pwm_bl.c index 887166267443..9d2ec2a1cce8 100644 --- a/drivers/video/backlight/pwm_bl.c +++ b/drivers/video/backlight/pwm_bl.c @@ -22,8 +22,10 @@ struct pwm_bl_data { struct pwm_device *pwm; + struct device *dev; unsigned int period; - int (*notify)(int brightness); + int (*notify)(struct device *, + int brightness); }; static int pwm_backlight_update_status(struct backlight_device *bl) @@ -39,7 +41,7 @@ static int pwm_backlight_update_status(struct backlight_device *bl) brightness = 0; if (pb->notify) - brightness = pb->notify(brightness); + brightness = pb->notify(pb->dev, brightness); if (brightness == 0) { pwm_config(pb->pwm, 0, pb->period); @@ -56,7 +58,7 @@ static int pwm_backlight_get_brightness(struct backlight_device *bl) return bl->props.brightness; } -static struct backlight_ops pwm_backlight_ops = { +static const struct backlight_ops pwm_backlight_ops = { .update_status = pwm_backlight_update_status, .get_brightness = pwm_backlight_get_brightness, }; @@ -88,6 +90,7 @@ static int pwm_backlight_probe(struct platform_device *pdev) pb->period = data->pwm_period_ns; pb->notify = data->notify; + pb->dev = &pdev->dev; pb->pwm = pwm_request(data->pwm_id, "backlight"); if (IS_ERR(pb->pwm)) { @@ -146,7 +149,7 @@ static int pwm_backlight_suspend(struct platform_device *pdev, struct pwm_bl_data *pb = dev_get_drvdata(&bl->dev); if (pb->notify) - pb->notify(0); + pb->notify(pb->dev, 0); pwm_config(pb->pwm, 0, pb->period); pwm_disable(pb->pwm); return 0; diff --git a/drivers/video/backlight/tosa_bl.c b/drivers/video/backlight/tosa_bl.c index 43edbada12d1..e14ce4d469f5 100644 --- a/drivers/video/backlight/tosa_bl.c +++ b/drivers/video/backlight/tosa_bl.c @@ -72,7 +72,7 @@ static int tosa_bl_get_brightness(struct backlight_device *dev) return props->brightness; } -static struct backlight_ops bl_ops = { +static const struct backlight_ops bl_ops = { .get_brightness = tosa_bl_get_brightness, .update_status = tosa_bl_update_status, }; diff --git a/drivers/video/backlight/wm831x_bl.c b/drivers/video/backlight/wm831x_bl.c index 467bdb7efb23..e32add37a203 100644 --- a/drivers/video/backlight/wm831x_bl.c +++ b/drivers/video/backlight/wm831x_bl.c @@ -112,7 +112,7 @@ static int wm831x_backlight_get_brightness(struct backlight_device *bl) return data->current_brightness; } -static struct backlight_ops wm831x_backlight_ops = { +static const struct backlight_ops wm831x_backlight_ops = { .options = BL_CORE_SUSPENDRESUME, .update_status = wm831x_backlight_update_status, .get_brightness = wm831x_backlight_get_brightness, diff --git a/drivers/video/via/viafbdev.c b/drivers/video/via/viafbdev.c index 10d8c4b4baeb..d8df17a7d5fc 100644 --- a/drivers/video/via/viafbdev.c +++ b/drivers/video/via/viafbdev.c @@ -680,7 +680,7 @@ static int viafb_ioctl(struct fb_info *info, u_int cmd, u_long arg) if (!viafb_gamma_table) return -ENOMEM; if (copy_from_user(viafb_gamma_table, argp, - sizeof(viafb_gamma_table))) { + 256 * sizeof(u32))) { kfree(viafb_gamma_table); return -EFAULT; } @@ -694,7 +694,7 @@ static int viafb_ioctl(struct fb_info *info, u_int cmd, u_long arg) return -ENOMEM; viafb_get_gamma_table(viafb_gamma_table); if (copy_to_user(argp, viafb_gamma_table, - sizeof(viafb_gamma_table))) { + 256 * sizeof(u32))) { kfree(viafb_gamma_table); return -EFAULT; } diff --git a/fs/Kconfig b/fs/Kconfig index f8fccaaad628..64d44efad7a5 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -6,10 +6,6 @@ menu "File systems" if BLOCK -config FS_JOURNAL_INFO - bool - default n - source "fs/ext2/Kconfig" source "fs/ext3/Kconfig" source "fs/ext4/Kconfig" diff --git a/fs/binfmt_aout.c b/fs/binfmt_aout.c index b639dcf7c778..346b69405363 100644 --- a/fs/binfmt_aout.c +++ b/fs/binfmt_aout.c @@ -32,7 +32,7 @@ static int load_aout_binary(struct linux_binprm *, struct pt_regs * regs); static int load_aout_library(struct file*); -static int aout_core_dump(long signr, struct pt_regs *regs, struct file *file, unsigned long limit); +static int aout_core_dump(struct coredump_params *cprm); static struct linux_binfmt aout_format = { .module = THIS_MODULE, @@ -89,8 +89,9 @@ if (file->f_op->llseek) { \ * dumping of the process results in another error.. */ -static int aout_core_dump(long signr, struct pt_regs *regs, struct file *file, unsigned long limit) +static int aout_core_dump(struct coredump_params *cprm) { + struct file *file = cprm->file; mm_segment_t fs; int has_dumped = 0; unsigned long dump_start, dump_size; @@ -108,16 +109,16 @@ static int aout_core_dump(long signr, struct pt_regs *regs, struct file *file, u current->flags |= PF_DUMPCORE; strncpy(dump.u_comm, current->comm, sizeof(dump.u_comm)); dump.u_ar0 = offsetof(struct user, regs); - dump.signal = signr; - aout_dump_thread(regs, &dump); + dump.signal = cprm->signr; + aout_dump_thread(cprm->regs, &dump); /* If the size of the dump file exceeds the rlimit, then see what would happen if we wrote the stack, but not the data area. */ - if ((dump.u_dsize + dump.u_ssize+1) * PAGE_SIZE > limit) + if ((dump.u_dsize + dump.u_ssize+1) * PAGE_SIZE > cprm->limit) dump.u_dsize = 0; /* Make sure we have enough room to write the stack and data areas. */ - if ((dump.u_ssize + 1) * PAGE_SIZE > limit) + if ((dump.u_ssize + 1) * PAGE_SIZE > cprm->limit) dump.u_ssize = 0; /* make sure we actually have a data and stack area to dump */ diff --git a/fs/binfmt_elf.c b/fs/binfmt_elf.c index 97b6e9efeb7f..edd90c49003c 100644 --- a/fs/binfmt_elf.c +++ b/fs/binfmt_elf.c @@ -45,7 +45,7 @@ static unsigned long elf_map(struct file *, unsigned long, struct elf_phdr *, * don't even try. */ #ifdef CONFIG_ELF_CORE -static int elf_core_dump(long signr, struct pt_regs *regs, struct file *file, unsigned long limit); +static int elf_core_dump(struct coredump_params *cprm); #else #define elf_core_dump NULL #endif @@ -1272,8 +1272,9 @@ static int writenote(struct memelfnote *men, struct file *file, } #undef DUMP_WRITE -#define DUMP_WRITE(addr, nr) \ - if ((size += (nr)) > limit || !dump_write(file, (addr), (nr))) \ +#define DUMP_WRITE(addr, nr) \ + if ((size += (nr)) > cprm->limit || \ + !dump_write(cprm->file, (addr), (nr))) \ goto end_coredump; static void fill_elf_header(struct elfhdr *elf, int segs, @@ -1901,7 +1902,7 @@ static struct vm_area_struct *next_vma(struct vm_area_struct *this_vma, * and then they are actually written out. If we run out of core limit * we just truncate. */ -static int elf_core_dump(long signr, struct pt_regs *regs, struct file *file, unsigned long limit) +static int elf_core_dump(struct coredump_params *cprm) { int has_dumped = 0; mm_segment_t fs; @@ -1947,7 +1948,7 @@ static int elf_core_dump(long signr, struct pt_regs *regs, struct file *file, un * notes. This also sets up the file header. */ if (!fill_note_info(elf, segs + 1, /* including notes section */ - &info, signr, regs)) + &info, cprm->signr, cprm->regs)) goto cleanup; has_dumped = 1; @@ -2009,14 +2010,14 @@ static int elf_core_dump(long signr, struct pt_regs *regs, struct file *file, un #endif /* write out the notes section */ - if (!write_note_info(&info, file, &foffset)) + if (!write_note_info(&info, cprm->file, &foffset)) goto end_coredump; - if (elf_coredump_extra_notes_write(file, &foffset)) + if (elf_coredump_extra_notes_write(cprm->file, &foffset)) goto end_coredump; /* Align to page */ - if (!dump_seek(file, dataoff - foffset)) + if (!dump_seek(cprm->file, dataoff - foffset)) goto end_coredump; for (vma = first_vma(current, gate_vma); vma != NULL; @@ -2033,12 +2034,13 @@ static int elf_core_dump(long signr, struct pt_regs *regs, struct file *file, un page = get_dump_page(addr); if (page) { void *kaddr = kmap(page); - stop = ((size += PAGE_SIZE) > limit) || - !dump_write(file, kaddr, PAGE_SIZE); + stop = ((size += PAGE_SIZE) > cprm->limit) || + !dump_write(cprm->file, kaddr, + PAGE_SIZE); kunmap(page); page_cache_release(page); } else - stop = !dump_seek(file, PAGE_SIZE); + stop = !dump_seek(cprm->file, PAGE_SIZE); if (stop) goto end_coredump; } diff --git a/fs/binfmt_elf_fdpic.c b/fs/binfmt_elf_fdpic.c index 7b055385db8e..c25256a5c5b0 100644 --- a/fs/binfmt_elf_fdpic.c +++ b/fs/binfmt_elf_fdpic.c @@ -76,7 +76,7 @@ static int elf_fdpic_map_file_by_direct_mmap(struct elf_fdpic_params *, struct file *, struct mm_struct *); #ifdef CONFIG_ELF_CORE -static int elf_fdpic_core_dump(long, struct pt_regs *, struct file *, unsigned long limit); +static int elf_fdpic_core_dump(struct coredump_params *cprm); #endif static struct linux_binfmt elf_fdpic_format = { @@ -1326,8 +1326,9 @@ static int writenote(struct memelfnote *men, struct file *file) #undef DUMP_WRITE #undef DUMP_SEEK -#define DUMP_WRITE(addr, nr) \ - if ((size += (nr)) > limit || !dump_write(file, (addr), (nr))) \ +#define DUMP_WRITE(addr, nr) \ + if ((size += (nr)) > cprm->limit || \ + !dump_write(cprm->file, (addr), (nr))) \ goto end_coredump; static inline void fill_elf_fdpic_header(struct elfhdr *elf, int segs) @@ -1582,8 +1583,7 @@ static int elf_fdpic_dump_segments(struct file *file, size_t *size, * and then they are actually written out. If we run out of core limit * we just truncate. */ -static int elf_fdpic_core_dump(long signr, struct pt_regs *regs, - struct file *file, unsigned long limit) +static int elf_fdpic_core_dump(struct coredump_params *cprm) { #define NUM_NOTES 6 int has_dumped = 0; @@ -1642,7 +1642,7 @@ static int elf_fdpic_core_dump(long signr, struct pt_regs *regs, goto cleanup; #endif - if (signr) { + if (cprm->signr) { struct core_thread *ct; struct elf_thread_status *tmp; @@ -1661,14 +1661,14 @@ static int elf_fdpic_core_dump(long signr, struct pt_regs *regs, int sz; tmp = list_entry(t, struct elf_thread_status, list); - sz = elf_dump_thread_status(signr, tmp); + sz = elf_dump_thread_status(cprm->signr, tmp); thread_status_size += sz; } } /* now collect the dump for the current */ - fill_prstatus(prstatus, current, signr); - elf_core_copy_regs(&prstatus->pr_reg, regs); + fill_prstatus(prstatus, current, cprm->signr); + elf_core_copy_regs(&prstatus->pr_reg, cprm->regs); segs = current->mm->map_count; #ifdef ELF_CORE_EXTRA_PHDRS @@ -1703,7 +1703,7 @@ static int elf_fdpic_core_dump(long signr, struct pt_regs *regs, /* Try to dump the FPU. */ if ((prstatus->pr_fpvalid = - elf_core_copy_task_fpregs(current, regs, fpu))) + elf_core_copy_task_fpregs(current, cprm->regs, fpu))) fill_note(notes + numnote++, "CORE", NT_PRFPREG, sizeof(*fpu), fpu); #ifdef ELF_CORE_COPY_XFPREGS @@ -1774,7 +1774,7 @@ static int elf_fdpic_core_dump(long signr, struct pt_regs *regs, /* write out the notes section */ for (i = 0; i < numnote; i++) - if (!writenote(notes + i, file)) + if (!writenote(notes + i, cprm->file)) goto end_coredump; /* write out the thread status notes section */ @@ -1783,14 +1783,15 @@ static int elf_fdpic_core_dump(long signr, struct pt_regs *regs, list_entry(t, struct elf_thread_status, list); for (i = 0; i < tmp->num_notes; i++) - if (!writenote(&tmp->notes[i], file)) + if (!writenote(&tmp->notes[i], cprm->file)) goto end_coredump; } - if (!dump_seek(file, dataoff)) + if (!dump_seek(cprm->file, dataoff)) goto end_coredump; - if (elf_fdpic_dump_segments(file, &size, &limit, mm_flags) < 0) + if (elf_fdpic_dump_segments(cprm->file, &size, &cprm->limit, + mm_flags) < 0) goto end_coredump; #ifdef ELF_CORE_WRITE_EXTRA_DATA diff --git a/fs/binfmt_flat.c b/fs/binfmt_flat.c index a2796651e756..d4a00ea1054c 100644 --- a/fs/binfmt_flat.c +++ b/fs/binfmt_flat.c @@ -87,7 +87,7 @@ static int load_flat_shared_library(int id, struct lib_info *p); #endif static int load_flat_binary(struct linux_binprm *, struct pt_regs * regs); -static int flat_core_dump(long signr, struct pt_regs *regs, struct file *file, unsigned long limit); +static int flat_core_dump(struct coredump_params *cprm); static struct linux_binfmt flat_format = { .module = THIS_MODULE, @@ -102,10 +102,10 @@ static struct linux_binfmt flat_format = { * Currently only a stub-function. */ -static int flat_core_dump(long signr, struct pt_regs *regs, struct file *file, unsigned long limit) +static int flat_core_dump(struct coredump_params *cprm) { printk("Process %s:%d received signr %d and should have core dumped\n", - current->comm, current->pid, (int) signr); + current->comm, current->pid, (int) cprm->signr); return(1); } diff --git a/fs/binfmt_som.c b/fs/binfmt_som.c index eff74b9c9e77..2a9b5330cc5e 100644 --- a/fs/binfmt_som.c +++ b/fs/binfmt_som.c @@ -43,7 +43,7 @@ static int load_som_library(struct file *); * don't even try. */ #if 0 -static int som_core_dump(long signr, struct pt_regs *regs, unsigned long limit); +static int som_core_dump(struct coredump_params *cprm); #else #define som_core_dump NULL #endif diff --git a/fs/btrfs/Kconfig b/fs/btrfs/Kconfig index 402afe0a0bfb..7bb3c020e570 100644 --- a/fs/btrfs/Kconfig +++ b/fs/btrfs/Kconfig @@ -4,7 +4,6 @@ config BTRFS_FS select LIBCRC32C select ZLIB_INFLATE select ZLIB_DEFLATE - select FS_JOURNAL_INFO help Btrfs is a new filesystem with extents, writable snapshotting, support for multiple devices and many more features. diff --git a/fs/exec.c b/fs/exec.c index 623a5cc3076a..632b02e34ec7 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -826,7 +826,9 @@ static int de_thread(struct task_struct *tsk) attach_pid(tsk, PIDTYPE_PID, task_pid(leader)); transfer_pid(leader, tsk, PIDTYPE_PGID); transfer_pid(leader, tsk, PIDTYPE_SID); + list_replace_rcu(&leader->tasks, &tsk->tasks); + list_replace_init(&leader->sibling, &tsk->sibling); tsk->group_leader = tsk; leader->group_leader = tsk; @@ -1761,17 +1763,20 @@ void do_coredump(long signr, int exit_code, struct pt_regs *regs) struct mm_struct *mm = current->mm; struct linux_binfmt * binfmt; struct inode * inode; - struct file * file; const struct cred *old_cred; struct cred *cred; int retval = 0; int flag = 0; int ispipe = 0; - unsigned long core_limit = current->signal->rlim[RLIMIT_CORE].rlim_cur; char **helper_argv = NULL; int helper_argc = 0; int dump_count = 0; static atomic_t core_dump_count = ATOMIC_INIT(0); + struct coredump_params cprm = { + .signr = signr, + .regs = regs, + .limit = current->signal->rlim[RLIMIT_CORE].rlim_cur, + }; audit_core_dumps(signr); @@ -1827,15 +1832,15 @@ void do_coredump(long signr, int exit_code, struct pt_regs *regs) ispipe = format_corename(corename, signr); unlock_kernel(); - if ((!ispipe) && (core_limit < binfmt->min_coredump)) + if ((!ispipe) && (cprm.limit < binfmt->min_coredump)) goto fail_unlock; if (ispipe) { - if (core_limit == 0) { + if (cprm.limit == 0) { /* * Normally core limits are irrelevant to pipes, since * we're not writing to the file system, but we use - * core_limit of 0 here as a speacial value. Any + * cprm.limit of 0 here as a speacial value. Any * non-zero limit gets set to RLIM_INFINITY below, but * a limit of 0 skips the dump. This is a consistent * way to catch recursive crashes. We can still crash @@ -1868,25 +1873,25 @@ void do_coredump(long signr, int exit_code, struct pt_regs *regs) goto fail_dropcount; } - core_limit = RLIM_INFINITY; + cprm.limit = RLIM_INFINITY; /* SIGPIPE can happen, but it's just never processed */ if (call_usermodehelper_pipe(helper_argv[0], helper_argv, NULL, - &file)) { + &cprm.file)) { printk(KERN_INFO "Core dump to %s pipe failed\n", corename); goto fail_dropcount; } } else - file = filp_open(corename, + cprm.file = filp_open(corename, O_CREAT | 2 | O_NOFOLLOW | O_LARGEFILE | flag, 0600); - if (IS_ERR(file)) + if (IS_ERR(cprm.file)) goto fail_dropcount; - inode = file->f_path.dentry->d_inode; + inode = cprm.file->f_path.dentry->d_inode; if (inode->i_nlink > 1) goto close_fail; /* multiple links - don't dump */ - if (!ispipe && d_unhashed(file->f_path.dentry)) + if (!ispipe && d_unhashed(cprm.file->f_path.dentry)) goto close_fail; /* AK: actually i see no reason to not allow this for named pipes etc., @@ -1899,21 +1904,22 @@ void do_coredump(long signr, int exit_code, struct pt_regs *regs) */ if (inode->i_uid != current_fsuid()) goto close_fail; - if (!file->f_op) + if (!cprm.file->f_op) goto close_fail; - if (!file->f_op->write) + if (!cprm.file->f_op->write) goto close_fail; - if (!ispipe && do_truncate(file->f_path.dentry, 0, 0, file) != 0) + if (!ispipe && + do_truncate(cprm.file->f_path.dentry, 0, 0, cprm.file) != 0) goto close_fail; - retval = binfmt->core_dump(signr, regs, file, core_limit); + retval = binfmt->core_dump(&cprm); if (retval) current->signal->group_exit_code |= 0x80; close_fail: if (ispipe && core_pipe_limit) - wait_for_dump_helpers(file); - filp_close(file, NULL); + wait_for_dump_helpers(cprm.file); + filp_close(cprm.file, NULL); fail_dropcount: if (dump_count) atomic_dec(&core_dump_count); diff --git a/fs/ext4/Kconfig b/fs/ext4/Kconfig index e5f6774846e4..9acf7e808139 100644 --- a/fs/ext4/Kconfig +++ b/fs/ext4/Kconfig @@ -2,7 +2,6 @@ config EXT4_FS tristate "The Extended 4 (ext4) filesystem" select JBD2 select CRC16 - select FS_JOURNAL_INFO help This is the next generation of the ext3 filesystem. diff --git a/fs/gfs2/Kconfig b/fs/gfs2/Kconfig index b192c661caa6..4dcddf83326f 100644 --- a/fs/gfs2/Kconfig +++ b/fs/gfs2/Kconfig @@ -10,7 +10,6 @@ config GFS2_FS select SLOW_WORK select QUOTA select QUOTACTL - select FS_JOURNAL_INFO help A cluster filesystem. diff --git a/fs/jbd/Kconfig b/fs/jbd/Kconfig index a8408983abd4..4e28beeed157 100644 --- a/fs/jbd/Kconfig +++ b/fs/jbd/Kconfig @@ -1,6 +1,5 @@ config JBD tristate - select FS_JOURNAL_INFO help This is a generic journalling layer for block devices. It is currently used by the ext3 file system, but it could also be diff --git a/fs/jbd2/Kconfig b/fs/jbd2/Kconfig index 0f7d1ceafdfd..f32f346f4b0a 100644 --- a/fs/jbd2/Kconfig +++ b/fs/jbd2/Kconfig @@ -1,7 +1,6 @@ config JBD2 tristate select CRC32 - select FS_JOURNAL_INFO help This is a generic journaling layer for block devices that support both 32-bit and 64-bit block numbers. It is currently used by diff --git a/fs/namespace.c b/fs/namespace.c index faab1273281e..7d70d63ceb29 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -2068,7 +2068,7 @@ struct mnt_namespace *copy_mnt_ns(unsigned long flags, struct mnt_namespace *ns, * create_mnt_ns - creates a private namespace and adds a root filesystem * @mnt: pointer to the new root filesystem mountpoint */ -static struct mnt_namespace *create_mnt_ns(struct vfsmount *mnt) +struct mnt_namespace *create_mnt_ns(struct vfsmount *mnt) { struct mnt_namespace *new_ns; @@ -2080,6 +2080,7 @@ static struct mnt_namespace *create_mnt_ns(struct vfsmount *mnt) } return new_ns; } +EXPORT_SYMBOL(create_mnt_ns); SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name, char __user *, type, unsigned long, flags, void __user *, data) diff --git a/fs/nfs/super.c b/fs/nfs/super.c index d5b112bcf3de..ce907efc5508 100644 --- a/fs/nfs/super.c +++ b/fs/nfs/super.c @@ -2648,13 +2648,21 @@ out_freepage: static int nfs_follow_remote_path(struct vfsmount *root_mnt, const char *export_path, struct vfsmount *mnt_target) { + struct mnt_namespace *ns_private; struct nameidata nd; struct super_block *s; int ret; + ns_private = create_mnt_ns(root_mnt); + ret = PTR_ERR(ns_private); + if (IS_ERR(ns_private)) + goto out_mntput; + ret = vfs_path_lookup(root_mnt->mnt_root, root_mnt, export_path, LOOKUP_FOLLOW, &nd); + put_mnt_ns(ns_private); + if (ret != 0) goto out_err; diff --git a/fs/nilfs2/Kconfig b/fs/nilfs2/Kconfig index 1225af7b2166..251da07b2a1d 100644 --- a/fs/nilfs2/Kconfig +++ b/fs/nilfs2/Kconfig @@ -2,7 +2,6 @@ config NILFS2_FS tristate "NILFS2 file system support (EXPERIMENTAL)" depends on EXPERIMENTAL select CRC32 - select FS_JOURNAL_INFO help NILFS2 is a log-structured file system (LFS) supporting continuous snapshotting. In addition to versioning capability of the entire diff --git a/fs/ramfs/file-nommu.c b/fs/ramfs/file-nommu.c index 32fae4040ebf..2efc57173fd7 100644 --- a/fs/ramfs/file-nommu.c +++ b/fs/ramfs/file-nommu.c @@ -60,7 +60,7 @@ const struct inode_operations ramfs_file_inode_operations = { */ int ramfs_nommu_expand_for_mapping(struct inode *inode, size_t newsize) { - unsigned long npages, xpages, loop, limit; + unsigned long npages, xpages, loop; struct page *pages; unsigned order; void *data; diff --git a/fs/reiserfs/Kconfig b/fs/reiserfs/Kconfig index ac7cd75c86f8..513f431038f9 100644 --- a/fs/reiserfs/Kconfig +++ b/fs/reiserfs/Kconfig @@ -1,7 +1,6 @@ config REISERFS_FS tristate "Reiserfs support" select CRC32 - select FS_JOURNAL_INFO help Stores not just filenames but the files themselves in a balanced tree. Uses journalling. diff --git a/fs/reiserfs/inode.c b/fs/reiserfs/inode.c index 3a28e7751b3c..290ae38fca8a 100644 --- a/fs/reiserfs/inode.c +++ b/fs/reiserfs/inode.c @@ -2538,6 +2538,12 @@ static int reiserfs_writepage(struct page *page, struct writeback_control *wbc) return reiserfs_write_full_page(page, wbc); } +static void reiserfs_truncate_failed_write(struct inode *inode) +{ + truncate_inode_pages(inode->i_mapping, inode->i_size); + reiserfs_truncate_file(inode, 0); +} + static int reiserfs_write_begin(struct file *file, struct address_space *mapping, loff_t pos, unsigned len, unsigned flags, @@ -2604,6 +2610,8 @@ static int reiserfs_write_begin(struct file *file, if (ret) { unlock_page(page); page_cache_release(page); + /* Truncate allocated blocks */ + reiserfs_truncate_failed_write(inode); } return ret; } @@ -2701,9 +2709,7 @@ static int reiserfs_write_end(struct file *file, struct address_space *mapping, ** transaction tracking stuff when the size changes. So, we have ** to do the i_size updates here. */ - pos += copied; - - if (pos > inode->i_size) { + if (pos + copied > inode->i_size) { struct reiserfs_transaction_handle myth; lock_depth = reiserfs_write_lock_once(inode->i_sb); locked = true; @@ -2721,7 +2727,7 @@ static int reiserfs_write_end(struct file *file, struct address_space *mapping, goto journal_error; reiserfs_update_inode_transaction(inode); - inode->i_size = pos; + inode->i_size = pos + copied; /* * this will just nest into our transaction. It's important * to use mark_inode_dirty so the inode gets pushed around on the @@ -2751,6 +2757,10 @@ static int reiserfs_write_end(struct file *file, struct address_space *mapping, reiserfs_write_unlock_once(inode->i_sb, lock_depth); unlock_page(page); page_cache_release(page); + + if (pos + len > inode->i_size) + reiserfs_truncate_failed_write(inode); + return ret == 0 ? copied : ret; journal_error: diff --git a/include/linux/backlight.h b/include/linux/backlight.h index 0f5f57858a23..8c4f884db6b4 100644 --- a/include/linux/backlight.h +++ b/include/linux/backlight.h @@ -36,18 +36,18 @@ struct backlight_device; struct fb_info; struct backlight_ops { - unsigned int options; + const unsigned int options; #define BL_CORE_SUSPENDRESUME (1 << 0) /* Notify the backlight driver some property has changed */ - int (*update_status)(struct backlight_device *); + int (* const update_status)(struct backlight_device *); /* Return the current backlight brightness (accounting for power, fb_blank etc.) */ - int (*get_brightness)(struct backlight_device *); + int (* const get_brightness)(struct backlight_device *); /* Check if given framebuffer device is the one bound to this backlight; return 0 if not, !=0 if it is. If NULL, backlight always matches the fb. */ - int (*check_fb)(struct fb_info *); + int (* const check_fb)(struct fb_info *); }; /* This structure defines all the properties of a backlight */ @@ -86,7 +86,7 @@ struct backlight_device { registered this device has been unloaded, and if class_get_devdata() points to something in the body of that driver, it is also invalid. */ struct mutex ops_lock; - struct backlight_ops *ops; + const struct backlight_ops *ops; /* The framebuffer notifier block */ struct notifier_block fb_notif; @@ -103,7 +103,7 @@ static inline void backlight_update_status(struct backlight_device *bd) } extern struct backlight_device *backlight_device_register(const char *name, - struct device *dev, void *devdata, struct backlight_ops *ops); + struct device *dev, void *devdata, const struct backlight_ops *ops); extern void backlight_device_unregister(struct backlight_device *bd); extern void backlight_force_update(struct backlight_device *bd, enum backlight_update_reason reason); diff --git a/include/linux/binfmts.h b/include/linux/binfmts.h index aece486ac734..cd4349bdc34e 100644 --- a/include/linux/binfmts.h +++ b/include/linux/binfmts.h @@ -68,6 +68,14 @@ struct linux_binprm{ #define BINPRM_MAX_RECURSION 4 +/* Function parameter for binfmt->coredump */ +struct coredump_params { + long signr; + struct pt_regs *regs; + struct file *file; + unsigned long limit; +}; + /* * This structure defines the functions that are used to load the binary formats that * linux accepts. @@ -77,7 +85,7 @@ struct linux_binfmt { struct module *module; int (*load_binary)(struct linux_binprm *, struct pt_regs * regs); int (*load_shlib)(struct file *); - int (*core_dump)(long signr, struct pt_regs *regs, struct file *file, unsigned long limit); + int (*core_dump)(struct coredump_params *cprm); unsigned long min_coredump; /* minimal dump size */ int hasvdso; }; diff --git a/include/linux/init_task.h b/include/linux/init_task.h index 5ed8b9c50355..abec69b63d7e 100644 --- a/include/linux/init_task.h +++ b/include/linux/init_task.h @@ -111,12 +111,6 @@ extern struct cred init_cred; # define INIT_PERF_EVENTS(tsk) #endif -#ifdef CONFIG_FS_JOURNAL_INFO -#define INIT_JOURNAL_INFO .journal_info = NULL, -#else -#define INIT_JOURNAL_INFO -#endif - /* * INIT_TASK is used to set up the first task table, touch at * your own risk!. Base=0, limit=0x1fffff (=2MB) @@ -168,6 +162,7 @@ extern struct cred init_cred; .signal = {{0}}}, \ .blocked = {{0}}, \ .alloc_lock = __SPIN_LOCK_UNLOCKED(tsk.alloc_lock), \ + .journal_info = NULL, \ .cpu_timers = INIT_CPU_TIMERS(tsk.cpu_timers), \ .fs_excl = ATOMIC_INIT(0), \ .pi_lock = __RAW_SPIN_LOCK_UNLOCKED(tsk.pi_lock), \ @@ -178,7 +173,6 @@ extern struct cred init_cred; [PIDTYPE_SID] = INIT_PID_LINK(PIDTYPE_SID), \ }, \ .dirties = INIT_PROP_LOCAL_SINGLE(dirties), \ - INIT_JOURNAL_INFO \ INIT_IDS \ INIT_PERF_EVENTS(tsk) \ INIT_TRACE_IRQFLAGS \ diff --git a/include/linux/kmemleak.h b/include/linux/kmemleak.h index 3c7497d46ee9..99d9a6766f7e 100644 --- a/include/linux/kmemleak.h +++ b/include/linux/kmemleak.h @@ -32,8 +32,7 @@ extern void kmemleak_padding(const void *ptr, unsigned long offset, size_t size) __ref; extern void kmemleak_not_leak(const void *ptr) __ref; extern void kmemleak_ignore(const void *ptr) __ref; -extern void kmemleak_scan_area(const void *ptr, unsigned long offset, - size_t length, gfp_t gfp) __ref; +extern void kmemleak_scan_area(const void *ptr, size_t size, gfp_t gfp) __ref; extern void kmemleak_no_scan(const void *ptr) __ref; static inline void kmemleak_alloc_recursive(const void *ptr, size_t size, @@ -84,8 +83,7 @@ static inline void kmemleak_not_leak(const void *ptr) static inline void kmemleak_ignore(const void *ptr) { } -static inline void kmemleak_scan_area(const void *ptr, unsigned long offset, - size_t length, gfp_t gfp) +static inline void kmemleak_scan_area(const void *ptr, size_t size, gfp_t gfp) { } static inline void kmemleak_erase(void **ptr) diff --git a/include/linux/leds-lp3944.h b/include/linux/leds-lp3944.h index afc9f9fd70f5..2618aa9063bc 100644 --- a/include/linux/leds-lp3944.h +++ b/include/linux/leds-lp3944.h @@ -12,9 +12,6 @@ #ifndef __LINUX_LEDS_LP3944_H #define __LINUX_LEDS_LP3944_H -#include <linux/leds.h> -#include <linux/workqueue.h> - #define LP3944_LED0 0 #define LP3944_LED1 1 #define LP3944_LED2 2 diff --git a/include/linux/leds-pca9532.h b/include/linux/leds-pca9532.h index 96eea90f01a8..f158eb1149aa 100644 --- a/include/linux/leds-pca9532.h +++ b/include/linux/leds-pca9532.h @@ -32,7 +32,7 @@ struct pca9532_led { struct i2c_client *client; char *name; struct led_classdev ldev; - struct work_struct work; + struct work_struct work; enum pca9532_type type; enum pca9532_state state; }; diff --git a/include/linux/leds-regulator.h b/include/linux/leds-regulator.h new file mode 100644 index 000000000000..5a8eb389aab8 --- /dev/null +++ b/include/linux/leds-regulator.h @@ -0,0 +1,46 @@ +/* + * leds-regulator.h - platform data structure for regulator driven LEDs. + * + * Copyright (C) 2009 Antonio Ospite <ospite@studenti.unina.it> + * + * 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. + * + */ + +#ifndef __LINUX_LEDS_REGULATOR_H +#define __LINUX_LEDS_REGULATOR_H + +/* + * Use "vled" as supply id when declaring the regulator consumer: + * + * static struct regulator_consumer_supply pcap_regulator_VVIB_consumers [] = { + * { .dev_name = "leds-regulator.0", supply = "vled" }, + * }; + * + * If you have several regulator driven LEDs, you can append a numerical id to + * .dev_name as done above, and use the same id when declaring the platform + * device: + * + * static struct led_regulator_platform_data a780_vibrator_data = { + * .name = "a780::vibrator", + * }; + * + * static struct platform_device a780_vibrator = { + * .name = "leds-regulator", + * .id = 0, + * .dev = { + * .platform_data = &a780_vibrator_data, + * }, + * }; + */ + +#include <linux/leds.h> + +struct led_regulator_platform_data { + char *name; /* LED name as expected by LED class */ + enum led_brightness brightness; /* initial brightness value */ +}; + +#endif /* __LINUX_LEDS_REGULATOR_H */ diff --git a/include/linux/mnt_namespace.h b/include/linux/mnt_namespace.h index d9ebf1037dfa..d74785c2393a 100644 --- a/include/linux/mnt_namespace.h +++ b/include/linux/mnt_namespace.h @@ -23,6 +23,7 @@ struct proc_mounts { struct fs_struct; +extern struct mnt_namespace *create_mnt_ns(struct vfsmount *mnt); extern struct mnt_namespace *copy_mnt_ns(unsigned long, struct mnt_namespace *, struct fs_struct *); extern void put_mnt_ns(struct mnt_namespace *ns); diff --git a/include/linux/pwm_backlight.h b/include/linux/pwm_backlight.h index 7a9754c96775..01b3d759f1fc 100644 --- a/include/linux/pwm_backlight.h +++ b/include/linux/pwm_backlight.h @@ -10,7 +10,7 @@ struct platform_pwm_backlight_data { unsigned int dft_brightness; unsigned int pwm_period_ns; int (*init)(struct device *dev); - int (*notify)(int brightness); + int (*notify)(struct device *dev, int brightness); void (*exit)(struct device *dev); }; diff --git a/include/linux/sched.h b/include/linux/sched.h index 244c287a5ac1..211ed32befbd 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -1446,10 +1446,8 @@ struct task_struct { gfp_t lockdep_reclaim_gfp; #endif -#ifdef CONFIG_FS_JOURNAL_INFO /* journalling filesystem info */ void *journal_info; -#endif /* stacked block device info */ struct bio *bio_list, **bio_tail; diff --git a/include/linux/spi/dw_spi.h b/include/linux/spi/dw_spi.h new file mode 100644 index 000000000000..51b3e771a9a3 --- /dev/null +++ b/include/linux/spi/dw_spi.h @@ -0,0 +1,212 @@ +#ifndef DW_SPI_HEADER_H +#define DW_SPI_HEADER_H +#include <linux/io.h> + +/* Bit fields in CTRLR0 */ +#define SPI_DFS_OFFSET 0 + +#define SPI_FRF_OFFSET 4 +#define SPI_FRF_SPI 0x0 +#define SPI_FRF_SSP 0x1 +#define SPI_FRF_MICROWIRE 0x2 +#define SPI_FRF_RESV 0x3 + +#define SPI_MODE_OFFSET 6 +#define SPI_SCPH_OFFSET 6 +#define SPI_SCOL_OFFSET 7 +#define SPI_TMOD_OFFSET 8 +#define SPI_TMOD_TR 0x0 /* xmit & recv */ +#define SPI_TMOD_TO 0x1 /* xmit only */ +#define SPI_TMOD_RO 0x2 /* recv only */ +#define SPI_TMOD_EPROMREAD 0x3 /* eeprom read mode */ + +#define SPI_SLVOE_OFFSET 10 +#define SPI_SRL_OFFSET 11 +#define SPI_CFS_OFFSET 12 + +/* Bit fields in SR, 7 bits */ +#define SR_MASK 0x7f /* cover 7 bits */ +#define SR_BUSY (1 << 0) +#define SR_TF_NOT_FULL (1 << 1) +#define SR_TF_EMPT (1 << 2) +#define SR_RF_NOT_EMPT (1 << 3) +#define SR_RF_FULL (1 << 4) +#define SR_TX_ERR (1 << 5) +#define SR_DCOL (1 << 6) + +/* Bit fields in ISR, IMR, RISR, 7 bits */ +#define SPI_INT_TXEI (1 << 0) +#define SPI_INT_TXOI (1 << 1) +#define SPI_INT_RXUI (1 << 2) +#define SPI_INT_RXOI (1 << 3) +#define SPI_INT_RXFI (1 << 4) +#define SPI_INT_MSTI (1 << 5) + +/* TX RX interrupt level threshhold, max can be 256 */ +#define SPI_INT_THRESHOLD 32 + +enum dw_ssi_type { + SSI_MOTO_SPI = 0, + SSI_TI_SSP, + SSI_NS_MICROWIRE, +}; + +struct dw_spi_reg { + u32 ctrl0; + u32 ctrl1; + u32 ssienr; + u32 mwcr; + u32 ser; + u32 baudr; + u32 txfltr; + u32 rxfltr; + u32 txflr; + u32 rxflr; + u32 sr; + u32 imr; + u32 isr; + u32 risr; + u32 txoicr; + u32 rxoicr; + u32 rxuicr; + u32 msticr; + u32 icr; + u32 dmacr; + u32 dmatdlr; + u32 dmardlr; + u32 idr; + u32 version; + u32 dr; /* Currently oper as 32 bits, + though only low 16 bits matters */ +} __packed; + +struct dw_spi { + struct spi_master *master; + struct spi_device *cur_dev; + struct device *parent_dev; + enum dw_ssi_type type; + + void __iomem *regs; + unsigned long paddr; + u32 iolen; + int irq; + u32 max_freq; /* max bus freq supported */ + + u16 bus_num; + u16 num_cs; /* supported slave numbers */ + + /* Driver message queue */ + struct workqueue_struct *workqueue; + struct work_struct pump_messages; + spinlock_t lock; + struct list_head queue; + int busy; + int run; + + /* Message Transfer pump */ + struct tasklet_struct pump_transfers; + + /* Current message transfer state info */ + struct spi_message *cur_msg; + struct spi_transfer *cur_transfer; + struct chip_data *cur_chip; + struct chip_data *prev_chip; + size_t len; + void *tx; + void *tx_end; + void *rx; + void *rx_end; + int dma_mapped; + dma_addr_t rx_dma; + dma_addr_t tx_dma; + size_t rx_map_len; + size_t tx_map_len; + u8 n_bytes; /* current is a 1/2 bytes op */ + u8 max_bits_per_word; /* maxim is 16b */ + u32 dma_width; + int cs_change; + int (*write)(struct dw_spi *dws); + int (*read)(struct dw_spi *dws); + irqreturn_t (*transfer_handler)(struct dw_spi *dws); + void (*cs_control)(u32 command); + + /* Dma info */ + int dma_inited; + struct dma_chan *txchan; + struct dma_chan *rxchan; + int txdma_done; + int rxdma_done; + u64 tx_param; + u64 rx_param; + struct device *dma_dev; + dma_addr_t dma_addr; + + /* Bus interface info */ + void *priv; +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs; +#endif +}; + +#define dw_readl(dw, name) \ + __raw_readl(&(((struct dw_spi_reg *)dw->regs)->name)) +#define dw_writel(dw, name, val) \ + __raw_writel((val), &(((struct dw_spi_reg *)dw->regs)->name)) +#define dw_readw(dw, name) \ + __raw_readw(&(((struct dw_spi_reg *)dw->regs)->name)) +#define dw_writew(dw, name, val) \ + __raw_writew((val), &(((struct dw_spi_reg *)dw->regs)->name)) + +static inline void spi_enable_chip(struct dw_spi *dws, int enable) +{ + dw_writel(dws, ssienr, (enable ? 1 : 0)); +} + +static inline void spi_set_clk(struct dw_spi *dws, u16 div) +{ + dw_writel(dws, baudr, div); +} + +static inline void spi_chip_sel(struct dw_spi *dws, u16 cs) +{ + if (cs > dws->num_cs) + return; + dw_writel(dws, ser, 1 << cs); +} + +/* Disable IRQ bits */ +static inline void spi_mask_intr(struct dw_spi *dws, u32 mask) +{ + u32 new_mask; + + new_mask = dw_readl(dws, imr) & ~mask; + dw_writel(dws, imr, new_mask); +} + +/* Enable IRQ bits */ +static inline void spi_umask_intr(struct dw_spi *dws, u32 mask) +{ + u32 new_mask; + + new_mask = dw_readl(dws, imr) | mask; + dw_writel(dws, imr, new_mask); +} + +/* + * Each SPI slave device to work with dw_api controller should + * has such a structure claiming its working mode (PIO/DMA etc), + * which can be save in the "controller_data" member of the + * struct spi_device + */ +struct dw_spi_chip { + u8 poll_mode; /* 0 for contoller polling mode */ + u8 type; /* SPI/SSP/Micrwire */ + u8 enable_dma; + void (*cs_control)(u32 command); +}; + +extern int dw_spi_add_host(struct dw_spi *dws); +extern void dw_spi_remove_host(struct dw_spi *dws); +extern int dw_spi_suspend_host(struct dw_spi *dws); +extern int dw_spi_resume_host(struct dw_spi *dws); +#endif /* DW_SPI_HEADER_H */ diff --git a/include/linux/vt.h b/include/linux/vt.h index 3fb9944e50a6..d5dd0bc408fd 100644 --- a/include/linux/vt.h +++ b/include/linux/vt.h @@ -84,6 +84,8 @@ struct vt_setactivate { #define VT_SETACTIVATE 0x560F /* Activate and set the mode of a console */ +#ifdef __KERNEL__ + #ifdef CONFIG_VT_CONSOLE extern int vt_kmsg_redirect(int new); @@ -97,6 +99,8 @@ static inline int vt_kmsg_redirect(int new) #endif +#endif /* __KERNEL__ */ + #define vt_get_kmsg_redirect() vt_kmsg_redirect(-1) #endif /* _LINUX_VT_H */ diff --git a/kernel/exit.c b/kernel/exit.c index 5962d7ccf243..546774a31a66 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -68,10 +68,10 @@ static void __unhash_process(struct task_struct *p) detach_pid(p, PIDTYPE_SID); list_del_rcu(&p->tasks); + list_del_init(&p->sibling); __get_cpu_var(process_counts)--; } list_del_rcu(&p->thread_group); - list_del_init(&p->sibling); } /* @@ -736,12 +736,9 @@ static struct task_struct *find_new_reaper(struct task_struct *father) /* * Any that need to be release_task'd are put on the @dead list. */ -static void reparent_thread(struct task_struct *father, struct task_struct *p, +static void reparent_leader(struct task_struct *father, struct task_struct *p, struct list_head *dead) { - if (p->pdeath_signal) - group_send_sig_info(p->pdeath_signal, SEND_SIG_NOINFO, p); - list_move_tail(&p->sibling, &p->real_parent->children); if (task_detached(p)) @@ -780,12 +777,18 @@ static void forget_original_parent(struct task_struct *father) reaper = find_new_reaper(father); list_for_each_entry_safe(p, n, &father->children, sibling) { - p->real_parent = reaper; - if (p->parent == father) { - BUG_ON(task_ptrace(p)); - p->parent = p->real_parent; - } - reparent_thread(father, p, &dead_children); + struct task_struct *t = p; + do { + t->real_parent = reaper; + if (t->parent == father) { + BUG_ON(task_ptrace(t)); + t->parent = t->real_parent; + } + if (t->pdeath_signal) + group_send_sig_info(t->pdeath_signal, + SEND_SIG_NOINFO, t); + } while_each_thread(p, t); + reparent_leader(father, p, &dead_children); } write_unlock_irq(&tasklist_lock); @@ -1551,14 +1554,9 @@ static int do_wait_thread(struct wait_opts *wo, struct task_struct *tsk) struct task_struct *p; list_for_each_entry(p, &tsk->children, sibling) { - /* - * Do not consider detached threads. - */ - if (!task_detached(p)) { - int ret = wait_consider_task(wo, 0, p); - if (ret) - return ret; - } + int ret = wait_consider_task(wo, 0, p); + if (ret) + return ret; } return 0; diff --git a/kernel/fork.c b/kernel/fork.c index 202a0ba63d3c..5b2959b3ffc2 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -1291,7 +1291,6 @@ static struct task_struct *copy_process(unsigned long clone_flags, } if (likely(p->pid)) { - list_add_tail(&p->sibling, &p->real_parent->children); tracehook_finish_clone(p, clone_flags, trace); if (thread_group_leader(p)) { @@ -1303,6 +1302,7 @@ static struct task_struct *copy_process(unsigned long clone_flags, p->signal->tty = tty_kref_get(current->signal->tty); attach_pid(p, PIDTYPE_PGID, task_pgrp(current)); attach_pid(p, PIDTYPE_SID, task_session(current)); + list_add_tail(&p->sibling, &p->real_parent->children); list_add_tail_rcu(&p->tasks, &init_task.tasks); __get_cpu_var(process_counts)++; } diff --git a/kernel/module.c b/kernel/module.c index a65dc787a27b..e96b8ed1cb6a 100644 --- a/kernel/module.c +++ b/kernel/module.c @@ -1910,9 +1910,7 @@ static void kmemleak_load_module(struct module *mod, Elf_Ehdr *hdr, unsigned int i; /* only scan the sections containing data */ - kmemleak_scan_area(mod->module_core, (unsigned long)mod - - (unsigned long)mod->module_core, - sizeof(struct module), GFP_KERNEL); + kmemleak_scan_area(mod, sizeof(struct module), GFP_KERNEL); for (i = 1; i < hdr->e_shnum; i++) { if (!(sechdrs[i].sh_flags & SHF_ALLOC)) @@ -1921,8 +1919,7 @@ static void kmemleak_load_module(struct module *mod, Elf_Ehdr *hdr, && strncmp(secstrings + sechdrs[i].sh_name, ".bss", 4) != 0) continue; - kmemleak_scan_area(mod->module_core, sechdrs[i].sh_addr - - (unsigned long)mod->module_core, + kmemleak_scan_area((void *)sechdrs[i].sh_addr, sechdrs[i].sh_size, GFP_KERNEL); } } @@ -2250,6 +2247,12 @@ static noinline struct module *load_module(void __user *umod, "_ftrace_events", sizeof(*mod->trace_events), &mod->num_trace_events); + /* + * This section contains pointers to allocated objects in the trace + * code and not scanning it leads to false positives. + */ + kmemleak_scan_area(mod->trace_events, sizeof(*mod->trace_events) * + mod->num_trace_events, GFP_KERNEL); #endif #ifdef CONFIG_FTRACE_MCOUNT_RECORD /* sechdrs[0].sh_size is always zero */ diff --git a/kernel/printk.c b/kernel/printk.c index 1ded8e7dd19b..17463ca2e229 100644 --- a/kernel/printk.c +++ b/kernel/printk.c @@ -1412,7 +1412,7 @@ static LIST_HEAD(dump_list); /** * kmsg_dump_register - register a kernel log dumper. - * @dump: pointer to the kmsg_dumper structure + * @dumper: pointer to the kmsg_dumper structure * * Adds a kernel log dumper to the system. The dump callback in the * structure will be called when the kernel oopses or panics and must be @@ -1442,7 +1442,7 @@ EXPORT_SYMBOL_GPL(kmsg_dump_register); /** * kmsg_dump_unregister - unregister a kmsg dumper. - * @dump: pointer to the kmsg_dumper structure + * @dumper: pointer to the kmsg_dumper structure * * Removes a dump device from the system. Returns zero on success and * %-EINVAL otherwise. diff --git a/kernel/sysctl.c b/kernel/sysctl.c index 45e4bef0012a..6665761c006d 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -1131,7 +1131,7 @@ static struct ctl_table vm_table[] = { .data = &sysctl_max_map_count, .maxlen = sizeof(sysctl_max_map_count), .mode = 0644, - .proc_handler = proc_dointvec, + .proc_handler = proc_dointvec_minmax, .extra1 = &zero, }, #else diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 8cf9938dd147..25c3ed594c54 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -360,6 +360,7 @@ config DEBUG_KMEMLEAK select DEBUG_FS if SYSFS select STACKTRACE if STACKTRACE_SUPPORT select KALLSYMS + select CRC32 help Say Y here if you want to enable the memory leak detector. The memory allocation/freeing is traced in a way diff --git a/lib/vsprintf.c b/lib/vsprintf.c index 735343fc857a..d4996cf46eb6 100644 --- a/lib/vsprintf.c +++ b/lib/vsprintf.c @@ -1179,7 +1179,18 @@ qualifier: * %ps output the name of a text symbol without offset * %pF output the name of a function pointer with its offset * %pf output the name of a function pointer without its offset - * %pR output the address range in a struct resource + * %pR output the address range in a struct resource with decoded flags + * %pr output the address range in a struct resource with raw flags + * %pM output a 6-byte MAC address with colons + * %pm output a 6-byte MAC address without colons + * %pI4 print an IPv4 address without leading zeros + * %pi4 print an IPv4 address with leading zeros + * %pI6 print an IPv6 address with colons + * %pi6 print an IPv6 address without colons + * %pI6c print an IPv6 address as specified by + * http://www.ietf.org/id/draft-kawamura-ipv6-text-representation-03.txt + * %pU[bBlL] print a UUID/GUID in big or little endian using lower or upper + * case. * %n is ignored * * The return value is the number of characters which would diff --git a/mm/kmemleak.c b/mm/kmemleak.c index 13f33b3081ec..5b069e4f5e48 100644 --- a/mm/kmemleak.c +++ b/mm/kmemleak.c @@ -93,6 +93,7 @@ #include <linux/nodemask.h> #include <linux/mm.h> #include <linux/workqueue.h> +#include <linux/crc32.h> #include <asm/sections.h> #include <asm/processor.h> @@ -108,7 +109,6 @@ #define MSECS_MIN_AGE 5000 /* minimum object age for reporting */ #define SECS_FIRST_SCAN 60 /* delay before the first scan */ #define SECS_SCAN_WAIT 600 /* subsequent auto scanning delay */ -#define GRAY_LIST_PASSES 25 /* maximum number of gray list scans */ #define MAX_SCAN_SIZE 4096 /* maximum size of a scanned block */ #define BYTES_PER_POINTER sizeof(void *) @@ -119,8 +119,8 @@ /* scanning area inside a memory block */ struct kmemleak_scan_area { struct hlist_node node; - unsigned long offset; - size_t length; + unsigned long start; + size_t size; }; #define KMEMLEAK_GREY 0 @@ -149,6 +149,8 @@ struct kmemleak_object { int min_count; /* the total number of pointers found pointing to this object */ int count; + /* checksum for detecting modified objects */ + u32 checksum; /* memory ranges to be scanned inside an object (empty for all) */ struct hlist_head area_list; unsigned long trace[MAX_TRACE]; @@ -164,8 +166,6 @@ struct kmemleak_object { #define OBJECT_REPORTED (1 << 1) /* flag set to not scan the object */ #define OBJECT_NO_SCAN (1 << 2) -/* flag set on newly allocated objects */ -#define OBJECT_NEW (1 << 3) /* number of bytes to print per line; must be 16 or 32 */ #define HEX_ROW_SIZE 16 @@ -241,8 +241,6 @@ struct early_log { const void *ptr; /* allocated/freed memory block */ size_t size; /* memory block size */ int min_count; /* minimum reference count */ - unsigned long offset; /* scan area offset */ - size_t length; /* scan area length */ unsigned long trace[MAX_TRACE]; /* stack trace */ unsigned int trace_len; /* stack trace length */ }; @@ -323,11 +321,6 @@ static bool color_gray(const struct kmemleak_object *object) object->count >= object->min_count; } -static bool color_black(const struct kmemleak_object *object) -{ - return object->min_count == KMEMLEAK_BLACK; -} - /* * Objects are considered unreferenced only if their color is white, they have * not be deleted and have a minimum age to avoid false positives caused by @@ -335,7 +328,7 @@ static bool color_black(const struct kmemleak_object *object) */ static bool unreferenced_object(struct kmemleak_object *object) { - return (object->flags & OBJECT_ALLOCATED) && color_white(object) && + return (color_white(object) && object->flags & OBJECT_ALLOCATED) && time_before_eq(object->jiffies + jiffies_min_age, jiffies_last_scan); } @@ -348,11 +341,13 @@ static void print_unreferenced(struct seq_file *seq, struct kmemleak_object *object) { int i; + unsigned int msecs_age = jiffies_to_msecs(jiffies - object->jiffies); seq_printf(seq, "unreferenced object 0x%08lx (size %zu):\n", object->pointer, object->size); - seq_printf(seq, " comm \"%s\", pid %d, jiffies %lu\n", - object->comm, object->pid, object->jiffies); + seq_printf(seq, " comm \"%s\", pid %d, jiffies %lu (age %d.%03ds)\n", + object->comm, object->pid, object->jiffies, + msecs_age / 1000, msecs_age % 1000); hex_dump_object(seq, object); seq_printf(seq, " backtrace:\n"); @@ -381,6 +376,7 @@ static void dump_object_info(struct kmemleak_object *object) pr_notice(" min_count = %d\n", object->min_count); pr_notice(" count = %d\n", object->count); pr_notice(" flags = 0x%lx\n", object->flags); + pr_notice(" checksum = %d\n", object->checksum); pr_notice(" backtrace:\n"); print_stack_trace(&trace, 4); } @@ -522,12 +518,13 @@ static struct kmemleak_object *create_object(unsigned long ptr, size_t size, INIT_HLIST_HEAD(&object->area_list); spin_lock_init(&object->lock); atomic_set(&object->use_count, 1); - object->flags = OBJECT_ALLOCATED | OBJECT_NEW; + object->flags = OBJECT_ALLOCATED; object->pointer = ptr; object->size = size; object->min_count = min_count; - object->count = -1; /* no color initially */ + object->count = 0; /* white color initially */ object->jiffies = jiffies; + object->checksum = 0; /* task information */ if (in_irq()) { @@ -720,14 +717,13 @@ static void make_black_object(unsigned long ptr) * Add a scanning area to the object. If at least one such area is added, * kmemleak will only scan these ranges rather than the whole memory block. */ -static void add_scan_area(unsigned long ptr, unsigned long offset, - size_t length, gfp_t gfp) +static void add_scan_area(unsigned long ptr, size_t size, gfp_t gfp) { unsigned long flags; struct kmemleak_object *object; struct kmemleak_scan_area *area; - object = find_and_get_object(ptr, 0); + object = find_and_get_object(ptr, 1); if (!object) { kmemleak_warn("Adding scan area to unknown object at 0x%08lx\n", ptr); @@ -741,7 +737,7 @@ static void add_scan_area(unsigned long ptr, unsigned long offset, } spin_lock_irqsave(&object->lock, flags); - if (offset + length > object->size) { + if (ptr + size > object->pointer + object->size) { kmemleak_warn("Scan area larger than object 0x%08lx\n", ptr); dump_object_info(object); kmem_cache_free(scan_area_cache, area); @@ -749,8 +745,8 @@ static void add_scan_area(unsigned long ptr, unsigned long offset, } INIT_HLIST_NODE(&area->node); - area->offset = offset; - area->length = length; + area->start = ptr; + area->size = size; hlist_add_head(&area->node, &object->area_list); out_unlock: @@ -786,7 +782,7 @@ static void object_no_scan(unsigned long ptr) * processed later once kmemleak is fully initialized. */ static void __init log_early(int op_type, const void *ptr, size_t size, - int min_count, unsigned long offset, size_t length) + int min_count) { unsigned long flags; struct early_log *log; @@ -808,8 +804,6 @@ static void __init log_early(int op_type, const void *ptr, size_t size, log->ptr = ptr; log->size = size; log->min_count = min_count; - log->offset = offset; - log->length = length; if (op_type == KMEMLEAK_ALLOC) log->trace_len = __save_stack_trace(log->trace); crt_early_log++; @@ -858,7 +852,7 @@ void __ref kmemleak_alloc(const void *ptr, size_t size, int min_count, if (atomic_read(&kmemleak_enabled) && ptr && !IS_ERR(ptr)) create_object((unsigned long)ptr, size, min_count, gfp); else if (atomic_read(&kmemleak_early_log)) - log_early(KMEMLEAK_ALLOC, ptr, size, min_count, 0, 0); + log_early(KMEMLEAK_ALLOC, ptr, size, min_count); } EXPORT_SYMBOL_GPL(kmemleak_alloc); @@ -873,7 +867,7 @@ void __ref kmemleak_free(const void *ptr) if (atomic_read(&kmemleak_enabled) && ptr && !IS_ERR(ptr)) delete_object_full((unsigned long)ptr); else if (atomic_read(&kmemleak_early_log)) - log_early(KMEMLEAK_FREE, ptr, 0, 0, 0, 0); + log_early(KMEMLEAK_FREE, ptr, 0, 0); } EXPORT_SYMBOL_GPL(kmemleak_free); @@ -888,7 +882,7 @@ void __ref kmemleak_free_part(const void *ptr, size_t size) if (atomic_read(&kmemleak_enabled) && ptr && !IS_ERR(ptr)) delete_object_part((unsigned long)ptr, size); else if (atomic_read(&kmemleak_early_log)) - log_early(KMEMLEAK_FREE_PART, ptr, size, 0, 0, 0); + log_early(KMEMLEAK_FREE_PART, ptr, size, 0); } EXPORT_SYMBOL_GPL(kmemleak_free_part); @@ -903,7 +897,7 @@ void __ref kmemleak_not_leak(const void *ptr) if (atomic_read(&kmemleak_enabled) && ptr && !IS_ERR(ptr)) make_gray_object((unsigned long)ptr); else if (atomic_read(&kmemleak_early_log)) - log_early(KMEMLEAK_NOT_LEAK, ptr, 0, 0, 0, 0); + log_early(KMEMLEAK_NOT_LEAK, ptr, 0, 0); } EXPORT_SYMBOL(kmemleak_not_leak); @@ -919,22 +913,21 @@ void __ref kmemleak_ignore(const void *ptr) if (atomic_read(&kmemleak_enabled) && ptr && !IS_ERR(ptr)) make_black_object((unsigned long)ptr); else if (atomic_read(&kmemleak_early_log)) - log_early(KMEMLEAK_IGNORE, ptr, 0, 0, 0, 0); + log_early(KMEMLEAK_IGNORE, ptr, 0, 0); } EXPORT_SYMBOL(kmemleak_ignore); /* * Limit the range to be scanned in an allocated memory block. */ -void __ref kmemleak_scan_area(const void *ptr, unsigned long offset, - size_t length, gfp_t gfp) +void __ref kmemleak_scan_area(const void *ptr, size_t size, gfp_t gfp) { pr_debug("%s(0x%p)\n", __func__, ptr); if (atomic_read(&kmemleak_enabled) && ptr && !IS_ERR(ptr)) - add_scan_area((unsigned long)ptr, offset, length, gfp); + add_scan_area((unsigned long)ptr, size, gfp); else if (atomic_read(&kmemleak_early_log)) - log_early(KMEMLEAK_SCAN_AREA, ptr, 0, 0, offset, length); + log_early(KMEMLEAK_SCAN_AREA, ptr, size, 0); } EXPORT_SYMBOL(kmemleak_scan_area); @@ -948,11 +941,25 @@ void __ref kmemleak_no_scan(const void *ptr) if (atomic_read(&kmemleak_enabled) && ptr && !IS_ERR(ptr)) object_no_scan((unsigned long)ptr); else if (atomic_read(&kmemleak_early_log)) - log_early(KMEMLEAK_NO_SCAN, ptr, 0, 0, 0, 0); + log_early(KMEMLEAK_NO_SCAN, ptr, 0, 0); } EXPORT_SYMBOL(kmemleak_no_scan); /* + * Update an object's checksum and return true if it was modified. + */ +static bool update_checksum(struct kmemleak_object *object) +{ + u32 old_csum = object->checksum; + + if (!kmemcheck_is_obj_initialized(object->pointer, object->size)) + return false; + + object->checksum = crc32(0, (void *)object->pointer, object->size); + return object->checksum != old_csum; +} + +/* * Memory scanning is a long process and it needs to be interruptable. This * function checks whether such interrupt condition occured. */ @@ -1031,11 +1038,14 @@ static void scan_block(void *_start, void *_end, * added to the gray_list. */ object->count++; - if (color_gray(object)) + if (color_gray(object)) { list_add_tail(&object->gray_list, &gray_list); - else - put_object(object); + spin_unlock_irqrestore(&object->lock, flags); + continue; + } + spin_unlock_irqrestore(&object->lock, flags); + put_object(object); } } @@ -1075,14 +1085,47 @@ static void scan_object(struct kmemleak_object *object) } } else hlist_for_each_entry(area, elem, &object->area_list, node) - scan_block((void *)(object->pointer + area->offset), - (void *)(object->pointer + area->offset - + area->length), object, 0); + scan_block((void *)area->start, + (void *)(area->start + area->size), + object, 0); out: spin_unlock_irqrestore(&object->lock, flags); } /* + * Scan the objects already referenced (gray objects). More objects will be + * referenced and, if there are no memory leaks, all the objects are scanned. + */ +static void scan_gray_list(void) +{ + struct kmemleak_object *object, *tmp; + + /* + * The list traversal is safe for both tail additions and removals + * from inside the loop. The kmemleak objects cannot be freed from + * outside the loop because their use_count was incremented. + */ + object = list_entry(gray_list.next, typeof(*object), gray_list); + while (&object->gray_list != &gray_list) { + cond_resched(); + + /* may add new objects to the list */ + if (!scan_should_stop()) + scan_object(object); + + tmp = list_entry(object->gray_list.next, typeof(*object), + gray_list); + + /* remove the object from the list and release it */ + list_del(&object->gray_list); + put_object(object); + + object = tmp; + } + WARN_ON(!list_empty(&gray_list)); +} + +/* * Scan data sections and all the referenced memory blocks allocated via the * kernel's standard allocators. This function must be called with the * scan_mutex held. @@ -1090,10 +1133,9 @@ out: static void kmemleak_scan(void) { unsigned long flags; - struct kmemleak_object *object, *tmp; + struct kmemleak_object *object; int i; int new_leaks = 0; - int gray_list_pass = 0; jiffies_last_scan = jiffies; @@ -1114,7 +1156,6 @@ static void kmemleak_scan(void) #endif /* reset the reference count (whiten the object) */ object->count = 0; - object->flags &= ~OBJECT_NEW; if (color_gray(object) && get_object(object)) list_add_tail(&object->gray_list, &gray_list); @@ -1172,62 +1213,36 @@ static void kmemleak_scan(void) /* * Scan the objects already referenced from the sections scanned - * above. More objects will be referenced and, if there are no memory - * leaks, all the objects will be scanned. The list traversal is safe - * for both tail additions and removals from inside the loop. The - * kmemleak objects cannot be freed from outside the loop because their - * use_count was increased. + * above. */ -repeat: - object = list_entry(gray_list.next, typeof(*object), gray_list); - while (&object->gray_list != &gray_list) { - cond_resched(); - - /* may add new objects to the list */ - if (!scan_should_stop()) - scan_object(object); - - tmp = list_entry(object->gray_list.next, typeof(*object), - gray_list); - - /* remove the object from the list and release it */ - list_del(&object->gray_list); - put_object(object); - - object = tmp; - } - - if (scan_should_stop() || ++gray_list_pass >= GRAY_LIST_PASSES) - goto scan_end; + scan_gray_list(); /* - * Check for new objects allocated during this scanning and add them - * to the gray list. + * Check for new or unreferenced objects modified since the previous + * scan and color them gray until the next scan. */ rcu_read_lock(); list_for_each_entry_rcu(object, &object_list, object_list) { spin_lock_irqsave(&object->lock, flags); - if ((object->flags & OBJECT_NEW) && !color_black(object) && - get_object(object)) { - object->flags &= ~OBJECT_NEW; + if (color_white(object) && (object->flags & OBJECT_ALLOCATED) + && update_checksum(object) && get_object(object)) { + /* color it gray temporarily */ + object->count = object->min_count; list_add_tail(&object->gray_list, &gray_list); } spin_unlock_irqrestore(&object->lock, flags); } rcu_read_unlock(); - if (!list_empty(&gray_list)) - goto repeat; - -scan_end: - WARN_ON(!list_empty(&gray_list)); + /* + * Re-scan the gray list for modified unreferenced objects. + */ + scan_gray_list(); /* - * If scanning was stopped or new objects were being allocated at a - * higher rate than gray list scanning, do not report any new - * unreferenced objects. + * If scanning was stopped do not report any new unreferenced objects. */ - if (scan_should_stop() || gray_list_pass >= GRAY_LIST_PASSES) + if (scan_should_stop()) return; /* @@ -1642,8 +1657,7 @@ void __init kmemleak_init(void) kmemleak_ignore(log->ptr); break; case KMEMLEAK_SCAN_AREA: - kmemleak_scan_area(log->ptr, log->offset, log->length, - GFP_KERNEL); + kmemleak_scan_area(log->ptr, log->size, GFP_KERNEL); break; case KMEMLEAK_NO_SCAN: kmemleak_no_scan(log->ptr); diff --git a/mm/readahead.c b/mm/readahead.c index aa1aa2345235..033bc135a41f 100644 --- a/mm/readahead.c +++ b/mm/readahead.c @@ -547,5 +547,17 @@ page_cache_async_readahead(struct address_space *mapping, /* do read-ahead */ ondemand_readahead(mapping, ra, filp, true, offset, req_size); + +#ifdef CONFIG_BLOCK + /* + * Normally the current page is !uptodate and lock_page() will be + * immediately called to implicitly unplug the device. However this + * is not always true for RAID conifgurations, where data arrives + * not strictly in their submission order. In this case we need to + * explicitly kick off the IO. + */ + if (PageUptodate(page)) + blk_run_backing_dev(mapping->backing_dev_info, NULL); +#endif } EXPORT_SYMBOL_GPL(page_cache_async_readahead); diff --git a/mm/slab.c b/mm/slab.c index 3f4822938f46..e17cc2c337b8 100644 --- a/mm/slab.c +++ b/mm/slab.c @@ -2275,9 +2275,11 @@ kmem_cache_create (const char *name, size_t size, size_t align, /* * Determine if the slab management is 'on' or 'off' slab. * (bootstrapping cannot cope with offslab caches so don't do - * it too early on.) + * it too early on. Always use on-slab management when + * SLAB_NOLEAKTRACE to avoid recursive calls into kmemleak) */ - if ((size >= (PAGE_SIZE >> 3)) && !slab_early_init) + if ((size >= (PAGE_SIZE >> 3)) && !slab_early_init && + !(flags & SLAB_NOLEAKTRACE)) /* * Size is large, assume best to place the slab management obj * off-slab (should allow better packing of objs). @@ -2596,8 +2598,8 @@ static struct slab *alloc_slabmgmt(struct kmem_cache *cachep, void *objp, * kmemleak does not treat the ->s_mem pointer as a reference * to the object. Otherwise we will not report the leak. */ - kmemleak_scan_area(slabp, offsetof(struct slab, list), - sizeof(struct list_head), local_flags); + kmemleak_scan_area(&slabp->list, sizeof(struct list_head), + local_flags); if (!slabp) return NULL; } else { |