[PATCH 7/9] Experimental S3C2410A cpufreq driver (serial)

Cesar Eduardo Barros cesarb at cesarb.net
Wed Feb 13 02:24:35 CET 2008


This is the cpufreq notifier for the S3C2410 serial driver.

It uses the hardware flow control, when available, to avoid losing characters
during the transition.

Signed-off-by: Cesar Eduardo Barros <cesarb at cesarb.net>
---
 drivers/serial/Kconfig   |    2 +-
 drivers/serial/s3c2410.c |  309 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 310 insertions(+), 1 deletions(-)

diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig
index d7e1996..8ee7101 100644
--- a/drivers/serial/Kconfig
+++ b/drivers/serial/Kconfig
@@ -435,7 +435,7 @@ config SERIAL_CLPS711X_CONSOLE
 
 config SERIAL_S3C2410
 	tristate "Samsung S3C2410/S3C2440/S3C2442/S3C2412 Serial port support"
-	depends on ARM && ARCH_S3C2410
+	depends on ARM && ARCH_S3C2410 && (CPU_FREQ=n || CPU_FREQ_S3C2410)
 	select SERIAL_CORE
 	help
 	  Support for the on-chip UARTs on the Samsung S3C24XX series CPUs,
diff --git a/drivers/serial/s3c2410.c b/drivers/serial/s3c2410.c
index c9857d9..d117a83 100644
--- a/drivers/serial/s3c2410.c
+++ b/drivers/serial/s3c2410.c
@@ -72,6 +72,8 @@
 #include <linux/serial.h>
 #include <linux/delay.h>
 #include <linux/clk.h>
+#include <linux/cpufreq.h>
+#include <linux/delay.h>
 
 #include <asm/io.h>
 #include <asm/irq.h>
@@ -80,6 +82,7 @@
 
 #include <asm/plat-s3c/regs-serial.h>
 #include <asm/arch/regs-gpio.h>
+#include <asm/arch/s3c2410-cpufreq.h>
 
 /* structures */
 
@@ -101,6 +104,11 @@ struct s3c24xx_uart_info {
 
 	/* uart controls */
 	int (*reset_port)(struct uart_port *, struct s3c2410_uartcfg *);
+
+#ifdef CONFIG_CPU_FREQ
+	bool (*policy_adjust_cb)(struct uart_port *port,
+		const struct s3c2410_cpufreq_clocks *clocks);
+#endif
 };
 
 struct s3c24xx_uart_port {
@@ -112,6 +120,14 @@ struct s3c24xx_uart_port {
 	struct clk			*clk;
 	struct clk			*baudclk;
 	struct uart_port		port;
+
+#ifdef CONFIG_CPU_FREQ
+	struct notifier_block		cpufreq_transition_nb;
+	struct notifier_block		cpufreq_policy_nb;
+	unsigned int			baud;
+	unsigned int			flags;
+#define S3C24XX_UART_PORT_AFC_PAUSED	(1 << 0)
+#endif
 };
 
 
@@ -203,6 +219,42 @@ static inline const char *s3c24xx_serial_portname(struct uart_port *port)
 	return to_platform_device(port->dev)->name;
 }
 
+#ifdef CONFIG_CPU_FREQ
+
+static inline void s3c24xx_serial_cpufreq_register_notifiers(
+		struct s3c24xx_uart_port *ourport)
+{
+	cpufreq_register_notifier(&ourport->cpufreq_transition_nb,
+			CPUFREQ_TRANSITION_NOTIFIER);
+	cpufreq_register_notifier(&ourport->cpufreq_policy_nb,
+			CPUFREQ_POLICY_NOTIFIER);
+	s3c2410_cpufreq_update_policy();
+}
+
+static inline void s3c24xx_serial_cpufreq_unregister_notifiers(
+		struct s3c24xx_uart_port *ourport)
+{
+	cpufreq_unregister_notifier(&ourport->cpufreq_transition_nb,
+			CPUFREQ_TRANSITION_NOTIFIER);
+	cpufreq_unregister_notifier(&ourport->cpufreq_policy_nb,
+			CPUFREQ_POLICY_NOTIFIER);
+	s3c2410_cpufreq_update_policy();
+}
+
+#else
+
+static inline void s3c24xx_serial_cpufreq_register_notifiers(
+		struct s3c24xx_uart_port *ourport __maybe_unused)
+{
+}
+
+static inline void s3c24xx_serial_cpufreq_unregister_notifiers(
+		struct s3c24xx_uart_port *ourport __maybe_unused)
+{
+}
+
+#endif
+
 static int s3c24xx_serial_txempty_nofifo(struct uart_port *port)
 {
 	return (rd_regl(port, S3C2410_UTRSTAT) & S3C2410_UTRSTAT_TXE);
@@ -493,6 +545,8 @@ static void s3c24xx_serial_shutdown(struct uart_port *port)
 {
 	struct s3c24xx_uart_port *ourport = to_ourport(port);
 
+	s3c24xx_serial_cpufreq_unregister_notifiers(ourport);
+
 	if (ourport->tx_claimed) {
 		free_irq(TX_IRQ(port), ourport);
 		tx_enabled(port) = 0;
@@ -543,6 +597,8 @@ static int s3c24xx_serial_startup(struct uart_port *port)
 
 	ourport->tx_claimed = 1;
 
+	s3c24xx_serial_cpufreq_register_notifiers(ourport);
+
 	dbg("s3c24xx_serial_startup ok\n");
 
 	/* the port reset code should have done the correct
@@ -825,6 +881,10 @@ static void s3c24xx_serial_set_termios(struct uart_port *port,
 
 	spin_lock_irqsave(&port->lock, flags);
 
+#ifdef CONFIG_CPU_FREQ
+	ourport->baud = baud;
+#endif
+
 	dbg("setting ulcon to %08x, brddiv to %d\n", ulcon, quot);
 
 	wr_regl(port, S3C2410_ULCON, ulcon);
@@ -864,6 +924,8 @@ static void s3c24xx_serial_set_termios(struct uart_port *port,
 		port->ignore_status_mask |= RXSTAT_DUMMY_READ;
 
 	spin_unlock_irqrestore(&port->lock, flags);
+
+	s3c2410_cpufreq_update_policy();
 }
 
 static const char *s3c24xx_serial_type(struct uart_port *port)
@@ -916,6 +978,185 @@ s3c24xx_serial_verify_port(struct uart_port *port, struct serial_struct *ser)
 	return 0;
 }
 
+#ifdef CONFIG_CPU_FREQ
+
+static inline bool s3c24xx_serial_txempty(struct uart_port *port)
+{
+	if ((rd_regl(port, S3C2410_UFCON) & S3C2410_UFCON_FIFOMODE))
+		if ((rd_regl(port, S3C2410_UFSTAT) & S3C2410_UFSTAT_TXMASK))
+			return false;
+	return (rd_regl(port, S3C2410_UTRSTAT) & S3C2410_UTRSTAT_TXE);
+}
+
+static void s3c24xx_serial_drain_txfifo(struct uart_port *port)
+{
+	/* This loop was mostly copied from s3c24xx_serial_rx_enable. */
+	int count = 10000;
+
+	while (--count && !s3c24xx_serial_txempty(port))
+		udelay(100);
+}
+
+/*
+ * To avoid having a incoming character corrupted by being received while the
+ * frequency is changing, we pretend our FIFO is full and ask the other end to
+ * pause for a bit.
+ *
+ * On the other direction, pretend the other side asked us to stop.
+ *
+ * FIXME: Something similar should be done in the XON/XOFF case.
+ */
+static void s3c24xx_serial_cpufreq_afc_pause(struct uart_port *port)
+{
+	struct s3c24xx_uart_port *ourport = to_ourport(port);
+	unsigned int umcon;
+	unsigned int baud;
+	unsigned int delay;
+	unsigned long flags;
+
+	spin_lock_irqsave(&port->lock, flags);
+
+	umcon = rd_regl(port, S3C2410_UMCON);
+	if ((umcon & S3C2410_UMCOM_AFC)) {
+		uart_handle_cts_change(port, 0);
+
+		/*
+		 * Wait for the TX fifo to drain. This has to be done before
+		 * disabling AFC, to avoid the remaining contents of the FIFO
+		 * from being sent as soon as AFC is disabled.
+		 */
+		s3c24xx_serial_drain_txfifo(port);
+
+		umcon &= ~(S3C2410_UMCOM_AFC | S3C2410_UMCOM_RTS_LOW);
+		wr_regl(port, S3C2410_UMCON, umcon);
+
+		ourport->flags |= S3C24XX_UART_PORT_AFC_PAUSED;
+
+		baud = ourport->baud;
+		spin_unlock_irqrestore(&port->lock, flags);
+
+		if (unlikely(!baud))
+			return;
+
+		/*
+		 * The other end might have already checked CTS (our RTS), so
+		 * wait for a few moments to be sure any pending transmission
+		 * has already been received.
+		 *
+		 * The extra +1 factor is to round up the result. To compensate
+		 * for wire delays and extra paranoia, we double the delay.
+		 */
+		delay = (1000000 / baud + 1) * 2;
+		if (delay <= MAX_UDELAY_MS * 1000)
+			udelay(delay);
+		else
+			mdelay((delay + 999) / 1000);
+	} else
+		spin_unlock_irqrestore(&port->lock, flags);
+}
+
+static void s3c24xx_serial_cpufreq_afc_unpause(struct uart_port *port)
+{
+	struct s3c24xx_uart_port *ourport = to_ourport(port);
+	unsigned int umcon;
+	unsigned long flags;
+
+	spin_lock_irqsave(&port->lock, flags);
+
+	if ((ourport->flags & S3C24XX_UART_PORT_AFC_PAUSED)) {
+		ourport->flags &= ~S3C24XX_UART_PORT_AFC_PAUSED;
+
+		umcon = rd_regl(port, S3C2410_UMCON);
+		umcon |= S3C2410_UMCOM_AFC;
+		wr_regl(port, S3C2410_UMCON, umcon);
+
+		/* We're pretending it's always set here because
+		 * s3c24xx_serial_tx_chars does the same. */
+		uart_handle_cts_change(port, S3C2410_UMSTAT_CTS);
+	}
+
+	spin_unlock_irqrestore(&port->lock, flags);
+}
+
+static int s3c24xx_serial_cpufreq_transition_notifier(struct notifier_block *nb,
+		unsigned long val, void *data __maybe_unused)
+{
+	struct s3c24xx_uart_port *ourport =
+		container_of(nb, struct s3c24xx_uart_port,
+				cpufreq_transition_nb);
+	struct uart_port *port = &ourport->port;
+	unsigned int baud, quot;
+	bool do_unpause = false;
+	unsigned long flags;
+
+	switch (val) {
+	case CPUFREQ_PRECHANGE:
+		s3c24xx_serial_cpufreq_afc_pause(port);
+		break;
+	case CPUFREQ_POSTCHANGE:
+		do_unpause = true;
+		/* fall through */
+	case CPUFREQ_RESUMECHANGE:
+	case CPUFREQ_SUSPENDCHANGE:
+		spin_lock_irqsave(&port->lock, flags);
+
+		baud = ourport->baud;
+		if (!baud) {
+			spin_unlock_irqrestore(&port->lock, flags);
+			break;
+		}
+
+		quot = s3c24xx_serial_calc_ubrdiv(port, baud);
+		wr_regl(port, S3C2410_UBRDIV, quot);
+
+		spin_unlock_irqrestore(&port->lock, flags);
+
+		if (do_unpause)
+			s3c24xx_serial_cpufreq_afc_unpause(port);
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static bool s3c2410_serial_cpufreq_policy_adjust_cb(void *data,
+		const struct s3c2410_cpufreq_clocks *clocks)
+{
+	struct s3c24xx_uart_port *ourport = data;
+	struct uart_port *port = &ourport->port;
+
+	if (ourport->info->policy_adjust_cb)
+		return (ourport->info->policy_adjust_cb)(port, clocks);
+
+	return true;
+}
+
+static int s3c24xx_serial_cpufreq_policy_notifier(struct notifier_block *nb,
+		unsigned long val, void *data)
+{
+	struct s3c24xx_uart_port *ourport =
+		container_of(nb, struct s3c24xx_uart_port,
+				cpufreq_policy_nb);
+	struct cpufreq_policy *policy = data;
+
+	switch (val)
+	{
+	case CPUFREQ_ADJUST:
+		s3c2410_cpufreq_adjust_table(policy,
+				s3c2410_serial_cpufreq_policy_adjust_cb,
+				ourport);
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+#endif
+
 
 #ifdef CONFIG_SERIAL_S3C2410_CONSOLE
 
@@ -1042,6 +1283,12 @@ static int s3c24xx_serial_init_port(struct s3c24xx_uart_port *ourport,
 	/* setup info for port */
 	port->dev	= &platdev->dev;
 	ourport->info	= info;
+#ifdef CONFIG_CPU_FREQ
+	ourport->cpufreq_transition_nb.notifier_call
+			= s3c24xx_serial_cpufreq_transition_notifier;
+	ourport->cpufreq_policy_nb.notifier_call
+			= s3c24xx_serial_cpufreq_policy_notifier;
+#endif
 
 	/* copy the info in from provided structure */
 	ourport->port.fifosize = info->fifosize;
@@ -1308,6 +1555,34 @@ static int s3c2410_serial_resetport(struct uart_port *port,
 	return 0;
 }
 
+#ifdef CONFIG_CPU_FREQ
+static bool s3c2410_serial_policy_adjust_cb(struct uart_port *port,
+		const struct s3c2410_cpufreq_clocks *clocks)
+{
+	struct s3c24xx_uart_port *ourport = to_ourport(port);
+	struct s3c2410_uartcfg *cfg = s3c24xx_port_to_cfg(port);
+	unsigned int baud, quot, calc, diff;
+
+	/* FIXME: this function is not correct in all cases. */
+
+	/* If we can use uclk, it should always work. */
+	if (cfg->clocks_size >= 2)
+		return true;
+
+	baud = ourport->baud;
+
+	if (!baud)
+		return true;
+
+	/* Copied from s3c24xx_serial_calcbaud. */
+	quot = (clocks->pclk + (8 * baud)) / (16 * baud);
+	calc = (clocks->pclk / (quot * 16));
+
+	diff = abs((int)baud - (int)calc);
+	return diff*160 < baud*3;	/* diff/baud < 3/160 */
+}
+#endif
+
 static struct s3c24xx_uart_info s3c2410_uart_inf = {
 	.name		= "Samsung S3C2410 UART",
 	.type		= PORT_S3C2410,
@@ -1321,6 +1596,9 @@ static struct s3c24xx_uart_info s3c2410_uart_inf = {
 	.get_clksrc	= s3c2410_serial_getsource,
 	.set_clksrc	= s3c2410_serial_setsource,
 	.reset_port	= s3c2410_serial_resetport,
+#ifdef CONFIG_CPU_FREQ
+	.policy_adjust_cb = s3c2410_serial_policy_adjust_cb,
+#endif
 };
 
 /* device management */
@@ -1470,6 +1748,34 @@ static int s3c2440_serial_resetport(struct uart_port *port,
 	return 0;
 }
 
+#ifdef CONFIG_CPU_FREQ
+static bool s3c2440_serial_policy_adjust_cb(struct uart_port *port,
+		const struct s3c2410_cpufreq_clocks *clocks)
+{
+	struct s3c24xx_uart_port *ourport = to_ourport(port);
+	struct s3c2410_uartcfg *cfg = s3c24xx_port_to_cfg(port);
+	unsigned int baud, quot, calc, diff;
+
+	/* FIXME: this function is not correct unless you only have pclk. */
+
+	/* If we can use uclk/fclk, it should always work. */
+	if (cfg->clocks_size >= 2)
+		return true;
+
+	baud = ourport->baud;
+
+	if (!baud)
+		return true;
+
+	/* Copied from s3c24xx_serial_calcbaud. */
+	quot = (clocks->pclk + (8 * baud)) / (16 * baud);
+	calc = (clocks->pclk / (quot * 16));
+
+	diff = abs((int)baud - (int)calc);
+	return diff*160 < baud*3;	/* diff/baud < 3/160 */
+}
+#endif
+
 static struct s3c24xx_uart_info s3c2440_uart_inf = {
 	.name		= "Samsung S3C2440 UART",
 	.type		= PORT_S3C2440,
@@ -1483,6 +1789,9 @@ static struct s3c24xx_uart_info s3c2440_uart_inf = {
 	.get_clksrc	= s3c2440_serial_getsource,
 	.set_clksrc	= s3c2440_serial_setsource,
 	.reset_port	= s3c2440_serial_resetport,
+#ifdef CONFIG_CPU_FREQ
+	.policy_adjust_cb = s3c2440_serial_policy_adjust_cb,
+#endif
 };
 
 /* device management */
-- 
1.5.4





More information about the openmoko-kernel mailing list