2019-01-07 22:53:52 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* simple driver for PWM ( Pulse Width Modulator ) controller
*
* Derived from pxa PWM driver by eric miao < eric . miao @ marvell . com >
*/
# include <linux/bitfield.h>
# include <linux/bitops.h>
# include <linux/clk.h>
# include <linux/delay.h>
# include <linux/err.h>
# include <linux/io.h>
# include <linux/kernel.h>
# 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>
# define MX1_PWMC 0x00 /* PWM Control Register */
# define MX1_PWMS 0x04 /* PWM Sample Register */
# define MX1_PWMP 0x08 /* PWM Period Register */
# define MX1_PWMC_EN BIT(4)
struct pwm_imx1_chip {
struct clk * clk_ipg ;
struct clk * clk_per ;
void __iomem * mmio_base ;
struct pwm_chip chip ;
} ;
# define to_pwm_imx1_chip(chip) container_of(chip, struct pwm_imx1_chip, chip)
static int pwm_imx1_clk_prepare_enable ( struct pwm_chip * chip )
{
struct pwm_imx1_chip * imx = to_pwm_imx1_chip ( chip ) ;
int ret ;
ret = clk_prepare_enable ( imx - > clk_ipg ) ;
if ( ret )
return ret ;
ret = clk_prepare_enable ( imx - > clk_per ) ;
if ( ret ) {
clk_disable_unprepare ( imx - > clk_ipg ) ;
return ret ;
}
return 0 ;
}
static void pwm_imx1_clk_disable_unprepare ( struct pwm_chip * chip )
{
struct pwm_imx1_chip * imx = to_pwm_imx1_chip ( chip ) ;
clk_disable_unprepare ( imx - > clk_per ) ;
clk_disable_unprepare ( imx - > clk_ipg ) ;
}
static int pwm_imx1_config ( struct pwm_chip * chip ,
struct pwm_device * pwm , int duty_ns , int period_ns )
{
struct pwm_imx1_chip * imx = to_pwm_imx1_chip ( chip ) ;
u32 max , p ;
/*
* The PWM subsystem allows for exact frequencies . However ,
* I cannot connect a scope on my device to the PWM line and
* thus cannot provide the program the PWM controller
* exactly . Instead , I ' m relying on the fact that the
* Bootloader ( u - boot or WinCE + haret ) has programmed the PWM
* function group already . So I ' ll just modify the PWM sample
* register to follow the ratio of duty_ns vs . period_ns
* accordingly .
*
* This is good enough for programming the brightness of
* the LCD backlight .
*
* The real implementation would divide PERCLK [ 0 ] first by
* both the prescaler ( / 1 . . / 128 ) and then by CLKSEL
* ( / 2 . . / 16 ) .
*/
max = readl ( imx - > mmio_base + MX1_PWMP ) ;
p = max * duty_ns / period_ns ;
writel ( max - p , imx - > mmio_base + MX1_PWMS ) ;
return 0 ;
}
static int pwm_imx1_enable ( struct pwm_chip * chip , struct pwm_device * pwm )
{
struct pwm_imx1_chip * imx = to_pwm_imx1_chip ( chip ) ;
u32 value ;
int ret ;
ret = pwm_imx1_clk_prepare_enable ( chip ) ;
if ( ret < 0 )
return ret ;
value = readl ( imx - > mmio_base + MX1_PWMC ) ;
value | = MX1_PWMC_EN ;
writel ( value , imx - > mmio_base + MX1_PWMC ) ;
return 0 ;
}
static void pwm_imx1_disable ( struct pwm_chip * chip , struct pwm_device * pwm )
{
struct pwm_imx1_chip * imx = to_pwm_imx1_chip ( chip ) ;
u32 value ;
value = readl ( imx - > mmio_base + MX1_PWMC ) ;
value & = ~ MX1_PWMC_EN ;
writel ( value , imx - > mmio_base + MX1_PWMC ) ;
pwm_imx1_clk_disable_unprepare ( chip ) ;
}
static const struct pwm_ops pwm_imx1_ops = {
. enable = pwm_imx1_enable ,
. disable = pwm_imx1_disable ,
. config = pwm_imx1_config ,
. owner = THIS_MODULE ,
} ;
static const struct of_device_id pwm_imx1_dt_ids [ ] = {
{ . compatible = " fsl,imx1-pwm " , } ,
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( of , pwm_imx1_dt_ids ) ;
static int pwm_imx1_probe ( struct platform_device * pdev )
{
struct pwm_imx1_chip * imx ;
imx = devm_kzalloc ( & pdev - > dev , sizeof ( * imx ) , GFP_KERNEL ) ;
if ( ! imx )
return - ENOMEM ;
imx - > clk_ipg = devm_clk_get ( & pdev - > dev , " ipg " ) ;
2020-08-11 09:24:32 +03:00
if ( IS_ERR ( imx - > clk_ipg ) )
return dev_err_probe ( & pdev - > dev , PTR_ERR ( imx - > clk_ipg ) ,
" getting ipg clock failed \n " ) ;
2019-01-07 22:53:52 +03:00
imx - > clk_per = devm_clk_get ( & pdev - > dev , " per " ) ;
2020-08-11 09:24:32 +03:00
if ( IS_ERR ( imx - > clk_per ) )
return dev_err_probe ( & pdev - > dev , PTR_ERR ( imx - > clk_per ) ,
" failed to get peripheral clock \n " ) ;
2019-01-07 22:53:52 +03:00
imx - > chip . ops = & pwm_imx1_ops ;
imx - > chip . dev = & pdev - > dev ;
imx - > chip . npwm = 1 ;
2019-12-29 11:05:58 +03:00
imx - > mmio_base = devm_platform_ioremap_resource ( pdev , 0 ) ;
2019-01-07 22:53:52 +03:00
if ( IS_ERR ( imx - > mmio_base ) )
return PTR_ERR ( imx - > mmio_base ) ;
2021-05-25 09:35:28 +03:00
return devm_pwmchip_add ( & pdev - > dev , & imx - > chip ) ;
2019-01-07 22:53:52 +03:00
}
static struct platform_driver pwm_imx1_driver = {
. driver = {
. name = " pwm-imx1 " ,
. of_match_table = pwm_imx1_dt_ids ,
} ,
. probe = pwm_imx1_probe ,
} ;
module_platform_driver ( pwm_imx1_driver ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_AUTHOR ( " Sascha Hauer <s.hauer@pengutronix.de> " ) ;