2014-11-04 14:15:31 +08:00
# include <linux/init.h>
# include <linux/pci.h>
# include <linux/percpu.h>
# include <linux/delay.h>
# include <linux/spinlock.h>
# include <linux/interrupt.h>
# include <asm/hpet.h>
# include <asm/time.h>
# define SMBUS_CFG_BASE (loongson_sysconf.ht_control_base + 0x0300a000)
# define SMBUS_PCI_REG40 0x40
# define SMBUS_PCI_REG64 0x64
# define SMBUS_PCI_REGB4 0xb4
2016-01-21 21:09:50 +08:00
# define HPET_MIN_CYCLES 64
# define HPET_MIN_PROG_DELTA (HPET_MIN_CYCLES + (HPET_MIN_CYCLES >> 1))
2014-11-04 14:15:31 +08:00
static DEFINE_SPINLOCK ( hpet_lock ) ;
DEFINE_PER_CPU ( struct clock_event_device , hpet_clockevent_device ) ;
static unsigned int smbus_read ( int offset )
{
return * ( volatile unsigned int * ) ( SMBUS_CFG_BASE + offset ) ;
}
static void smbus_write ( int offset , int data )
{
* ( volatile unsigned int * ) ( SMBUS_CFG_BASE + offset ) = data ;
}
static void smbus_enable ( int offset , int bit )
{
unsigned int cfg = smbus_read ( offset ) ;
cfg | = bit ;
smbus_write ( offset , cfg ) ;
}
static int hpet_read ( int offset )
{
return * ( volatile unsigned int * ) ( HPET_MMIO_ADDR + offset ) ;
}
static void hpet_write ( int offset , int data )
{
* ( volatile unsigned int * ) ( HPET_MMIO_ADDR + offset ) = data ;
}
static void hpet_start_counter ( void )
{
unsigned int cfg = hpet_read ( HPET_CFG ) ;
cfg | = HPET_CFG_ENABLE ;
hpet_write ( HPET_CFG , cfg ) ;
}
static void hpet_stop_counter ( void )
{
unsigned int cfg = hpet_read ( HPET_CFG ) ;
cfg & = ~ HPET_CFG_ENABLE ;
hpet_write ( HPET_CFG , cfg ) ;
}
static void hpet_reset_counter ( void )
{
hpet_write ( HPET_COUNTER , 0 ) ;
hpet_write ( HPET_COUNTER + 4 , 0 ) ;
}
static void hpet_restart_counter ( void )
{
hpet_stop_counter ( ) ;
hpet_reset_counter ( ) ;
hpet_start_counter ( ) ;
}
static void hpet_enable_legacy_int ( void )
{
/* Do nothing on Loongson-3 */
}
2015-07-06 16:42:01 +05:30
static int hpet_set_state_periodic ( struct clock_event_device * evt )
2014-11-04 14:15:31 +08:00
{
2015-07-06 16:42:01 +05:30
int cfg ;
2014-11-04 14:15:31 +08:00
spin_lock ( & hpet_lock ) ;
2015-07-06 16:42:01 +05:30
pr_info ( " set clock event to periodic mode! \n " ) ;
/* stop counter */
hpet_stop_counter ( ) ;
/* enables the timer0 to generate a periodic interrupt */
cfg = hpet_read ( HPET_T0_CFG ) ;
cfg & = ~ HPET_TN_LEVEL ;
cfg | = HPET_TN_ENABLE | HPET_TN_PERIODIC | HPET_TN_SETVAL |
HPET_TN_32BIT ;
hpet_write ( HPET_T0_CFG , cfg ) ;
/* set the comparator */
hpet_write ( HPET_T0_CMP , HPET_COMPARE_VAL ) ;
udelay ( 1 ) ;
hpet_write ( HPET_T0_CMP , HPET_COMPARE_VAL ) ;
/* start counter */
hpet_start_counter ( ) ;
spin_unlock ( & hpet_lock ) ;
return 0 ;
}
static int hpet_set_state_shutdown ( struct clock_event_device * evt )
{
int cfg ;
spin_lock ( & hpet_lock ) ;
cfg = hpet_read ( HPET_T0_CFG ) ;
cfg & = ~ HPET_TN_ENABLE ;
hpet_write ( HPET_T0_CFG , cfg ) ;
2014-11-04 14:15:31 +08:00
spin_unlock ( & hpet_lock ) ;
2015-07-06 16:42:01 +05:30
return 0 ;
}
static int hpet_set_state_oneshot ( struct clock_event_device * evt )
{
int cfg ;
spin_lock ( & hpet_lock ) ;
pr_info ( " set clock event to one shot mode! \n " ) ;
cfg = hpet_read ( HPET_T0_CFG ) ;
/*
* set timer0 type
* 1 : periodic interrupt
* 0 : non - periodic ( oneshot ) interrupt
*/
cfg & = ~ HPET_TN_PERIODIC ;
cfg | = HPET_TN_ENABLE | HPET_TN_32BIT ;
hpet_write ( HPET_T0_CFG , cfg ) ;
spin_unlock ( & hpet_lock ) ;
return 0 ;
}
static int hpet_tick_resume ( struct clock_event_device * evt )
{
spin_lock ( & hpet_lock ) ;
hpet_enable_legacy_int ( ) ;
spin_unlock ( & hpet_lock ) ;
return 0 ;
2014-11-04 14:15:31 +08:00
}
static int hpet_next_event ( unsigned long delta ,
struct clock_event_device * evt )
{
unsigned int cnt ;
int res ;
cnt = hpet_read ( HPET_COUNTER ) ;
cnt + = delta ;
hpet_write ( HPET_T0_CMP , cnt ) ;
2016-01-21 21:09:50 +08:00
res = ( int ) ( cnt - hpet_read ( HPET_COUNTER ) ) ;
return res < HPET_MIN_CYCLES ? - ETIME : 0 ;
2014-11-04 14:15:31 +08:00
}
static irqreturn_t hpet_irq_handler ( int irq , void * data )
{
int is_irq ;
struct clock_event_device * cd ;
unsigned int cpu = smp_processor_id ( ) ;
is_irq = hpet_read ( HPET_STATUS ) ;
if ( is_irq & HPET_T0_IRS ) {
/* clear the TIMER0 irq status register */
hpet_write ( HPET_STATUS , HPET_T0_IRS ) ;
cd = & per_cpu ( hpet_clockevent_device , cpu ) ;
cd - > event_handler ( cd ) ;
return IRQ_HANDLED ;
}
return IRQ_NONE ;
}
static struct irqaction hpet_irq = {
. handler = hpet_irq_handler ,
2015-03-05 15:23:08 +01:00
. flags = IRQF_NOBALANCING | IRQF_TIMER ,
2014-11-04 14:15:31 +08:00
. name = " hpet " ,
} ;
/*
* hpet address assignation and irq setting should be done in bios .
* but pmon don ' t do this , we just setup here directly .
* The operation under is normal . unfortunately , hpet_setup process
* is before pci initialize .
*
* {
* struct pci_dev * pdev ;
*
* pdev = pci_get_device ( PCI_VENDOR_ID_ATI , PCI_DEVICE_ID_ATI_SBX00_SMBUS , NULL ) ;
* pci_write_config_word ( pdev , SMBUS_PCI_REGB4 , HPET_ADDR ) ;
*
* . . .
* }
*/
static void hpet_setup ( void )
{
/* set hpet base address */
smbus_write ( SMBUS_PCI_REGB4 , HPET_ADDR ) ;
2016-05-21 14:00:26 +02:00
/* enable decoding of access to HPET MMIO*/
2014-11-04 14:15:31 +08:00
smbus_enable ( SMBUS_PCI_REG40 , ( 1 < < 28 ) ) ;
/* HPET irq enable */
smbus_enable ( SMBUS_PCI_REG64 , ( 1 < < 10 ) ) ;
hpet_enable_legacy_int ( ) ;
}
void __init setup_hpet_timer ( void )
{
unsigned int cpu = smp_processor_id ( ) ;
struct clock_event_device * cd ;
hpet_setup ( ) ;
cd = & per_cpu ( hpet_clockevent_device , cpu ) ;
cd - > name = " hpet " ;
cd - > rating = 320 ;
cd - > features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT ;
2015-07-06 16:42:01 +05:30
cd - > set_state_shutdown = hpet_set_state_shutdown ;
cd - > set_state_periodic = hpet_set_state_periodic ;
cd - > set_state_oneshot = hpet_set_state_oneshot ;
cd - > tick_resume = hpet_tick_resume ;
2014-11-04 14:15:31 +08:00
cd - > set_next_event = hpet_next_event ;
cd - > irq = HPET_T0_IRQ ;
cd - > cpumask = cpumask_of ( cpu ) ;
clockevent_set_clock ( cd , HPET_FREQ ) ;
cd - > max_delta_ns = clockevent_delta2ns ( 0x7fffffff , cd ) ;
2016-01-21 21:09:50 +08:00
cd - > min_delta_ns = clockevent_delta2ns ( HPET_MIN_PROG_DELTA , cd ) ;
2014-11-04 14:15:31 +08:00
clockevents_register_device ( cd ) ;
setup_irq ( HPET_T0_IRQ , & hpet_irq ) ;
pr_info ( " hpet clock event device register \n " ) ;
}
static cycle_t hpet_read_counter ( struct clocksource * cs )
{
return ( cycle_t ) hpet_read ( HPET_COUNTER ) ;
}
static void hpet_suspend ( struct clocksource * cs )
{
}
static void hpet_resume ( struct clocksource * cs )
{
hpet_setup ( ) ;
hpet_restart_counter ( ) ;
}
static struct clocksource csrc_hpet = {
. name = " hpet " ,
/* mips clocksource rating is less than 300, so hpet is better. */
. rating = 300 ,
. read = hpet_read_counter ,
. mask = CLOCKSOURCE_MASK ( 32 ) ,
/* oneshot mode work normal with this flag */
. flags = CLOCK_SOURCE_IS_CONTINUOUS ,
. suspend = hpet_suspend ,
. resume = hpet_resume ,
. mult = 0 ,
. shift = 10 ,
} ;
int __init init_hpet_clocksource ( void )
{
csrc_hpet . mult = clocksource_hz2mult ( HPET_FREQ , csrc_hpet . shift ) ;
return clocksource_register_hz ( & csrc_hpet , HPET_FREQ ) ;
}
arch_initcall ( init_hpet_clocksource ) ;