From f826caa44902ddbe93174f0b642d8abf9698c08e Mon Sep 17 00:00:00 2001 From: Haavard Skinnemoen Date: Sun, 24 Feb 2008 14:34:45 +0100 Subject: atmel_serial: Fix build on avr32 with CONFIG_PM enabled AVR32 doesn't have at91_suspend_entering_slow_clock(). Just assume the clock will keep running for now. David has a better solution for this, but this works for now. Leaving the USART clock running won't prevent the PM code from entering deep power-down modes anyway. Signed-off-by: Haavard Skinnemoen Cc: David Brownell Cc: Andrew Victor --- drivers/serial/atmel_serial.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'drivers/serial') diff --git a/drivers/serial/atmel_serial.c b/drivers/serial/atmel_serial.c index 42be8b01a40f..5f0414fc1b10 100644 --- a/drivers/serial/atmel_serial.c +++ b/drivers/serial/atmel_serial.c @@ -1439,6 +1439,15 @@ static struct uart_driver atmel_uart = { }; #ifdef CONFIG_PM +static bool atmel_serial_clk_will_stop(void) +{ +#ifdef CONFIG_ARCH_AT91 + return at91_suspend_entering_slow_clock(); +#else + return false; +#endif +} + static int atmel_serial_suspend(struct platform_device *pdev, pm_message_t state) { @@ -1446,7 +1455,7 @@ static int atmel_serial_suspend(struct platform_device *pdev, struct atmel_uart_port *atmel_port = to_atmel_uart_port(port); if (device_may_wakeup(&pdev->dev) - && !at91_suspend_entering_slow_clock()) + && !atmel_serial_clk_will_stop()) enable_irq_wake(port->irq); else { uart_suspend_port(&atmel_uart, port); -- cgit v1.2.3 From e1c609efbc0333840f2af2d875ca52ed8ee18587 Mon Sep 17 00:00:00 2001 From: Haavard Skinnemoen Date: Fri, 14 Mar 2008 14:54:13 +0100 Subject: atmel_serial: Drain console TX shifter before suspending Funny things may happen if we stop the USART clock before the shifter is empty. Prevent this from happening by waiting until the shifter is completely drained before allowing suspend to continue. Signed-off-by: Haavard Skinnemoen Cc: Andrew Victor --- drivers/serial/atmel_serial.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'drivers/serial') diff --git a/drivers/serial/atmel_serial.c b/drivers/serial/atmel_serial.c index 5f0414fc1b10..6aeef22bd203 100644 --- a/drivers/serial/atmel_serial.c +++ b/drivers/serial/atmel_serial.c @@ -1454,6 +1454,12 @@ static int atmel_serial_suspend(struct platform_device *pdev, struct uart_port *port = platform_get_drvdata(pdev); struct atmel_uart_port *atmel_port = to_atmel_uart_port(port); + if (atmel_is_console_port(port) && console_suspend_enabled) { + /* Drain the TX shifter */ + while (!(UART_GET_CSR(port) & ATMEL_US_TXEMPTY)) + cpu_relax(); + } + if (device_may_wakeup(&pdev->dev) && !atmel_serial_clk_will_stop()) enable_irq_wake(port->irq); -- cgit v1.2.3 From 05a78966395c5a115be3d38f6eb82efc94ee45b0 Mon Sep 17 00:00:00 2001 From: Ben Dooks Date: Thu, 22 May 2008 16:36:45 +0100 Subject: [ARM] 5050/1: S3C2410: Cleanup header on S3C2410 serial driver Remove the changelog which should really be found in the version control system. Signed-off-by: Ben Dooks Signed-off-by: Russell King --- drivers/serial/s3c2410.c | 35 ++++------------------------------- 1 file changed, 4 insertions(+), 31 deletions(-) (limited to 'drivers/serial') diff --git a/drivers/serial/s3c2410.c b/drivers/serial/s3c2410.c index 2b6a013639e6..c32cf93d7a43 100644 --- a/drivers/serial/s3c2410.c +++ b/drivers/serial/s3c2410.c @@ -1,36 +1,9 @@ -/* - * linux/drivers/serial/s3c2410.c - * - * Driver for onboard UARTs on the Samsung S3C24XX - * - * Based on drivers/char/serial.c and drivers/char/21285.c - * - * Ben Dooks, (c) 2003-2005 Simtec Electronics - * http://www.simtec.co.uk/products/SWLINUX/ - * - * Changelog: - * - * 22-Jul-2004 BJD Finished off device rewrite - * - * 21-Jul-2004 BJD Thanks to for pointing out - * problems with baud rate and loss of IR settings. Update - * to add configuration via platform_device structure - * - * 28-Sep-2004 BJD Re-write for the following items - * - S3C2410 and S3C2440 serial support - * - Power Management support - * - Fix console via IrDA devices - * - SysReq (Herbert Pötzl) - * - Break character handling (Herbert Pötzl) - * - spin-lock initialisation (Dimitry Andric) - * - added clock control - * - updated init code to use platform_device info - * - * 06-Mar-2005 BJD Add s3c2440 fclk clock source +/* linux/drivers/serial/s3c2410.c * - * 09-Mar-2005 BJD Add s3c2400 support + * Driver for Samsung SoC onboard UARTs. * - * 10-Mar-2005 LCVR Changed S3C2410_VA_UART to S3C24XX_VA_UART + * Ben Dooks, Copyright (c) 2003-2005 Simtec Electronics + * http://armlinux.simtec.co.uk/ */ /* Note on 2440 fclk clock source handling -- cgit v1.2.3 From 8fe059df33f82fb785dd795391498ad8bc6fac09 Mon Sep 17 00:00:00 2001 From: Ben Dooks Date: Fri, 23 May 2008 11:48:00 +0100 Subject: [ARM] 5054/1: S3C2410: Add GPLv2 license to the s3c2410 serial driver The original driver had an MODULE_LICENSE statement for GPL, but no explict license in the header of the file. To make this more explicit, and since I am the original authour, we will add a GPLv2 header. Signed-off-by: Ben Dooks Signed-off-by: Russell King --- drivers/serial/s3c2410.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'drivers/serial') diff --git a/drivers/serial/s3c2410.c b/drivers/serial/s3c2410.c index c32cf93d7a43..e284af8071de 100644 --- a/drivers/serial/s3c2410.c +++ b/drivers/serial/s3c2410.c @@ -4,6 +4,10 @@ * * Ben Dooks, Copyright (c) 2003-2005 Simtec Electronics * http://armlinux.simtec.co.uk/ + * + * 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. */ /* Note on 2440 fclk clock source handling @@ -1908,7 +1912,7 @@ console_initcall(s3c24xx_serial_initconsole); #endif /* CONFIG_SERIAL_S3C2410_CONSOLE */ -MODULE_LICENSE("GPL"); +MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Ben Dooks "); MODULE_DESCRIPTION("Samsung S3C2410/S3C2440/S3C2412 Serial port driver"); MODULE_ALIAS("platform:s3c2400-uart"); -- cgit v1.2.3 From ea304e394f78af6bafee07e0ed916af9c38abf48 Mon Sep 17 00:00:00 2001 From: Ben Dooks Date: Thu, 3 Jul 2008 11:24:23 +0100 Subject: [ARM] S3C2410: Add sysfs attribute for serial driver clock source Add attribute to show the current clock source for the serial driver and remove old and annoying debug output. Note, this only currently shows the current source with a "* " prefix to indicate that it is the current source. Future code will list all the clock sources, with the non-selected one with " " prefix. Signed-off-by: Ben Dooks PATCH FOLLOWS KernelVersion: 2.6.26-rc3 --- drivers/serial/s3c2410.c | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) (limited to 'drivers/serial') diff --git a/drivers/serial/s3c2410.c b/drivers/serial/s3c2410.c index e284af8071de..b87c0b55aa22 100644 --- a/drivers/serial/s3c2410.c +++ b/drivers/serial/s3c2410.c @@ -685,11 +685,6 @@ static unsigned int s3c24xx_serial_getclk(struct uart_port *port, int calc_deviation; for (sptr = res; sptr < resptr; sptr++) { - printk(KERN_DEBUG - "found clk %p (%s) quot %d, calc %d\n", - sptr->clksrc, sptr->clksrc->name, - sptr->quot, sptr->calc); - calc_deviation = baud - sptr->calc; if (calc_deviation < 0) calc_deviation = -calc_deviation; @@ -699,13 +694,8 @@ static unsigned int s3c24xx_serial_getclk(struct uart_port *port, deviation = calc_deviation; } } - - printk(KERN_DEBUG "best %p (deviation %d)\n", best, deviation); } - printk(KERN_DEBUG "selected clock %p (%s) quot %d, calc %d\n", - best->clksrc, best->clksrc->name, best->quot, best->calc); - /* store results to pass back */ *clksrc = best->clksrc; @@ -1058,6 +1048,18 @@ static int s3c24xx_serial_init_port(struct s3c24xx_uart_port *ourport, return 0; } +static ssize_t s3c24xx_serial_show_clksrc(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct uart_port *port = s3c24xx_dev_to_port(dev); + struct s3c24xx_uart_port *ourport = to_ourport(port); + + return snprintf(buf, PAGE_SIZE, "* %s\n", ourport->clksrc->name); +} + +static DEVICE_ATTR(clock_source, S_IRUGO, s3c24xx_serial_show_clksrc, NULL); + /* Device driver serial port probe */ static int probe_index = 0; @@ -1083,6 +1085,11 @@ static int s3c24xx_serial_probe(struct platform_device *dev, uart_add_one_port(&s3c24xx_uart_drv, &ourport->port); platform_set_drvdata(dev, &ourport->port); + ret = device_create_file(&dev->dev, &dev_attr_clock_source); + if (ret < 0) { + printk(KERN_ERR "%s: failed to add clksrc attr.\n", __func__); + } + return 0; probe_err: @@ -1093,8 +1100,10 @@ static int s3c24xx_serial_remove(struct platform_device *dev) { struct uart_port *port = s3c24xx_dev_to_port(&dev->dev); - if (port) + if (port) { + device_remove_file(&dev->dev, &dev_attr_clock_source); uart_remove_one_port(&s3c24xx_uart_drv, port); + } return 0; } -- cgit v1.2.3 From b497549a035c2a81b71c7a27f2b00c8a16c09423 Mon Sep 17 00:00:00 2001 From: Ben Dooks Date: Thu, 3 Jul 2008 12:32:51 +0100 Subject: [ARM] S3C24XX: Split serial driver into core and per-cpu drivers The S3C2410 serial driver in drivers/serial/s3c2410.c has been growing bigger with the addition of more variants of this hardware with the growing Samsung SoCs range. As such, it would be easier to split this code up into a core and per-cpu drivers to make driver addition easier, and the core smaller. Signed-off-by: Ben Dooks --- arch/arm/configs/s3c2410_defconfig | 15 +- drivers/serial/Kconfig | 54 +- drivers/serial/Makefile | 4 + drivers/serial/s3c2400.c | 106 +++ drivers/serial/s3c2410.c | 1854 +----------------------------------- drivers/serial/s3c2412.c | 151 +++ drivers/serial/s3c2440.c | 181 ++++ drivers/serial/samsung.c | 1317 +++++++++++++++++++++++++ drivers/serial/samsung.h | 102 ++ 9 files changed, 1939 insertions(+), 1845 deletions(-) create mode 100644 drivers/serial/s3c2400.c create mode 100644 drivers/serial/s3c2412.c create mode 100644 drivers/serial/s3c2440.c create mode 100644 drivers/serial/samsung.c create mode 100644 drivers/serial/samsung.h (limited to 'drivers/serial') diff --git a/arch/arm/configs/s3c2410_defconfig b/arch/arm/configs/s3c2410_defconfig index 53d599cf86b0..1a0434b0079c 100644 --- a/arch/arm/configs/s3c2410_defconfig +++ b/arch/arm/configs/s3c2410_defconfig @@ -1,7 +1,7 @@ # # Automatically generated make config: don't edit # Linux kernel version: 2.6.26-rc8 -# Wed Jun 25 13:39:12 2008 +# Thu Jul 3 11:48:41 2008 # CONFIG_ARM=y CONFIG_SYS_SUPPORTS_APM_EMULATION=y @@ -12,6 +12,7 @@ CONFIG_MMU=y CONFIG_NO_IOPORT=y CONFIG_GENERIC_HARDIRQS=y CONFIG_STACKTRACE_SUPPORT=y +CONFIG_HAVE_LATENCYTOP_SUPPORT=y CONFIG_LOCKDEP_SUPPORT=y CONFIG_TRACE_IRQFLAGS_SUPPORT=y CONFIG_HARDIRQS_SW_RESEND=y @@ -23,6 +24,7 @@ CONFIG_GENERIC_HWEIGHT=y CONFIG_GENERIC_CALIBRATE_DELAY=y CONFIG_ARCH_SUPPORTS_AOUT=y CONFIG_ZONE_DMA=y +CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ=y CONFIG_VECTORS_BASE=0xffff0000 CONFIG_DEFCONFIG_LIST="/lib/modules/$UNAME_RELEASE/.config" @@ -131,7 +133,6 @@ CONFIG_CLASSIC_RCU=y # CONFIG_ARCH_AT91 is not set # CONFIG_ARCH_CLPS7500 is not set # CONFIG_ARCH_CLPS711X is not set -# CONFIG_ARCH_CO285 is not set # CONFIG_ARCH_EBSA110 is not set # CONFIG_ARCH_EP93XX is not set # CONFIG_ARCH_FOOTBRIDGE is not set @@ -145,8 +146,11 @@ CONFIG_CLASSIC_RCU=y # CONFIG_ARCH_IXP2000 is not set # CONFIG_ARCH_IXP4XX is not set # CONFIG_ARCH_L7200 is not set +# CONFIG_ARCH_KIRKWOOD is not set # CONFIG_ARCH_KS8695 is not set # CONFIG_ARCH_NS9XXX is not set +# CONFIG_ARCH_LOKI is not set +# CONFIG_ARCH_MV78XX0 is not set # CONFIG_ARCH_MXC is not set # CONFIG_ARCH_ORION5X is not set # CONFIG_ARCH_PNX4008 is not set @@ -871,8 +875,12 @@ CONFIG_SERIAL_8250_SHARE_IRQ=y # # Non-8250 serial port support # +CONFIG_SERIAL_SAMSUNG=y +# CONFIG_SERIAL_SAMSUNG_DEBUG is not set +CONFIG_SERIAL_SAMSUNG_CONSOLE=y CONFIG_SERIAL_S3C2410=y -CONFIG_SERIAL_S3C2410_CONSOLE=y +CONFIG_SERIAL_S3C2412=y +CONFIG_SERIAL_S3C2440=y CONFIG_SERIAL_CORE=y CONFIG_SERIAL_CORE_CONSOLE=y CONFIG_UNIX98_PTYS=y @@ -1544,6 +1552,7 @@ CONFIG_FRAME_POINTER=y # CONFIG_RCU_TORTURE_TEST is not set # CONFIG_BACKTRACE_SELF_TEST is not set # CONFIG_FAULT_INJECTION is not set +# CONFIG_LATENCYTOP is not set # CONFIG_SAMPLES is not set CONFIG_DEBUG_USER=y CONFIG_DEBUG_ERRORS=y diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig index 9bc42763623c..5a9754455eed 100644 --- a/drivers/serial/Kconfig +++ b/drivers/serial/Kconfig @@ -448,22 +448,27 @@ config SERIAL_CLPS711X_CONSOLE your boot loader (lilo or loadlin) about how to pass options to the kernel at boot time.) -config SERIAL_S3C2410 - tristate "Samsung S3C2410/S3C2440/S3C2442/S3C2412 Serial port support" - depends on ARM && ARCH_S3C2410 - select SERIAL_CORE +config SERIAL_SAMSUNG + tristate "Samsung SoC serial support" + depends on ARM && PLAT_S3C24XX help Support for the on-chip UARTs on the Samsung S3C24XX series CPUs, providing /dev/ttySAC0, 1 and 2 (note, some machines may not provide all of these ports, depending on how the serial port pins are configured. - Currently this driver supports the UARTS on the S3C2410, S3C2440, - S3C2442, S3C2412 and S3C2413 CPUs. +config SERIAL_SAMSUNG_DEBUG + bool "Samsung SoC serial debug" + depends on SERIAL_SAMSUNG + help + Add support for debugging the serial driver. Since this is + generally being used as a console, we use our own output + routines that go via the low-level debug printascii() + function. -config SERIAL_S3C2410_CONSOLE - bool "Support for console on S3C2410 serial port" - depends on SERIAL_S3C2410=y +config SERIAL_SAMSUNG_CONSOLE + bool "Support for console on Samsung SoC serial port" + depends on SERIAL_SAMSUNG=y select SERIAL_CORE_CONSOLE help Allow selection of the S3C24XX on-board serial ports for use as @@ -476,6 +481,37 @@ config SERIAL_S3C2410_CONSOLE your boot loader about how to pass options to the kernel at boot time.) +config SERIAL_S3C2400 + tristate "Samsung S3C2410 Serial port support" + depends on ARM && SERIAL_SAMSUNG && CPU_S3C2400 + default y if CPU_S3C2400 + help + Serial port support for the Samsung S3C2400 SoC + +config SERIAL_S3C2410 + tristate "Samsung S3C2410 Serial port support" + depends on SERIAL_SAMSUNG && CPU_S3C2410 + default y if CPU_S3C2410 + help + Serial port support for the Samsung S3C2410 SoC + +config SERIAL_S3C2412 + tristate "Samsung S3C2412/S3C2413 Serial port support" + depends on SERIAL_SAMSUNG && CPU_S3C2412 + default y if CPU_S3C2412 + help + Serial port support for the Samsung S3C2412 and S3C2413 SoC + +config SERIAL_S3C2440 + tristate "Samsung S3C2440/S3C2442 Serial port support" + depends on SERIAL_SAMSUNG && (CPU_S3C2440 || CPU_S3C2442) + default y if CPU_S3C2440 + default y if CPU_S3C2442 + help + Serial port support for the Samsung S3C2440 and S3C2442 SoC + + + config SERIAL_DZ bool "DECstation DZ serial driver" depends on MACH_DECSTATION && 32BIT diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile index 0d9c09b1e836..7d85c1fbe7e0 100644 --- a/drivers/serial/Makefile +++ b/drivers/serial/Makefile @@ -28,7 +28,11 @@ obj-$(CONFIG_SERIAL_PNX8XXX) += pnx8xxx_uart.o obj-$(CONFIG_SERIAL_SA1100) += sa1100.o obj-$(CONFIG_SERIAL_BFIN) += bfin_5xx.o obj-$(CONFIG_SERIAL_BFIN_SPORT) += bfin_sport_uart.o +obj-$(CONFIG_SERIAL_SAMSUNG) += samsung.o +obj-$(CONFIG_SERIAL_S3C2400) += s3c2400.o obj-$(CONFIG_SERIAL_S3C2410) += s3c2410.o +obj-$(CONFIG_SERIAL_S3C2412) += s3c2412.o +obj-$(CONFIG_SERIAL_S3C2440) += s3c2440.o obj-$(CONFIG_SERIAL_SUNCORE) += suncore.o obj-$(CONFIG_SERIAL_SUNHV) += sunhv.o obj-$(CONFIG_SERIAL_SUNZILOG) += sunzilog.o diff --git a/drivers/serial/s3c2400.c b/drivers/serial/s3c2400.c new file mode 100644 index 000000000000..a1102053e553 --- /dev/null +++ b/drivers/serial/s3c2400.c @@ -0,0 +1,106 @@ +/* linux/drivers/serial/s3c240.c + * + * Driver for Samsung SoC onboard UARTs. + * + * Ben Dooks, Copyright (c) 2003-2005 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include +#include +#include +#include + +#include + +#include + +#include +#include + +#include "samsung.h" + +static int s3c2400_serial_getsource(struct uart_port *port, + struct s3c24xx_uart_clksrc *clk) +{ + clk->divisor = 1; + clk->name = "pclk"; + + return 0; +} + +static int s3c2400_serial_setsource(struct uart_port *port, + struct s3c24xx_uart_clksrc *clk) +{ + return 0; +} + +static int s3c2400_serial_resetport(struct uart_port *port, + struct s3c2410_uartcfg *cfg) +{ + dbg("s3c2400_serial_resetport: port=%p (%08lx), cfg=%p\n", + port, port->mapbase, cfg); + + wr_regl(port, S3C2410_UCON, cfg->ucon); + wr_regl(port, S3C2410_ULCON, cfg->ulcon); + + /* reset both fifos */ + + wr_regl(port, S3C2410_UFCON, cfg->ufcon | S3C2410_UFCON_RESETBOTH); + wr_regl(port, S3C2410_UFCON, cfg->ufcon); + + return 0; +} + +static struct s3c24xx_uart_info s3c2400_uart_inf = { + .name = "Samsung S3C2400 UART", + .type = PORT_S3C2400, + .fifosize = 16, + .rx_fifomask = S3C2410_UFSTAT_RXMASK, + .rx_fifoshift = S3C2410_UFSTAT_RXSHIFT, + .rx_fifofull = S3C2410_UFSTAT_RXFULL, + .tx_fifofull = S3C2410_UFSTAT_TXFULL, + .tx_fifomask = S3C2410_UFSTAT_TXMASK, + .tx_fifoshift = S3C2410_UFSTAT_TXSHIFT, + .get_clksrc = s3c2400_serial_getsource, + .set_clksrc = s3c2400_serial_setsource, + .reset_port = s3c2400_serial_resetport, +}; + +static int s3c2400_serial_probe(struct platform_device *dev) +{ + return s3c24xx_serial_probe(dev, &s3c2400_uart_inf); +} + +static struct platform_driver s3c2400_serial_drv = { + .probe = s3c2400_serial_probe, + .remove = s3c24xx_serial_remove, + .driver = { + .name = "s3c2400-uart", + .owner = THIS_MODULE, + }, +}; + +s3c24xx_console_init(&s3c2400_serial_drv, &s3c2400_uart_inf); + +static inline int s3c2400_serial_init(void) +{ + return s3c24xx_serial_init(&s3c2400_serial_drv, &s3c2400_uart_inf); +} + +static inline void s3c2400_serial_exit(void) +{ + platform_driver_unregister(&s3c2400_serial_drv); +} + +module_init(s3c2400_serial_init); +module_exit(s3c2400_serial_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Ben Dooks "); +MODULE_DESCRIPTION("Samsung S3C2400 SoC Serial port driver"); +MODULE_ALIAS("platform:s3c2400-uart"); diff --git a/drivers/serial/s3c2410.c b/drivers/serial/s3c2410.c index b87c0b55aa22..c5f03f41686f 100644 --- a/drivers/serial/s3c2410.c +++ b/drivers/serial/s3c2410.c @@ -1,8 +1,8 @@ /* linux/drivers/serial/s3c2410.c * - * Driver for Samsung SoC onboard UARTs. + * Driver for Samsung S3C2410 SoC onboard UARTs. * - * Ben Dooks, Copyright (c) 2003-2005 Simtec Electronics + * Ben Dooks, Copyright (c) 2003-2005,2008 Simtec Electronics * http://armlinux.simtec.co.uk/ * * This program is free software; you can redistribute it and/or modify @@ -10,1247 +10,21 @@ * published by the Free Software Foundation. */ -/* Note on 2440 fclk clock source handling - * - * Whilst it is possible to use the fclk as clock source, the method - * of properly switching too/from this is currently un-implemented, so - * whichever way is configured at startup is the one that will be used. -*/ - -/* Hote on 2410 error handling - * - * The s3c2410 manual has a love/hate affair with the contents of the - * UERSTAT register in the UART blocks, and keeps marking some of the - * error bits as reserved. Having checked with the s3c2410x01, - * it copes with BREAKs properly, so I am happy to ignore the RESERVED - * feature from the latter versions of the manual. - * - * If it becomes aparrent that latter versions of the 2410 remove these - * bits, then action will have to be taken to differentiate the versions - * and change the policy on BREAK - * - * BJD, 04-Nov-2004 -*/ - - -#if defined(CONFIG_SERIAL_S3C2410_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ) -#define SUPPORT_SYSRQ -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -#include -#include - -/* structures */ - -struct s3c24xx_uart_info { - char *name; - unsigned int type; - unsigned int fifosize; - unsigned long rx_fifomask; - unsigned long rx_fifoshift; - unsigned long rx_fifofull; - unsigned long tx_fifomask; - unsigned long tx_fifoshift; - unsigned long tx_fifofull; - - /* clock source control */ - - int (*get_clksrc)(struct uart_port *, struct s3c24xx_uart_clksrc *clk); - int (*set_clksrc)(struct uart_port *, struct s3c24xx_uart_clksrc *clk); - - /* uart controls */ - int (*reset_port)(struct uart_port *, struct s3c2410_uartcfg *); -}; - -struct s3c24xx_uart_port { - unsigned char rx_claimed; - unsigned char tx_claimed; - - struct s3c24xx_uart_info *info; - struct s3c24xx_uart_clksrc *clksrc; - struct clk *clk; - struct clk *baudclk; - struct uart_port port; -}; - - -/* configuration defines */ - -#if 0 -#if 1 -/* send debug to the low-level output routines */ - -extern void printascii(const char *); - -static void -s3c24xx_serial_dbg(const char *fmt, ...) -{ - va_list va; - char buff[256]; - - va_start(va, fmt); - vsprintf(buff, fmt, va); - va_end(va); - - printascii(buff); -} - -#define dbg(x...) s3c24xx_serial_dbg(x) - -#else -#define dbg(x...) printk(KERN_DEBUG "s3c24xx: "); -#endif -#else /* no debug */ -#define dbg(x...) do {} while(0) -#endif - -/* UART name and device definitions */ - -#define S3C24XX_SERIAL_NAME "ttySAC" -#define S3C24XX_SERIAL_MAJOR 204 -#define S3C24XX_SERIAL_MINOR 64 - - -/* conversion functions */ - -#define s3c24xx_dev_to_port(__dev) (struct uart_port *)dev_get_drvdata(__dev) -#define s3c24xx_dev_to_cfg(__dev) (struct s3c2410_uartcfg *)((__dev)->platform_data) - -/* we can support 3 uarts, but not always use them */ - -#ifdef CONFIG_CPU_S3C2400 -#define NR_PORTS (2) -#else -#define NR_PORTS (3) -#endif - -/* port irq numbers */ - -#define TX_IRQ(port) ((port)->irq + 1) -#define RX_IRQ(port) ((port)->irq) - -/* register access controls */ - -#define portaddr(port, reg) ((port)->membase + (reg)) - -#define rd_regb(port, reg) (__raw_readb(portaddr(port, reg))) -#define rd_regl(port, reg) (__raw_readl(portaddr(port, reg))) - -#define wr_regb(port, reg, val) \ - do { __raw_writeb(val, portaddr(port, reg)); } while(0) - -#define wr_regl(port, reg, val) \ - do { __raw_writel(val, portaddr(port, reg)); } while(0) - -/* macros to change one thing to another */ - -#define tx_enabled(port) ((port)->unused[0]) -#define rx_enabled(port) ((port)->unused[1]) - -/* flag to ignore all characters comming in */ -#define RXSTAT_DUMMY_READ (0x10000000) - -static inline struct s3c24xx_uart_port *to_ourport(struct uart_port *port) -{ - return container_of(port, struct s3c24xx_uart_port, port); -} - -/* translate a port to the device name */ - -static inline const char *s3c24xx_serial_portname(struct uart_port *port) -{ - return to_platform_device(port->dev)->name; -} - -static int s3c24xx_serial_txempty_nofifo(struct uart_port *port) -{ - return (rd_regl(port, S3C2410_UTRSTAT) & S3C2410_UTRSTAT_TXE); -} - -static void s3c24xx_serial_rx_enable(struct uart_port *port) -{ - unsigned long flags; - unsigned int ucon, ufcon; - int count = 10000; - - spin_lock_irqsave(&port->lock, flags); - - while (--count && !s3c24xx_serial_txempty_nofifo(port)) - udelay(100); - - ufcon = rd_regl(port, S3C2410_UFCON); - ufcon |= S3C2410_UFCON_RESETRX; - wr_regl(port, S3C2410_UFCON, ufcon); - - ucon = rd_regl(port, S3C2410_UCON); - ucon |= S3C2410_UCON_RXIRQMODE; - wr_regl(port, S3C2410_UCON, ucon); - - rx_enabled(port) = 1; - spin_unlock_irqrestore(&port->lock, flags); -} - -static void s3c24xx_serial_rx_disable(struct uart_port *port) -{ - unsigned long flags; - unsigned int ucon; - - spin_lock_irqsave(&port->lock, flags); - - ucon = rd_regl(port, S3C2410_UCON); - ucon &= ~S3C2410_UCON_RXIRQMODE; - wr_regl(port, S3C2410_UCON, ucon); - - rx_enabled(port) = 0; - spin_unlock_irqrestore(&port->lock, flags); -} - -static void s3c24xx_serial_stop_tx(struct uart_port *port) -{ - if (tx_enabled(port)) { - disable_irq(TX_IRQ(port)); - tx_enabled(port) = 0; - if (port->flags & UPF_CONS_FLOW) - s3c24xx_serial_rx_enable(port); - } -} - -static void s3c24xx_serial_start_tx(struct uart_port *port) -{ - if (!tx_enabled(port)) { - if (port->flags & UPF_CONS_FLOW) - s3c24xx_serial_rx_disable(port); - - enable_irq(TX_IRQ(port)); - tx_enabled(port) = 1; - } -} - - -static void s3c24xx_serial_stop_rx(struct uart_port *port) -{ - if (rx_enabled(port)) { - dbg("s3c24xx_serial_stop_rx: port=%p\n", port); - disable_irq(RX_IRQ(port)); - rx_enabled(port) = 0; - } -} - -static void s3c24xx_serial_enable_ms(struct uart_port *port) -{ -} - -static inline struct s3c24xx_uart_info *s3c24xx_port_to_info(struct uart_port *port) -{ - return to_ourport(port)->info; -} - -static inline struct s3c2410_uartcfg *s3c24xx_port_to_cfg(struct uart_port *port) -{ - if (port->dev == NULL) - return NULL; - - return (struct s3c2410_uartcfg *)port->dev->platform_data; -} - -static int s3c24xx_serial_rx_fifocnt(struct s3c24xx_uart_port *ourport, - unsigned long ufstat) -{ - struct s3c24xx_uart_info *info = ourport->info; - - if (ufstat & info->rx_fifofull) - return info->fifosize; - - return (ufstat & info->rx_fifomask) >> info->rx_fifoshift; -} - - -/* ? - where has parity gone?? */ -#define S3C2410_UERSTAT_PARITY (0x1000) - -static irqreturn_t -s3c24xx_serial_rx_chars(int irq, void *dev_id) -{ - struct s3c24xx_uart_port *ourport = dev_id; - struct uart_port *port = &ourport->port; - struct tty_struct *tty = port->info->tty; - unsigned int ufcon, ch, flag, ufstat, uerstat; - int max_count = 64; - - while (max_count-- > 0) { - ufcon = rd_regl(port, S3C2410_UFCON); - ufstat = rd_regl(port, S3C2410_UFSTAT); - - if (s3c24xx_serial_rx_fifocnt(ourport, ufstat) == 0) - break; - - uerstat = rd_regl(port, S3C2410_UERSTAT); - ch = rd_regb(port, S3C2410_URXH); - - if (port->flags & UPF_CONS_FLOW) { - int txe = s3c24xx_serial_txempty_nofifo(port); - - if (rx_enabled(port)) { - if (!txe) { - rx_enabled(port) = 0; - continue; - } - } else { - if (txe) { - ufcon |= S3C2410_UFCON_RESETRX; - wr_regl(port, S3C2410_UFCON, ufcon); - rx_enabled(port) = 1; - goto out; - } - continue; - } - } - - /* insert the character into the buffer */ - - flag = TTY_NORMAL; - port->icount.rx++; - - if (unlikely(uerstat & S3C2410_UERSTAT_ANY)) { - dbg("rxerr: port ch=0x%02x, rxs=0x%08x\n", - ch, uerstat); - - /* check for break */ - if (uerstat & S3C2410_UERSTAT_BREAK) { - dbg("break!\n"); - port->icount.brk++; - if (uart_handle_break(port)) - goto ignore_char; - } - - if (uerstat & S3C2410_UERSTAT_FRAME) - port->icount.frame++; - if (uerstat & S3C2410_UERSTAT_OVERRUN) - port->icount.overrun++; - - uerstat &= port->read_status_mask; - - if (uerstat & S3C2410_UERSTAT_BREAK) - flag = TTY_BREAK; - else if (uerstat & S3C2410_UERSTAT_PARITY) - flag = TTY_PARITY; - else if (uerstat & ( S3C2410_UERSTAT_FRAME | S3C2410_UERSTAT_OVERRUN)) - flag = TTY_FRAME; - } - - if (uart_handle_sysrq_char(port, ch)) - goto ignore_char; - - uart_insert_char(port, uerstat, S3C2410_UERSTAT_OVERRUN, ch, flag); - - ignore_char: - continue; - } - tty_flip_buffer_push(tty); - - out: - return IRQ_HANDLED; -} - -static irqreturn_t s3c24xx_serial_tx_chars(int irq, void *id) -{ - struct s3c24xx_uart_port *ourport = id; - struct uart_port *port = &ourport->port; - struct circ_buf *xmit = &port->info->xmit; - int count = 256; - - if (port->x_char) { - wr_regb(port, S3C2410_UTXH, port->x_char); - port->icount.tx++; - port->x_char = 0; - goto out; - } - - /* if there isnt anything more to transmit, or the uart is now - * stopped, disable the uart and exit - */ - - if (uart_circ_empty(xmit) || uart_tx_stopped(port)) { - s3c24xx_serial_stop_tx(port); - goto out; - } - - /* try and drain the buffer... */ - - while (!uart_circ_empty(xmit) && count-- > 0) { - if (rd_regl(port, S3C2410_UFSTAT) & ourport->info->tx_fifofull) - break; - - wr_regb(port, S3C2410_UTXH, xmit->buf[xmit->tail]); - xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); - port->icount.tx++; - } - - if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) - uart_write_wakeup(port); - - if (uart_circ_empty(xmit)) - s3c24xx_serial_stop_tx(port); - - out: - return IRQ_HANDLED; -} - -static unsigned int s3c24xx_serial_tx_empty(struct uart_port *port) -{ - struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port); - unsigned long ufstat = rd_regl(port, S3C2410_UFSTAT); - unsigned long ufcon = rd_regl(port, S3C2410_UFCON); - - if (ufcon & S3C2410_UFCON_FIFOMODE) { - if ((ufstat & info->tx_fifomask) != 0 || - (ufstat & info->tx_fifofull)) - return 0; - - return 1; - } - - return s3c24xx_serial_txempty_nofifo(port); -} - -/* no modem control lines */ -static unsigned int s3c24xx_serial_get_mctrl(struct uart_port *port) -{ - unsigned int umstat = rd_regb(port,S3C2410_UMSTAT); - - if (umstat & S3C2410_UMSTAT_CTS) - return TIOCM_CAR | TIOCM_DSR | TIOCM_CTS; - else - return TIOCM_CAR | TIOCM_DSR; -} - -static void s3c24xx_serial_set_mctrl(struct uart_port *port, unsigned int mctrl) -{ - /* todo - possibly remove AFC and do manual CTS */ -} - -static void s3c24xx_serial_break_ctl(struct uart_port *port, int break_state) -{ - unsigned long flags; - unsigned int ucon; - - spin_lock_irqsave(&port->lock, flags); - - ucon = rd_regl(port, S3C2410_UCON); - - if (break_state) - ucon |= S3C2410_UCON_SBREAK; - else - ucon &= ~S3C2410_UCON_SBREAK; - - wr_regl(port, S3C2410_UCON, ucon); - - spin_unlock_irqrestore(&port->lock, flags); -} - -static void s3c24xx_serial_shutdown(struct uart_port *port) -{ - struct s3c24xx_uart_port *ourport = to_ourport(port); - - if (ourport->tx_claimed) { - free_irq(TX_IRQ(port), ourport); - tx_enabled(port) = 0; - ourport->tx_claimed = 0; - } - - if (ourport->rx_claimed) { - free_irq(RX_IRQ(port), ourport); - ourport->rx_claimed = 0; - rx_enabled(port) = 0; - } -} - - -static int s3c24xx_serial_startup(struct uart_port *port) -{ - struct s3c24xx_uart_port *ourport = to_ourport(port); - int ret; - - dbg("s3c24xx_serial_startup: port=%p (%08lx,%p)\n", - port->mapbase, port->membase); - - rx_enabled(port) = 1; - - ret = request_irq(RX_IRQ(port), - s3c24xx_serial_rx_chars, 0, - s3c24xx_serial_portname(port), ourport); - - if (ret != 0) { - printk(KERN_ERR "cannot get irq %d\n", RX_IRQ(port)); - return ret; - } - - ourport->rx_claimed = 1; - - dbg("requesting tx irq...\n"); - - tx_enabled(port) = 1; - - ret = request_irq(TX_IRQ(port), - s3c24xx_serial_tx_chars, 0, - s3c24xx_serial_portname(port), ourport); - - if (ret) { - printk(KERN_ERR "cannot get irq %d\n", TX_IRQ(port)); - goto err; - } - - ourport->tx_claimed = 1; - - dbg("s3c24xx_serial_startup ok\n"); - - /* the port reset code should have done the correct - * register setup for the port controls */ - - return ret; - - err: - s3c24xx_serial_shutdown(port); - return ret; -} - -/* power power management control */ - -static void s3c24xx_serial_pm(struct uart_port *port, unsigned int level, - unsigned int old) -{ - struct s3c24xx_uart_port *ourport = to_ourport(port); - - switch (level) { - case 3: - if (!IS_ERR(ourport->baudclk) && ourport->baudclk != NULL) - clk_disable(ourport->baudclk); - - clk_disable(ourport->clk); - break; - - case 0: - clk_enable(ourport->clk); - - if (!IS_ERR(ourport->baudclk) && ourport->baudclk != NULL) - clk_enable(ourport->baudclk); - - break; - default: - printk(KERN_ERR "s3c24xx_serial: unknown pm %d\n", level); - } -} - -/* baud rate calculation - * - * The UARTs on the S3C2410/S3C2440 can take their clocks from a number - * of different sources, including the peripheral clock ("pclk") and an - * external clock ("uclk"). The S3C2440 also adds the core clock ("fclk") - * with a programmable extra divisor. - * - * The following code goes through the clock sources, and calculates the - * baud clocks (and the resultant actual baud rates) and then tries to - * pick the closest one and select that. - * -*/ - - -#define MAX_CLKS (8) - -static struct s3c24xx_uart_clksrc tmp_clksrc = { - .name = "pclk", - .min_baud = 0, - .max_baud = 0, - .divisor = 1, -}; - -static inline int -s3c24xx_serial_getsource(struct uart_port *port, struct s3c24xx_uart_clksrc *c) -{ - struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port); - - return (info->get_clksrc)(port, c); -} - -static inline int -s3c24xx_serial_setsource(struct uart_port *port, struct s3c24xx_uart_clksrc *c) -{ - struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port); - - return (info->set_clksrc)(port, c); -} - -struct baud_calc { - struct s3c24xx_uart_clksrc *clksrc; - unsigned int calc; - unsigned int quot; - struct clk *src; -}; - -static int s3c24xx_serial_calcbaud(struct baud_calc *calc, - struct uart_port *port, - struct s3c24xx_uart_clksrc *clksrc, - unsigned int baud) -{ - unsigned long rate; - - calc->src = clk_get(port->dev, clksrc->name); - if (calc->src == NULL || IS_ERR(calc->src)) - return 0; - - rate = clk_get_rate(calc->src); - rate /= clksrc->divisor; - - calc->clksrc = clksrc; - calc->quot = (rate + (8 * baud)) / (16 * baud); - calc->calc = (rate / (calc->quot * 16)); - - calc->quot--; - return 1; -} - -static unsigned int s3c24xx_serial_getclk(struct uart_port *port, - struct s3c24xx_uart_clksrc **clksrc, - struct clk **clk, - unsigned int baud) -{ - struct s3c2410_uartcfg *cfg = s3c24xx_port_to_cfg(port); - struct s3c24xx_uart_clksrc *clkp; - struct baud_calc res[MAX_CLKS]; - struct baud_calc *resptr, *best, *sptr; - int i; - - clkp = cfg->clocks; - best = NULL; - - if (cfg->clocks_size < 2) { - if (cfg->clocks_size == 0) - clkp = &tmp_clksrc; - - /* check to see if we're sourcing fclk, and if so we're - * going to have to update the clock source - */ - - if (strcmp(clkp->name, "fclk") == 0) { - struct s3c24xx_uart_clksrc src; - - s3c24xx_serial_getsource(port, &src); - - /* check that the port already using fclk, and if - * not, then re-select fclk - */ - - if (strcmp(src.name, clkp->name) == 0) { - s3c24xx_serial_setsource(port, clkp); - s3c24xx_serial_getsource(port, &src); - } - - clkp->divisor = src.divisor; - } - - s3c24xx_serial_calcbaud(res, port, clkp, baud); - best = res; - resptr = best + 1; - } else { - resptr = res; - - for (i = 0; i < cfg->clocks_size; i++, clkp++) { - if (s3c24xx_serial_calcbaud(resptr, port, clkp, baud)) - resptr++; - } - } - - /* ok, we now need to select the best clock we found */ - - if (!best) { - unsigned int deviation = (1<<30)|((1<<30)-1); - int calc_deviation; - - for (sptr = res; sptr < resptr; sptr++) { - calc_deviation = baud - sptr->calc; - if (calc_deviation < 0) - calc_deviation = -calc_deviation; - - if (calc_deviation < deviation) { - best = sptr; - deviation = calc_deviation; - } - } - } - - /* store results to pass back */ - - *clksrc = best->clksrc; - *clk = best->src; - - return best->quot; -} - -static void s3c24xx_serial_set_termios(struct uart_port *port, - struct ktermios *termios, - struct ktermios *old) -{ - struct s3c2410_uartcfg *cfg = s3c24xx_port_to_cfg(port); - struct s3c24xx_uart_port *ourport = to_ourport(port); - struct s3c24xx_uart_clksrc *clksrc = NULL; - struct clk *clk = NULL; - unsigned long flags; - unsigned int baud, quot; - unsigned int ulcon; - unsigned int umcon; - - /* - * We don't support modem control lines. - */ - termios->c_cflag &= ~(HUPCL | CMSPAR); - termios->c_cflag |= CLOCAL; - - /* - * Ask the core to calculate the divisor for us. - */ - - baud = uart_get_baud_rate(port, termios, old, 0, 115200*8); - - if (baud == 38400 && (port->flags & UPF_SPD_MASK) == UPF_SPD_CUST) - quot = port->custom_divisor; - else - quot = s3c24xx_serial_getclk(port, &clksrc, &clk, baud); - - /* check to see if we need to change clock source */ - - if (ourport->clksrc != clksrc || ourport->baudclk != clk) { - s3c24xx_serial_setsource(port, clksrc); - - if (ourport->baudclk != NULL && !IS_ERR(ourport->baudclk)) { - clk_disable(ourport->baudclk); - ourport->baudclk = NULL; - } - - clk_enable(clk); - - ourport->clksrc = clksrc; - ourport->baudclk = clk; - } - - switch (termios->c_cflag & CSIZE) { - case CS5: - dbg("config: 5bits/char\n"); - ulcon = S3C2410_LCON_CS5; - break; - case CS6: - dbg("config: 6bits/char\n"); - ulcon = S3C2410_LCON_CS6; - break; - case CS7: - dbg("config: 7bits/char\n"); - ulcon = S3C2410_LCON_CS7; - break; - case CS8: - default: - dbg("config: 8bits/char\n"); - ulcon = S3C2410_LCON_CS8; - break; - } - - /* preserve original lcon IR settings */ - ulcon |= (cfg->ulcon & S3C2410_LCON_IRM); - - if (termios->c_cflag & CSTOPB) - ulcon |= S3C2410_LCON_STOPB; - - umcon = (termios->c_cflag & CRTSCTS) ? S3C2410_UMCOM_AFC : 0; - - if (termios->c_cflag & PARENB) { - if (termios->c_cflag & PARODD) - ulcon |= S3C2410_LCON_PODD; - else - ulcon |= S3C2410_LCON_PEVEN; - } else { - ulcon |= S3C2410_LCON_PNONE; - } - - spin_lock_irqsave(&port->lock, flags); - - dbg("setting ulcon to %08x, brddiv to %d\n", ulcon, quot); - - wr_regl(port, S3C2410_ULCON, ulcon); - wr_regl(port, S3C2410_UBRDIV, quot); - wr_regl(port, S3C2410_UMCON, umcon); - - dbg("uart: ulcon = 0x%08x, ucon = 0x%08x, ufcon = 0x%08x\n", - rd_regl(port, S3C2410_ULCON), - rd_regl(port, S3C2410_UCON), - rd_regl(port, S3C2410_UFCON)); - - /* - * Update the per-port timeout. - */ - uart_update_timeout(port, termios->c_cflag, baud); - - /* - * Which character status flags are we interested in? - */ - port->read_status_mask = S3C2410_UERSTAT_OVERRUN; - if (termios->c_iflag & INPCK) - port->read_status_mask |= S3C2410_UERSTAT_FRAME | S3C2410_UERSTAT_PARITY; - - /* - * Which character status flags should we ignore? - */ - port->ignore_status_mask = 0; - if (termios->c_iflag & IGNPAR) - port->ignore_status_mask |= S3C2410_UERSTAT_OVERRUN; - if (termios->c_iflag & IGNBRK && termios->c_iflag & IGNPAR) - port->ignore_status_mask |= S3C2410_UERSTAT_FRAME; - - /* - * Ignore all characters if CREAD is not set. - */ - if ((termios->c_cflag & CREAD) == 0) - port->ignore_status_mask |= RXSTAT_DUMMY_READ; - - spin_unlock_irqrestore(&port->lock, flags); -} - -static const char *s3c24xx_serial_type(struct uart_port *port) -{ - switch (port->type) { - case PORT_S3C2410: - return "S3C2410"; - case PORT_S3C2440: - return "S3C2440"; - case PORT_S3C2412: - return "S3C2412"; - default: - return NULL; - } -} - -#define MAP_SIZE (0x100) - -static void s3c24xx_serial_release_port(struct uart_port *port) -{ - release_mem_region(port->mapbase, MAP_SIZE); -} - -static int s3c24xx_serial_request_port(struct uart_port *port) -{ - const char *name = s3c24xx_serial_portname(port); - return request_mem_region(port->mapbase, MAP_SIZE, name) ? 0 : -EBUSY; -} - -static void s3c24xx_serial_config_port(struct uart_port *port, int flags) -{ - struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port); - - if (flags & UART_CONFIG_TYPE && - s3c24xx_serial_request_port(port) == 0) - port->type = info->type; -} - -/* - * verify the new serial_struct (for TIOCSSERIAL). - */ -static int -s3c24xx_serial_verify_port(struct uart_port *port, struct serial_struct *ser) -{ - struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port); - - if (ser->type != PORT_UNKNOWN && ser->type != info->type) - return -EINVAL; - - return 0; -} - - -#ifdef CONFIG_SERIAL_S3C2410_CONSOLE - -static struct console s3c24xx_serial_console; - -#define S3C24XX_SERIAL_CONSOLE &s3c24xx_serial_console -#else -#define S3C24XX_SERIAL_CONSOLE NULL -#endif - -static struct uart_ops s3c24xx_serial_ops = { - .pm = s3c24xx_serial_pm, - .tx_empty = s3c24xx_serial_tx_empty, - .get_mctrl = s3c24xx_serial_get_mctrl, - .set_mctrl = s3c24xx_serial_set_mctrl, - .stop_tx = s3c24xx_serial_stop_tx, - .start_tx = s3c24xx_serial_start_tx, - .stop_rx = s3c24xx_serial_stop_rx, - .enable_ms = s3c24xx_serial_enable_ms, - .break_ctl = s3c24xx_serial_break_ctl, - .startup = s3c24xx_serial_startup, - .shutdown = s3c24xx_serial_shutdown, - .set_termios = s3c24xx_serial_set_termios, - .type = s3c24xx_serial_type, - .release_port = s3c24xx_serial_release_port, - .request_port = s3c24xx_serial_request_port, - .config_port = s3c24xx_serial_config_port, - .verify_port = s3c24xx_serial_verify_port, -}; - - -static struct uart_driver s3c24xx_uart_drv = { - .owner = THIS_MODULE, - .dev_name = "s3c2410_serial", - .nr = 3, - .cons = S3C24XX_SERIAL_CONSOLE, - .driver_name = S3C24XX_SERIAL_NAME, - .major = S3C24XX_SERIAL_MAJOR, - .minor = S3C24XX_SERIAL_MINOR, -}; - -static struct s3c24xx_uart_port s3c24xx_serial_ports[NR_PORTS] = { - [0] = { - .port = { - .lock = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[0].port.lock), - .iotype = UPIO_MEM, - .irq = IRQ_S3CUART_RX0, - .uartclk = 0, - .fifosize = 16, - .ops = &s3c24xx_serial_ops, - .flags = UPF_BOOT_AUTOCONF, - .line = 0, - } - }, - [1] = { - .port = { - .lock = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[1].port.lock), - .iotype = UPIO_MEM, - .irq = IRQ_S3CUART_RX1, - .uartclk = 0, - .fifosize = 16, - .ops = &s3c24xx_serial_ops, - .flags = UPF_BOOT_AUTOCONF, - .line = 1, - } - }, -#if NR_PORTS > 2 - - [2] = { - .port = { - .lock = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[2].port.lock), - .iotype = UPIO_MEM, - .irq = IRQ_S3CUART_RX2, - .uartclk = 0, - .fifosize = 16, - .ops = &s3c24xx_serial_ops, - .flags = UPF_BOOT_AUTOCONF, - .line = 2, - } - } -#endif -}; - -/* s3c24xx_serial_resetport - * - * wrapper to call the specific reset for this port (reset the fifos - * and the settings) -*/ - -static inline int s3c24xx_serial_resetport(struct uart_port * port, - struct s3c2410_uartcfg *cfg) -{ - struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port); - - return (info->reset_port)(port, cfg); -} - -/* s3c24xx_serial_init_port - * - * initialise a single serial port from the platform device given - */ - -static int s3c24xx_serial_init_port(struct s3c24xx_uart_port *ourport, - struct s3c24xx_uart_info *info, - struct platform_device *platdev) -{ - struct uart_port *port = &ourport->port; - struct s3c2410_uartcfg *cfg; - struct resource *res; - int ret; - - dbg("s3c24xx_serial_init_port: port=%p, platdev=%p\n", port, platdev); - - if (platdev == NULL) - return -ENODEV; - - cfg = s3c24xx_dev_to_cfg(&platdev->dev); - - if (port->mapbase != 0) - return 0; - - if (cfg->hwport > 3) - return -EINVAL; - - /* setup info for port */ - port->dev = &platdev->dev; - ourport->info = info; - - /* copy the info in from provided structure */ - ourport->port.fifosize = info->fifosize; - - dbg("s3c24xx_serial_init_port: %p (hw %d)...\n", port, cfg->hwport); - - port->uartclk = 1; - - if (cfg->uart_flags & UPF_CONS_FLOW) { - dbg("s3c24xx_serial_init_port: enabling flow control\n"); - port->flags |= UPF_CONS_FLOW; - } - - /* sort our the physical and virtual addresses for each UART */ - - res = platform_get_resource(platdev, IORESOURCE_MEM, 0); - if (res == NULL) { - printk(KERN_ERR "failed to find memory resource for uart\n"); - return -EINVAL; - } - - dbg("resource %p (%lx..%lx)\n", res, res->start, res->end); - - port->mapbase = res->start; - port->membase = S3C24XX_VA_UART + (res->start - S3C24XX_PA_UART); - ret = platform_get_irq(platdev, 0); - if (ret < 0) - port->irq = 0; - else - port->irq = ret; - - ourport->clk = clk_get(&platdev->dev, "uart"); - - dbg("port: map=%08x, mem=%08x, irq=%d, clock=%ld\n", - port->mapbase, port->membase, port->irq, port->uartclk); - - /* reset the fifos (and setup the uart) */ - s3c24xx_serial_resetport(port, cfg); - return 0; -} - -static ssize_t s3c24xx_serial_show_clksrc(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct uart_port *port = s3c24xx_dev_to_port(dev); - struct s3c24xx_uart_port *ourport = to_ourport(port); - - return snprintf(buf, PAGE_SIZE, "* %s\n", ourport->clksrc->name); -} - -static DEVICE_ATTR(clock_source, S_IRUGO, s3c24xx_serial_show_clksrc, NULL); - -/* Device driver serial port probe */ - -static int probe_index = 0; - -static int s3c24xx_serial_probe(struct platform_device *dev, - struct s3c24xx_uart_info *info) -{ - struct s3c24xx_uart_port *ourport; - int ret; - - dbg("s3c24xx_serial_probe(%p, %p) %d\n", dev, info, probe_index); - - ourport = &s3c24xx_serial_ports[probe_index]; - probe_index++; - - dbg("%s: initialising port %p...\n", __func__, ourport); - - ret = s3c24xx_serial_init_port(ourport, info, dev); - if (ret < 0) - goto probe_err; - - dbg("%s: adding port\n", __func__); - uart_add_one_port(&s3c24xx_uart_drv, &ourport->port); - platform_set_drvdata(dev, &ourport->port); - - ret = device_create_file(&dev->dev, &dev_attr_clock_source); - if (ret < 0) { - printk(KERN_ERR "%s: failed to add clksrc attr.\n", __func__); - } - - return 0; - - probe_err: - return ret; -} - -static int s3c24xx_serial_remove(struct platform_device *dev) -{ - struct uart_port *port = s3c24xx_dev_to_port(&dev->dev); - - if (port) { - device_remove_file(&dev->dev, &dev_attr_clock_source); - uart_remove_one_port(&s3c24xx_uart_drv, port); - } - - return 0; -} - -/* UART power management code */ - -#ifdef CONFIG_PM - -static int s3c24xx_serial_suspend(struct platform_device *dev, pm_message_t state) -{ - struct uart_port *port = s3c24xx_dev_to_port(&dev->dev); - - if (port) - uart_suspend_port(&s3c24xx_uart_drv, port); - - return 0; -} - -static int s3c24xx_serial_resume(struct platform_device *dev) -{ - struct uart_port *port = s3c24xx_dev_to_port(&dev->dev); - struct s3c24xx_uart_port *ourport = to_ourport(port); - - if (port) { - clk_enable(ourport->clk); - s3c24xx_serial_resetport(port, s3c24xx_port_to_cfg(port)); - clk_disable(ourport->clk); - - uart_resume_port(&s3c24xx_uart_drv, port); - } - - return 0; -} - -#else -#define s3c24xx_serial_suspend NULL -#define s3c24xx_serial_resume NULL -#endif - -static int s3c24xx_serial_init(struct platform_driver *drv, - struct s3c24xx_uart_info *info) -{ - dbg("s3c24xx_serial_init(%p,%p)\n", drv, info); - return platform_driver_register(drv); -} - - -/* now comes the code to initialise either the s3c2410 or s3c2440 serial - * port information -*/ - -/* cpu specific variations on the serial port support */ - -#ifdef CONFIG_CPU_S3C2400 - -static int s3c2400_serial_getsource(struct uart_port *port, - struct s3c24xx_uart_clksrc *clk) -{ - clk->divisor = 1; - clk->name = "pclk"; - - return 0; -} - -static int s3c2400_serial_setsource(struct uart_port *port, - struct s3c24xx_uart_clksrc *clk) -{ - return 0; -} - -static int s3c2400_serial_resetport(struct uart_port *port, - struct s3c2410_uartcfg *cfg) -{ - dbg("s3c2400_serial_resetport: port=%p (%08lx), cfg=%p\n", - port, port->mapbase, cfg); - - wr_regl(port, S3C2410_UCON, cfg->ucon); - wr_regl(port, S3C2410_ULCON, cfg->ulcon); - - /* reset both fifos */ - - wr_regl(port, S3C2410_UFCON, cfg->ufcon | S3C2410_UFCON_RESETBOTH); - wr_regl(port, S3C2410_UFCON, cfg->ufcon); - - return 0; -} - -static struct s3c24xx_uart_info s3c2400_uart_inf = { - .name = "Samsung S3C2400 UART", - .type = PORT_S3C2400, - .fifosize = 16, - .rx_fifomask = S3C2410_UFSTAT_RXMASK, - .rx_fifoshift = S3C2410_UFSTAT_RXSHIFT, - .rx_fifofull = S3C2410_UFSTAT_RXFULL, - .tx_fifofull = S3C2410_UFSTAT_TXFULL, - .tx_fifomask = S3C2410_UFSTAT_TXMASK, - .tx_fifoshift = S3C2410_UFSTAT_TXSHIFT, - .get_clksrc = s3c2400_serial_getsource, - .set_clksrc = s3c2400_serial_setsource, - .reset_port = s3c2400_serial_resetport, -}; - -static int s3c2400_serial_probe(struct platform_device *dev) -{ - return s3c24xx_serial_probe(dev, &s3c2400_uart_inf); -} - -static struct platform_driver s3c2400_serial_drv = { - .probe = s3c2400_serial_probe, - .remove = s3c24xx_serial_remove, - .suspend = s3c24xx_serial_suspend, - .resume = s3c24xx_serial_resume, - .driver = { - .name = "s3c2400-uart", - .owner = THIS_MODULE, - }, -}; - -static inline int s3c2400_serial_init(void) -{ - return s3c24xx_serial_init(&s3c2400_serial_drv, &s3c2400_uart_inf); -} - -static inline void s3c2400_serial_exit(void) -{ - platform_driver_unregister(&s3c2400_serial_drv); -} - -#define s3c2400_uart_inf_at &s3c2400_uart_inf -#else - -static inline int s3c2400_serial_init(void) -{ - return 0; -} - -static inline void s3c2400_serial_exit(void) -{ -} - -#define s3c2400_uart_inf_at NULL +#include +#include +#include +#include +#include +#include +#include -#endif /* CONFIG_CPU_S3C2400 */ +#include +#include -/* S3C2410 support */ +#include +#include -#ifdef CONFIG_CPU_S3C2410 +#include "samsung.h" static int s3c2410_serial_setsource(struct uart_port *port, struct s3c24xx_uart_clksrc *clk) @@ -1309,8 +83,6 @@ static struct s3c24xx_uart_info s3c2410_uart_inf = { .reset_port = s3c2410_serial_resetport, }; -/* device management */ - static int s3c2410_serial_probe(struct platform_device *dev) { return s3c24xx_serial_probe(dev, &s3c2410_uart_inf); @@ -1319,612 +91,28 @@ static int s3c2410_serial_probe(struct platform_device *dev) static struct platform_driver s3c2410_serial_drv = { .probe = s3c2410_serial_probe, .remove = s3c24xx_serial_remove, - .suspend = s3c24xx_serial_suspend, - .resume = s3c24xx_serial_resume, .driver = { .name = "s3c2410-uart", .owner = THIS_MODULE, }, }; -static inline int s3c2410_serial_init(void) +s3c24xx_console_init(&s3c2410_serial_drv, &s3c2410_uart_inf); + +static int __init s3c2410_serial_init(void) { return s3c24xx_serial_init(&s3c2410_serial_drv, &s3c2410_uart_inf); } -static inline void s3c2410_serial_exit(void) +static void __exit s3c2410_serial_exit(void) { platform_driver_unregister(&s3c2410_serial_drv); } -#define s3c2410_uart_inf_at &s3c2410_uart_inf -#else - -static inline int s3c2410_serial_init(void) -{ - return 0; -} - -static inline void s3c2410_serial_exit(void) -{ -} - -#define s3c2410_uart_inf_at NULL - -#endif /* CONFIG_CPU_S3C2410 */ - -#if defined(CONFIG_CPU_S3C2440) || defined(CONFIG_CPU_S3C2442) - -static int s3c2440_serial_setsource(struct uart_port *port, - struct s3c24xx_uart_clksrc *clk) -{ - unsigned long ucon = rd_regl(port, S3C2410_UCON); - - // todo - proper fclk<>nonfclk switch // - - ucon &= ~S3C2440_UCON_CLKMASK; - - if (strcmp(clk->name, "uclk") == 0) - ucon |= S3C2440_UCON_UCLK; - else if (strcmp(clk->name, "pclk") == 0) - ucon |= S3C2440_UCON_PCLK; - else if (strcmp(clk->name, "fclk") == 0) - ucon |= S3C2440_UCON_FCLK; - else { - printk(KERN_ERR "unknown clock source %s\n", clk->name); - return -EINVAL; - } - - wr_regl(port, S3C2410_UCON, ucon); - return 0; -} - - -static int s3c2440_serial_getsource(struct uart_port *port, - struct s3c24xx_uart_clksrc *clk) -{ - unsigned long ucon = rd_regl(port, S3C2410_UCON); - unsigned long ucon0, ucon1, ucon2; - - switch (ucon & S3C2440_UCON_CLKMASK) { - case S3C2440_UCON_UCLK: - clk->divisor = 1; - clk->name = "uclk"; - break; - - case S3C2440_UCON_PCLK: - case S3C2440_UCON_PCLK2: - clk->divisor = 1; - clk->name = "pclk"; - break; - - case S3C2440_UCON_FCLK: - /* the fun of calculating the uart divisors on - * the s3c2440 */ - - ucon0 = __raw_readl(S3C24XX_VA_UART0 + S3C2410_UCON); - ucon1 = __raw_readl(S3C24XX_VA_UART1 + S3C2410_UCON); - ucon2 = __raw_readl(S3C24XX_VA_UART2 + S3C2410_UCON); - - printk("ucons: %08lx, %08lx, %08lx\n", ucon0, ucon1, ucon2); - - ucon0 &= S3C2440_UCON0_DIVMASK; - ucon1 &= S3C2440_UCON1_DIVMASK; - ucon2 &= S3C2440_UCON2_DIVMASK; - - if (ucon0 != 0) { - clk->divisor = ucon0 >> S3C2440_UCON_DIVSHIFT; - clk->divisor += 6; - } else if (ucon1 != 0) { - clk->divisor = ucon1 >> S3C2440_UCON_DIVSHIFT; - clk->divisor += 21; - } else if (ucon2 != 0) { - clk->divisor = ucon2 >> S3C2440_UCON_DIVSHIFT; - clk->divisor += 36; - } else { - /* manual calims 44, seems to be 9 */ - clk->divisor = 9; - } - - clk->name = "fclk"; - break; - } - - return 0; -} - -static int s3c2440_serial_resetport(struct uart_port *port, - struct s3c2410_uartcfg *cfg) -{ - unsigned long ucon = rd_regl(port, S3C2410_UCON); - - dbg("s3c2440_serial_resetport: port=%p (%08lx), cfg=%p\n", - port, port->mapbase, cfg); - - /* ensure we don't change the clock settings... */ - - ucon &= (S3C2440_UCON0_DIVMASK | (3<<10)); - - wr_regl(port, S3C2410_UCON, ucon | cfg->ucon); - wr_regl(port, S3C2410_ULCON, cfg->ulcon); - - /* reset both fifos */ - - wr_regl(port, S3C2410_UFCON, cfg->ufcon | S3C2410_UFCON_RESETBOTH); - wr_regl(port, S3C2410_UFCON, cfg->ufcon); - - return 0; -} - -static struct s3c24xx_uart_info s3c2440_uart_inf = { - .name = "Samsung S3C2440 UART", - .type = PORT_S3C2440, - .fifosize = 64, - .rx_fifomask = S3C2440_UFSTAT_RXMASK, - .rx_fifoshift = S3C2440_UFSTAT_RXSHIFT, - .rx_fifofull = S3C2440_UFSTAT_RXFULL, - .tx_fifofull = S3C2440_UFSTAT_TXFULL, - .tx_fifomask = S3C2440_UFSTAT_TXMASK, - .tx_fifoshift = S3C2440_UFSTAT_TXSHIFT, - .get_clksrc = s3c2440_serial_getsource, - .set_clksrc = s3c2440_serial_setsource, - .reset_port = s3c2440_serial_resetport, -}; - -/* device management */ - -static int s3c2440_serial_probe(struct platform_device *dev) -{ - dbg("s3c2440_serial_probe: dev=%p\n", dev); - return s3c24xx_serial_probe(dev, &s3c2440_uart_inf); -} - -static struct platform_driver s3c2440_serial_drv = { - .probe = s3c2440_serial_probe, - .remove = s3c24xx_serial_remove, - .suspend = s3c24xx_serial_suspend, - .resume = s3c24xx_serial_resume, - .driver = { - .name = "s3c2440-uart", - .owner = THIS_MODULE, - }, -}; - - -static inline int s3c2440_serial_init(void) -{ - return s3c24xx_serial_init(&s3c2440_serial_drv, &s3c2440_uart_inf); -} - -static inline void s3c2440_serial_exit(void) -{ - platform_driver_unregister(&s3c2440_serial_drv); -} - -#define s3c2440_uart_inf_at &s3c2440_uart_inf -#else - -static inline int s3c2440_serial_init(void) -{ - return 0; -} - -static inline void s3c2440_serial_exit(void) -{ -} - -#define s3c2440_uart_inf_at NULL -#endif /* CONFIG_CPU_S3C2440 */ - -#if defined(CONFIG_CPU_S3C2412) - -static int s3c2412_serial_setsource(struct uart_port *port, - struct s3c24xx_uart_clksrc *clk) -{ - unsigned long ucon = rd_regl(port, S3C2410_UCON); - - ucon &= ~S3C2412_UCON_CLKMASK; - - if (strcmp(clk->name, "uclk") == 0) - ucon |= S3C2440_UCON_UCLK; - else if (strcmp(clk->name, "pclk") == 0) - ucon |= S3C2440_UCON_PCLK; - else if (strcmp(clk->name, "usysclk") == 0) - ucon |= S3C2412_UCON_USYSCLK; - else { - printk(KERN_ERR "unknown clock source %s\n", clk->name); - return -EINVAL; - } - - wr_regl(port, S3C2410_UCON, ucon); - return 0; -} - - -static int s3c2412_serial_getsource(struct uart_port *port, - struct s3c24xx_uart_clksrc *clk) -{ - unsigned long ucon = rd_regl(port, S3C2410_UCON); - - switch (ucon & S3C2412_UCON_CLKMASK) { - case S3C2412_UCON_UCLK: - clk->divisor = 1; - clk->name = "uclk"; - break; - - case S3C2412_UCON_PCLK: - case S3C2412_UCON_PCLK2: - clk->divisor = 1; - clk->name = "pclk"; - break; - - case S3C2412_UCON_USYSCLK: - clk->divisor = 1; - clk->name = "usysclk"; - break; - } - - return 0; -} - -static int s3c2412_serial_resetport(struct uart_port *port, - struct s3c2410_uartcfg *cfg) -{ - unsigned long ucon = rd_regl(port, S3C2410_UCON); - - dbg("%s: port=%p (%08lx), cfg=%p\n", - __func__, port, port->mapbase, cfg); - - /* ensure we don't change the clock settings... */ - - ucon &= S3C2412_UCON_CLKMASK; - - wr_regl(port, S3C2410_UCON, ucon | cfg->ucon); - wr_regl(port, S3C2410_ULCON, cfg->ulcon); - - /* reset both fifos */ - - wr_regl(port, S3C2410_UFCON, cfg->ufcon | S3C2410_UFCON_RESETBOTH); - wr_regl(port, S3C2410_UFCON, cfg->ufcon); - - return 0; -} - -static struct s3c24xx_uart_info s3c2412_uart_inf = { - .name = "Samsung S3C2412 UART", - .type = PORT_S3C2412, - .fifosize = 64, - .rx_fifomask = S3C2440_UFSTAT_RXMASK, - .rx_fifoshift = S3C2440_UFSTAT_RXSHIFT, - .rx_fifofull = S3C2440_UFSTAT_RXFULL, - .tx_fifofull = S3C2440_UFSTAT_TXFULL, - .tx_fifomask = S3C2440_UFSTAT_TXMASK, - .tx_fifoshift = S3C2440_UFSTAT_TXSHIFT, - .get_clksrc = s3c2412_serial_getsource, - .set_clksrc = s3c2412_serial_setsource, - .reset_port = s3c2412_serial_resetport, -}; - -/* device management */ - -static int s3c2412_serial_probe(struct platform_device *dev) -{ - dbg("s3c2440_serial_probe: dev=%p\n", dev); - return s3c24xx_serial_probe(dev, &s3c2412_uart_inf); -} - -static struct platform_driver s3c2412_serial_drv = { - .probe = s3c2412_serial_probe, - .remove = s3c24xx_serial_remove, - .suspend = s3c24xx_serial_suspend, - .resume = s3c24xx_serial_resume, - .driver = { - .name = "s3c2412-uart", - .owner = THIS_MODULE, - }, -}; - - -static inline int s3c2412_serial_init(void) -{ - return s3c24xx_serial_init(&s3c2412_serial_drv, &s3c2412_uart_inf); -} - -static inline void s3c2412_serial_exit(void) -{ - platform_driver_unregister(&s3c2412_serial_drv); -} - -#define s3c2412_uart_inf_at &s3c2412_uart_inf -#else - -static inline int s3c2412_serial_init(void) -{ - return 0; -} - -static inline void s3c2412_serial_exit(void) -{ -} - -#define s3c2412_uart_inf_at NULL -#endif /* CONFIG_CPU_S3C2440 */ - - -/* module initialisation code */ - -static int __init s3c24xx_serial_modinit(void) -{ - int ret; - - ret = uart_register_driver(&s3c24xx_uart_drv); - if (ret < 0) { - printk(KERN_ERR "failed to register UART driver\n"); - return -1; - } - - s3c2400_serial_init(); - s3c2410_serial_init(); - s3c2412_serial_init(); - s3c2440_serial_init(); - - return 0; -} - -static void __exit s3c24xx_serial_modexit(void) -{ - s3c2400_serial_exit(); - s3c2410_serial_exit(); - s3c2412_serial_exit(); - s3c2440_serial_exit(); - - uart_unregister_driver(&s3c24xx_uart_drv); -} - - -module_init(s3c24xx_serial_modinit); -module_exit(s3c24xx_serial_modexit); - -/* Console code */ - -#ifdef CONFIG_SERIAL_S3C2410_CONSOLE - -static struct uart_port *cons_uart; - -static int -s3c24xx_serial_console_txrdy(struct uart_port *port, unsigned int ufcon) -{ - struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port); - unsigned long ufstat, utrstat; - - if (ufcon & S3C2410_UFCON_FIFOMODE) { - /* fifo mode - check ammount of data in fifo registers... */ - - ufstat = rd_regl(port, S3C2410_UFSTAT); - return (ufstat & info->tx_fifofull) ? 0 : 1; - } - - /* in non-fifo mode, we go and use the tx buffer empty */ - - utrstat = rd_regl(port, S3C2410_UTRSTAT); - return (utrstat & S3C2410_UTRSTAT_TXE) ? 1 : 0; -} - -static void -s3c24xx_serial_console_putchar(struct uart_port *port, int ch) -{ - unsigned int ufcon = rd_regl(cons_uart, S3C2410_UFCON); - while (!s3c24xx_serial_console_txrdy(port, ufcon)) - barrier(); - wr_regb(cons_uart, S3C2410_UTXH, ch); -} - -static void -s3c24xx_serial_console_write(struct console *co, const char *s, - unsigned int count) -{ - uart_console_write(cons_uart, s, count, s3c24xx_serial_console_putchar); -} - -static void __init -s3c24xx_serial_get_options(struct uart_port *port, int *baud, - int *parity, int *bits) -{ - struct s3c24xx_uart_clksrc clksrc; - struct clk *clk; - unsigned int ulcon; - unsigned int ucon; - unsigned int ubrdiv; - unsigned long rate; - - ulcon = rd_regl(port, S3C2410_ULCON); - ucon = rd_regl(port, S3C2410_UCON); - ubrdiv = rd_regl(port, S3C2410_UBRDIV); - - dbg("s3c24xx_serial_get_options: port=%p\n" - "registers: ulcon=%08x, ucon=%08x, ubdriv=%08x\n", - port, ulcon, ucon, ubrdiv); - - if ((ucon & 0xf) != 0) { - /* consider the serial port configured if the tx/rx mode set */ - - switch (ulcon & S3C2410_LCON_CSMASK) { - case S3C2410_LCON_CS5: - *bits = 5; - break; - case S3C2410_LCON_CS6: - *bits = 6; - break; - case S3C2410_LCON_CS7: - *bits = 7; - break; - default: - case S3C2410_LCON_CS8: - *bits = 8; - break; - } - - switch (ulcon & S3C2410_LCON_PMASK) { - case S3C2410_LCON_PEVEN: - *parity = 'e'; - break; - - case S3C2410_LCON_PODD: - *parity = 'o'; - break; - - case S3C2410_LCON_PNONE: - default: - *parity = 'n'; - } - - /* now calculate the baud rate */ - - s3c24xx_serial_getsource(port, &clksrc); - - clk = clk_get(port->dev, clksrc.name); - if (!IS_ERR(clk) && clk != NULL) - rate = clk_get_rate(clk) / clksrc.divisor; - else - rate = 1; - - - *baud = rate / ( 16 * (ubrdiv + 1)); - dbg("calculated baud %d\n", *baud); - } - -} - -/* s3c24xx_serial_init_ports - * - * initialise the serial ports from the machine provided initialisation - * data. -*/ - -static int s3c24xx_serial_init_ports(struct s3c24xx_uart_info *info) -{ - struct s3c24xx_uart_port *ptr = s3c24xx_serial_ports; - struct platform_device **platdev_ptr; - int i; - - dbg("s3c24xx_serial_init_ports: initialising ports...\n"); - - platdev_ptr = s3c24xx_uart_devs; - - for (i = 0; i < NR_PORTS; i++, ptr++, platdev_ptr++) { - s3c24xx_serial_init_port(ptr, info, *platdev_ptr); - } - - return 0; -} - -static int __init -s3c24xx_serial_console_setup(struct console *co, char *options) -{ - struct uart_port *port; - int baud = 9600; - int bits = 8; - int parity = 'n'; - int flow = 'n'; - - dbg("s3c24xx_serial_console_setup: co=%p (%d), %s\n", - co, co->index, options); - - /* is this a valid port */ - - if (co->index == -1 || co->index >= NR_PORTS) - co->index = 0; - - port = &s3c24xx_serial_ports[co->index].port; - - /* is the port configured? */ - - if (port->mapbase == 0x0) { - co->index = 0; - port = &s3c24xx_serial_ports[co->index].port; - } - - cons_uart = port; - - dbg("s3c24xx_serial_console_setup: port=%p (%d)\n", port, co->index); - - /* - * Check whether an invalid uart number has been specified, and - * if so, search for the first available port that does have - * console support. - */ - if (options) - uart_parse_options(options, &baud, &parity, &bits, &flow); - else - s3c24xx_serial_get_options(port, &baud, &parity, &bits); - - dbg("s3c24xx_serial_console_setup: baud %d\n", baud); - - return uart_set_options(port, co, baud, parity, bits, flow); -} - -/* s3c24xx_serial_initconsole - * - * initialise the console from one of the uart drivers -*/ - -static struct console s3c24xx_serial_console = -{ - .name = S3C24XX_SERIAL_NAME, - .device = uart_console_device, - .flags = CON_PRINTBUFFER, - .index = -1, - .write = s3c24xx_serial_console_write, - .setup = s3c24xx_serial_console_setup -}; - -static int s3c24xx_serial_initconsole(void) -{ - struct s3c24xx_uart_info *info; - struct platform_device *dev = s3c24xx_uart_devs[0]; - - dbg("s3c24xx_serial_initconsole\n"); - - /* select driver based on the cpu */ - - if (dev == NULL) { - printk(KERN_ERR "s3c24xx: no devices for console init\n"); - return 0; - } - - if (strcmp(dev->name, "s3c2400-uart") == 0) { - info = s3c2400_uart_inf_at; - } else if (strcmp(dev->name, "s3c2410-uart") == 0) { - info = s3c2410_uart_inf_at; - } else if (strcmp(dev->name, "s3c2440-uart") == 0) { - info = s3c2440_uart_inf_at; - } else if (strcmp(dev->name, "s3c2412-uart") == 0) { - info = s3c2412_uart_inf_at; - } else { - printk(KERN_ERR "s3c24xx: no driver for %s\n", dev->name); - return 0; - } - - if (info == NULL) { - printk(KERN_ERR "s3c24xx: no driver for console\n"); - return 0; - } - - s3c24xx_serial_console.data = &s3c24xx_uart_drv; - s3c24xx_serial_init_ports(info); - - register_console(&s3c24xx_serial_console); - return 0; -} - -console_initcall(s3c24xx_serial_initconsole); - -#endif /* CONFIG_SERIAL_S3C2410_CONSOLE */ +module_init(s3c2410_serial_init); +module_exit(s3c2410_serial_exit); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Ben Dooks "); -MODULE_DESCRIPTION("Samsung S3C2410/S3C2440/S3C2412 Serial port driver"); -MODULE_ALIAS("platform:s3c2400-uart"); +MODULE_DESCRIPTION("Samsung S3C2410 SoC Serial port driver"); MODULE_ALIAS("platform:s3c2410-uart"); -MODULE_ALIAS("platform:s3c2412-uart"); -MODULE_ALIAS("platform:s3c2440-uart"); diff --git a/drivers/serial/s3c2412.c b/drivers/serial/s3c2412.c new file mode 100644 index 000000000000..ce0c220e3e92 --- /dev/null +++ b/drivers/serial/s3c2412.c @@ -0,0 +1,151 @@ +/* linux/drivers/serial/s3c2412.c + * + * Driver for Samsung S3C2412 and S3C2413 SoC onboard UARTs. + * + * Ben Dooks, Copyright (c) 2003-2005,2008 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "samsung.h" + +static int s3c2412_serial_setsource(struct uart_port *port, + struct s3c24xx_uart_clksrc *clk) +{ + unsigned long ucon = rd_regl(port, S3C2410_UCON); + + ucon &= ~S3C2412_UCON_CLKMASK; + + if (strcmp(clk->name, "uclk") == 0) + ucon |= S3C2440_UCON_UCLK; + else if (strcmp(clk->name, "pclk") == 0) + ucon |= S3C2440_UCON_PCLK; + else if (strcmp(clk->name, "usysclk") == 0) + ucon |= S3C2412_UCON_USYSCLK; + else { + printk(KERN_ERR "unknown clock source %s\n", clk->name); + return -EINVAL; + } + + wr_regl(port, S3C2410_UCON, ucon); + return 0; +} + + +static int s3c2412_serial_getsource(struct uart_port *port, + struct s3c24xx_uart_clksrc *clk) +{ + unsigned long ucon = rd_regl(port, S3C2410_UCON); + + switch (ucon & S3C2412_UCON_CLKMASK) { + case S3C2412_UCON_UCLK: + clk->divisor = 1; + clk->name = "uclk"; + break; + + case S3C2412_UCON_PCLK: + case S3C2412_UCON_PCLK2: + clk->divisor = 1; + clk->name = "pclk"; + break; + + case S3C2412_UCON_USYSCLK: + clk->divisor = 1; + clk->name = "usysclk"; + break; + } + + return 0; +} + +static int s3c2412_serial_resetport(struct uart_port *port, + struct s3c2410_uartcfg *cfg) +{ + unsigned long ucon = rd_regl(port, S3C2410_UCON); + + dbg("%s: port=%p (%08lx), cfg=%p\n", + __func__, port, port->mapbase, cfg); + + /* ensure we don't change the clock settings... */ + + ucon &= S3C2412_UCON_CLKMASK; + + wr_regl(port, S3C2410_UCON, ucon | cfg->ucon); + wr_regl(port, S3C2410_ULCON, cfg->ulcon); + + /* reset both fifos */ + + wr_regl(port, S3C2410_UFCON, cfg->ufcon | S3C2410_UFCON_RESETBOTH); + wr_regl(port, S3C2410_UFCON, cfg->ufcon); + + return 0; +} + +static struct s3c24xx_uart_info s3c2412_uart_inf = { + .name = "Samsung S3C2412 UART", + .type = PORT_S3C2412, + .fifosize = 64, + .rx_fifomask = S3C2440_UFSTAT_RXMASK, + .rx_fifoshift = S3C2440_UFSTAT_RXSHIFT, + .rx_fifofull = S3C2440_UFSTAT_RXFULL, + .tx_fifofull = S3C2440_UFSTAT_TXFULL, + .tx_fifomask = S3C2440_UFSTAT_TXMASK, + .tx_fifoshift = S3C2440_UFSTAT_TXSHIFT, + .get_clksrc = s3c2412_serial_getsource, + .set_clksrc = s3c2412_serial_setsource, + .reset_port = s3c2412_serial_resetport, +}; + +/* device management */ + +static int s3c2412_serial_probe(struct platform_device *dev) +{ + dbg("s3c2440_serial_probe: dev=%p\n", dev); + return s3c24xx_serial_probe(dev, &s3c2412_uart_inf); +} + +static struct platform_driver s3c2412_serial_drv = { + .probe = s3c2412_serial_probe, + .remove = s3c24xx_serial_remove, + .driver = { + .name = "s3c2412-uart", + .owner = THIS_MODULE, + }, +}; + +s3c24xx_console_init(&s3c2412_serial_drv, &s3c2412_uart_inf); + +static inline int s3c2412_serial_init(void) +{ + return s3c24xx_serial_init(&s3c2412_serial_drv, &s3c2412_uart_inf); +} + +static inline void s3c2412_serial_exit(void) +{ + platform_driver_unregister(&s3c2412_serial_drv); +} + +module_init(s3c2412_serial_init); +module_exit(s3c2412_serial_exit); + +MODULE_DESCRIPTION("Samsung S3C2412,S3C2413 SoC Serial port driver"); +MODULE_AUTHOR("Ben Dooks "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:s3c2412-uart"); diff --git a/drivers/serial/s3c2440.c b/drivers/serial/s3c2440.c new file mode 100644 index 000000000000..38f954bd39c6 --- /dev/null +++ b/drivers/serial/s3c2440.c @@ -0,0 +1,181 @@ +/* linux/drivers/serial/s3c2440.c + * + * Driver for Samsung S3C2440 and S3C2442 SoC onboard UARTs. + * + * Ben Dooks, Copyright (c) 2003-2005,2008 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "samsung.h" + + +static int s3c2440_serial_setsource(struct uart_port *port, + struct s3c24xx_uart_clksrc *clk) +{ + unsigned long ucon = rd_regl(port, S3C2410_UCON); + + /* todo - proper fclk<>nonfclk switch. */ + + ucon &= ~S3C2440_UCON_CLKMASK; + + if (strcmp(clk->name, "uclk") == 0) + ucon |= S3C2440_UCON_UCLK; + else if (strcmp(clk->name, "pclk") == 0) + ucon |= S3C2440_UCON_PCLK; + else if (strcmp(clk->name, "fclk") == 0) + ucon |= S3C2440_UCON_FCLK; + else { + printk(KERN_ERR "unknown clock source %s\n", clk->name); + return -EINVAL; + } + + wr_regl(port, S3C2410_UCON, ucon); + return 0; +} + + +static int s3c2440_serial_getsource(struct uart_port *port, + struct s3c24xx_uart_clksrc *clk) +{ + unsigned long ucon = rd_regl(port, S3C2410_UCON); + unsigned long ucon0, ucon1, ucon2; + + switch (ucon & S3C2440_UCON_CLKMASK) { + case S3C2440_UCON_UCLK: + clk->divisor = 1; + clk->name = "uclk"; + break; + + case S3C2440_UCON_PCLK: + case S3C2440_UCON_PCLK2: + clk->divisor = 1; + clk->name = "pclk"; + break; + + case S3C2440_UCON_FCLK: + /* the fun of calculating the uart divisors on + * the s3c2440 */ + + ucon0 = __raw_readl(S3C24XX_VA_UART0 + S3C2410_UCON); + ucon1 = __raw_readl(S3C24XX_VA_UART1 + S3C2410_UCON); + ucon2 = __raw_readl(S3C24XX_VA_UART2 + S3C2410_UCON); + + printk("ucons: %08lx, %08lx, %08lx\n", ucon0, ucon1, ucon2); + + ucon0 &= S3C2440_UCON0_DIVMASK; + ucon1 &= S3C2440_UCON1_DIVMASK; + ucon2 &= S3C2440_UCON2_DIVMASK; + + if (ucon0 != 0) { + clk->divisor = ucon0 >> S3C2440_UCON_DIVSHIFT; + clk->divisor += 6; + } else if (ucon1 != 0) { + clk->divisor = ucon1 >> S3C2440_UCON_DIVSHIFT; + clk->divisor += 21; + } else if (ucon2 != 0) { + clk->divisor = ucon2 >> S3C2440_UCON_DIVSHIFT; + clk->divisor += 36; + } else { + /* manual calims 44, seems to be 9 */ + clk->divisor = 9; + } + + clk->name = "fclk"; + break; + } + + return 0; +} + +static int s3c2440_serial_resetport(struct uart_port *port, + struct s3c2410_uartcfg *cfg) +{ + unsigned long ucon = rd_regl(port, S3C2410_UCON); + + dbg("s3c2440_serial_resetport: port=%p (%08lx), cfg=%p\n", + port, port->mapbase, cfg); + + /* ensure we don't change the clock settings... */ + + ucon &= (S3C2440_UCON0_DIVMASK | (3<<10)); + + wr_regl(port, S3C2410_UCON, ucon | cfg->ucon); + wr_regl(port, S3C2410_ULCON, cfg->ulcon); + + /* reset both fifos */ + + wr_regl(port, S3C2410_UFCON, cfg->ufcon | S3C2410_UFCON_RESETBOTH); + wr_regl(port, S3C2410_UFCON, cfg->ufcon); + + return 0; +} + +static struct s3c24xx_uart_info s3c2440_uart_inf = { + .name = "Samsung S3C2440 UART", + .type = PORT_S3C2440, + .fifosize = 64, + .rx_fifomask = S3C2440_UFSTAT_RXMASK, + .rx_fifoshift = S3C2440_UFSTAT_RXSHIFT, + .rx_fifofull = S3C2440_UFSTAT_RXFULL, + .tx_fifofull = S3C2440_UFSTAT_TXFULL, + .tx_fifomask = S3C2440_UFSTAT_TXMASK, + .tx_fifoshift = S3C2440_UFSTAT_TXSHIFT, + .get_clksrc = s3c2440_serial_getsource, + .set_clksrc = s3c2440_serial_setsource, + .reset_port = s3c2440_serial_resetport, +}; + +/* device management */ + +static int s3c2440_serial_probe(struct platform_device *dev) +{ + dbg("s3c2440_serial_probe: dev=%p\n", dev); + return s3c24xx_serial_probe(dev, &s3c2440_uart_inf); +} + +static struct platform_driver s3c2440_serial_drv = { + .probe = s3c2440_serial_probe, + .remove = s3c24xx_serial_remove, + .driver = { + .name = "s3c2440-uart", + .owner = THIS_MODULE, + }, +}; + +s3c24xx_console_init(&s3c2440_serial_drv, &s3c2440_uart_inf); + +static int __init s3c2440_serial_init(void) +{ + return s3c24xx_serial_init(&s3c2440_serial_drv, &s3c2440_uart_inf); +} + +static void __exit s3c2440_serial_exit(void) +{ + platform_driver_unregister(&s3c2440_serial_drv); +} + +module_init(s3c2440_serial_init); +module_exit(s3c2440_serial_exit); + +MODULE_DESCRIPTION("Samsung S3C2440,S3C2442 SoC Serial port driver"); +MODULE_AUTHOR("Ben Dooks "); +MODULE_LICENSE("GPLi v2"); +MODULE_ALIAS("platform:s3c2440-uart"); diff --git a/drivers/serial/samsung.c b/drivers/serial/samsung.c new file mode 100644 index 000000000000..4a3ecaa629e6 --- /dev/null +++ b/drivers/serial/samsung.c @@ -0,0 +1,1317 @@ +/* linux/drivers/serial/samsuing.c + * + * Driver core for Samsung SoC onboard UARTs. + * + * Ben Dooks, Copyright (c) 2003-2005,2008 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * + * 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. +*/ + +/* Hote on 2410 error handling + * + * The s3c2410 manual has a love/hate affair with the contents of the + * UERSTAT register in the UART blocks, and keeps marking some of the + * error bits as reserved. Having checked with the s3c2410x01, + * it copes with BREAKs properly, so I am happy to ignore the RESERVED + * feature from the latter versions of the manual. + * + * If it becomes aparrent that latter versions of the 2410 remove these + * bits, then action will have to be taken to differentiate the versions + * and change the policy on BREAK + * + * BJD, 04-Nov-2004 +*/ + +#if defined(CONFIG_SERIAL_SAMSUNG_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ) +#define SUPPORT_SYSRQ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include + +#include "samsung.h" + +/* UART name and device definitions */ + +#define S3C24XX_SERIAL_NAME "ttySAC" +#define S3C24XX_SERIAL_MAJOR 204 +#define S3C24XX_SERIAL_MINOR 64 + +/* we can support 3 uarts, but not always use them */ + +#ifdef CONFIG_CPU_S3C2400 +#define NR_PORTS (2) +#else +#define NR_PORTS (3) +#endif + +/* port irq numbers */ + +#define TX_IRQ(port) ((port)->irq + 1) +#define RX_IRQ(port) ((port)->irq) + +/* macros to change one thing to another */ + +#define tx_enabled(port) ((port)->unused[0]) +#define rx_enabled(port) ((port)->unused[1]) + +/* flag to ignore all characters comming in */ +#define RXSTAT_DUMMY_READ (0x10000000) + +static inline struct s3c24xx_uart_port *to_ourport(struct uart_port *port) +{ + return container_of(port, struct s3c24xx_uart_port, port); +} + +/* translate a port to the device name */ + +static inline const char *s3c24xx_serial_portname(struct uart_port *port) +{ + return to_platform_device(port->dev)->name; +} + +static int s3c24xx_serial_txempty_nofifo(struct uart_port *port) +{ + return (rd_regl(port, S3C2410_UTRSTAT) & S3C2410_UTRSTAT_TXE); +} + +static void s3c24xx_serial_rx_enable(struct uart_port *port) +{ + unsigned long flags; + unsigned int ucon, ufcon; + int count = 10000; + + spin_lock_irqsave(&port->lock, flags); + + while (--count && !s3c24xx_serial_txempty_nofifo(port)) + udelay(100); + + ufcon = rd_regl(port, S3C2410_UFCON); + ufcon |= S3C2410_UFCON_RESETRX; + wr_regl(port, S3C2410_UFCON, ufcon); + + ucon = rd_regl(port, S3C2410_UCON); + ucon |= S3C2410_UCON_RXIRQMODE; + wr_regl(port, S3C2410_UCON, ucon); + + rx_enabled(port) = 1; + spin_unlock_irqrestore(&port->lock, flags); +} + +static void s3c24xx_serial_rx_disable(struct uart_port *port) +{ + unsigned long flags; + unsigned int ucon; + + spin_lock_irqsave(&port->lock, flags); + + ucon = rd_regl(port, S3C2410_UCON); + ucon &= ~S3C2410_UCON_RXIRQMODE; + wr_regl(port, S3C2410_UCON, ucon); + + rx_enabled(port) = 0; + spin_unlock_irqrestore(&port->lock, flags); +} + +static void s3c24xx_serial_stop_tx(struct uart_port *port) +{ + if (tx_enabled(port)) { + disable_irq(TX_IRQ(port)); + tx_enabled(port) = 0; + if (port->flags & UPF_CONS_FLOW) + s3c24xx_serial_rx_enable(port); + } +} + +static void s3c24xx_serial_start_tx(struct uart_port *port) +{ + if (!tx_enabled(port)) { + if (port->flags & UPF_CONS_FLOW) + s3c24xx_serial_rx_disable(port); + + enable_irq(TX_IRQ(port)); + tx_enabled(port) = 1; + } +} + + +static void s3c24xx_serial_stop_rx(struct uart_port *port) +{ + if (rx_enabled(port)) { + dbg("s3c24xx_serial_stop_rx: port=%p\n", port); + disable_irq(RX_IRQ(port)); + rx_enabled(port) = 0; + } +} + +static void s3c24xx_serial_enable_ms(struct uart_port *port) +{ +} + +static inline struct s3c24xx_uart_info *s3c24xx_port_to_info(struct uart_port *port) +{ + return to_ourport(port)->info; +} + +static inline struct s3c2410_uartcfg *s3c24xx_port_to_cfg(struct uart_port *port) +{ + if (port->dev == NULL) + return NULL; + + return (struct s3c2410_uartcfg *)port->dev->platform_data; +} + +static int s3c24xx_serial_rx_fifocnt(struct s3c24xx_uart_port *ourport, + unsigned long ufstat) +{ + struct s3c24xx_uart_info *info = ourport->info; + + if (ufstat & info->rx_fifofull) + return info->fifosize; + + return (ufstat & info->rx_fifomask) >> info->rx_fifoshift; +} + + +/* ? - where has parity gone?? */ +#define S3C2410_UERSTAT_PARITY (0x1000) + +static irqreturn_t +s3c24xx_serial_rx_chars(int irq, void *dev_id) +{ + struct s3c24xx_uart_port *ourport = dev_id; + struct uart_port *port = &ourport->port; + struct tty_struct *tty = port->info->tty; + unsigned int ufcon, ch, flag, ufstat, uerstat; + int max_count = 64; + + while (max_count-- > 0) { + ufcon = rd_regl(port, S3C2410_UFCON); + ufstat = rd_regl(port, S3C2410_UFSTAT); + + if (s3c24xx_serial_rx_fifocnt(ourport, ufstat) == 0) + break; + + uerstat = rd_regl(port, S3C2410_UERSTAT); + ch = rd_regb(port, S3C2410_URXH); + + if (port->flags & UPF_CONS_FLOW) { + int txe = s3c24xx_serial_txempty_nofifo(port); + + if (rx_enabled(port)) { + if (!txe) { + rx_enabled(port) = 0; + continue; + } + } else { + if (txe) { + ufcon |= S3C2410_UFCON_RESETRX; + wr_regl(port, S3C2410_UFCON, ufcon); + rx_enabled(port) = 1; + goto out; + } + continue; + } + } + + /* insert the character into the buffer */ + + flag = TTY_NORMAL; + port->icount.rx++; + + if (unlikely(uerstat & S3C2410_UERSTAT_ANY)) { + dbg("rxerr: port ch=0x%02x, rxs=0x%08x\n", + ch, uerstat); + + /* check for break */ + if (uerstat & S3C2410_UERSTAT_BREAK) { + dbg("break!\n"); + port->icount.brk++; + if (uart_handle_break(port)) + goto ignore_char; + } + + if (uerstat & S3C2410_UERSTAT_FRAME) + port->icount.frame++; + if (uerstat & S3C2410_UERSTAT_OVERRUN) + port->icount.overrun++; + + uerstat &= port->read_status_mask; + + if (uerstat & S3C2410_UERSTAT_BREAK) + flag = TTY_BREAK; + else if (uerstat & S3C2410_UERSTAT_PARITY) + flag = TTY_PARITY; + else if (uerstat & (S3C2410_UERSTAT_FRAME | + S3C2410_UERSTAT_OVERRUN)) + flag = TTY_FRAME; + } + + if (uart_handle_sysrq_char(port, ch)) + goto ignore_char; + + uart_insert_char(port, uerstat, S3C2410_UERSTAT_OVERRUN, + ch, flag); + + ignore_char: + continue; + } + tty_flip_buffer_push(tty); + + out: + return IRQ_HANDLED; +} + +static irqreturn_t s3c24xx_serial_tx_chars(int irq, void *id) +{ + struct s3c24xx_uart_port *ourport = id; + struct uart_port *port = &ourport->port; + struct circ_buf *xmit = &port->info->xmit; + int count = 256; + + if (port->x_char) { + wr_regb(port, S3C2410_UTXH, port->x_char); + port->icount.tx++; + port->x_char = 0; + goto out; + } + + /* if there isnt anything more to transmit, or the uart is now + * stopped, disable the uart and exit + */ + + if (uart_circ_empty(xmit) || uart_tx_stopped(port)) { + s3c24xx_serial_stop_tx(port); + goto out; + } + + /* try and drain the buffer... */ + + while (!uart_circ_empty(xmit) && count-- > 0) { + if (rd_regl(port, S3C2410_UFSTAT) & ourport->info->tx_fifofull) + break; + + wr_regb(port, S3C2410_UTXH, xmit->buf[xmit->tail]); + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); + port->icount.tx++; + } + + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + uart_write_wakeup(port); + + if (uart_circ_empty(xmit)) + s3c24xx_serial_stop_tx(port); + + out: + return IRQ_HANDLED; +} + +static unsigned int s3c24xx_serial_tx_empty(struct uart_port *port) +{ + struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port); + unsigned long ufstat = rd_regl(port, S3C2410_UFSTAT); + unsigned long ufcon = rd_regl(port, S3C2410_UFCON); + + if (ufcon & S3C2410_UFCON_FIFOMODE) { + if ((ufstat & info->tx_fifomask) != 0 || + (ufstat & info->tx_fifofull)) + return 0; + + return 1; + } + + return s3c24xx_serial_txempty_nofifo(port); +} + +/* no modem control lines */ +static unsigned int s3c24xx_serial_get_mctrl(struct uart_port *port) +{ + unsigned int umstat = rd_regb(port, S3C2410_UMSTAT); + + if (umstat & S3C2410_UMSTAT_CTS) + return TIOCM_CAR | TIOCM_DSR | TIOCM_CTS; + else + return TIOCM_CAR | TIOCM_DSR; +} + +static void s3c24xx_serial_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ + /* todo - possibly remove AFC and do manual CTS */ +} + +static void s3c24xx_serial_break_ctl(struct uart_port *port, int break_state) +{ + unsigned long flags; + unsigned int ucon; + + spin_lock_irqsave(&port->lock, flags); + + ucon = rd_regl(port, S3C2410_UCON); + + if (break_state) + ucon |= S3C2410_UCON_SBREAK; + else + ucon &= ~S3C2410_UCON_SBREAK; + + wr_regl(port, S3C2410_UCON, ucon); + + spin_unlock_irqrestore(&port->lock, flags); +} + +static void s3c24xx_serial_shutdown(struct uart_port *port) +{ + struct s3c24xx_uart_port *ourport = to_ourport(port); + + if (ourport->tx_claimed) { + free_irq(TX_IRQ(port), ourport); + tx_enabled(port) = 0; + ourport->tx_claimed = 0; + } + + if (ourport->rx_claimed) { + free_irq(RX_IRQ(port), ourport); + ourport->rx_claimed = 0; + rx_enabled(port) = 0; + } +} + + +static int s3c24xx_serial_startup(struct uart_port *port) +{ + struct s3c24xx_uart_port *ourport = to_ourport(port); + int ret; + + dbg("s3c24xx_serial_startup: port=%p (%08lx,%p)\n", + port->mapbase, port->membase); + + rx_enabled(port) = 1; + + ret = request_irq(RX_IRQ(port), + s3c24xx_serial_rx_chars, 0, + s3c24xx_serial_portname(port), ourport); + + if (ret != 0) { + printk(KERN_ERR "cannot get irq %d\n", RX_IRQ(port)); + return ret; + } + + ourport->rx_claimed = 1; + + dbg("requesting tx irq...\n"); + + tx_enabled(port) = 1; + + ret = request_irq(TX_IRQ(port), + s3c24xx_serial_tx_chars, 0, + s3c24xx_serial_portname(port), ourport); + + if (ret) { + printk(KERN_ERR "cannot get irq %d\n", TX_IRQ(port)); + goto err; + } + + ourport->tx_claimed = 1; + + dbg("s3c24xx_serial_startup ok\n"); + + /* the port reset code should have done the correct + * register setup for the port controls */ + + return ret; + + err: + s3c24xx_serial_shutdown(port); + return ret; +} + +/* power power management control */ + +static void s3c24xx_serial_pm(struct uart_port *port, unsigned int level, + unsigned int old) +{ + struct s3c24xx_uart_port *ourport = to_ourport(port); + + switch (level) { + case 3: + if (!IS_ERR(ourport->baudclk) && ourport->baudclk != NULL) + clk_disable(ourport->baudclk); + + clk_disable(ourport->clk); + break; + + case 0: + clk_enable(ourport->clk); + + if (!IS_ERR(ourport->baudclk) && ourport->baudclk != NULL) + clk_enable(ourport->baudclk); + + break; + default: + printk(KERN_ERR "s3c24xx_serial: unknown pm %d\n", level); + } +} + +/* baud rate calculation + * + * The UARTs on the S3C2410/S3C2440 can take their clocks from a number + * of different sources, including the peripheral clock ("pclk") and an + * external clock ("uclk"). The S3C2440 also adds the core clock ("fclk") + * with a programmable extra divisor. + * + * The following code goes through the clock sources, and calculates the + * baud clocks (and the resultant actual baud rates) and then tries to + * pick the closest one and select that. + * +*/ + + +#define MAX_CLKS (8) + +static struct s3c24xx_uart_clksrc tmp_clksrc = { + .name = "pclk", + .min_baud = 0, + .max_baud = 0, + .divisor = 1, +}; + +static inline int +s3c24xx_serial_getsource(struct uart_port *port, struct s3c24xx_uart_clksrc *c) +{ + struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port); + + return (info->get_clksrc)(port, c); +} + +static inline int +s3c24xx_serial_setsource(struct uart_port *port, struct s3c24xx_uart_clksrc *c) +{ + struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port); + + return (info->set_clksrc)(port, c); +} + +struct baud_calc { + struct s3c24xx_uart_clksrc *clksrc; + unsigned int calc; + unsigned int quot; + struct clk *src; +}; + +static int s3c24xx_serial_calcbaud(struct baud_calc *calc, + struct uart_port *port, + struct s3c24xx_uart_clksrc *clksrc, + unsigned int baud) +{ + unsigned long rate; + + calc->src = clk_get(port->dev, clksrc->name); + if (calc->src == NULL || IS_ERR(calc->src)) + return 0; + + rate = clk_get_rate(calc->src); + rate /= clksrc->divisor; + + calc->clksrc = clksrc; + calc->quot = (rate + (8 * baud)) / (16 * baud); + calc->calc = (rate / (calc->quot * 16)); + + calc->quot--; + return 1; +} + +static unsigned int s3c24xx_serial_getclk(struct uart_port *port, + struct s3c24xx_uart_clksrc **clksrc, + struct clk **clk, + unsigned int baud) +{ + struct s3c2410_uartcfg *cfg = s3c24xx_port_to_cfg(port); + struct s3c24xx_uart_clksrc *clkp; + struct baud_calc res[MAX_CLKS]; + struct baud_calc *resptr, *best, *sptr; + int i; + + clkp = cfg->clocks; + best = NULL; + + if (cfg->clocks_size < 2) { + if (cfg->clocks_size == 0) + clkp = &tmp_clksrc; + + /* check to see if we're sourcing fclk, and if so we're + * going to have to update the clock source + */ + + if (strcmp(clkp->name, "fclk") == 0) { + struct s3c24xx_uart_clksrc src; + + s3c24xx_serial_getsource(port, &src); + + /* check that the port already using fclk, and if + * not, then re-select fclk + */ + + if (strcmp(src.name, clkp->name) == 0) { + s3c24xx_serial_setsource(port, clkp); + s3c24xx_serial_getsource(port, &src); + } + + clkp->divisor = src.divisor; + } + + s3c24xx_serial_calcbaud(res, port, clkp, baud); + best = res; + resptr = best + 1; + } else { + resptr = res; + + for (i = 0; i < cfg->clocks_size; i++, clkp++) { + if (s3c24xx_serial_calcbaud(resptr, port, clkp, baud)) + resptr++; + } + } + + /* ok, we now need to select the best clock we found */ + + if (!best) { + unsigned int deviation = (1<<30)|((1<<30)-1); + int calc_deviation; + + for (sptr = res; sptr < resptr; sptr++) { + calc_deviation = baud - sptr->calc; + if (calc_deviation < 0) + calc_deviation = -calc_deviation; + + if (calc_deviation < deviation) { + best = sptr; + deviation = calc_deviation; + } + } + } + + /* store results to pass back */ + + *clksrc = best->clksrc; + *clk = best->src; + + return best->quot; +} + +static void s3c24xx_serial_set_termios(struct uart_port *port, + struct ktermios *termios, + struct ktermios *old) +{ + struct s3c2410_uartcfg *cfg = s3c24xx_port_to_cfg(port); + struct s3c24xx_uart_port *ourport = to_ourport(port); + struct s3c24xx_uart_clksrc *clksrc = NULL; + struct clk *clk = NULL; + unsigned long flags; + unsigned int baud, quot; + unsigned int ulcon; + unsigned int umcon; + + /* + * We don't support modem control lines. + */ + termios->c_cflag &= ~(HUPCL | CMSPAR); + termios->c_cflag |= CLOCAL; + + /* + * Ask the core to calculate the divisor for us. + */ + + baud = uart_get_baud_rate(port, termios, old, 0, 115200*8); + + if (baud == 38400 && (port->flags & UPF_SPD_MASK) == UPF_SPD_CUST) + quot = port->custom_divisor; + else + quot = s3c24xx_serial_getclk(port, &clksrc, &clk, baud); + + /* check to see if we need to change clock source */ + + if (ourport->clksrc != clksrc || ourport->baudclk != clk) { + s3c24xx_serial_setsource(port, clksrc); + + if (ourport->baudclk != NULL && !IS_ERR(ourport->baudclk)) { + clk_disable(ourport->baudclk); + ourport->baudclk = NULL; + } + + clk_enable(clk); + + ourport->clksrc = clksrc; + ourport->baudclk = clk; + } + + switch (termios->c_cflag & CSIZE) { + case CS5: + dbg("config: 5bits/char\n"); + ulcon = S3C2410_LCON_CS5; + break; + case CS6: + dbg("config: 6bits/char\n"); + ulcon = S3C2410_LCON_CS6; + break; + case CS7: + dbg("config: 7bits/char\n"); + ulcon = S3C2410_LCON_CS7; + break; + case CS8: + default: + dbg("config: 8bits/char\n"); + ulcon = S3C2410_LCON_CS8; + break; + } + + /* preserve original lcon IR settings */ + ulcon |= (cfg->ulcon & S3C2410_LCON_IRM); + + if (termios->c_cflag & CSTOPB) + ulcon |= S3C2410_LCON_STOPB; + + umcon = (termios->c_cflag & CRTSCTS) ? S3C2410_UMCOM_AFC : 0; + + if (termios->c_cflag & PARENB) { + if (termios->c_cflag & PARODD) + ulcon |= S3C2410_LCON_PODD; + else + ulcon |= S3C2410_LCON_PEVEN; + } else { + ulcon |= S3C2410_LCON_PNONE; + } + + spin_lock_irqsave(&port->lock, flags); + + dbg("setting ulcon to %08x, brddiv to %d\n", ulcon, quot); + + wr_regl(port, S3C2410_ULCON, ulcon); + wr_regl(port, S3C2410_UBRDIV, quot); + wr_regl(port, S3C2410_UMCON, umcon); + + dbg("uart: ulcon = 0x%08x, ucon = 0x%08x, ufcon = 0x%08x\n", + rd_regl(port, S3C2410_ULCON), + rd_regl(port, S3C2410_UCON), + rd_regl(port, S3C2410_UFCON)); + + /* + * Update the per-port timeout. + */ + uart_update_timeout(port, termios->c_cflag, baud); + + /* + * Which character status flags are we interested in? + */ + port->read_status_mask = S3C2410_UERSTAT_OVERRUN; + if (termios->c_iflag & INPCK) + port->read_status_mask |= S3C2410_UERSTAT_FRAME | S3C2410_UERSTAT_PARITY; + + /* + * Which character status flags should we ignore? + */ + port->ignore_status_mask = 0; + if (termios->c_iflag & IGNPAR) + port->ignore_status_mask |= S3C2410_UERSTAT_OVERRUN; + if (termios->c_iflag & IGNBRK && termios->c_iflag & IGNPAR) + port->ignore_status_mask |= S3C2410_UERSTAT_FRAME; + + /* + * Ignore all characters if CREAD is not set. + */ + if ((termios->c_cflag & CREAD) == 0) + port->ignore_status_mask |= RXSTAT_DUMMY_READ; + + spin_unlock_irqrestore(&port->lock, flags); +} + +static const char *s3c24xx_serial_type(struct uart_port *port) +{ + switch (port->type) { + case PORT_S3C2410: + return "S3C2410"; + case PORT_S3C2440: + return "S3C2440"; + case PORT_S3C2412: + return "S3C2412"; + default: + return NULL; + } +} + +#define MAP_SIZE (0x100) + +static void s3c24xx_serial_release_port(struct uart_port *port) +{ + release_mem_region(port->mapbase, MAP_SIZE); +} + +static int s3c24xx_serial_request_port(struct uart_port *port) +{ + const char *name = s3c24xx_serial_portname(port); + return request_mem_region(port->mapbase, MAP_SIZE, name) ? 0 : -EBUSY; +} + +static void s3c24xx_serial_config_port(struct uart_port *port, int flags) +{ + struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port); + + if (flags & UART_CONFIG_TYPE && + s3c24xx_serial_request_port(port) == 0) + port->type = info->type; +} + +/* + * verify the new serial_struct (for TIOCSSERIAL). + */ +static int +s3c24xx_serial_verify_port(struct uart_port *port, struct serial_struct *ser) +{ + struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port); + + if (ser->type != PORT_UNKNOWN && ser->type != info->type) + return -EINVAL; + + return 0; +} + + +#ifdef CONFIG_SERIAL_SAMSUNG_CONSOLE + +static struct console s3c24xx_serial_console; + +#define S3C24XX_SERIAL_CONSOLE &s3c24xx_serial_console +#else +#define S3C24XX_SERIAL_CONSOLE NULL +#endif + +static struct uart_ops s3c24xx_serial_ops = { + .pm = s3c24xx_serial_pm, + .tx_empty = s3c24xx_serial_tx_empty, + .get_mctrl = s3c24xx_serial_get_mctrl, + .set_mctrl = s3c24xx_serial_set_mctrl, + .stop_tx = s3c24xx_serial_stop_tx, + .start_tx = s3c24xx_serial_start_tx, + .stop_rx = s3c24xx_serial_stop_rx, + .enable_ms = s3c24xx_serial_enable_ms, + .break_ctl = s3c24xx_serial_break_ctl, + .startup = s3c24xx_serial_startup, + .shutdown = s3c24xx_serial_shutdown, + .set_termios = s3c24xx_serial_set_termios, + .type = s3c24xx_serial_type, + .release_port = s3c24xx_serial_release_port, + .request_port = s3c24xx_serial_request_port, + .config_port = s3c24xx_serial_config_port, + .verify_port = s3c24xx_serial_verify_port, +}; + + +static struct uart_driver s3c24xx_uart_drv = { + .owner = THIS_MODULE, + .dev_name = "s3c2410_serial", + .nr = 3, + .cons = S3C24XX_SERIAL_CONSOLE, + .driver_name = S3C24XX_SERIAL_NAME, + .major = S3C24XX_SERIAL_MAJOR, + .minor = S3C24XX_SERIAL_MINOR, +}; + +static struct s3c24xx_uart_port s3c24xx_serial_ports[NR_PORTS] = { + [0] = { + .port = { + .lock = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[0].port.lock), + .iotype = UPIO_MEM, + .irq = IRQ_S3CUART_RX0, + .uartclk = 0, + .fifosize = 16, + .ops = &s3c24xx_serial_ops, + .flags = UPF_BOOT_AUTOCONF, + .line = 0, + } + }, + [1] = { + .port = { + .lock = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[1].port.lock), + .iotype = UPIO_MEM, + .irq = IRQ_S3CUART_RX1, + .uartclk = 0, + .fifosize = 16, + .ops = &s3c24xx_serial_ops, + .flags = UPF_BOOT_AUTOCONF, + .line = 1, + } + }, +#if NR_PORTS > 2 + + [2] = { + .port = { + .lock = __SPIN_LOCK_UNLOCKED(s3c24xx_serial_ports[2].port.lock), + .iotype = UPIO_MEM, + .irq = IRQ_S3CUART_RX2, + .uartclk = 0, + .fifosize = 16, + .ops = &s3c24xx_serial_ops, + .flags = UPF_BOOT_AUTOCONF, + .line = 2, + } + } +#endif +}; + +/* s3c24xx_serial_resetport + * + * wrapper to call the specific reset for this port (reset the fifos + * and the settings) +*/ + +static inline int s3c24xx_serial_resetport(struct uart_port *port, + struct s3c2410_uartcfg *cfg) +{ + struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port); + + return (info->reset_port)(port, cfg); +} + +/* s3c24xx_serial_init_port + * + * initialise a single serial port from the platform device given + */ + +static int s3c24xx_serial_init_port(struct s3c24xx_uart_port *ourport, + struct s3c24xx_uart_info *info, + struct platform_device *platdev) +{ + struct uart_port *port = &ourport->port; + struct s3c2410_uartcfg *cfg; + struct resource *res; + int ret; + + dbg("s3c24xx_serial_init_port: port=%p, platdev=%p\n", port, platdev); + + if (platdev == NULL) + return -ENODEV; + + cfg = s3c24xx_dev_to_cfg(&platdev->dev); + + if (port->mapbase != 0) + return 0; + + if (cfg->hwport > 3) + return -EINVAL; + + /* setup info for port */ + port->dev = &platdev->dev; + ourport->info = info; + + /* copy the info in from provided structure */ + ourport->port.fifosize = info->fifosize; + + dbg("s3c24xx_serial_init_port: %p (hw %d)...\n", port, cfg->hwport); + + port->uartclk = 1; + + if (cfg->uart_flags & UPF_CONS_FLOW) { + dbg("s3c24xx_serial_init_port: enabling flow control\n"); + port->flags |= UPF_CONS_FLOW; + } + + /* sort our the physical and virtual addresses for each UART */ + + res = platform_get_resource(platdev, IORESOURCE_MEM, 0); + if (res == NULL) { + printk(KERN_ERR "failed to find memory resource for uart\n"); + return -EINVAL; + } + + dbg("resource %p (%lx..%lx)\n", res, res->start, res->end); + + port->mapbase = res->start; + port->membase = S3C24XX_VA_UART + (res->start - S3C24XX_PA_UART); + ret = platform_get_irq(platdev, 0); + if (ret < 0) + port->irq = 0; + else + port->irq = ret; + + ourport->clk = clk_get(&platdev->dev, "uart"); + + dbg("port: map=%08x, mem=%08x, irq=%d, clock=%ld\n", + port->mapbase, port->membase, port->irq, port->uartclk); + + /* reset the fifos (and setup the uart) */ + s3c24xx_serial_resetport(port, cfg); + return 0; +} + +static ssize_t s3c24xx_serial_show_clksrc(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct uart_port *port = s3c24xx_dev_to_port(dev); + struct s3c24xx_uart_port *ourport = to_ourport(port); + + return snprintf(buf, PAGE_SIZE, "* %s\n", ourport->clksrc->name); +} + +static DEVICE_ATTR(clock_source, S_IRUGO, s3c24xx_serial_show_clksrc, NULL); + +/* Device driver serial port probe */ + +static int probe_index; + +int s3c24xx_serial_probe(struct platform_device *dev, + struct s3c24xx_uart_info *info) +{ + struct s3c24xx_uart_port *ourport; + int ret; + + dbg("s3c24xx_serial_probe(%p, %p) %d\n", dev, info, probe_index); + + ourport = &s3c24xx_serial_ports[probe_index]; + probe_index++; + + dbg("%s: initialising port %p...\n", __func__, ourport); + + ret = s3c24xx_serial_init_port(ourport, info, dev); + if (ret < 0) + goto probe_err; + + dbg("%s: adding port\n", __func__); + uart_add_one_port(&s3c24xx_uart_drv, &ourport->port); + platform_set_drvdata(dev, &ourport->port); + + ret = device_create_file(&dev->dev, &dev_attr_clock_source); + if (ret < 0) + printk(KERN_ERR "%s: failed to add clksrc attr.\n", __func__); + + return 0; + + probe_err: + return ret; +} + +EXPORT_SYMBOL_GPL(s3c24xx_serial_probe); + +int s3c24xx_serial_remove(struct platform_device *dev) +{ + struct uart_port *port = s3c24xx_dev_to_port(&dev->dev); + + if (port) { + device_remove_file(&dev->dev, &dev_attr_clock_source); + uart_remove_one_port(&s3c24xx_uart_drv, port); + } + + return 0; +} + +EXPORT_SYMBOL_GPL(s3c24xx_serial_remove); + +/* UART power management code */ + +#ifdef CONFIG_PM + +static int s3c24xx_serial_suspend(struct platform_device *dev, pm_message_t state) +{ + struct uart_port *port = s3c24xx_dev_to_port(&dev->dev); + + if (port) + uart_suspend_port(&s3c24xx_uart_drv, port); + + return 0; +} + +static int s3c24xx_serial_resume(struct platform_device *dev) +{ + struct uart_port *port = s3c24xx_dev_to_port(&dev->dev); + struct s3c24xx_uart_port *ourport = to_ourport(port); + + if (port) { + clk_enable(ourport->clk); + s3c24xx_serial_resetport(port, s3c24xx_port_to_cfg(port)); + clk_disable(ourport->clk); + + uart_resume_port(&s3c24xx_uart_drv, port); + } + + return 0; +} +#endif + +int s3c24xx_serial_init(struct platform_driver *drv, + struct s3c24xx_uart_info *info) +{ + dbg("s3c24xx_serial_init(%p,%p)\n", drv, info); + +#ifdef CONFIG_PM + drv->suspend = s3c24xx_serial_suspend; + drv->resume = s3c24xx_serial_resume; +#endif + + return platform_driver_register(drv); +} + +EXPORT_SYMBOL_GPL(s3c24xx_serial_init); + +/* module initialisation code */ + +static int __init s3c24xx_serial_modinit(void) +{ + int ret; + + ret = uart_register_driver(&s3c24xx_uart_drv); + if (ret < 0) { + printk(KERN_ERR "failed to register UART driver\n"); + return -1; + } + + return 0; +} + +static void __exit s3c24xx_serial_modexit(void) +{ + uart_unregister_driver(&s3c24xx_uart_drv); +} + +module_init(s3c24xx_serial_modinit); +module_exit(s3c24xx_serial_modexit); + +/* Console code */ + +#ifdef CONFIG_SERIAL_SAMSUNG_CONSOLE + +static struct uart_port *cons_uart; + +static int +s3c24xx_serial_console_txrdy(struct uart_port *port, unsigned int ufcon) +{ + struct s3c24xx_uart_info *info = s3c24xx_port_to_info(port); + unsigned long ufstat, utrstat; + + if (ufcon & S3C2410_UFCON_FIFOMODE) { + /* fifo mode - check ammount of data in fifo registers... */ + + ufstat = rd_regl(port, S3C2410_UFSTAT); + return (ufstat & info->tx_fifofull) ? 0 : 1; + } + + /* in non-fifo mode, we go and use the tx buffer empty */ + + utrstat = rd_regl(port, S3C2410_UTRSTAT); + return (utrstat & S3C2410_UTRSTAT_TXE) ? 1 : 0; +} + +static void +s3c24xx_serial_console_putchar(struct uart_port *port, int ch) +{ + unsigned int ufcon = rd_regl(cons_uart, S3C2410_UFCON); + while (!s3c24xx_serial_console_txrdy(port, ufcon)) + barrier(); + wr_regb(cons_uart, S3C2410_UTXH, ch); +} + +static void +s3c24xx_serial_console_write(struct console *co, const char *s, + unsigned int count) +{ + uart_console_write(cons_uart, s, count, s3c24xx_serial_console_putchar); +} + +static void __init +s3c24xx_serial_get_options(struct uart_port *port, int *baud, + int *parity, int *bits) +{ + struct s3c24xx_uart_clksrc clksrc; + struct clk *clk; + unsigned int ulcon; + unsigned int ucon; + unsigned int ubrdiv; + unsigned long rate; + + ulcon = rd_regl(port, S3C2410_ULCON); + ucon = rd_regl(port, S3C2410_UCON); + ubrdiv = rd_regl(port, S3C2410_UBRDIV); + + dbg("s3c24xx_serial_get_options: port=%p\n" + "registers: ulcon=%08x, ucon=%08x, ubdriv=%08x\n", + port, ulcon, ucon, ubrdiv); + + if ((ucon & 0xf) != 0) { + /* consider the serial port configured if the tx/rx mode set */ + + switch (ulcon & S3C2410_LCON_CSMASK) { + case S3C2410_LCON_CS5: + *bits = 5; + break; + case S3C2410_LCON_CS6: + *bits = 6; + break; + case S3C2410_LCON_CS7: + *bits = 7; + break; + default: + case S3C2410_LCON_CS8: + *bits = 8; + break; + } + + switch (ulcon & S3C2410_LCON_PMASK) { + case S3C2410_LCON_PEVEN: + *parity = 'e'; + break; + + case S3C2410_LCON_PODD: + *parity = 'o'; + break; + + case S3C2410_LCON_PNONE: + default: + *parity = 'n'; + } + + /* now calculate the baud rate */ + + s3c24xx_serial_getsource(port, &clksrc); + + clk = clk_get(port->dev, clksrc.name); + if (!IS_ERR(clk) && clk != NULL) + rate = clk_get_rate(clk) / clksrc.divisor; + else + rate = 1; + + + *baud = rate / (16 * (ubrdiv + 1)); + dbg("calculated baud %d\n", *baud); + } + +} + +/* s3c24xx_serial_init_ports + * + * initialise the serial ports from the machine provided initialisation + * data. +*/ + +static int s3c24xx_serial_init_ports(struct s3c24xx_uart_info *info) +{ + struct s3c24xx_uart_port *ptr = s3c24xx_serial_ports; + struct platform_device **platdev_ptr; + int i; + + dbg("s3c24xx_serial_init_ports: initialising ports...\n"); + + platdev_ptr = s3c24xx_uart_devs; + + for (i = 0; i < NR_PORTS; i++, ptr++, platdev_ptr++) { + s3c24xx_serial_init_port(ptr, info, *platdev_ptr); + } + + return 0; +} + +static int __init +s3c24xx_serial_console_setup(struct console *co, char *options) +{ + struct uart_port *port; + int baud = 9600; + int bits = 8; + int parity = 'n'; + int flow = 'n'; + + dbg("s3c24xx_serial_console_setup: co=%p (%d), %s\n", + co, co->index, options); + + /* is this a valid port */ + + if (co->index == -1 || co->index >= NR_PORTS) + co->index = 0; + + port = &s3c24xx_serial_ports[co->index].port; + + /* is the port configured? */ + + if (port->mapbase == 0x0) { + co->index = 0; + port = &s3c24xx_serial_ports[co->index].port; + } + + cons_uart = port; + + dbg("s3c24xx_serial_console_setup: port=%p (%d)\n", port, co->index); + + /* + * Check whether an invalid uart number has been specified, and + * if so, search for the first available port that does have + * console support. + */ + if (options) + uart_parse_options(options, &baud, &parity, &bits, &flow); + else + s3c24xx_serial_get_options(port, &baud, &parity, &bits); + + dbg("s3c24xx_serial_console_setup: baud %d\n", baud); + + return uart_set_options(port, co, baud, parity, bits, flow); +} + +/* s3c24xx_serial_initconsole + * + * initialise the console from one of the uart drivers +*/ + +static struct console s3c24xx_serial_console = { + .name = S3C24XX_SERIAL_NAME, + .device = uart_console_device, + .flags = CON_PRINTBUFFER, + .index = -1, + .write = s3c24xx_serial_console_write, + .setup = s3c24xx_serial_console_setup +}; + +int s3c24xx_serial_initconsole(struct platform_driver *drv, + struct s3c24xx_uart_info *info) + +{ + struct platform_device *dev = s3c24xx_uart_devs[0]; + + dbg("s3c24xx_serial_initconsole\n"); + + /* select driver based on the cpu */ + + if (dev == NULL) { + printk(KERN_ERR "s3c24xx: no devices for console init\n"); + return 0; + } + + if (strcmp(dev->name, drv->driver.name) != 0) + return 0; + + s3c24xx_serial_console.data = &s3c24xx_uart_drv; + s3c24xx_serial_init_ports(info); + + register_console(&s3c24xx_serial_console); + return 0; +} + +#endif /* CONFIG_SERIAL_SAMSUNG_CONSOLE */ + +MODULE_DESCRIPTION("Samsung SoC Serial port driver"); +MODULE_AUTHOR("Ben Dooks "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/serial/samsung.h b/drivers/serial/samsung.h new file mode 100644 index 000000000000..5c92ebbe7d9e --- /dev/null +++ b/drivers/serial/samsung.h @@ -0,0 +1,102 @@ +/* linux/drivers/serial/samsung.h + * + * Driver for Samsung SoC onboard UARTs. + * + * Ben Dooks, Copyright (c) 2003-2005,2008 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * + * 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. +*/ + +struct s3c24xx_uart_info { + char *name; + unsigned int type; + unsigned int fifosize; + unsigned long rx_fifomask; + unsigned long rx_fifoshift; + unsigned long rx_fifofull; + unsigned long tx_fifomask; + unsigned long tx_fifoshift; + unsigned long tx_fifofull; + + /* clock source control */ + + int (*get_clksrc)(struct uart_port *, struct s3c24xx_uart_clksrc *clk); + int (*set_clksrc)(struct uart_port *, struct s3c24xx_uart_clksrc *clk); + + /* uart controls */ + int (*reset_port)(struct uart_port *, struct s3c2410_uartcfg *); +}; + +struct s3c24xx_uart_port { + unsigned char rx_claimed; + unsigned char tx_claimed; + + struct s3c24xx_uart_info *info; + struct s3c24xx_uart_clksrc *clksrc; + struct clk *clk; + struct clk *baudclk; + struct uart_port port; +}; + +/* conversion functions */ + +#define s3c24xx_dev_to_port(__dev) (struct uart_port *)dev_get_drvdata(__dev) +#define s3c24xx_dev_to_cfg(__dev) (struct s3c2410_uartcfg *)((__dev)->platform_data) + +/* register access controls */ + +#define portaddr(port, reg) ((port)->membase + (reg)) + +#define rd_regb(port, reg) (__raw_readb(portaddr(port, reg))) +#define rd_regl(port, reg) (__raw_readl(portaddr(port, reg))) + +#define wr_regb(port, reg, val) __raw_writeb(val, portaddr(port, reg)) +#define wr_regl(port, reg, val) __raw_writel(val, portaddr(port, reg)) + +extern int s3c24xx_serial_probe(struct platform_device *dev, + struct s3c24xx_uart_info *uart); + +extern int s3c24xx_serial_remove(struct platform_device *dev); + +extern int s3c24xx_serial_initconsole(struct platform_driver *drv, + struct s3c24xx_uart_info *uart); + +extern int s3c24xx_serial_init(struct platform_driver *drv, + struct s3c24xx_uart_info *info); + +#ifdef CONFIG_SERIAL_SAMSUNG_CONSOLE + +#define s3c24xx_console_init(__drv, __inf) \ +static int __init s3c_serial_console_init(void) \ +{ \ + return s3c24xx_serial_initconsole(__drv, __inf); \ +} \ + \ +console_initcall(s3c_serial_console_init) + +#else +#define s3c24xx_console_init(drv, inf) extern void no_console(void) +#endif + +#ifdef CONFIG_SERIAL_SAMSUNG_DEBUG + +extern void printascii(const char *); + +static void dbg(const char *fmt, ...) +{ + va_list va; + char buff[256]; + + va_start(va, fmt); + vsprintf(buff, fmt, va); + va_end(va); + + printascii(buff); +} + +#else +#define dbg(x...) do { } while (0) +#endif -- cgit v1.2.3 From 036bb15ec94216e28cb1550af0fdcdfb90c549df Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Sat, 5 Jul 2008 10:02:44 +0200 Subject: IMX UART: do not assume 16MHz reference frequency We assumed a 16MHz reference frequency for the UART. While this is true for i.MX1 most of the time it is not true for MX27/MX31. Also, add handling for the ONEMS register which is present on newer versions of the chip and pass a sane minimum baudrate to uart_get_baud_rate(). Signed-off-by: Sascha Hauer --- drivers/serial/imx.c | 50 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 39 insertions(+), 11 deletions(-) (limited to 'drivers/serial') diff --git a/drivers/serial/imx.c b/drivers/serial/imx.c index 5a375bf0ebf4..6226e66c7966 100644 --- a/drivers/serial/imx.c +++ b/drivers/serial/imx.c @@ -589,6 +589,7 @@ imx_set_termios(struct uart_port *port, struct ktermios *termios, unsigned long flags; unsigned int ucr2, old_ucr1, old_txrxen, baud, quot; unsigned int old_csize = old ? old->c_cflag & CSIZE : CS8; + unsigned int div, num, denom, ufcr; /* * If we don't support modem control lines, don't allow @@ -634,7 +635,7 @@ imx_set_termios(struct uart_port *port, struct ktermios *termios, /* * Ask the core to calculate the divisor for us. */ - baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk/16); + baud = uart_get_baud_rate(port, termios, old, 50, port->uartclk / 16); quot = uart_get_divisor(port, baud); spin_lock_irqsave(&sport->port.lock, flags); @@ -684,14 +685,41 @@ imx_set_termios(struct uart_port *port, struct ktermios *termios, sport->port.membase + UCR2); old_txrxen &= (UCR2_TXEN | UCR2_RXEN); - /* set the baud rate. We assume uartclk = 16 MHz - * - * baud * 16 UBIR - 1 - * --------- = -------- - * uartclk UBMR - 1 - */ - writel((baud / 100) - 1, sport->port.membase + UBIR); - writel(10000 - 1, sport->port.membase + UBMR); + div = sport->port.uartclk / (baud * 16); + if (div > 7) + div = 7; + if (!div) + div = 1; + + num = baud; + denom = port->uartclk / div / 16; + + /* shift num and denom right until they fit into 16 bits */ + while (num > 0x10000 || denom > 0x10000) { + num >>= 1; + denom >>= 1; + } + if (num > 0) + num -= 1; + if (denom > 0) + denom -= 1; + + writel(num, sport->port.membase + UBIR); + writel(denom, sport->port.membase + UBMR); + + if (div == 7) + div = 6; /* 6 in RFDIV means divide by 7 */ + else + div = 6 - div; + + ufcr = readl(sport->port.membase + UFCR); + ufcr = (ufcr & (~UFCR_RFDIV)) | + (div << 7); + writel(ufcr, sport->port.membase + UFCR); + +#ifdef ONEMS + writel(sport->port.uartclk / div / 1000, sport->port.membase + ONEMS); +#endif writel(old_ucr1, sport->port.membase + UCR1); @@ -812,7 +840,6 @@ static struct imx_port imx_ports[] = { .membase = (void *)IMX_UART1_BASE, .mapbase = 0x00206000, .irq = UART1_MINT_RX, - .uartclk = 16000000, .fifosize = 32, .flags = UPF_BOOT_AUTOCONF, .ops = &imx_pops, @@ -828,7 +855,6 @@ static struct imx_port imx_ports[] = { .membase = (void *)IMX_UART2_BASE, .mapbase = 0x00207000, .irq = UART2_MINT_RX, - .uartclk = 16000000, .fifosize = 32, .flags = UPF_BOOT_AUTOCONF, .ops = &imx_pops, @@ -858,6 +884,8 @@ static void __init imx_init_ports(void) init_timer(&imx_ports[i].timer); imx_ports[i].timer.function = imx_timeout; imx_ports[i].timer.data = (unsigned long)&imx_ports[i]; + + imx_ports[i].port.uartclk = imx_get_perclk1(); } } -- cgit v1.2.3 From 2582d8c1655f2eda42d5358a242b256fd6e88571 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Sat, 5 Jul 2008 10:02:45 +0200 Subject: IMX UART: Add board specific init/exit functions Add platform specific init functions. Also rename the struct platform_device dev into pdev. Signed-off-by: Sascha Hauer --- drivers/serial/imx.c | 28 +++++++++++++++++++--------- include/asm-arm/arch-imx/imx-uart.h | 2 ++ 2 files changed, 21 insertions(+), 9 deletions(-) (limited to 'drivers/serial') diff --git a/drivers/serial/imx.c b/drivers/serial/imx.c index 6226e66c7966..77968432b81e 100644 --- a/drivers/serial/imx.c +++ b/drivers/serial/imx.c @@ -1078,30 +1078,40 @@ static int serial_imx_resume(struct platform_device *dev) return 0; } -static int serial_imx_probe(struct platform_device *dev) +static int serial_imx_probe(struct platform_device *pdev) { struct imxuart_platform_data *pdata; - imx_ports[dev->id].port.dev = &dev->dev; + imx_ports[pdev->id].port.dev = &pdev->dev; - pdata = (struct imxuart_platform_data *)dev->dev.platform_data; + pdata = pdev->dev.platform_data; if(pdata && (pdata->flags & IMXUART_HAVE_RTSCTS)) - imx_ports[dev->id].have_rtscts = 1; + imx_ports[pdev->id].have_rtscts = 1; + + if (pdata->init) + pdata->init(pdev); + + uart_add_one_port(&imx_reg, &imx_ports[pdev->id].port); + platform_set_drvdata(pdev, &imx_ports[pdev->id]); - uart_add_one_port(&imx_reg, &imx_ports[dev->id].port); - platform_set_drvdata(dev, &imx_ports[dev->id]); return 0; } -static int serial_imx_remove(struct platform_device *dev) +static int serial_imx_remove(struct platform_device *pdev) { - struct imx_port *sport = platform_get_drvdata(dev); + struct imxuart_platform_data *pdata; + struct imx_port *sport = platform_get_drvdata(pdev); - platform_set_drvdata(dev, NULL); + pdata = pdev->dev.platform_data; + + platform_set_drvdata(pdev, NULL); if (sport) uart_remove_one_port(&imx_reg, &sport->port); + if (pdata->exit) + pdata->exit(pdev); + return 0; } diff --git a/include/asm-arm/arch-imx/imx-uart.h b/include/asm-arm/arch-imx/imx-uart.h index 3a685e1780ea..d54eb1d48026 100644 --- a/include/asm-arm/arch-imx/imx-uart.h +++ b/include/asm-arm/arch-imx/imx-uart.h @@ -4,6 +4,8 @@ #define IMXUART_HAVE_RTSCTS (1<<0) struct imxuart_platform_data { + int (*init)(struct platform_device *pdev); + void (*exit)(struct platform_device *pdev); unsigned int flags; }; -- cgit v1.2.3 From dbff4e9ea2e83fda89143389bfb229cb29425a32 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Sat, 5 Jul 2008 10:02:45 +0200 Subject: IMX UART: remove statically initialized tables This patch removes the statically initialized tables from the i.MX serial driver and makes the driver fully dependent on the information provided by the platform_device. Signed-off-by: Sascha Hauer --- arch/arm/mach-imx/mx1ads.c | 10 ++++ drivers/serial/imx.c | 129 +++++++++++++++++++-------------------------- 2 files changed, 64 insertions(+), 75 deletions(-) (limited to 'drivers/serial') diff --git a/arch/arm/mach-imx/mx1ads.c b/arch/arm/mach-imx/mx1ads.c index a9778c1587ab..9635d5812bcd 100644 --- a/arch/arm/mach-imx/mx1ads.c +++ b/arch/arm/mach-imx/mx1ads.c @@ -69,6 +69,11 @@ static struct resource imx_uart1_resources[] = { .end = (UART1_MINT_TX), .flags = IORESOURCE_IRQ, }, + [3] = { + .start = UART1_MINT_RTS, + .end = UART1_MINT_RTS, + .flags = IORESOURCE_IRQ, + }, }; static struct platform_device imx_uart1_device = { @@ -97,6 +102,11 @@ static struct resource imx_uart2_resources[] = { .end = (UART2_MINT_TX), .flags = IORESOURCE_IRQ, }, + [3] = { + .start = UART2_MINT_RTS, + .end = UART2_MINT_RTS, + .flags = IORESOURCE_IRQ, + }, }; static struct platform_device imx_uart2_device = { diff --git a/drivers/serial/imx.c b/drivers/serial/imx.c index 77968432b81e..8d6cb745bd93 100644 --- a/drivers/serial/imx.c +++ b/drivers/serial/imx.c @@ -176,6 +176,8 @@ #define DRIVER_NAME "IMX-uart" +#define UART_NR 8 + struct imx_port { struct uart_port port; struct timer_list timer; @@ -829,65 +831,7 @@ static struct uart_ops imx_pops = { .verify_port = imx_verify_port, }; -static struct imx_port imx_ports[] = { - { - .txirq = UART1_MINT_TX, - .rxirq = UART1_MINT_RX, - .rtsirq = UART1_MINT_RTS, - .port = { - .type = PORT_IMX, - .iotype = UPIO_MEM, - .membase = (void *)IMX_UART1_BASE, - .mapbase = 0x00206000, - .irq = UART1_MINT_RX, - .fifosize = 32, - .flags = UPF_BOOT_AUTOCONF, - .ops = &imx_pops, - .line = 0, - }, - }, { - .txirq = UART2_MINT_TX, - .rxirq = UART2_MINT_RX, - .rtsirq = UART2_MINT_RTS, - .port = { - .type = PORT_IMX, - .iotype = UPIO_MEM, - .membase = (void *)IMX_UART2_BASE, - .mapbase = 0x00207000, - .irq = UART2_MINT_RX, - .fifosize = 32, - .flags = UPF_BOOT_AUTOCONF, - .ops = &imx_pops, - .line = 1, - }, - } -}; - -/* - * Setup the IMX serial ports. - * Note also that we support "console=ttySMXx" where "x" is either 0 or 1. - * Which serial port this ends up being depends on the machine you're - * running this kernel on. I'm not convinced that this is a good idea, - * but that's the way it traditionally works. - * - */ -static void __init imx_init_ports(void) -{ - static int first = 1; - int i; - - if (!first) - return; - first = 0; - - for (i = 0; i < ARRAY_SIZE(imx_ports); i++) { - init_timer(&imx_ports[i].timer); - imx_ports[i].timer.function = imx_timeout; - imx_ports[i].timer.data = (unsigned long)&imx_ports[i]; - - imx_ports[i].port.uartclk = imx_get_perclk1(); - } -} +static struct imx_port *imx_ports[UART_NR]; #ifdef CONFIG_SERIAL_IMX_CONSOLE static void imx_console_putchar(struct uart_port *port, int ch) @@ -906,7 +850,7 @@ static void imx_console_putchar(struct uart_port *port, int ch) static void imx_console_write(struct console *co, const char *s, unsigned int count) { - struct imx_port *sport = &imx_ports[co->index]; + struct imx_port *sport = imx_ports[co->index]; unsigned int old_ucr1, old_ucr2; /* @@ -1012,7 +956,7 @@ imx_console_setup(struct console *co, char *options) */ if (co->index == -1 || co->index >= ARRAY_SIZE(imx_ports)) co->index = 0; - sport = &imx_ports[co->index]; + sport = imx_ports[co->index]; if (options) uart_parse_options(options, &baud, &parity, &bits, &flow); @@ -1035,14 +979,6 @@ static struct console imx_console = { .data = &imx_reg, }; -static int __init imx_rs_console_init(void) -{ - imx_init_ports(); - register_console(&imx_console); - return 0; -} -console_initcall(imx_rs_console_init); - #define IMX_CONSOLE &imx_console #else #define IMX_CONSOLE NULL @@ -1080,21 +1016,63 @@ static int serial_imx_resume(struct platform_device *dev) static int serial_imx_probe(struct platform_device *pdev) { + struct imx_port *sport; struct imxuart_platform_data *pdata; + void __iomem *base; + int ret = 0; + struct resource *res; + + sport = kzalloc(sizeof(*sport), GFP_KERNEL); + if (!sport) + return -ENOMEM; - imx_ports[pdev->id].port.dev = &pdev->dev; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = -ENODEV; + goto free; + } + + base = ioremap(res->start, PAGE_SIZE); + if (!base) { + ret = -ENOMEM; + goto free; + } + + sport->port.dev = &pdev->dev; + sport->port.mapbase = res->start; + sport->port.membase = base; + sport->port.type = PORT_IMX, + sport->port.iotype = UPIO_MEM; + sport->port.irq = platform_get_irq(pdev, 0); + sport->rxirq = platform_get_irq(pdev, 0); + sport->txirq = platform_get_irq(pdev, 1); + sport->rtsirq = platform_get_irq(pdev, 2); + sport->port.fifosize = 32; + sport->port.ops = &imx_pops; + sport->port.flags = UPF_BOOT_AUTOCONF; + sport->port.line = pdev->id; + init_timer(&sport->timer); + sport->timer.function = imx_timeout; + sport->timer.data = (unsigned long)sport; + sport->port.uartclk = imx_get_perclk1(); + + imx_ports[pdev->id] = sport; pdata = pdev->dev.platform_data; if(pdata && (pdata->flags & IMXUART_HAVE_RTSCTS)) - imx_ports[pdev->id].have_rtscts = 1; + sport->have_rtscts = 1; if (pdata->init) pdata->init(pdev); - uart_add_one_port(&imx_reg, &imx_ports[pdev->id].port); - platform_set_drvdata(pdev, &imx_ports[pdev->id]); + uart_add_one_port(&imx_reg, &sport->port); + platform_set_drvdata(pdev, &sport->port); return 0; +free: + kfree(sport); + + return ret; } static int serial_imx_remove(struct platform_device *pdev) @@ -1112,6 +1090,9 @@ static int serial_imx_remove(struct platform_device *pdev) if (pdata->exit) pdata->exit(pdev); + iounmap(sport->port.membase); + kfree(sport); + return 0; } @@ -1133,8 +1114,6 @@ static int __init imx_serial_init(void) printk(KERN_INFO "Serial: IMX driver\n"); - imx_init_ports(); - ret = uart_register_driver(&imx_reg); if (ret) return ret; -- cgit v1.2.3 From 38a41fdf94c449c165213e4665c3f8a0d30f8aba Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Sat, 5 Jul 2008 10:02:46 +0200 Subject: IMX: introduce clock API This patch introduces the clock API for i.MX and converts all in-Kernel drivers to use it. Signed-off-by: Sascha Hauer --- arch/arm/mach-imx/Makefile | 2 +- arch/arm/mach-imx/clock.c | 205 ++++++++++++++++++++++++++++++++++++ arch/arm/mach-imx/cpufreq.c | 20 +++- arch/arm/mach-imx/generic.c | 76 ------------- arch/arm/mach-imx/time.c | 23 ++-- drivers/mmc/host/imxmmc.c | 19 +++- drivers/serial/imx.c | 25 ++++- drivers/spi/spi_imx.c | 38 +++++-- include/asm-arm/arch-imx/hardware.h | 8 -- 9 files changed, 307 insertions(+), 109 deletions(-) create mode 100644 arch/arm/mach-imx/clock.c (limited to 'drivers/serial') diff --git a/arch/arm/mach-imx/Makefile b/arch/arm/mach-imx/Makefile index 88d5e61a2e13..b047c7e795a9 100644 --- a/arch/arm/mach-imx/Makefile +++ b/arch/arm/mach-imx/Makefile @@ -4,7 +4,7 @@ # Object file lists. -obj-y += irq.o time.o dma.o generic.o +obj-y += irq.o time.o dma.o generic.o clock.o obj-$(CONFIG_CPU_FREQ_IMX) += cpufreq.o diff --git a/arch/arm/mach-imx/clock.c b/arch/arm/mach-imx/clock.c new file mode 100644 index 000000000000..6a90fe5578df --- /dev/null +++ b/arch/arm/mach-imx/clock.c @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2008 Sascha Hauer , Pengutronix + * + * 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 +#include +#include +#include +#include + +#include +#include + +/* + * Very simple approach: We can't disable clocks, so we do + * not need refcounting + */ + +struct clk { + struct list_head node; + const char *name; + unsigned long (*get_rate)(void); +}; + +/* + * get the system pll clock in Hz + * + * mfi + mfn / (mfd +1) + * f = 2 * f_ref * -------------------- + * pd + 1 + */ +static unsigned long imx_decode_pll(unsigned int pll, u32 f_ref) +{ + unsigned long long ll; + unsigned long quot; + + u32 mfi = (pll >> 10) & 0xf; + u32 mfn = pll & 0x3ff; + u32 mfd = (pll >> 16) & 0x3ff; + u32 pd = (pll >> 26) & 0xf; + + mfi = mfi <= 5 ? 5 : mfi; + + ll = 2 * (unsigned long long)f_ref * + ((mfi << 16) + (mfn << 16) / (mfd + 1)); + quot = (pd + 1) * (1 << 16); + ll += quot / 2; + do_div(ll, quot); + return (unsigned long)ll; +} + +static unsigned long imx_get_system_clk(void) +{ + u32 f_ref = (CSCR & CSCR_SYSTEM_SEL) ? 16000000 : (CLK32 * 512); + + return imx_decode_pll(SPCTL0, f_ref); +} + +static unsigned long imx_get_mcu_clk(void) +{ + return imx_decode_pll(MPCTL0, CLK32 * 512); +} + +/* + * get peripheral clock 1 ( UART[12], Timer[12], PWM ) + */ +static unsigned long imx_get_perclk1(void) +{ + return imx_get_system_clk() / (((PCDR) & 0xf)+1); +} + +/* + * get peripheral clock 2 ( LCD, SD, SPI[12] ) + */ +static unsigned long imx_get_perclk2(void) +{ + return imx_get_system_clk() / (((PCDR>>4) & 0xf)+1); +} + +/* + * get peripheral clock 3 ( SSI ) + */ +static unsigned long imx_get_perclk3(void) +{ + return imx_get_system_clk() / (((PCDR>>16) & 0x7f)+1); +} + +/* + * get hclk ( SDRAM, CSI, Memory Stick, I2C, DMA ) + */ +static unsigned long imx_get_hclk(void) +{ + return imx_get_system_clk() / (((CSCR>>10) & 0xf)+1); +} + +static struct clk clk_system_clk = { + .name = "system_clk", + .get_rate = imx_get_system_clk, +}; + +static struct clk clk_hclk = { + .name = "hclk", + .get_rate = imx_get_hclk, +}; + +static struct clk clk_mcu_clk = { + .name = "mcu_clk", + .get_rate = imx_get_mcu_clk, +}; + +static struct clk clk_perclk1 = { + .name = "perclk1", + .get_rate = imx_get_perclk1, +}; + +static struct clk clk_uart_clk = { + .name = "uart_clk", + .get_rate = imx_get_perclk1, +}; + +static struct clk clk_perclk2 = { + .name = "perclk2", + .get_rate = imx_get_perclk2, +}; + +static struct clk clk_perclk3 = { + .name = "perclk3", + .get_rate = imx_get_perclk3, +}; + +static struct clk *clks[] = { + &clk_perclk1, + &clk_perclk2, + &clk_perclk3, + &clk_system_clk, + &clk_hclk, + &clk_mcu_clk, + &clk_uart_clk, +}; + +static LIST_HEAD(clocks); +static DEFINE_MUTEX(clocks_mutex); + +struct clk *clk_get(struct device *dev, const char *id) +{ + struct clk *p, *clk = ERR_PTR(-ENOENT); + + mutex_lock(&clocks_mutex); + list_for_each_entry(p, &clocks, node) { + if (!strcmp(p->name, id)) { + clk = p; + goto found; + } + } + +found: + mutex_unlock(&clocks_mutex); + + return clk; +} + +void clk_put(struct clk *clk) +{ +} + +int clk_enable(struct clk *clk) +{ + return 0; +} + +void clk_disable(struct clk *clk) +{ +} + +unsigned long clk_get_rate(struct clk *clk) +{ + return clk->get_rate(); +} + +int imx_clocks_init(void) +{ + int i; + + mutex_lock(&clocks_mutex); + for (i = 0; i < ARRAY_SIZE(clks); i++) + list_add(&clks[i]->node, &clocks); + mutex_unlock(&clocks_mutex); + + return 0; +} + diff --git a/arch/arm/mach-imx/cpufreq.c b/arch/arm/mach-imx/cpufreq.c index e548ba74a4d2..be0809b33e08 100644 --- a/arch/arm/mach-imx/cpufreq.c +++ b/arch/arm/mach-imx/cpufreq.c @@ -32,6 +32,8 @@ #include #include #include +#include +#include #include #include @@ -52,6 +54,8 @@ static u32 mpctl0_at_boot; static u32 bclk_div_at_boot; +static struct clk *system_clk, *mcu_clk; + static void imx_set_async_mode(void) { adjust_cr(CR_920T_CLOCK_MODE, CR_920T_ASYNC_MODE); @@ -160,10 +164,10 @@ static unsigned int imx_get_speed(unsigned int cpu) cr = get_cr(); if((cr & CR_920T_CLOCK_MODE) == CR_920T_FASTBUS_MODE) { - freq = imx_get_system_clk(); + freq = clk_get_rate(system_clk); freq = (freq + bclk_div/2) / bclk_div; } else { - freq = imx_get_mcu_clk(); + freq = clk_get_rate(mcu_clk); if (cscr & CSCR_MPU_PRESC) freq /= 2; } @@ -201,7 +205,7 @@ static int imx_set_target(struct cpufreq_policy *policy, pr_debug(KERN_DEBUG "imx: requested frequency %ld Hz, mpctl0 at boot 0x%08x\n", freq, mpctl0_at_boot); - sysclk = imx_get_system_clk(); + sysclk = clk_get_rate(system_clk); if (freq > sysclk / bclk_div_at_boot + 1000000) { freq = imx_compute_mpctl(&mpctl0, mpctl0_at_boot, CLK32 * 512, freq, relation); @@ -290,6 +294,16 @@ static int __init imx_cpufreq_init(void) bclk_div_at_boot = __mfld2val(CSCR_BCLK_DIV, CSCR) + 1; mpctl0_at_boot = 0; + system_clk = clk_get(NULL, "system_clk"); + if (IS_ERR(system_clk)) + return PTR_ERR(system_clk); + + mcu_clk = clk_get(NULL, "mcu_clk"); + if (IS_ERR(mcu_clk)) { + clk_put(system_clk); + return PTR_ERR(mcu_clk); + } + if((CSCR & CSCR_MPEN) && ((get_cr() & CR_920T_CLOCK_MODE) != CR_920T_FASTBUS_MODE)) mpctl0_at_boot = MPCTL0; diff --git a/arch/arm/mach-imx/generic.c b/arch/arm/mach-imx/generic.c index 4cfc9d3af28a..98ddd8a6d05f 100644 --- a/arch/arm/mach-imx/generic.c +++ b/arch/arm/mach-imx/generic.c @@ -214,82 +214,6 @@ int imx_irq_to_gpio(unsigned irq) EXPORT_SYMBOL(imx_irq_to_gpio); -/* - * get the system pll clock in Hz - * - * mfi + mfn / (mfd +1) - * f = 2 * f_ref * -------------------- - * pd + 1 - */ -static unsigned int imx_decode_pll(unsigned int pll, u32 f_ref) -{ - unsigned long long ll; - unsigned long quot; - - u32 mfi = (pll >> 10) & 0xf; - u32 mfn = pll & 0x3ff; - u32 mfd = (pll >> 16) & 0x3ff; - u32 pd = (pll >> 26) & 0xf; - - mfi = mfi <= 5 ? 5 : mfi; - - ll = 2 * (unsigned long long)f_ref * ( (mfi<<16) + (mfn<<16) / (mfd+1) ); - quot = (pd+1) * (1<<16); - ll += quot / 2; - do_div(ll, quot); - return (unsigned int) ll; -} - -unsigned int imx_get_system_clk(void) -{ - u32 f_ref = (CSCR & CSCR_SYSTEM_SEL) ? 16000000 : (CLK32 * 512); - - return imx_decode_pll(SPCTL0, f_ref); -} -EXPORT_SYMBOL(imx_get_system_clk); - -unsigned int imx_get_mcu_clk(void) -{ - return imx_decode_pll(MPCTL0, CLK32 * 512); -} -EXPORT_SYMBOL(imx_get_mcu_clk); - -/* - * get peripheral clock 1 ( UART[12], Timer[12], PWM ) - */ -unsigned int imx_get_perclk1(void) -{ - return imx_get_system_clk() / (((PCDR) & 0xf)+1); -} -EXPORT_SYMBOL(imx_get_perclk1); - -/* - * get peripheral clock 2 ( LCD, SD, SPI[12] ) - */ -unsigned int imx_get_perclk2(void) -{ - return imx_get_system_clk() / (((PCDR>>4) & 0xf)+1); -} -EXPORT_SYMBOL(imx_get_perclk2); - -/* - * get peripheral clock 3 ( SSI ) - */ -unsigned int imx_get_perclk3(void) -{ - return imx_get_system_clk() / (((PCDR>>16) & 0x7f)+1); -} -EXPORT_SYMBOL(imx_get_perclk3); - -/* - * get hclk ( SDRAM, CSI, Memory Stick, I2C, DMA ) - */ -unsigned int imx_get_hclk(void) -{ - return imx_get_system_clk() / (((CSCR>>10) & 0xf)+1); -} -EXPORT_SYMBOL(imx_get_hclk); - static struct resource imx_mmc_resources[] = { [0] = { .start = 0x00214000, diff --git a/arch/arm/mach-imx/time.c b/arch/arm/mach-imx/time.c index d86d124aea22..5a41e96e8586 100644 --- a/arch/arm/mach-imx/time.c +++ b/arch/arm/mach-imx/time.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -86,10 +87,10 @@ static struct clocksource clocksource_imx = { .flags = CLOCK_SOURCE_IS_CONTINUOUS, }; -static int __init imx_clocksource_init(void) +static int __init imx_clocksource_init(unsigned long rate) { clocksource_imx.mult = - clocksource_hz2mult(imx_get_perclk1(), clocksource_imx.shift); + clocksource_hz2mult(rate, clocksource_imx.shift); clocksource_register(&clocksource_imx); return 0; @@ -174,9 +175,9 @@ static struct clock_event_device clockevent_imx = { .rating = 200, }; -static int __init imx_clockevent_init(void) +static int __init imx_clockevent_init(unsigned long rate) { - clockevent_imx.mult = div_sc(imx_get_perclk1(), NSEC_PER_SEC, + clockevent_imx.mult = div_sc(rate, NSEC_PER_SEC, clockevent_imx.shift); clockevent_imx.max_delta_ns = clockevent_delta2ns(0xfffffffe, &clockevent_imx); @@ -190,13 +191,23 @@ static int __init imx_clockevent_init(void) return 0; } +extern int imx_clocks_init(void); static void __init imx_timer_init(void) { + struct clk *clk; + unsigned long rate; + + imx_clocks_init(); + + clk = clk_get(NULL, "perclk1"); + clk_enable(clk); + rate = clk_get_rate(clk); + imx_timer_hardware_init(); - imx_clocksource_init(); + imx_clocksource_init(rate); - imx_clockevent_init(); + imx_clockevent_init(rate); /* * Make irqs happen for the system timer diff --git a/drivers/mmc/host/imxmmc.c b/drivers/mmc/host/imxmmc.c index 95f33e87a99c..ef2f6fc86541 100644 --- a/drivers/mmc/host/imxmmc.c +++ b/drivers/mmc/host/imxmmc.c @@ -42,6 +42,7 @@ #include #include #include +#include #include #include @@ -92,6 +93,8 @@ struct imxmci_host { unsigned char actual_bus_width; int prev_cmd_code; + + struct clk *clk; }; #define IMXMCI_PEND_IRQ_b 0 @@ -841,7 +844,7 @@ static void imxmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) /* The prescaler is 5 for PERCLK2 equal to 96MHz * then 96MHz / 5 = 19.2 MHz */ - clk=imx_get_perclk2(); + clk = clk_get_rate(host->clk); prescaler=(clk+(CLK_RATE*7)/8)/CLK_RATE; switch(prescaler) { case 0: @@ -994,6 +997,13 @@ static int imxmci_probe(struct platform_device *pdev) host->res = r; host->irq = irq; + host->clk = clk_get(&pdev->dev, "perclk2"); + if (IS_ERR(host->clk)) { + ret = PTR_ERR(host->clk); + goto out; + } + clk_enable(host->clk); + imx_gpio_mode(PB8_PF_SD_DAT0); imx_gpio_mode(PB9_PF_SD_DAT1); imx_gpio_mode(PB10_PF_SD_DAT2); @@ -1053,6 +1063,10 @@ out: imx_dma_free(host->dma); host->dma_allocated=0; } + if (host->clk) { + clk_disable(host->clk); + clk_put(host->clk); + } } if (mmc) mmc_free_host(mmc); @@ -1082,6 +1096,9 @@ static int imxmci_remove(struct platform_device *pdev) tasklet_kill(&host->tasklet); + clk_disable(host->clk); + clk_put(host->clk); + release_resource(host->res); mmc_free_host(mmc); diff --git a/drivers/serial/imx.c b/drivers/serial/imx.c index 8d6cb745bd93..9e2162ebf1bb 100644 --- a/drivers/serial/imx.c +++ b/drivers/serial/imx.c @@ -40,6 +40,7 @@ #include #include #include +#include #include #include @@ -184,6 +185,7 @@ struct imx_port { unsigned int old_status; int txirq,rxirq,rtsirq; int have_rtscts:1; + struct clk *clk; }; /* @@ -479,7 +481,8 @@ static int imx_setup_ufcr(struct imx_port *sport, unsigned int mode) * RFDIV is set such way to satisfy requested uartclk value */ val = TXTL << 10 | RXTL; - ufcr_rfdiv = (imx_get_perclk1() + sport->port.uartclk / 2) / sport->port.uartclk; + ufcr_rfdiv = (clk_get_rate(sport->clk) + sport->port.uartclk / 2) + / sport->port.uartclk; if(!ufcr_rfdiv) ufcr_rfdiv = 1; @@ -916,7 +919,7 @@ imx_console_get_options(struct imx_port *sport, int *baud, else ucfr_rfdiv = 6 - ucfr_rfdiv; - uartclk = imx_get_perclk1(); + uartclk = clk_get_rate(sport->clk); uartclk /= ucfr_rfdiv; { /* @@ -1054,7 +1057,15 @@ static int serial_imx_probe(struct platform_device *pdev) init_timer(&sport->timer); sport->timer.function = imx_timeout; sport->timer.data = (unsigned long)sport; - sport->port.uartclk = imx_get_perclk1(); + + sport->clk = clk_get(&pdev->dev, "uart_clk"); + if (IS_ERR(sport->clk)) { + ret = PTR_ERR(sport->clk); + goto unmap; + } + clk_enable(sport->clk); + + sport->port.uartclk = clk_get_rate(sport->clk); imx_ports[pdev->id] = sport; @@ -1069,6 +1080,8 @@ static int serial_imx_probe(struct platform_device *pdev) platform_set_drvdata(pdev, &sport->port); return 0; +unmap: + iounmap(sport->port.membase); free: kfree(sport); @@ -1084,8 +1097,12 @@ static int serial_imx_remove(struct platform_device *pdev) platform_set_drvdata(pdev, NULL); - if (sport) + if (sport) { uart_remove_one_port(&imx_reg, &sport->port); + clk_put(sport->clk); + } + + clk_disable(sport->clk); if (pdata->exit) pdata->exit(pdev); diff --git a/drivers/spi/spi_imx.c b/drivers/spi/spi_imx.c index c730d05bfeb6..bd0729b6b6ed 100644 --- a/drivers/spi/spi_imx.c +++ b/drivers/spi/spi_imx.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -250,6 +251,8 @@ struct driver_data { int tx_dma_needs_unmap; size_t tx_map_len; u32 dummy_dma_buf ____cacheline_aligned; + + struct clk *clk; }; /* Runtime state */ @@ -855,15 +858,15 @@ static irqreturn_t spi_int(int irq, void *dev_id) return drv_data->transfer_handler(drv_data); } -static inline u32 spi_speed_hz(u32 data_rate) +static inline u32 spi_speed_hz(struct driver_data *drv_data, u32 data_rate) { - return imx_get_perclk2() / (4 << ((data_rate) >> 13)); + return clk_get_rate(drv_data->clk) / (4 << ((data_rate) >> 13)); } -static u32 spi_data_rate(u32 speed_hz) +static u32 spi_data_rate(struct driver_data *drv_data, u32 speed_hz) { u32 div; - u32 quantized_hz = imx_get_perclk2() >> 2; + u32 quantized_hz = clk_get_rate(drv_data->clk) >> 2; for (div = SPI_PERCLK2_DIV_MIN; div <= SPI_PERCLK2_DIV_MAX; @@ -947,7 +950,7 @@ static void pump_transfers(unsigned long data) tmp = transfer->speed_hz; if (tmp == 0) tmp = chip->max_speed_hz; - tmp = spi_data_rate(tmp); + tmp = spi_data_rate(drv_data, tmp); u32_EDIT(control, SPI_CONTROL_DATARATE, tmp); writel(control, regs + SPI_CONTROL); @@ -1109,7 +1112,7 @@ static int transfer(struct spi_device *spi, struct spi_message *msg) msg->actual_length = 0; /* Per transfer setup check */ - min_speed_hz = spi_speed_hz(SPI_CONTROL_DATARATE_MIN); + min_speed_hz = spi_speed_hz(drv_data, SPI_CONTROL_DATARATE_MIN); max_speed_hz = spi->max_speed_hz; list_for_each_entry(trans, &msg->transfers, transfer_list) { tmp = trans->bits_per_word; @@ -1176,6 +1179,7 @@ msg_rejected: applied and notified to the calling driver. */ static int setup(struct spi_device *spi) { + struct driver_data *drv_data = spi_master_get_devdata(spi->master); struct spi_imx_chip *chip_info; struct chip_data *chip; int first_setup = 0; @@ -1304,14 +1308,14 @@ static int setup(struct spi_device *spi) chip->n_bytes = (tmp <= 8) ? 1 : 2; /* SPI datarate */ - tmp = spi_data_rate(spi->max_speed_hz); + tmp = spi_data_rate(drv_data, spi->max_speed_hz); if (tmp == SPI_CONTROL_DATARATE_BAD) { status = -EINVAL; dev_err(&spi->dev, "setup - " "HW min speed (%d Hz) exceeds required " "max speed (%d Hz)\n", - spi_speed_hz(SPI_CONTROL_DATARATE_MIN), + spi_speed_hz(drv_data, SPI_CONTROL_DATARATE_MIN), spi->max_speed_hz); if (first_setup) goto err_first_setup; @@ -1321,7 +1325,7 @@ static int setup(struct spi_device *spi) } else { u32_EDIT(chip->control, SPI_CONTROL_DATARATE, tmp); /* Actual rounded max_speed_hz */ - tmp = spi_speed_hz(tmp); + tmp = spi_speed_hz(drv_data, tmp); spi->max_speed_hz = tmp; chip->max_speed_hz = tmp; } @@ -1352,7 +1356,7 @@ static int setup(struct spi_device *spi) chip->period & SPI_PERIOD_WAIT, spi->mode, spi->bits_per_word, - spi_speed_hz(SPI_CONTROL_DATARATE_MIN), + spi_speed_hz(drv_data, SPI_CONTROL_DATARATE_MIN), spi->max_speed_hz); return status; @@ -1465,6 +1469,14 @@ static int __init spi_imx_probe(struct platform_device *pdev) goto err_no_pdata; } + drv_data->clk = clk_get(&pdev->dev, "perclk2"); + if (IS_ERR(drv_data->clk)) { + dev_err(&pdev->dev, "probe - cannot get get\n"); + status = PTR_ERR(drv_data->clk); + goto err_no_clk; + } + clk_enable(drv_data->clk); + /* Allocate master with space for drv_data */ master = spi_alloc_master(dev, sizeof(struct driver_data)); if (!master) { @@ -1623,6 +1635,9 @@ err_no_iores: spi_master_put(master); err_no_pdata: + clk_disable(drv_data->clk); + clk_put(drv_data->clk); +err_no_clk: err_no_mem: return status; } @@ -1662,6 +1677,9 @@ static int __exit spi_imx_remove(struct platform_device *pdev) if (irq >= 0) free_irq(irq, drv_data); + clk_disable(drv_data->clk); + clk_put(drv_data->clk); + /* Release map resources */ iounmap(drv_data->regs); release_resource(drv_data->ioarea); diff --git a/include/asm-arm/arch-imx/hardware.h b/include/asm-arm/arch-imx/hardware.h index adffb6acf42a..6542ca5e8c33 100644 --- a/include/asm-arm/arch-imx/hardware.h +++ b/include/asm-arm/arch-imx/hardware.h @@ -73,14 +73,6 @@ */ extern void imx_gpio_mode( int gpio_mode ); -/* get frequencies in Hz */ -extern unsigned int imx_get_system_clk(void); -extern unsigned int imx_get_mcu_clk(void); -extern unsigned int imx_get_perclk1(void); /* UART[12], Timer[12], PWM */ -extern unsigned int imx_get_perclk2(void); /* LCD, SD, SPI[12] */ -extern unsigned int imx_get_perclk3(void); /* SSI */ -extern unsigned int imx_get_hclk(void); /* SDRAM, CSI, Memory Stick,*/ - /* I2C, DMA */ #endif #define MAXIRQNUM 62 -- cgit v1.2.3 From e3d13ff4b9d3b05d7a969153e2c049548e25deea Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Sat, 5 Jul 2008 10:02:48 +0200 Subject: mxc: add MX3 support for i.MX internal UART driver This patch adds MX3 support for the i.MX internal uart driver. Signed-off-by: Sascha Hauer --- arch/arm/mach-mx3/Makefile | 2 +- arch/arm/mach-mx3/devices.c | 153 ++++++++++++++++++++++++++++++++++++ drivers/serial/Kconfig | 2 +- drivers/serial/imx.c | 100 ++++++++++++++++++----- include/asm-arm/arch-mxc/imx-uart.h | 32 ++++++++ 5 files changed, 267 insertions(+), 22 deletions(-) create mode 100644 arch/arm/mach-mx3/devices.c create mode 100644 include/asm-arm/arch-mxc/imx-uart.h (limited to 'drivers/serial') diff --git a/arch/arm/mach-mx3/Makefile b/arch/arm/mach-mx3/Makefile index febb37f23935..a788cb0b793e 100644 --- a/arch/arm/mach-mx3/Makefile +++ b/arch/arm/mach-mx3/Makefile @@ -4,5 +4,5 @@ # Object file lists. -obj-y := mm.o time.o clock.o +obj-y := mm.o time.o clock.o devices.o obj-$(CONFIG_MACH_MX31ADS) += mx31ads.o diff --git a/arch/arm/mach-mx3/devices.c b/arch/arm/mach-mx3/devices.c new file mode 100644 index 000000000000..1bc6d23a1d58 --- /dev/null +++ b/arch/arm/mach-mx3/devices.c @@ -0,0 +1,153 @@ +/* + * Copyright 2006-2007 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Sascha Hauer, kernel@pengutronix.de + * + * 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., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include + +static struct resource uart0[] = { + { + .start = UART1_BASE_ADDR, + .end = UART1_BASE_ADDR + 0x0B5, + .flags = IORESOURCE_MEM, + }, { + .start = MXC_INT_UART1, + .end = MXC_INT_UART1, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device mxc_uart_device0 = { + .name = "imx-uart", + .id = 0, + .resource = uart0, + .num_resources = ARRAY_SIZE(uart0), +}; + +static struct resource uart1[] = { + { + .start = UART2_BASE_ADDR, + .end = UART2_BASE_ADDR + 0x0B5, + .flags = IORESOURCE_MEM, + }, { + .start = MXC_INT_UART2, + .end = MXC_INT_UART2, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device mxc_uart_device1 = { + .name = "imx-uart", + .id = 1, + .resource = uart1, + .num_resources = ARRAY_SIZE(uart1), +}; + +static struct resource uart2[] = { + { + .start = UART3_BASE_ADDR, + .end = UART3_BASE_ADDR + 0x0B5, + .flags = IORESOURCE_MEM, + }, { + .start = MXC_INT_UART3, + .end = MXC_INT_UART3, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device mxc_uart_device2 = { + .name = "imx-uart", + .id = 2, + .resource = uart2, + .num_resources = ARRAY_SIZE(uart2), +}; + +static struct resource uart3[] = { + { + .start = UART4_BASE_ADDR, + .end = UART4_BASE_ADDR + 0x0B5, + .flags = IORESOURCE_MEM, + }, { + .start = MXC_INT_UART4, + .end = MXC_INT_UART4, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device mxc_uart_device3 = { + .name = "imx-uart", + .id = 3, + .resource = uart3, + .num_resources = ARRAY_SIZE(uart3), +}; + +static struct resource uart4[] = { + { + .start = UART5_BASE_ADDR, + .end = UART5_BASE_ADDR + 0x0B5, + .flags = IORESOURCE_MEM, + }, { + .start = MXC_INT_UART5, + .end = MXC_INT_UART5, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device mxc_uart_device4 = { + .name = "imx-uart", + .id = 4, + .resource = uart4, + .num_resources = ARRAY_SIZE(uart4), +}; + +/* + * Register only those UARTs that physically exist + */ +int __init imx_init_uart(int uart_no, struct imxuart_platform_data *pdata) +{ + switch (uart_no) { + case 0: + mxc_uart_device0.dev.platform_data = pdata; + platform_device_register(&mxc_uart_device0); + break; + case 1: + mxc_uart_device1.dev.platform_data = pdata; + platform_device_register(&mxc_uart_device1); + break; + case 2: + mxc_uart_device2.dev.platform_data = pdata; + platform_device_register(&mxc_uart_device2); + break; + case 3: + mxc_uart_device3.dev.platform_data = pdata; + platform_device_register(&mxc_uart_device3); + break; + case 4: + mxc_uart_device4.dev.platform_data = pdata; + platform_device_register(&mxc_uart_device4); + break; + default: + return -ENODEV; + } + + return 0; +} + diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig index 9bc42763623c..0843c5400684 100644 --- a/drivers/serial/Kconfig +++ b/drivers/serial/Kconfig @@ -753,7 +753,7 @@ config BFIN_UART3_CTSRTS config SERIAL_IMX bool "IMX serial port support" - depends on ARM && ARCH_IMX + depends on ARM && (ARCH_IMX || ARCH_MXC) select SERIAL_CORE help If you have a machine based on a Motorola IMX CPU you diff --git a/drivers/serial/imx.c b/drivers/serial/imx.c index 9e2162ebf1bb..549440b098b2 100644 --- a/drivers/serial/imx.c +++ b/drivers/serial/imx.c @@ -62,6 +62,11 @@ #define UBIR 0xa4 /* BRM Incremental Register */ #define UBMR 0xa8 /* BRM Modulator Register */ #define UBRC 0xac /* Baud Rate Count Register */ +#ifdef CONFIG_ARCH_MX3 +#define ONEMS 0xb0 /* One Millisecond register */ +#define UTS 0xb4 /* UART Test Register */ +#endif +#ifdef CONFIG_ARCH_IMX #define BIPR1 0xb0 /* Incremental Preset Register 1 */ #define BIPR2 0xb4 /* Incremental Preset Register 2 */ #define BIPR3 0xb8 /* Incremental Preset Register 3 */ @@ -71,6 +76,7 @@ #define BMPR3 0xc8 /* BRM Modulator Register 3 */ #define BMPR4 0xcc /* BRM Modulator Register 4 */ #define UTS 0xd0 /* UART Test Register */ +#endif /* UART Control Register Bit Fields.*/ #define URXD_CHARRDY (1<<15) @@ -90,7 +96,12 @@ #define UCR1_RTSDEN (1<<5) /* RTS delta interrupt enable */ #define UCR1_SNDBRK (1<<4) /* Send break */ #define UCR1_TDMAEN (1<<3) /* Transmitter ready DMA enable */ +#ifdef CONFIG_ARCH_IMX #define UCR1_UARTCLKEN (1<<2) /* UART clock enabled */ +#endif +#ifdef CONFIG_ARCH_MX3 +#define UCR1_UARTCLKEN (0) /* not present on mx2/mx3 */ +#endif #define UCR1_DOZE (1<<1) /* Doze */ #define UCR1_UARTEN (1<<0) /* UART enabled */ #define UCR2_ESCI (1<<15) /* Escape seq interrupt enable */ @@ -164,8 +175,19 @@ #define UTS_SOFTRST (1<<0) /* Software reset */ /* We've been assigned a range on the "Low-density serial ports" major */ +#ifdef CONFIG_ARCH_IMX #define SERIAL_IMX_MAJOR 204 #define MINOR_START 41 +#define DEV_NAME "ttySMX" +#define MAX_INTERNAL_IRQ IMX_IRQS +#endif + +#ifdef CONFIG_ARCH_MX3 +#define SERIAL_IMX_MAJOR 207 +#define MINOR_START 16 +#define DEV_NAME "ttymxc" +#define MAX_INTERNAL_IRQ MXC_MAX_INT_LINES +#endif /* * This determines how often we check the modem status signals @@ -409,6 +431,26 @@ out: return IRQ_HANDLED; } +static irqreturn_t imx_int(int irq, void *dev_id) +{ + struct imx_port *sport = dev_id; + unsigned int sts; + + sts = readl(sport->port.membase + USR1); + + if (sts & USR1_RRDY) + imx_rxint(irq, dev_id); + + if (sts & USR1_TRDY && + readl(sport->port.membase + UCR1) & UCR1_TXMPTYEN) + imx_txint(irq, dev_id); + + if (sts & USR1_RTSS) + imx_rtsint(irq, dev_id); + + return IRQ_HANDLED; +} + /* * Return TIOCSER_TEMT when transmitter is not busy. */ @@ -514,21 +556,34 @@ static int imx_startup(struct uart_port *port) writel(temp & ~UCR4_DREN, sport->port.membase + UCR4); /* - * Allocate the IRQ + * Allocate the IRQ(s) i.MX1 has three interrupts whereas later + * chips only have one interrupt. */ - retval = request_irq(sport->rxirq, imx_rxint, 0, - DRIVER_NAME, sport); - if (retval) goto error_out1; - - retval = request_irq(sport->txirq, imx_txint, 0, - DRIVER_NAME, sport); - if (retval) goto error_out2; - - retval = request_irq(sport->rtsirq, imx_rtsint, - (sport->rtsirq < IMX_IRQS) ? 0 : + if (sport->txirq > 0) { + retval = request_irq(sport->rxirq, imx_rxint, 0, + DRIVER_NAME, sport); + if (retval) + goto error_out1; + + retval = request_irq(sport->txirq, imx_txint, 0, + DRIVER_NAME, sport); + if (retval) + goto error_out2; + + retval = request_irq(sport->rtsirq, imx_rtsint, + (sport->rtsirq < MAX_INTERNAL_IRQ) ? 0 : IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, - DRIVER_NAME, sport); - if (retval) goto error_out3; + DRIVER_NAME, sport); + if (retval) + goto error_out3; + } else { + retval = request_irq(sport->port.irq, imx_int, 0, + DRIVER_NAME, sport); + if (retval) { + free_irq(sport->port.irq, sport); + goto error_out1; + } + } /* * Finally, clear and enable interrupts @@ -553,9 +608,11 @@ static int imx_startup(struct uart_port *port) return 0; error_out3: - free_irq(sport->txirq, sport); + if (sport->txirq) + free_irq(sport->txirq, sport); error_out2: - free_irq(sport->rxirq, sport); + if (sport->rxirq) + free_irq(sport->rxirq, sport); error_out1: return retval; } @@ -573,9 +630,12 @@ static void imx_shutdown(struct uart_port *port) /* * Free the interrupts */ - free_irq(sport->rtsirq, sport); - free_irq(sport->txirq, sport); - free_irq(sport->rxirq, sport); + if (sport->txirq > 0) { + free_irq(sport->rtsirq, sport); + free_irq(sport->txirq, sport); + free_irq(sport->rxirq, sport); + } else + free_irq(sport->port.irq, sport); /* * Disable all interrupts, port and break condition. @@ -973,7 +1033,7 @@ imx_console_setup(struct console *co, char *options) static struct uart_driver imx_reg; static struct console imx_console = { - .name = "ttySMX", + .name = DEV_NAME, .write = imx_console_write, .device = uart_console_device, .setup = imx_console_setup, @@ -990,7 +1050,7 @@ static struct console imx_console = { static struct uart_driver imx_reg = { .owner = THIS_MODULE, .driver_name = DRIVER_NAME, - .dev_name = "ttySMX", + .dev_name = DEV_NAME, .major = SERIAL_IMX_MAJOR, .minor = MINOR_START, .nr = ARRAY_SIZE(imx_ports), diff --git a/include/asm-arm/arch-mxc/imx-uart.h b/include/asm-arm/arch-mxc/imx-uart.h new file mode 100644 index 000000000000..83fb72c4048a --- /dev/null +++ b/include/asm-arm/arch-mxc/imx-uart.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2008 by Sascha Hauer + * + * 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., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#ifndef ASMARM_ARCH_UART_H +#define ASMARM_ARCH_UART_H + +#define IMXUART_HAVE_RTSCTS (1<<0) + +struct imxuart_platform_data { + int (*init)(struct platform_device *pdev); + int (*exit)(struct platform_device *pdev); + unsigned int flags; +}; + +int __init imx_init_uart(int uart_no, struct imxuart_platform_data *pdata); + +#endif -- cgit v1.2.3 From 604cbadce2292d979749e2f5c6c3f75ee10f4c9e Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Sat, 5 Jul 2008 10:02:58 +0200 Subject: MX2 add support for mx2 in i.MX serial driver add support for mx2 in i.MX serial driver Signed-off-by: Sascha Hauer --- arch/arm/mach-mx2/Makefile | 2 +- arch/arm/mach-mx2/serial.c | 177 +++++++++++++++++++++++++++++++++++++++++++++ drivers/serial/imx.c | 6 +- 3 files changed, 181 insertions(+), 4 deletions(-) create mode 100644 arch/arm/mach-mx2/serial.c (limited to 'drivers/serial') diff --git a/arch/arm/mach-mx2/Makefile b/arch/arm/mach-mx2/Makefile index db4d9c6f2738..f8f8ecb01c97 100644 --- a/arch/arm/mach-mx2/Makefile +++ b/arch/arm/mach-mx2/Makefile @@ -4,4 +4,4 @@ # Object file lists. -obj-y := system.o generic.o devices.o +obj-y := system.o generic.o devices.o serial.o diff --git a/arch/arm/mach-mx2/serial.c b/arch/arm/mach-mx2/serial.c new file mode 100644 index 000000000000..570c02b8e5df --- /dev/null +++ b/arch/arm/mach-mx2/serial.c @@ -0,0 +1,177 @@ +/* + * Copyright 2006-2007 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Juergen Beisert, kernel@pengutronix.de + * + * 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., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include + +static struct resource uart0[] = { + { + .start = UART1_BASE_ADDR, + .end = UART1_BASE_ADDR + 0x0B5, + .flags = IORESOURCE_MEM, + }, { + .start = MXC_INT_UART1, + .end = MXC_INT_UART1, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device mxc_uart_device0 = { + .name = "imx-uart", + .id = 0, + .resource = uart0, + .num_resources = ARRAY_SIZE(uart0), +}; + +static struct resource uart1[] = { + { + .start = UART2_BASE_ADDR, + .end = UART2_BASE_ADDR + 0x0B5, + .flags = IORESOURCE_MEM, + }, { + .start = MXC_INT_UART2, + .end = MXC_INT_UART2, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device mxc_uart_device1 = { + .name = "imx-uart", + .id = 1, + .resource = uart1, + .num_resources = ARRAY_SIZE(uart1), +}; + +static struct resource uart2[] = { + { + .start = UART3_BASE_ADDR, + .end = UART3_BASE_ADDR + 0x0B5, + .flags = IORESOURCE_MEM, + }, { + .start = MXC_INT_UART3, + .end = MXC_INT_UART3, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device mxc_uart_device2 = { + .name = "imx-uart", + .id = 2, + .resource = uart2, + .num_resources = ARRAY_SIZE(uart2), +}; + +static struct resource uart3[] = { + { + .start = UART4_BASE_ADDR, + .end = UART4_BASE_ADDR + 0x0B5, + .flags = IORESOURCE_MEM, + }, { + .start = MXC_INT_UART4, + .end = MXC_INT_UART4, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device mxc_uart_device3 = { + .name = "imx-uart", + .id = 3, + .resource = uart3, + .num_resources = ARRAY_SIZE(uart3), +}; + +static struct resource uart4[] = { + { + .start = UART5_BASE_ADDR, + .end = UART5_BASE_ADDR + 0x0B5, + .flags = IORESOURCE_MEM, + }, { + .start = MXC_INT_UART5, + .end = MXC_INT_UART5, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device mxc_uart_device4 = { + .name = "imx-uart", + .id = 4, + .resource = uart4, + .num_resources = ARRAY_SIZE(uart4), +}; + +static struct resource uart5[] = { + { + .start = UART6_BASE_ADDR, + .end = UART6_BASE_ADDR + 0x0B5, + .flags = IORESOURCE_MEM, + }, { + .start = MXC_INT_UART6, + .end = MXC_INT_UART6, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device mxc_uart_device5 = { + .name = "imx-uart", + .id = 5, + .resource = uart5, + .num_resources = ARRAY_SIZE(uart5), +}; + +/* + * Register only those UARTs that physically exists + */ +int __init imx_init_uart(int uart_no, struct imxuart_platform_data *pdata) +{ + switch (uart_no) { + case 0: + mxc_uart_device0.dev.platform_data = pdata; + platform_device_register(&mxc_uart_device0); + break; + case 1: + mxc_uart_device1.dev.platform_data = pdata; + platform_device_register(&mxc_uart_device1); + break; +#ifndef CONFIG_MXC_IRDA + case 2: + mxc_uart_device2.dev.platform_data = pdata; + platform_device_register(&mxc_uart_device2); + break; +#endif + case 3: + mxc_uart_device3.dev.platform_data = pdata; + platform_device_register(&mxc_uart_device3); + break; + case 4: + mxc_uart_device4.dev.platform_data = pdata; + platform_device_register(&mxc_uart_device4); + break; + case 5: + mxc_uart_device5.dev.platform_data = pdata; + platform_device_register(&mxc_uart_device5); + break; + default: + return -ENODEV; + } + + return 0; +} diff --git a/drivers/serial/imx.c b/drivers/serial/imx.c index 549440b098b2..64acb39a51ba 100644 --- a/drivers/serial/imx.c +++ b/drivers/serial/imx.c @@ -62,7 +62,7 @@ #define UBIR 0xa4 /* BRM Incremental Register */ #define UBMR 0xa8 /* BRM Modulator Register */ #define UBRC 0xac /* Baud Rate Count Register */ -#ifdef CONFIG_ARCH_MX3 +#if defined CONFIG_ARCH_MX3 || defined CONFIG_ARCH_MX2 #define ONEMS 0xb0 /* One Millisecond register */ #define UTS 0xb4 /* UART Test Register */ #endif @@ -99,7 +99,7 @@ #ifdef CONFIG_ARCH_IMX #define UCR1_UARTCLKEN (1<<2) /* UART clock enabled */ #endif -#ifdef CONFIG_ARCH_MX3 +#if defined CONFIG_ARCH_MX3 || defined CONFIG_ARCH_MX2 #define UCR1_UARTCLKEN (0) /* not present on mx2/mx3 */ #endif #define UCR1_DOZE (1<<1) /* Doze */ @@ -182,7 +182,7 @@ #define MAX_INTERNAL_IRQ IMX_IRQS #endif -#ifdef CONFIG_ARCH_MX3 +#if defined CONFIG_ARCH_MX3 || defined CONFIG_ARCH_MX2 #define SERIAL_IMX_MAJOR 207 #define MINOR_START 16 #define DEV_NAME "ttymxc" -- cgit v1.2.3