aboutsummaryrefslogtreecommitdiff
path: root/arch/mips/cavium-octeon/serial.c
blob: 8240728d485a563f1598fea7f58318301c932444 (plain)
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
/*
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file "COPYING" in the main directory of this archive
 * for more details.
 *
 * Copyright (C) 2004-2007 Cavium Networks
 */
#include <linux/console.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/serial.h>
#include <linux/serial_8250.h>
#include <linux/serial_reg.h>
#include <linux/tty.h>

#include <asm/time.h>

#include <asm/octeon/octeon.h>

#ifdef CONFIG_GDB_CONSOLE
#define DEBUG_UART 0
#else
#define DEBUG_UART 1
#endif

unsigned int octeon_serial_in(struct uart_port *up, int offset)
{
	int rv = cvmx_read_csr((uint64_t)(up->membase + (offset << 3)));
	if (offset == UART_IIR && (rv & 0xf) == 7) {
		/* Busy interrupt, read the USR (39) and try again. */
		cvmx_read_csr((uint64_t)(up->membase + (39 << 3)));
		rv = cvmx_read_csr((uint64_t)(up->membase + (offset << 3)));
	}
	return rv;
}

void octeon_serial_out(struct uart_port *up, int offset, int value)
{
	/*
	 * If bits 6 or 7 of the OCTEON UART's LCR are set, it quits
	 * working.
	 */
	if (offset == UART_LCR)
		value &= 0x9f;
	cvmx_write_csr((uint64_t)(up->membase + (offset << 3)), (u8)value);
}

/*
 * Allocated in .bss, so it is all zeroed.
 */
#define OCTEON_MAX_UARTS 3
static struct plat_serial8250_port octeon_uart8250_data[OCTEON_MAX_UARTS + 1];
static struct platform_device octeon_uart8250_device = {
	.name			= "serial8250",
	.id			= PLAT8250_DEV_PLATFORM,
	.dev			= {
		.platform_data	= octeon_uart8250_data,
	},
};

static void __init octeon_uart_set_common(struct plat_serial8250_port *p)
{
	p->flags = ASYNC_SKIP_TEST | UPF_SHARE_IRQ | UPF_FIXED_TYPE;
	p->type = PORT_OCTEON;
	p->iotype = UPIO_MEM;
	p->regshift = 3;	/* I/O addresses are every 8 bytes */
	p->uartclk = mips_hpt_frequency;
	p->serial_in = octeon_serial_in;
	p->serial_out = octeon_serial_out;
}

static int __init octeon_serial_init(void)
{
	int enable_uart0;
	int enable_uart1;
	int enable_uart2;
	struct plat_serial8250_port *p;

#ifdef CONFIG_CAVIUM_OCTEON_2ND_KERNEL
	/*
	 * If we are configured to run as the second of two kernels,
	 * disable uart0 and enable uart1. Uart0 is owned by the first
	 * kernel
	 */
	enable_uart0 = 0;
	enable_uart1 = 1;
#else
	/*
	 * We are configured for the first kernel. We'll enable uart0
	 * if the bootloader told us to use 0, otherwise will enable
	 * uart 1.
	 */
	enable_uart0 = (octeon_get_boot_uart() == 0);
	enable_uart1 = (octeon_get_boot_uart() == 1);
#ifdef CONFIG_KGDB
	enable_uart1 = 1;
#endif
#endif

	/* Right now CN52XX is the only chip with a third uart */
	enable_uart2 = OCTEON_IS_MODEL(OCTEON_CN52XX);

	p = octeon_uart8250_data;
	if (enable_uart0) {
		/* Add a ttyS device for hardware uart 0 */
		octeon_uart_set_common(p);
		p->membase = (void *) CVMX_MIO_UARTX_RBR(0);
		p->mapbase = CVMX_MIO_UARTX_RBR(0) & ((1ull << 49) - 1);
		p->irq = OCTEON_IRQ_UART0;
		p++;
	}

	if (enable_uart1) {
		/* Add a ttyS device for hardware uart 1 */
		octeon_uart_set_common(p);
		p->membase = (void *) CVMX_MIO_UARTX_RBR(1);
		p->mapbase = CVMX_MIO_UARTX_RBR(1) & ((1ull << 49) - 1);
		p->irq = OCTEON_IRQ_UART1;
		p++;
	}
	if (enable_uart2) {
		/* Add a ttyS device for hardware uart 2 */
		octeon_uart_set_common(p);
		p->membase = (void *) CVMX_MIO_UART2_RBR;
		p->mapbase = CVMX_MIO_UART2_RBR & ((1ull << 49) - 1);
		p->irq = OCTEON_IRQ_UART2;
		p++;
	}

	BUG_ON(p > &octeon_uart8250_data[OCTEON_MAX_UARTS]);

	return platform_device_register(&octeon_uart8250_device);
}

device_initcall(octeon_serial_init);