diff options
Diffstat (limited to 'drivers/tty/serial/sccnxp.c')
| -rw-r--r-- | drivers/tty/serial/sccnxp.c | 171 | 
1 files changed, 113 insertions, 58 deletions
| diff --git a/drivers/tty/serial/sccnxp.c b/drivers/tty/serial/sccnxp.c index e869eab180be..08dbfb88d42c 100644 --- a/drivers/tty/serial/sccnxp.c +++ b/drivers/tty/serial/sccnxp.c @@ -24,8 +24,9 @@  #include <linux/io.h>  #include <linux/tty.h>  #include <linux/tty_flip.h> +#include <linux/spinlock.h>  #include <linux/platform_device.h> -#include <linux/platform_data/sccnxp.h> +#include <linux/platform_data/serial-sccnxp.h>  #define SCCNXP_NAME			"uart-sccnxp"  #define SCCNXP_MAJOR			204 @@ -107,6 +108,7 @@ enum {  struct sccnxp_port {  	struct uart_driver	uart;  	struct uart_port	port[SCCNXP_MAX_UARTS]; +	bool			opened[SCCNXP_MAX_UARTS];  	const char		*name;  	int			irq; @@ -123,7 +125,10 @@ struct sccnxp_port {  	struct console		console;  #endif -	struct mutex		sccnxp_mutex; +	spinlock_t		lock; + +	bool			poll; +	struct timer_list	timer;  	struct sccnxp_pdata	pdata;  }; @@ -175,14 +180,12 @@ static int sccnxp_update_best_err(int a, int b, int *besterr)  	return 1;  } -struct baud_table { +static const struct {  	u8	csr;  	u8	acr;  	u8	mr0;  	int	baud; -}; - -const struct baud_table baud_std[] = { +} baud_std[] = {  	{ 0,	ACR_BAUD0,	MR0_BAUD_NORMAL,	50, },  	{ 0,	ACR_BAUD1,	MR0_BAUD_NORMAL,	75, },  	{ 1,	ACR_BAUD0,	MR0_BAUD_NORMAL,	110, }, @@ -286,10 +289,6 @@ static void sccnxp_handle_rx(struct uart_port *port)  {  	u8 sr;  	unsigned int ch, flag; -	struct tty_struct *tty = tty_port_tty_get(&port->state->port); - -	if (!tty) -		return;  	for (;;) {  		sr = sccnxp_port_read(port, SCCNXP_SR_REG); @@ -305,14 +304,19 @@ static void sccnxp_handle_rx(struct uart_port *port)  		if (unlikely(sr)) {  			if (sr & SR_BRK) {  				port->icount.brk++; +				sccnxp_port_write(port, SCCNXP_CR_REG, +						  CR_CMD_BREAK_RESET);  				if (uart_handle_break(port))  					continue;  			} else if (sr & SR_PE)  				port->icount.parity++;  			else if (sr & SR_FE)  				port->icount.frame++; -			else if (sr & SR_OVR) +			else if (sr & SR_OVR) {  				port->icount.overrun++; +				sccnxp_port_write(port, SCCNXP_CR_REG, +						  CR_CMD_STATUS_RESET); +			}  			sr &= port->read_status_mask;  			if (sr & SR_BRK) @@ -334,9 +338,7 @@ static void sccnxp_handle_rx(struct uart_port *port)  		uart_insert_char(port, sr, SR_OVR, ch, flag);  	} -	tty_flip_buffer_push(tty); - -	tty_kref_put(tty); +	tty_flip_buffer_push(&port->state->port);  }  static void sccnxp_handle_tx(struct uart_port *port) @@ -378,31 +380,48 @@ static void sccnxp_handle_tx(struct uart_port *port)  		uart_write_wakeup(port);  } -static irqreturn_t sccnxp_ist(int irq, void *dev_id) +static void sccnxp_handle_events(struct sccnxp_port *s)  {  	int i;  	u8 isr; -	struct sccnxp_port *s = (struct sccnxp_port *)dev_id; - -	mutex_lock(&s->sccnxp_mutex); -	for (;;) { +	do {  		isr = sccnxp_read(&s->port[0], SCCNXP_ISR_REG);  		isr &= s->imr;  		if (!isr)  			break; -		dev_dbg(s->port[0].dev, "IRQ status: 0x%02x\n", isr); -  		for (i = 0; i < s->uart.nr; i++) { -			if (isr & ISR_RXRDY(i)) +			if (s->opened[i] && (isr & ISR_RXRDY(i)))  				sccnxp_handle_rx(&s->port[i]); -			if (isr & ISR_TXRDY(i)) +			if (s->opened[i] && (isr & ISR_TXRDY(i)))  				sccnxp_handle_tx(&s->port[i]);  		} -	} +	} while (1); +} + +static void sccnxp_timer(unsigned long data) +{ +	struct sccnxp_port *s = (struct sccnxp_port *)data; +	unsigned long flags; -	mutex_unlock(&s->sccnxp_mutex); +	spin_lock_irqsave(&s->lock, flags); +	sccnxp_handle_events(s); +	spin_unlock_irqrestore(&s->lock, flags); + +	if (!timer_pending(&s->timer)) +		mod_timer(&s->timer, jiffies + +			  usecs_to_jiffies(s->pdata.poll_time_us)); +} + +static irqreturn_t sccnxp_ist(int irq, void *dev_id) +{ +	struct sccnxp_port *s = (struct sccnxp_port *)dev_id; +	unsigned long flags; + +	spin_lock_irqsave(&s->lock, flags); +	sccnxp_handle_events(s); +	spin_unlock_irqrestore(&s->lock, flags);  	return IRQ_HANDLED;  } @@ -410,8 +429,9 @@ static irqreturn_t sccnxp_ist(int irq, void *dev_id)  static void sccnxp_start_tx(struct uart_port *port)  {  	struct sccnxp_port *s = dev_get_drvdata(port->dev); +	unsigned long flags; -	mutex_lock(&s->sccnxp_mutex); +	spin_lock_irqsave(&s->lock, flags);  	/* Set direction to output */  	if (s->flags & SCCNXP_HAVE_IO) @@ -419,7 +439,7 @@ static void sccnxp_start_tx(struct uart_port *port)  	sccnxp_enable_irq(port, IMR_TXRDY); -	mutex_unlock(&s->sccnxp_mutex); +	spin_unlock_irqrestore(&s->lock, flags);  }  static void sccnxp_stop_tx(struct uart_port *port) @@ -430,20 +450,22 @@ static void sccnxp_stop_tx(struct uart_port *port)  static void sccnxp_stop_rx(struct uart_port *port)  {  	struct sccnxp_port *s = dev_get_drvdata(port->dev); +	unsigned long flags; -	mutex_lock(&s->sccnxp_mutex); +	spin_lock_irqsave(&s->lock, flags);  	sccnxp_port_write(port, SCCNXP_CR_REG, CR_RX_DISABLE); -	mutex_unlock(&s->sccnxp_mutex); +	spin_unlock_irqrestore(&s->lock, flags);  }  static unsigned int sccnxp_tx_empty(struct uart_port *port)  {  	u8 val; +	unsigned long flags;  	struct sccnxp_port *s = dev_get_drvdata(port->dev); -	mutex_lock(&s->sccnxp_mutex); +	spin_lock_irqsave(&s->lock, flags);  	val = sccnxp_port_read(port, SCCNXP_SR_REG); -	mutex_unlock(&s->sccnxp_mutex); +	spin_unlock_irqrestore(&s->lock, flags);  	return (val & SR_TXEMT) ? TIOCSER_TEMT : 0;  } @@ -456,28 +478,30 @@ static void sccnxp_enable_ms(struct uart_port *port)  static void sccnxp_set_mctrl(struct uart_port *port, unsigned int mctrl)  {  	struct sccnxp_port *s = dev_get_drvdata(port->dev); +	unsigned long flags;  	if (!(s->flags & SCCNXP_HAVE_IO))  		return; -	mutex_lock(&s->sccnxp_mutex); +	spin_lock_irqsave(&s->lock, flags);  	sccnxp_set_bit(port, DTR_OP, mctrl & TIOCM_DTR);  	sccnxp_set_bit(port, RTS_OP, mctrl & TIOCM_RTS); -	mutex_unlock(&s->sccnxp_mutex); +	spin_unlock_irqrestore(&s->lock, flags);  }  static unsigned int sccnxp_get_mctrl(struct uart_port *port)  {  	u8 bitmask, ipr; +	unsigned long flags;  	struct sccnxp_port *s = dev_get_drvdata(port->dev);  	unsigned int mctrl = TIOCM_DSR | TIOCM_CTS | TIOCM_CAR;  	if (!(s->flags & SCCNXP_HAVE_IO))  		return mctrl; -	mutex_lock(&s->sccnxp_mutex); +	spin_lock_irqsave(&s->lock, flags);  	ipr = ~sccnxp_read(port, SCCNXP_IPCR_REG); @@ -506,7 +530,7 @@ static unsigned int sccnxp_get_mctrl(struct uart_port *port)  		mctrl |= (ipr & bitmask) ? TIOCM_RNG : 0;  	} -	mutex_unlock(&s->sccnxp_mutex); +	spin_unlock_irqrestore(&s->lock, flags);  	return mctrl;  } @@ -514,21 +538,23 @@ static unsigned int sccnxp_get_mctrl(struct uart_port *port)  static void sccnxp_break_ctl(struct uart_port *port, int break_state)  {  	struct sccnxp_port *s = dev_get_drvdata(port->dev); +	unsigned long flags; -	mutex_lock(&s->sccnxp_mutex); +	spin_lock_irqsave(&s->lock, flags);  	sccnxp_port_write(port, SCCNXP_CR_REG, break_state ?  			  CR_CMD_START_BREAK : CR_CMD_STOP_BREAK); -	mutex_unlock(&s->sccnxp_mutex); +	spin_unlock_irqrestore(&s->lock, flags);  }  static void sccnxp_set_termios(struct uart_port *port,  			       struct ktermios *termios, struct ktermios *old)  {  	struct sccnxp_port *s = dev_get_drvdata(port->dev); +	unsigned long flags;  	u8 mr1, mr2;  	int baud; -	mutex_lock(&s->sccnxp_mutex); +	spin_lock_irqsave(&s->lock, flags);  	/* Mask termios capabilities we don't support */  	termios->c_cflag &= ~CMSPAR; @@ -595,20 +621,22 @@ static void sccnxp_set_termios(struct uart_port *port,  	/* Update timeout according to new baud rate */  	uart_update_timeout(port, termios->c_cflag, baud); +	/* Report actual baudrate back to core */  	if (tty_termios_baud_rate(termios))  		tty_termios_encode_baud_rate(termios, baud, baud);  	/* Enable RX & TX */  	sccnxp_port_write(port, SCCNXP_CR_REG, CR_RX_ENABLE | CR_TX_ENABLE); -	mutex_unlock(&s->sccnxp_mutex); +	spin_unlock_irqrestore(&s->lock, flags);  }  static int sccnxp_startup(struct uart_port *port)  {  	struct sccnxp_port *s = dev_get_drvdata(port->dev); +	unsigned long flags; -	mutex_lock(&s->sccnxp_mutex); +	spin_lock_irqsave(&s->lock, flags);  	if (s->flags & SCCNXP_HAVE_IO) {  		/* Outputs are controlled manually */ @@ -627,7 +655,9 @@ static int sccnxp_startup(struct uart_port *port)  	/* Enable RX interrupt */  	sccnxp_enable_irq(port, IMR_RXRDY); -	mutex_unlock(&s->sccnxp_mutex); +	s->opened[port->line] = 1; + +	spin_unlock_irqrestore(&s->lock, flags);  	return 0;  } @@ -635,8 +665,11 @@ static int sccnxp_startup(struct uart_port *port)  static void sccnxp_shutdown(struct uart_port *port)  {  	struct sccnxp_port *s = dev_get_drvdata(port->dev); +	unsigned long flags; -	mutex_lock(&s->sccnxp_mutex); +	spin_lock_irqsave(&s->lock, flags); + +	s->opened[port->line] = 0;  	/* Disable interrupts */  	sccnxp_disable_irq(port, IMR_TXRDY | IMR_RXRDY); @@ -648,7 +681,7 @@ static void sccnxp_shutdown(struct uart_port *port)  	if (s->flags & SCCNXP_HAVE_IO)  		sccnxp_set_bit(port, DIR_OP, 0); -	mutex_unlock(&s->sccnxp_mutex); +	spin_unlock_irqrestore(&s->lock, flags);  }  static const char *sccnxp_type(struct uart_port *port) @@ -722,10 +755,11 @@ static void sccnxp_console_write(struct console *co, const char *c, unsigned n)  {  	struct sccnxp_port *s = (struct sccnxp_port *)co->data;  	struct uart_port *port = &s->port[co->index]; +	unsigned long flags; -	mutex_lock(&s->sccnxp_mutex); +	spin_lock_irqsave(&s->lock, flags);  	uart_console_write(port, c, n, sccnxp_console_putchar); -	mutex_unlock(&s->sccnxp_mutex); +	spin_unlock_irqrestore(&s->lock, flags);  }  static int sccnxp_console_setup(struct console *co, char *options) @@ -764,7 +798,7 @@ static int sccnxp_probe(struct platform_device *pdev)  	}  	platform_set_drvdata(pdev, s); -	mutex_init(&s->sccnxp_mutex); +	spin_lock_init(&s->lock);  	/* Individual chip settings */  	switch (chiptype) { @@ -861,11 +895,19 @@ static int sccnxp_probe(struct platform_device *pdev)  	} else  		memcpy(&s->pdata, pdata, sizeof(struct sccnxp_pdata)); -	s->irq = platform_get_irq(pdev, 0); -	if (s->irq <= 0) { -		dev_err(&pdev->dev, "Missing irq resource data\n"); -		ret = -ENXIO; -		goto err_out; +	if (s->pdata.poll_time_us) { +		dev_info(&pdev->dev, "Using poll mode, resolution %u usecs\n", +			 s->pdata.poll_time_us); +		s->poll = 1; +	} + +	if (!s->poll) { +		s->irq = platform_get_irq(pdev, 0); +		if (s->irq < 0) { +			dev_err(&pdev->dev, "Missing irq resource data\n"); +			ret = -ENXIO; +			goto err_out; +		}  	}  	/* Check input frequency */ @@ -929,13 +971,23 @@ static int sccnxp_probe(struct platform_device *pdev)  	if (s->pdata.init)  		s->pdata.init(); -	ret = devm_request_threaded_irq(&pdev->dev, s->irq, NULL, sccnxp_ist, -					IRQF_TRIGGER_FALLING | IRQF_ONESHOT, -					dev_name(&pdev->dev), s); -	if (!ret) +	if (!s->poll) { +		ret = devm_request_threaded_irq(&pdev->dev, s->irq, NULL, +						sccnxp_ist, +						IRQF_TRIGGER_FALLING | +						IRQF_ONESHOT, +						dev_name(&pdev->dev), s); +		if (!ret) +			return 0; + +		dev_err(&pdev->dev, "Unable to reguest IRQ %i\n", s->irq); +	} else { +		init_timer(&s->timer); +		setup_timer(&s->timer, sccnxp_timer, (unsigned long)s); +		mod_timer(&s->timer, jiffies + +			  usecs_to_jiffies(s->pdata.poll_time_us));  		return 0; - -	dev_err(&pdev->dev, "Unable to reguest IRQ %i\n", s->irq); +	}  err_out:  	platform_set_drvdata(pdev, NULL); @@ -948,7 +1000,10 @@ static int sccnxp_remove(struct platform_device *pdev)  	int i;  	struct sccnxp_port *s = platform_get_drvdata(pdev); -	devm_free_irq(&pdev->dev, s->irq, s); +	if (!s->poll) +		devm_free_irq(&pdev->dev, s->irq, s); +	else +		del_timer_sync(&s->timer);  	for (i = 0; i < s->uart.nr; i++)  		uart_remove_one_port(&s->uart, &s->port[i]); | 
