sc16is7xx: support multiple devices
We currently register the uart driver during device probe which makes it hard to support more than one chip. Move the driver registration to module init/exit time and preallocate space for up to 8 lines (4-8 chips). Reported-by: Michael Allwright <michael.allwright@upb.de> Signed-off-by: Jakub Kicinski <kubakici@wp.pl> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
e92a886bf7
commit
c64349722d
1 changed files with 49 additions and 24 deletions
|
@ -11,6 +11,8 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||||
|
|
||||||
#include <linux/bitops.h>
|
#include <linux/bitops.h>
|
||||||
#include <linux/clk.h>
|
#include <linux/clk.h>
|
||||||
#include <linux/delay.h>
|
#include <linux/delay.h>
|
||||||
|
@ -29,6 +31,7 @@
|
||||||
#include <linux/uaccess.h>
|
#include <linux/uaccess.h>
|
||||||
|
|
||||||
#define SC16IS7XX_NAME "sc16is7xx"
|
#define SC16IS7XX_NAME "sc16is7xx"
|
||||||
|
#define SC16IS7XX_MAX_DEVS 8
|
||||||
|
|
||||||
/* SC16IS7XX register definitions */
|
/* SC16IS7XX register definitions */
|
||||||
#define SC16IS7XX_RHR_REG (0x00) /* RX FIFO */
|
#define SC16IS7XX_RHR_REG (0x00) /* RX FIFO */
|
||||||
|
@ -319,7 +322,6 @@ struct sc16is7xx_one {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc16is7xx_port {
|
struct sc16is7xx_port {
|
||||||
struct uart_driver uart;
|
|
||||||
struct sc16is7xx_devtype *devtype;
|
struct sc16is7xx_devtype *devtype;
|
||||||
struct regmap *regmap;
|
struct regmap *regmap;
|
||||||
struct clk *clk;
|
struct clk *clk;
|
||||||
|
@ -333,6 +335,14 @@ struct sc16is7xx_port {
|
||||||
struct sc16is7xx_one p[0];
|
struct sc16is7xx_one p[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static unsigned long sc16is7xx_lines;
|
||||||
|
|
||||||
|
static struct uart_driver sc16is7xx_uart = {
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.dev_name = "ttySC",
|
||||||
|
.nr = SC16IS7XX_MAX_DEVS,
|
||||||
|
};
|
||||||
|
|
||||||
#define to_sc16is7xx_port(p,e) ((container_of((p), struct sc16is7xx_port, e)))
|
#define to_sc16is7xx_port(p,e) ((container_of((p), struct sc16is7xx_port, e)))
|
||||||
#define to_sc16is7xx_one(p,e) ((container_of((p), struct sc16is7xx_one, e)))
|
#define to_sc16is7xx_one(p,e) ((container_of((p), struct sc16is7xx_one, e)))
|
||||||
|
|
||||||
|
@ -394,6 +404,18 @@ static void sc16is7xx_port_update(struct uart_port *port, u8 reg,
|
||||||
mask, val);
|
mask, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int sc16is7xx_alloc_line(void)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
BUILD_BUG_ON(SC16IS7XX_MAX_DEVS > BITS_PER_LONG);
|
||||||
|
|
||||||
|
for (i = 0; i < SC16IS7XX_MAX_DEVS; i++)
|
||||||
|
if (!test_and_set_bit(i, &sc16is7xx_lines))
|
||||||
|
break;
|
||||||
|
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
static void sc16is7xx_power(struct uart_port *port, int on)
|
static void sc16is7xx_power(struct uart_port *port, int on)
|
||||||
{
|
{
|
||||||
|
@ -671,7 +693,7 @@ static void sc16is7xx_ist(struct kthread_work *ws)
|
||||||
struct sc16is7xx_port *s = to_sc16is7xx_port(ws, irq_work);
|
struct sc16is7xx_port *s = to_sc16is7xx_port(ws, irq_work);
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
for (i = 0; i < s->uart.nr; ++i)
|
for (i = 0; i < s->devtype->nr_uart; ++i)
|
||||||
sc16is7xx_port_irq(s, i);
|
sc16is7xx_port_irq(s, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1144,23 +1166,13 @@ static int sc16is7xx_probe(struct device *dev,
|
||||||
s->devtype = devtype;
|
s->devtype = devtype;
|
||||||
dev_set_drvdata(dev, s);
|
dev_set_drvdata(dev, s);
|
||||||
|
|
||||||
/* Register UART driver */
|
|
||||||
s->uart.owner = THIS_MODULE;
|
|
||||||
s->uart.dev_name = "ttySC";
|
|
||||||
s->uart.nr = devtype->nr_uart;
|
|
||||||
ret = uart_register_driver(&s->uart);
|
|
||||||
if (ret) {
|
|
||||||
dev_err(dev, "Registering UART driver failed\n");
|
|
||||||
goto out_clk;
|
|
||||||
}
|
|
||||||
|
|
||||||
init_kthread_worker(&s->kworker);
|
init_kthread_worker(&s->kworker);
|
||||||
init_kthread_work(&s->irq_work, sc16is7xx_ist);
|
init_kthread_work(&s->irq_work, sc16is7xx_ist);
|
||||||
s->kworker_task = kthread_run(kthread_worker_fn, &s->kworker,
|
s->kworker_task = kthread_run(kthread_worker_fn, &s->kworker,
|
||||||
"sc16is7xx");
|
"sc16is7xx");
|
||||||
if (IS_ERR(s->kworker_task)) {
|
if (IS_ERR(s->kworker_task)) {
|
||||||
ret = PTR_ERR(s->kworker_task);
|
ret = PTR_ERR(s->kworker_task);
|
||||||
goto out_uart;
|
goto out_clk;
|
||||||
}
|
}
|
||||||
sched_setscheduler(s->kworker_task, SCHED_FIFO, &sched_param);
|
sched_setscheduler(s->kworker_task, SCHED_FIFO, &sched_param);
|
||||||
|
|
||||||
|
@ -1186,7 +1198,6 @@ static int sc16is7xx_probe(struct device *dev,
|
||||||
for (i = 0; i < devtype->nr_uart; ++i) {
|
for (i = 0; i < devtype->nr_uart; ++i) {
|
||||||
s->p[i].line = i;
|
s->p[i].line = i;
|
||||||
/* Initialize port data */
|
/* Initialize port data */
|
||||||
s->p[i].port.line = i;
|
|
||||||
s->p[i].port.dev = dev;
|
s->p[i].port.dev = dev;
|
||||||
s->p[i].port.irq = irq;
|
s->p[i].port.irq = irq;
|
||||||
s->p[i].port.type = PORT_SC16IS7XX;
|
s->p[i].port.type = PORT_SC16IS7XX;
|
||||||
|
@ -1196,6 +1207,12 @@ static int sc16is7xx_probe(struct device *dev,
|
||||||
s->p[i].port.uartclk = freq;
|
s->p[i].port.uartclk = freq;
|
||||||
s->p[i].port.rs485_config = sc16is7xx_config_rs485;
|
s->p[i].port.rs485_config = sc16is7xx_config_rs485;
|
||||||
s->p[i].port.ops = &sc16is7xx_ops;
|
s->p[i].port.ops = &sc16is7xx_ops;
|
||||||
|
s->p[i].port.line = sc16is7xx_alloc_line();
|
||||||
|
if (s->p[i].port.line >= SC16IS7XX_MAX_DEVS) {
|
||||||
|
ret = -ENOMEM;
|
||||||
|
goto out_ports;
|
||||||
|
}
|
||||||
|
|
||||||
/* Disable all interrupts */
|
/* Disable all interrupts */
|
||||||
sc16is7xx_port_write(&s->p[i].port, SC16IS7XX_IER_REG, 0);
|
sc16is7xx_port_write(&s->p[i].port, SC16IS7XX_IER_REG, 0);
|
||||||
/* Disable TX/RX */
|
/* Disable TX/RX */
|
||||||
|
@ -1206,7 +1223,7 @@ static int sc16is7xx_probe(struct device *dev,
|
||||||
init_kthread_work(&s->p[i].tx_work, sc16is7xx_tx_proc);
|
init_kthread_work(&s->p[i].tx_work, sc16is7xx_tx_proc);
|
||||||
init_kthread_work(&s->p[i].reg_work, sc16is7xx_reg_proc);
|
init_kthread_work(&s->p[i].reg_work, sc16is7xx_reg_proc);
|
||||||
/* Register port */
|
/* Register port */
|
||||||
uart_add_one_port(&s->uart, &s->p[i].port);
|
uart_add_one_port(&sc16is7xx_uart, &s->p[i].port);
|
||||||
/* Go to suspend mode */
|
/* Go to suspend mode */
|
||||||
sc16is7xx_power(&s->p[i].port, 0);
|
sc16is7xx_power(&s->p[i].port, 0);
|
||||||
}
|
}
|
||||||
|
@ -1217,8 +1234,11 @@ static int sc16is7xx_probe(struct device *dev,
|
||||||
if (!ret)
|
if (!ret)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
for (i = 0; i < s->uart.nr; i++)
|
out_ports:
|
||||||
uart_remove_one_port(&s->uart, &s->p[i].port);
|
for (i--; i >= 0; i--) {
|
||||||
|
uart_remove_one_port(&sc16is7xx_uart, &s->p[i].port);
|
||||||
|
clear_bit(s->p[i].port.line, &sc16is7xx_lines);
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_GPIOLIB
|
#ifdef CONFIG_GPIOLIB
|
||||||
if (devtype->nr_gpio)
|
if (devtype->nr_gpio)
|
||||||
|
@ -1228,9 +1248,6 @@ out_thread:
|
||||||
#endif
|
#endif
|
||||||
kthread_stop(s->kworker_task);
|
kthread_stop(s->kworker_task);
|
||||||
|
|
||||||
out_uart:
|
|
||||||
uart_unregister_driver(&s->uart);
|
|
||||||
|
|
||||||
out_clk:
|
out_clk:
|
||||||
if (!IS_ERR(s->clk))
|
if (!IS_ERR(s->clk))
|
||||||
clk_disable_unprepare(s->clk);
|
clk_disable_unprepare(s->clk);
|
||||||
|
@ -1248,15 +1265,15 @@ static int sc16is7xx_remove(struct device *dev)
|
||||||
gpiochip_remove(&s->gpio);
|
gpiochip_remove(&s->gpio);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
for (i = 0; i < s->uart.nr; i++) {
|
for (i = 0; i < s->devtype->nr_uart; i++) {
|
||||||
uart_remove_one_port(&s->uart, &s->p[i].port);
|
uart_remove_one_port(&sc16is7xx_uart, &s->p[i].port);
|
||||||
|
clear_bit(s->p[i].port.line, &sc16is7xx_lines);
|
||||||
sc16is7xx_power(&s->p[i].port, 0);
|
sc16is7xx_power(&s->p[i].port, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
flush_kthread_worker(&s->kworker);
|
flush_kthread_worker(&s->kworker);
|
||||||
kthread_stop(s->kworker_task);
|
kthread_stop(s->kworker_task);
|
||||||
|
|
||||||
uart_unregister_driver(&s->uart);
|
|
||||||
if (!IS_ERR(s->clk))
|
if (!IS_ERR(s->clk))
|
||||||
clk_disable_unprepare(s->clk);
|
clk_disable_unprepare(s->clk);
|
||||||
|
|
||||||
|
@ -1408,7 +1425,14 @@ MODULE_ALIAS("i2c:sc16is7xx");
|
||||||
|
|
||||||
static int __init sc16is7xx_init(void)
|
static int __init sc16is7xx_init(void)
|
||||||
{
|
{
|
||||||
int ret = 0;
|
int ret;
|
||||||
|
|
||||||
|
ret = uart_register_driver(&sc16is7xx_uart);
|
||||||
|
if (ret) {
|
||||||
|
pr_err("Registering UART driver failed\n");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_SERIAL_SC16IS7XX_I2C
|
#ifdef CONFIG_SERIAL_SC16IS7XX_I2C
|
||||||
ret = i2c_add_driver(&sc16is7xx_i2c_uart_driver);
|
ret = i2c_add_driver(&sc16is7xx_i2c_uart_driver);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
|
@ -1437,6 +1461,7 @@ static void __exit sc16is7xx_exit(void)
|
||||||
#ifdef CONFIG_SERIAL_SC16IS7XX_SPI
|
#ifdef CONFIG_SERIAL_SC16IS7XX_SPI
|
||||||
spi_unregister_driver(&sc16is7xx_spi_uart_driver);
|
spi_unregister_driver(&sc16is7xx_spi_uart_driver);
|
||||||
#endif
|
#endif
|
||||||
|
uart_unregister_driver(&sc16is7xx_uart);
|
||||||
}
|
}
|
||||||
module_exit(sc16is7xx_exit);
|
module_exit(sc16is7xx_exit);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue