2019-05-20 17:04:21 +03:00
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
2016-08-22 18:36:30 +03:00
/*
2019-06-12 22:59:11 +03:00
* PWM controller driver for Amlogic Meson SoCs .
*
* This PWM is only a set of Gates , Dividers and Counters :
* PWM output is achieved by calculating a clock that permits calculating
* two periods ( low and high ) . The counter then has to be set to switch after
* N cycles for the first half period .
* The hardware has no " polarity " setting . This driver reverses the period
* cycles ( the low length is inverted with the high length ) for
* PWM_POLARITY_INVERSED . This means that . get_state cannot read the polarity
* from the hardware .
* Setting the duty cycle will disable and re - enable the PWM output .
* Disabling the PWM stops the output immediately ( without waiting for the
* current period to complete first ) .
*
* The public S912 ( GXM ) datasheet contains some documentation for this PWM
* controller starting on page 543 :
* https : //dl.khadas.com/Hardware/VIM2/Datasheet/S912_Datasheet_V0.220170314publicversion-Wesion.pdf
* An updated version of this IP block is found in S922X ( G12B ) SoCs . The
* datasheet contains the description for this IP block revision starting at
* page 1084 :
* https : //dn.odroid.com/S922X/ODROID-N2/Datasheet/S922X_Public_Datasheet_V0.2.pdf
*
2016-08-22 18:36:30 +03:00
* Copyright ( c ) 2016 BayLibre , SAS .
* Author : Neil Armstrong < narmstrong @ baylibre . com >
* Copyright ( C ) 2014 Amlogic , Inc .
*/
2019-06-12 22:59:00 +03:00
# include <linux/bitfield.h>
# include <linux/bits.h>
2016-08-22 18:36:30 +03:00
# include <linux/clk.h>
# include <linux/clk-provider.h>
# include <linux/err.h>
# include <linux/io.h>
# include <linux/kernel.h>
2019-06-12 22:59:07 +03:00
# include <linux/math64.h>
2016-08-22 18:36:30 +03:00
# include <linux/module.h>
# include <linux/of.h>
# include <linux/of_device.h>
# include <linux/platform_device.h>
# include <linux/pwm.h>
# include <linux/slab.h>
# include <linux/spinlock.h>
# define REG_PWM_A 0x0
# define REG_PWM_B 0x4
2019-06-12 22:59:00 +03:00
# define PWM_LOW_MASK GENMASK(15, 0)
# define PWM_HIGH_MASK GENMASK(31, 16)
2016-08-22 18:36:30 +03:00
# define REG_MISC_AB 0x8
# define MISC_B_CLK_EN BIT(23)
# define MISC_A_CLK_EN BIT(15)
# define MISC_CLK_DIV_MASK 0x7f
# define MISC_B_CLK_DIV_SHIFT 16
# define MISC_A_CLK_DIV_SHIFT 8
# define MISC_B_CLK_SEL_SHIFT 6
# define MISC_A_CLK_SEL_SHIFT 4
2019-06-12 22:59:01 +03:00
# define MISC_CLK_SEL_MASK 0x3
2016-08-22 18:36:30 +03:00
# define MISC_B_EN BIT(1)
# define MISC_A_EN BIT(0)
2019-06-12 22:59:04 +03:00
# define MESON_NUM_PWMS 2
2019-06-12 22:59:05 +03:00
static struct meson_pwm_channel_data {
u8 reg_offset ;
u8 clk_sel_shift ;
u8 clk_div_shift ;
u32 clk_en_mask ;
u32 pwm_en_mask ;
} meson_pwm_per_channel_data [ MESON_NUM_PWMS ] = {
{
. reg_offset = REG_PWM_A ,
. clk_sel_shift = MISC_A_CLK_SEL_SHIFT ,
. clk_div_shift = MISC_A_CLK_DIV_SHIFT ,
. clk_en_mask = MISC_A_CLK_EN ,
. pwm_en_mask = MISC_A_EN ,
} ,
{
. reg_offset = REG_PWM_B ,
. clk_sel_shift = MISC_B_CLK_SEL_SHIFT ,
. clk_div_shift = MISC_B_CLK_DIV_SHIFT ,
. clk_en_mask = MISC_B_CLK_EN ,
. pwm_en_mask = MISC_B_EN ,
}
2016-08-22 18:36:30 +03:00
} ;
struct meson_pwm_channel {
unsigned int hi ;
unsigned int lo ;
u8 pre_div ;
struct clk * clk_parent ;
struct clk_mux mux ;
struct clk * clk ;
} ;
struct meson_pwm_data {
const char * const * parent_names ;
2017-06-08 15:24:15 +03:00
unsigned int num_parents ;
2016-08-22 18:36:30 +03:00
} ;
struct meson_pwm {
struct pwm_chip chip ;
const struct meson_pwm_data * data ;
2019-06-12 22:59:04 +03:00
struct meson_pwm_channel channels [ MESON_NUM_PWMS ] ;
2016-08-22 18:36:30 +03:00
void __iomem * base ;
2019-04-01 20:57:48 +03:00
/*
* Protects register ( write ) access to the REG_MISC_AB register
* that is shared between the two PWMs .
*/
2016-08-22 18:36:30 +03:00
spinlock_t lock ;
} ;
static inline struct meson_pwm * to_meson_pwm ( struct pwm_chip * chip )
{
return container_of ( chip , struct meson_pwm , chip ) ;
}
static int meson_pwm_request ( struct pwm_chip * chip , struct pwm_device * pwm )
{
2019-06-12 22:59:06 +03:00
struct meson_pwm * meson = to_meson_pwm ( chip ) ;
struct meson_pwm_channel * channel ;
2016-08-22 18:36:30 +03:00
struct device * dev = chip - > dev ;
int err ;
2019-06-12 22:59:06 +03:00
channel = pwm_get_chip_data ( pwm ) ;
if ( channel )
return 0 ;
channel = & meson - > channels [ pwm - > hwpwm ] ;
2016-08-22 18:36:30 +03:00
if ( channel - > clk_parent ) {
err = clk_set_parent ( channel - > clk , channel - > clk_parent ) ;
if ( err < 0 ) {
dev_err ( dev , " failed to set parent %s for %s: %d \n " ,
__clk_get_name ( channel - > clk_parent ) ,
__clk_get_name ( channel - > clk ) , err ) ;
return err ;
}
}
err = clk_prepare_enable ( channel - > clk ) ;
if ( err < 0 ) {
dev_err ( dev , " failed to enable clock %s: %d \n " ,
__clk_get_name ( channel - > clk ) , err ) ;
return err ;
}
2019-06-12 22:59:06 +03:00
return pwm_set_chip_data ( pwm , channel ) ;
2016-08-22 18:36:30 +03:00
}
static void meson_pwm_free ( struct pwm_chip * chip , struct pwm_device * pwm )
{
struct meson_pwm_channel * channel = pwm_get_chip_data ( pwm ) ;
if ( channel )
clk_disable_unprepare ( channel - > clk ) ;
}
2019-06-12 22:59:03 +03:00
static int meson_pwm_calc ( struct meson_pwm * meson , struct pwm_device * pwm ,
2019-08-24 18:37:07 +03:00
const struct pwm_state * state )
2016-08-22 18:36:30 +03:00
{
2019-06-12 22:59:03 +03:00
struct meson_pwm_channel * channel = pwm_get_chip_data ( pwm ) ;
2019-06-12 22:59:02 +03:00
unsigned int duty , period , pre_div , cnt , duty_cnt ;
2017-06-08 15:24:16 +03:00
unsigned long fin_freq = - 1 ;
2016-08-22 18:36:30 +03:00
2019-06-12 22:59:02 +03:00
duty = state - > duty_cycle ;
period = state - > period ;
if ( state - > polarity = = PWM_POLARITY_INVERSED )
2016-08-22 18:36:30 +03:00
duty = period - duty ;
fin_freq = clk_get_rate ( channel - > clk ) ;
if ( fin_freq = = 0 ) {
dev_err ( meson - > chip . dev , " invalid source clock frequency \n " ) ;
return - EINVAL ;
}
dev_dbg ( meson - > chip . dev , " fin_freq: %lu Hz \n " , fin_freq ) ;
2019-06-12 22:59:07 +03:00
pre_div = div64_u64 ( fin_freq * ( u64 ) period , NSEC_PER_SEC * 0xffffLL ) ;
2019-04-01 21:18:16 +03:00
if ( pre_div > MISC_CLK_DIV_MASK ) {
2016-08-22 18:36:30 +03:00
dev_err ( meson - > chip . dev , " unable to get period pre_div \n " ) ;
return - EINVAL ;
}
2019-06-12 22:59:07 +03:00
cnt = div64_u64 ( fin_freq * ( u64 ) period , NSEC_PER_SEC * ( pre_div + 1 ) ) ;
if ( cnt > 0xffff ) {
dev_err ( meson - > chip . dev , " unable to get period cnt \n " ) ;
return - EINVAL ;
}
2016-08-22 18:36:30 +03:00
dev_dbg ( meson - > chip . dev , " period=%u pre_div=%u cnt=%u \n " , period ,
pre_div , cnt ) ;
if ( duty = = period ) {
channel - > pre_div = pre_div ;
channel - > hi = cnt ;
channel - > lo = 0 ;
} else if ( duty = = 0 ) {
channel - > pre_div = pre_div ;
channel - > hi = 0 ;
channel - > lo = cnt ;
} else {
/* Then check is we can have the duty with the same pre_div */
2019-06-12 22:59:07 +03:00
duty_cnt = div64_u64 ( fin_freq * ( u64 ) duty ,
NSEC_PER_SEC * ( pre_div + 1 ) ) ;
2016-08-22 18:36:30 +03:00
if ( duty_cnt > 0xffff ) {
dev_err ( meson - > chip . dev , " unable to get duty cycle \n " ) ;
return - EINVAL ;
}
dev_dbg ( meson - > chip . dev , " duty=%u pre_div=%u duty_cnt=%u \n " ,
duty , pre_div , duty_cnt ) ;
channel - > pre_div = pre_div ;
channel - > hi = duty_cnt ;
channel - > lo = cnt - duty_cnt ;
}
return 0 ;
}
2019-06-12 22:58:58 +03:00
static void meson_pwm_enable ( struct meson_pwm * meson , struct pwm_device * pwm )
2016-08-22 18:36:30 +03:00
{
2019-06-12 22:58:58 +03:00
struct meson_pwm_channel * channel = pwm_get_chip_data ( pwm ) ;
2019-06-12 22:59:05 +03:00
struct meson_pwm_channel_data * channel_data ;
2019-04-01 20:57:48 +03:00
unsigned long flags ;
2019-06-12 22:59:05 +03:00
u32 value ;
2016-08-22 18:36:30 +03:00
2019-06-12 22:59:05 +03:00
channel_data = & meson_pwm_per_channel_data [ pwm - > hwpwm ] ;
2016-08-22 18:36:30 +03:00
2019-04-01 20:57:48 +03:00
spin_lock_irqsave ( & meson - > lock , flags ) ;
2016-08-22 18:36:30 +03:00
value = readl ( meson - > base + REG_MISC_AB ) ;
2019-06-12 22:59:05 +03:00
value & = ~ ( MISC_CLK_DIV_MASK < < channel_data - > clk_div_shift ) ;
value | = channel - > pre_div < < channel_data - > clk_div_shift ;
value | = channel_data - > clk_en_mask ;
2016-08-22 18:36:30 +03:00
writel ( value , meson - > base + REG_MISC_AB ) ;
2019-06-12 22:59:00 +03:00
value = FIELD_PREP ( PWM_HIGH_MASK , channel - > hi ) |
FIELD_PREP ( PWM_LOW_MASK , channel - > lo ) ;
2019-06-12 22:59:05 +03:00
writel ( value , meson - > base + channel_data - > reg_offset ) ;
2016-08-22 18:36:30 +03:00
value = readl ( meson - > base + REG_MISC_AB ) ;
2019-06-12 22:59:05 +03:00
value | = channel_data - > pwm_en_mask ;
2016-08-22 18:36:30 +03:00
writel ( value , meson - > base + REG_MISC_AB ) ;
2019-04-01 20:57:48 +03:00
spin_unlock_irqrestore ( & meson - > lock , flags ) ;
2016-08-22 18:36:30 +03:00
}
2019-06-12 22:58:58 +03:00
static void meson_pwm_disable ( struct meson_pwm * meson , struct pwm_device * pwm )
2016-08-22 18:36:30 +03:00
{
2019-04-01 20:57:48 +03:00
unsigned long flags ;
2019-06-12 22:59:05 +03:00
u32 value ;
2016-08-22 18:36:30 +03:00
2019-04-01 20:57:48 +03:00
spin_lock_irqsave ( & meson - > lock , flags ) ;
2016-08-22 18:36:30 +03:00
value = readl ( meson - > base + REG_MISC_AB ) ;
2019-06-12 22:59:05 +03:00
value & = ~ meson_pwm_per_channel_data [ pwm - > hwpwm ] . pwm_en_mask ;
2016-08-22 18:36:30 +03:00
writel ( value , meson - > base + REG_MISC_AB ) ;
2019-04-01 20:57:48 +03:00
spin_unlock_irqrestore ( & meson - > lock , flags ) ;
2016-08-22 18:36:30 +03:00
}
static int meson_pwm_apply ( struct pwm_chip * chip , struct pwm_device * pwm ,
2019-08-24 18:37:07 +03:00
const struct pwm_state * state )
2016-08-22 18:36:30 +03:00
{
2019-06-12 22:59:10 +03:00
struct meson_pwm_channel * channel = pwm_get_chip_data ( pwm ) ;
2016-08-22 18:36:30 +03:00
struct meson_pwm * meson = to_meson_pwm ( chip ) ;
int err = 0 ;
if ( ! state )
return - EINVAL ;
if ( ! state - > enabled ) {
2019-06-12 22:59:10 +03:00
if ( state - > polarity = = PWM_POLARITY_INVERSED ) {
/*
* This IP block revision doesn ' t have an " always high "
* setting which we can use for " inverted disabled " .
* Instead we achieve this using the same settings
* that we use a pre_div of 0 ( to get the shortest
* possible duration for one " count " ) and
* " period == duty_cycle " . This results in a signal
* which is LOW for one " count " , while being HIGH for
* the rest of the ( so the signal is HIGH for slightly
* less than 100 % of the period , but this is the best
* we can achieve ) .
*/
channel - > pre_div = 0 ;
channel - > hi = ~ 0 ;
channel - > lo = 0 ;
meson_pwm_enable ( meson , pwm ) ;
} else {
meson_pwm_disable ( meson , pwm ) ;
}
2019-06-12 22:59:09 +03:00
} else {
2019-06-12 22:59:03 +03:00
err = meson_pwm_calc ( meson , pwm , state ) ;
2016-08-22 18:36:30 +03:00
if ( err < 0 )
2019-04-01 20:57:48 +03:00
return err ;
2016-08-22 18:36:30 +03:00
2019-06-12 22:58:58 +03:00
meson_pwm_enable ( meson , pwm ) ;
2016-08-22 18:36:30 +03:00
}
2019-04-01 20:57:48 +03:00
return 0 ;
2016-08-22 18:36:30 +03:00
}
2019-06-12 22:59:08 +03:00
static unsigned int meson_pwm_cnt_to_ns ( struct pwm_chip * chip ,
struct pwm_device * pwm , u32 cnt )
{
struct meson_pwm * meson = to_meson_pwm ( chip ) ;
struct meson_pwm_channel * channel ;
unsigned long fin_freq ;
u32 fin_ns ;
/* to_meson_pwm() can only be used after .get_state() is called */
channel = & meson - > channels [ pwm - > hwpwm ] ;
fin_freq = clk_get_rate ( channel - > clk ) ;
if ( fin_freq = = 0 )
return 0 ;
fin_ns = div_u64 ( NSEC_PER_SEC , fin_freq ) ;
return cnt * fin_ns * ( channel - > pre_div + 1 ) ;
}
2016-08-22 18:36:30 +03:00
static void meson_pwm_get_state ( struct pwm_chip * chip , struct pwm_device * pwm ,
struct pwm_state * state )
{
struct meson_pwm * meson = to_meson_pwm ( chip ) ;
2019-06-12 22:59:08 +03:00
struct meson_pwm_channel_data * channel_data ;
struct meson_pwm_channel * channel ;
u32 value , tmp ;
2016-08-22 18:36:30 +03:00
if ( ! state )
return ;
2019-06-12 22:59:08 +03:00
channel = & meson - > channels [ pwm - > hwpwm ] ;
channel_data = & meson_pwm_per_channel_data [ pwm - > hwpwm ] ;
2016-08-22 18:36:30 +03:00
value = readl ( meson - > base + REG_MISC_AB ) ;
2019-06-12 22:59:08 +03:00
tmp = channel_data - > pwm_en_mask | channel_data - > clk_en_mask ;
state - > enabled = ( value & tmp ) = = tmp ;
tmp = value > > channel_data - > clk_div_shift ;
channel - > pre_div = FIELD_GET ( MISC_CLK_DIV_MASK , tmp ) ;
value = readl ( meson - > base + channel_data - > reg_offset ) ;
channel - > lo = FIELD_GET ( PWM_LOW_MASK , value ) ;
channel - > hi = FIELD_GET ( PWM_HIGH_MASK , value ) ;
if ( channel - > lo = = 0 ) {
state - > period = meson_pwm_cnt_to_ns ( chip , pwm , channel - > hi ) ;
state - > duty_cycle = state - > period ;
} else if ( channel - > lo > = channel - > hi ) {
state - > period = meson_pwm_cnt_to_ns ( chip , pwm ,
channel - > lo + channel - > hi ) ;
state - > duty_cycle = meson_pwm_cnt_to_ns ( chip , pwm ,
channel - > hi ) ;
} else {
state - > period = 0 ;
state - > duty_cycle = 0 ;
}
2016-08-22 18:36:30 +03:00
}
static const struct pwm_ops meson_pwm_ops = {
. request = meson_pwm_request ,
. free = meson_pwm_free ,
. apply = meson_pwm_apply ,
. get_state = meson_pwm_get_state ,
. owner = THIS_MODULE ,
} ;
static const char * const pwm_meson8b_parent_names [ ] = {
" xtal " , " vid_pll " , " fclk_div4 " , " fclk_div3 "
} ;
static const struct meson_pwm_data pwm_meson8b_data = {
. parent_names = pwm_meson8b_parent_names ,
2017-06-08 15:24:15 +03:00
. num_parents = ARRAY_SIZE ( pwm_meson8b_parent_names ) ,
2016-08-22 18:36:30 +03:00
} ;
static const char * const pwm_gxbb_parent_names [ ] = {
" xtal " , " hdmi_pll " , " fclk_div4 " , " fclk_div3 "
} ;
static const struct meson_pwm_data pwm_gxbb_data = {
. parent_names = pwm_gxbb_parent_names ,
2017-06-08 15:24:15 +03:00
. num_parents = ARRAY_SIZE ( pwm_gxbb_parent_names ) ,
} ;
/*
* Only the 2 first inputs of the GXBB AO PWMs are valid
* The last 2 are grounded
*/
static const char * const pwm_gxbb_ao_parent_names [ ] = {
" xtal " , " clk81 "
} ;
static const struct meson_pwm_data pwm_gxbb_ao_data = {
. parent_names = pwm_gxbb_ao_parent_names ,
. num_parents = ARRAY_SIZE ( pwm_gxbb_ao_parent_names ) ,
2016-08-22 18:36:30 +03:00
} ;
2017-12-04 09:00:17 +03:00
static const char * const pwm_axg_ee_parent_names [ ] = {
" xtal " , " fclk_div5 " , " fclk_div4 " , " fclk_div3 "
} ;
static const struct meson_pwm_data pwm_axg_ee_data = {
. parent_names = pwm_axg_ee_parent_names ,
. num_parents = ARRAY_SIZE ( pwm_axg_ee_parent_names ) ,
} ;
static const char * const pwm_axg_ao_parent_names [ ] = {
" aoclk81 " , " xtal " , " fclk_div4 " , " fclk_div5 "
} ;
static const struct meson_pwm_data pwm_axg_ao_data = {
. parent_names = pwm_axg_ao_parent_names ,
. num_parents = ARRAY_SIZE ( pwm_axg_ao_parent_names ) ,
} ;
2019-06-20 17:46:55 +03:00
static const char * const pwm_g12a_ao_ab_parent_names [ ] = {
" xtal " , " aoclk81 " , " fclk_div4 " , " fclk_div5 "
} ;
static const struct meson_pwm_data pwm_g12a_ao_ab_data = {
. parent_names = pwm_g12a_ao_ab_parent_names ,
. num_parents = ARRAY_SIZE ( pwm_g12a_ao_ab_parent_names ) ,
} ;
2019-04-23 16:36:45 +03:00
static const char * const pwm_g12a_ao_cd_parent_names [ ] = {
2019-06-20 17:46:55 +03:00
" xtal " , " aoclk81 " ,
2019-04-23 16:36:45 +03:00
} ;
static const struct meson_pwm_data pwm_g12a_ao_cd_data = {
. parent_names = pwm_g12a_ao_cd_parent_names ,
. num_parents = ARRAY_SIZE ( pwm_g12a_ao_cd_parent_names ) ,
} ;
static const char * const pwm_g12a_ee_parent_names [ ] = {
" xtal " , " hdmi_pll " , " fclk_div4 " , " fclk_div3 "
} ;
static const struct meson_pwm_data pwm_g12a_ee_data = {
. parent_names = pwm_g12a_ee_parent_names ,
. num_parents = ARRAY_SIZE ( pwm_g12a_ee_parent_names ) ,
} ;
2016-08-22 18:36:30 +03:00
static const struct of_device_id meson_pwm_matches [ ] = {
2017-06-08 15:24:15 +03:00
{
. compatible = " amlogic,meson8b-pwm " ,
. data = & pwm_meson8b_data
} ,
{
. compatible = " amlogic,meson-gxbb-pwm " ,
. data = & pwm_gxbb_data
} ,
{
. compatible = " amlogic,meson-gxbb-ao-pwm " ,
. data = & pwm_gxbb_ao_data
} ,
2017-12-04 09:00:17 +03:00
{
. compatible = " amlogic,meson-axg-ee-pwm " ,
. data = & pwm_axg_ee_data
} ,
{
. compatible = " amlogic,meson-axg-ao-pwm " ,
. data = & pwm_axg_ao_data
} ,
2019-04-23 16:36:45 +03:00
{
. compatible = " amlogic,meson-g12a-ee-pwm " ,
. data = & pwm_g12a_ee_data
} ,
{
. compatible = " amlogic,meson-g12a-ao-pwm-ab " ,
2019-06-20 17:46:55 +03:00
. data = & pwm_g12a_ao_ab_data
2019-04-23 16:36:45 +03:00
} ,
{
. compatible = " amlogic,meson-g12a-ao-pwm-cd " ,
. data = & pwm_g12a_ao_cd_data
} ,
2016-08-22 18:36:30 +03:00
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , meson_pwm_matches ) ;
2019-06-12 22:59:04 +03:00
static int meson_pwm_init_channels ( struct meson_pwm * meson )
2016-08-22 18:36:30 +03:00
{
struct device * dev = meson - > chip . dev ;
struct clk_init_data init ;
unsigned int i ;
char name [ 255 ] ;
int err ;
for ( i = 0 ; i < meson - > chip . npwm ; i + + ) {
2019-06-12 22:59:04 +03:00
struct meson_pwm_channel * channel = & meson - > channels [ i ] ;
2016-08-22 18:36:30 +03:00
2018-08-01 13:57:20 +03:00
snprintf ( name , sizeof ( name ) , " %s#mux%u " , dev_name ( dev ) , i ) ;
2016-08-22 18:36:30 +03:00
init . name = name ;
init . ops = & clk_mux_ops ;
2019-04-25 20:57:37 +03:00
init . flags = 0 ;
2016-08-22 18:36:30 +03:00
init . parent_names = meson - > data - > parent_names ;
2017-06-08 15:24:15 +03:00
init . num_parents = meson - > data - > num_parents ;
2016-08-22 18:36:30 +03:00
channel - > mux . reg = meson - > base + REG_MISC_AB ;
2019-06-12 22:59:05 +03:00
channel - > mux . shift =
meson_pwm_per_channel_data [ i ] . clk_sel_shift ;
2019-06-12 22:59:01 +03:00
channel - > mux . mask = MISC_CLK_SEL_MASK ;
2016-08-22 18:36:30 +03:00
channel - > mux . flags = 0 ;
channel - > mux . lock = & meson - > lock ;
channel - > mux . table = NULL ;
channel - > mux . hw . init = & init ;
channel - > clk = devm_clk_register ( dev , & channel - > mux . hw ) ;
if ( IS_ERR ( channel - > clk ) ) {
err = PTR_ERR ( channel - > clk ) ;
dev_err ( dev , " failed to register %s: %d \n " , name , err ) ;
return err ;
}
snprintf ( name , sizeof ( name ) , " clkin%u " , i ) ;
2019-06-12 22:58:59 +03:00
channel - > clk_parent = devm_clk_get_optional ( dev , name ) ;
if ( IS_ERR ( channel - > clk_parent ) )
return PTR_ERR ( channel - > clk_parent ) ;
2016-08-22 18:36:30 +03:00
}
return 0 ;
}
static int meson_pwm_probe ( struct platform_device * pdev )
{
struct meson_pwm * meson ;
struct resource * regs ;
int err ;
meson = devm_kzalloc ( & pdev - > dev , sizeof ( * meson ) , GFP_KERNEL ) ;
if ( ! meson )
return - ENOMEM ;
regs = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
meson - > base = devm_ioremap_resource ( & pdev - > dev , regs ) ;
if ( IS_ERR ( meson - > base ) )
return PTR_ERR ( meson - > base ) ;
2016-09-10 04:55:49 +03:00
spin_lock_init ( & meson - > lock ) ;
2016-08-22 18:36:30 +03:00
meson - > chip . dev = & pdev - > dev ;
meson - > chip . ops = & meson_pwm_ops ;
meson - > chip . base = - 1 ;
2019-06-12 22:59:04 +03:00
meson - > chip . npwm = MESON_NUM_PWMS ;
2016-08-22 18:36:30 +03:00
meson - > chip . of_xlate = of_pwm_xlate_with_flags ;
meson - > chip . of_pwm_n_cells = 3 ;
meson - > data = of_device_get_match_data ( & pdev - > dev ) ;
2019-06-12 22:59:04 +03:00
err = meson_pwm_init_channels ( meson ) ;
2016-08-22 18:36:30 +03:00
if ( err < 0 )
return err ;
err = pwmchip_add ( & meson - > chip ) ;
if ( err < 0 ) {
dev_err ( & pdev - > dev , " failed to register PWM chip: %d \n " , err ) ;
return err ;
}
platform_set_drvdata ( pdev , meson ) ;
return 0 ;
}
static int meson_pwm_remove ( struct platform_device * pdev )
{
struct meson_pwm * meson = platform_get_drvdata ( pdev ) ;
return pwmchip_remove ( & meson - > chip ) ;
}
static struct platform_driver meson_pwm_driver = {
. driver = {
. name = " meson-pwm " ,
. of_match_table = meson_pwm_matches ,
} ,
. probe = meson_pwm_probe ,
. remove = meson_pwm_remove ,
} ;
module_platform_driver ( meson_pwm_driver ) ;
MODULE_DESCRIPTION ( " Amlogic Meson PWM Generator driver " ) ;
MODULE_AUTHOR ( " Neil Armstrong <narmstrong@baylibre.com> " ) ;
MODULE_LICENSE ( " Dual BSD/GPL " ) ;