2014-08-14 19:55:38 +04:00
/*
* Toshiba HDD Active Protection Sensor ( HAPS ) driver
*
* Copyright ( C ) 2014 Azael Avalos < coproscefalo @ gmail . com >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
*/
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/init.h>
# include <linux/types.h>
# include <linux/acpi.h>
MODULE_AUTHOR ( " Azael Avalos <coproscefalo@gmail.com> " ) ;
MODULE_DESCRIPTION ( " Toshiba HDD Active Protection Sensor " ) ;
MODULE_LICENSE ( " GPL " ) ;
struct toshiba_haps_dev {
struct acpi_device * acpi_dev ;
int protection_level ;
} ;
static struct toshiba_haps_dev * toshiba_haps ;
/* HAPS functions */
static int toshiba_haps_reset_protection ( acpi_handle handle )
{
acpi_status status ;
status = acpi_evaluate_object ( handle , " RSSS " , NULL , NULL ) ;
if ( ACPI_FAILURE ( status ) ) {
pr_err ( " Unable to reset the HDD protection \n " ) ;
return - EIO ;
}
return 0 ;
}
static int toshiba_haps_protection_level ( acpi_handle handle , int level )
{
acpi_status status ;
status = acpi_execute_simple_method ( handle , " PTLV " , level ) ;
if ( ACPI_FAILURE ( status ) ) {
pr_err ( " Error while setting the protection level \n " ) ;
return - EIO ;
}
2016-09-07 18:28:14 +03:00
pr_debug ( " HDD protection level set to: %d \n " , level ) ;
2014-08-14 19:55:38 +04:00
return 0 ;
}
/* sysfs files */
static ssize_t protection_level_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct toshiba_haps_dev * haps = dev_get_drvdata ( dev ) ;
return sprintf ( buf , " %i \n " , haps - > protection_level ) ;
}
static ssize_t protection_level_store ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t count )
{
struct toshiba_haps_dev * haps = dev_get_drvdata ( dev ) ;
2015-02-26 20:58:30 +03:00
int level ;
int ret ;
2014-08-14 19:55:38 +04:00
2015-02-26 20:58:30 +03:00
ret = kstrtoint ( buf , 0 , & level ) ;
if ( ret )
return ret ;
/*
* Check for supported levels , which can be :
2014-08-14 19:55:38 +04:00
* 0 - Disabled | 1 - Low | 2 - Medium | 3 - High
*/
2015-02-26 20:58:30 +03:00
if ( level < 0 | | level > 3 )
return - EINVAL ;
/* Set the sensor level */
2014-08-14 19:55:38 +04:00
ret = toshiba_haps_protection_level ( haps - > acpi_dev - > handle , level ) ;
if ( ret ! = 0 )
return ret ;
haps - > protection_level = level ;
return count ;
}
2015-02-26 20:58:31 +03:00
static DEVICE_ATTR_RW ( protection_level ) ;
2014-08-14 19:55:38 +04:00
static ssize_t reset_protection_store ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t count )
{
struct toshiba_haps_dev * haps = dev_get_drvdata ( dev ) ;
2015-02-26 20:58:30 +03:00
int reset ;
int ret ;
2014-08-14 19:55:38 +04:00
2015-02-26 20:58:30 +03:00
ret = kstrtoint ( buf , 0 , & reset ) ;
if ( ret )
return ret ;
/* The only accepted value is 1 */
if ( reset ! = 1 )
2014-08-14 19:55:38 +04:00
return - EINVAL ;
/* Reset the protection interface */
ret = toshiba_haps_reset_protection ( haps - > acpi_dev - > handle ) ;
if ( ret ! = 0 )
return ret ;
return count ;
}
2015-02-26 20:58:31 +03:00
static DEVICE_ATTR_WO ( reset_protection ) ;
2014-08-14 19:55:38 +04:00
static struct attribute * haps_attributes [ ] = {
& dev_attr_protection_level . attr ,
& dev_attr_reset_protection . attr ,
NULL ,
} ;
2017-06-23 11:35:56 +03:00
static const struct attribute_group haps_attr_group = {
2014-08-14 19:55:38 +04:00
. attrs = haps_attributes ,
} ;
/*
* ACPI stuff
*/
static void toshiba_haps_notify ( struct acpi_device * device , u32 event )
{
2016-09-07 18:28:14 +03:00
pr_debug ( " Received event: 0x%x " , event ) ;
2014-08-14 19:55:38 +04:00
acpi_bus_generate_netlink_event ( device - > pnp . device_class ,
dev_name ( & device - > dev ) ,
event , 0 ) ;
}
static int toshiba_haps_remove ( struct acpi_device * device )
{
sysfs_remove_group ( & device - > dev . kobj , & haps_attr_group ) ;
if ( toshiba_haps )
toshiba_haps = NULL ;
return 0 ;
}
/* Helper function */
static int toshiba_haps_available ( acpi_handle handle )
{
acpi_status status ;
u64 hdd_present ;
/*
* A non existent device as well as having ( only )
* Solid State Drives can cause the call to fail .
*/
2016-09-07 18:28:13 +03:00
status = acpi_evaluate_integer ( handle , " _STA " , NULL , & hdd_present ) ;
if ( ACPI_FAILURE ( status ) ) {
pr_err ( " ACPI call to query HDD protection failed \n " ) ;
return 0 ;
}
if ( ! hdd_present ) {
2014-08-14 19:55:38 +04:00
pr_info ( " HDD protection not available or using SSD \n " ) ;
return 0 ;
}
return 1 ;
}
static int toshiba_haps_add ( struct acpi_device * acpi_dev )
{
struct toshiba_haps_dev * haps ;
int ret ;
if ( toshiba_haps )
return - EBUSY ;
if ( ! toshiba_haps_available ( acpi_dev - > handle ) )
return - ENODEV ;
pr_info ( " Toshiba HDD Active Protection Sensor device \n " ) ;
haps = kzalloc ( sizeof ( struct toshiba_haps_dev ) , GFP_KERNEL ) ;
if ( ! haps )
return - ENOMEM ;
haps - > acpi_dev = acpi_dev ;
haps - > protection_level = 2 ;
acpi_dev - > driver_data = haps ;
dev_set_drvdata ( & acpi_dev - > dev , haps ) ;
/* Set the protection level, currently at level 2 (Medium) */
ret = toshiba_haps_protection_level ( acpi_dev - > handle , 2 ) ;
if ( ret ! = 0 )
return ret ;
ret = sysfs_create_group ( & acpi_dev - > dev . kobj , & haps_attr_group ) ;
if ( ret )
return ret ;
toshiba_haps = haps ;
return 0 ;
}
# ifdef CONFIG_PM_SLEEP
static int toshiba_haps_suspend ( struct device * device )
{
struct toshiba_haps_dev * haps ;
int ret ;
haps = acpi_driver_data ( to_acpi_device ( device ) ) ;
/* Deactivate the protection on suspend */
ret = toshiba_haps_protection_level ( haps - > acpi_dev - > handle , 0 ) ;
return ret ;
}
static int toshiba_haps_resume ( struct device * device )
{
struct toshiba_haps_dev * haps ;
int ret ;
haps = acpi_driver_data ( to_acpi_device ( device ) ) ;
/* Set the stored protection level */
ret = toshiba_haps_protection_level ( haps - > acpi_dev - > handle ,
haps - > protection_level ) ;
/* Reset the protection on resume */
ret = toshiba_haps_reset_protection ( haps - > acpi_dev - > handle ) ;
if ( ret ! = 0 )
return ret ;
return ret ;
}
# endif
static SIMPLE_DEV_PM_OPS ( toshiba_haps_pm ,
toshiba_haps_suspend , toshiba_haps_resume ) ;
static const struct acpi_device_id haps_device_ids [ ] = {
{ " TOS620A " , 0 } ,
{ " " , 0 } ,
} ;
MODULE_DEVICE_TABLE ( acpi , haps_device_ids ) ;
static struct acpi_driver toshiba_haps_driver = {
. name = " Toshiba HAPS " ,
. owner = THIS_MODULE ,
. ids = haps_device_ids ,
. flags = ACPI_DRIVER_ALL_NOTIFY_EVENTS ,
. ops = {
. add = toshiba_haps_add ,
. remove = toshiba_haps_remove ,
. notify = toshiba_haps_notify ,
} ,
. drv . pm = & toshiba_haps_pm ,
} ;
module_acpi_driver ( toshiba_haps_driver ) ;