diff options
Diffstat (limited to 'arch/arm')
-rw-r--r-- | arch/arm/include/asm/fiq.h | 5 | ||||
-rw-r--r-- | arch/arm/kernel/fiq.c | 79 | ||||
-rw-r--r-- | arch/arm/mach-s3c2410/Kconfig | 19 | ||||
-rw-r--r-- | arch/arm/mach-s3c2410/Makefile | 1 | ||||
-rw-r--r-- | arch/arm/mach-s3c2410/include/mach/gta01.h | 67 | ||||
-rw-r--r-- | arch/arm/mach-s3c2410/include/mach/irqs.h | 22 | ||||
-rw-r--r-- | arch/arm/mach-s3c2410/mach-gta01.c | 997 | ||||
-rw-r--r-- | arch/arm/plat-s3c/Kconfig | 5 | ||||
-rw-r--r-- | arch/arm/plat-s3c/Makefile | 1 | ||||
-rw-r--r-- | arch/arm/plat-s3c/include/plat/nand.h | 1 | ||||
-rw-r--r-- | arch/arm/plat-s3c/include/plat/pwm.h | 45 | ||||
-rw-r--r-- | arch/arm/plat-s3c/pwm.c | 288 | ||||
-rw-r--r-- | arch/arm/plat-s3c24xx/cpu.c | 10 | ||||
-rw-r--r-- | arch/arm/plat-s3c24xx/include/plat/irq.h | 21 | ||||
-rw-r--r-- | arch/arm/plat-s3c24xx/irq.c | 50 | ||||
-rw-r--r-- | arch/arm/plat-s3c24xx/pwm-clock.c | 437 |
16 files changed, 2034 insertions, 14 deletions
diff --git a/arch/arm/include/asm/fiq.h b/arch/arm/include/asm/fiq.h index 2242ce22ec6..7ade2b8445d 100644 --- a/arch/arm/include/asm/fiq.h +++ b/arch/arm/include/asm/fiq.h @@ -29,8 +29,9 @@ struct fiq_handler { extern int claim_fiq(struct fiq_handler *f); extern void release_fiq(struct fiq_handler *f); extern void set_fiq_handler(void *start, unsigned int length); -extern void set_fiq_regs(struct pt_regs *regs); -extern void get_fiq_regs(struct pt_regs *regs); +extern void set_fiq_c_handler(void (*handler)(void)); +extern void __attribute__((naked)) set_fiq_regs(struct pt_regs *regs); +extern void __attribute__((naked)) get_fiq_regs(struct pt_regs *regs); extern void enable_fiq(int fiq); extern void disable_fiq(int fiq); diff --git a/arch/arm/kernel/fiq.c b/arch/arm/kernel/fiq.c index 6ff7919613d..c07691ef4c0 100644 --- a/arch/arm/kernel/fiq.c +++ b/arch/arm/kernel/fiq.c @@ -8,6 +8,8 @@ * * FIQ support re-written by Russell King to be more generic * + * FIQ handler in C supoprt written by Andy Green <andy@openmoko.com> + * * We now properly support a method by which the FIQ handlers can * be stacked onto the vector. We still do not support sharing * the FIQ vector itself. @@ -124,6 +126,83 @@ void __naked get_fiq_regs(struct pt_regs *regs) : "r" (®s->ARM_r8), "I" (PSR_I_BIT | PSR_F_BIT | FIQ_MODE)); } +/* -------- FIQ handler in C --------- + * + * Major Caveats for using this + * --------------------------- + * * + * * 1) it CANNOT touch any vmalloc()'d memory, only memory + * that was kmalloc()'d. Static allocations in the monolithic kernel + * are kmalloc()'d so they are okay. You can touch memory-mapped IO, but + * the pointer for it has to have been stored in kmalloc'd memory. The + * reason for this is simple: every now and then Linux turns off interrupts + * and reorders the paging tables. If a FIQ happens during this time, the + * virtual memory space can be partly or entirely disordered or missing. + * + * 2) Because vmalloc() is used when a module is inserted, THIS FIQ + * ISR HAS TO BE IN THE MONOLITHIC KERNEL, not a module. But the way + * it is set up, you can all to enable and disable it from your module + * and intercommunicate with it through struct fiq_ipc + * fiq_ipc which you can define in + * asm/archfiq_ipc_type.h. The reason is the same as above, a + * FIQ could happen while even the ISR is not present in virtual memory + * space due to pagetables being changed at the time. + * + * 3) You can't call any Linux API code except simple macros + * - understand that FIQ can come in at any time, no matter what + * state of undress the kernel may privately be in, thinking it + * locked the door by turning off interrupts... FIQ is an + * unstoppable monster force (which is its value) + * - they are not vmalloc()'d memory safe + * - they might do crazy stuff like sleep: FIQ pisses fire and + * is not interested in 'sleep' that the weak seem to need + * - calling APIs from FIQ can re-enter un-renterable things + * - summary: you cannot interoperate with linux APIs directly in the FIQ ISR + * + * If you follow these rules, it is fantastic, an extremely powerful, solid, + * genuine hard realtime feature. + */ + +static void (*current_fiq_c_isr)(void); +#define FIQ_C_ISR_STACK_SIZE 256 + +static void __attribute__((naked)) __jump_to_isr(void) +{ + asm __volatile__ ("mov pc, r8"); +} + + +static void __attribute__((naked)) __actual_isr(void) +{ + asm __volatile__ ( + "stmdb sp!, {r0-r12, lr};" + "mov fp, sp;" + ); + + current_fiq_c_isr(); + + asm __volatile__ ( + "ldmia sp!, {r0-r12, lr};" + "subs pc, lr, #4;" + ); +} + +void set_fiq_c_handler(void (*isr)(void)) +{ + struct pt_regs regs; + + memset(®s, 0, sizeof(regs)); + regs.ARM_r8 = (unsigned long) __actual_isr; + regs.ARM_sp = 0xffff001c + FIQ_C_ISR_STACK_SIZE; + + set_fiq_handler(__jump_to_isr, 4); + + current_fiq_c_isr = isr; + + set_fiq_regs(®s); +} +/* -------- FIQ handler in C ---------*/ + int claim_fiq(struct fiq_handler *f) { int ret = 0; diff --git a/arch/arm/mach-s3c2410/Kconfig b/arch/arm/mach-s3c2410/Kconfig index 41bb65d5b91..d336ac42289 100644 --- a/arch/arm/mach-s3c2410/Kconfig +++ b/arch/arm/mach-s3c2410/Kconfig @@ -139,4 +139,23 @@ config MACH_QT2410 help Say Y here if you are using the Armzone QT2410 +config MACH_NEO1973_GTA01 + bool "FIC Neo1973 GSM Phone (GTA01 Hardware)" + select CPU_S3C2410 + select MACH_NEO1973 + select S3C_DEV_USB_HOST + select MFD_PCF50606 + select INPUT_PCF50606_PMU + select PCF50606_ADC + select PCF50606_GPIO + select RTC_DRV_PCF50606 + select REGULATOR_PCF50606 + select CHARGER_PCF50606 + select PCF50606_WATCHDOG + select POWER_SUPPLY + select BATTERY_GTA01 + select S3C24XX_ADC + help + Say Y here if you are using the FIC Neo1973 GSM Phone + endmenu diff --git a/arch/arm/mach-s3c2410/Makefile b/arch/arm/mach-s3c2410/Makefile index fca02f82711..ed3939d2cf7 100644 --- a/arch/arm/mach-s3c2410/Makefile +++ b/arch/arm/mach-s3c2410/Makefile @@ -37,3 +37,4 @@ obj-$(CONFIG_SIMTEC_NOR) += nor-simtec.o # machine additions obj-$(CONFIG_MACH_BAST_IDE) += bast-ide.o +obj-$(CONFIG_MACH_NEO1973_GTA01) += mach-gta01.o diff --git a/arch/arm/mach-s3c2410/include/mach/gta01.h b/arch/arm/mach-s3c2410/include/mach/gta01.h new file mode 100644 index 00000000000..673a1161105 --- /dev/null +++ b/arch/arm/mach-s3c2410/include/mach/gta01.h @@ -0,0 +1,67 @@ +#ifndef _GTA01_H +#define _GTA01_H + +#include <mach/regs-gpio.h> +#include <mach/irqs.h> + +/* Different hardware revisions, passed in ATAG_REVISION by u-boot */ +#define GTA01v3_SYSTEM_REV 0x00000130 +#define GTA01v4_SYSTEM_REV 0x00000140 +#define GTA01Bv2_SYSTEM_REV 0x00000220 +#define GTA01Bv3_SYSTEM_REV 0x00000230 +#define GTA01Bv4_SYSTEM_REV 0x00000240 + +/* !!!!!!!!!!! */ +#define S3C_SYSTEM_REV_ATAG GTA01Bv2_SYSTEM_REV + +/* Backlight */ + +/* Definitions common to all revisions */ +#define GTA01_GPIO_BACKLIGHT S3C2410_GPB(0) +#define GTA01_GPIO_GPS_PWRON S3C2410_GPB(1) +#define GTA01_GPIO_MODEM_RST S3C2410_GPB(6) +#define GTA01_GPIO_MODEM_ON S3C2410_GPB(7) +#define GTA01_GPIO_LCD_RESET S3C2410_GPC(6) +#define GTA01_GPIO_PMU_IRQ S3C2410_GPG(8) +#define GTA01_GPIO_JACK_INSERT S3C2410_GPF(4) +#define GTA01_GPIO_nSD_DETECT S3C2410_GPF(5) +#define GTA01_GPIO_AUX_KEY S3C2410_GPF(6) +#define GTA01_GPIO_HOLD_KEY S3C2410_GPF(7) + +#define GTA01_IRQ_MODEM IRQ_EINT1 +#define GTA01_IRQ_JACK_INSERT IRQ_EINT4 +#define GTA01_IRQ_nSD_DETECT IRQ_EINT5 +#define GTA01_IRQ_AUX_KEY IRQ_EINT6 + +/* GTA01v3 */ +#define GTA01v3_GPIO_nGSM_EN S3C2410_GPG(9) + +/* GTA01v4 */ +#define GTA01_GPIO_MODEM_DNLOAD S3C2410_GPG(0) + +/* GTA01Bv2 */ +#define GTA01Bv2_GPIO_nGSM_EN S3C2410_GPF(2) +#define GTA01Bv2_GPIO_VIBRATOR_ON S3C2410_GPB(10) +#define GTA01Bv2_IRQ_PCF50606 IRQ_EINT16 + +/* GTA01Bv3 */ +#define GTA01_GPIO_GPS_EN_3V3 S3C2410_GPG(9) + +#define GTA01_GPIO_SDMMC_ON S3C2410_GPB(2) +#define GTA01_GPIO_BT_EN S3C2410_GPB(5) +#define GTA01_GPIO_USB_PULLUP S3C2410_GPB(9) + +#define GTA01_GPIO_GPS_EN_2V8 S3C2410_GPG(9) +#define GTA01_GPIO_GPS_EN_3V S3C2410_GPG(10) +#define GTA01_GPIO_GPS_RESET S3C2410_GPC(0) + +/* GTA01Bv4 */ +#define GTA01Bv4_GPIO_nNAND_WP S3C2410_GPA(16) +#define GTA01Bv4_GPIO_VIBRATOR_ON S3C2410_GPB(3) +#define GTA01Bv4_GPIO_PMU_IRQ S3C2410_GPG(1) + +#define GTA01Bv4_IRQ_PCF50606 IRQ_EINT9 + +extern struct pcf50606 *gta01_pcf; + +#endif /* _GTA01_H */ diff --git a/arch/arm/mach-s3c2410/include/mach/irqs.h b/arch/arm/mach-s3c2410/include/mach/irqs.h index 2a2384ffa7b..b6d2c2adbcf 100644 --- a/arch/arm/mach-s3c2410/include/mach/irqs.h +++ b/arch/arm/mach-s3c2410/include/mach/irqs.h @@ -153,9 +153,9 @@ #define IRQ_S3C2443_AC97 S3C2410_IRQSUB(28) #ifdef CONFIG_CPU_S3C2443 -#define NR_IRQS (IRQ_S3C2443_AC97+1) +#define S3C2410_NR_INTERNAL_IRQS (IRQ_S3C2443_AC97+1) #else -#define NR_IRQS (IRQ_S3C2440_AC97+1) +#define S3C2410_NR_INTERNAL_IRQS (IRQ_S3C2440_AC97+1) #endif /* compatibility define. */ @@ -167,4 +167,22 @@ /* Our FIQs are routable from IRQ_EINT0 to IRQ_ADCPARENT */ #define FIQ_START IRQ_EINT0 +/* Board specific IRQs + * If your board needs a extra set of IRQ numbers add it to the list here. + * Make sure that the numbers are kept in descending, so if multiple boards are + * selected the maximum will be used and there are enough IRQ numbers available + * for each board. + */ + +#if defined(CONFIG_MACH_NEO1973_GTA02) +#define S3C2410_NR_BOARD_IRQS 9 +#else +#define S3C2410_NR_BOARD_IRQS 0 +#endif + +#define S3C2410_BOARD_IRQ_START S3C2410_NR_INTERNAL_IRQS +#define S3C2410_BOARD_IRQ_END (S3C2410_BOARD_IRQ_START + S3C2410_NR_BOARD_IRQS) + +#define NR_IRQS S3C2410_BOARD_IRQ_END + #endif /* __ASM_ARCH_IRQ_H */ diff --git a/arch/arm/mach-s3c2410/mach-gta01.c b/arch/arm/mach-s3c2410/mach-gta01.c new file mode 100644 index 00000000000..1602f06d05c --- /dev/null +++ b/arch/arm/mach-s3c2410/mach-gta01.c @@ -0,0 +1,997 @@ +/* + * linux/arch/arm/mach-s3c2410/mach-gta01.c + * + * S3C2410 Machine Support for the FIC Neo1973 GTA01 + * + * Copyright (C) 2006-2007 by Openmoko, Inc. + * Author: Harald Welte <laforge@openmoko.org> + * Copyright (C) 2009, Lars-Peter Clausen <lars@metafoo.de> + * All rights reserved. + * + * 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/types.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/workqueue.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <linux/serial_core.h> +#include <linux/mmc/mmc.h> +#include <linux/mmc/host.h> +#include <linux/gta01_battery.h> + +#include <linux/mtd/mtd.h> +#include <linux/mtd/nand.h> +#include <linux/mtd/nand_ecc.h> +#include <linux/mtd/partitions.h> + +#include <linux/mmc/host.h> + +#include <linux/mfd/pcf50606/core.h> +#include <linux/mfd/pcf50606/pmic.h> +#include <linux/mfd/pcf50606/mbc.h> +#include <linux/mfd/pcf50606/adc.h> + +/*#include <linux/gta01_battery.h>*/ + +#include <linux/regulator/machine.h> +#include <linux/regulator/consumer.h> + + +#include <asm/mach/arch.h> +#include <asm/mach/map.h> +#include <asm/mach/irq.h> + +#include <mach/hardware.h> +#include <mach/io.h> +#include <asm/mach-types.h> + +#include <mach/regs-gpio.h> +#include <mach/gpio-fns.h> +#include <mach/fb.h> + +#include <linux/spi/spi.h> +#include <linux/spi/spi_gpio.h> + +#include <mach/gta01.h> + +#include <plat/regs-serial.h> +#include <plat/nand.h> +#include <plat/devs.h> +#include <plat/cpu.h> +#include <plat/pm.h> +#include <plat/udc.h> +#include <plat/iic.h> +#include <plat/mci.h> +#include <plat/usb-control.h> +/*#include <mach/neo1973-pm-gsm.h>*/ + +#include <linux/jbt6k74.h> + +#include <linux/input.h> +#include <linux/gpio_keys.h> + +#include <linux/pwm_backlight.h> +#include <linux/leds_pwm.h> + +#include <linux/gpio.h> + +/* +#include <../drivers/input/touchscreen/ts_filter_chain.h> +#ifdef CONFIG_TOUCHSCREEN_FILTER +#include <../drivers/input/touchscreen/ts_filter_linear.h> +#include <../drivers/input/touchscreen/ts_filter_mean.h> +#include <../drivers/input/touchscreen/ts_filter_median.h> +#include <../drivers/input/touchscreen/ts_filter_group.h> +#endif +*/ + +static struct map_desc gta01_iodesc[] __initdata = { + { + .virtual = 0xe0000000, + .pfn = __phys_to_pfn(S3C2410_CS3+0x01000000), + .length = SZ_1M, + .type = MT_DEVICE + }, +}; + +#define UCON S3C2410_UCON_DEFAULT +#define ULCON S3C2410_LCON_CS8 | S3C2410_LCON_PNONE | S3C2410_LCON_STOPB +#define UFCON S3C2410_UFCON_RXTRIG8 | S3C2410_UFCON_FIFOMODE +/* UFCON for the gta01 sets the FIFO trigger level at 4, not 8 */ +#define UFCON_GTA01_PORT0 S3C2410_UFCON_FIFOMODE + +static struct s3c2410_uartcfg gta01_uartcfgs[] = { + [0] = { + .hwport = 0, + .flags = 0, + .ucon = UCON, + .ulcon = ULCON, + .ufcon = UFCON_GTA01_PORT0, + }, + [1] = { + .hwport = 1, + .flags = 0, + .ucon = UCON, + .ulcon = ULCON, + .ufcon = UFCON, + }, +}; + +/* TODO */ +static void gta01_pmu_event_callback(struct pcf50606 *pcf, int irq) +{ + /*TODO : Handle ACD here */ +} +#if 0 +/* FIXME : Goes away when ACD is handled above */ +static int pmu_callback(struct device *dev, unsigned int feature, + enum pmu_event event) +{ + switch (feature) { + case PCF50606_FEAT_ACD: + switch (event) { + case PMU_EVT_INSERT: + pcf50606_charge_fast(pcf50606_global, 1); + break; + case PMU_EVT_REMOVE: + pcf50606_charge_fast(pcf50606_global, 0); + break; + default: + break; + } + break; + default: + break; + } + + return 0; +} +#endif +struct pcf50606 *gta01_pcf; + +static struct platform_device gta01_pm_gsm_dev = { + .name = "neo1973-pm-gsm", +}; + +static struct platform_device gta01_pm_bt_dev = { + .name = "neo1973-pm-bt", +}; +static struct platform_device gta01_pm_gps_dev = { + .name = "neo1973-pm-gps", +}; + +static struct regulator_consumer_supply ioreg_consumers[] = { + { + .dev = >a01_pm_gps_dev.dev, + .supply = "GPS_2V8", + }, +}; + +static struct regulator_consumer_supply d1reg_consumers[] = { + { + .dev = >a01_pm_gps_dev.dev, + .supply = "GPS_3V", + }, + { + .dev = >a01_pm_bt_dev.dev, + .supply = "BT_3V1", + }, +}; + +static struct regulator_consumer_supply dcd_consumers[] = { + { + .dev = >a01_pm_gps_dev.dev, + .supply = "GPS_3V3", + }, + { + .dev = >a01_pm_gps_dev.dev, + .supply = "GPS_1V5", + }, +}; + +static struct regulator_consumer_supply d2reg_consumers[] = { + { + .dev = >a01_pm_gps_dev.dev, + .supply = "GPS_2V5", + }, + { + .dev = &s3c_device_sdi.dev, + .supply = "SD_3V3", + }, +}; + +#if 0 +/* This will come with 2.6.32. Don't forget to uncomment it then. */ +static struct regulator_consumer_supply lpreg_consumers[] = { + REGULATOR_SUPPLY("VDC", "jbt6k74"), + REGULATOR_SUPPLY("VDDIO", "jbt6k74"), +}; +#else +static struct regulator_consumer_supply lpreg_consumers[] = { + { .supply = "VDC", }, + { .supply = "VDDIO", }, +}; +#endif + +static int gta01_bat_get_charging_status(void) +{ + struct pcf50606 *pcf = gta01_pcf; + u8 mbcc1, chgmod; + + mbcc1 = pcf50606_reg_read(pcf, PCF50606_REG_MBCC1); + chgmod = mbcc1 & PCF50606_MBCC1_CHGMOD_MASK; + + if (chgmod == PCF50606_MBCC1_CHGMOD_IDLE) + return 0; + else + return 1; +} + +static int gta01_bat_get_voltage(void) +{ + struct pcf50606 *pcf = gta01_pcf; + u16 adc, mv = 0; + + adc = pcf50606_adc_sync_read(pcf, PCF50606_ADCMUX_BATVOLT_RES); + mv = (adc * 6000) / 1024; + + return mv * 1000; +} + +static int gta01_bat_get_current(void) +{ + struct pcf50606 *pcf = gta01_pcf; + u16 adc_battvolt, adc_adcin1; + s32 res; + + adc_battvolt = pcf50606_adc_sync_read(pcf, PCF50606_ADCMUX_BATVOLT_SUBTR); + adc_adcin1 = pcf50606_adc_sync_read(pcf, PCF50606_ADCMUX_ADCIN1_SUBTR); + res = (adc_battvolt - adc_adcin1) * 2400; + + /*rsense is 220 milli */ + return (res * 1000) / (220 * 1024) * 1000; +} + +static struct gta01_bat_platform_data gta01_bat_pdata = { + .get_charging_status = gta01_bat_get_charging_status, + .get_voltage = gta01_bat_get_voltage, + .get_current = gta01_bat_get_current, +}; + +static struct platform_device gta01_bat = { + .name = "gta01_battery", + .id = -1, + .dev = { + .platform_data = >a01_bat_pdata, + } +}; + +static void gta01_pcf_probe_done(struct pcf50606 *pcf) +{ + gta01_pcf = pcf; + gta01_bat.dev.parent = pcf->dev; + platform_device_register(>a01_bat); +} + +static int gps_registered_regulators = 0; + +static void gta01_pmu_regulator_registered(struct pcf50606 *pcf, int id) +{ + switch(id) { + case PCF50606_REGULATOR_D1REG: + platform_device_register(>a01_pm_bt_dev); + gps_registered_regulators++; + break; + case PCF50606_REGULATOR_D2REG: + gps_registered_regulators++; + break; + case PCF50606_REGULATOR_IOREG: + case PCF50606_REGULATOR_DCD: + gps_registered_regulators++; + break; + } + + /* All GPS related regulators registered ? */ +/* if (gps_registered_regulators == 4) + platform_device_register(>a01_pm_gps_dev);*/ +} + +static struct pcf50606_platform_data gta01_pcf_pdata = { + .resumers = { + [0] = PCF50606_INT1_ALARM | + PCF50606_INT1_ONKEYF | + PCF50606_INT1_EXTONR, + [1] = PCF50606_INT2_CHGWD10S | + PCF50606_INT2_CHGPROT | + PCF50606_INT2_CHGERR, + [2] = PCF50606_INT3_LOWBAT | + PCF50606_INT3_HIGHTMP | + PCF50606_INT3_ACDINS, + }, + .mbc_event_callback = gta01_pmu_event_callback, + .reg_init_data = { + /* BT, GPS */ + [PCF50606_REGULATOR_D1REG] = { + .constraints = { + .min_uV = 3000000, + .max_uV = 3150000, + .valid_modes_mask = REGULATOR_MODE_NORMAL, + .apply_uV = 1, + }, + .num_consumer_supplies = ARRAY_SIZE(d1reg_consumers), + .consumer_supplies = d1reg_consumers, + }, + /* GPS */ + [PCF50606_REGULATOR_D2REG] = { + .constraints = { + .min_uV = 1650000, + .max_uV = 3300000, + .valid_modes_mask = REGULATOR_MODE_NORMAL, + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .apply_uV = 1, + }, + .num_consumer_supplies = ARRAY_SIZE(d2reg_consumers), + .consumer_supplies = d2reg_consumers, + + }, + /* RTC/Standby */ + [PCF50606_REGULATOR_D3REG] = { + .constraints = { + .min_uV = 1800000, + .max_uV = 2100000, + .valid_modes_mask = REGULATOR_MODE_NORMAL, + .always_on = 1, + .state_mem = { + .enabled = 1, + }, + }, + .num_consumer_supplies = 0, + }, + /* GPS */ + [PCF50606_REGULATOR_DCD] = { + .constraints = { + .min_uV = 1500000, + .max_uV = 1500000, + .valid_modes_mask = REGULATOR_MODE_NORMAL, + .apply_uV = 1, + }, + .num_consumer_supplies = ARRAY_SIZE(dcd_consumers), + .consumer_supplies = dcd_consumers, + }, + + /* S3C2410 Memory and IO, Vibrator, RAM, NAND, AMP, SD Card */ + [PCF50606_REGULATOR_DCDE] = { + .constraints = { + .min_uV = 3300000, + .max_uV = 3300000, + .valid_modes_mask = REGULATOR_MODE_NORMAL, + .apply_uV = 1, + .always_on = 1, + .state_mem = { + .enabled = 1, + }, + }, + .num_consumer_supplies = 0, + }, + /* SoC */ + [PCF50606_REGULATOR_DCUD] = { + .constraints = { + .min_uV = 2100000, + .max_uV = 2100000, + .valid_modes_mask = REGULATOR_MODE_NORMAL, + .apply_uV = 1, + .always_on = 1, + .state_mem = { + .enabled = 1, + }, + }, + .num_consumer_supplies = 0, + }, + /* Codec, GPS */ + [PCF50606_REGULATOR_IOREG] = { + .constraints = { + .min_uV = 3300000, + .max_uV = 3300000, + .valid_modes_mask = REGULATOR_MODE_NORMAL, + .apply_uV = 1, + .always_on = 1, + }, + .num_consumer_supplies = ARRAY_SIZE(ioreg_consumers), + .consumer_supplies = ioreg_consumers, + + }, + /* LCM */ + [PCF50606_REGULATOR_LPREG] = { + .constraints = { + .min_uV = 3300000, + .max_uV = 3300000, + .valid_modes_mask = REGULATOR_MODE_NORMAL, + .apply_uV = 1, + }, + .num_consumer_supplies = ARRAY_SIZE(lpreg_consumers), + .consumer_supplies = lpreg_consumers, + }, + }, + .probe_done = gta01_pcf_probe_done, + .regulator_registered = gta01_pmu_regulator_registered, +}; + +static void cfg_pmu_vrail(struct regulator_init_data *vrail, + unsigned int suspend_on, unsigned int min, + unsigned int max) +{ + vrail->constraints.state_mem.enabled = suspend_on; + vrail->constraints.min_uV = min; + vrail->constraints.max_uV = max; + vrail->constraints.apply_uV = 1; +} + +static void mangle_pmu_pdata_by_system_rev(void) +{ + struct regulator_init_data *reg_init_data; + + reg_init_data = gta01_pcf_pdata.reg_init_data; + + switch (S3C_SYSTEM_REV_ATAG) { + case GTA01Bv4_SYSTEM_REV: + + /* FIXME : gta01_pcf_pdata.used_features |= PCF50606_FEAT_ACD; */ + break; + case GTA01Bv3_SYSTEM_REV: + case GTA01Bv2_SYSTEM_REV: + reg_init_data[PCF50606_REGULATOR_D3REG].constraints.state_mem.enabled = 1; + break; + case GTA01v4_SYSTEM_REV: + cfg_pmu_vrail(®_init_data[PCF50606_REGULATOR_DCUD], + 1, 18000000, 1800000); + cfg_pmu_vrail(®_init_data[PCF50606_REGULATOR_D1REG], + 0, 3000000, 3000000); + cfg_pmu_vrail(®_init_data[PCF50606_REGULATOR_D3REG], + 0, 2800000, 2800000); + cfg_pmu_vrail(®_init_data[PCF50606_REGULATOR_DCD], + 0, 3500000, 3500000); + break; + case GTA01v3_SYSTEM_REV: + cfg_pmu_vrail(®_init_data[PCF50606_REGULATOR_D1REG], + 0, 3000000, 3000000); + cfg_pmu_vrail(®_init_data[PCF50606_REGULATOR_D2REG], + 0, 3300000, 3300000); + cfg_pmu_vrail(®_init_data[PCF50606_REGULATOR_D3REG], + 0, 3300000, 3300000); + cfg_pmu_vrail(®_init_data[PCF50606_REGULATOR_DCD], + 0, 3300000, 3300000); + cfg_pmu_vrail(®_init_data[PCF50606_REGULATOR_DCUD], + 1, 1800000, 1800000); + cfg_pmu_vrail(®_init_data[PCF50606_REGULATOR_IOREG], + 0, 2800000, 2800000); + break; + } +} + +static void gta01_power_off(void) +{/* + pcf50606_reg_write(gta01_pcf, PCF50606_REG_OOCC1, + PCF50606_OOCC1_GOSTDBY);*/ +} + +/* LCD driver info */ + +/* Configuration for 480x640 toppoly TD028TTEC1. + * Do not mark this as __initdata or it will break! */ +static struct s3c2410fb_display gta01_displays[] = { + { + .type = S3C2410_LCDCON1_TFT, + .width = 43, + .height = 58, + .xres = 480, + .yres = 640, + .bpp = 16, + + .pixclock = 40000, /* HCLK/4 */ + .left_margin = 104, + .right_margin = 8, + .hsync_len = 8, + .upper_margin = 2, + .lower_margin = 16, + .vsync_len = 2, + .lcdcon5 = S3C2410_LCDCON5_FRM565 | + S3C2410_LCDCON5_INVVCLK | + S3C2410_LCDCON5_INVVLINE | + S3C2410_LCDCON5_INVVFRAME | + S3C2410_LCDCON5_PWREN | + S3C2410_LCDCON5_HWSWP, + }, + { + .type = S3C2410_LCDCON1_TFT, + .width = 43, + .height = 58, + .xres = 480, + .yres = 640, + .bpp = 32, + + .pixclock = 40000, /* HCLK/4 */ + .left_margin = 104, + .right_margin = 8, + .hsync_len = 8, + .upper_margin = 2, + .lower_margin = 16, + .vsync_len = 2, + .lcdcon5 = S3C2410_LCDCON5_FRM565 | + S3C2410_LCDCON5_INVVCLK | + S3C2410_LCDCON5_INVVLINE | + S3C2410_LCDCON5_INVVFRAME | + S3C2410_LCDCON5_PWREN | + S3C2410_LCDCON5_HWSWP, + }, + { + .type = S3C2410_LCDCON1_TFT, + .width = 43, + .height = 58, + .xres = 240, + .yres = 320, + .bpp = 16, + + .pixclock = 40000, /* HCLK/4 */ + .left_margin = 104, + .right_margin = 8, + .hsync_len = 8, + .upper_margin = 2, + .lower_margin = 16, + .vsync_len = 2, + .lcdcon5 = S3C2410_LCDCON5_FRM565 | + S3C2410_LCDCON5_INVVCLK | + S3C2410_LCDCON5_INVVLINE | + S3C2410_LCDCON5_INVVFRAME | + S3C2410_LCDCON5_PWREN | + S3C2410_LCDCON5_HWSWP, + }, +}; + +static struct s3c2410fb_mach_info gta01_lcd_cfg __initdata = { + .displays = gta01_displays, + .num_displays = ARRAY_SIZE(gta01_displays), + .default_display = 0, + + .lpcsel = ((0xCE6) & ~7) | 1<<4, +}; + +static struct s3c2410_nand_set gta01_nand_sets[] = { + [0] = { + .name = "neo1973-nand", + .nr_chips = 1, +/* .flags = S3C2410_NAND_BBT,*/ + }, +}; + +static struct s3c2410_platform_nand gta01_nand_info = { + .tacls = 20, + .twrph0 = 60, + .twrph1 = 20, + .nr_sets = ARRAY_SIZE(gta01_nand_sets), + .sets = gta01_nand_sets, +}; + +/* MMC */ + +static void gta01_mmc_set_power(unsigned char power_mode, unsigned short vdd) +{ + printk(KERN_DEBUG "mmc_set_power(power_mode=%u, vdd=%u)\n", + power_mode, vdd); + + switch (power_mode) { + case MMC_POWER_OFF: + gpio_set_value(GTA01_GPIO_SDMMC_ON, 1); + break; + case MMC_POWER_UP: + gpio_set_value(GTA01_GPIO_SDMMC_ON, 0); + break; + } +} + +static struct s3c24xx_mci_pdata gta01_mmc_cfg = { + .gpio_detect = GTA01_GPIO_nSD_DETECT, + .set_power = >a01_mmc_set_power, + .ocr_avail = MMC_VDD_32_33, +}; + +/* UDC */ + +static void gta01_udc_command(enum s3c2410_udc_cmd_e cmd) +{ + printk("udc command: %d\n", cmd); + switch (cmd) { + case S3C2410_UDC_P_ENABLE: + gpio_set_value(GTA01_GPIO_USB_PULLUP, 1); + break; + case S3C2410_UDC_P_DISABLE: + gpio_set_value(GTA01_GPIO_USB_PULLUP, 0); + break; + default: + break; + } +} + +/* use a work queue, since I2C API inherently schedules + * and we get called in hardirq context from UDC driver */ + +struct vbus_draw { + struct work_struct work; + int ma; +}; +static struct vbus_draw gta01_udc_vbus_drawer; + +static void __gta01_udc_vbus_draw(struct work_struct *work) +{ + /* + * This is a fix to work around boot-time ordering problems if the + * s3c2410_udc is initialized before the pcf50606 driver has defined + * pcf50606_global + */ + if (!gta01_pcf) + return; +#if 0 + if (gta01_udc_vbus_drawer.ma >= 500) { + /* enable fast charge */ + printk(KERN_DEBUG "udc: enabling fast charge\n"); + pcf50606_charge_fast(gta01_pcf, 1); + } else { + /* disable fast charge */ + printk(KERN_DEBUG "udc: disabling fast charge\n"); + pcf50606_charge_fast(gta01_pcf, 0); + } +#endif +} + +static void gta01_udc_vbus_draw(unsigned int ma) +{ + gta01_udc_vbus_drawer.ma = ma; + schedule_work(>a01_udc_vbus_drawer.work); +} + +static struct s3c2410_udc_mach_info gta01_udc_cfg = { + .vbus_draw = gta01_udc_vbus_draw, + .udc_command = gta01_udc_command, +}; + +/* Touchscreen configuration. */ + +#if 0 + +#ifdef CONFIG_TOUCHSCREEN_FILTER +const static struct ts_filter_group_configuration gta01_ts_group = { + .length = 12, + .close_enough = 10, + .threshold = 6, /* At least half of the points in a group. */ + .attempts = 10, +}; + +const static struct ts_filter_median_configuration gta01_ts_median = { + .extent = 20, + .decimation_below = 3, + .decimation_threshold = 8 * 3, + .decimation_above = 4, +}; + +const static struct ts_filter_mean_configuration gta01_ts_mean = { + .length = 4, +}; + +const static struct ts_filter_linear_configuration gta01_ts_linear = { + .constants = {1, 0, 0, 0, 1, 0, 1}, /* Don't modify coords. */ + .coord0 = 0, + .coord1 = 1, +}; +#endif + +const static struct ts_filter_chain_configuration gta01_filter_configuration[] = +{ +#ifdef CONFIG_TOUCHSCREEN_FILTER + {&ts_filter_group_api, >a01_ts_group.config}, + {&ts_filter_median_api, >a01_ts_median.config}, + {&ts_filter_mean_api, >a01_ts_mean.config}, + {&ts_filter_linear_api, >a01_ts_linear.config}, +#endif + {NULL, NULL}, +}; + +const static struct s3c2410_ts_mach_info gta01_ts_cfg = { + .delay = 10000, + .presc = 0xff, /* slow as we can go */ + .filter_config = gta01_filter_configuration, +}; +#endif + +/* SPI / Display */ + +const struct jbt6k74_platform_data gta01_jbt6k74_pdata = { + .gpio_reset = GTA01_GPIO_LCD_RESET, +}; + +static struct spi_board_info gta01_spi_board_info[] = { + { + .modalias = "jbt6k74", + .platform_data = >a01_jbt6k74_pdata, + .controller_data = (void*)S3C2410_GPG(3), + .max_speed_hz = 10 * 1000 * 1000, + .bus_num = 1, + }, +}; + +static struct spi_gpio_platform_data spigpio_platform_data = { + .sck = S3C2410_GPG(7), + .mosi = S3C2410_GPG(6), + .miso = S3C2410_GPG(5), + .num_chipselect = 1, +}; + +static struct platform_device gta01_lcm_spigpio_device = { + .name = "spi_gpio", + .id = 1, + .dev = { + .platform_data = &spigpio_platform_data, + }, +}; + +/* Backlight */ + +static struct platform_pwm_backlight_data gta01_bl_pdata = { + .max_brightness = 0xff, + .dft_brightness = 0xff, + .pwm_period_ns = 5000000, /* TODO: Tweak this */ + .pwm_id = 0, +}; + +static struct platform_device gta01_bl_device = { + .name = "pwm-backlight", + .id = -1, + .dev = { + .platform_data = >a01_bl_pdata, + .parent = &s3c_device_timer[0].dev, + }, +}; + +/* Vibrator */ +static struct led_pwm gta01_vibrator = { + .name = "gta01::vibrator", + .max_brightness = 0xff, + .pwm_period_ns = 500000, /* TODO: Tweak this */ + .pwm_id = 3, +}; + +static struct led_pwm_platform_data gta01_vibrator_pdata = { + .num_leds = 1, + .leds = >a01_vibrator, +}; + +static struct platform_device gta01_vibrator_device_bv4 = { + .name = "leds_pwm", + .id = -1, + .dev = { + .platform_data = >a01_vibrator_pdata, + .parent = &s3c_device_timer[3].dev, + } +}; + +static struct gpio_led gta01_vibrator_bv2 = { + .name = "gta01::vibrator", + .gpio = GTA01Bv2_GPIO_VIBRATOR_ON, + .default_state = LEDS_GPIO_DEFSTATE_OFF, +}; + +static struct gpio_led_platform_data gta01_vibrator_pdata_bv2 = { + .num_leds = 1, + .leds = >a01_vibrator_bv2, +}; + +static struct platform_device gta01_vibrator_device_bv2 = { + .name = "leds-gpio", + .id = -1, + .dev = { + .platform_data = >a01_vibrator_pdata_bv2, + } +}; + +static struct platform_device *gta01_vibrator_device = >a01_vibrator_device_bv4; +/* Buttons */ + +static struct gpio_keys_button gta01_buttons[] = { + { + .gpio = GTA01_GPIO_AUX_KEY, + .code = KEY_PHONE, + .desc = "Aux", + .type = EV_KEY, + .debounce_interval = 100, + }, + { + .gpio = GTA01_GPIO_HOLD_KEY, + .code = KEY_PAUSE, + .desc = "Hold", + .type = EV_KEY, + .debounce_interval = 100, + }, +}; + +static struct gpio_keys_platform_data gta01_buttons_pdata = { + .buttons = gta01_buttons, + .nbuttons = ARRAY_SIZE(gta01_buttons), +}; + +static struct platform_device gta01_buttons_device = { + .name = "gpio-keys", + .id = -1, + .dev = { + .platform_data = >a01_buttons_pdata, + }, +}; + +/* USB */ + +static struct s3c2410_hcd_info gta01_usb_info = { + .port[0] = { + .flags = S3C_HCDFLG_USED, + }, + .port[1] = { + .flags = 0, + }, +}; + +static struct platform_device *gta01_devices[] __initdata = { + &s3c_device_usb, + &s3c_device_lcd, + &s3c_device_wdt, + &s3c_device_i2c0, + &s3c_device_iis, + &s3c_device_sdi, + &s3c_device_usbgadget, + &s3c_device_nand, + &s3c_device_adc, +/* &s3c_device_ts,*/ + &s3c_device_timer[0], + &s3c_device_timer[3], + >a01_bl_device, + >a01_buttons_device, + >a01_pm_gsm_dev, +}; + +static struct platform_device *gta01_pmu_child_devices[] = { + >a01_lcm_spigpio_device, +}; + +static void __init gta01_map_io(void) +{ + s3c24xx_init_io(gta01_iodesc, ARRAY_SIZE(gta01_iodesc)); + s3c24xx_init_clocks(12*1000*1000); + s3c24xx_init_uarts(gta01_uartcfgs, ARRAY_SIZE(gta01_uartcfgs)); +} + +static irqreturn_t gta01_modem_irq(int irq, void *param) +{ + printk(KERN_DEBUG "GSM wakeup interrupt (IRQ %d)\n", irq); +/* gta_gsm_interrupts++;*/ + return IRQ_HANDLED; +} + +static struct i2c_board_info gta01_i2c_devs[] __initdata = { + { + I2C_BOARD_INFO("pcf50606", 0x08), + .irq = GTA01Bv4_IRQ_PCF50606, + .platform_data = >a01_pcf_pdata, + }, + { + I2C_BOARD_INFO("lm4857", 0x7c), + }, + { + I2C_BOARD_INFO("wm8753", 0x1a), + }, +}; + +static int __init gta01_init_gpio(void) +{ + int ret; + + ret = gpio_request(GTA01_GPIO_USB_PULLUP, "udc pullup"); + + ret = gpio_direction_output(GTA01_GPIO_USB_PULLUP, 0); + + ret = gpio_request(GTA01_GPIO_SDMMC_ON, "SD/MMC power"); + + ret = gpio_direction_output(GTA01_GPIO_SDMMC_ON, 1); + + s3c2410_gpio_cfgpin(GTA01_GPIO_BACKLIGHT, S3C2410_GPB0_TOUT0); + if (S3C_SYSTEM_REV_ATAG == GTA01Bv4_SYSTEM_REV) + s3c2410_gpio_cfgpin(GTA01Bv4_GPIO_VIBRATOR_ON, S3C2410_GPB3_TOUT3); + + return ret; +} + +static void __init gta01_machine_init(void) +{ + int rc; + + gta01_init_gpio(); + + if (S3C_SYSTEM_REV_ATAG != GTA01Bv4_SYSTEM_REV) { + gta01_vibrator_device = >a01_vibrator_device_bv2; + gta01_i2c_devs[0].irq = GTA01Bv2_IRQ_PCF50606; + } + + s3c_device_usb.dev.platform_data = >a01_usb_info; + s3c_device_nand.dev.platform_data = >a01_nand_info; + s3c_device_sdi.dev.platform_data = >a01_mmc_cfg; + + s3c24xx_fb_set_platdata(>a01_lcd_cfg); + + INIT_WORK(>a01_udc_vbus_drawer.work, __gta01_udc_vbus_draw); + s3c24xx_udc_set_platdata(>a01_udc_cfg); + s3c_i2c0_set_platdata(NULL); + +/* set_s3c2410ts_info(>a01_ts_cfg);*/ + +#if 0 + switch (S3C_SYSTEM_REV_ATAG) { + case GTA01v3_SYSTEM_REV: + case GTA01v4_SYSTEM_REV: + /* just use the default (GTA01_IRQ_PCF50606) */ + break; + case GTA01Bv2_SYSTEM_REV: + case GTA01Bv3_SYSTEM_REV: + /* just use the default (GTA01_IRQ_PCF50606) */ + gta01_led_resources[0].start = + gta01_led_resources[0].end = GTA01Bv2_GPIO_VIBRATOR_ON; + break; + case GTA01Bv4_SYSTEM_REV: + gta01_i2c_devs[0].irq = GTA01Bv4_IRQ_PCF50606; + gta01_led_resources[0].start = + gta01_led_resources[0].end = GTA01Bv4_GPIO_VIBRATOR_ON; + break; + } +#endif + mangle_pmu_pdata_by_system_rev(); + + i2c_register_board_info(0, gta01_i2c_devs, ARRAY_SIZE(gta01_i2c_devs)); + spi_register_board_info(gta01_spi_board_info, + ARRAY_SIZE(gta01_spi_board_info)); + + platform_add_devices(gta01_devices, ARRAY_SIZE(gta01_devices)); +/* platform_add_devices(gta01_vibrator_device, 1);*/ + + s3c_pm_init(); + + set_irq_type(GTA01_IRQ_MODEM, IRQ_TYPE_EDGE_RISING); + rc = request_irq(GTA01_IRQ_MODEM, gta01_modem_irq, IRQF_DISABLED, + "modem", NULL); + enable_irq_wake(GTA01_IRQ_MODEM); + + printk(KERN_DEBUG "Enabled GSM wakeup IRQ %d (rc=%d)\n", + GTA01_IRQ_MODEM, rc); + + pm_power_off = >a01_power_off; +} + +MACHINE_START(NEO1973_GTA01, "GTA01") + .phys_io = S3C2410_PA_UART, + .io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc, + .boot_params = S3C2410_SDRAM_PA + 0x100, + .map_io = gta01_map_io, + .init_irq = s3c24xx_init_irq, + .init_machine = gta01_machine_init, + .timer = &s3c24xx_timer, +MACHINE_END diff --git a/arch/arm/plat-s3c/Kconfig b/arch/arm/plat-s3c/Kconfig index 935c7558469..bae4b955edf 100644 --- a/arch/arm/plat-s3c/Kconfig +++ b/arch/arm/plat-s3c/Kconfig @@ -166,6 +166,11 @@ config S3C_DMA help Internal configuration for S3C DMA core +config S3C_PWM + bool + help + PWM timer code for the S3C2410, and similar processors + # device definitions to compile in config S3C_DEV_HSMMC diff --git a/arch/arm/plat-s3c/Makefile b/arch/arm/plat-s3c/Makefile index 0761766b183..4d8e4a35d34 100644 --- a/arch/arm/plat-s3c/Makefile +++ b/arch/arm/plat-s3c/Makefile @@ -38,3 +38,4 @@ obj-$(CONFIG_SND_S3C64XX_SOC_I2S) += dev-audio.o obj-$(CONFIG_S3C_DEV_FB) += dev-fb.o obj-$(CONFIG_S3C_DEV_USB_HOST) += dev-usb.o obj-$(CONFIG_S3C_DEV_USB_HSOTG) += dev-usb-hsotg.o +obj-$(CONFIG_S3C_PWM) += pwm.o diff --git a/arch/arm/plat-s3c/include/plat/nand.h b/arch/arm/plat-s3c/include/plat/nand.h index 18f958801e6..723ab032059 100644 --- a/arch/arm/plat-s3c/include/plat/nand.h +++ b/arch/arm/plat-s3c/include/plat/nand.h @@ -47,6 +47,7 @@ struct s3c2410_platform_nand { int twrph1; /* time for release CLE/ALE from nWE/nOE inactive */ unsigned int ignore_unset_ecc:1; + unsigned int software_ecc:1; /* force software ecc at runtime */ int nr_sets; struct s3c2410_nand_set *sets; diff --git a/arch/arm/plat-s3c/include/plat/pwm.h b/arch/arm/plat-s3c/include/plat/pwm.h new file mode 100644 index 00000000000..6a41b0ad840 --- /dev/null +++ b/arch/arm/plat-s3c/include/plat/pwm.h @@ -0,0 +1,45 @@ +#ifndef __S3C2410_PWM_H +#define __S3C2410_PWM_H + +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/clk.h> + +#include <mach/io.h> +#include <mach/hardware.h> +#include <asm/mach-types.h> +#include <plat/regs-timer.h> + +enum pwm_timer { + PWM0, + PWM1, + PWM2, + PWM3, + PWM4 +}; + +struct s3c2410_pwm { + enum pwm_timer timerid; + struct clk *pclk; + unsigned long pclk_rate; + unsigned long prescaler; + unsigned long divider; + unsigned long counter; + unsigned long comparer; +}; + +struct s3c24xx_pwm_platform_data{ + /* callback to attach platform children (to enforce suspend / resume + * ordering */ + void (*attach_child_devices)(struct device *parent_device); +}; + +int s3c2410_pwm_init(struct s3c2410_pwm *s3c2410_pwm); +int s3c2410_pwm_enable(struct s3c2410_pwm *s3c2410_pwm); +int s3c2410_pwm_disable(struct s3c2410_pwm *s3c2410_pwm); +int s3c2410_pwm_start(struct s3c2410_pwm *s3c2410_pwm); +int s3c2410_pwm_stop(struct s3c2410_pwm *s3c2410_pwm); +int s3c2410_pwm_duty_cycle(int reg_value, struct s3c2410_pwm *s3c2410_pwm); +int s3c2410_pwm_dumpregs(void); + +#endif /* __S3C2410_PWM_H */ diff --git a/arch/arm/plat-s3c/pwm.c b/arch/arm/plat-s3c/pwm.c new file mode 100644 index 00000000000..250bd2bc9b8 --- /dev/null +++ b/arch/arm/plat-s3c/pwm.c @@ -0,0 +1,288 @@ +/* + * arch/arm/plat-s3c/pwm.c + * + * Copyright (c) by Javi Roman <javiroman@kernel-labs.org> + * for the Openmoko Project. + * + * S3C2410A SoC PWM support + * + * 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. + * + * 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/clk.h> +#include <linux/device.h> +#include <mach/hardware.h> +#include <plat/regs-timer.h> +#include <plat/pwm.h> +#include <asm/io.h> + +#ifdef CONFIG_PM + static unsigned long standby_reg_tcon; + static unsigned long standby_reg_tcfg0; + static unsigned long standby_reg_tcfg1; +#endif + +int s3c2410_pwm_disable(struct s3c2410_pwm *pwm) +{ + unsigned long tcon; + + /* stop timer */ + tcon = __raw_readl(S3C2410_TCON); + tcon &= 0xffffff00; + __raw_writel(tcon, S3C2410_TCON); + + clk_disable(pwm->pclk); + clk_put(pwm->pclk); + + return 0; +} +EXPORT_SYMBOL_GPL(s3c2410_pwm_disable); + +int s3c2410_pwm_init(struct s3c2410_pwm *pwm) +{ + pwm->pclk = clk_get(NULL, "timers"); + if (IS_ERR(pwm->pclk)) + return PTR_ERR(pwm->pclk); + + clk_enable(pwm->pclk); + pwm->pclk_rate = clk_get_rate(pwm->pclk); + return 0; +} +EXPORT_SYMBOL_GPL(s3c2410_pwm_init); + +int s3c2410_pwm_enable(struct s3c2410_pwm *pwm) +{ + unsigned long tcfg0, tcfg1, tcnt, tcmp; + + /* control registers bits */ + tcfg1 = __raw_readl(S3C2410_TCFG1); + tcfg0 = __raw_readl(S3C2410_TCFG0); + + /* divider & scaler slection */ + switch (pwm->timerid) { + case PWM0: + tcfg1 &= ~S3C2410_TCFG1_MUX0_MASK; + tcfg0 &= ~S3C2410_TCFG_PRESCALER0_MASK; + break; + case PWM1: + tcfg1 &= ~S3C2410_TCFG1_MUX1_MASK; + tcfg0 &= ~S3C2410_TCFG_PRESCALER0_MASK; + break; + case PWM2: + tcfg1 &= ~S3C2410_TCFG1_MUX2_MASK; + tcfg0 &= ~S3C2410_TCFG_PRESCALER1_MASK; + break; + case PWM3: + tcfg1 &= ~S3C2410_TCFG1_MUX3_MASK; + tcfg0 &= ~S3C2410_TCFG_PRESCALER1_MASK; + break; + case PWM4: + /* timer four is not capable of doing PWM */ + break; + default: + clk_disable(pwm->pclk); + clk_put(pwm->pclk); + return -1; + } + + /* divider & scaler values */ + tcfg1 |= pwm->divider; + __raw_writel(tcfg1, S3C2410_TCFG1); + + switch (pwm->timerid) { + case PWM0: + case PWM1: + tcfg0 |= pwm->prescaler; + __raw_writel(tcfg0, S3C2410_TCFG0); + break; + default: + if ((tcfg0 | pwm->prescaler) != tcfg0) { + printk(KERN_WARNING "not changing prescaler of PWM %u," + " since it's shared with timer4 (clock tick)\n", + pwm->timerid); + } + break; + } + + /* timer count and compare buffer initial values */ + tcnt = pwm->counter; + tcmp = pwm->comparer; + + __raw_writel(tcnt, S3C2410_TCNTB(pwm->timerid)); + __raw_writel(tcmp, S3C2410_TCMPB(pwm->timerid)); + + /* ensure timer is stopped */ + s3c2410_pwm_stop(pwm); + + return 0; +} +EXPORT_SYMBOL_GPL(s3c2410_pwm_enable); + +int s3c2410_pwm_start(struct s3c2410_pwm *pwm) +{ + unsigned long tcon; + + tcon = __raw_readl(S3C2410_TCON); + + switch (pwm->timerid) { + case PWM0: + tcon |= S3C2410_TCON_T0START; + tcon &= ~S3C2410_TCON_T0MANUALUPD; + break; + case PWM1: + tcon |= S3C2410_TCON_T1START; + tcon &= ~S3C2410_TCON_T1MANUALUPD; + break; + case PWM2: + tcon |= S3C2410_TCON_T2START; + tcon &= ~S3C2410_TCON_T2MANUALUPD; + break; + case PWM3: + tcon |= S3C2410_TCON_T3START; + tcon &= ~S3C2410_TCON_T3MANUALUPD; + break; + case PWM4: + /* timer four is not capable of doing PWM */ + default: + return -ENODEV; + } + + __raw_writel(tcon, S3C2410_TCON); + + return 0; +} +EXPORT_SYMBOL_GPL(s3c2410_pwm_start); + +int s3c2410_pwm_stop(struct s3c2410_pwm *pwm) +{ + unsigned long tcon; + + tcon = __raw_readl(S3C2410_TCON); + + switch (pwm->timerid) { + case PWM0: + tcon &= ~0x00000000; + tcon |= S3C2410_TCON_T0RELOAD; + tcon |= S3C2410_TCON_T0MANUALUPD; + break; + case PWM1: + tcon &= ~0x00000080; + tcon |= S3C2410_TCON_T1RELOAD; + tcon |= S3C2410_TCON_T1MANUALUPD; + break; + case PWM2: + tcon &= ~0x00000800; + tcon |= S3C2410_TCON_T2RELOAD; + tcon |= S3C2410_TCON_T2MANUALUPD; + break; + case PWM3: + tcon &= ~0x00008000; + tcon |= S3C2410_TCON_T3RELOAD; + tcon |= S3C2410_TCON_T3MANUALUPD; + break; + case PWM4: + /* timer four is not capable of doing PWM */ + default: + return -ENODEV; + } + + __raw_writel(tcon, S3C2410_TCON); + + return 0; +} +EXPORT_SYMBOL_GPL(s3c2410_pwm_stop); + +int s3c2410_pwm_duty_cycle(int reg_value, struct s3c2410_pwm *pwm) +{ + __raw_writel(reg_value, S3C2410_TCMPB(pwm->timerid)); + + return 0; +} +EXPORT_SYMBOL_GPL(s3c2410_pwm_duty_cycle); + +int s3c2410_pwm_dumpregs(void) +{ + printk(KERN_INFO "TCON: %08lx, TCFG0: %08lx, TCFG1: %08lx\n", + (unsigned long) __raw_readl(S3C2410_TCON), + (unsigned long) __raw_readl(S3C2410_TCFG0), + (unsigned long) __raw_readl(S3C2410_TCFG1)); + + return 0; +} +EXPORT_SYMBOL_GPL(s3c2410_pwm_dumpregs); + +static int __init s3c24xx_pwm_probe(struct platform_device *pdev) +{ + struct s3c24xx_pwm_platform_data *pdata = pdev->dev.platform_data; + + dev_info(&pdev->dev, "s3c24xx_pwm is registered \n"); + + /* if platform was interested, give him a chance to register + * platform devices that switch power with us as the parent + * at registration time -- ensures suspend / resume ordering + */ + if (pdata) + if (pdata->attach_child_devices) + (pdata->attach_child_devices)(&pdev->dev); + + return 0; +} + +#ifdef CONFIG_PM +static int s3c24xx_pwm_suspend(struct platform_device *pdev, pm_message_t state) +{ + /* PWM config should be kept in suspending */ + standby_reg_tcon = __raw_readl(S3C2410_TCON); + standby_reg_tcfg0 = __raw_readl(S3C2410_TCFG0); + standby_reg_tcfg1 = __raw_readl(S3C2410_TCFG1); + + return 0; +} + +static int s3c24xx_pwm_resume(struct platform_device *pdev) +{ + __raw_writel(standby_reg_tcon, S3C2410_TCON); + __raw_writel(standby_reg_tcfg0, S3C2410_TCFG0); + __raw_writel(standby_reg_tcfg1, S3C2410_TCFG1); + + return 0; +} +#else +#define s3c24xx_pwm_suspend NULL +#define s3c24xx_pwm_resume NULL +#endif + +static struct platform_driver s3c24xx_pwm_driver = { + .driver = { + .name = "s3c24xx_pwm", + .owner = THIS_MODULE, + }, + .probe = s3c24xx_pwm_probe, + .suspend = s3c24xx_pwm_suspend, + .resume = s3c24xx_pwm_resume, +}; + +static int __init s3c24xx_pwm_init(void) +{ + return platform_driver_register(&s3c24xx_pwm_driver); +} + +static void __exit s3c24xx_pwm_exit(void) +{ +} + +MODULE_AUTHOR("Javi Roman <javiroman@kernel-labs.org>"); +MODULE_LICENSE("GPL"); + +module_init(s3c24xx_pwm_init); +module_exit(s3c24xx_pwm_exit); diff --git a/arch/arm/plat-s3c24xx/cpu.c b/arch/arm/plat-s3c24xx/cpu.c index 1932b7e0da1..ed4c19fa196 100644 --- a/arch/arm/plat-s3c24xx/cpu.c +++ b/arch/arm/plat-s3c24xx/cpu.c @@ -61,6 +61,7 @@ static const char name_s3c2410[] = "S3C2410"; static const char name_s3c2412[] = "S3C2412"; static const char name_s3c2440[] = "S3C2440"; static const char name_s3c2442[] = "S3C2442"; +static const char name_s3c2442b[] = "S3C2442B"; static const char name_s3c2443[] = "S3C2443"; static const char name_s3c2410a[] = "S3C2410A"; static const char name_s3c2440a[] = "S3C2440A"; @@ -112,6 +113,15 @@ static struct cpu_table cpu_ids[] __initdata = { .name = name_s3c2442 }, { + .idcode = 0x32440aab, + .idmask = 0xffffffff, + .map_io = s3c244x_map_io, + .init_clocks = s3c244x_init_clocks, + .init_uarts = s3c244x_init_uarts, + .init = s3c2442_init, + .name = name_s3c2442b + }, + { .idcode = 0x32412001, .idmask = 0xffffffff, .map_io = s3c2412_map_io, diff --git a/arch/arm/plat-s3c24xx/include/plat/irq.h b/arch/arm/plat-s3c24xx/include/plat/irq.h index 69e1be8bec3..11a866466d8 100644 --- a/arch/arm/plat-s3c24xx/include/plat/irq.h +++ b/arch/arm/plat-s3c24xx/include/plat/irq.h @@ -12,6 +12,7 @@ #include <linux/io.h> +#include <mach/irqs.h> #include <mach/hardware.h> #include <mach/regs-irq.h> #include <mach/regs-gpio.h> @@ -31,8 +32,15 @@ s3c_irqsub_mask(unsigned int irqno, unsigned int parentbit, { unsigned long mask; unsigned long submask; +#ifdef CONFIG_S3C2440_C_FIQ + unsigned long flags; +#endif submask = __raw_readl(S3C2410_INTSUBMSK); +#ifdef CONFIG_S3C2440_C_FIQ + local_save_flags(flags); + local_fiq_disable(); +#endif mask = __raw_readl(S3C2410_INTMSK); submask |= (1UL << (irqno - IRQ_S3CUART_RX0)); @@ -45,6 +53,9 @@ s3c_irqsub_mask(unsigned int irqno, unsigned int parentbit, /* write back masks */ __raw_writel(submask, S3C2410_INTSUBMSK); +#ifdef CONFIG_S3C2440_C_FIQ + local_irq_restore(flags); +#endif } @@ -53,8 +64,15 @@ s3c_irqsub_unmask(unsigned int irqno, unsigned int parentbit) { unsigned long mask; unsigned long submask; +#ifdef CONFIG_S3C2440_C_FIQ + unsigned long flags; +#endif submask = __raw_readl(S3C2410_INTSUBMSK); +#ifdef CONFIG_S3C2440_C_FIQ + local_save_flags(flags); + local_fiq_disable(); +#endif mask = __raw_readl(S3C2410_INTMSK); submask &= ~(1UL << (irqno - IRQ_S3CUART_RX0)); @@ -63,6 +81,9 @@ s3c_irqsub_unmask(unsigned int irqno, unsigned int parentbit) /* write back masks */ __raw_writel(submask, S3C2410_INTSUBMSK); __raw_writel(mask, S3C2410_INTMSK); +#ifdef CONFIG_S3C2440_C_FIQ + local_irq_restore(flags); +#endif } diff --git a/arch/arm/plat-s3c24xx/irq.c b/arch/arm/plat-s3c24xx/irq.c index 958737775ad..4b21ac9d693 100644 --- a/arch/arm/plat-s3c24xx/irq.c +++ b/arch/arm/plat-s3c24xx/irq.c @@ -28,6 +28,8 @@ #include <asm/mach/irq.h> #include <plat/regs-irqtype.h> +#include <mach/regs-irq.h> +#include <mach/regs-gpio.h> #include <plat/cpu.h> #include <plat/pm.h> @@ -37,12 +39,20 @@ static void s3c_irq_mask(unsigned int irqno) { unsigned long mask; - +#ifdef CONFIG_S3C2440_C_FIQ + unsigned long flags; +#endif irqno -= IRQ_EINT0; - +#ifdef CONFIG_S3C2440_C_FIQ + local_save_flags(flags); + local_fiq_disable(); +#endif mask = __raw_readl(S3C2410_INTMSK); mask |= 1UL << irqno; __raw_writel(mask, S3C2410_INTMSK); +#ifdef CONFIG_S3C2440_C_FIQ + local_irq_restore(flags); +#endif } static inline void @@ -59,9 +69,19 @@ s3c_irq_maskack(unsigned int irqno) { unsigned long bitval = 1UL << (irqno - IRQ_EINT0); unsigned long mask; - +#ifdef CONFIG_S3C2440_C_FIQ + unsigned long flags; +#endif + +#ifdef CONFIG_S3C2440_C_FIQ + local_save_flags(flags); + local_fiq_disable(); +#endif mask = __raw_readl(S3C2410_INTMSK); __raw_writel(mask|bitval, S3C2410_INTMSK); +#ifdef CONFIG_S3C2440_C_FIQ + local_irq_restore(flags); +#endif __raw_writel(bitval, S3C2410_SRCPND); __raw_writel(bitval, S3C2410_INTPND); @@ -72,15 +92,25 @@ static void s3c_irq_unmask(unsigned int irqno) { unsigned long mask; +#ifdef CONFIG_S3C2440_C_FIQ + unsigned long flags; +#endif if (irqno != IRQ_TIMER4 && irqno != IRQ_EINT8t23) irqdbf2("s3c_irq_unmask %d\n", irqno); irqno -= IRQ_EINT0; +#ifdef CONFIG_S3C2440_C_FIQ + local_save_flags(flags); + local_fiq_disable(); +#endif mask = __raw_readl(S3C2410_INTMSK); mask &= ~(1UL << irqno); __raw_writel(mask, S3C2410_INTMSK); +#ifdef CONFIG_S3C2440_C_FIQ + local_irq_restore(flags); +#endif } struct irq_chip s3c_irq_level_chip = { @@ -523,26 +553,26 @@ void __init s3c24xx_init_irq(void) last = 0; for (i = 0; i < 4; i++) { - pend = __raw_readl(S3C2410_INTPND); + pend = __raw_readl(S3C2410_SUBSRCPND); if (pend == 0 || pend == last) break; - __raw_writel(pend, S3C2410_SRCPND); - __raw_writel(pend, S3C2410_INTPND); - printk("irq: clearing pending status %08x\n", (int)pend); + printk("irq: clearing subpending status %08x\n", (int)pend); + __raw_writel(pend, S3C2410_SUBSRCPND); last = pend; } last = 0; for (i = 0; i < 4; i++) { - pend = __raw_readl(S3C2410_SUBSRCPND); + pend = __raw_readl(S3C2410_INTPND); if (pend == 0 || pend == last) break; - printk("irq: clearing subpending status %08x\n", (int)pend); - __raw_writel(pend, S3C2410_SUBSRCPND); + __raw_writel(pend, S3C2410_SRCPND); + __raw_writel(pend, S3C2410_INTPND); + printk("irq: clearing pending status %08x\n", (int)pend); last = pend; } diff --git a/arch/arm/plat-s3c24xx/pwm-clock.c b/arch/arm/plat-s3c24xx/pwm-clock.c new file mode 100644 index 00000000000..d41cccd6a25 --- /dev/null +++ b/arch/arm/plat-s3c24xx/pwm-clock.c @@ -0,0 +1,437 @@ +/* linux/arch/arm/plat-s3c24xx/pwm-clock.c + * + * Copyright (c) 2007 Simtec Electronics + * Copyright (c) 2007, 2008 Ben Dooks + * Ben Dooks <ben-linux@fluff.org> + * + * 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. +*/ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/errno.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> + +#include <mach/hardware.h> +#include <asm/irq.h> + +#include <mach/regs-clock.h> +#include <mach/regs-gpio.h> + +#include <asm/plat-s3c24xx/clock.h> +#include <asm/plat-s3c24xx/cpu.h> + +#include <asm/plat-s3c/regs-timer.h> + +/* Each of the timers 0 through 5 go through the following + * clock tree, with the inputs depending on the timers. + * + * pclk ---- [ prescaler 0 ] -+---> timer 0 + * +---> timer 1 + * + * pclk ---- [ prescaler 1 ] -+---> timer 2 + * +---> timer 3 + * \---> timer 4 + * + * Which are fed into the timers as so: + * + * prescaled 0 ---- [ div 2,4,8,16 ] ---\ + * [mux] -> timer 0 + * tclk 0 ------------------------------/ + * + * prescaled 0 ---- [ div 2,4,8,16 ] ---\ + * [mux] -> timer 1 + * tclk 0 ------------------------------/ + * + * + * prescaled 1 ---- [ div 2,4,8,16 ] ---\ + * [mux] -> timer 2 + * tclk 1 ------------------------------/ + * + * prescaled 1 ---- [ div 2,4,8,16 ] ---\ + * [mux] -> timer 3 + * tclk 1 ------------------------------/ + * + * prescaled 1 ---- [ div 2,4,8, 16 ] --\ + * [mux] -> timer 4 + * tclk 1 ------------------------------/ + * + * Since the mux and the divider are tied together in the + * same register space, it is impossible to set the parent + * and the rate at the same time. To avoid this, we add an + * intermediate 'prescaled-and-divided' clock to select + * as the parent for the timer input clock called tdiv. + * + * prescaled clk --> pwm-tdiv ---\ + * [ mux ] --> timer X + * tclk -------------------------/ +*/ + +static unsigned long clk_pwm_scaler_getrate(struct clk *clk) +{ + unsigned long tcfg0 = __raw_readl(S3C2410_TCFG0); + + if (clk->id == 1) { + tcfg0 &= S3C2410_TCFG_PRESCALER1_MASK; + tcfg0 >>= S3C2410_TCFG_PRESCALER1_SHIFT; + } else { + tcfg0 &= S3C2410_TCFG_PRESCALER0_MASK; + } + + return clk_get_rate(clk->parent) / (tcfg0 + 1); +} + +/* TODO - add set rate calls. */ + +static struct clk clk_timer_scaler[] = { + [0] = { + .name = "pwm-scaler0", + .id = -1, + .get_rate = clk_pwm_scaler_getrate, + }, + [1] = { + .name = "pwm-scaler1", + .id = -1, + .get_rate = clk_pwm_scaler_getrate, + }, +}; + +static struct clk clk_timer_tclk[] = { + [0] = { + .name = "pwm-tclk0", + .id = -1, + }, + [1] = { + .name = "pwm-tclk1", + .id = -1, + }, +}; + +struct pwm_tdiv_clk { + struct clk clk; + unsigned int divisor; +}; + +static inline struct pwm_tdiv_clk *to_tdiv(struct clk *clk) +{ + return container_of(clk, struct pwm_tdiv_clk, clk); +} + +static inline unsigned long tcfg_to_divisor(unsigned long tcfg1) +{ + return 1 << (1 + tcfg1); +} + +static unsigned long clk_pwm_tdiv_get_rate(struct clk *clk) +{ + unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1); + unsigned int divisor; + + tcfg1 >>= S3C2410_TCFG1_SHIFT(clk->id); + tcfg1 &= S3C2410_TCFG1_MUX_MASK; + + if (tcfg1 == S3C2410_TCFG1_MUX_TCLK) + divisor = to_tdiv(clk)->divisor; + else + divisor = tcfg_to_divisor(tcfg1); + + return clk_get_rate(clk->parent) / divisor; +} + +static unsigned long clk_pwm_tdiv_round_rate(struct clk *clk, + unsigned long rate) +{ + unsigned long parent_rate; + unsigned long divisor; + + parent_rate = clk_get_rate(clk->parent); + divisor = parent_rate / rate; + + if (divisor <= 2) + divisor = 2; + else if (divisor <= 4) + divisor = 4; + else if (divisor <= 8) + divisor = 8; + else + divisor = 16; + + return parent_rate / divisor; +} + +static unsigned long clk_pwm_tdiv_bits(struct pwm_tdiv_clk *divclk) +{ + unsigned long bits; + + switch (divclk->divisor) { + case 2: + bits = S3C2410_TCFG1_MUX_DIV2; + break; + case 4: + bits = S3C2410_TCFG1_MUX_DIV4; + break; + case 8: + bits = S3C2410_TCFG1_MUX_DIV8; + break; + case 16: + default: + bits = S3C2410_TCFG1_MUX_DIV16; + break; + } + + return bits; +} + +static void clk_pwm_tdiv_update(struct pwm_tdiv_clk *divclk) +{ + unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1); + unsigned long bits = clk_pwm_tdiv_bits(divclk); + unsigned long flags; + unsigned long shift = S3C2410_TCFG1_SHIFT(divclk->clk.id); + + local_irq_save(flags); + + tcfg1 = __raw_readl(S3C2410_TCFG1); + tcfg1 &= ~(S3C2410_TCFG1_MUX_MASK << shift); + tcfg1 |= bits << shift; + __raw_writel(tcfg1, S3C2410_TCFG1); + + local_irq_restore(flags); +} + +static int clk_pwm_tdiv_set_rate(struct clk *clk, unsigned long rate) +{ + struct pwm_tdiv_clk *divclk = to_tdiv(clk); + unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1); + unsigned long parent_rate = clk_get_rate(clk->parent); + unsigned long divisor; + + tcfg1 >>= S3C2410_TCFG1_SHIFT(clk->id); + tcfg1 &= S3C2410_TCFG1_MUX_MASK; + + rate = clk_round_rate(clk, rate); + divisor = parent_rate / rate; + + if (divisor > 16) + return -EINVAL; + + divclk->divisor = divisor; + + /* Update the current MUX settings if we are currently + * selected as the clock source for this clock. */ + + if (tcfg1 != S3C2410_TCFG1_MUX_TCLK) + clk_pwm_tdiv_update(divclk); + + return 0; +} + +static struct pwm_tdiv_clk clk_timer_tdiv[] = { + [0] = { + .clk = { + .name = "pwm-tdiv", + .parent = &clk_timer_scaler[0], + .get_rate = clk_pwm_tdiv_get_rate, + .set_rate = clk_pwm_tdiv_set_rate, + .round_rate = clk_pwm_tdiv_round_rate, + }, + }, + [1] = { + .clk = { + .name = "pwm-tdiv", + .parent = &clk_timer_scaler[0], + .get_rate = clk_pwm_tdiv_get_rate, + .set_rate = clk_pwm_tdiv_set_rate, + .round_rate = clk_pwm_tdiv_round_rate, + } + }, + [2] = { + .clk = { + .name = "pwm-tdiv", + .parent = &clk_timer_scaler[1], + .get_rate = clk_pwm_tdiv_get_rate, + .set_rate = clk_pwm_tdiv_set_rate, + .round_rate = clk_pwm_tdiv_round_rate, + }, + }, + [3] = { + .clk = { + .name = "pwm-tdiv", + .parent = &clk_timer_scaler[1], + .get_rate = clk_pwm_tdiv_get_rate, + .set_rate = clk_pwm_tdiv_set_rate, + .round_rate = clk_pwm_tdiv_round_rate, + }, + }, + [4] = { + .clk = { + .name = "pwm-tdiv", + .parent = &clk_timer_scaler[1], + .get_rate = clk_pwm_tdiv_get_rate, + .set_rate = clk_pwm_tdiv_set_rate, + .round_rate = clk_pwm_tdiv_round_rate, + }, + }, +}; + +static int __init clk_pwm_tdiv_register(unsigned int id) +{ + struct pwm_tdiv_clk *divclk = &clk_timer_tdiv[id]; + unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1); + + tcfg1 >>= S3C2410_TCFG1_SHIFT(id); + tcfg1 &= S3C2410_TCFG1_MUX_MASK; + + divclk->clk.id = id; + divclk->divisor = tcfg_to_divisor(tcfg1); + + return s3c24xx_register_clock(&divclk->clk); +} + +static inline struct clk *s3c24xx_pwmclk_tclk(unsigned int id) +{ + return (id >= 2) ? &clk_timer_tclk[1] : &clk_timer_tclk[0]; +} + +static inline struct clk *s3c24xx_pwmclk_tdiv(unsigned int id) +{ + return &clk_timer_tdiv[id].clk; +} + +static int clk_pwm_tin_set_parent(struct clk *clk, struct clk *parent) +{ + unsigned int id = clk->id; + unsigned long tcfg1; + unsigned long flags; + unsigned long bits; + unsigned long shift = S3C2410_TCFG1_SHIFT(id); + + if (parent == s3c24xx_pwmclk_tclk(id)) + bits = S3C2410_TCFG1_MUX_TCLK << shift; + else if (parent == s3c24xx_pwmclk_tdiv(id)) + bits = clk_pwm_tdiv_bits(to_tdiv(parent)) << shift; + else + return -EINVAL; + + clk->parent = parent; + + local_irq_save(flags); + + tcfg1 = __raw_readl(S3C2410_TCFG1); + tcfg1 &= ~(S3C2410_TCFG1_MUX_MASK << shift); + __raw_writel(tcfg1 | bits, S3C2410_TCFG1); + + local_irq_restore(flags); + + return 0; +} + +static struct clk clk_tin[] = { + [0] = { + .name = "pwm-tin", + .id = 0, + .set_parent = clk_pwm_tin_set_parent, + }, + [1] = { + .name = "pwm-tin", + .id = 1, + .set_parent = clk_pwm_tin_set_parent, + }, + [2] = { + .name = "pwm-tin", + .id = 2, + .set_parent = clk_pwm_tin_set_parent, + }, + [3] = { + .name = "pwm-tin", + .id = 3, + .set_parent = clk_pwm_tin_set_parent, + }, + [4] = { + .name = "pwm-tin", + .id = 4, + .set_parent = clk_pwm_tin_set_parent, + }, +}; + +static __init int clk_pwm_tin_register(struct clk *pwm) +{ + unsigned long tcfg1 = __raw_readl(S3C2410_TCFG1); + unsigned int id = pwm->id; + + struct clk *parent; + int ret; + + ret = s3c24xx_register_clock(pwm); + if (ret < 0) + return ret; + + tcfg1 >>= S3C2410_TCFG1_SHIFT(id); + tcfg1 &= S3C2410_TCFG1_MUX_MASK; + + if (tcfg1 == S3C2410_TCFG1_MUX_TCLK) + parent = s3c24xx_pwmclk_tclk(id); + else + parent = s3c24xx_pwmclk_tdiv(id); + + return clk_set_parent(pwm, parent); +} + +static __init int s3c24xx_pwmclk_init(void) +{ + struct clk *clk_timers; + unsigned int clk; + int ret; + + clk_timers = clk_get(NULL, "timers"); + if (IS_ERR(clk_timers)) { + printk(KERN_ERR "%s: no parent clock\n", __func__); + return -EINVAL; + } + + for (clk = 0; clk < ARRAY_SIZE(clk_timer_scaler); clk++) { + clk_timer_scaler[clk].parent = clk_timers; + ret = s3c24xx_register_clock(&clk_timer_scaler[clk]); + if (ret < 0) { + printk(KERN_ERR "error adding pwm scaler%d clock\n", clk); + goto err; + } + } + + for (clk = 0; clk < ARRAY_SIZE(clk_timer_tclk); clk++) { + ret = s3c24xx_register_clock(&clk_timer_tclk[clk]); + if (ret < 0) { + printk(KERN_ERR "error adding pww tclk%d\n", clk); + goto err; + } + } + + for (clk = 0; clk < ARRAY_SIZE(clk_timer_tdiv); clk++) { + ret = clk_pwm_tdiv_register(clk); + if (ret < 0) { + printk(KERN_ERR "error adding pwm%d tdiv clock\n", clk); + goto err; + } + } + + for (clk = 0; clk < ARRAY_SIZE(clk_tin); clk++) { + ret = clk_pwm_tin_register(&clk_tin[clk]); + if (ret < 0) { + printk(KERN_ERR "error adding pwm%d tin clock\n", clk); + goto err; + } + } + + return 0; + + err: + return ret; +} + +arch_initcall(s3c24xx_pwmclk_init); |