From a0087f2fcf5cb4e16502f5334168fbe304af318b Mon Sep 17 00:00:00 2001 From: Sylver Bruneau Date: Wed, 30 Apr 2008 08:14:58 +0200 Subject: [ARM] Orion: implement power-off method for Kurobox Pro 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 Acked-by: Russell King Signed-off-by: Lennert Buytenhek --- arch/arm/mach-orion5x/kurobox_pro-setup.c | 147 +++++++++++++++++++++++++++++- 1 file changed, 143 insertions(+), 4 deletions(-) (limited to 'arch/arm/mach-orion5x/kurobox_pro-setup.c') diff --git a/arch/arm/mach-orion5x/kurobox_pro-setup.c b/arch/arm/mach-orion5x/kurobox_pro-setup.c index 8b4a8aa5a836..37b4faa6ace7 100644 --- a/arch/arm/mach-orion5x/kurobox_pro-setup.c +++ b/arch/arm/mach-orion5x/kurobox_pro-setup.c @@ -13,10 +13,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -174,6 +176,140 @@ static struct mv_sata_platform_data kurobox_pro_sata_data = { .n_ports = 2, }; +/***************************************************************************** + * 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 ****************************************************************************/ @@ -194,10 +330,10 @@ static struct orion5x_mpp_mode kurobox_pro_mpp_modes[] __initdata = { { 13, MPP_SATA_LED }, /* SATA 1 presence */ { 14, MPP_SATA_LED }, /* SATA 0 active */ { 15, MPP_SATA_LED }, /* SATA 1 active */ - { 16, MPP_UNUSED }, - { 17, MPP_UNUSED }, - { 18, MPP_UNUSED }, - { 19, MPP_UNUSED }, + { 16, MPP_UART }, /* UART1 RXD */ + { 17, MPP_UART }, /* UART1 TXD */ + { 18, MPP_UART }, /* UART1 CTSn */ + { 19, MPP_UART }, /* UART1 RTSn */ { -1 }, }; @@ -231,6 +367,9 @@ static void __init kurobox_pro_init(void) } 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 -- cgit v1.2.3-59-g8ed1b