[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