aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/macintosh/smu.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/macintosh/smu.c')
-rw-r--r--drivers/macintosh/smu.c364
1 files changed, 364 insertions, 0 deletions
diff --git a/drivers/macintosh/smu.c b/drivers/macintosh/smu.c
new file mode 100644
index 000000000000..fb535737d17d
--- /dev/null
+++ b/drivers/macintosh/smu.c
@@ -0,0 +1,364 @@
1/*
2 * PowerMac G5 SMU driver
3 *
4 * Copyright 2004 J. Mayer <l_indien@magic.fr>
5 * Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
6 *
7 * Released under the term of the GNU GPL v2.
8 */
9
10/*
11 * For now, this driver includes:
12 * - RTC get & set
13 * - reboot & shutdown commands
14 * all synchronous with IRQ disabled (ugh)
15 *
16 * TODO:
17 * rework in a way the PMU driver works, that is asynchronous
18 * with a queue of commands. I'll do that as soon as I have an
19 * SMU based machine at hand. Some more cleanup is needed too,
20 * like maybe fitting it into a platform device, etc...
21 * Also check what's up with cache coherency, and if we really
22 * can't do better than flushing the cache, maybe build a table
23 * of command len/reply len like the PMU driver to only flush
24 * what is actually necessary.
25 * --BenH.
26 */
27
28#include <linux/config.h>
29#include <linux/types.h>
30#include <linux/kernel.h>
31#include <linux/device.h>
32#include <linux/dmapool.h>
33#include <linux/bootmem.h>
34#include <linux/vmalloc.h>
35#include <linux/highmem.h>
36#include <linux/jiffies.h>
37#include <linux/interrupt.h>
38#include <linux/rtc.h>
39
40#include <asm/byteorder.h>
41#include <asm/io.h>
42#include <asm/prom.h>
43#include <asm/machdep.h>
44#include <asm/pmac_feature.h>
45#include <asm/smu.h>
46#include <asm/sections.h>
47#include <asm/abs_addr.h>
48
49#define DEBUG_SMU 1
50
51#ifdef DEBUG_SMU
52#define DPRINTK(fmt, args...) do { printk(KERN_DEBUG fmt , ##args); } while (0)
53#else
54#define DPRINTK(fmt, args...) do { } while (0)
55#endif
56
57/*
58 * This is the command buffer passed to the SMU hardware
59 */
60struct smu_cmd_buf {
61 u8 cmd;
62 u8 length;
63 u8 data[0x0FFE];
64};
65
66struct smu_device {
67 spinlock_t lock;
68 struct device_node *of_node;
69 int db_ack; /* doorbell ack GPIO */
70 int db_req; /* doorbell req GPIO */
71 u32 __iomem *db_buf; /* doorbell buffer */
72 struct smu_cmd_buf *cmd_buf; /* command buffer virtual */
73 u32 cmd_buf_abs; /* command buffer absolute */
74};
75
76/*
77 * I don't think there will ever be more than one SMU, so
78 * for now, just hard code that
79 */
80static struct smu_device *smu;
81
82/*
83 * SMU low level communication stuff
84 */
85static inline int smu_cmd_stat(struct smu_cmd_buf *cmd_buf, u8 cmd_ack)
86{
87 rmb();
88 return cmd_buf->cmd == cmd_ack && cmd_buf->length != 0;
89}
90
91static inline u8 smu_save_ack_cmd(struct smu_cmd_buf *cmd_buf)
92{
93 return (~cmd_buf->cmd) & 0xff;
94}
95
96static void smu_send_cmd(struct smu_device *dev)
97{
98 /* SMU command buf is currently cacheable, we need a physical
99 * address. This isn't exactly a DMA mapping here, I suspect
100 * the SMU is actually communicating with us via i2c to the
101 * northbridge or the CPU to access RAM.
102 */
103 writel(dev->cmd_buf_abs, dev->db_buf);
104
105 /* Ring the SMU doorbell */
106 pmac_do_feature_call(PMAC_FTR_WRITE_GPIO, NULL, dev->db_req, 4);
107 pmac_do_feature_call(PMAC_FTR_READ_GPIO, NULL, dev->db_req, 4);
108}
109
110static int smu_cmd_done(struct smu_device *dev)
111{
112 unsigned long wait = 0;
113 int gpio;
114
115 /* Check the SMU doorbell */
116 do {
117 gpio = pmac_do_feature_call(PMAC_FTR_READ_GPIO,
118 NULL, dev->db_ack);
119 if ((gpio & 7) == 7)
120 return 0;
121 udelay(100);
122 } while(++wait < 10000);
123
124 printk(KERN_ERR "SMU timeout !\n");
125 return -ENXIO;
126}
127
128static int smu_do_cmd(struct smu_device *dev)
129{
130 int rc;
131 u8 cmd_ack;
132
133 DPRINTK("SMU do_cmd %02x len=%d %02x\n",
134 dev->cmd_buf->cmd, dev->cmd_buf->length,
135 dev->cmd_buf->data[0]);
136
137 cmd_ack = smu_save_ack_cmd(dev->cmd_buf);
138
139 /* Clear cmd_buf cache lines */
140 flush_inval_dcache_range((unsigned long)dev->cmd_buf,
141 ((unsigned long)dev->cmd_buf) +
142 sizeof(struct smu_cmd_buf));
143 smu_send_cmd(dev);
144 rc = smu_cmd_done(dev);
145 if (rc == 0)
146 rc = smu_cmd_stat(dev->cmd_buf, cmd_ack) ? 0 : -1;
147
148 DPRINTK("SMU do_cmd %02x len=%d %02x => %d (%02x)\n",
149 dev->cmd_buf->cmd, dev->cmd_buf->length,
150 dev->cmd_buf->data[0], rc, cmd_ack);
151
152 return rc;
153}
154
155/* RTC low level commands */
156static inline int bcd2hex (int n)
157{
158 return (((n & 0xf0) >> 4) * 10) + (n & 0xf);
159}
160
161static inline int hex2bcd (int n)
162{
163 return ((n / 10) << 4) + (n % 10);
164}
165
166#if 0
167static inline void smu_fill_set_pwrup_timer_cmd(struct smu_cmd_buf *cmd_buf)
168{
169 cmd_buf->cmd = 0x8e;
170 cmd_buf->length = 8;
171 cmd_buf->data[0] = 0x00;
172 memset(cmd_buf->data + 1, 0, 7);
173}
174
175static inline void smu_fill_get_pwrup_timer_cmd(struct smu_cmd_buf *cmd_buf)
176{
177 cmd_buf->cmd = 0x8e;
178 cmd_buf->length = 1;
179 cmd_buf->data[0] = 0x01;
180}
181
182static inline void smu_fill_dis_pwrup_timer_cmd(struct smu_cmd_buf *cmd_buf)
183{
184 cmd_buf->cmd = 0x8e;
185 cmd_buf->length = 1;
186 cmd_buf->data[0] = 0x02;
187}
188#endif
189
190static inline void smu_fill_set_rtc_cmd(struct smu_cmd_buf *cmd_buf,
191 struct rtc_time *time)
192{
193 cmd_buf->cmd = 0x8e;
194 cmd_buf->length = 8;
195 cmd_buf->data[0] = 0x80;
196 cmd_buf->data[1] = hex2bcd(time->tm_sec);
197 cmd_buf->data[2] = hex2bcd(time->tm_min);
198 cmd_buf->data[3] = hex2bcd(time->tm_hour);
199 cmd_buf->data[4] = time->tm_wday;
200 cmd_buf->data[5] = hex2bcd(time->tm_mday);
201 cmd_buf->data[6] = hex2bcd(time->tm_mon) + 1;
202 cmd_buf->data[7] = hex2bcd(time->tm_year - 100);
203}
204
205static inline void smu_fill_get_rtc_cmd(struct smu_cmd_buf *cmd_buf)
206{
207 cmd_buf->cmd = 0x8e;
208 cmd_buf->length = 1;
209 cmd_buf->data[0] = 0x81;
210}
211
212static void smu_parse_get_rtc_reply(struct smu_cmd_buf *cmd_buf,
213 struct rtc_time *time)
214{
215 time->tm_sec = bcd2hex(cmd_buf->data[0]);
216 time->tm_min = bcd2hex(cmd_buf->data[1]);
217 time->tm_hour = bcd2hex(cmd_buf->data[2]);
218 time->tm_wday = bcd2hex(cmd_buf->data[3]);
219 time->tm_mday = bcd2hex(cmd_buf->data[4]);
220 time->tm_mon = bcd2hex(cmd_buf->data[5]) - 1;
221 time->tm_year = bcd2hex(cmd_buf->data[6]) + 100;
222}
223
224int smu_get_rtc_time(struct rtc_time *time)
225{
226 unsigned long flags;
227 int rc;
228
229 if (smu == NULL)
230 return -ENODEV;
231
232 memset(time, 0, sizeof(struct rtc_time));
233 spin_lock_irqsave(&smu->lock, flags);
234 smu_fill_get_rtc_cmd(smu->cmd_buf);
235 rc = smu_do_cmd(smu);
236 if (rc == 0)
237 smu_parse_get_rtc_reply(smu->cmd_buf, time);
238 spin_unlock_irqrestore(&smu->lock, flags);
239
240 return rc;
241}
242
243int smu_set_rtc_time(struct rtc_time *time)
244{
245 unsigned long flags;
246 int rc;
247
248 if (smu == NULL)
249 return -ENODEV;
250
251 spin_lock_irqsave(&smu->lock, flags);
252 smu_fill_set_rtc_cmd(smu->cmd_buf, time);
253 rc = smu_do_cmd(smu);
254 spin_unlock_irqrestore(&smu->lock, flags);
255
256 return rc;
257}
258
259void smu_shutdown(void)
260{
261 const unsigned char *command = "SHUTDOWN";
262 unsigned long flags;
263
264 if (smu == NULL)
265 return;
266
267 spin_lock_irqsave(&smu->lock, flags);
268 smu->cmd_buf->cmd = 0xaa;
269 smu->cmd_buf->length = strlen(command);
270 strcpy(smu->cmd_buf->data, command);
271 smu_do_cmd(smu);
272 for (;;)
273 ;
274 spin_unlock_irqrestore(&smu->lock, flags);
275}
276
277void smu_restart(void)
278{
279 const unsigned char *command = "RESTART";
280 unsigned long flags;
281
282 if (smu == NULL)
283 return;
284
285 spin_lock_irqsave(&smu->lock, flags);
286 smu->cmd_buf->cmd = 0xaa;
287 smu->cmd_buf->length = strlen(command);
288 strcpy(smu->cmd_buf->data, command);
289 smu_do_cmd(smu);
290 for (;;)
291 ;
292 spin_unlock_irqrestore(&smu->lock, flags);
293}
294
295int smu_present(void)
296{
297 return smu != NULL;
298}
299
300
301int smu_init (void)
302{
303 struct device_node *np;
304 u32 *data;
305
306 np = of_find_node_by_type(NULL, "smu");
307 if (np == NULL)
308 return -ENODEV;
309
310 if (smu_cmdbuf_abs == 0) {
311 printk(KERN_ERR "SMU: Command buffer not allocated !\n");
312 return -EINVAL;
313 }
314
315 smu = alloc_bootmem(sizeof(struct smu_device));
316 if (smu == NULL)
317 return -ENOMEM;
318 memset(smu, 0, sizeof(*smu));
319
320 spin_lock_init(&smu->lock);
321 smu->of_node = np;
322 /* smu_cmdbuf_abs is in the low 2G of RAM, can be converted to a
323 * 32 bits value safely
324 */
325 smu->cmd_buf_abs = (u32)smu_cmdbuf_abs;
326 smu->cmd_buf = (struct smu_cmd_buf *)abs_to_virt(smu_cmdbuf_abs);
327
328 np = of_find_node_by_name(NULL, "smu-doorbell");
329 if (np == NULL) {
330 printk(KERN_ERR "SMU: Can't find doorbell GPIO !\n");
331 goto fail;
332 }
333 data = (u32 *)get_property(np, "reg", NULL);
334 of_node_put(np);
335 if (data == NULL) {
336 printk(KERN_ERR "SMU: Can't find doorbell GPIO address !\n");
337 goto fail;
338 }
339
340 /* Current setup has one doorbell GPIO that does both doorbell
341 * and ack. GPIOs are at 0x50, best would be to find that out
342 * in the device-tree though.
343 */
344 smu->db_req = 0x50 + *data;
345 smu->db_ack = 0x50 + *data;
346
347 /* Doorbell buffer is currently hard-coded, I didn't find a proper
348 * device-tree entry giving the address. Best would probably to use
349 * an offset for K2 base though, but let's do it that way for now.
350 */
351 smu->db_buf = ioremap(0x8000860c, 0x1000);
352 if (smu->db_buf == NULL) {
353 printk(KERN_ERR "SMU: Can't map doorbell buffer pointer !\n");
354 goto fail;
355 }
356
357 sys_ctrler = SYS_CTRLER_SMU;
358 return 0;
359
360 fail:
361 smu = NULL;
362 return -ENXIO;
363
364}