1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (c) 2009,2010 One Laptop per Child |
4 | */ |
5 | |
6 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
7 | |
8 | #include <linux/acpi.h> |
9 | #include <linux/delay.h> |
10 | #include <linux/i2c.h> |
11 | #include <linux/gpio/consumer.h> |
12 | #include <linux/gpio/machine.h> |
13 | #include <asm/olpc.h> |
14 | |
15 | /* TODO: this eventually belongs in linux/vx855.h */ |
16 | #define NR_VX855_GPI 14 |
17 | #define NR_VX855_GPO 13 |
18 | #define NR_VX855_GPIO 15 |
19 | |
20 | #define VX855_GPI(n) (n) |
21 | #define VX855_GPO(n) (NR_VX855_GPI + (n)) |
22 | #define VX855_GPIO(n) (NR_VX855_GPI + NR_VX855_GPO + (n)) |
23 | |
24 | #include "olpc_dcon.h" |
25 | |
26 | /* Hardware setup on the XO 1.5: |
27 | * DCONLOAD connects to VX855_GPIO1 (not SMBCK2) |
28 | * DCONBLANK connects to VX855_GPIO8 (not SSPICLK) unused in driver |
29 | * DCONSTAT0 connects to VX855_GPI10 (not SSPISDI) |
30 | * DCONSTAT1 connects to VX855_GPI11 (not nSSPISS) |
31 | * DCONIRQ connects to VX855_GPIO12 |
32 | * DCONSMBDATA connects to VX855 graphics CRTSPD |
33 | * DCONSMBCLK connects to VX855 graphics CRTSPCLK |
34 | */ |
35 | |
36 | #define VX855_GENL_PURPOSE_OUTPUT 0x44c /* PMIO_Rx4c-4f */ |
37 | #define VX855_GPI_STATUS_CHG 0x450 /* PMIO_Rx50 */ |
38 | #define VX855_GPI_SCI_SMI 0x452 /* PMIO_Rx52 */ |
39 | #define BIT_GPIO12 0x40 |
40 | |
41 | #define PREFIX "OLPC DCON:" |
42 | |
43 | enum dcon_gpios { |
44 | OLPC_DCON_STAT0, |
45 | OLPC_DCON_STAT1, |
46 | OLPC_DCON_LOAD, |
47 | }; |
48 | |
49 | struct gpiod_lookup_table gpios_table = { |
50 | .dev_id = NULL, |
51 | .table = { |
52 | GPIO_LOOKUP("VX855 South Bridge" , VX855_GPIO(1), "dcon_load" , |
53 | GPIO_ACTIVE_LOW), |
54 | GPIO_LOOKUP("VX855 South Bridge" , VX855_GPI(10), "dcon_stat0" , |
55 | GPIO_ACTIVE_LOW), |
56 | GPIO_LOOKUP("VX855 South Bridge" , VX855_GPI(11), "dcon_stat1" , |
57 | GPIO_ACTIVE_LOW), |
58 | { }, |
59 | }, |
60 | }; |
61 | |
62 | static const struct dcon_gpio gpios_asis[] = { |
63 | [OLPC_DCON_STAT0] = { .name = "dcon_stat0" , .flags = GPIOD_ASIS }, |
64 | [OLPC_DCON_STAT1] = { .name = "dcon_stat1" , .flags = GPIOD_ASIS }, |
65 | [OLPC_DCON_LOAD] = { .name = "dcon_load" , .flags = GPIOD_ASIS }, |
66 | }; |
67 | |
68 | static struct gpio_desc *gpios[3]; |
69 | |
70 | static void dcon_clear_irq(void) |
71 | { |
72 | /* irq status will appear in PMIO_Rx50[6] (RW1C) on gpio12 */ |
73 | outb(BIT_GPIO12, VX855_GPI_STATUS_CHG); |
74 | } |
75 | |
76 | static int dcon_was_irq(void) |
77 | { |
78 | u8 tmp; |
79 | |
80 | /* irq status will appear in PMIO_Rx50[6] on gpio12 */ |
81 | tmp = inb(VX855_GPI_STATUS_CHG); |
82 | |
83 | return !!(tmp & BIT_GPIO12); |
84 | } |
85 | |
86 | static int dcon_init_xo_1_5(struct dcon_priv *dcon) |
87 | { |
88 | unsigned int irq; |
89 | const struct dcon_gpio *pin = &gpios_asis[0]; |
90 | int i; |
91 | int ret; |
92 | |
93 | /* Add GPIO look up table */ |
94 | gpios_table.dev_id = dev_name(dev: &dcon->client->dev); |
95 | gpiod_add_lookup_table(table: &gpios_table); |
96 | |
97 | /* Get GPIO descriptor */ |
98 | for (i = 0; i < ARRAY_SIZE(gpios_asis); i++) { |
99 | gpios[i] = devm_gpiod_get(dev: &dcon->client->dev, con_id: pin[i].name, |
100 | flags: pin[i].flags); |
101 | if (IS_ERR(ptr: gpios[i])) { |
102 | ret = PTR_ERR(ptr: gpios[i]); |
103 | pr_err("failed to request %s GPIO: %d\n" , pin[i].name, |
104 | ret); |
105 | return ret; |
106 | } |
107 | } |
108 | |
109 | dcon_clear_irq(); |
110 | |
111 | /* set PMIO_Rx52[6] to enable SCI/SMI on gpio12 */ |
112 | outb(inb(VX855_GPI_SCI_SMI) | BIT_GPIO12, VX855_GPI_SCI_SMI); |
113 | |
114 | /* Determine the current state of DCONLOAD, likely set by firmware */ |
115 | /* GPIO1 */ |
116 | dcon->curr_src = (inl(VX855_GENL_PURPOSE_OUTPUT) & 0x1000) ? |
117 | DCON_SOURCE_CPU : DCON_SOURCE_DCON; |
118 | dcon->pending_src = dcon->curr_src; |
119 | |
120 | /* we're sharing the IRQ with ACPI */ |
121 | irq = acpi_gbl_FADT.sci_interrupt; |
122 | if (request_irq(irq, handler: &dcon_interrupt, IRQF_SHARED, name: "DCON" , dev: dcon)) { |
123 | pr_err("DCON (IRQ%d) allocation failed\n" , irq); |
124 | return 1; |
125 | } |
126 | |
127 | return 0; |
128 | } |
129 | |
130 | static void set_i2c_line(int sda, int scl) |
131 | { |
132 | unsigned char tmp; |
133 | unsigned int port = 0x26; |
134 | |
135 | /* FIXME: This directly accesses the CRT GPIO controller !!! */ |
136 | outb(value: port, port: 0x3c4); |
137 | tmp = inb(port: 0x3c5); |
138 | |
139 | if (scl) |
140 | tmp |= 0x20; |
141 | else |
142 | tmp &= ~0x20; |
143 | |
144 | if (sda) |
145 | tmp |= 0x10; |
146 | else |
147 | tmp &= ~0x10; |
148 | |
149 | tmp |= 0x01; |
150 | |
151 | outb(value: port, port: 0x3c4); |
152 | outb(value: tmp, port: 0x3c5); |
153 | } |
154 | |
155 | static void dcon_wiggle_xo_1_5(void) |
156 | { |
157 | int x; |
158 | |
159 | /* |
160 | * According to HiMax, when powering the DCON up we should hold |
161 | * SMB_DATA high for 8 SMB_CLK cycles. This will force the DCON |
162 | * state machine to reset to a (sane) initial state. Mitch Bradley |
163 | * did some testing and discovered that holding for 16 SMB_CLK cycles |
164 | * worked a lot more reliably, so that's what we do here. |
165 | */ |
166 | set_i2c_line(sda: 1, scl: 1); |
167 | |
168 | for (x = 0; x < 16; x++) { |
169 | udelay(5); |
170 | set_i2c_line(sda: 1, scl: 0); |
171 | udelay(5); |
172 | set_i2c_line(sda: 1, scl: 1); |
173 | } |
174 | udelay(5); |
175 | |
176 | /* set PMIO_Rx52[6] to enable SCI/SMI on gpio12 */ |
177 | outb(inb(VX855_GPI_SCI_SMI) | BIT_GPIO12, VX855_GPI_SCI_SMI); |
178 | } |
179 | |
180 | static void dcon_set_dconload_xo_1_5(int val) |
181 | { |
182 | gpiod_set_value(desc: gpios[OLPC_DCON_LOAD], value: val); |
183 | } |
184 | |
185 | static int dcon_read_status_xo_1_5(u8 *status) |
186 | { |
187 | if (!dcon_was_irq()) |
188 | return -1; |
189 | |
190 | /* i believe this is the same as "inb(0x44b) & 3" */ |
191 | *status = gpiod_get_value(desc: gpios[OLPC_DCON_STAT0]); |
192 | *status |= gpiod_get_value(desc: gpios[OLPC_DCON_STAT1]) << 1; |
193 | |
194 | dcon_clear_irq(); |
195 | |
196 | return 0; |
197 | } |
198 | |
199 | struct dcon_platform_data dcon_pdata_xo_1_5 = { |
200 | .init = dcon_init_xo_1_5, |
201 | .bus_stabilize_wiggle = dcon_wiggle_xo_1_5, |
202 | .set_dconload = dcon_set_dconload_xo_1_5, |
203 | .read_status = dcon_read_status_xo_1_5, |
204 | }; |
205 | |