/*
* 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.
*/
/*
* TODO:
* - maybe add timeout to commands ?
* - blocking version of time functions
* - polling version of i2c commands (including timer that works with
* interrutps off)
* - maybe avoid some data copies with i2c by directly using the smu cmd
* buffer and a lower level internal interface
* - understand SMU -> CPU events and implement reception of them via
* the userland interface
*/
#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 <linux/completion.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <linux/sysdev.h>
#include <linux/poll.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>
#include <asm/uaccess.h>
#include <asm/of_device.h>
#define VERSION "0.6"
#define AUTHOR "(c) 2005 Benjamin Herrenschmidt, IBM Corp."
#undef DEBUG_SMU
#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
*/
#define SMU_MAX_DATA 254
struct smu_cmd_buf {
u8 cmd;
u8 length;
u8 data[SMU_MAX_DATA];
};
struct smu_device {
spinlock_t lock;
struct device_node *of_node;
struct of_device *of_dev;
int doorbell; /* doorbell gpio */
u32 __iomem *db_buf; /* doorbell buffer */
int db_irq;
int msg;
int msg_irq;
struct smu_cmd_buf *cmd_buf; /* command buffer virtual */
u32 cmd_buf_abs; /* command buffer absolute */
struct list_head cmd_list;
struct smu_cmd *cmd_cur; /* pending command */
struct list_head cmd_i2c_list;
struct smu_i2c_cmd *cmd_i2c_cur; /* pending i2c command */
struct timer_list i2c_timer;
};
/*
* 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 driver low level stuff
*/
static void smu_start_cmd(void)
{
unsigned long faddr, fend;
struct smu_cmd *cmd;
if (list_empty(&smu->cmd_list))
return;
/* Fetch first command in queue */
cmd = list_entry(smu->cmd_list.next, struct smu_cmd, link);
smu->cmd_cur = cmd;
list_del(&cmd->link);
DPRINTK("SMU: starting cmd %x, %d bytes data\n", cmd->cmd,
cmd->data_len);
DPRINTK("SMU: data buffer: %02x %02x %02x %02x ...\n",
((u8 *)cmd->data_buf)[0], ((u8 *)cmd->data_buf)[1],
((u8 *)cmd->data_buf)[2], ((u8 *)cmd->data_buf)[3]);
/* Fill the SMU command buffer */
smu->cmd_buf->cmd = cmd->cmd;
smu->cmd_buf->length = cmd->data_len;
memcpy(smu->cmd_buf->data, cmd->data_buf, cmd->data_len);
/* Flush command and data to RAM */
faddr = (unsigned long)smu->cmd_buf;
fend = faddr + smu->cmd_buf->length + 2;
flush_inval_dcache_range(faddr, fend);
/* 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(smu->cmd_buf_abs, smu->db_buf);
/* Ring the SMU doorbell */
pmac_do_feature_call(PMAC_FTR_WRITE_GPIO, NULL, smu->doorbell, 4);
}
static irqreturn_t smu_db_intr(int irq, void *arg, struct pt_regs *regs)
{
unsigned long flags;
struct smu_cmd *cmd;
void (*done)(struct smu_cmd *cmd, void *misc) = NULL;
void *misc = NULL;
u8 gpio;
int rc = 0;
/* SMU completed the command, well, w