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 <[EMAIL PROTECTED]>
---
 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


Reply via email to