2013-03-07 02:46:20 +04:00
/*
* ACPI support for Intel Lynxpoint LPSS .
*
* Copyright ( C ) 2013 , Intel Corporation
* Authors : Mika Westerberg < mika . westerberg @ linux . intel . com >
* Rafael J . Wysocki < rafael . j . wysocki @ intel . com >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*/
# include <linux/acpi.h>
# include <linux/clk.h>
# include <linux/clkdev.h>
# include <linux/clk-provider.h>
# include <linux/err.h>
# include <linux/io.h>
# include <linux/platform_device.h>
# include <linux/platform_data/clk-lpss.h>
2013-03-07 02:46:28 +04:00
# include <linux/pm_runtime.h>
2013-03-07 02:46:20 +04:00
# include "internal.h"
ACPI_MODULE_NAME ( " acpi_lpss " ) ;
# define LPSS_CLK_SIZE 0x04
2013-03-07 02:46:28 +04:00
# define LPSS_LTR_SIZE 0x18
/* Offsets relative to LPSS_PRIVATE_OFFSET */
# define LPSS_GENERAL 0x08
# define LPSS_GENERAL_LTR_MODE_SW BIT(2)
# define LPSS_SW_LTR 0x10
# define LPSS_AUTO_LTR 0x14
2013-03-07 02:46:20 +04:00
struct lpss_device_desc {
bool clk_required ;
const char * clk_parent ;
2013-03-07 02:46:28 +04:00
bool ltr_required ;
unsigned int prv_offset ;
2013-03-07 02:46:20 +04:00
} ;
struct lpss_private_data {
void __iomem * mmio_base ;
resource_size_t mmio_size ;
struct clk * clk ;
const struct lpss_device_desc * dev_desc ;
} ;
static struct lpss_device_desc lpt_dev_desc = {
. clk_required = true ,
. clk_parent = " lpss_clk " ,
2013-03-07 02:46:28 +04:00
. prv_offset = 0x800 ,
. ltr_required = true ,
} ;
static struct lpss_device_desc lpt_sdio_dev_desc = {
. prv_offset = 0x1000 ,
. ltr_required = true ,
2013-03-07 02:46:20 +04:00
} ;
static const struct acpi_device_id acpi_lpss_device_ids [ ] = {
/* Lynxpoint LPSS devices */
{ " INT33C0 " , ( unsigned long ) & lpt_dev_desc } ,
{ " INT33C1 " , ( unsigned long ) & lpt_dev_desc } ,
{ " INT33C2 " , ( unsigned long ) & lpt_dev_desc } ,
{ " INT33C3 " , ( unsigned long ) & lpt_dev_desc } ,
{ " INT33C4 " , ( unsigned long ) & lpt_dev_desc } ,
{ " INT33C5 " , ( unsigned long ) & lpt_dev_desc } ,
2013-03-07 02:46:28 +04:00
{ " INT33C6 " , ( unsigned long ) & lpt_sdio_dev_desc } ,
2013-03-07 02:46:20 +04:00
{ " INT33C7 " , } ,
{ }
} ;
static int is_memory ( struct acpi_resource * res , void * not_used )
{
struct resource r ;
return ! acpi_dev_resource_memory ( res , & r ) ;
}
/* LPSS main clock device. */
static struct platform_device * lpss_clk_dev ;
static inline void lpt_register_clock_device ( void )
{
lpss_clk_dev = platform_device_register_simple ( " clk-lpt " , - 1 , NULL , 0 ) ;
}
static int register_device_clock ( struct acpi_device * adev ,
struct lpss_private_data * pdata )
{
const struct lpss_device_desc * dev_desc = pdata - > dev_desc ;
if ( ! lpss_clk_dev )
lpt_register_clock_device ( ) ;
if ( ! dev_desc - > clk_parent | | ! pdata - > mmio_base
2013-03-07 02:46:28 +04:00
| | pdata - > mmio_size < dev_desc - > prv_offset + LPSS_CLK_SIZE )
2013-03-07 02:46:20 +04:00
return - ENODATA ;
pdata - > clk = clk_register_gate ( NULL , dev_name ( & adev - > dev ) ,
dev_desc - > clk_parent , 0 ,
2013-03-07 02:46:28 +04:00
pdata - > mmio_base + dev_desc - > prv_offset ,
2013-03-07 02:46:20 +04:00
0 , 0 , NULL ) ;
if ( IS_ERR ( pdata - > clk ) )
return PTR_ERR ( pdata - > clk ) ;
clk_register_clkdev ( pdata - > clk , NULL , dev_name ( & adev - > dev ) ) ;
return 0 ;
}
static int acpi_lpss_create_device ( struct acpi_device * adev ,
const struct acpi_device_id * id )
{
struct lpss_device_desc * dev_desc ;
struct lpss_private_data * pdata ;
struct resource_list_entry * rentry ;
struct list_head resource_list ;
int ret ;
dev_desc = ( struct lpss_device_desc * ) id - > driver_data ;
if ( ! dev_desc )
return acpi_create_platform_device ( adev , id ) ;
pdata = kzalloc ( sizeof ( * pdata ) , GFP_KERNEL ) ;
if ( ! pdata )
return - ENOMEM ;
INIT_LIST_HEAD ( & resource_list ) ;
ret = acpi_dev_get_resources ( adev , & resource_list , is_memory , NULL ) ;
if ( ret < 0 )
goto err_out ;
list_for_each_entry ( rentry , & resource_list , node )
if ( resource_type ( & rentry - > res ) = = IORESOURCE_MEM ) {
pdata - > mmio_size = resource_size ( & rentry - > res ) ;
pdata - > mmio_base = ioremap ( rentry - > res . start ,
pdata - > mmio_size ) ;
pdata - > dev_desc = dev_desc ;
break ;
}
acpi_dev_free_resource_list ( & resource_list ) ;
if ( dev_desc - > clk_required ) {
ret = register_device_clock ( adev , pdata ) ;
if ( ret ) {
/*
* Skip the device , but don ' t terminate the namespace
* scan .
*/
2013-03-20 16:14:30 +04:00
kfree ( pdata ) ;
return 0 ;
2013-03-07 02:46:20 +04:00
}
}
adev - > driver_data = pdata ;
ret = acpi_create_platform_device ( adev , id ) ;
if ( ret > 0 )
return ret ;
adev - > driver_data = NULL ;
err_out :
kfree ( pdata ) ;
return ret ;
}
2013-03-07 02:46:28 +04:00
static int lpss_reg_read ( struct device * dev , unsigned int reg , u32 * val )
{
struct acpi_device * adev ;
struct lpss_private_data * pdata ;
unsigned long flags ;
int ret ;
ret = acpi_bus_get_device ( ACPI_HANDLE ( dev ) , & adev ) ;
if ( WARN_ON ( ret ) )
return ret ;
spin_lock_irqsave ( & dev - > power . lock , flags ) ;
if ( pm_runtime_suspended ( dev ) ) {
ret = - EAGAIN ;
goto out ;
}
pdata = acpi_driver_data ( adev ) ;
if ( WARN_ON ( ! pdata | | ! pdata - > mmio_base ) ) {
ret = - ENODEV ;
goto out ;
}
* val = readl ( pdata - > mmio_base + pdata - > dev_desc - > prv_offset + reg ) ;
out :
spin_unlock_irqrestore ( & dev - > power . lock , flags ) ;
return ret ;
}
static ssize_t lpss_ltr_show ( struct device * dev , struct device_attribute * attr ,
char * buf )
{
u32 ltr_value = 0 ;
unsigned int reg ;
int ret ;
reg = strcmp ( attr - > attr . name , " auto_ltr " ) ? LPSS_SW_LTR : LPSS_AUTO_LTR ;
ret = lpss_reg_read ( dev , reg , & ltr_value ) ;
if ( ret )
return ret ;
return snprintf ( buf , PAGE_SIZE , " %08x \n " , ltr_value ) ;
}
static ssize_t lpss_ltr_mode_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
u32 ltr_mode = 0 ;
char * outstr ;
int ret ;
ret = lpss_reg_read ( dev , LPSS_GENERAL , & ltr_mode ) ;
if ( ret )
return ret ;
outstr = ( ltr_mode & LPSS_GENERAL_LTR_MODE_SW ) ? " sw " : " auto " ;
return sprintf ( buf , " %s \n " , outstr ) ;
}
static DEVICE_ATTR ( auto_ltr , S_IRUSR , lpss_ltr_show , NULL ) ;
static DEVICE_ATTR ( sw_ltr , S_IRUSR , lpss_ltr_show , NULL ) ;
static DEVICE_ATTR ( ltr_mode , S_IRUSR , lpss_ltr_mode_show , NULL ) ;
static struct attribute * lpss_attrs [ ] = {
& dev_attr_auto_ltr . attr ,
& dev_attr_sw_ltr . attr ,
& dev_attr_ltr_mode . attr ,
NULL ,
} ;
static struct attribute_group lpss_attr_group = {
. attrs = lpss_attrs ,
. name = " lpss_ltr " ,
} ;
static int acpi_lpss_platform_notify ( struct notifier_block * nb ,
unsigned long action , void * data )
{
struct platform_device * pdev = to_platform_device ( data ) ;
struct lpss_private_data * pdata ;
struct acpi_device * adev ;
const struct acpi_device_id * id ;
int ret = 0 ;
id = acpi_match_device ( acpi_lpss_device_ids , & pdev - > dev ) ;
if ( ! id | | ! id - > driver_data )
return 0 ;
if ( acpi_bus_get_device ( ACPI_HANDLE ( & pdev - > dev ) , & adev ) )
return 0 ;
pdata = acpi_driver_data ( adev ) ;
if ( ! pdata | | ! pdata - > mmio_base | | ! pdata - > dev_desc - > ltr_required )
return 0 ;
if ( pdata - > mmio_size < pdata - > dev_desc - > prv_offset + LPSS_LTR_SIZE ) {
dev_err ( & pdev - > dev , " MMIO size insufficient to access LTR \n " ) ;
return 0 ;
}
if ( action = = BUS_NOTIFY_ADD_DEVICE )
ret = sysfs_create_group ( & pdev - > dev . kobj , & lpss_attr_group ) ;
else if ( action = = BUS_NOTIFY_DEL_DEVICE )
sysfs_remove_group ( & pdev - > dev . kobj , & lpss_attr_group ) ;
return ret ;
}
static struct notifier_block acpi_lpss_nb = {
. notifier_call = acpi_lpss_platform_notify ,
} ;
2013-03-07 02:46:20 +04:00
static struct acpi_scan_handler lpss_handler = {
. ids = acpi_lpss_device_ids ,
. attach = acpi_lpss_create_device ,
} ;
void __init acpi_lpss_init ( void )
{
2013-03-07 02:46:28 +04:00
if ( ! lpt_clk_init ( ) ) {
bus_register_notifier ( & platform_bus_type , & acpi_lpss_nb ) ;
2013-03-07 02:46:20 +04:00
acpi_scan_add_handler ( & lpss_handler ) ;
2013-03-07 02:46:28 +04:00
}
2013-03-07 02:46:20 +04:00
}