Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 | // SPDX-License-Identifier: GPL-2.0
/*
* Serial port driver for BCM2835AUX UART
*
* Copyright (C) 2016 Martin Sperl <kernel@martin.sperl.org>
*
* Based on 8250_lpc18xx.c:
* Copyright (C) 2015 Joachim Eastwood <manabian@gmail.com>
*
* The bcm2835aux is capable of RTS auto flow-control, but this driver doesn't
* take advantage of it yet. When adding support, be sure not to enable it
* simultaneously to rs485.
*/
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include "8250.h"
#define BCM2835_AUX_UART_CNTL 8
#define BCM2835_AUX_UART_CNTL_RXEN 0x01 /* Receiver enable */
#define BCM2835_AUX_UART_CNTL_TXEN 0x02 /* Transmitter enable */
#define BCM2835_AUX_UART_CNTL_AUTORTS 0x04 /* RTS set by RX fill level */
#define BCM2835_AUX_UART_CNTL_AUTOCTS 0x08 /* CTS stops transmitter */
#define BCM2835_AUX_UART_CNTL_RTS3 0x00 /* RTS set until 3 chars left */
#define BCM2835_AUX_UART_CNTL_RTS2 0x10 /* RTS set until 2 chars left */
#define BCM2835_AUX_UART_CNTL_RTS1 0x20 /* RTS set until 1 chars left */
#define BCM2835_AUX_UART_CNTL_RTS4 0x30 /* RTS set until 4 chars left */
#define BCM2835_AUX_UART_CNTL_RTSINV 0x40 /* Invert auto RTS polarity */
#define BCM2835_AUX_UART_CNTL_CTSINV 0x80 /* Invert auto CTS polarity */
/**
* struct bcm2835aux_data - driver private data of BCM2835 auxiliary UART
* @clk: clock producer of the port's uartclk
* @line: index of the port's serial8250_ports[] entry
* @cntl: cached copy of CNTL register
*/
struct bcm2835aux_data {
struct clk *clk;
int line;
u32 cntl;
};
static void bcm2835aux_rs485_start_tx(struct uart_8250_port *up)
{
if (!(up->port.rs485.flags & SER_RS485_RX_DURING_TX)) {
struct bcm2835aux_data *data = dev_get_drvdata(up->port.dev);
data->cntl &= ~BCM2835_AUX_UART_CNTL_RXEN;
serial_out(up, BCM2835_AUX_UART_CNTL, data->cntl);
}
/*
* On the bcm2835aux, the MCR register contains no other
* flags besides RTS. So no need for a read-modify-write.
*/
if (up->port.rs485.flags & SER_RS485_RTS_ON_SEND)
serial8250_out_MCR(up, 0);
else
serial8250_out_MCR(up, UART_MCR_RTS);
}
static void bcm2835aux_rs485_stop_tx(struct uart_8250_port *up)
{
if (up->port.rs485.flags & SER_RS485_RTS_AFTER_SEND)
serial8250_out_MCR(up, 0);
else
serial8250_out_MCR(up, UART_MCR_RTS);
if (!(up->port.rs485.flags & SER_RS485_RX_DURING_TX)) {
struct bcm2835aux_data *data = dev_get_drvdata(up->port.dev);
data->cntl |= BCM2835_AUX_UART_CNTL_RXEN;
serial_out(up, BCM2835_AUX_UART_CNTL, data->cntl);
}
}
static int bcm2835aux_serial_probe(struct platform_device *pdev)
{
struct uart_8250_port up = { };
struct bcm2835aux_data *data;
struct resource *res;
int ret;
/* allocate the custom structure */
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
/* initialize data */
up.capabilities = UART_CAP_FIFO | UART_CAP_MINI;
up.port.dev = &pdev->dev;
up.port.regshift = 2;
up.port.type = PORT_16550;
up.port.iotype = UPIO_MEM;
up.port.fifosize = 8;
up.port.flags = UPF_SHARE_IRQ | UPF_FIXED_PORT | UPF_FIXED_TYPE |
UPF_SKIP_TEST | UPF_IOREMAP;
up.port.rs485_config = serial8250_em485_config;
up.rs485_start_tx = bcm2835aux_rs485_start_tx;
up.rs485_stop_tx = bcm2835aux_rs485_stop_tx;
/* initialize cached copy with power-on reset value */
data->cntl = BCM2835_AUX_UART_CNTL_RXEN | BCM2835_AUX_UART_CNTL_TXEN;
platform_set_drvdata(pdev, data);
/* get the clock - this also enables the HW */
data->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(data->clk))
return dev_err_probe(&pdev->dev, PTR_ERR(data->clk), "could not get clk\n");
/* get the interrupt */
ret = platform_get_irq(pdev, 0);
if (ret < 0)
return ret;
up.port.irq = ret;
/* map the main registers */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "memory resource not found");
return -EINVAL;
}
up.port.mapbase = res->start;
up.port.mapsize = resource_size(res);
/* Check for a fixed line number */
ret = of_alias_get_id(pdev->dev.of_node, "serial");
if (ret >= 0)
up.port.line = ret;
/* enable the clock as a last step */
ret = clk_prepare_enable(data->clk);
if (ret) {
dev_err(&pdev->dev, "unable to enable uart clock - %d\n",
ret);
return ret;
}
/* the HW-clock divider for bcm2835aux is 8,
* but 8250 expects a divider of 16,
* so we have to multiply the actual clock by 2
* to get identical baudrates.
*/
up.port.uartclk = clk_get_rate(data->clk) * 2;
/* register the port */
ret = serial8250_register_8250_port(&up);
if (ret < 0) {
dev_err_probe(&pdev->dev, ret, "unable to register 8250 port\n");
goto dis_clk;
}
data->line = ret;
return 0;
dis_clk:
clk_disable_unprepare(data->clk);
return ret;
}
static int bcm2835aux_serial_remove(struct platform_device *pdev)
{
struct bcm2835aux_data *data = platform_get_drvdata(pdev);
serial8250_unregister_port(data->line);
clk_disable_unprepare(data->clk);
return 0;
}
static const struct of_device_id bcm2835aux_serial_match[] = {
{ .compatible = "brcm,bcm2835-aux-uart" },
{ },
};
MODULE_DEVICE_TABLE(of, bcm2835aux_serial_match);
static struct platform_driver bcm2835aux_serial_driver = {
.driver = {
.name = "bcm2835-aux-uart",
.of_match_table = bcm2835aux_serial_match,
},
.probe = bcm2835aux_serial_probe,
.remove = bcm2835aux_serial_remove,
};
module_platform_driver(bcm2835aux_serial_driver);
#ifdef CONFIG_SERIAL_8250_CONSOLE
static int __init early_bcm2835aux_setup(struct earlycon_device *device,
const char *options)
{
if (!device->port.membase)
return -ENODEV;
device->port.iotype = UPIO_MEM32;
device->port.regshift = 2;
return early_serial8250_setup(device, NULL);
}
OF_EARLYCON_DECLARE(bcm2835aux, "brcm,bcm2835-aux-uart",
early_bcm2835aux_setup);
#endif
MODULE_DESCRIPTION("BCM2835 auxiliar UART driver");
MODULE_AUTHOR("Martin Sperl <kernel@martin.sperl.org>");
MODULE_LICENSE("GPL v2");
|