1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Mainly by David Woodhouse, somewhat modified by Jordan Crouse |
4 | * |
5 | * Copyright © 2006-2007 Red Hat, Inc. |
6 | * Copyright © 2006-2007 Advanced Micro Devices, Inc. |
7 | * Copyright © 2009 VIA Technology, Inc. |
8 | * Copyright (c) 2010 Andres Salomon <dilinger@queued.net> |
9 | */ |
10 | |
11 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
12 | |
13 | #include <linux/cs5535.h> |
14 | #include <linux/gpio/consumer.h> |
15 | #include <linux/delay.h> |
16 | #include <linux/i2c.h> |
17 | #include <asm/olpc.h> |
18 | |
19 | #include "olpc_dcon.h" |
20 | |
21 | enum dcon_gpios { |
22 | OLPC_DCON_STAT0, |
23 | OLPC_DCON_STAT1, |
24 | OLPC_DCON_IRQ, |
25 | OLPC_DCON_LOAD, |
26 | OLPC_DCON_BLANK, |
27 | }; |
28 | |
29 | static const struct dcon_gpio gpios_asis[] = { |
30 | [OLPC_DCON_STAT0] = { .name = "dcon_stat0" , .flags = GPIOD_ASIS }, |
31 | [OLPC_DCON_STAT1] = { .name = "dcon_stat1" , .flags = GPIOD_ASIS }, |
32 | [OLPC_DCON_IRQ] = { .name = "dcon_irq" , .flags = GPIOD_ASIS }, |
33 | [OLPC_DCON_LOAD] = { .name = "dcon_load" , .flags = GPIOD_ASIS }, |
34 | [OLPC_DCON_BLANK] = { .name = "dcon_blank" , .flags = GPIOD_ASIS }, |
35 | }; |
36 | |
37 | static struct gpio_desc *gpios[5]; |
38 | |
39 | static int dcon_init_xo_1(struct dcon_priv *dcon) |
40 | { |
41 | unsigned char lob; |
42 | int ret, i; |
43 | const struct dcon_gpio *pin = &gpios_asis[0]; |
44 | |
45 | for (i = 0; i < ARRAY_SIZE(gpios_asis); i++) { |
46 | gpios[i] = devm_gpiod_get(dev: &dcon->client->dev, con_id: pin[i].name, |
47 | flags: pin[i].flags); |
48 | if (IS_ERR(ptr: gpios[i])) { |
49 | ret = PTR_ERR(ptr: gpios[i]); |
50 | pr_err("failed to request %s GPIO: %d\n" , pin[i].name, |
51 | ret); |
52 | return ret; |
53 | } |
54 | } |
55 | |
56 | /* Turn off the event enable for GPIO7 just to be safe */ |
57 | cs5535_gpio_clear(OLPC_GPIO_DCON_IRQ, GPIO_EVENTS_ENABLE); |
58 | |
59 | /* |
60 | * Determine the current state by reading the GPIO bit; earlier |
61 | * stages of the boot process have established the state. |
62 | * |
63 | * Note that we read GPIO_OUTPUT_VAL rather than GPIO_READ_BACK here; |
64 | * this is because OFW will disable input for the pin and set a value.. |
65 | * READ_BACK will only contain a valid value if input is enabled and |
66 | * then a value is set. So, future readings of the pin can use |
67 | * READ_BACK, but the first one cannot. Awesome, huh? |
68 | */ |
69 | dcon->curr_src = cs5535_gpio_isset(OLPC_GPIO_DCON_LOAD, GPIO_OUTPUT_VAL) |
70 | ? DCON_SOURCE_CPU |
71 | : DCON_SOURCE_DCON; |
72 | dcon->pending_src = dcon->curr_src; |
73 | |
74 | /* Set the directions for the GPIO pins */ |
75 | gpiod_direction_input(desc: gpios[OLPC_DCON_STAT0]); |
76 | gpiod_direction_input(desc: gpios[OLPC_DCON_STAT1]); |
77 | gpiod_direction_input(desc: gpios[OLPC_DCON_IRQ]); |
78 | gpiod_direction_input(desc: gpios[OLPC_DCON_BLANK]); |
79 | gpiod_direction_output(desc: gpios[OLPC_DCON_LOAD], |
80 | value: dcon->curr_src == DCON_SOURCE_CPU); |
81 | |
82 | /* Set up the interrupt mappings */ |
83 | |
84 | /* Set the IRQ to pair 2 */ |
85 | cs5535_gpio_setup_event(OLPC_GPIO_DCON_IRQ, pair: 2, pme: 0); |
86 | |
87 | /* Enable group 2 to trigger the DCON interrupt */ |
88 | cs5535_gpio_set_irq(group: 2, DCON_IRQ); |
89 | |
90 | /* Select edge level for interrupt (in PIC) */ |
91 | lob = inb(port: 0x4d0); |
92 | lob &= ~(1 << DCON_IRQ); |
93 | outb(value: lob, port: 0x4d0); |
94 | |
95 | /* Register the interrupt handler */ |
96 | if (request_irq(DCON_IRQ, handler: &dcon_interrupt, flags: 0, name: "DCON" , dev: dcon)) { |
97 | pr_err("failed to request DCON's irq\n" ); |
98 | return -EIO; |
99 | } |
100 | |
101 | /* Clear INV_EN for GPIO7 (DCONIRQ) */ |
102 | cs5535_gpio_clear(OLPC_GPIO_DCON_IRQ, GPIO_INPUT_INVERT); |
103 | |
104 | /* Enable filter for GPIO12 (DCONBLANK) */ |
105 | cs5535_gpio_set(OLPC_GPIO_DCON_BLANK, GPIO_INPUT_FILTER); |
106 | |
107 | /* Disable filter for GPIO7 */ |
108 | cs5535_gpio_clear(OLPC_GPIO_DCON_IRQ, GPIO_INPUT_FILTER); |
109 | |
110 | /* Disable event counter for GPIO7 (DCONIRQ) and GPIO12 (DCONBLANK) */ |
111 | cs5535_gpio_clear(OLPC_GPIO_DCON_IRQ, GPIO_INPUT_EVENT_COUNT); |
112 | cs5535_gpio_clear(OLPC_GPIO_DCON_BLANK, GPIO_INPUT_EVENT_COUNT); |
113 | |
114 | /* Add GPIO12 to the Filter Event Pair #7 */ |
115 | cs5535_gpio_set(OLPC_GPIO_DCON_BLANK, GPIO_FE7_SEL); |
116 | |
117 | /* Turn off negative Edge Enable for GPIO12 */ |
118 | cs5535_gpio_clear(OLPC_GPIO_DCON_BLANK, GPIO_NEGATIVE_EDGE_EN); |
119 | |
120 | /* Enable negative Edge Enable for GPIO7 */ |
121 | cs5535_gpio_set(OLPC_GPIO_DCON_IRQ, GPIO_NEGATIVE_EDGE_EN); |
122 | |
123 | /* Zero the filter amount for Filter Event Pair #7 */ |
124 | cs5535_gpio_set(offset: 0, GPIO_FLTR7_AMOUNT); |
125 | |
126 | /* Clear the negative edge status for GPIO7 and GPIO12 */ |
127 | cs5535_gpio_set(OLPC_GPIO_DCON_IRQ, GPIO_NEGATIVE_EDGE_STS); |
128 | cs5535_gpio_set(OLPC_GPIO_DCON_BLANK, GPIO_NEGATIVE_EDGE_STS); |
129 | |
130 | /* FIXME: Clear the positive status as well, just to be sure */ |
131 | cs5535_gpio_set(OLPC_GPIO_DCON_IRQ, GPIO_POSITIVE_EDGE_STS); |
132 | cs5535_gpio_set(OLPC_GPIO_DCON_BLANK, GPIO_POSITIVE_EDGE_STS); |
133 | |
134 | /* Enable events for GPIO7 (DCONIRQ) and GPIO12 (DCONBLANK) */ |
135 | cs5535_gpio_set(OLPC_GPIO_DCON_IRQ, GPIO_EVENTS_ENABLE); |
136 | cs5535_gpio_set(OLPC_GPIO_DCON_BLANK, GPIO_EVENTS_ENABLE); |
137 | |
138 | return 0; |
139 | } |
140 | |
141 | static void dcon_wiggle_xo_1(void) |
142 | { |
143 | int x; |
144 | |
145 | /* |
146 | * According to HiMax, when powering the DCON up we should hold |
147 | * SMB_DATA high for 8 SMB_CLK cycles. This will force the DCON |
148 | * state machine to reset to a (sane) initial state. Mitch Bradley |
149 | * did some testing and discovered that holding for 16 SMB_CLK cycles |
150 | * worked a lot more reliably, so that's what we do here. |
151 | * |
152 | * According to the cs5536 spec, to set GPIO14 to SMB_CLK we must |
153 | * simultaneously set AUX1 IN/OUT to GPIO14; ditto for SMB_DATA and |
154 | * GPIO15. |
155 | */ |
156 | cs5535_gpio_set(OLPC_GPIO_SMB_CLK, GPIO_OUTPUT_VAL); |
157 | cs5535_gpio_set(OLPC_GPIO_SMB_DATA, GPIO_OUTPUT_VAL); |
158 | cs5535_gpio_set(OLPC_GPIO_SMB_CLK, GPIO_OUTPUT_ENABLE); |
159 | cs5535_gpio_set(OLPC_GPIO_SMB_DATA, GPIO_OUTPUT_ENABLE); |
160 | cs5535_gpio_clear(OLPC_GPIO_SMB_CLK, GPIO_OUTPUT_AUX1); |
161 | cs5535_gpio_clear(OLPC_GPIO_SMB_DATA, GPIO_OUTPUT_AUX1); |
162 | cs5535_gpio_clear(OLPC_GPIO_SMB_CLK, GPIO_OUTPUT_AUX2); |
163 | cs5535_gpio_clear(OLPC_GPIO_SMB_DATA, GPIO_OUTPUT_AUX2); |
164 | cs5535_gpio_clear(OLPC_GPIO_SMB_CLK, GPIO_INPUT_AUX1); |
165 | cs5535_gpio_clear(OLPC_GPIO_SMB_DATA, GPIO_INPUT_AUX1); |
166 | |
167 | for (x = 0; x < 16; x++) { |
168 | udelay(5); |
169 | cs5535_gpio_clear(OLPC_GPIO_SMB_CLK, GPIO_OUTPUT_VAL); |
170 | udelay(5); |
171 | cs5535_gpio_set(OLPC_GPIO_SMB_CLK, GPIO_OUTPUT_VAL); |
172 | } |
173 | udelay(5); |
174 | cs5535_gpio_set(OLPC_GPIO_SMB_CLK, GPIO_OUTPUT_AUX1); |
175 | cs5535_gpio_set(OLPC_GPIO_SMB_DATA, GPIO_OUTPUT_AUX1); |
176 | cs5535_gpio_set(OLPC_GPIO_SMB_CLK, GPIO_INPUT_AUX1); |
177 | cs5535_gpio_set(OLPC_GPIO_SMB_DATA, GPIO_INPUT_AUX1); |
178 | } |
179 | |
180 | static void dcon_set_dconload_1(int val) |
181 | { |
182 | gpiod_set_value(desc: gpios[OLPC_DCON_LOAD], value: val); |
183 | } |
184 | |
185 | static int dcon_read_status_xo_1(u8 *status) |
186 | { |
187 | *status = gpiod_get_value(desc: gpios[OLPC_DCON_STAT0]); |
188 | *status |= gpiod_get_value(desc: gpios[OLPC_DCON_STAT1]) << 1; |
189 | |
190 | /* Clear the negative edge status for GPIO7 */ |
191 | cs5535_gpio_set(OLPC_GPIO_DCON_IRQ, GPIO_NEGATIVE_EDGE_STS); |
192 | |
193 | return 0; |
194 | } |
195 | |
196 | struct dcon_platform_data dcon_pdata_xo_1 = { |
197 | .init = dcon_init_xo_1, |
198 | .bus_stabilize_wiggle = dcon_wiggle_xo_1, |
199 | .set_dconload = dcon_set_dconload_1, |
200 | .read_status = dcon_read_status_xo_1, |
201 | }; |
202 | |