Commit c4b0510c authored by Soren Brinkmann's avatar Soren Brinkmann Committed by Greg Kroah-Hartman

tty: xuartps: Dynamically adjust to input frequency changes

Add a clock notifier to dynamically handle frequency changes of the
input clock by reprogramming the UART in order to keep the baud rate
constant.
Signed-off-by: default avatarSoren Brinkmann <soren.brinkmann@xilinx.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent e6b39bfd
...@@ -163,13 +163,20 @@ MODULE_PARM_DESC(rx_timeout, "Rx timeout, 1-255"); ...@@ -163,13 +163,20 @@ MODULE_PARM_DESC(rx_timeout, "Rx timeout, 1-255");
/** /**
* struct xuartps - device data * struct xuartps - device data
* @refclk Reference clock * @port Pointer to the UART port
* @aperclk APB clock * @refclk Reference clock
* @aperclk APB clock
* @baud Current baud rate
* @clk_rate_change_nb Notifier block for clock changes
*/ */
struct xuartps { struct xuartps {
struct uart_port *port;
struct clk *refclk; struct clk *refclk;
struct clk *aperclk; struct clk *aperclk;
unsigned int baud;
struct notifier_block clk_rate_change_nb;
}; };
#define to_xuartps(_nb) container_of(_nb, struct xuartps, clk_rate_change_nb);
/** /**
* xuartps_isr - Interrupt handler * xuartps_isr - Interrupt handler
...@@ -385,6 +392,7 @@ static unsigned int xuartps_set_baud_rate(struct uart_port *port, ...@@ -385,6 +392,7 @@ static unsigned int xuartps_set_baud_rate(struct uart_port *port,
u32 cd, bdiv; u32 cd, bdiv;
u32 mreg; u32 mreg;
int div8; int div8;
struct xuartps *xuartps = port->private_data;
calc_baud = xuartps_calc_baud_divs(port->uartclk, baud, &bdiv, &cd, calc_baud = xuartps_calc_baud_divs(port->uartclk, baud, &bdiv, &cd,
&div8); &div8);
...@@ -398,10 +406,105 @@ static unsigned int xuartps_set_baud_rate(struct uart_port *port, ...@@ -398,10 +406,105 @@ static unsigned int xuartps_set_baud_rate(struct uart_port *port,
xuartps_writel(mreg, XUARTPS_MR_OFFSET); xuartps_writel(mreg, XUARTPS_MR_OFFSET);
xuartps_writel(cd, XUARTPS_BAUDGEN_OFFSET); xuartps_writel(cd, XUARTPS_BAUDGEN_OFFSET);
xuartps_writel(bdiv, XUARTPS_BAUDDIV_OFFSET); xuartps_writel(bdiv, XUARTPS_BAUDDIV_OFFSET);
xuartps->baud = baud;
return calc_baud; return calc_baud;
} }
/**
* xuartps_clk_notitifer_cb - Clock notifier callback
* @nb: Notifier block
* @event: Notify event
* @data: Notifier data
* Returns NOTIFY_OK on success, NOTIFY_BAD on error.
*/
static int xuartps_clk_notifier_cb(struct notifier_block *nb,
unsigned long event, void *data)
{
u32 ctrl_reg;
struct uart_port *port;
int locked = 0;
struct clk_notifier_data *ndata = data;
unsigned long flags = 0;
struct xuartps *xuartps = to_xuartps(nb);
port = xuartps->port;
if (port->suspended)
return NOTIFY_OK;
switch (event) {
case PRE_RATE_CHANGE:
{
u32 bdiv;
u32 cd;
int div8;
/*
* Find out if current baud-rate can be achieved with new clock
* frequency.
*/
if (!xuartps_calc_baud_divs(ndata->new_rate, xuartps->baud,
&bdiv, &cd, &div8))
return NOTIFY_BAD;
spin_lock_irqsave(&xuartps->port->lock, flags);
/* Disable the TX and RX to set baud rate */
xuartps_writel(xuartps_readl(XUARTPS_CR_OFFSET) |
(XUARTPS_CR_TX_DIS | XUARTPS_CR_RX_DIS),
XUARTPS_CR_OFFSET);
spin_unlock_irqrestore(&xuartps->port->lock, flags);
return NOTIFY_OK;
}
case POST_RATE_CHANGE:
/*
* Set clk dividers to generate correct baud with new clock
* frequency.
*/
spin_lock_irqsave(&xuartps->port->lock, flags);
locked = 1;
port->uartclk = ndata->new_rate;
xuartps->baud = xuartps_set_baud_rate(xuartps->port,
xuartps->baud);
/* fall through */
case ABORT_RATE_CHANGE:
if (!locked)
spin_lock_irqsave(&xuartps->port->lock, flags);
/* Set TX/RX Reset */
xuartps_writel(xuartps_readl(XUARTPS_CR_OFFSET) |
(XUARTPS_CR_TXRST | XUARTPS_CR_RXRST),
XUARTPS_CR_OFFSET);
while (xuartps_readl(XUARTPS_CR_OFFSET) &
(XUARTPS_CR_TXRST | XUARTPS_CR_RXRST))
cpu_relax();
/*
* Clear the RX disable and TX disable bits and then set the TX
* enable bit and RX enable bit to enable the transmitter and
* receiver.
*/
xuartps_writel(rx_timeout, XUARTPS_RXTOUT_OFFSET);
ctrl_reg = xuartps_readl(XUARTPS_CR_OFFSET);
xuartps_writel(
(ctrl_reg & ~(XUARTPS_CR_TX_DIS | XUARTPS_CR_RX_DIS)) |
(XUARTPS_CR_TX_EN | XUARTPS_CR_RX_EN),
XUARTPS_CR_OFFSET);
spin_unlock_irqrestore(&xuartps->port->lock, flags);
return NOTIFY_OK;
default:
return NOTIFY_DONE;
}
}
/*----------------------Uart Operations---------------------------*/ /*----------------------Uart Operations---------------------------*/
/** /**
...@@ -1164,13 +1267,19 @@ static int xuartps_probe(struct platform_device *pdev) ...@@ -1164,13 +1267,19 @@ static int xuartps_probe(struct platform_device *pdev)
goto err_out_clk_disable; goto err_out_clk_disable;
} }
xuartps_data->clk_rate_change_nb.notifier_call =
xuartps_clk_notifier_cb;
if (clk_notifier_register(xuartps_data->refclk,
&xuartps_data->clk_rate_change_nb))
dev_warn(&pdev->dev, "Unable to register clock notifier.\n");
/* Initialize the port structure */ /* Initialize the port structure */
port = xuartps_get_port(); port = xuartps_get_port();
if (!port) { if (!port) {
dev_err(&pdev->dev, "Cannot get uart_port structure\n"); dev_err(&pdev->dev, "Cannot get uart_port structure\n");
rc = -ENODEV; rc = -ENODEV;
goto err_out_clk_disable; goto err_out_notif_unreg;
} else { } else {
/* Register the port. /* Register the port.
* This function also registers this device with the tty layer * This function also registers this device with the tty layer
...@@ -1181,16 +1290,20 @@ static int xuartps_probe(struct platform_device *pdev) ...@@ -1181,16 +1290,20 @@ static int xuartps_probe(struct platform_device *pdev)
port->dev = &pdev->dev; port->dev = &pdev->dev;
port->uartclk = clk_get_rate(xuartps_data->refclk); port->uartclk = clk_get_rate(xuartps_data->refclk);
port->private_data = xuartps_data; port->private_data = xuartps_data;
xuartps_data->port = port;
platform_set_drvdata(pdev, port); platform_set_drvdata(pdev, port);
rc = uart_add_one_port(&xuartps_uart_driver, port); rc = uart_add_one_port(&xuartps_uart_driver, port);
if (rc) { if (rc) {
dev_err(&pdev->dev, dev_err(&pdev->dev,
"uart_add_one_port() failed; err=%i\n", rc); "uart_add_one_port() failed; err=%i\n", rc);
goto err_out_clk_disable; goto err_out_notif_unreg;
} }
return 0; return 0;
} }
err_out_notif_unreg:
clk_notifier_unregister(xuartps_data->refclk,
&xuartps_data->clk_rate_change_nb);
err_out_clk_disable: err_out_clk_disable:
clk_disable_unprepare(xuartps_data->refclk); clk_disable_unprepare(xuartps_data->refclk);
err_out_clk_dis_aper: err_out_clk_dis_aper:
...@@ -1212,6 +1325,8 @@ static int xuartps_remove(struct platform_device *pdev) ...@@ -1212,6 +1325,8 @@ static int xuartps_remove(struct platform_device *pdev)
int rc; int rc;
/* Remove the xuartps port from the serial core */ /* Remove the xuartps port from the serial core */
clk_notifier_unregister(xuartps_data->refclk,
&xuartps_data->clk_rate_change_nb);
rc = uart_remove_one_port(&xuartps_uart_driver, port); rc = uart_remove_one_port(&xuartps_uart_driver, port);
port->mapbase = 0; port->mapbase = 0;
clk_disable_unprepare(xuartps_data->refclk); clk_disable_unprepare(xuartps_data->refclk);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment