aboutsummaryrefslogblamecommitdiffstats
path: root/drivers/macintosh/smu.c
blob: fb535737d17d114244481fea4ba7421a768ce1ed (plain) (tree)











































































































































































































































































































































































                                                                               
/*
 * 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;

}