2020-05-18 20:43:21 +03:00
// SPDX-License-Identifier: GPL-2.0-only
// Copyright(c) 2019-2020 Intel Corporation.
# include <linux/device.h>
# include <linux/acpi.h>
2020-05-18 20:43:22 +03:00
# include <linux/pm_runtime.h>
2020-05-18 20:43:21 +03:00
# include <linux/soundwire/sdw.h>
# include <linux/soundwire/sdw_type.h>
# include "bus.h"
2020-11-24 16:07:42 +03:00
/*
* The 3 s value for autosuspend will only be used if there are no
* devices physically attached on a bus segment . In practice enabling
* the bus operation will result in children devices become active and
* the master device will only suspend when all its children are no
* longer active .
*/
# define SDW_MASTER_SUSPEND_DELAY_MS 3000
2020-05-18 23:35:50 +03:00
/*
* The sysfs for properties reflects the MIPI description as given
* in the MIPI DisCo spec
*
* Base file is :
* sdw - master - N
* | - - - - revision
* | - - - - clk_stop_modes
* | - - - - max_clk_freq
* | - - - - clk_freq
* | - - - - clk_gears
* | - - - - default_row
* | - - - - default_col
* | - - - - dynamic_shape
* | - - - - err_threshold
*/
# define sdw_master_attr(field, format_string) \
static ssize_t field # # _show ( struct device * dev , \
struct device_attribute * attr , \
char * buf ) \
{ \
struct sdw_master_device * md = dev_to_sdw_master_device ( dev ) ; \
return sprintf ( buf , format_string , md - > bus - > prop . field ) ; \
} \
static DEVICE_ATTR_RO ( field )
sdw_master_attr ( revision , " 0x%x \n " ) ;
sdw_master_attr ( clk_stop_modes , " 0x%x \n " ) ;
sdw_master_attr ( max_clk_freq , " %d \n " ) ;
sdw_master_attr ( default_row , " %d \n " ) ;
sdw_master_attr ( default_col , " %d \n " ) ;
sdw_master_attr ( default_frame_rate , " %d \n " ) ;
sdw_master_attr ( dynamic_frame , " %d \n " ) ;
sdw_master_attr ( err_threshold , " %d \n " ) ;
static ssize_t clock_frequencies_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct sdw_master_device * md = dev_to_sdw_master_device ( dev ) ;
ssize_t size = 0 ;
int i ;
for ( i = 0 ; i < md - > bus - > prop . num_clk_freq ; i + + )
size + = sprintf ( buf + size , " %8d " ,
md - > bus - > prop . clk_freq [ i ] ) ;
size + = sprintf ( buf + size , " \n " ) ;
return size ;
}
static DEVICE_ATTR_RO ( clock_frequencies ) ;
static ssize_t clock_gears_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct sdw_master_device * md = dev_to_sdw_master_device ( dev ) ;
ssize_t size = 0 ;
int i ;
for ( i = 0 ; i < md - > bus - > prop . num_clk_gears ; i + + )
size + = sprintf ( buf + size , " %8d " ,
md - > bus - > prop . clk_gears [ i ] ) ;
size + = sprintf ( buf + size , " \n " ) ;
return size ;
}
static DEVICE_ATTR_RO ( clock_gears ) ;
static struct attribute * master_node_attrs [ ] = {
& dev_attr_revision . attr ,
& dev_attr_clk_stop_modes . attr ,
& dev_attr_max_clk_freq . attr ,
& dev_attr_default_row . attr ,
& dev_attr_default_col . attr ,
& dev_attr_default_frame_rate . attr ,
& dev_attr_dynamic_frame . attr ,
& dev_attr_err_threshold . attr ,
& dev_attr_clock_frequencies . attr ,
& dev_attr_clock_gears . attr ,
NULL ,
} ;
ATTRIBUTE_GROUPS ( master_node ) ;
2020-05-18 20:43:21 +03:00
static void sdw_master_device_release ( struct device * dev )
{
struct sdw_master_device * md = dev_to_sdw_master_device ( dev ) ;
kfree ( md ) ;
}
2020-05-18 20:43:22 +03:00
static const struct dev_pm_ops master_dev_pm = {
SET_RUNTIME_PM_OPS ( pm_generic_runtime_suspend ,
pm_generic_runtime_resume , NULL )
} ;
2020-05-18 20:43:21 +03:00
struct device_type sdw_master_type = {
. name = " soundwire_master " ,
. release = sdw_master_device_release ,
2020-05-18 20:43:22 +03:00
. pm = & master_dev_pm ,
2020-05-18 20:43:21 +03:00
} ;
/**
* sdw_master_device_add ( ) - create a Linux Master Device representation .
* @ bus : SDW bus instance
* @ parent : parent device
* @ fwnode : firmware node handle
*/
int sdw_master_device_add ( struct sdw_bus * bus , struct device * parent ,
struct fwnode_handle * fwnode )
{
struct sdw_master_device * md ;
int ret ;
if ( ! parent )
return - EINVAL ;
md = kzalloc ( sizeof ( * md ) , GFP_KERNEL ) ;
if ( ! md )
return - ENOMEM ;
md - > dev . bus = & sdw_bus_type ;
md - > dev . type = & sdw_master_type ;
md - > dev . parent = parent ;
2020-05-18 23:35:50 +03:00
md - > dev . groups = master_node_groups ;
2020-05-18 20:43:21 +03:00
md - > dev . of_node = parent - > of_node ;
md - > dev . fwnode = fwnode ;
md - > dev . dma_mask = parent - > dma_mask ;
dev_set_name ( & md - > dev , " sdw-master-%d " , bus - > id ) ;
ret = device_register ( & md - > dev ) ;
if ( ret ) {
dev_err ( parent , " Failed to add master: ret %d \n " , ret ) ;
/*
* On err , don ' t free but drop ref as this will be freed
* when release method is invoked .
*/
put_device ( & md - > dev ) ;
goto device_register_err ;
}
/* add shortcuts to improve code readability/compactness */
md - > bus = bus ;
bus - > dev = & md - > dev ;
bus - > md = md ;
2020-11-24 16:07:42 +03:00
pm_runtime_set_autosuspend_delay ( & bus - > md - > dev , SDW_MASTER_SUSPEND_DELAY_MS ) ;
pm_runtime_use_autosuspend ( & bus - > md - > dev ) ;
pm_runtime_mark_last_busy ( & bus - > md - > dev ) ;
pm_runtime_set_active ( & bus - > md - > dev ) ;
2020-07-27 00:59:45 +03:00
pm_runtime_enable ( & bus - > md - > dev ) ;
2020-11-24 16:07:42 +03:00
pm_runtime_idle ( & bus - > md - > dev ) ;
2020-05-18 20:43:21 +03:00
device_register_err :
return ret ;
}
/**
* sdw_master_device_del ( ) - delete a Linux Master Device representation .
* @ bus : bus handle
*
* This function is the dual of sdw_master_device_add ( )
*/
int sdw_master_device_del ( struct sdw_bus * bus )
{
2020-07-27 00:59:45 +03:00
pm_runtime_disable ( & bus - > md - > dev ) ;
2020-05-18 20:43:21 +03:00
device_unregister ( bus - > dev ) ;
return 0 ;
}