linux/arch/ppc64/kernel/pmac_time.c
Benjamin Herrenschmidt 0365ba7fb1 [PATCH] ppc64: SMU driver update & i2c support
The SMU is the "system controller" chip used by Apple recent G5 machines
including the iMac G5.  It drives things like fans, i2c busses, real time
clock, etc...

The current kernel contains a very crude driver that doesn't do much more
than reading the real time clock synchronously.  This is a completely
rewritten driver that provides interrupt based command queuing, a userland
interface, and an i2c/smbus driver for accessing the devices hanging off
the SMU i2c busses like temperature sensors.  This driver is a basic block
for upcoming work on thermal control for those machines, among others.

Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Cc: Jean Delvare <khali@linux-fr.org>
Cc: Greg KH <greg@kroah.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2005-09-22 22:17:35 -07:00

196 lines
4.9 KiB
C

/*
* Support for periodic interrupts (100 per second) and for getting
* the current time from the RTC on Power Macintoshes.
*
* We use the decrementer register for our periodic interrupts.
*
* Paul Mackerras August 1996.
* Copyright (C) 1996 Paul Mackerras.
* Copyright (C) 2003-2005 Benjamin Herrenschmidt.
*
*/
#include <linux/config.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/param.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/init.h>
#include <linux/time.h>
#include <linux/adb.h>
#include <linux/pmu.h>
#include <linux/interrupt.h>
#include <asm/sections.h>
#include <asm/prom.h>
#include <asm/system.h>
#include <asm/io.h>
#include <asm/pgtable.h>
#include <asm/machdep.h>
#include <asm/time.h>
#include <asm/nvram.h>
#include <asm/smu.h>
#undef DEBUG
#ifdef DEBUG
#define DBG(x...) printk(x)
#else
#define DBG(x...)
#endif
/* Apparently the RTC stores seconds since 1 Jan 1904 */
#define RTC_OFFSET 2082844800
/*
* Calibrate the decrementer frequency with the VIA timer 1.
*/
#define VIA_TIMER_FREQ_6 4700000 /* time 1 frequency * 6 */
extern struct timezone sys_tz;
extern void to_tm(int tim, struct rtc_time * tm);
void __pmac pmac_get_rtc_time(struct rtc_time *tm)
{
switch(sys_ctrler) {
#ifdef CONFIG_ADB_PMU
case SYS_CTRLER_PMU: {
/* TODO: Move that to a function in the PMU driver */
struct adb_request req;
unsigned int now;
if (pmu_request(&req, NULL, 1, PMU_READ_RTC) < 0)
return;
pmu_wait_complete(&req);
if (req.reply_len != 4)
printk(KERN_ERR "pmac_get_rtc_time: PMU returned a %d"
" bytes reply\n", req.reply_len);
now = (req.reply[0] << 24) + (req.reply[1] << 16)
+ (req.reply[2] << 8) + req.reply[3];
DBG("get: %u -> %u\n", (int)now, (int)(now - RTC_OFFSET));
now -= RTC_OFFSET;
to_tm(now, tm);
tm->tm_year -= 1900;
tm->tm_mon -= 1;
DBG("-> tm_mday: %d, tm_mon: %d, tm_year: %d, %d:%02d:%02d\n",
tm->tm_mday, tm->tm_mon, tm->tm_year,
tm->tm_hour, tm->tm_min, tm->tm_sec);
break;
}
#endif /* CONFIG_ADB_PMU */
#ifdef CONFIG_PMAC_SMU
case SYS_CTRLER_SMU:
smu_get_rtc_time(tm, 1);
break;
#endif /* CONFIG_PMAC_SMU */
default:
;
}
}
int __pmac pmac_set_rtc_time(struct rtc_time *tm)
{
switch(sys_ctrler) {
#ifdef CONFIG_ADB_PMU
case SYS_CTRLER_PMU: {
/* TODO: Move that to a function in the PMU driver */
struct adb_request req;
unsigned int nowtime;
DBG("set: tm_mday: %d, tm_mon: %d, tm_year: %d,"
" %d:%02d:%02d\n",
tm->tm_mday, tm->tm_mon, tm->tm_year,
tm->tm_hour, tm->tm_min, tm->tm_sec);
nowtime = mktime(tm->tm_year + 1900, tm->tm_mon + 1,
tm->tm_mday, tm->tm_hour, tm->tm_min,
tm->tm_sec);
DBG("-> %u -> %u\n", (int)nowtime,
(int)(nowtime + RTC_OFFSET));
nowtime += RTC_OFFSET;
if (pmu_request(&req, NULL, 5, PMU_SET_RTC,
nowtime >> 24, nowtime >> 16,
nowtime >> 8, nowtime) < 0)
return -ENXIO;
pmu_wait_complete(&req);
if (req.reply_len != 0)
printk(KERN_ERR "pmac_set_rtc_time: PMU returned a %d"
" bytes reply\n", req.reply_len);
return 0;
}
#endif /* CONFIG_ADB_PMU */
#ifdef CONFIG_PMAC_SMU
case SYS_CTRLER_SMU:
return smu_set_rtc_time(tm, 1);
#endif /* CONFIG_PMAC_SMU */
default:
return -ENODEV;
}
}
void __init pmac_get_boot_time(struct rtc_time *tm)
{
pmac_get_rtc_time(tm);
#ifdef disabled__CONFIG_NVRAM
s32 delta = 0;
int dst;
delta = ((s32)pmac_xpram_read(PMAC_XPRAM_MACHINE_LOC + 0x9)) << 16;
delta |= ((s32)pmac_xpram_read(PMAC_XPRAM_MACHINE_LOC + 0xa)) << 8;
delta |= pmac_xpram_read(PMAC_XPRAM_MACHINE_LOC + 0xb);
if (delta & 0x00800000UL)
delta |= 0xFF000000UL;
dst = ((pmac_xpram_read(PMAC_XPRAM_MACHINE_LOC + 0x8) & 0x80) != 0);
printk("GMT Delta read from XPRAM: %d minutes, DST: %s\n", delta/60,
dst ? "on" : "off");
#endif
}
/*
* Query the OF and get the decr frequency.
* FIXME: merge this with generic_calibrate_decr
*/
void __init pmac_calibrate_decr(void)
{
struct device_node *cpu;
unsigned int freq, *fp;
struct div_result divres;
/*
* The cpu node should have a timebase-frequency property
* to tell us the rate at which the decrementer counts.
*/
cpu = find_type_devices("cpu");
if (cpu == 0)
panic("can't find cpu node in time_init");
fp = (unsigned int *) get_property(cpu, "timebase-frequency", NULL);
if (fp == 0)
panic("can't get cpu timebase frequency");
freq = *fp;
printk("time_init: decrementer frequency = %u.%.6u MHz\n",
freq/1000000, freq%1000000);
tb_ticks_per_jiffy = freq / HZ;
tb_ticks_per_sec = tb_ticks_per_jiffy * HZ;
tb_ticks_per_usec = freq / 1000000;
tb_to_us = mulhwu_scale_factor(freq, 1000000);
div128_by_32( 1024*1024, 0, tb_ticks_per_sec, &divres );
tb_to_xs = divres.result_low;
ppc_tb_freq = freq;
fp = (unsigned int *)get_property(cpu, "clock-frequency", NULL);
if (fp == 0)
panic("can't get cpu processor frequency");
ppc_proc_freq = *fp;
setup_default_decr();
}