2019-05-28 19:57:21 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2014-12-05 16:36:58 +03:00
/*
* Copyright ( C ) 2014 Linaro Ltd
*
* Author : Ulf Hansson < ulf . hansson @ linaro . org >
*
* Simple MMC power sequence management
*/
2015-01-29 18:00:06 +03:00
# include <linux/clk.h>
2016-04-14 16:02:16 +03:00
# include <linux/init.h>
2014-12-05 16:36:58 +03:00
# include <linux/kernel.h>
2016-04-14 16:02:16 +03:00
# include <linux/platform_device.h>
# include <linux/module.h>
2014-12-05 16:36:58 +03:00
# include <linux/slab.h>
# include <linux/device.h>
# include <linux/err.h>
2014-12-15 18:07:11 +03:00
# include <linux/gpio/consumer.h>
2016-08-07 22:02:38 +03:00
# include <linux/delay.h>
# include <linux/property.h>
2014-12-05 16:36:58 +03:00
# include <linux/mmc/host.h>
# include "pwrseq.h"
struct mmc_pwrseq_simple {
struct mmc_pwrseq pwrseq ;
2015-01-29 18:00:06 +03:00
bool clk_enabled ;
2016-08-07 22:02:38 +03:00
u32 post_power_on_delay_ms ;
2017-05-06 12:43:05 +03:00
u32 power_off_delay_us ;
2015-01-29 18:00:06 +03:00
struct clk * ext_clk ;
2015-09-21 15:14:54 +03:00
struct gpio_descs * reset_gpios ;
2014-12-05 16:36:58 +03:00
} ;
2016-04-14 16:02:14 +03:00
# define to_pwrseq_simple(p) container_of(p, struct mmc_pwrseq_simple, pwrseq)
2015-01-29 18:00:04 +03:00
static void mmc_pwrseq_simple_set_gpios_value ( struct mmc_pwrseq_simple * pwrseq ,
int value )
{
2015-09-21 15:14:54 +03:00
struct gpio_descs * reset_gpios = pwrseq - > reset_gpios ;
2015-01-29 18:00:04 +03:00
2016-01-20 18:08:03 +03:00
if ( ! IS_ERR ( reset_gpios ) ) {
2018-10-12 22:09:01 +03:00
unsigned long * values ;
2018-03-26 09:33:14 +03:00
int nvalues = reset_gpios - > ndescs ;
2015-09-21 15:14:54 +03:00
2018-10-12 22:09:01 +03:00
values = bitmap_alloc ( nvalues , GFP_KERNEL ) ;
if ( ! values )
return ;
if ( value )
bitmap_fill ( values , nvalues ) ;
else
bitmap_zero ( values , nvalues ) ;
2018-03-26 09:33:14 +03:00
2018-09-06 00:50:05 +03:00
gpiod_set_array_value_cansleep ( nvalues , reset_gpios - > desc ,
2018-09-06 00:50:07 +03:00
reset_gpios - > info , values ) ;
2018-10-12 22:09:01 +03:00
kfree ( values ) ;
2016-01-20 18:08:03 +03:00
}
2015-01-29 18:00:04 +03:00
}
2014-12-15 18:07:11 +03:00
static void mmc_pwrseq_simple_pre_power_on ( struct mmc_host * host )
{
2016-04-14 16:02:14 +03:00
struct mmc_pwrseq_simple * pwrseq = to_pwrseq_simple ( host - > pwrseq ) ;
2014-12-15 18:07:11 +03:00
2015-01-29 18:00:06 +03:00
if ( ! IS_ERR ( pwrseq - > ext_clk ) & & ! pwrseq - > clk_enabled ) {
clk_prepare_enable ( pwrseq - > ext_clk ) ;
pwrseq - > clk_enabled = true ;
}
2015-01-29 18:00:04 +03:00
mmc_pwrseq_simple_set_gpios_value ( pwrseq , 1 ) ;
2014-12-15 18:07:11 +03:00
}
static void mmc_pwrseq_simple_post_power_on ( struct mmc_host * host )
{
2016-04-14 16:02:14 +03:00
struct mmc_pwrseq_simple * pwrseq = to_pwrseq_simple ( host - > pwrseq ) ;
2014-12-15 18:07:11 +03:00
2015-01-29 18:00:04 +03:00
mmc_pwrseq_simple_set_gpios_value ( pwrseq , 0 ) ;
2016-08-07 22:02:38 +03:00
if ( pwrseq - > post_power_on_delay_ms )
msleep ( pwrseq - > post_power_on_delay_ms ) ;
2014-12-15 18:07:11 +03:00
}
2015-01-29 18:00:06 +03:00
static void mmc_pwrseq_simple_power_off ( struct mmc_host * host )
{
2016-04-14 16:02:14 +03:00
struct mmc_pwrseq_simple * pwrseq = to_pwrseq_simple ( host - > pwrseq ) ;
2015-01-29 18:00:06 +03:00
mmc_pwrseq_simple_set_gpios_value ( pwrseq , 1 ) ;
2017-05-06 12:43:05 +03:00
if ( pwrseq - > power_off_delay_us )
usleep_range ( pwrseq - > power_off_delay_us ,
2 * pwrseq - > power_off_delay_us ) ;
2015-01-29 18:00:06 +03:00
if ( ! IS_ERR ( pwrseq - > ext_clk ) & & pwrseq - > clk_enabled ) {
clk_disable_unprepare ( pwrseq - > ext_clk ) ;
pwrseq - > clk_enabled = false ;
}
}
2015-11-14 20:05:20 +03:00
static const struct mmc_pwrseq_ops mmc_pwrseq_simple_ops = {
2014-12-15 18:07:11 +03:00
. pre_power_on = mmc_pwrseq_simple_pre_power_on ,
. post_power_on = mmc_pwrseq_simple_post_power_on ,
2015-01-29 18:00:06 +03:00
. power_off = mmc_pwrseq_simple_power_off ,
2014-12-05 16:36:58 +03:00
} ;
2016-04-14 16:02:16 +03:00
static const struct of_device_id mmc_pwrseq_simple_of_match [ ] = {
{ . compatible = " mmc-pwrseq-simple " , } ,
{ /* sentinel */ } ,
} ;
MODULE_DEVICE_TABLE ( of , mmc_pwrseq_simple_of_match ) ;
static int mmc_pwrseq_simple_probe ( struct platform_device * pdev )
2014-12-05 16:36:58 +03:00
{
struct mmc_pwrseq_simple * pwrseq ;
2016-04-14 16:02:16 +03:00
struct device * dev = & pdev - > dev ;
2015-01-29 18:00:04 +03:00
2016-04-14 16:02:16 +03:00
pwrseq = devm_kzalloc ( dev , sizeof ( * pwrseq ) , GFP_KERNEL ) ;
2014-12-05 16:36:58 +03:00
if ( ! pwrseq )
2016-04-14 16:02:16 +03:00
return - ENOMEM ;
2014-12-05 16:36:58 +03:00
2016-04-14 16:02:16 +03:00
pwrseq - > ext_clk = devm_clk_get ( dev , " ext_clock " ) ;
if ( IS_ERR ( pwrseq - > ext_clk ) & & PTR_ERR ( pwrseq - > ext_clk ) ! = - ENOENT )
return PTR_ERR ( pwrseq - > ext_clk ) ;
2015-01-29 18:00:06 +03:00
2016-04-14 16:02:16 +03:00
pwrseq - > reset_gpios = devm_gpiod_get_array ( dev , " reset " ,
GPIOD_OUT_HIGH ) ;
2016-01-20 18:08:03 +03:00
if ( IS_ERR ( pwrseq - > reset_gpios ) & &
PTR_ERR ( pwrseq - > reset_gpios ) ! = - ENOENT & &
PTR_ERR ( pwrseq - > reset_gpios ) ! = - ENOSYS ) {
2016-04-14 16:02:16 +03:00
return PTR_ERR ( pwrseq - > reset_gpios ) ;
2014-12-15 18:07:11 +03:00
}
2016-08-07 22:02:38 +03:00
device_property_read_u32 ( dev , " post-power-on-delay-ms " ,
& pwrseq - > post_power_on_delay_ms ) ;
2017-05-06 12:43:05 +03:00
device_property_read_u32 ( dev , " power-off-delay-us " ,
& pwrseq - > power_off_delay_us ) ;
2016-08-07 22:02:38 +03:00
2016-04-14 16:02:16 +03:00
pwrseq - > pwrseq . dev = dev ;
2014-12-05 16:36:58 +03:00
pwrseq - > pwrseq . ops = & mmc_pwrseq_simple_ops ;
2016-04-14 16:02:16 +03:00
pwrseq - > pwrseq . owner = THIS_MODULE ;
platform_set_drvdata ( pdev , pwrseq ) ;
2014-12-05 16:36:58 +03:00
2016-04-14 16:02:16 +03:00
return mmc_pwrseq_register ( & pwrseq - > pwrseq ) ;
2014-12-05 16:36:58 +03:00
}
2016-04-14 16:02:16 +03:00
static int mmc_pwrseq_simple_remove ( struct platform_device * pdev )
{
struct mmc_pwrseq_simple * pwrseq = platform_get_drvdata ( pdev ) ;
mmc_pwrseq_unregister ( & pwrseq - > pwrseq ) ;
return 0 ;
}
static struct platform_driver mmc_pwrseq_simple_driver = {
. probe = mmc_pwrseq_simple_probe ,
. remove = mmc_pwrseq_simple_remove ,
. driver = {
. name = " pwrseq_simple " ,
. of_match_table = mmc_pwrseq_simple_of_match ,
} ,
} ;
module_platform_driver ( mmc_pwrseq_simple_driver ) ;
MODULE_LICENSE ( " GPL v2 " ) ;