1/* linux/drivers/serial/s3c2440.c 2 * 3 * Driver for Samsung S3C2440 and S3C2442 SoC onboard UARTs. 4 * 5 * Ben Dooks, Copyright (c) 2003-2008 Simtec Electronics 6 * http://armlinux.simtec.co.uk/ 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU General Public License version 2 as 10 * published by the Free Software Foundation. 11*/ 12 13#include <linux/module.h> 14#include <linux/ioport.h> 15#include <linux/io.h> 16#include <linux/platform_device.h> 17#include <linux/init.h> 18#include <linux/serial_core.h> 19#include <linux/serial.h> 20 21#include <asm/irq.h> 22#include <mach/hardware.h> 23 24#include <plat/regs-serial.h> 25#include <mach/regs-gpio.h> 26 27#include "samsung.h" 28 29 30static int s3c2440_serial_setsource(struct uart_port *port, 31 struct s3c24xx_uart_clksrc *clk) 32{ 33 unsigned long ucon = rd_regl(port, S3C2410_UCON); 34 35 /* todo - proper fclk<>nonfclk switch. */ 36 37 ucon &= ~S3C2440_UCON_CLKMASK; 38 39 if (strcmp(clk->name, "uclk") == 0) 40 ucon |= S3C2440_UCON_UCLK; 41 else if (strcmp(clk->name, "pclk") == 0) 42 ucon |= S3C2440_UCON_PCLK; 43 else if (strcmp(clk->name, "fclk") == 0) 44 ucon |= S3C2440_UCON_FCLK; 45 else { 46 printk(KERN_ERR "unknown clock source %s\n", clk->name); 47 return -EINVAL; 48 } 49 50 wr_regl(port, S3C2410_UCON, ucon); 51 return 0; 52} 53 54 55static int s3c2440_serial_getsource(struct uart_port *port, 56 struct s3c24xx_uart_clksrc *clk) 57{ 58 unsigned long ucon = rd_regl(port, S3C2410_UCON); 59 unsigned long ucon0, ucon1, ucon2; 60 61 switch (ucon & S3C2440_UCON_CLKMASK) { 62 case S3C2440_UCON_UCLK: 63 clk->divisor = 1; 64 clk->name = "uclk"; 65 break; 66 67 case S3C2440_UCON_PCLK: 68 case S3C2440_UCON_PCLK2: 69 clk->divisor = 1; 70 clk->name = "pclk"; 71 break; 72 73 case S3C2440_UCON_FCLK: 74 /* the fun of calculating the uart divisors on 75 * the s3c2440 */ 76 77 ucon0 = __raw_readl(S3C24XX_VA_UART0 + S3C2410_UCON); 78 ucon1 = __raw_readl(S3C24XX_VA_UART1 + S3C2410_UCON); 79 ucon2 = __raw_readl(S3C24XX_VA_UART2 + S3C2410_UCON); 80 81 printk("ucons: %08lx, %08lx, %08lx\n", ucon0, ucon1, ucon2); 82 83 ucon0 &= S3C2440_UCON0_DIVMASK; 84 ucon1 &= S3C2440_UCON1_DIVMASK; 85 ucon2 &= S3C2440_UCON2_DIVMASK; 86 87 if (ucon0 != 0) { 88 clk->divisor = ucon0 >> S3C2440_UCON_DIVSHIFT; 89 clk->divisor += 6; 90 } else if (ucon1 != 0) { 91 clk->divisor = ucon1 >> S3C2440_UCON_DIVSHIFT; 92 clk->divisor += 21; 93 } else if (ucon2 != 0) { 94 clk->divisor = ucon2 >> S3C2440_UCON_DIVSHIFT; 95 clk->divisor += 36; 96 } else { 97 /* manual calims 44, seems to be 9 */ 98 clk->divisor = 9; 99 } 100 101 clk->name = "fclk"; 102 break; 103 } 104 105 return 0; 106} 107 108static int s3c2440_serial_resetport(struct uart_port *port, 109 struct s3c2410_uartcfg *cfg) 110{ 111 unsigned long ucon = rd_regl(port, S3C2410_UCON); 112 113 dbg("s3c2440_serial_resetport: port=%p (%08lx), cfg=%p\n", 114 port, port->mapbase, cfg); 115 116 /* ensure we don't change the clock settings... */ 117 118 ucon &= (S3C2440_UCON0_DIVMASK | (3<<10)); 119 120 wr_regl(port, S3C2410_UCON, ucon | cfg->ucon); 121 wr_regl(port, S3C2410_ULCON, cfg->ulcon); 122 123 /* reset both fifos */ 124 125 wr_regl(port, S3C2410_UFCON, cfg->ufcon | S3C2410_UFCON_RESETBOTH); 126 wr_regl(port, S3C2410_UFCON, cfg->ufcon); 127 128 return 0; 129} 130 131static struct s3c24xx_uart_info s3c2440_uart_inf = { 132 .name = "Samsung S3C2440 UART", 133 .type = PORT_S3C2440, 134 .fifosize = 64, 135 .rx_fifomask = S3C2440_UFSTAT_RXMASK, 136 .rx_fifoshift = S3C2440_UFSTAT_RXSHIFT, 137 .rx_fifofull = S3C2440_UFSTAT_RXFULL, 138 .tx_fifofull = S3C2440_UFSTAT_TXFULL, 139 .tx_fifomask = S3C2440_UFSTAT_TXMASK, 140 .tx_fifoshift = S3C2440_UFSTAT_TXSHIFT, 141 .get_clksrc = s3c2440_serial_getsource, 142 .set_clksrc = s3c2440_serial_setsource, 143 .reset_port = s3c2440_serial_resetport, 144}; 145 146/* device management */ 147 148static int s3c2440_serial_probe(struct platform_device *dev) 149{ 150 dbg("s3c2440_serial_probe: dev=%p\n", dev); 151 return s3c24xx_serial_probe(dev, &s3c2440_uart_inf); 152} 153 154static struct platform_driver s3c2440_serial_driver = { 155 .probe = s3c2440_serial_probe, 156 .remove = __devexit_p(s3c24xx_serial_remove), 157 .driver = { 158 .name = "s3c2440-uart", 159 .owner = THIS_MODULE, 160 }, 161}; 162 163s3c24xx_console_init(&s3c2440_serial_driver, &s3c2440_uart_inf); 164 165static int __init s3c2440_serial_init(void) 166{ 167 return s3c24xx_serial_init(&s3c2440_serial_driver, &s3c2440_uart_inf); 168} 169 170static void __exit s3c2440_serial_exit(void) 171{ 172 platform_driver_unregister(&s3c2440_serial_driver); 173} 174 175module_init(s3c2440_serial_init); 176module_exit(s3c2440_serial_exit); 177 178MODULE_DESCRIPTION("Samsung S3C2440,S3C2442 SoC Serial port driver"); 179MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>"); 180MODULE_LICENSE("GPL v2"); 181MODULE_ALIAS("platform:s3c2440-uart"); 182