aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/orion/patches/015-implement_power-off_method_for_kurobox_pro.patch
blob: 581485238813d6447a8abb5b6f59b1e3dcc77f2a (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
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
From: Sylver Bruneau <sylver.bruneau@googlemail.com>

This patch implements the communication with the microcontroller on the
Kurobox Pro and Linkstation Pro/Live boards.  This is allowing to send
the commands needed to power-off the board correctly.

Signed-off-by: Sylver Bruneau <sylver.bruneau@googlemail.com>
---
 arch/arm/mach-orion5x/kurobox_pro-setup.c |  147 ++++++++++++++++++++++++++++-
 1 files changed, 143 insertions(+), 4 deletions(-)

--- a/arch/arm/mach-orion5x/kurobox_pro-setup.c
+++ b/arch/arm/mach-orion5x/kurobox_pro-setup.c
@@ -13,10 +13,12 @@
 #include <linux/platform_device.h>
 #include <linux/pci.h>
 #include <linux/irq.h>
+#include <linux/delay.h>
 #include <linux/mtd/physmap.h>
 #include <linux/mtd/nand.h>
 #include <linux/mv643xx_eth.h>
 #include <linux/i2c.h>
+#include <linux/serial_reg.h>
 #include <linux/ata_platform.h>
 #include <asm/mach-types.h>
 #include <asm/gpio.h>
@@ -177,6 +179,140 @@
 };
 
 /*****************************************************************************
+ * Kurobox Pro specific power off method via UART1-attached microcontroller
+ ****************************************************************************/
+
+#define UART1_REG(x)	(UART1_VIRT_BASE + ((UART_##x) << 2))
+
+static int kurobox_pro_miconread(unsigned char *buf, int count)
+{
+	int i;
+	int timeout;
+
+	for (i = 0; i < count; i++) {
+		timeout = 10;
+
+		while (!(readl(UART1_REG(LSR)) & UART_LSR_DR)) {
+			if (--timeout == 0)
+				break;
+			udelay(1000);
+		}
+
+		if (timeout == 0)
+			break;
+		buf[i] = readl(UART1_REG(RX));
+	}
+
+	/* return read bytes */
+	return i;
+}
+
+static int kurobox_pro_miconwrite(const unsigned char *buf, int count)
+{
+	int i = 0;
+
+	while (count--) {
+		while (!(readl(UART1_REG(LSR)) & UART_LSR_THRE))
+			barrier();
+		writel(buf[i++], UART1_REG(TX));
+	}
+
+	return 0;
+}
+
+static int kurobox_pro_miconsend(const unsigned char *data, int count)
+{
+	int i;
+	unsigned char checksum = 0;
+	unsigned char recv_buf[40];
+	unsigned char send_buf[40];
+	unsigned char correct_ack[3];
+	int retry = 2;
+
+	/* Generate checksum */
+	for (i = 0; i < count; i++)
+		checksum -=  data[i];
+
+	do {
+		/* Send data */
+		kurobox_pro_miconwrite(data, count);
+
+		/* send checksum */
+		kurobox_pro_miconwrite(&checksum, 1);
+
+		if (kurobox_pro_miconread(recv_buf, sizeof(recv_buf)) <= 3) {
+			printk(KERN_ERR ">%s: receive failed.\n", __func__);
+
+			/* send preamble to clear the receive buffer */
+			memset(&send_buf, 0xff, sizeof(send_buf));
+			kurobox_pro_miconwrite(send_buf, sizeof(send_buf));
+
+			/* make dummy reads */
+			mdelay(100);
+			kurobox_pro_miconread(recv_buf, sizeof(recv_buf));
+		} else {
+			/* Generate expected ack */
+			correct_ack[0] = 0x01;
+			correct_ack[1] = data[1];
+			correct_ack[2] = 0x00;
+
+			/* checksum Check */
+			if ((recv_buf[0] + recv_buf[1] + recv_buf[2] +
+			     recv_buf[3]) & 0xFF) {
+				printk(KERN_ERR ">%s: Checksum Error : "
+					"Received data[%02x, %02x, %02x, %02x]"
+					"\n", __func__, recv_buf[0],
+					recv_buf[1], recv_buf[2], recv_buf[3]);
+			} else {
+				/* Check Received Data */
+				if (correct_ack[0] == recv_buf[0] &&
+				    correct_ack[1] == recv_buf[1] &&
+				    correct_ack[2] == recv_buf[2]) {
+					/* Interval for next command */
+					mdelay(10);
+
+					/* Receive ACK */
+					return 0;
+				}
+			}
+			/* Received NAK or illegal Data */
+			printk(KERN_ERR ">%s: Error : NAK or Illegal Data "
+					"Received\n", __func__);
+		}
+	} while (retry--);
+
+	/* Interval for next command */
+	mdelay(10);
+
+	return -1;
+}
+
+static void kurobox_pro_power_off(void)
+{
+	const unsigned char watchdogkill[]	= {0x01, 0x35, 0x00};
+	const unsigned char shutdownwait[]	= {0x00, 0x0c};
+	const unsigned char poweroff[]		= {0x00, 0x06};
+	/* 38400 baud divisor */
+	const unsigned divisor = ((ORION5X_TCLK + (8 * 38400)) / (16 * 38400));
+
+	pr_info("%s: triggering power-off...\n", __func__);
+
+	/* hijack uart1 and reset into sane state (38400,8n1,even parity) */
+	writel(0x83, UART1_REG(LCR));
+	writel(divisor & 0xff, UART1_REG(DLL));
+	writel((divisor >> 8) & 0xff, UART1_REG(DLM));
+	writel(0x1b, UART1_REG(LCR));
+	writel(0x00, UART1_REG(IER));
+	writel(0x07, UART1_REG(FCR));
+	writel(0x00, UART1_REG(MCR));
+
+	/* Send the commands to shutdown the Kurobox Pro */
+	kurobox_pro_miconsend(watchdogkill, sizeof(watchdogkill)) ;
+	kurobox_pro_miconsend(shutdownwait, sizeof(shutdownwait)) ;
+	kurobox_pro_miconsend(poweroff, sizeof(poweroff));
+}
+
+/*****************************************************************************
  * General Setup
  ****************************************************************************/
 
@@ -203,10 +339,10 @@
 	orion5x_mpp_conf(13, MPP_SATA_LED);	/* SATA 1 presence */
 	orion5x_mpp_conf(14, MPP_SATA_LED);	/* SATA 0 active */
 	orion5x_mpp_conf(15, MPP_SATA_LED);	/* SATA 1 active */
-	orion5x_mpp_conf(16, MPP_UNUSED);
-	orion5x_mpp_conf(17, MPP_UNUSED);
-	orion5x_mpp_conf(18, MPP_UNUSED);
-	orion5x_mpp_conf(19, MPP_UNUSED);
+	orion5x_mpp_conf(16, MPP_UART);		/* UART1 RXD */
+	orion5x_mpp_conf(17, MPP_UART);		/* UART1 TXD */
+	orion5x_mpp_conf(18, MPP_UART);		/* UART1 CTSn */
+	orion5x_mpp_conf(19, MPP_UART);		/* UART1 RTSn */
 
 	/*
 	 * Configure peripherals.
@@ -229,6 +365,9 @@
 	}
 
 	i2c_register_board_info(0, &kurobox_pro_i2c_rtc, 1);
+
+	/* register Kurobox Pro specific power-off method */
+	pm_power_off = kurobox_pro_power_off;
 }
 
 #ifdef CONFIG_MACH_KUROBOX_PRO