summaryrefslogtreecommitdiff
path: root/drivers/staging/olpc_dcon/olpc_dcon_xo_1_5.c
diff options
context:
space:
mode:
authorAndres Salomon <dilinger@queued.net>2010-09-24 19:13:42 -0700
committerGreg Kroah-Hartman <gregkh@suse.de>2010-09-29 18:01:40 -0700
commiteecb3e4e5d9d83ebe1bef222a707eb7618498b67 (patch)
tree64e1c95f9e7530195f3b7a80fe120932c0fe30b4 /drivers/staging/olpc_dcon/olpc_dcon_xo_1_5.c
parent25417922694e60f04cd4dc8448ada9236f18c532 (diff)
staging: olpc_dcon: add OLPC display controller (DCON) support
This adds DCON support for the OLPC XO. The DCON is found in XO-1 and XO-1.5 hardware. The XO-1 has a CS5536 southbridge, while the XO-1.5 has a Via chipset; the GPIO magic that's necessary to communicate with the DCON chip is unfortunately different across both platforms. This driver supports both. This driver is in bad state atm, so I'm requesting its inclusion into staging so it can be cleaned up while staying in the kernel tree. Original driver by Dave Woodhouse, and modified extensively by Jordan Crouse, myself, Deepak Saxena, Paul Fox, Daniel Drake, and probably others that I've missed. Signed-off-by: Andres Salomon <dilinger@queued.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/staging/olpc_dcon/olpc_dcon_xo_1_5.c')
-rw-r--r--drivers/staging/olpc_dcon/olpc_dcon_xo_1_5.c219
1 files changed, 219 insertions, 0 deletions
diff --git a/drivers/staging/olpc_dcon/olpc_dcon_xo_1_5.c b/drivers/staging/olpc_dcon/olpc_dcon_xo_1_5.c
new file mode 100644
index 000000000000..cca6a235ef96
--- /dev/null
+++ b/drivers/staging/olpc_dcon/olpc_dcon_xo_1_5.c
@@ -0,0 +1,219 @@
+/*
+ * Copyright (c) 2009,2010 One Laptop per Child
+ *
+ * This program is free software. You can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ */
+
+#include <linux/acpi.h>
+
+/* Hardware setup on the XO 1.5:
+ * DCONLOAD connects to
+ * VX855_GPO12 (not nCR_PWOFF) (rev A)
+ * VX855_GPIO1 (not SMBCK2) (rev B)
+ * DCONBLANK connects to VX855_GPIO8 (not SSPICLK) unused in driver
+ * DCONSTAT0 connects to VX855_GPI10 (not SSPISDI)
+ * DCONSTAT1 connects to VX855_GPI11 (not nSSPISS)
+ * DCONIRQ connects to VX855_GPIO12 (on B3. on B2, it goes to
+ * SMBALRT, which doesn't work.)
+ * DCONSMBDATA connects to VX855 graphics CRTSPD
+ * DCONSMBCLK connects to VX855 graphics CRTSPCLK
+ */
+
+#define TEST_B2 0 // define to test B3 paths on a modded B2 board
+
+#define VX855_GENL_PURPOSE_OUTPUT 0x44c // PMIO_Rx4c-4f
+#define VX855_GPI_STATUS_CHG 0x450 // PMIO_Rx50
+#define VX855_GPI_SCI_SMI 0x452 // PMIO_Rx52
+#define BIT_GPIO12 0x40
+
+#define PREFIX "OLPC DCON:"
+
+/*
+ there is no support here for DCONIRQ on 1.5 boards earlier than
+ B3. the issue is that the DCONIRQ signal on earlier boards is
+ routed to SMBALRT, which turns out to to be a level sensitive
+ interrupt. the DCONIRQ signal is far too short (11usec) to
+ be detected reliably in that case. including support for
+ DCONIRQ functions no better than none at all.
+*/
+
+static struct dcon_platform_data dcon_pdata_xo_1_5;
+
+static void dcon_clear_irq(void)
+{
+ if (TEST_B2 || olpc_board_at_least(olpc_board(BOARD_XO_1_5_B3))) {
+ // irq status will appear in PMIO_Rx50[6] (RW1C) on gpio12
+ outb(BIT_GPIO12, VX855_GPI_STATUS_CHG);
+ }
+}
+
+static int dcon_was_irq(void)
+{
+ u_int8_t tmp;
+
+ if (TEST_B2 || olpc_board_at_least(olpc_board(BOARD_XO_1_5_B3))) {
+ // irq status will appear in PMIO_Rx50[6] on gpio12
+ tmp = inb(VX855_GPI_STATUS_CHG);
+ return !!(tmp & BIT_GPIO12);
+ }
+
+ return 0;
+}
+
+static int dcon_init_xo_1_5(void)
+{
+ unsigned int irq;
+ u_int8_t tmp;
+ struct pci_dev *pdev;
+
+
+ pdev = pci_get_device(PCI_VENDOR_ID_VIA,
+ PCI_DEVICE_ID_VIA_VX855, NULL);
+ if (!pdev) {
+ printk(KERN_ERR "cannot find VX855 PCI ID\n");
+ return 1;
+ }
+
+ if (olpc_board_at_least(olpc_board(BOARD_XO_1_5_B1))) {
+ pci_read_config_byte(pdev, 0x95, &tmp);
+ pci_write_config_byte(pdev, 0x95, tmp|0x0c);
+ } else {
+ /* Set GPO12 to GPO mode, not nCR_PWOFF */
+ pci_read_config_byte(pdev, 0x9b, &tmp);
+ pci_write_config_byte(pdev, 0x9b, tmp|0x01);
+ }
+
+ /* Set GPIO8 to GPIO mode, not SSPICLK */
+ pci_read_config_byte(pdev, 0xe3, &tmp);
+ pci_write_config_byte(pdev, 0xe3, tmp | 0x04);
+
+ /* Set GPI10/GPI11 to GPI mode, not SSPISDI/SSPISS */
+ pci_read_config_byte(pdev, 0xe4, &tmp);
+ pci_write_config_byte(pdev, 0xe4, tmp|0x08);
+
+ if (TEST_B2 || olpc_board_at_least(olpc_board(BOARD_XO_1_5_B3))) {
+ // clear PMU_RxE1[6] to select SCI on GPIO12
+ // clear PMU_RxE0[6] to choose falling edge
+ pci_read_config_byte(pdev, 0xe1, &tmp);
+ pci_write_config_byte(pdev, 0xe1, tmp & ~BIT_GPIO12);
+ pci_read_config_byte(pdev, 0xe0, &tmp);
+ pci_write_config_byte(pdev, 0xe0, tmp & ~BIT_GPIO12);
+
+ dcon_clear_irq();
+
+ // set PMIO_Rx52[6] to enable SCI/SMI on gpio12
+ outb(inb(VX855_GPI_SCI_SMI)|BIT_GPIO12, VX855_GPI_SCI_SMI);
+
+ }
+
+ /* Determine the current state of DCONLOAD, likely set by firmware */
+ if (olpc_board_at_least(olpc_board(BOARD_XO_1_5_B1))) {
+ // GPIO1
+ dcon_source = (inl(VX855_GENL_PURPOSE_OUTPUT) & 0x1000) ?
+ DCON_SOURCE_CPU : DCON_SOURCE_DCON;
+ } else {
+ // GPO12
+ dcon_source = (inl(VX855_GENL_PURPOSE_OUTPUT) & 0x04000000) ?
+ DCON_SOURCE_CPU : DCON_SOURCE_DCON;
+ }
+ dcon_pending = dcon_source;
+
+ pci_dev_put(pdev);
+
+ /* we're sharing the IRQ with ACPI */
+ irq = acpi_gbl_FADT.sci_interrupt;
+ if (request_irq(irq, &dcon_interrupt, IRQF_SHARED, "DCON", &dcon_driver)) {
+ printk(KERN_ERR PREFIX "DCON (IRQ%d) allocation failed\n", irq);
+ return 1;
+ }
+
+
+ return 0;
+}
+
+static void set_i2c_line(int sda, int scl)
+{
+ unsigned char tmp;
+ unsigned int port = 0x26;
+
+ /* FIXME: This directly accesses the CRT GPIO controller !!! */
+ outb(port, 0x3c4);
+ tmp = inb(0x3c5);
+
+ if (scl)
+ tmp |= 0x20;
+ else
+ tmp &= ~0x20;
+
+ if (sda)
+ tmp |= 0x10;
+ else
+ tmp &= ~0x10;
+
+ tmp |= 0x01;
+
+ outb(port, 0x3c4);
+ outb(tmp, 0x3c5);
+}
+
+
+static void dcon_wiggle_xo_1_5(void)
+{
+ int x;
+
+ /*
+ * According to HiMax, when powering the DCON up we should hold
+ * SMB_DATA high for 8 SMB_CLK cycles. This will force the DCON
+ * state machine to reset to a (sane) initial state. Mitch Bradley
+ * did some testing and discovered that holding for 16 SMB_CLK cycles
+ * worked a lot more reliably, so that's what we do here.
+ */
+ set_i2c_line(1, 1);
+
+ for (x = 0; x < 16; x++) {
+ udelay(5);
+ set_i2c_line(1, 0);
+ udelay(5);
+ set_i2c_line(1, 1);
+ }
+ udelay(5);
+
+ if (TEST_B2 || olpc_board_at_least(olpc_board(BOARD_XO_1_5_B3))) {
+ // set PMIO_Rx52[6] to enable SCI/SMI on gpio12
+ outb(inb(VX855_GPI_SCI_SMI)|BIT_GPIO12, VX855_GPI_SCI_SMI);
+ }
+}
+
+static void dcon_set_dconload_xo_1_5(int val)
+{
+ if (olpc_board_at_least(olpc_board(BOARD_XO_1_5_B1))) {
+ gpio_set_value(VX855_GPIO(1), val);
+ } else {
+ gpio_set_value(VX855_GPO(12), val);
+ }
+}
+
+static int dcon_read_status_xo_1_5(void)
+{
+ int status;
+
+ if (!dcon_was_irq())
+ return -1;
+
+ // i believe this is the same as "inb(0x44b) & 3"
+ status = gpio_get_value(VX855_GPI(10));
+ status |= gpio_get_value(VX855_GPI(11)) << 1;
+
+ dcon_clear_irq();
+
+ return status;
+}
+
+static struct dcon_platform_data dcon_pdata_xo_1_5 = {
+ .init = dcon_init_xo_1_5,
+ .bus_stabilize_wiggle = dcon_wiggle_xo_1_5,
+ .set_dconload = dcon_set_dconload_xo_1_5,
+ .read_status = dcon_read_status_xo_1_5,
+};