diff options
Diffstat (limited to 'drivers/tty/serial/8250/8250_of.c')
| -rw-r--r-- | drivers/tty/serial/8250/8250_of.c | 225 |
1 files changed, 121 insertions, 104 deletions
diff --git a/drivers/tty/serial/8250/8250_of.c b/drivers/tty/serial/8250/8250_of.c index 1b461fba15a3..9799356b65f7 100644 --- a/drivers/tty/serial/8250/8250_of.c +++ b/drivers/tty/serial/8250/8250_of.c @@ -4,7 +4,10 @@ * * Copyright (C) 2006 Arnd Bergmann <arnd@arndb.de>, IBM Corp. */ + +#include <linux/bits.h> #include <linux/console.h> +#include <linux/math.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/serial_core.h> @@ -15,16 +18,69 @@ #include <linux/pm_runtime.h> #include <linux/clk.h> #include <linux/reset.h> +#include <linux/notifier.h> #include "8250.h" struct of_serial_info { struct clk *clk; + struct clk *bus_clk; struct reset_control *rst; int type; int line; + struct notifier_block clk_notifier; }; +/* Nuvoton NPCM timeout register */ +#define UART_NPCM_TOR 7 +#define UART_NPCM_TOIE BIT(7) /* Timeout Interrupt Enable */ + +static int npcm_startup(struct uart_port *port) +{ + /* + * Nuvoton calls the scratch register 'UART_TOR' (timeout + * register). Enable it, and set TIOC (timeout interrupt + * comparator) to be 0x20 for correct operation. + */ + serial_port_out(port, UART_NPCM_TOR, UART_NPCM_TOIE | 0x20); + + return serial8250_do_startup(port); +} + +/* Nuvoton NPCM UARTs have a custom divisor calculation */ +static unsigned int npcm_get_divisor(struct uart_port *port, unsigned int baud, + unsigned int *frac) +{ + return DIV_ROUND_CLOSEST(port->uartclk, 16 * baud + 2) - 2; +} + +static int npcm_setup(struct uart_port *port) +{ + port->get_divisor = npcm_get_divisor; + port->startup = npcm_startup; + return 0; +} + +static inline struct of_serial_info *clk_nb_to_info(struct notifier_block *nb) +{ + return container_of(nb, struct of_serial_info, clk_notifier); +} + +static int of_platform_serial_clk_notifier_cb(struct notifier_block *nb, unsigned long event, + void *data) +{ + struct of_serial_info *info = clk_nb_to_info(nb); + struct uart_8250_port *port8250 = serial8250_get_port(info->line); + struct clk_notifier_data *ndata = data; + + if (event == POST_RATE_CHANGE) { + serial8250_update_uartclk(&port8250->port, ndata->new_rate); + return NOTIFY_OK; + } + + return NOTIFY_DONE; +} + /* * Fill a struct uart_port for a given device node */ @@ -33,137 +89,78 @@ static int of_platform_serial_setup(struct platform_device *ofdev, struct of_serial_info *info) { struct resource resource; - struct device_node *np = ofdev->dev.of_node; + struct device *dev = &ofdev->dev; + struct device_node *np = dev->of_node; struct uart_port *port = &up->port; - u32 clk, spd, prop; - int ret, irq; + u32 spd; + int ret; - memset(port, 0, sizeof *port); + memset(port, 0, sizeof(*port)); pm_runtime_enable(&ofdev->dev); pm_runtime_get_sync(&ofdev->dev); - if (of_property_read_u32(np, "clock-frequency", &clk)) { - - /* Get clk rate through clk driver if present */ - info->clk = devm_clk_get(&ofdev->dev, NULL); - if (IS_ERR(info->clk)) { - ret = PTR_ERR(info->clk); - if (ret != -EPROBE_DEFER) - dev_warn(&ofdev->dev, - "failed to get clock: %d\n", ret); - goto err_pmruntime; - } - - ret = clk_prepare_enable(info->clk); - if (ret < 0) - goto err_pmruntime; - - clk = clk_get_rate(info->clk); - } - /* If current-speed was set, then try not to change it. */ - if (of_property_read_u32(np, "current-speed", &spd) == 0) - port->custom_divisor = clk / (16 * spd); - ret = of_address_to_resource(np, 0, &resource); if (ret) { - dev_warn(&ofdev->dev, "invalid address\n"); - goto err_unprepare; + dev_err_probe(dev, ret, "invalid address\n"); + goto err_pmruntime; } - port->flags = UPF_SHARE_IRQ | UPF_BOOT_AUTOCONF | UPF_FIXED_PORT | - UPF_FIXED_TYPE; + port->dev = &ofdev->dev; + port->flags = UPF_BOOT_AUTOCONF | UPF_FIXED_PORT | UPF_FIXED_TYPE; spin_lock_init(&port->lock); if (resource_type(&resource) == IORESOURCE_IO) { - port->iotype = UPIO_PORT; port->iobase = resource.start; } else { port->mapbase = resource.start; port->mapsize = resource_size(&resource); - - /* Check for shifted address mapping */ - if (of_property_read_u32(np, "reg-offset", &prop) == 0) { - if (prop >= port->mapsize) { - dev_warn(&ofdev->dev, "reg-offset %u exceeds region size %pa\n", - prop, &port->mapsize); - ret = -EINVAL; - goto err_unprepare; - } - - port->mapbase += prop; - port->mapsize -= prop; - } - - port->iotype = UPIO_MEM; - if (of_property_read_u32(np, "reg-io-width", &prop) == 0) { - switch (prop) { - case 1: - port->iotype = UPIO_MEM; - break; - case 2: - port->iotype = UPIO_MEM16; - break; - case 4: - port->iotype = of_device_is_big_endian(np) ? - UPIO_MEM32BE : UPIO_MEM32; - break; - default: - dev_warn(&ofdev->dev, "unsupported reg-io-width (%d)\n", - prop); - ret = -EINVAL; - goto err_unprepare; - } - } port->flags |= UPF_IOREMAP; } - /* Compatibility with the deprecated pxa driver and 8250_pxa drivers. */ - if (of_device_is_compatible(np, "mrvl,mmp-uart")) - port->regshift = 2; - - /* Check for registers offset within the devices address range */ - if (of_property_read_u32(np, "reg-shift", &prop) == 0) - port->regshift = prop; + ret = uart_read_and_validate_port_properties(port); + if (ret) + goto err_pmruntime; - /* Check for fifo size */ - if (of_property_read_u32(np, "fifo-size", &prop) == 0) - port->fifosize = prop; + /* Get clk rate through clk driver if present */ + if (!port->uartclk) { + struct clk *bus_clk; - /* Check for a fixed line number */ - ret = of_alias_get_id(np, "serial"); - if (ret >= 0) - port->line = ret; + bus_clk = devm_clk_get_optional_enabled(dev, "bus"); + if (IS_ERR(bus_clk)) { + ret = dev_err_probe(dev, PTR_ERR(bus_clk), "failed to get bus clock\n"); + goto err_pmruntime; + } - irq = of_irq_get(np, 0); - if (irq < 0) { - if (irq == -EPROBE_DEFER) { - ret = -EPROBE_DEFER; - goto err_unprepare; + /* If the bus clock is required, core clock must be named */ + info->clk = devm_clk_get_enabled(dev, bus_clk ? "core" : NULL); + if (IS_ERR(info->clk)) { + ret = dev_err_probe(dev, PTR_ERR(info->clk), "failed to get clock\n"); + goto err_pmruntime; } - /* IRQ support not mandatory */ - irq = 0; + + info->bus_clk = bus_clk; + port->uartclk = clk_get_rate(info->clk); } + /* If current-speed was set, then try not to change it. */ + if (of_property_read_u32(np, "current-speed", &spd) == 0) + port->custom_divisor = port->uartclk / (16 * spd); - port->irq = irq; + /* Compatibility with the deprecated pxa driver and 8250_pxa drivers. */ + if (of_device_is_compatible(np, "mrvl,mmp-uart")) + port->regshift = 2; info->rst = devm_reset_control_get_optional_shared(&ofdev->dev, NULL); if (IS_ERR(info->rst)) { ret = PTR_ERR(info->rst); - goto err_unprepare; + goto err_pmruntime; } ret = reset_control_deassert(info->rst); if (ret) - goto err_unprepare; + goto err_pmruntime; port->type = type; - port->uartclk = clk; - - if (of_property_read_bool(np, "no-loopback-test")) - port->flags |= UPF_SKIP_TEST; - - port->dev = &ofdev->dev; port->rs485_config = serial8250_em485_config; port->rs485_supported = serial8250_em485_supported; up->rs485_start_tx = serial8250_em485_start_tx; @@ -171,11 +168,20 @@ static int of_platform_serial_setup(struct platform_device *ofdev, switch (type) { case PORT_RT2880: - port->iotype = UPIO_AU; + ret = rt288x_setup(port); + break; + case PORT_NPCM: + ret = npcm_setup(port); + break; + default: + /* Nothing to do */ + ret = 0; break; } + if (ret) + goto err_pmruntime; - if (IS_ENABLED(CONFIG_SERIAL_8250_FSL) && + if (IS_REACHABLE(CONFIG_SERIAL_8250_FSL) && (of_device_is_compatible(np, "fsl,ns16550") || of_device_is_compatible(np, "fsl,16550-FIFO64"))) { port->handle_irq = fsl8250_handle_irq; @@ -183,8 +189,6 @@ static int of_platform_serial_setup(struct platform_device *ofdev, } return 0; -err_unprepare: - clk_disable_unprepare(info->clk); err_pmruntime: pm_runtime_put_sync(&ofdev->dev); pm_runtime_disable(&ofdev->dev); @@ -246,12 +250,22 @@ static int of_platform_serial_probe(struct platform_device *ofdev) info->type = port_type; info->line = ret; platform_set_drvdata(ofdev, info); + + if (info->clk) { + info->clk_notifier.notifier_call = of_platform_serial_clk_notifier_cb; + ret = clk_notifier_register(info->clk, &info->clk_notifier); + if (ret) { + dev_err_probe(port8250.port.dev, ret, "Failed to set the clock notifier\n"); + goto err_unregister; + } + } + return 0; +err_unregister: + serial8250_unregister_port(info->line); err_dispose: - irq_dispose_mapping(port8250.port.irq); pm_runtime_put_sync(&ofdev->dev); pm_runtime_disable(&ofdev->dev); - clk_disable_unprepare(info->clk); err_free: kfree(info); return ret; @@ -260,18 +274,19 @@ err_free: /* * Release a line */ -static int of_platform_serial_remove(struct platform_device *ofdev) +static void of_platform_serial_remove(struct platform_device *ofdev) { struct of_serial_info *info = platform_get_drvdata(ofdev); + if (info->clk) + clk_notifier_unregister(info->clk, &info->clk_notifier); + serial8250_unregister_port(info->line); reset_control_assert(info->rst); pm_runtime_put_sync(&ofdev->dev); pm_runtime_disable(&ofdev->dev); - clk_disable_unprepare(info->clk); kfree(info); - return 0; } #ifdef CONFIG_PM_SLEEP @@ -286,6 +301,7 @@ static int of_serial_suspend(struct device *dev) if (!uart_console(port) || console_suspend_enabled) { pm_runtime_put_sync(dev); clk_disable_unprepare(info->clk); + clk_disable_unprepare(info->bus_clk); } return 0; } @@ -298,6 +314,7 @@ static int of_serial_resume(struct device *dev) if (!uart_console(port) || console_suspend_enabled) { pm_runtime_get_sync(dev); + clk_prepare_enable(info->bus_clk); clk_prepare_enable(info->clk); } |
