@ -0,0 +1,252 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* SoC driver for Cirrus EP93xx chips .
* Copyright ( C ) 2022 Nikita Shubin < nikita . shubin @ maquefel . me >
*
* Based on a rewrite of arch / arm / mach - ep93xx / core . c
* Copyright ( C ) 2006 Lennert Buytenhek < buytenh @ wantstofly . org >
* Copyright ( C ) 2007 Herbert Valerio Riedel < hvr @ gnu . org >
*
* Thanks go to Michael Burian and Ray Lehtiniemi for their key
* role in the ep93xx Linux community .
*/
# include <linux/bits.h>
# include <linux/cleanup.h>
# include <linux/init.h>
# include <linux/mfd/syscon.h>
# include <linux/of.h>
# include <linux/of_fdt.h>
# include <linux/platform_device.h>
# include <linux/regmap.h>
# include <linux/slab.h>
# include <linux/spinlock.h>
# include <linux/sys_soc.h>
# include <linux/soc/cirrus/ep93xx.h>
# define EP93XX_SYSCON_DEVCFG 0x80
# define EP93XX_SWLOCK_MAGICK 0xaa
# define EP93XX_SYSCON_SWLOCK 0xc0
# define EP93XX_SYSCON_SYSCFG 0x9c
# define EP93XX_SYSCON_SYSCFG_REV_MASK GENMASK(31, 28)
# define EP93XX_SYSCON_SYSCFG_REV_SHIFT 28
struct ep93xx_map_info {
spinlock_t lock ;
void __iomem * base ;
struct regmap * map ;
} ;
/*
* EP93xx System Controller software locked register write
*
* Logic safeguards are included to condition the control signals for
* power connection to the matrix to prevent part damage . In addition , a
* software lock register is included that must be written with 0xAA
* before each register write to change the values of the four switch
* matrix control registers .
*/
static void ep93xx_regmap_write ( struct regmap * map , spinlock_t * lock ,
unsigned int reg , unsigned int val )
{
guard ( spinlock_irqsave ) ( lock ) ;
regmap_write ( map , EP93XX_SYSCON_SWLOCK , EP93XX_SWLOCK_MAGICK ) ;
regmap_write ( map , reg , val ) ;
}
static void ep93xx_regmap_update_bits ( struct regmap * map , spinlock_t * lock ,
unsigned int reg , unsigned int mask ,
unsigned int val )
{
guard ( spinlock_irqsave ) ( lock ) ;
regmap_write ( map , EP93XX_SYSCON_SWLOCK , EP93XX_SWLOCK_MAGICK ) ;
/* force write is required to clear swlock if no changes are made */
regmap_update_bits_base ( map , reg , mask , val , NULL , false , true ) ;
}
static void ep93xx_unregister_adev ( void * _adev )
{
struct auxiliary_device * adev = _adev ;
auxiliary_device_delete ( adev ) ;
auxiliary_device_uninit ( adev ) ;
}
static void ep93xx_adev_release ( struct device * dev )
{
struct auxiliary_device * adev = to_auxiliary_dev ( dev ) ;
struct ep93xx_regmap_adev * rdev = to_ep93xx_regmap_adev ( adev ) ;
kfree ( rdev ) ;
}
static struct auxiliary_device __init * ep93xx_adev_alloc ( struct device * parent ,
const char * name ,
struct ep93xx_map_info * info )
{
struct ep93xx_regmap_adev * rdev __free ( kfree ) = NULL ;
struct auxiliary_device * adev ;
int ret ;
rdev = kzalloc ( sizeof ( * rdev ) , GFP_KERNEL ) ;
if ( ! rdev )
return ERR_PTR ( - ENOMEM ) ;
rdev - > map = info - > map ;
rdev - > base = info - > base ;
rdev - > lock = & info - > lock ;
rdev - > write = ep93xx_regmap_write ;
rdev - > update_bits = ep93xx_regmap_update_bits ;
adev = & rdev - > adev ;
adev - > name = name ;
adev - > dev . parent = parent ;
adev - > dev . release = ep93xx_adev_release ;
ret = auxiliary_device_init ( adev ) ;
if ( ret )
return ERR_PTR ( ret ) ;
return & no_free_ptr ( rdev ) - > adev ;
}
static int __init ep93xx_controller_register ( struct device * parent , const char * name ,
struct ep93xx_map_info * info )
{
struct auxiliary_device * adev ;
int ret ;
adev = ep93xx_adev_alloc ( parent , name , info ) ;
if ( IS_ERR ( adev ) )
return PTR_ERR ( adev ) ;
ret = auxiliary_device_add ( adev ) ;
if ( ret ) {
auxiliary_device_uninit ( adev ) ;
return ret ;
}
return devm_add_action_or_reset ( parent , ep93xx_unregister_adev , adev ) ;
}
static unsigned int __init ep93xx_soc_revision ( struct regmap * map )
{
unsigned int val ;
regmap_read ( map , EP93XX_SYSCON_SYSCFG , & val ) ;
val & = EP93XX_SYSCON_SYSCFG_REV_MASK ;
val > > = EP93XX_SYSCON_SYSCFG_REV_SHIFT ;
return val ;
}
static const char __init * ep93xx_get_soc_rev ( unsigned int rev )
{
switch ( rev ) {
case EP93XX_CHIP_REV_D0 :
return " D0 " ;
case EP93XX_CHIP_REV_D1 :
return " D1 " ;
case EP93XX_CHIP_REV_E0 :
return " E0 " ;
case EP93XX_CHIP_REV_E1 :
return " E1 " ;
case EP93XX_CHIP_REV_E2 :
return " E2 " ;
default :
return " unknown " ;
}
}
static const char * pinctrl_names [ ] __initconst = {
" pinctrl-ep9301 " , /* EP93XX_9301_SOC */
" pinctrl-ep9307 " , /* EP93XX_9307_SOC */
" pinctrl-ep9312 " , /* EP93XX_9312_SOC */
} ;
static int __init ep93xx_syscon_probe ( struct platform_device * pdev )
{
enum ep93xx_soc_model model ;
struct ep93xx_map_info * map_info ;
struct soc_device_attribute * attrs ;
struct soc_device * soc_dev ;
struct device * dev = & pdev - > dev ;
struct regmap * map ;
void __iomem * base ;
unsigned int rev ;
int ret ;
model = ( enum ep93xx_soc_model ) ( uintptr_t ) device_get_match_data ( dev ) ;
map = device_node_to_regmap ( dev - > of_node ) ;
if ( IS_ERR ( map ) )
return PTR_ERR ( map ) ;
base = devm_platform_ioremap_resource ( pdev , 0 ) ;
if ( IS_ERR ( base ) )
return PTR_ERR ( base ) ;
attrs = devm_kzalloc ( dev , sizeof ( * attrs ) , GFP_KERNEL ) ;
if ( ! attrs )
return - ENOMEM ;
rev = ep93xx_soc_revision ( map ) ;
attrs - > machine = of_flat_dt_get_machine_name ( ) ;
attrs - > family = " Cirrus Logic EP93xx " ;
attrs - > revision = ep93xx_get_soc_rev ( rev ) ;
soc_dev = soc_device_register ( attrs ) ;
if ( IS_ERR ( soc_dev ) )
return PTR_ERR ( soc_dev ) ;
map_info = devm_kzalloc ( dev , sizeof ( * map_info ) , GFP_KERNEL ) ;
if ( ! map_info )
return - ENOMEM ;
spin_lock_init ( & map_info - > lock ) ;
map_info - > map = map ;
map_info - > base = base ;
ret = ep93xx_controller_register ( dev , pinctrl_names [ model ] , map_info ) ;
if ( ret )
dev_err ( dev , " registering pinctrl controller failed \n " ) ;
/*
* EP93xx SSP clock rate was doubled in version E2 . For more information
* see section 6 " 2x SSP (Synchronous Serial Port) Clock – Revision E2 only " :
* http : //www.cirrus.com/en/pubs/appNote/AN273REV4.pdf
*/
if ( rev = = EP93XX_CHIP_REV_E2 )
ret = ep93xx_controller_register ( dev , " clk-ep93xx.e2 " , map_info ) ;
else
ret = ep93xx_controller_register ( dev , " clk-ep93xx " , map_info ) ;
if ( ret )
dev_err ( dev , " registering clock controller failed \n " ) ;
ret = ep93xx_controller_register ( dev , " reset-ep93xx " , map_info ) ;
if ( ret )
dev_err ( dev , " registering reset controller failed \n " ) ;
return 0 ;
}
static const struct of_device_id ep9301_syscon_of_device_ids [ ] = {
{ . compatible = " cirrus,ep9301-syscon " , . data = ( void * ) EP93XX_9301_SOC } ,
{ . compatible = " cirrus,ep9302-syscon " , . data = ( void * ) EP93XX_9301_SOC } ,
{ . compatible = " cirrus,ep9307-syscon " , . data = ( void * ) EP93XX_9307_SOC } ,
{ . compatible = " cirrus,ep9312-syscon " , . data = ( void * ) EP93XX_9312_SOC } ,
{ . compatible = " cirrus,ep9315-syscon " , . data = ( void * ) EP93XX_9312_SOC } ,
{ /* sentinel */ }
} ;
static struct platform_driver ep9301_syscon_driver = {
. driver = {
. name = " ep9301-syscon " ,
. of_match_table = ep9301_syscon_of_device_ids ,
} ,
} ;
builtin_platform_driver_probe ( ep9301_syscon_driver , ep93xx_syscon_probe ) ;