2006-11-16 06:03:33 +03:00
/*
* RackMac vu - meter driver
*
* ( c ) Copyright 2006 Benjamin Herrenschmidt , IBM Corp .
* < benh @ kernel . crashing . org >
*
* Released under the term of the GNU GPL v2 .
*
* Support the CPU - meter LEDs of the Xserve G5
*
* TODO : Implement PWM to do variable intensity and provide userland
* interface for fun . Also , the CPU - meter could be made nicer by being
* a bit less " immediate " but giving instead a more average load over
* time . Patches welcome : - )
*
*/
# undef DEBUG
# include <linux/types.h>
# include <linux/kernel.h>
# include <linux/device.h>
# include <linux/interrupt.h>
# include <linux/module.h>
# include <linux/pci.h>
# include <linux/dma-mapping.h>
# include <linux/kernel_stat.h>
# include <asm/io.h>
# include <asm/prom.h>
# include <asm/machdep.h>
# include <asm/pmac_feature.h>
# include <asm/dbdma.h>
# include <asm/dbdma.h>
# include <asm/macio.h>
# include <asm/keylargo.h>
/* Number of samples in a sample buffer */
# define SAMPLE_COUNT 256
/* CPU meter sampling rate in ms */
# define CPU_SAMPLING_RATE 250
struct rackmeter_dma {
struct dbdma_cmd cmd [ 4 ] ____cacheline_aligned ;
u32 mark ____cacheline_aligned ;
u32 buf1 [ SAMPLE_COUNT ] ____cacheline_aligned ;
u32 buf2 [ SAMPLE_COUNT ] ____cacheline_aligned ;
} ____cacheline_aligned ;
struct rackmeter_cpu {
2006-12-05 22:36:26 +03:00
struct delayed_work sniffer ;
struct rackmeter * rm ;
2006-11-16 06:03:33 +03:00
cputime64_t prev_wall ;
cputime64_t prev_idle ;
int zero ;
} ____cacheline_aligned ;
struct rackmeter {
struct macio_dev * mdev ;
unsigned int irq ;
struct device_node * i2s ;
u8 * ubuf ;
struct dbdma_regs __iomem * dma_regs ;
void __iomem * i2s_regs ;
dma_addr_t dma_buf_p ;
struct rackmeter_dma * dma_buf_v ;
int stale_irq ;
struct rackmeter_cpu cpu [ 2 ] ;
int paused ;
struct mutex sem ;
} ;
/* To be set as a tunable */
static int rackmeter_ignore_nice ;
/* This GPIO is whacked by the OS X driver when initializing */
# define RACKMETER_MAGIC_GPIO 0x78
/* This is copied from cpufreq_ondemand, maybe we should put it in
* a common header somewhere
*/
static inline cputime64_t get_cpu_idle_time ( unsigned int cpu )
{
cputime64_t retval ;
retval = cputime64_add ( kstat_cpu ( cpu ) . cpustat . idle ,
kstat_cpu ( cpu ) . cpustat . iowait ) ;
if ( rackmeter_ignore_nice )
retval = cputime64_add ( retval , kstat_cpu ( cpu ) . cpustat . nice ) ;
return retval ;
}
static void rackmeter_setup_i2s ( struct rackmeter * rm )
{
struct macio_chip * macio = rm - > mdev - > bus - > chip ;
/* First whack magic GPIO */
pmac_call_feature ( PMAC_FTR_WRITE_GPIO , NULL , RACKMETER_MAGIC_GPIO , 5 ) ;
/* Call feature code to enable the sound channel and the proper
* clock sources
*/
pmac_call_feature ( PMAC_FTR_SOUND_CHIP_ENABLE , rm - > i2s , 0 , 1 ) ;
/* Power i2s and stop i2s clock. We whack MacIO FCRs directly for now.
* This is a bit racy , thus we should add new platform functions to
* handle that . snd - aoa needs that too
*/
MACIO_BIS ( KEYLARGO_FCR1 , KL1_I2S0_ENABLE ) ;
MACIO_BIC ( KEYLARGO_FCR1 , KL1_I2S0_CLK_ENABLE_BIT ) ;
( void ) MACIO_IN32 ( KEYLARGO_FCR1 ) ;
udelay ( 10 ) ;
/* Then setup i2s. For now, we use the same magic value that
* the OS X driver seems to use . We might want to play around
* with the clock divisors later
*/
out_le32 ( rm - > i2s_regs + 0x10 , 0x01fa0000 ) ;
( void ) in_le32 ( rm - > i2s_regs + 0x10 ) ;
udelay ( 10 ) ;
/* Fully restart i2s*/
MACIO_BIS ( KEYLARGO_FCR1 , KL1_I2S0_CELL_ENABLE |
KL1_I2S0_CLK_ENABLE_BIT ) ;
( void ) MACIO_IN32 ( KEYLARGO_FCR1 ) ;
udelay ( 10 ) ;
}
static void rackmeter_set_default_pattern ( struct rackmeter * rm )
{
int i ;
for ( i = 0 ; i < 16 ; i + + ) {
if ( i < 8 )
rm - > ubuf [ i ] = ( i & 1 ) * 255 ;
else
rm - > ubuf [ i ] = ( ( ~ i ) & 1 ) * 255 ;
}
}
static void rackmeter_do_pause ( struct rackmeter * rm , int pause )
{
struct rackmeter_dma * rdma = rm - > dma_buf_v ;
pr_debug ( " rackmeter: %s \n " , pause ? " paused " : " started " ) ;
rm - > paused = pause ;
if ( pause ) {
DBDMA_DO_STOP ( rm - > dma_regs ) ;
return ;
}
memset ( rdma - > buf1 , 0 , SAMPLE_COUNT & sizeof ( u32 ) ) ;
memset ( rdma - > buf2 , 0 , SAMPLE_COUNT & sizeof ( u32 ) ) ;
rm - > dma_buf_v - > mark = 0 ;
mb ( ) ;
out_le32 ( & rm - > dma_regs - > cmdptr_hi , 0 ) ;
out_le32 ( & rm - > dma_regs - > cmdptr , rm - > dma_buf_p ) ;
out_le32 ( & rm - > dma_regs - > control , ( RUN < < 16 ) | RUN ) ;
}
static void rackmeter_setup_dbdma ( struct rackmeter * rm )
{
struct rackmeter_dma * db = rm - > dma_buf_v ;
struct dbdma_cmd * cmd = db - > cmd ;
/* Make sure dbdma is reset */
DBDMA_DO_RESET ( rm - > dma_regs ) ;
2007-02-09 19:39:05 +03:00
pr_debug ( " rackmeter: mark offset=0x%zx \n " ,
2006-11-16 06:03:33 +03:00
offsetof ( struct rackmeter_dma , mark ) ) ;
2007-02-09 19:39:05 +03:00
pr_debug ( " rackmeter: buf1 offset=0x%zx \n " ,
2006-11-16 06:03:33 +03:00
offsetof ( struct rackmeter_dma , buf1 ) ) ;
2007-02-09 19:39:05 +03:00
pr_debug ( " rackmeter: buf2 offset=0x%zx \n " ,
2006-11-16 06:03:33 +03:00
offsetof ( struct rackmeter_dma , buf2 ) ) ;
/* Prepare 4 dbdma commands for the 2 buffers */
memset ( cmd , 0 , 4 * sizeof ( struct dbdma_cmd ) ) ;
st_le16 ( & cmd - > req_count , 4 ) ;
st_le16 ( & cmd - > command , STORE_WORD | INTR_ALWAYS | KEY_SYSTEM ) ;
st_le32 ( & cmd - > phy_addr , rm - > dma_buf_p +
offsetof ( struct rackmeter_dma , mark ) ) ;
st_le32 ( & cmd - > cmd_dep , 0x02000000 ) ;
cmd + + ;
st_le16 ( & cmd - > req_count , SAMPLE_COUNT * 4 ) ;
st_le16 ( & cmd - > command , OUTPUT_MORE ) ;
st_le32 ( & cmd - > phy_addr , rm - > dma_buf_p +
offsetof ( struct rackmeter_dma , buf1 ) ) ;
cmd + + ;
st_le16 ( & cmd - > req_count , 4 ) ;
st_le16 ( & cmd - > command , STORE_WORD | INTR_ALWAYS | KEY_SYSTEM ) ;
st_le32 ( & cmd - > phy_addr , rm - > dma_buf_p +
offsetof ( struct rackmeter_dma , mark ) ) ;
st_le32 ( & cmd - > cmd_dep , 0x01000000 ) ;
cmd + + ;
st_le16 ( & cmd - > req_count , SAMPLE_COUNT * 4 ) ;
st_le16 ( & cmd - > command , OUTPUT_MORE | BR_ALWAYS ) ;
st_le32 ( & cmd - > phy_addr , rm - > dma_buf_p +
offsetof ( struct rackmeter_dma , buf2 ) ) ;
st_le32 ( & cmd - > cmd_dep , rm - > dma_buf_p ) ;
rackmeter_do_pause ( rm , 0 ) ;
}
2006-12-05 22:36:26 +03:00
static void rackmeter_do_timer ( struct work_struct * work )
2006-11-16 06:03:33 +03:00
{
2006-12-05 22:36:26 +03:00
struct rackmeter_cpu * rcpu =
container_of ( work , struct rackmeter_cpu , sniffer . work ) ;
struct rackmeter * rm = rcpu - > rm ;
2006-11-16 06:03:33 +03:00
unsigned int cpu = smp_processor_id ( ) ;
cputime64_t cur_jiffies , total_idle_ticks ;
unsigned int total_ticks , idle_ticks ;
int i , offset , load , cumm , pause ;
cur_jiffies = jiffies64_to_cputime64 ( get_jiffies_64 ( ) ) ;
total_ticks = ( unsigned int ) cputime64_sub ( cur_jiffies ,
rcpu - > prev_wall ) ;
rcpu - > prev_wall = cur_jiffies ;
total_idle_ticks = get_cpu_idle_time ( cpu ) ;
idle_ticks = ( unsigned int ) cputime64_sub ( total_idle_ticks ,
rcpu - > prev_idle ) ;
rcpu - > prev_idle = total_idle_ticks ;
/* We do a very dumb calculation to update the LEDs for now,
* we ' ll do better once we have actual PWM implemented
*/
load = ( 9 * ( total_ticks - idle_ticks ) ) / total_ticks ;
offset = cpu < < 3 ;
cumm = 0 ;
for ( i = 0 ; i < 8 ; i + + ) {
u8 ub = ( load > i ) ? 0xff : 0 ;
rm - > ubuf [ i + offset ] = ub ;
cumm | = ub ;
}
rcpu - > zero = ( cumm = = 0 ) ;
/* Now check if LEDs are all 0, we can stop DMA */
pause = ( rm - > cpu [ 0 ] . zero & & rm - > cpu [ 1 ] . zero ) ;
if ( pause ! = rm - > paused ) {
mutex_lock ( & rm - > sem ) ;
pause = ( rm - > cpu [ 0 ] . zero & & rm - > cpu [ 1 ] . zero ) ;
rackmeter_do_pause ( rm , pause ) ;
mutex_unlock ( & rm - > sem ) ;
}
schedule_delayed_work_on ( cpu , & rcpu - > sniffer ,
msecs_to_jiffies ( CPU_SAMPLING_RATE ) ) ;
}
static void __devinit rackmeter_init_cpu_sniffer ( struct rackmeter * rm )
{
unsigned int cpu ;
/* This driver works only with 1 or 2 CPUs numbered 0 and 1,
* but that ' s really all we have on Apple Xserve . It doesn ' t
* play very nice with CPU hotplug neither but we don ' t do that
* on those machines yet
*/
2006-12-05 22:36:26 +03:00
rm - > cpu [ 0 ] . rm = rm ;
INIT_DELAYED_WORK ( & rm - > cpu [ 0 ] . sniffer , rackmeter_do_timer ) ;
rm - > cpu [ 1 ] . rm = rm ;
INIT_DELAYED_WORK ( & rm - > cpu [ 1 ] . sniffer , rackmeter_do_timer ) ;
2006-11-16 06:03:33 +03:00
for_each_online_cpu ( cpu ) {
struct rackmeter_cpu * rcpu ;
if ( cpu > 1 )
continue ;
rcpu = & rm - > cpu [ cpu ] ; ;
rcpu - > prev_idle = get_cpu_idle_time ( cpu ) ;
rcpu - > prev_wall = jiffies64_to_cputime64 ( get_jiffies_64 ( ) ) ;
schedule_delayed_work_on ( cpu , & rm - > cpu [ cpu ] . sniffer ,
msecs_to_jiffies ( CPU_SAMPLING_RATE ) ) ;
}
}
static void __devexit rackmeter_stop_cpu_sniffer ( struct rackmeter * rm )
{
cancel_rearming_delayed_work ( & rm - > cpu [ 0 ] . sniffer ) ;
cancel_rearming_delayed_work ( & rm - > cpu [ 1 ] . sniffer ) ;
}
static int rackmeter_setup ( struct rackmeter * rm )
{
pr_debug ( " rackmeter: setting up i2s.. \n " ) ;
rackmeter_setup_i2s ( rm ) ;
pr_debug ( " rackmeter: setting up default pattern.. \n " ) ;
rackmeter_set_default_pattern ( rm ) ;
pr_debug ( " rackmeter: setting up dbdma.. \n " ) ;
rackmeter_setup_dbdma ( rm ) ;
pr_debug ( " rackmeter: start CPU measurements.. \n " ) ;
rackmeter_init_cpu_sniffer ( rm ) ;
printk ( KERN_INFO " RackMeter initialized \n " ) ;
return 0 ;
}
/* XXX FIXME: No PWM yet, this is 0/1 */
static u32 rackmeter_calc_sample ( struct rackmeter * rm , unsigned int index )
{
int led ;
u32 sample = 0 ;
for ( led = 0 ; led < 16 ; led + + ) {
sample > > = 1 ;
sample | = ( ( rm - > ubuf [ led ] > = 0x80 ) < < 15 ) ;
}
return ( sample < < 17 ) | ( sample > > 15 ) ;
}
static irqreturn_t rackmeter_irq ( int irq , void * arg )
{
struct rackmeter * rm = arg ;
struct rackmeter_dma * db = rm - > dma_buf_v ;
unsigned int mark , i ;
u32 * buf ;
/* Flush PCI buffers with an MMIO read. Maybe we could actually
* check the status one day . . . in case things go wrong , though
* this never happened to me
*/
( void ) in_le32 ( & rm - > dma_regs - > status ) ;
/* Make sure the CPU gets us in order */
rmb ( ) ;
/* Read mark */
mark = db - > mark ;
if ( mark ! = 1 & & mark ! = 2 ) {
printk ( KERN_WARNING " rackmeter: Incorrect DMA mark 0x%08x \n " ,
mark ) ;
/* We allow for 3 errors like that (stale DBDMA irqs) */
if ( + + rm - > stale_irq > 3 ) {
printk ( KERN_ERR " rackmeter: Too many errors, "
" stopping DMA \n " ) ;
DBDMA_DO_RESET ( rm - > dma_regs ) ;
}
return IRQ_HANDLED ;
}
/* Next buffer we need to fill is mark value */
buf = mark = = 1 ? db - > buf1 : db - > buf2 ;
/* Fill it now. This routine converts the 8 bits depth sample array
* into the PWM bitmap for each LED .
*/
for ( i = 0 ; i < SAMPLE_COUNT ; i + + )
buf [ i ] = rackmeter_calc_sample ( rm , i ) ;
return IRQ_HANDLED ;
}
static int __devinit rackmeter_probe ( struct macio_dev * mdev ,
const struct of_device_id * match )
{
struct device_node * i2s = NULL , * np = NULL ;
struct rackmeter * rm = NULL ;
struct resource ri2s , rdma ;
int rc = - ENODEV ;
pr_debug ( " rackmeter_probe() \n " ) ;
/* Get i2s-a node */
while ( ( i2s = of_get_next_child ( mdev - > ofdev . node , i2s ) ) ! = NULL )
if ( strcmp ( i2s - > name , " i2s-a " ) = = 0 )
break ;
if ( i2s = = NULL ) {
pr_debug ( " i2s-a child not found \n " ) ;
goto bail ;
}
/* Get lightshow or virtual sound */
while ( ( np = of_get_next_child ( i2s , np ) ) ! = NULL ) {
if ( strcmp ( np - > name , " lightshow " ) = = 0 )
break ;
if ( ( strcmp ( np - > name , " sound " ) = = 0 ) & &
2007-04-27 07:41:15 +04:00
of_get_property ( np , " virtual " , NULL ) ! = NULL )
2006-11-16 06:03:33 +03:00
break ;
}
if ( np = = NULL ) {
pr_debug ( " lightshow or sound+virtual child not found \n " ) ;
goto bail ;
}
/* Create and initialize our instance data */
rm = kzalloc ( sizeof ( struct rackmeter ) , GFP_KERNEL ) ;
if ( rm = = NULL ) {
printk ( KERN_ERR " rackmeter: failed to allocate memory ! \n " ) ;
rc = - ENOMEM ;
goto bail_release ;
}
rm - > mdev = mdev ;
rm - > i2s = i2s ;
mutex_init ( & rm - > sem ) ;
dev_set_drvdata ( & mdev - > ofdev . dev , rm ) ;
/* Check resources availability. We need at least resource 0 and 1 */
#if 0 /* Use that when i2s-a is finally an mdev per-se */
if ( macio_resource_count ( mdev ) < 2 | | macio_irq_count ( mdev ) < 2 ) {
printk ( KERN_ERR
" rackmeter: found match but lacks resources: %s "
" (%d resources, %d interrupts) \n " ,
mdev - > ofdev . node - > full_name ) ;
rc = - ENXIO ;
goto bail_free ;
}
if ( macio_request_resources ( mdev , " rackmeter " ) ) {
printk ( KERN_ERR
" rackmeter: failed to request resources: %s \n " ,
mdev - > ofdev . node - > full_name ) ;
rc = - EBUSY ;
goto bail_free ;
}
rm - > irq = macio_irq ( mdev , 1 ) ;
# else
rm - > irq = irq_of_parse_and_map ( i2s , 1 ) ;
if ( rm - > irq = = NO_IRQ | |
of_address_to_resource ( i2s , 0 , & ri2s ) | |
of_address_to_resource ( i2s , 1 , & rdma ) ) {
printk ( KERN_ERR
" rackmeter: found match but lacks resources: %s " ,
mdev - > ofdev . node - > full_name ) ;
rc = - ENXIO ;
goto bail_free ;
}
# endif
pr_debug ( " i2s @0x%08x \n " , ( unsigned int ) ri2s . start ) ;
pr_debug ( " dma @0x%08x \n " , ( unsigned int ) rdma . start ) ;
pr_debug ( " irq %d \n " , rm - > irq ) ;
rm - > ubuf = ( u8 * ) __get_free_page ( GFP_KERNEL ) ;
if ( rm - > ubuf = = NULL ) {
printk ( KERN_ERR
" rackmeter: failed to allocate samples page ! \n " ) ;
rc = - ENOMEM ;
goto bail_release ;
}
rm - > dma_buf_v = dma_alloc_coherent ( & macio_get_pci_dev ( mdev ) - > dev ,
sizeof ( struct rackmeter_dma ) ,
& rm - > dma_buf_p , GFP_KERNEL ) ;
if ( rm - > dma_buf_v = = NULL ) {
printk ( KERN_ERR
" rackmeter: failed to allocate dma buffer ! \n " ) ;
rc = - ENOMEM ;
goto bail_free_samples ;
}
#if 0
rm - > i2s_regs = ioremap ( macio_resource_start ( mdev , 0 ) , 0x1000 ) ;
# else
rm - > i2s_regs = ioremap ( ri2s . start , 0x1000 ) ;
# endif
if ( rm - > i2s_regs = = NULL ) {
printk ( KERN_ERR
" rackmeter: failed to map i2s registers ! \n " ) ;
rc = - ENXIO ;
goto bail_free_dma ;
}
#if 0
rm - > dma_regs = ioremap ( macio_resource_start ( mdev , 1 ) , 0x100 ) ;
# else
rm - > dma_regs = ioremap ( rdma . start , 0x100 ) ;
# endif
if ( rm - > dma_regs = = NULL ) {
printk ( KERN_ERR
" rackmeter: failed to map dma registers ! \n " ) ;
rc = - ENXIO ;
goto bail_unmap_i2s ;
}
rc = rackmeter_setup ( rm ) ;
if ( rc ) {
printk ( KERN_ERR
" rackmeter: failed to initialize ! \n " ) ;
rc = - ENXIO ;
goto bail_unmap_dma ;
}
rc = request_irq ( rm - > irq , rackmeter_irq , 0 , " rackmeter " , rm ) ;
if ( rc ! = 0 ) {
printk ( KERN_ERR
" rackmeter: failed to request interrupt ! \n " ) ;
goto bail_stop_dma ;
}
of_node_put ( np ) ;
return 0 ;
bail_stop_dma :
DBDMA_DO_RESET ( rm - > dma_regs ) ;
bail_unmap_dma :
iounmap ( rm - > dma_regs ) ;
bail_unmap_i2s :
iounmap ( rm - > i2s_regs ) ;
bail_free_dma :
dma_free_coherent ( & macio_get_pci_dev ( mdev ) - > dev ,
sizeof ( struct rackmeter_dma ) ,
rm - > dma_buf_v , rm - > dma_buf_p ) ;
bail_free_samples :
free_page ( ( unsigned long ) rm - > ubuf ) ;
bail_release :
#if 0
macio_release_resources ( mdev ) ;
# endif
bail_free :
kfree ( rm ) ;
bail :
of_node_put ( i2s ) ;
of_node_put ( np ) ;
dev_set_drvdata ( & mdev - > ofdev . dev , NULL ) ;
return rc ;
}
static int __devexit rackmeter_remove ( struct macio_dev * mdev )
{
struct rackmeter * rm = dev_get_drvdata ( & mdev - > ofdev . dev ) ;
/* Stop CPU sniffer timer & work queues */
rackmeter_stop_cpu_sniffer ( rm ) ;
/* Clear reference to private data */
dev_set_drvdata ( & mdev - > ofdev . dev , NULL ) ;
/* Stop/reset dbdma */
DBDMA_DO_RESET ( rm - > dma_regs ) ;
/* Release the IRQ */
free_irq ( rm - > irq , rm ) ;
/* Unmap registers */
iounmap ( rm - > dma_regs ) ;
iounmap ( rm - > i2s_regs ) ;
/* Free DMA */
dma_free_coherent ( & macio_get_pci_dev ( mdev ) - > dev ,
sizeof ( struct rackmeter_dma ) ,
rm - > dma_buf_v , rm - > dma_buf_p ) ;
/* Free samples */
free_page ( ( unsigned long ) rm - > ubuf ) ;
#if 0
/* Release resources */
macio_release_resources ( mdev ) ;
# endif
/* Get rid of me */
kfree ( rm ) ;
return 0 ;
}
static int rackmeter_shutdown ( struct macio_dev * mdev )
{
struct rackmeter * rm = dev_get_drvdata ( & mdev - > ofdev . dev ) ;
if ( rm = = NULL )
return - ENODEV ;
/* Stop CPU sniffer timer & work queues */
rackmeter_stop_cpu_sniffer ( rm ) ;
/* Stop/reset dbdma */
DBDMA_DO_RESET ( rm - > dma_regs ) ;
return 0 ;
}
static struct of_device_id rackmeter_match [ ] = {
{ . name = " i2s " } ,
{ }
} ;
static struct macio_driver rackmeter_drv = {
. name = " rackmeter " ,
. owner = THIS_MODULE ,
. match_table = rackmeter_match ,
. probe = rackmeter_probe ,
. remove = rackmeter_remove ,
. shutdown = rackmeter_shutdown ,
} ;
static int __init rackmeter_init ( void )
{
pr_debug ( " rackmeter_init() \n " ) ;
return macio_register_driver ( & rackmeter_drv ) ;
}
static void __exit rackmeter_exit ( void )
{
pr_debug ( " rackmeter_exit() \n " ) ;
macio_unregister_driver ( & rackmeter_drv ) ;
}
module_init ( rackmeter_init ) ;
module_exit ( rackmeter_exit ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Benjamin Herrenschmidt <benh@kernel.crashing.org> " ) ;
MODULE_DESCRIPTION ( " RackMeter: Support vu-meter on XServe front panel " ) ;