2023-09-18 10:51:29 +01:00
// SPDX-License-Identifier: GPL-2.0-only
//
// KUnit test for the Cirrus side-codec library.
//
// Copyright (C) 2023 Cirrus Logic, Inc. and
// Cirrus Logic International Semiconductor Ltd.
# include <kunit/test.h>
# include <linux/gpio/driver.h>
# include <linux/module.h>
# include <linux/platform_device.h>
# include "cirrus_scodec.h"
struct cirrus_scodec_test_gpio {
unsigned int pin_state ;
struct gpio_chip chip ;
} ;
struct cirrus_scodec_test_priv {
struct platform_device amp_pdev ;
struct platform_device * gpio_pdev ;
struct cirrus_scodec_test_gpio * gpio_priv ;
} ;
static int cirrus_scodec_test_gpio_get_direction ( struct gpio_chip * chip ,
unsigned int offset )
{
return GPIO_LINE_DIRECTION_IN ;
}
static int cirrus_scodec_test_gpio_direction_in ( struct gpio_chip * chip ,
unsigned int offset )
{
return 0 ;
}
static int cirrus_scodec_test_gpio_get ( struct gpio_chip * chip , unsigned int offset )
{
struct cirrus_scodec_test_gpio * gpio_priv = gpiochip_get_data ( chip ) ;
return ! ! ( gpio_priv - > pin_state & BIT ( offset ) ) ;
}
static int cirrus_scodec_test_gpio_direction_out ( struct gpio_chip * chip ,
unsigned int offset , int value )
{
return - EOPNOTSUPP ;
}
static void cirrus_scodec_test_gpio_set ( struct gpio_chip * chip , unsigned int offset ,
int value )
{
}
static int cirrus_scodec_test_gpio_set_config ( struct gpio_chip * gc ,
unsigned int offset ,
unsigned long config )
{
switch ( pinconf_to_config_param ( config ) ) {
case PIN_CONFIG_OUTPUT :
case PIN_CONFIG_OUTPUT_ENABLE :
return - EOPNOTSUPP ;
default :
return 0 ;
}
}
static const struct gpio_chip cirrus_scodec_test_gpio_chip = {
. label = " cirrus_scodec_test_gpio " ,
. owner = THIS_MODULE ,
. request = gpiochip_generic_request ,
. free = gpiochip_generic_free ,
. get_direction = cirrus_scodec_test_gpio_get_direction ,
. direction_input = cirrus_scodec_test_gpio_direction_in ,
. get = cirrus_scodec_test_gpio_get ,
. direction_output = cirrus_scodec_test_gpio_direction_out ,
. set = cirrus_scodec_test_gpio_set ,
. set_config = cirrus_scodec_test_gpio_set_config ,
. base = - 1 ,
. ngpio = 32 ,
} ;
static int cirrus_scodec_test_gpio_probe ( struct platform_device * pdev )
{
struct cirrus_scodec_test_gpio * gpio_priv ;
int ret ;
gpio_priv = devm_kzalloc ( & pdev - > dev , sizeof ( * gpio_priv ) , GFP_KERNEL ) ;
if ( ! gpio_priv )
return - ENOMEM ;
/* GPIO core modifies our struct gpio_chip so use a copy */
gpio_priv - > chip = cirrus_scodec_test_gpio_chip ;
ret = devm_gpiochip_add_data ( & pdev - > dev , & gpio_priv - > chip , gpio_priv ) ;
if ( ret )
return dev_err_probe ( & pdev - > dev , ret , " Failed to add gpiochip \n " ) ;
dev_set_drvdata ( & pdev - > dev , gpio_priv ) ;
return 0 ;
}
static struct platform_driver cirrus_scodec_test_gpio_driver = {
. driver . name = " cirrus_scodec_test_gpio_drv " ,
. probe = cirrus_scodec_test_gpio_probe ,
} ;
/* software_node referencing the gpio driver */
static const struct software_node cirrus_scodec_test_gpio_swnode = {
. name = " cirrus_scodec_test_gpio " ,
} ;
static int cirrus_scodec_test_create_gpio ( struct kunit * test )
{
struct cirrus_scodec_test_priv * priv = test - > priv ;
int ret ;
priv - > gpio_pdev = platform_device_alloc ( cirrus_scodec_test_gpio_driver . driver . name , - 1 ) ;
if ( ! priv - > gpio_pdev )
return - ENOMEM ;
ret = device_add_software_node ( & priv - > gpio_pdev - > dev , & cirrus_scodec_test_gpio_swnode ) ;
if ( ret ) {
platform_device_put ( priv - > gpio_pdev ) ;
KUNIT_FAIL ( test , " Failed to add swnode to gpio: %d \n " , ret ) ;
return ret ;
}
ret = platform_device_add ( priv - > gpio_pdev ) ;
if ( ret ) {
platform_device_put ( priv - > gpio_pdev ) ;
KUNIT_FAIL ( test , " Failed to add gpio platform device: %d \n " , ret ) ;
return ret ;
}
priv - > gpio_priv = dev_get_drvdata ( & priv - > gpio_pdev - > dev ) ;
if ( ! priv - > gpio_priv ) {
platform_device_put ( priv - > gpio_pdev ) ;
2023-09-27 15:44:01 +03:00
KUNIT_FAIL ( test , " Failed to get gpio private data \n " ) ;
return - EINVAL ;
2023-09-18 10:51:29 +01:00
}
return 0 ;
}
static void cirrus_scodec_test_set_gpio_ref_arg ( struct software_node_ref_args * arg ,
int gpio_num )
{
struct software_node_ref_args template =
SOFTWARE_NODE_REFERENCE ( & cirrus_scodec_test_gpio_swnode , gpio_num , 0 ) ;
* arg = template ;
}
static int cirrus_scodec_test_set_spkid_swnode ( struct kunit * test ,
struct device * dev ,
struct software_node_ref_args * args ,
int num_args )
{
const struct property_entry props_template [ ] = {
PROPERTY_ENTRY_REF_ARRAY_LEN ( " spk-id-gpios " , args , num_args ) ,
{ }
} ;
struct property_entry * props ;
struct software_node * node ;
node = kunit_kzalloc ( test , sizeof ( * node ) , GFP_KERNEL ) ;
if ( ! node )
return - ENOMEM ;
props = kunit_kzalloc ( test , sizeof ( props_template ) , GFP_KERNEL ) ;
if ( ! props )
return - ENOMEM ;
memcpy ( props , props_template , sizeof ( props_template ) ) ;
node - > properties = props ;
return device_add_software_node ( dev , node ) ;
}
struct cirrus_scodec_test_spkid_param {
int num_amps ;
int gpios_per_amp ;
int num_amps_sharing ;
} ;
static void cirrus_scodec_test_spkid_parse ( struct kunit * test )
{
struct cirrus_scodec_test_priv * priv = test - > priv ;
const struct cirrus_scodec_test_spkid_param * param = test - > param_value ;
int num_spk_id_refs = param - > num_amps * param - > gpios_per_amp ;
struct software_node_ref_args * refs ;
struct device * dev = & priv - > amp_pdev . dev ;
unsigned int v ;
int i , ret ;
refs = kunit_kcalloc ( test , num_spk_id_refs , sizeof ( * refs ) , GFP_KERNEL ) ;
KUNIT_ASSERT_NOT_NULL ( test , refs ) ;
for ( i = 0 , v = 0 ; i < num_spk_id_refs ; ) {
cirrus_scodec_test_set_gpio_ref_arg ( & refs [ i + + ] , v + + ) ;
/*
* If amps are sharing GPIOs repeat the last set of
* GPIOs until we ' ve done that number of amps .
* We have done all GPIOs for an amp when i is a multiple
* of gpios_per_amp .
* We have done all amps sharing the same GPIOs when i is
* a multiple of ( gpios_per_amp * num_amps_sharing ) .
*/
if ( ! ( i % param - > gpios_per_amp ) & &
( i % ( param - > gpios_per_amp * param - > num_amps_sharing ) ) )
v - = param - > gpios_per_amp ;
}
ret = cirrus_scodec_test_set_spkid_swnode ( test , dev , refs , num_spk_id_refs ) ;
KUNIT_EXPECT_EQ_MSG ( test , ret , 0 , " Failed to add swnode \n " ) ;
for ( i = 0 ; i < param - > num_amps ; + + i ) {
for ( v = 0 ; v < ( 1 < < param - > gpios_per_amp ) ; + + v ) {
/* Set only the GPIO bits used by this amp */
priv - > gpio_priv - > pin_state =
v < < ( param - > gpios_per_amp * ( i / param - > num_amps_sharing ) ) ;
ret = cirrus_scodec_get_speaker_id ( dev , i , param - > num_amps , - 1 ) ;
KUNIT_EXPECT_EQ_MSG ( test , ret , v ,
" get_speaker_id failed amp:%d pin_state:%#x \n " ,
i , priv - > gpio_priv - > pin_state ) ;
}
}
}
static void cirrus_scodec_test_no_spkid ( struct kunit * test )
{
struct cirrus_scodec_test_priv * priv = test - > priv ;
struct device * dev = & priv - > amp_pdev . dev ;
int ret ;
ret = cirrus_scodec_get_speaker_id ( dev , 0 , 4 , - 1 ) ;
KUNIT_EXPECT_EQ ( test , ret , - ENOENT ) ;
}
static void cirrus_scodec_test_dev_release ( struct device * dev )
{
}
static int cirrus_scodec_test_case_init ( struct kunit * test )
{
struct cirrus_scodec_test_priv * priv ;
int ret ;
priv = kunit_kzalloc ( test , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
test - > priv = priv ;
/* Create dummy GPIO */
ret = cirrus_scodec_test_create_gpio ( test ) ;
if ( ret < 0 )
return ret ;
/* Create dummy amp driver dev */
priv - > amp_pdev . name = " cirrus_scodec_test_amp_drv " ;
priv - > amp_pdev . id = - 1 ;
priv - > amp_pdev . dev . release = cirrus_scodec_test_dev_release ;
ret = platform_device_register ( & priv - > amp_pdev ) ;
KUNIT_ASSERT_GE_MSG ( test , ret , 0 , " Failed to register amp platform device \n " ) ;
return 0 ;
}
static void cirrus_scodec_test_case_exit ( struct kunit * test )
{
struct cirrus_scodec_test_priv * priv = test - > priv ;
if ( priv - > amp_pdev . name )
platform_device_unregister ( & priv - > amp_pdev ) ;
if ( priv - > gpio_pdev ) {
device_remove_software_node ( & priv - > gpio_pdev - > dev ) ;
platform_device_unregister ( priv - > gpio_pdev ) ;
}
}
static int cirrus_scodec_test_suite_init ( struct kunit_suite * suite )
{
int ret ;
/* Register mock GPIO driver */
ret = platform_driver_register ( & cirrus_scodec_test_gpio_driver ) ;
if ( ret < 0 ) {
kunit_err ( suite , " Failed to register gpio platform driver, %d \n " , ret ) ;
return ret ;
}
return 0 ;
}
static void cirrus_scodec_test_suite_exit ( struct kunit_suite * suite )
{
platform_driver_unregister ( & cirrus_scodec_test_gpio_driver ) ;
}
static const struct cirrus_scodec_test_spkid_param cirrus_scodec_test_spkid_param_cases [ ] = {
{ . num_amps = 2 , . gpios_per_amp = 1 , . num_amps_sharing = 1 } ,
{ . num_amps = 2 , . gpios_per_amp = 2 , . num_amps_sharing = 1 } ,
{ . num_amps = 2 , . gpios_per_amp = 3 , . num_amps_sharing = 1 } ,
{ . num_amps = 2 , . gpios_per_amp = 4 , . num_amps_sharing = 1 } ,
{ . num_amps = 3 , . gpios_per_amp = 1 , . num_amps_sharing = 1 } ,
{ . num_amps = 3 , . gpios_per_amp = 2 , . num_amps_sharing = 1 } ,
{ . num_amps = 3 , . gpios_per_amp = 3 , . num_amps_sharing = 1 } ,
{ . num_amps = 3 , . gpios_per_amp = 4 , . num_amps_sharing = 1 } ,
{ . num_amps = 4 , . gpios_per_amp = 1 , . num_amps_sharing = 1 } ,
{ . num_amps = 4 , . gpios_per_amp = 2 , . num_amps_sharing = 1 } ,
{ . num_amps = 4 , . gpios_per_amp = 3 , . num_amps_sharing = 1 } ,
{ . num_amps = 4 , . gpios_per_amp = 4 , . num_amps_sharing = 1 } ,
/* Same GPIO shared by all amps */
{ . num_amps = 2 , . gpios_per_amp = 1 , . num_amps_sharing = 2 } ,
{ . num_amps = 2 , . gpios_per_amp = 2 , . num_amps_sharing = 2 } ,
{ . num_amps = 2 , . gpios_per_amp = 3 , . num_amps_sharing = 2 } ,
{ . num_amps = 2 , . gpios_per_amp = 4 , . num_amps_sharing = 2 } ,
{ . num_amps = 3 , . gpios_per_amp = 1 , . num_amps_sharing = 3 } ,
{ . num_amps = 3 , . gpios_per_amp = 2 , . num_amps_sharing = 3 } ,
{ . num_amps = 3 , . gpios_per_amp = 3 , . num_amps_sharing = 3 } ,
{ . num_amps = 3 , . gpios_per_amp = 4 , . num_amps_sharing = 3 } ,
{ . num_amps = 4 , . gpios_per_amp = 1 , . num_amps_sharing = 4 } ,
{ . num_amps = 4 , . gpios_per_amp = 2 , . num_amps_sharing = 4 } ,
{ . num_amps = 4 , . gpios_per_amp = 3 , . num_amps_sharing = 4 } ,
{ . num_amps = 4 , . gpios_per_amp = 4 , . num_amps_sharing = 4 } ,
/* Two sets of shared GPIOs */
{ . num_amps = 4 , . gpios_per_amp = 1 , . num_amps_sharing = 2 } ,
{ . num_amps = 4 , . gpios_per_amp = 2 , . num_amps_sharing = 2 } ,
{ . num_amps = 4 , . gpios_per_amp = 3 , . num_amps_sharing = 2 } ,
{ . num_amps = 4 , . gpios_per_amp = 4 , . num_amps_sharing = 2 } ,
} ;
static void cirrus_scodec_test_spkid_param_desc ( const struct cirrus_scodec_test_spkid_param * param ,
char * desc )
{
snprintf ( desc , KUNIT_PARAM_DESC_SIZE , " amps:%d gpios_per_amp:%d num_amps_sharing:%d " ,
param - > num_amps , param - > gpios_per_amp , param - > num_amps_sharing ) ;
}
KUNIT_ARRAY_PARAM ( cirrus_scodec_test_spkid , cirrus_scodec_test_spkid_param_cases ,
cirrus_scodec_test_spkid_param_desc ) ;
static struct kunit_case cirrus_scodec_test_cases [ ] = {
KUNIT_CASE_PARAM ( cirrus_scodec_test_spkid_parse , cirrus_scodec_test_spkid_gen_params ) ,
KUNIT_CASE ( cirrus_scodec_test_no_spkid ) ,
{ } /* terminator */
} ;
static struct kunit_suite cirrus_scodec_test_suite = {
. name = " snd-hda-scodec-cs35l56-test " ,
. suite_init = cirrus_scodec_test_suite_init ,
. suite_exit = cirrus_scodec_test_suite_exit ,
. init = cirrus_scodec_test_case_init ,
. exit = cirrus_scodec_test_case_exit ,
. test_cases = cirrus_scodec_test_cases ,
} ;
kunit_test_suite ( cirrus_scodec_test_suite ) ;
MODULE_IMPORT_NS ( SND_HDA_CIRRUS_SCODEC ) ;
MODULE_AUTHOR ( " Richard Fitzgerald <rf@opensource.cirrus.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;