aboutsummaryrefslogblamecommitdiffstats
path: root/drivers/tty/serial/sccnxp.c
blob: b7086d004f5f36cb47422d5225edf5540ad7d8b2 (plain) (tree)
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990


















                                                                         
                          




                              





























































































































































































                                                                               
                                                            





























                                                                           




                                                                            

























































































































































































































































































                                                                               


















                                                                              
                 
                




































                                                                            
                                           



                                                          


                                                                  






































































































































































































































































































































































































                                                                               
/*
 *  NXP (Philips) SCC+++(SCN+++) serial driver
 *
 *  Copyright (C) 2012 Alexander Shiyan <shc_work@mail.ru>
 *
 *  Based on sc26xx.c, by Thomas Bogendörfer (tsbogend@alpha.franken.de)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 */

#if defined(CONFIG_SERIAL_SCCNXP_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ)
#define SUPPORT_SYSRQ
#endif

#include <linux/module.h>
#include <linux/device.h>
#include <linux/console.h>
#include <linux/serial_core.h>
#include <linux/serial.h>
#include <linux/io.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/platform_device.h>
#include <linux/platform_data/sccnxp.h>

#define SCCNXP_NAME			"uart-sccnxp"
#define SCCNXP_MAJOR			204
#define SCCNXP_MINOR			205

#define SCCNXP_MR_REG			(0x00)
#	define MR0_BAUD_NORMAL		(0 << 0)
#	define MR0_BAUD_EXT1		(1 << 0)
#	define MR0_BAUD_EXT2		(5 << 0)
#	define MR0_FIFO			(1 << 3)
#	define MR0_TXLVL		(1 << 4)
#	define MR1_BITS_5		(0 << 0)
#	define MR1_BITS_6		(1 << 0)
#	define MR1_BITS_7		(2 << 0)
#	define MR1_BITS_8		(3 << 0)
#	define MR1_PAR_EVN		(0 << 2)
#	define MR1_PAR_ODD		(1 << 2)
#	define MR1_PAR_NO		(4 << 2)
#	define MR2_STOP1		(7 << 0)
#	define MR2_STOP2		(0xf << 0)
#define SCCNXP_SR_REG			(0x01)
#define SCCNXP_CSR_REG			SCCNXP_SR_REG
#	define SR_RXRDY			(1 << 0)
#	define SR_FULL			(1 << 1)
#	define SR_TXRDY			(1 << 2)
#	define SR_TXEMT			(1 << 3)
#	define SR_OVR			(1 << 4)
#	define SR_PE			(1 << 5)
#	define SR_FE			(1 << 6)
#	define SR_BRK			(1 << 7)
#define SCCNXP_CR_REG			(0x02)
#	define CR_RX_ENABLE		(1 << 0)
#	define CR_RX_DISABLE		(1 << 1)
#	define CR_TX_ENABLE		(1 << 2)
#	define CR_TX_DISABLE		(1 << 3)
#	define CR_CMD_MRPTR1		(0x01 << 4)
#	define CR_CMD_RX_RESET		(0x02 << 4)
#	define CR_CMD_TX_RESET		(0x03 << 4)
#	define CR_CMD_STATUS_RESET	(0x04 << 4)
#	define CR_CMD_BREAK_RESET	(0x05 << 4)
#	define CR_CMD_START_BREAK	(0x06 << 4)
#	define CR_CMD_STOP_BREAK	(0x07 << 4)
#	define CR_CMD_MRPTR0		(0x0b << 4)
#define SCCNXP_RHR_REG			(0x03)
#define SCCNXP_THR_REG			SCCNXP_RHR_REG
#define SCCNXP_IPCR_REG			(0x04)
#define SCCNXP_ACR_REG			SCCNXP_IPCR_REG
#	define ACR_BAUD0		(0 << 7)
#	define ACR_BAUD1		(1 << 7)
#	define ACR_TIMER_MODE		(6 << 4)
#define SCCNXP_ISR_REG			(0x05)
#define SCCNXP_IMR_REG			SCCNXP_ISR_REG
#	define IMR_TXRDY		(1 << 0)
#	define IMR_RXRDY		(1 << 1)
#	define ISR_TXRDY(x)		(1 << ((x * 4) + 0))
#	define ISR_RXRDY(x)		(1 << ((x * 4) + 1))
#define SCCNXP_IPR_REG			(0x0d)
#define SCCNXP_OPCR_REG			SCCNXP_IPR_REG
#define SCCNXP_SOP_REG			(0x0e)
#define SCCNXP_ROP_REG			(0x0f)

/* Route helpers */
#define MCTRL_MASK(sig)			(0xf << (sig))
#define MCTRL_IBIT(cfg, sig)		((((cfg) >> (sig)) & 0xf) - LINE_IP0)
#define MCTRL_OBIT(cfg, sig)		((((cfg) >> (sig)) & 0xf) - LINE_OP0)

/* Supported chip types */
enum {
	SCCNXP_TYPE_SC2681	= 2681,
	SCCNXP_TYPE_SC2691	= 2691,
	SCCNXP_TYPE_SC2692	= 2692,
	SCCNXP_TYPE_SC2891	= 2891,
	SCCNXP_TYPE_SC2892	= 2892,
	SCCNXP_TYPE_SC28202	= 28202,
	SCCNXP_TYPE_SC68681	= 68681,
	SCCNXP_TYPE_SC68692	= 68692,
};

struct sccnxp_port {
	struct uart_driver	uart;
	struct uart_port	port[SCCNXP_MAX_UARTS];

	const char		*name;
	int			irq;

	u8			imr;
	u8			addr_mask;
	int			freq_std;

	int			flags;
#define SCCNXP_HAVE_IO		0x00000001
#define SCCNXP_HAVE_MR0		0x00000002

#ifdef CONFIG_SERIAL_SCCNXP_CONSOLE
	struct console		console;
#endif

	struct mutex		sccnxp_mutex;

	struct sccnxp_pdata	pdata;
};

static inline u8 sccnxp_raw_read(void __iomem *base, u8 reg, u8 shift)
{
	return readb(base + (reg << shift));
}

static inline void sccnxp_raw_write(void __iomem *base, u8 reg, u8 shift, u8 v)
{
	writeb(v, base + (reg << shift));
}

static inline u8 sccnxp_read(struct uart_port *port, u8 reg)
{
	struct sccnxp_port *s = dev_get_drvdata(port->dev);

	return sccnxp_raw_read(port->membase, reg & s->addr_mask,
			       port->regshift);
}

static inline void sccnxp_write(struct uart_port *port, u8 reg, u8 v)
{
	struct sccnxp_port *s = dev_get_drvdata(port->dev);

	sccnxp_raw_write(port->membase, reg & s->addr_mask, port->regshift, v);
}

static inline u8 sccnxp_port_read(struct uart_port *port, u8 reg)
{
	return sccnxp_read(port, (port->line << 3) + reg);
}

static inline void sccnxp_port_write(struct uart_port *port, u8 reg, u8 v)
{
	sccnxp_write(port, (port->line << 3) + reg, v);
}

static int sccnxp_update_best_err(int a, int b, int *besterr)
{
	int err = abs(a - b);

	if ((*besterr < 0) || (*besterr > err)) {
		*besterr = err;
		return 0;
	}

	return 1;
}

struct baud_table {
	u8	csr;
	u8	acr;
	u8	mr0;
	int	baud;
};

const struct baud_table baud_std[] = {
	{ 0,	ACR_BAUD0,	MR0_BAUD_NORMAL,	50, },
	{ 0,	ACR_BAUD1,	MR0_BAUD_NORMAL,	75, },
	{ 1,	ACR_BAUD0,	MR0_BAUD_NORMAL,	110, },
	{ 2,	ACR_BAUD0,	MR0_BAUD_NORMAL,	134, },
	{ 3,	ACR_BAUD1,	MR0_BAUD_NORMAL,	150, },
	{ 3,	ACR_BAUD0,	MR0_BAUD_NORMAL,	200, },
	{ 4,	ACR_BAUD0,	MR0_BAUD_NORMAL,	300, },
	{ 0,	ACR_BAUD1,	MR0_BAUD_EXT1,		450, },
	{ 1,	ACR_BAUD0,	MR0_BAUD_EXT2,		880, },
	{ 3,	ACR_BAUD1,	MR0_BAUD_EXT1,		900, },
	{ 5,	ACR_BAUD0,	MR0_BAUD_NORMAL,	600, },
	{ 7,	ACR_BAUD0,	MR0_BAUD_NORMAL,	1050, },
	{ 2,	ACR_BAUD0,	MR0_BAUD_EXT2,		1076, },
	{ 6,	ACR_BAUD0,	MR0_BAUD_NORMAL,	1200, },
	{ 10,	ACR_BAUD1,	MR0_BAUD_NORMAL,	1800, },
	{ 7,	ACR_BAUD1,	MR0_BAUD_NORMAL,	2000, },
	{ 8,	ACR_BAUD0,	MR0_BAUD_NORMAL,	2400, },
	{ 5,	ACR_BAUD1,	MR0_BAUD_EXT1,		3600, },
	{ 9,	ACR_BAUD0,	MR0_BAUD_NORMAL,	4800, },
	{ 10,	ACR_BAUD0,	MR0_BAUD_NORMAL,	7200, },
	{ 11,	ACR_BAUD0,	MR0_BAUD_NORMAL,	9600, },
	{ 8,	ACR_BAUD0,	MR0_BAUD_EXT1,		14400, },
	{ 12,	ACR_BAUD1,	MR0_BAUD_NORMAL,	19200, },
	{ 9,	ACR_BAUD0,	MR0_BAUD_EXT1,		28800, },
	{ 12,	ACR_BAUD0,	MR0_BAUD_NORMAL,	38400, },
	{ 11,	ACR_BAUD0,	MR0_BAUD_EXT1,		57600, },
	{ 12,	ACR_BAUD1,	MR0_BAUD_EXT1,		115200, },
	{ 12,	ACR_BAUD0,	MR0_BAUD_EXT1,		230400, },
	{ 0, 0, 0, 0 }
};

static int sccnxp_set_baud(struct uart_port *port, int baud)
{
	struct sccnxp_port *s = dev_get_drvdata(port->dev);
	int div_std, tmp_baud, bestbaud = baud, besterr = -1;
	u8 i, acr = 0, csr = 0, mr0 = 0;

	/* Find best baud from table */
	for (i = 0; baud_std[i].baud && besterr; i++) {
		if (baud_std[i].mr0 && !(s->flags & SCCNXP_HAVE_MR0))
			continue;
		div_std = DIV_ROUND_CLOSEST(s->freq_std, baud_std[i].baud);
		tmp_baud = DIV_ROUND_CLOSEST(port->uartclk, div_std);
		if (!sccnxp_update_best_err(baud, tmp_baud, &besterr)) {
			acr = baud_std[i].acr;
			csr = baud_std[i].csr;
			mr0 = baud_std[i].mr0;
			bestbaud = tmp_baud;
		}
	}

	if (s->flags & SCCNXP_HAVE_MR0) {
		/* Enable FIFO, set half level for TX */
		mr0 |= MR0_FIFO | MR0_TXLVL;
		/* Update MR0 */
		sccnxp_port_write(port, SCCNXP_CR_REG, CR_CMD_MRPTR0);
		sccnxp_port_write(port, SCCNXP_MR_REG, mr0);
	}

	sccnxp_port_write(port, SCCNXP_ACR_REG, acr | ACR_TIMER_MODE);
	sccnxp_port_write(port, SCCNXP_CSR_REG, (csr << 4) | csr);

	if (baud != bestbaud)
		dev_dbg(port->dev, "Baudrate desired: %i, calculated: %i\n",
			baud, bestbaud);

	return bestbaud;
}

static void sccnxp_enable_irq(struct uart_port *port, int mask)
{
	struct sccnxp_port *s = dev_get_drvdata(port->dev);

	s->imr |= mask << (port->line * 4);
	sccnxp_write(port, SCCNXP_IMR_REG, s->imr);
}

static void sccnxp_disable_irq(struct uart_port *port, int mask)
{
	struct sccnxp_port *s = dev_get_drvdata(port->dev);

	s->imr &= ~(mask << (port->line * 4));
	sccnxp_write(port, SCCNXP_IMR_REG, s->imr);
}

static void sccnxp_set_bit(struct uart_port *port, int sig, int state)
{
	u8 bitmask;
	struct sccnxp_port *s = dev_get_drvdata(port->dev);

	if (s->pdata.mctrl_cfg[port->line] & MCTRL_MASK(sig)) {
		bitmask = 1 << MCTRL_OBIT(s->pdata.mctrl_cfg[port->line], sig);
		if (state)
			sccnxp_write(port, SCCNXP_SOP_REG, bitmask);
		else
			sccnxp_write(port, SCCNXP_ROP_REG, bitmask);
	}
}

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);
		if (!(sr & SR_RXRDY))
			break;
		sr &= SR_PE | SR_FE | SR_OVR | SR_BRK;

		ch = sccnxp_port_read(port, SCCNXP_RHR_REG);

		port->icount.rx++;
		flag = TTY_NORMAL;

		if (unlikely(sr)) {
			if (sr & SR_BRK) {
				port->icount.brk++;
				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)
				port->icount.overrun++;

			sr &= port->read_status_mask;
			if (sr & SR_BRK)
				flag = TTY_BREAK;
			else if (sr & SR_PE)
				flag = TTY_PARITY;
			else if (sr & SR_FE)
				flag = TTY_FRAME;
			else if (sr & SR_OVR)
				flag = TTY_OVERRUN;
		}

		if (uart_handle_sysrq_char(port, ch))
			continue;

		if (sr & port->ignore_status_mask)
			continue;

		uart_insert_char(port, sr, SR_OVR, ch, flag);
	}

	tty_flip_buffer_push(tty);

	tty_kref_put(tty);
}

static void sccnxp_handle_tx(struct uart_port *port)
{
	u8 sr;
	struct circ_buf *xmit = &port->state->xmit;
	struct sccnxp_port *s = dev_get_drvdata(port->dev);

	if (unlikely(port->x_char)) {
		sccnxp_port_write(port, SCCNXP_THR_REG, port->x_char);
		port->icount.tx++;
		port->x_char = 0;
		return;
	}

	if (uart_circ_empty(xmit) || uart_tx_stopped(port)) {
		/* Disable TX if FIFO is empty */
		if (sccnxp_port_read(port, SCCNXP_SR_REG) & SR_TXEMT) {
			sccnxp_disable_irq(port, IMR_TXRDY);

			/* Set direction to input */
			if (s->flags & SCCNXP_HAVE_IO)
				sccnxp_set_bit(port, DIR_OP, 0);
		}
		return;
	}

	while (!uart_circ_empty(xmit)) {
		sr = sccnxp_port_read(port, SCCNXP_SR_REG);
		if (!(sr & SR_TXRDY))
			break;

		sccnxp_port_write(port, SCCNXP_THR_REG, xmit->buf[xmit->tail]);
		xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
		port->icount.tx++;
	}

	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
		uart_write_wakeup(port);
}

static irqreturn_t sccnxp_ist(int irq, void *dev_id)
{
	int i;
	u8 isr;
	struct sccnxp_port *s = (struct sccnxp_port *)dev_id;

	mutex_lock(&s->sccnxp_mutex);

	for (;;) {
		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))
				sccnxp_handle_rx(&s->port[i]);
			if (isr & ISR_TXRDY(i))
				sccnxp_handle_tx(&s->port[i]);
		}
	}

	mutex_unlock(&s->sccnxp_mutex);

	return IRQ_HANDLED;
}

static void sccnxp_start_tx(struct uart_port *port)
{
	struct sccnxp_port *s = dev_get_drvdata(port->dev);

	mutex_lock(&s->sccnxp_mutex);

	/* Set direction to output */
	if (s->flags & SCCNXP_HAVE_IO)
		sccnxp_set_bit(port, DIR_OP, 1);

	sccnxp_enable_irq(port, IMR_TXRDY);

	mutex_unlock(&s->sccnxp_mutex);
}

static void sccnxp_stop_tx(struct uart_port *port)
{
	/* Do nothing */
}

static void sccnxp_stop_rx(struct uart_port *port)
{
	struct sccnxp_port *s = dev_get_drvdata(port->dev);

	mutex_lock(&s->sccnxp_mutex);
	sccnxp_port_write(port, SCCNXP_CR_REG, CR_RX_DISABLE);
	mutex_unlock(&s->sccnxp_mutex);
}

static unsigned int sccnxp_tx_empty(struct uart_port *port)
{
	u8 val;
	struct sccnxp_port *s = dev_get_drvdata(port->dev);

	mutex_lock(&s->sccnxp_mutex);
	val = sccnxp_port_read(port, SCCNXP_SR_REG);
	mutex_unlock(&s->sccnxp_mutex);

	return (val & SR_TXEMT) ? TIOCSER_TEMT : 0;
}

static void sccnxp_enable_ms(struct uart_port *port)
{
	/* Do nothing */
}

static void sccnxp_set_mctrl(struct uart_port *port, unsigned int mctrl)
{
	struct sccnxp_port *s = dev_get_drvdata(port->dev);

	if (!(s->flags & SCCNXP_HAVE_IO))
		return;

	mutex_lock(&s->sccnxp_mutex);

	sccnxp_set_bit(port, DTR_OP, mctrl & TIOCM_DTR);
	sccnxp_set_bit(port, RTS_OP, mctrl & TIOCM_RTS);

	mutex_unlock(&s->sccnxp_mutex);
}

static unsigned int sccnxp_get_mctrl(struct uart_port *port)
{
	u8 bitmask, ipr;
	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);

	ipr = ~sccnxp_read(port, SCCNXP_IPCR_REG);

	if (s->pdata.mctrl_cfg[port->line] & MCTRL_MASK(DSR_IP)) {
		bitmask = 1 << MCTRL_IBIT(s->pdata.mctrl_cfg[port->line],
					  DSR_IP);
		mctrl &= ~TIOCM_DSR;
		mctrl |= (ipr & bitmask) ? TIOCM_DSR : 0;
	}
	if (s->pdata.mctrl_cfg[port->line] & MCTRL_MASK(CTS_IP)) {
		bitmask = 1 << MCTRL_IBIT(s->pdata.mctrl_cfg[port->line],
					  CTS_IP);
		mctrl &= ~TIOCM_CTS;
		mctrl |= (ipr & bitmask) ? TIOCM_CTS : 0;
	}
	if (s->pdata.mctrl_cfg[port->line] & MCTRL_MASK(DCD_IP)) {
		bitmask = 1 << MCTRL_IBIT(s->pdata.mctrl_cfg[port->line],
					  DCD_IP);
		mctrl &= ~TIOCM_CAR;
		mctrl |= (ipr & bitmask) ? TIOCM_CAR : 0;
	}
	if (s->pdata.mctrl_cfg[port->line] & MCTRL_MASK(RNG_IP)) {
		bitmask = 1 << MCTRL_IBIT(s->pdata.mctrl_cfg[port->line],
					  RNG_IP);
		mctrl &= ~TIOCM_RNG;
		mctrl |= (ipr & bitmask) ? TIOCM_RNG : 0;
	}

	mutex_unlock(&s->sccnxp_mutex);

	return mctrl;
}

static void sccnxp_break_ctl(struct uart_port *port, int break_state)
{
	struct sccnxp_port *s = dev_get_drvdata(port->dev);

	mutex_lock(&s->sccnxp_mutex);
	sccnxp_port_write(port, SCCNXP_CR_REG, break_state ?
			  CR_CMD_START_BREAK : CR_CMD_STOP_BREAK);
	mutex_unlock(&s->sccnxp_mutex);
}

static void sccnxp_set_termios(struct uart_port *port,
			       struct ktermios *termios, struct ktermios *old)
{
	struct sccnxp_port *s = dev_get_drvdata(port->dev);
	u8 mr1, mr2;
	int baud;

	mutex_lock(&s->sccnxp_mutex);

	/* Mask termios capabilities we don't support */
	termios->c_cflag &= ~CMSPAR;

	/* Disable RX & TX, reset break condition, status and FIFOs */
	sccnxp_port_write(port, SCCNXP_CR_REG, CR_CMD_RX_RESET |
					       CR_RX_DISABLE | CR_TX_DISABLE);
	sccnxp_port_write(port, SCCNXP_CR_REG, CR_CMD_TX_RESET);
	sccnxp_port_write(port, SCCNXP_CR_REG, CR_CMD_STATUS_RESET);
	sccnxp_port_write(port, SCCNXP_CR_REG, CR_CMD_BREAK_RESET);

	/* Word size */
	switch (termios->c_cflag & CSIZE) {
	case CS5:
		mr1 = MR1_BITS_5;
		break;
	case CS6:
		mr1 = MR1_BITS_6;
		break;
	case CS7:
		mr1 = MR1_BITS_7;
		break;
	case CS8:
	default:
		mr1 = MR1_BITS_8;
		break;
	}

	/* Parity */
	if (termios->c_cflag & PARENB) {
		if (termios->c_cflag & PARODD)
			mr1 |= MR1_PAR_ODD;
	} else
		mr1 |= MR1_PAR_NO;

	/* Stop bits */
	mr2 = (termios->c_cflag & CSTOPB) ? MR2_STOP2 : MR2_STOP1;

	/* Update desired format */
	sccnxp_port_write(port, SCCNXP_CR_REG, CR_CMD_MRPTR1);
	sccnxp_port_write(port, SCCNXP_MR_REG, mr1);
	sccnxp_port_write(port, SCCNXP_MR_REG, mr2);

	/* Set read status mask */
	port->read_status_mask = SR_OVR;
	if (termios->c_iflag & INPCK)
		port->read_status_mask |= SR_PE | SR_FE;
	if (termios->c_iflag & (BRKINT | PARMRK))
		port->read_status_mask |= SR_BRK;

	/* Set status ignore mask */
	port->ignore_status_mask = 0;
	if (termios->c_iflag & IGNBRK)
		port->ignore_status_mask |= SR_BRK;
	if (!(termios->c_cflag & CREAD))
		port->ignore_status_mask |= SR_PE | SR_OVR | SR_FE | SR_BRK;

	/* Setup baudrate */
	baud = uart_get_baud_rate(port, termios, old, 50,
				  (s->flags & SCCNXP_HAVE_MR0) ?
				  230400 : 38400);
	baud = sccnxp_set_baud(port, baud);

	/* Update timeout according to new baud rate */
	uart_update_timeout(port, termios->c_cflag, baud);

	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);
}

static int sccnxp_startup(struct uart_port *port)
{
	struct sccnxp_port *s = dev_get_drvdata(port->dev);

	mutex_lock(&s->sccnxp_mutex);

	if (s->flags & SCCNXP_HAVE_IO) {
		/* Outputs are controlled manually */
		sccnxp_write(port, SCCNXP_OPCR_REG, 0);
	}

	/* Reset break condition, status and FIFOs */
	sccnxp_port_write(port, SCCNXP_CR_REG, CR_CMD_RX_RESET);
	sccnxp_port_write(port, SCCNXP_CR_REG, CR_CMD_TX_RESET);
	sccnxp_port_write(port, SCCNXP_CR_REG, CR_CMD_STATUS_RESET);
	sccnxp_port_write(port, SCCNXP_CR_REG, CR_CMD_BREAK_RESET);

	/* Enable RX & TX */
	sccnxp_port_write(port, SCCNXP_CR_REG, CR_RX_ENABLE | CR_TX_ENABLE);

	/* Enable RX interrupt */
	sccnxp_enable_irq(port, IMR_RXRDY);

	mutex_unlock(&s->sccnxp_mutex);

	return 0;
}

static void sccnxp_shutdown(struct uart_port *port)
{
	struct sccnxp_port *s = dev_get_drvdata(port->dev);

	mutex_lock(&s->sccnxp_mutex);

	/* Disable interrupts */
	sccnxp_disable_irq(port, IMR_TXRDY | IMR_RXRDY);

	/* Disable TX & RX */
	sccnxp_port_write(port, SCCNXP_CR_REG, CR_RX_DISABLE | CR_TX_DISABLE);

	/* Leave direction to input */
	if (s->flags & SCCNXP_HAVE_IO)
		sccnxp_set_bit(port, DIR_OP, 0);

	mutex_unlock(&s->sccnxp_mutex);
}

static const char *sccnxp_type(struct uart_port *port)
{
	struct sccnxp_port *s = dev_get_drvdata(port->dev);

	return (port->type == PORT_SC26XX) ? s->name : NULL;
}

static void sccnxp_release_port(struct uart_port *port)
{
	/* Do nothing */
}

static int sccnxp_request_port(struct uart_port *port)
{
	/* Do nothing */
	return 0;
}

static void sccnxp_config_port(struct uart_port *port, int flags)
{
	if (flags & UART_CONFIG_TYPE)
		port->type = PORT_SC26XX;
}

static int sccnxp_verify_port(struct uart_port *port, struct serial_struct *s)
{
	if ((s->type == PORT_UNKNOWN) || (s->type == PORT_SC26XX))
		return 0;
	if (s->irq == port->irq)
		return 0;

	return -EINVAL;
}

static const struct uart_ops sccnxp_ops = {
	.tx_empty	= sccnxp_tx_empty,
	.set_mctrl	= sccnxp_set_mctrl,
	.get_mctrl	= sccnxp_get_mctrl,
	.stop_tx	= sccnxp_stop_tx,
	.start_tx	= sccnxp_start_tx,
	.stop_rx	= sccnxp_stop_rx,
	.enable_ms	= sccnxp_enable_ms,
	.break_ctl	= sccnxp_break_ctl,
	.startup	= sccnxp_startup,
	.shutdown	= sccnxp_shutdown,
	.set_termios	= sccnxp_set_termios,
	.type		= sccnxp_type,
	.release_port	= sccnxp_release_port,
	.request_port	= sccnxp_request_port,
	.config_port	= sccnxp_config_port,
	.verify_port	= sccnxp_verify_port,
};

#ifdef CONFIG_SERIAL_SCCNXP_CONSOLE
static void sccnxp_console_putchar(struct uart_port *port, int c)
{
	int tryes = 100000;

	while (tryes--) {
		if (sccnxp_port_read(port, SCCNXP_SR_REG) & SR_TXRDY) {
			sccnxp_port_write(port, SCCNXP_THR_REG, c);
			break;
		}
		barrier();
	}
}

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];

	mutex_lock(&s->sccnxp_mutex);
	uart_console_write(port, c, n, sccnxp_console_putchar);
	mutex_unlock(&s->sccnxp_mutex);
}

static int sccnxp_console_setup(struct console *co, char *options)
{
	struct sccnxp_port *s = (struct sccnxp_port *)co->data;
	struct uart_port *port = &s->port[(co->index > 0) ? co->index : 0];
	int baud = 9600, bits = 8, parity = 'n', flow = 'n';

	if (options)
		uart_parse_options(options, &baud, &parity, &bits, &flow);

	return uart_set_options(port, co, baud, parity, bits, flow);
}
#endif

static int __devinit sccnxp_probe(struct platform_device *pdev)
{
	struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	int chiptype = pdev->id_entry->driver_data;
	struct sccnxp_pdata *pdata = dev_get_platdata(&pdev->dev);
	int i, ret, fifosize, freq_min, freq_max;
	struct sccnxp_port *s;
	void __iomem *membase;

	if (!res) {
		dev_err(&pdev->dev, "Missing memory resource data\n");
		return -EADDRNOTAVAIL;
	}

	dev_set_name(&pdev->dev, SCCNXP_NAME);

	s = devm_kzalloc(&pdev->dev, sizeof(struct sccnxp_port), GFP_KERNEL);
	if (!s) {
		dev_err(&pdev->dev, "Error allocating port structure\n");
		return -ENOMEM;
	}
	platform_set_drvdata(pdev, s);

	mutex_init(&s->sccnxp_mutex);

	/* Individual chip settings */
	switch (chiptype) {
	case SCCNXP_TYPE_SC2681:
		s->name		= "SC2681";
		s->uart.nr	= 2;
		s->freq_std	= 3686400;
		s->addr_mask	= 0x0f;
		s->flags	= SCCNXP_HAVE_IO;
		fifosize	= 3;
		freq_min	= 1000000;
		freq_max	= 4000000;
		break;
	case SCCNXP_TYPE_SC2691:
		s->name		= "SC2691";
		s->uart.nr	= 1;
		s->freq_std	= 3686400;
		s->addr_mask	= 0x07;
		s->flags	= 0;
		fifosize	= 3;
		freq_min	= 1000000;
		freq_max	= 4000000;
		break;
	case SCCNXP_TYPE_SC2692:
		s->name		= "SC2692";
		s->uart.nr	= 2;
		s->freq_std	= 3686400;
		s->addr_mask	= 0x0f;
		s->flags	= SCCNXP_HAVE_IO;
		fifosize	= 3;
		freq_min	= 1000000;
		freq_max	= 4000000;
		break;
	case SCCNXP_TYPE_SC2891:
		s->name		= "SC2891";
		s->uart.nr	= 1;
		s->freq_std	= 3686400;
		s->addr_mask	= 0x0f;
		s->flags	= SCCNXP_HAVE_IO | SCCNXP_HAVE_MR0;
		fifosize	= 16;
		freq_min	= 100000;
		freq_max	= 8000000;
		break;
	case SCCNXP_TYPE_SC2892:
		s->name		= "SC2892";
		s->uart.nr	= 2;
		s->freq_std	= 3686400;
		s->addr_mask	= 0x0f;
		s->flags	= SCCNXP_HAVE_IO | SCCNXP_HAVE_MR0;
		fifosize	= 16;
		freq_min	= 100000;
		freq_max	= 8000000;
		break;
	case SCCNXP_TYPE_SC28202:
		s->name		= "SC28202";
		s->uart.nr	= 2;
		s->freq_std	= 14745600;
		s->addr_mask	= 0x7f;
		s->flags	= SCCNXP_HAVE_IO | SCCNXP_HAVE_MR0;
		fifosize	= 256;
		freq_min	= 1000000;
		freq_max	= 50000000;
		break;
	case SCCNXP_TYPE_SC68681:
		s->name		= "SC68681";
		s->uart.nr	= 2;
		s->freq_std	= 3686400;
		s->addr_mask	= 0x0f;
		s->flags	= SCCNXP_HAVE_IO;
		fifosize	= 3;
		freq_min	= 1000000;
		freq_max	= 4000000;
		break;
	case SCCNXP_TYPE_SC68692:
		s->name		= "SC68692";
		s->uart.nr	= 2;
		s->freq_std	= 3686400;
		s->addr_mask	= 0x0f;
		s->flags	= SCCNXP_HAVE_IO;
		fifosize	= 3;
		freq_min	= 1000000;
		freq_max	= 4000000;
		break;
	default:
		dev_err(&pdev->dev, "Unsupported chip type %i\n", chiptype);
		ret = -ENOTSUPP;
		goto err_out;
	}

	if (!pdata) {
		dev_warn(&pdev->dev,
			 "No platform data supplied, using defaults\n");
		s->pdata.frequency = s->freq_std;
	} 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;
	}

	/* Check input frequency */
	if ((s->pdata.frequency < freq_min) ||
	    (s->pdata.frequency > freq_max)) {
		dev_err(&pdev->dev, "Frequency out of bounds\n");
		ret = -EINVAL;
		goto err_out;
	}

	membase = devm_request_and_ioremap(&pdev->dev, res);
	if (!membase) {
		dev_err(&pdev->dev, "Failed to ioremap\n");
		ret = -EIO;
		goto err_out;
	}

	s->uart.owner		= THIS_MODULE;
	s->uart.dev_name	= "ttySC";
	s->uart.major		= SCCNXP_MAJOR;
	s->uart.minor		= SCCNXP_MINOR;
#ifdef CONFIG_SERIAL_SCCNXP_CONSOLE
	s->uart.cons		= &s->console;
	s->uart.cons->device	= uart_console_device;
	s->uart.cons->write	= sccnxp_console_write;
	s->uart.cons->setup	= sccnxp_console_setup;
	s->uart.cons->flags	= CON_PRINTBUFFER;
	s->uart.cons->index	= -1;
	s->uart.cons->data	= s;
	strcpy(s->uart.cons->name, "ttySC");
#endif
	ret = uart_register_driver(&s->uart);
	if (ret) {
		dev_err(&pdev->dev, "Registering UART driver failed\n");
		goto err_out;
	}

	for (i = 0; i < s->uart.nr; i++) {
		s->port[i].line		= i;
		s->port[i].dev		= &pdev->dev;
		s->port[i].irq		= s->irq;
		s->port[i].type		= PORT_SC26XX;
		s->port[i].fifosize	= fifosize;
		s->port[i].flags	= UPF_SKIP_TEST | UPF_FIXED_TYPE;
		s->port[i].iotype	= UPIO_MEM;
		s->port[i].mapbase	= res->start;
		s->port[i].membase	= membase;
		s->port[i].regshift	= s->pdata.reg_shift;
		s->port[i].uartclk	= s->pdata.frequency;
		s->port[i].ops		= &sccnxp_ops;
		uart_add_one_port(&s->uart, &s->port[i]);
		/* Set direction to input */
		if (s->flags & SCCNXP_HAVE_IO)
			sccnxp_set_bit(&s->port[i], DIR_OP, 0);
	}

	/* Disable interrupts */
	s->imr = 0;
	sccnxp_write(&s->port[0], SCCNXP_IMR_REG, 0);

	/* Board specific configure */
	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)
		return 0;

	dev_err(&pdev->dev, "Unable to reguest IRQ %i\n", s->irq);

err_out:
	platform_set_drvdata(pdev, NULL);

	return ret;
}

static int __devexit sccnxp_remove(struct platform_device *pdev)
{
	int i;
	struct sccnxp_port *s = platform_get_drvdata(pdev);

	devm_free_irq(&pdev->dev, s->irq, s);

	for (i = 0; i < s->uart.nr; i++)
		uart_remove_one_port(&s->uart, &s->port[i]);

	uart_unregister_driver(&s->uart);
	platform_set_drvdata(pdev, NULL);

	if (s->pdata.exit)
		s->pdata.exit();

	return 0;
}

static const struct platform_device_id sccnxp_id_table[] = {
	{ "sc2681",	SCCNXP_TYPE_SC2681 },
	{ "sc2691",	SCCNXP_TYPE_SC2691 },
	{ "sc2692",	SCCNXP_TYPE_SC2692 },
	{ "sc2891",	SCCNXP_TYPE_SC2891 },
	{ "sc2892",	SCCNXP_TYPE_SC2892 },
	{ "sc28202",	SCCNXP_TYPE_SC28202 },
	{ "sc68681",	SCCNXP_TYPE_SC68681 },
	{ "sc68692",	SCCNXP_TYPE_SC68692 },
};
MODULE_DEVICE_TABLE(platform, sccnxp_id_table);

static struct platform_driver sccnxp_uart_driver = {
	.driver = {
		.name	= SCCNXP_NAME,
		.owner	= THIS_MODULE,
	},
	.probe		= sccnxp_probe,
	.remove		= __devexit_p(sccnxp_remove),
	.id_table	= sccnxp_id_table,
};
module_platform_driver(sccnxp_uart_driver);

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>");
MODULE_DESCRIPTION("SCCNXP serial driver");