Line 0
Link Here
|
|
|
1 |
/* |
2 |
* PowerMac G5 SMU driver |
3 |
* |
4 |
* Copyright 2004 J. Mayer <l_indien@magic.fr> |
5 |
* |
6 |
* Released under the term of the GNU GPL v2. |
7 |
*/ |
8 |
|
9 |
/* |
10 |
* For now, this driver includes: |
11 |
* - RTC get & set |
12 |
* - reboot & shutdown commands |
13 |
*/ |
14 |
|
15 |
#include <linux/config.h> |
16 |
#include <linux/types.h> |
17 |
#include <linux/kernel.h> |
18 |
#include <linux/device.h> |
19 |
#include <linux/dmapool.h> |
20 |
#include <linux/bootmem.h> |
21 |
#include <linux/vmalloc.h> |
22 |
#include <linux/highmem.h> |
23 |
#include <linux/jiffies.h> |
24 |
#include <linux/interrupt.h> |
25 |
#include <linux/rtc.h> |
26 |
#include <asm/byteorder.h> |
27 |
#include <asm/io.h> |
28 |
#include <asm/prom.h> |
29 |
#include <asm/machdep.h> |
30 |
#include <asm/pmac_feature.h> |
31 |
#include <asm/sections.h> |
32 |
|
33 |
#define DEBUG_SMU 1 |
34 |
|
35 |
#if defined(DEBUG_SMU) |
36 |
#define DPRINTK(fmt, args...) do { printk(KERN_DEBUG fmt , ##args); } while (0) |
37 |
#else |
38 |
#define DPRINTK(fmt, args...) do { } while (0) |
39 |
#endif |
40 |
|
41 |
typedef struct cmd_buf_t cmd_buf_t; |
42 |
struct cmd_buf_t { |
43 |
uint8_t cmd; |
44 |
uint8_t length; |
45 |
uint8_t data[0x0FFE]; |
46 |
}; |
47 |
|
48 |
struct SMU_dev_t { |
49 |
spinlock_t lock; |
50 |
struct device_node *np; |
51 |
unsigned char name[16]; |
52 |
int db_ack; /* SMU doorbell ack GPIO */ |
53 |
int db_req; /* SMU doorbell req GPIO */ |
54 |
u32 db_buff0; |
55 |
u32 db_buff; /* SMU doorbell buffer location */ |
56 |
void *db_buff_remap; /* SMU doorbell buffer location remapped */ |
57 |
int SMU_irq; /* SMU IRQ */ |
58 |
int SMU_irq_gpio; /* SMU interrupt GPIO */ |
59 |
int programmer_switch; /* SMU programmer switch GPIO */ |
60 |
int programmer_irq; /* SMU programmer switch IRQ */ |
61 |
int WOR_disable; |
62 |
int WOR_enable; |
63 |
cmd_buf_t *cmd_buf; |
64 |
}; |
65 |
|
66 |
#define SMU_MAX 4 |
67 |
static struct SMU_dev_t SMU_devices[SMU_MAX]; |
68 |
static int SMU_nb; |
69 |
|
70 |
/* SMU low level stuff */ |
71 |
static inline int cmd_stat (cmd_buf_t *cmd_buf, u8 cmd_ack) |
72 |
{ |
73 |
return cmd_buf->cmd == cmd_ack && cmd_buf->length != 0; |
74 |
} |
75 |
|
76 |
static inline u8 save_ack_cmd (cmd_buf_t *cmd_buf) |
77 |
{ |
78 |
return (~cmd_buf->cmd) & 0xff; |
79 |
} |
80 |
|
81 |
static void send_cmd (struct SMU_dev_t *dev) |
82 |
{ |
83 |
cmd_buf_t *cmd_buf; |
84 |
|
85 |
cmd_buf = dev->cmd_buf; |
86 |
out_le32(dev->db_buff_remap, virt_to_phys(cmd_buf)); |
87 |
/* Ring the SMU doorbell */ |
88 |
pmac_do_feature_call(PMAC_FTR_WRITE_GPIO, NULL, dev->db_req, 4); |
89 |
} |
90 |
|
91 |
static int cmd_done (struct SMU_dev_t *dev) |
92 |
{ |
93 |
unsigned long wait; |
94 |
int gpio; |
95 |
|
96 |
/* Check the SMU doorbell */ |
97 |
for (wait = jiffies + HZ; time_before(jiffies, wait); ) { |
98 |
gpio = pmac_do_feature_call(PMAC_FTR_READ_GPIO, |
99 |
NULL, dev->db_ack); |
100 |
if ((gpio & 7) == 7) |
101 |
return 0; |
102 |
} |
103 |
|
104 |
return -1; |
105 |
} |
106 |
|
107 |
static int do_cmd (struct SMU_dev_t *dev) |
108 |
{ |
109 |
int ret; |
110 |
u8 cmd_ack; |
111 |
|
112 |
DPRINTK("SMU do_cmd %02x len=%d %02x\n", |
113 |
dev->cmd_buf->cmd, dev->cmd_buf->length, dev->cmd_buf->data[0]); |
114 |
cmd_ack = save_ack_cmd(dev->cmd_buf); |
115 |
/* Clear cmd_buf cache lines */ |
116 |
flush_inval_dcache_phys_range(virt_to_phys(dev->cmd_buf), |
117 |
virt_to_phys(dev->cmd_buf + 1)); |
118 |
mb(); |
119 |
send_cmd(dev); |
120 |
ret = cmd_done(dev); |
121 |
mb(); |
122 |
if (ret == 0) |
123 |
ret = cmd_stat(dev->cmd_buf, cmd_ack) ? 0 : -1; |
124 |
DPRINTK("SMU do_cmd %02x len=%d %02x => %d (%02x)\n", dev->cmd_buf->cmd, |
125 |
dev->cmd_buf->length, dev->cmd_buf->data[0], ret, cmd_ack); |
126 |
|
127 |
return ret; |
128 |
} |
129 |
|
130 |
static irqreturn_t SMU_irq_handler (int irq, void *arg, struct pt_regs *regs) |
131 |
{ |
132 |
/* Fake handler for now */ |
133 |
// printk("SMU irq %d\n", irq); |
134 |
|
135 |
return 0; |
136 |
} |
137 |
|
138 |
/* RTC low level commands */ |
139 |
static inline int bcd2hex (int n) |
140 |
{ |
141 |
return (((n & 0xf0) >> 4) * 10) + (n & 0xf); |
142 |
} |
143 |
|
144 |
static inline int hex2bcd (int n) |
145 |
{ |
146 |
return ((n / 10) << 4) + (n % 10); |
147 |
} |
148 |
|
149 |
static inline void set_pwrup_timer_cmd (cmd_buf_t *cmd_buf) |
150 |
{ |
151 |
cmd_buf->cmd = 0x8e; |
152 |
cmd_buf->length = 8; |
153 |
cmd_buf->data[0] = 0x00; |
154 |
memset(cmd_buf->data + 1, 0, 7); |
155 |
} |
156 |
|
157 |
static inline void get_pwrup_timer_cmd (cmd_buf_t *cmd_buf) |
158 |
{ |
159 |
cmd_buf->cmd = 0x8e; |
160 |
cmd_buf->length = 1; |
161 |
cmd_buf->data[0] = 0x01; |
162 |
} |
163 |
|
164 |
static inline void disable_pwrup_timer_cmd (cmd_buf_t *cmd_buf) |
165 |
{ |
166 |
cmd_buf->cmd = 0x8e; |
167 |
cmd_buf->length = 1; |
168 |
cmd_buf->data[0] = 0x02; |
169 |
} |
170 |
|
171 |
static inline void set_rtc_cmd (cmd_buf_t *cmd_buf, struct rtc_time *time) |
172 |
{ |
173 |
cmd_buf->cmd = 0x8e; |
174 |
cmd_buf->length = 8; |
175 |
cmd_buf->data[0] = 0x80; |
176 |
cmd_buf->data[1] = hex2bcd(time->tm_sec); |
177 |
cmd_buf->data[2] = hex2bcd(time->tm_min); |
178 |
cmd_buf->data[3] = hex2bcd(time->tm_hour); |
179 |
cmd_buf->data[4] = time->tm_wday; |
180 |
cmd_buf->data[5] = hex2bcd(time->tm_mday); |
181 |
cmd_buf->data[6] = hex2bcd(time->tm_mon) + 1; |
182 |
cmd_buf->data[7] = hex2bcd(time->tm_year - 100); |
183 |
} |
184 |
|
185 |
static inline void get_rtc_cmd (cmd_buf_t *cmd_buf) |
186 |
{ |
187 |
cmd_buf->cmd = 0x8e; |
188 |
cmd_buf->length = 1; |
189 |
cmd_buf->data[0] = 0x81; |
190 |
} |
191 |
|
192 |
/* RTC interface */ |
193 |
void __pmac pmac_get_rtc_time (struct rtc_time *time) |
194 |
{ |
195 |
struct SMU_dev_t *dev; |
196 |
cmd_buf_t *cmd_buf; |
197 |
|
198 |
if (SMU_nb == 0 || SMU_devices[0].cmd_buf == NULL) |
199 |
return; |
200 |
memset(time, 0, sizeof(struct rtc_time)); |
201 |
dev = &SMU_devices[0]; |
202 |
cmd_buf = dev->cmd_buf; |
203 |
DPRINTK("SMU get_rtc_time %p\n", cmd_buf); |
204 |
spin_lock(&dev->lock); |
205 |
get_rtc_cmd(cmd_buf); |
206 |
if (do_cmd(dev) == 0) { |
207 |
time->tm_sec = bcd2hex(cmd_buf->data[0]); |
208 |
time->tm_min = bcd2hex(cmd_buf->data[1]); |
209 |
time->tm_hour = bcd2hex(cmd_buf->data[2]); |
210 |
time->tm_wday = bcd2hex(cmd_buf->data[3]); |
211 |
time->tm_mday = bcd2hex(cmd_buf->data[4]); |
212 |
time->tm_mon = bcd2hex(cmd_buf->data[5]) - 1; |
213 |
time->tm_year = bcd2hex(cmd_buf->data[6]) + 100; |
214 |
} |
215 |
spin_unlock(&dev->lock); |
216 |
DPRINTK("SMU get_rtc_time done\n"); |
217 |
} |
218 |
|
219 |
void __pmac pmac_set_rtc_time (struct rtc_time *time) |
220 |
{ |
221 |
struct SMU_dev_t *dev; |
222 |
cmd_buf_t *cmd_buf; |
223 |
|
224 |
if (SMU_nb == 0 || SMU_devices[0].cmd_buf == NULL) |
225 |
return; |
226 |
DPRINTK("SMU set_rtc_time\n"); |
227 |
dev = &SMU_devices[0]; |
228 |
cmd_buf = dev->cmd_buf; |
229 |
spin_lock(&dev->lock); |
230 |
set_rtc_cmd(cmd_buf, time); |
231 |
do_cmd(dev); |
232 |
spin_unlock(&dev->lock); |
233 |
DPRINTK("SMU set_rtc_time done\n"); |
234 |
} |
235 |
|
236 |
void __init pmac_get_boot_time (struct rtc_time *tm) |
237 |
{ |
238 |
if (SMU_nb == 0 || SMU_devices[0].cmd_buf == NULL) { |
239 |
printk(KERN_ERR "%s: no SMU registered\n", __func__); |
240 |
return; |
241 |
} |
242 |
DPRINTK("SMU get_boot_time\n"); |
243 |
pmac_get_rtc_time(tm); |
244 |
} |
245 |
|
246 |
/* Misc functions */ |
247 |
void smu_shutdown (void) |
248 |
{ |
249 |
const unsigned char *command = "SHUTDOWN"; |
250 |
struct SMU_dev_t *dev; |
251 |
cmd_buf_t *cmd_buf; |
252 |
|
253 |
if (SMU_nb == 0) |
254 |
return; |
255 |
dev = &SMU_devices[0]; |
256 |
cmd_buf = dev->cmd_buf; |
257 |
spin_lock(&dev->lock); |
258 |
cmd_buf->cmd = 0xaa; |
259 |
cmd_buf->length = strlen(command); |
260 |
strcpy(cmd_buf->data, command); |
261 |
do_cmd(dev); |
262 |
/* If we get here, we got a problem */ |
263 |
{ |
264 |
int i; |
265 |
for (i = 0; i < cmd_buf->length; i++) |
266 |
printk("%02x ", cmd_buf->data[i]); |
267 |
printk("\n"); |
268 |
} |
269 |
spin_unlock(&dev->lock); |
270 |
} |
271 |
|
272 |
void smu_restart (void) |
273 |
{ |
274 |
const unsigned char *command = "RESTART"; |
275 |
struct SMU_dev_t *dev; |
276 |
cmd_buf_t *cmd_buf; |
277 |
|
278 |
if (SMU_nb == 0) |
279 |
return; |
280 |
dev = &SMU_devices[0]; |
281 |
cmd_buf = dev->cmd_buf; |
282 |
spin_lock(&dev->lock); |
283 |
cmd_buf->cmd = 0xaa; |
284 |
cmd_buf->length = strlen(command); |
285 |
strcpy(cmd_buf->data, command); |
286 |
do_cmd(dev); |
287 |
/* If we get here, we got a problem */ |
288 |
{ |
289 |
int i; |
290 |
for (i = 0; i < cmd_buf->length; i++) |
291 |
printk("%02x ", cmd_buf->data[i]); |
292 |
printk("\n"); |
293 |
} |
294 |
spin_unlock(&dev->lock); |
295 |
} |
296 |
|
297 |
/* SMU initialisation */ |
298 |
static int smu_register (struct device_node *np, |
299 |
int db_ack, int db_req, u32 db_buff, |
300 |
int SMU_irq, int SMU_irq_gpio) |
301 |
{ |
302 |
void *db_buff_remap; |
303 |
cmd_buf_t *cmd_buf; |
304 |
|
305 |
ppc64_boot_msg(0x13, "register one SMU\n"); |
306 |
sprintf(SMU_devices[SMU_nb].name, "SMU%d\n", SMU_nb); |
307 |
if (SMU_nb == SMU_MAX) { |
308 |
ppc64_boot_msg(0x13, "Too many SMUs\n"); |
309 |
return -1; |
310 |
} |
311 |
db_buff_remap = ioremap(0x8000860C, 16); |
312 |
if (db_buff_remap == NULL) { |
313 |
ppc64_boot_msg(0x13, "SMU remap fail\n"); |
314 |
return -1; |
315 |
} |
316 |
cmd_buf = alloc_bootmem(sizeof(cmd_buf_t)); |
317 |
if (cmd_buf == NULL) { |
318 |
ppc64_boot_msg(0x13, "SMU alloc fail\n"); |
319 |
iounmap(db_buff_remap); |
320 |
return -1; |
321 |
} |
322 |
#if O |
323 |
{ |
324 |
unsigned long flags; |
325 |
ppc64_boot_msg(0x13, "SMU IRQ\n"); |
326 |
flags = SA_SHIRQ | SA_INTERRUPT; |
327 |
if (request_irq(SMU_irq, &SMU_irq_handler, flags, |
328 |
SMU_devices[SMU_nb].name, &SMU_devices[SMU_nb]) < 0) { |
329 |
ppc64_boot_msg(0x13, "SMU IRQ fail\n"); |
330 |
iounmap(db_buff_remap); |
331 |
return -1; |
332 |
} |
333 |
} |
334 |
#endif |
335 |
SMU_devices[SMU_nb].np = np; |
336 |
SMU_devices[0].cmd_buf = cmd_buf; |
337 |
/* XXX: 0x50 should be retrieved from MacIO properties */ |
338 |
SMU_devices[SMU_nb].db_ack = db_ack + 0x50; |
339 |
SMU_devices[SMU_nb].db_req = db_req + 0x50; |
340 |
SMU_devices[SMU_nb].db_buff = 0x8000860C; |
341 |
SMU_devices[SMU_nb].db_buff0 = db_buff; |
342 |
SMU_devices[SMU_nb].db_buff_remap = db_buff_remap; |
343 |
SMU_devices[SMU_nb].SMU_irq = SMU_irq; |
344 |
SMU_devices[SMU_nb].SMU_irq_gpio = SMU_irq_gpio; |
345 |
SMU_nb++; |
346 |
|
347 |
return 0; |
348 |
} |
349 |
|
350 |
static int smu_locate_resource (struct device_node **np, u32 **pp, |
351 |
u32 **rp, int *nr, u32 **ip, int *ni, |
352 |
struct device_node *SMU_node, |
353 |
const unsigned char *name) |
354 |
{ |
355 |
unsigned int *r; |
356 |
int l; |
357 |
|
358 |
r = (unsigned int *)get_property(SMU_node, name, &l); |
359 |
if (l == 0) |
360 |
return -1; |
361 |
*np = of_find_node_by_phandle(*r); |
362 |
if (*np == NULL) |
363 |
return -1; |
364 |
*pp = (unsigned int *)get_property(*np, name, &l); |
365 |
if (l == 0) |
366 |
return -1; |
367 |
*rp = (unsigned int *)get_property(*np, "reg", nr); |
368 |
*ip = (unsigned int *)get_property(*np, "interrupts", ni); |
369 |
|
370 |
return 0; |
371 |
} |
372 |
|
373 |
int __openfirmware smu_init (void) |
374 |
{ |
375 |
struct device_node *SMU_node, *np; |
376 |
u32 *pp, *rp, *ip; |
377 |
u32 doorbell_buf; |
378 |
int doorbell_ack, doorbell_req, SMU_irq, SMU_irq_gpio; |
379 |
int nr, ni; |
380 |
|
381 |
ppc64_boot_msg(0x13, "Starting SMU probe\n"); |
382 |
SMU_node = NULL; |
383 |
while (1) { |
384 |
SMU_node = of_find_node_by_type(SMU_node, "smu"); |
385 |
if (SMU_node == NULL) |
386 |
break; |
387 |
/* Locate doorbell ACK and REQ */ |
388 |
if (smu_locate_resource(&np, &pp, &rp, &nr, &ip, &ni, |
389 |
SMU_node, "platform-doorbell-ack") < 0) { |
390 |
ppc64_boot_msg(0x13, "SMU 'ack'\n"); |
391 |
continue; |
392 |
} |
393 |
if (nr == 0) { |
394 |
ppc64_boot_msg(0x13, "MacIO GPIO 'ack'\n"); |
395 |
continue; |
396 |
} |
397 |
doorbell_ack = *rp; |
398 |
if (smu_locate_resource(&np, &pp, &rp, &nr, &ip, &ni, |
399 |
SMU_node, "platform-doorbell-req") < 0) { |
400 |
ppc64_boot_msg(0x13, "SMU 'req'\n"); |
401 |
continue; |
402 |
} |
403 |
if (nr == 0) { |
404 |
ppc64_boot_msg(0x13, "MacIO GPIO 'req'\n"); |
405 |
continue; |
406 |
} |
407 |
doorbell_req = *rp; |
408 |
/* Locate doorbell buffer */ |
409 |
if (smu_locate_resource(&np, &pp, &rp, &nr, &ip, &ni, |
410 |
SMU_node, "platform-doorbell-buff") < 0) { |
411 |
ppc64_boot_msg(0x13, "SMU 'buff'\n"); |
412 |
continue; |
413 |
} |
414 |
if (nr < 4) { |
415 |
ppc64_boot_msg(0x13, "MacIO regs 'buff'\n"); |
416 |
continue; |
417 |
} |
418 |
doorbell_buf = rp[1] | rp[3]; |
419 |
/* Locate programmer switch */ |
420 |
if (smu_locate_resource(&np, &pp, &rp, &nr, &ip, &ni, |
421 |
SMU_node, "platform-programmer-switch") < 0) { |
422 |
ppc64_boot_msg(0x13, "SMU 'switch'\n"); |
423 |
continue; |
424 |
} |
425 |
/* Locate SMU IRQ */ |
426 |
if (smu_locate_resource(&np, &pp, &rp, &nr, &ip, &ni, |
427 |
SMU_node, "platform-smu-interrupt") < 0) { |
428 |
ppc64_boot_msg(0x13, "SMU 'interrupt'\n"); |
429 |
continue; |
430 |
} |
431 |
if (np == 0 || ni == 0) { |
432 |
ppc64_boot_msg(0x13, "MacIO GPIO 'interrupt'\n"); |
433 |
continue; |
434 |
} |
435 |
SMU_irq_gpio = *rp; |
436 |
SMU_irq = *ip; |
437 |
/* Locate wor disable/enable */ |
438 |
if (smu_locate_resource(&np, &pp, &rp, &nr, &ip, &ni, |
439 |
SMU_node, "platform-wor-disable") < 0) { |
440 |
ppc64_boot_msg(0x13, "SMU 'wor-disable'\n"); |
441 |
continue; |
442 |
} |
443 |
if (smu_locate_resource(&np, &pp, &rp, &nr, &ip, &ni, |
444 |
SMU_node, "platform-wor-enable") < 0) { |
445 |
ppc64_boot_msg(0x13, "SMU 'wor-enable'\n"); |
446 |
continue; |
447 |
} |
448 |
#if 0 |
449 |
/* Locate powertune step point */ |
450 |
if (smu_locate_resource(&np, &pp, &rp, &nr, &ip, &ni, |
451 |
SMU_node, "powertune-step-point") < 0) { |
452 |
ppc64_boot_msg(0x13, "SMU 'step-point'\n"); |
453 |
continue; |
454 |
} |
455 |
#endif |
456 |
smu_register(SMU_node, doorbell_ack, doorbell_req, doorbell_buf, |
457 |
SMU_irq, SMU_irq_gpio); |
458 |
} |
459 |
ppc64_boot_msg(0x13, "SMU probe done\n"); |
460 |
|
461 |
return SMU_nb == 0 ? -1 : 0; |
462 |
} |