123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364 |
- /*
- * PowerMac G5 SMU driver
- *
- * Copyright 2004 J. Mayer <l_indien@magic.fr>
- * Copyright 2005 Benjamin Herrenschmidt, IBM Corp.
- *
- * Released under the term of the GNU GPL v2.
- */
- /*
- * For now, this driver includes:
- * - RTC get & set
- * - reboot & shutdown commands
- * all synchronous with IRQ disabled (ugh)
- *
- * TODO:
- * rework in a way the PMU driver works, that is asynchronous
- * with a queue of commands. I'll do that as soon as I have an
- * SMU based machine at hand. Some more cleanup is needed too,
- * like maybe fitting it into a platform device, etc...
- * Also check what's up with cache coherency, and if we really
- * can't do better than flushing the cache, maybe build a table
- * of command len/reply len like the PMU driver to only flush
- * what is actually necessary.
- * --BenH.
- */
- #include <linux/config.h>
- #include <linux/types.h>
- #include <linux/kernel.h>
- #include <linux/device.h>
- #include <linux/dmapool.h>
- #include <linux/bootmem.h>
- #include <linux/vmalloc.h>
- #include <linux/highmem.h>
- #include <linux/jiffies.h>
- #include <linux/interrupt.h>
- #include <linux/rtc.h>
- #include <asm/byteorder.h>
- #include <asm/io.h>
- #include <asm/prom.h>
- #include <asm/machdep.h>
- #include <asm/pmac_feature.h>
- #include <asm/smu.h>
- #include <asm/sections.h>
- #include <asm/abs_addr.h>
- #define DEBUG_SMU 1
- #ifdef DEBUG_SMU
- #define DPRINTK(fmt, args...) do { printk(KERN_DEBUG fmt , ##args); } while (0)
- #else
- #define DPRINTK(fmt, args...) do { } while (0)
- #endif
- /*
- * This is the command buffer passed to the SMU hardware
- */
- struct smu_cmd_buf {
- u8 cmd;
- u8 length;
- u8 data[0x0FFE];
- };
- struct smu_device {
- spinlock_t lock;
- struct device_node *of_node;
- int db_ack; /* doorbell ack GPIO */
- int db_req; /* doorbell req GPIO */
- u32 __iomem *db_buf; /* doorbell buffer */
- struct smu_cmd_buf *cmd_buf; /* command buffer virtual */
- u32 cmd_buf_abs; /* command buffer absolute */
- };
- /*
- * I don't think there will ever be more than one SMU, so
- * for now, just hard code that
- */
- static struct smu_device *smu;
- /*
- * SMU low level communication stuff
- */
- static inline int smu_cmd_stat(struct smu_cmd_buf *cmd_buf, u8 cmd_ack)
- {
- rmb();
- return cmd_buf->cmd == cmd_ack && cmd_buf->length != 0;
- }
- static inline u8 smu_save_ack_cmd(struct smu_cmd_buf *cmd_buf)
- {
- return (~cmd_buf->cmd) & 0xff;
- }
- static void smu_send_cmd(struct smu_device *dev)
- {
- /* SMU command buf is currently cacheable, we need a physical
- * address. This isn't exactly a DMA mapping here, I suspect
- * the SMU is actually communicating with us via i2c to the
- * northbridge or the CPU to access RAM.
- */
- writel(dev->cmd_buf_abs, dev->db_buf);
- /* Ring the SMU doorbell */
- pmac_do_feature_call(PMAC_FTR_WRITE_GPIO, NULL, dev->db_req, 4);
- pmac_do_feature_call(PMAC_FTR_READ_GPIO, NULL, dev->db_req, 4);
- }
- static int smu_cmd_done(struct smu_device *dev)
- {
- unsigned long wait = 0;
- int gpio;
- /* Check the SMU doorbell */
- do {
- gpio = pmac_do_feature_call(PMAC_FTR_READ_GPIO,
- NULL, dev->db_ack);
- if ((gpio & 7) == 7)
- return 0;
- udelay(100);
- } while(++wait < 10000);
- printk(KERN_ERR "SMU timeout !\n");
- return -ENXIO;
- }
- static int smu_do_cmd(struct smu_device *dev)
- {
- int rc;
- u8 cmd_ack;
- DPRINTK("SMU do_cmd %02x len=%d %02x\n",
- dev->cmd_buf->cmd, dev->cmd_buf->length,
- dev->cmd_buf->data[0]);
- cmd_ack = smu_save_ack_cmd(dev->cmd_buf);
- /* Clear cmd_buf cache lines */
- flush_inval_dcache_range((unsigned long)dev->cmd_buf,
- ((unsigned long)dev->cmd_buf) +
- sizeof(struct smu_cmd_buf));
- smu_send_cmd(dev);
- rc = smu_cmd_done(dev);
- if (rc == 0)
- rc = smu_cmd_stat(dev->cmd_buf, cmd_ack) ? 0 : -1;
- DPRINTK("SMU do_cmd %02x len=%d %02x => %d (%02x)\n",
- dev->cmd_buf->cmd, dev->cmd_buf->length,
- dev->cmd_buf->data[0], rc, cmd_ack);
- return rc;
- }
- /* RTC low level commands */
- static inline int bcd2hex (int n)
- {
- return (((n & 0xf0) >> 4) * 10) + (n & 0xf);
- }
- static inline int hex2bcd (int n)
- {
- return ((n / 10) << 4) + (n % 10);
- }
- #if 0
- static inline void smu_fill_set_pwrup_timer_cmd(struct smu_cmd_buf *cmd_buf)
- {
- cmd_buf->cmd = 0x8e;
- cmd_buf->length = 8;
- cmd_buf->data[0] = 0x00;
- memset(cmd_buf->data + 1, 0, 7);
- }
- static inline void smu_fill_get_pwrup_timer_cmd(struct smu_cmd_buf *cmd_buf)
- {
- cmd_buf->cmd = 0x8e;
- cmd_buf->length = 1;
- cmd_buf->data[0] = 0x01;
- }
- static inline void smu_fill_dis_pwrup_timer_cmd(struct smu_cmd_buf *cmd_buf)
- {
- cmd_buf->cmd = 0x8e;
- cmd_buf->length = 1;
- cmd_buf->data[0] = 0x02;
- }
- #endif
- static inline void smu_fill_set_rtc_cmd(struct smu_cmd_buf *cmd_buf,
- struct rtc_time *time)
- {
- cmd_buf->cmd = 0x8e;
- cmd_buf->length = 8;
- cmd_buf->data[0] = 0x80;
- cmd_buf->data[1] = hex2bcd(time->tm_sec);
- cmd_buf->data[2] = hex2bcd(time->tm_min);
- cmd_buf->data[3] = hex2bcd(time->tm_hour);
- cmd_buf->data[4] = time->tm_wday;
- cmd_buf->data[5] = hex2bcd(time->tm_mday);
- cmd_buf->data[6] = hex2bcd(time->tm_mon) + 1;
- cmd_buf->data[7] = hex2bcd(time->tm_year - 100);
- }
- static inline void smu_fill_get_rtc_cmd(struct smu_cmd_buf *cmd_buf)
- {
- cmd_buf->cmd = 0x8e;
- cmd_buf->length = 1;
- cmd_buf->data[0] = 0x81;
- }
- static void smu_parse_get_rtc_reply(struct smu_cmd_buf *cmd_buf,
- struct rtc_time *time)
- {
- time->tm_sec = bcd2hex(cmd_buf->data[0]);
- time->tm_min = bcd2hex(cmd_buf->data[1]);
- time->tm_hour = bcd2hex(cmd_buf->data[2]);
- time->tm_wday = bcd2hex(cmd_buf->data[3]);
- time->tm_mday = bcd2hex(cmd_buf->data[4]);
- time->tm_mon = bcd2hex(cmd_buf->data[5]) - 1;
- time->tm_year = bcd2hex(cmd_buf->data[6]) + 100;
- }
- int smu_get_rtc_time(struct rtc_time *time)
- {
- unsigned long flags;
- int rc;
- if (smu == NULL)
- return -ENODEV;
- memset(time, 0, sizeof(struct rtc_time));
- spin_lock_irqsave(&smu->lock, flags);
- smu_fill_get_rtc_cmd(smu->cmd_buf);
- rc = smu_do_cmd(smu);
- if (rc == 0)
- smu_parse_get_rtc_reply(smu->cmd_buf, time);
- spin_unlock_irqrestore(&smu->lock, flags);
- return rc;
- }
- int smu_set_rtc_time(struct rtc_time *time)
- {
- unsigned long flags;
- int rc;
- if (smu == NULL)
- return -ENODEV;
- spin_lock_irqsave(&smu->lock, flags);
- smu_fill_set_rtc_cmd(smu->cmd_buf, time);
- rc = smu_do_cmd(smu);
- spin_unlock_irqrestore(&smu->lock, flags);
- return rc;
- }
- void smu_shutdown(void)
- {
- const unsigned char *command = "SHUTDOWN";
- unsigned long flags;
- if (smu == NULL)
- return;
- spin_lock_irqsave(&smu->lock, flags);
- smu->cmd_buf->cmd = 0xaa;
- smu->cmd_buf->length = strlen(command);
- strcpy(smu->cmd_buf->data, command);
- smu_do_cmd(smu);
- for (;;)
- ;
- spin_unlock_irqrestore(&smu->lock, flags);
- }
- void smu_restart(void)
- {
- const unsigned char *command = "RESTART";
- unsigned long flags;
- if (smu == NULL)
- return;
- spin_lock_irqsave(&smu->lock, flags);
- smu->cmd_buf->cmd = 0xaa;
- smu->cmd_buf->length = strlen(command);
- strcpy(smu->cmd_buf->data, command);
- smu_do_cmd(smu);
- for (;;)
- ;
- spin_unlock_irqrestore(&smu->lock, flags);
- }
- int smu_present(void)
- {
- return smu != NULL;
- }
- int smu_init (void)
- {
- struct device_node *np;
- u32 *data;
- np = of_find_node_by_type(NULL, "smu");
- if (np == NULL)
- return -ENODEV;
- if (smu_cmdbuf_abs == 0) {
- printk(KERN_ERR "SMU: Command buffer not allocated !\n");
- return -EINVAL;
- }
- smu = alloc_bootmem(sizeof(struct smu_device));
- if (smu == NULL)
- return -ENOMEM;
- memset(smu, 0, sizeof(*smu));
- spin_lock_init(&smu->lock);
- smu->of_node = np;
- /* smu_cmdbuf_abs is in the low 2G of RAM, can be converted to a
- * 32 bits value safely
- */
- smu->cmd_buf_abs = (u32)smu_cmdbuf_abs;
- smu->cmd_buf = (struct smu_cmd_buf *)abs_to_virt(smu_cmdbuf_abs);
- np = of_find_node_by_name(NULL, "smu-doorbell");
- if (np == NULL) {
- printk(KERN_ERR "SMU: Can't find doorbell GPIO !\n");
- goto fail;
- }
- data = (u32 *)get_property(np, "reg", NULL);
- of_node_put(np);
- if (data == NULL) {
- printk(KERN_ERR "SMU: Can't find doorbell GPIO address !\n");
- goto fail;
- }
- /* Current setup has one doorbell GPIO that does both doorbell
- * and ack. GPIOs are at 0x50, best would be to find that out
- * in the device-tree though.
- */
- smu->db_req = 0x50 + *data;
- smu->db_ack = 0x50 + *data;
- /* Doorbell buffer is currently hard-coded, I didn't find a proper
- * device-tree entry giving the address. Best would probably to use
- * an offset for K2 base though, but let's do it that way for now.
- */
- smu->db_buf = ioremap(0x8000860c, 0x1000);
- if (smu->db_buf == NULL) {
- printk(KERN_ERR "SMU: Can't map doorbell buffer pointer !\n");
- goto fail;
- }
- sys_ctrler = SYS_CTRLER_SMU;
- return 0;
- fail:
- smu = NULL;
- return -ENXIO;
- }
|