2014-03-20 18:04:23 +04:00
/*
* Intel Low Power Subsystem PWM controller driver
*
* Copyright ( C ) 2014 , Intel Corporation
* Author : Mika Westerberg < mika . westerberg @ linux . intel . com >
* Author : Chew Kean Ho < kean . ho . chew @ intel . com >
* Author : Chang Rebecca Swee Fun < rebecca . swee . fun . chang @ intel . com >
* Author : Chew Chiau Ee < chiau . ee . chew @ intel . com >
2014-04-18 15:17:40 +04:00
* Author : Alan Cox < alan @ linux . intel . com >
2014-03-20 18:04:23 +04:00
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*/
2015-11-18 14:25:18 +03:00
# include <linux/delay.h>
2014-08-23 02:22:45 +04:00
# include <linux/io.h>
2017-01-28 18:10:42 +03:00
# include <linux/iopoll.h>
2014-03-20 18:04:23 +04:00
# include <linux/kernel.h>
# include <linux/module.h>
2015-10-26 13:58:27 +03:00
# include <linux/pm_runtime.h>
2015-11-17 12:20:15 +03:00
# include <linux/time.h>
2014-04-18 15:17:40 +04:00
2014-08-19 20:17:35 +04:00
# include "pwm-lpss.h"
2014-03-20 18:04:23 +04:00
# define PWM 0x00000000
# define PWM_ENABLE BIT(31)
# define PWM_SW_UPDATE BIT(30)
# define PWM_BASE_UNIT_SHIFT 8
# define PWM_ON_TIME_DIV_MASK 0x000000ff
2015-10-20 16:53:05 +03:00
/* Size of each PWM register space if multiple */
# define PWM_SIZE 0x400
2014-03-20 18:04:23 +04:00
struct pwm_lpss_chip {
struct pwm_chip chip ;
void __iomem * regs ;
2015-11-17 12:20:15 +03:00
const struct pwm_lpss_boardinfo * info ;
2014-04-18 15:17:40 +04:00
} ;
2014-03-20 18:04:23 +04:00
static inline struct pwm_lpss_chip * to_lpwm ( struct pwm_chip * chip )
{
return container_of ( chip , struct pwm_lpss_chip , chip ) ;
}
2015-10-20 16:53:05 +03:00
static inline u32 pwm_lpss_read ( const struct pwm_device * pwm )
{
struct pwm_lpss_chip * lpwm = to_lpwm ( pwm - > chip ) ;
return readl ( lpwm - > regs + pwm - > hwpwm * PWM_SIZE + PWM ) ;
}
static inline void pwm_lpss_write ( const struct pwm_device * pwm , u32 value )
{
struct pwm_lpss_chip * lpwm = to_lpwm ( pwm - > chip ) ;
writel ( value , lpwm - > regs + pwm - > hwpwm * PWM_SIZE + PWM ) ;
}
2017-01-28 18:10:42 +03:00
static int pwm_lpss_update ( struct pwm_device * pwm )
2015-11-18 14:25:18 +03:00
{
2017-01-28 18:10:42 +03:00
struct pwm_lpss_chip * lpwm = to_lpwm ( pwm - > chip ) ;
const void __iomem * addr = lpwm - > regs + pwm - > hwpwm * PWM_SIZE + PWM ;
const unsigned int ms = 500 * USEC_PER_MSEC ;
u32 val ;
int err ;
pwm_lpss_write ( pwm , pwm_lpss_read ( pwm ) | PWM_SW_UPDATE ) ;
2017-01-28 18:10:41 +03:00
/*
2017-01-28 18:10:42 +03:00
* PWM Configuration register has SW_UPDATE bit that is set when a new
* configuration is written to the register . The bit is automatically
* cleared at the start of the next output cycle by the IP block .
*
* If one writes a new configuration to the register while it still has
* the bit enabled , PWM may freeze . That is , while one can still write
* to the register , it won ' t have an effect . Thus , we try to sleep long
* enough that the bit gets cleared and make sure the bit is not
* enabled while we update the configuration .
2017-01-28 18:10:41 +03:00
*/
2017-01-28 18:10:42 +03:00
err = readl_poll_timeout ( addr , val , ! ( val & PWM_SW_UPDATE ) , 40 , ms ) ;
if ( err )
dev_err ( pwm - > chip - > dev , " PWM_SW_UPDATE was not cleared \n " ) ;
2017-01-28 18:10:41 +03:00
2017-01-28 18:10:42 +03:00
return err ;
}
static inline int pwm_lpss_is_updating ( struct pwm_device * pwm )
{
return ( pwm_lpss_read ( pwm ) & PWM_SW_UPDATE ) ? - EBUSY : 0 ;
2015-11-18 14:25:18 +03:00
}
2017-01-28 18:10:41 +03:00
static void pwm_lpss_prepare ( struct pwm_lpss_chip * lpwm , struct pwm_device * pwm ,
int duty_ns , int period_ns )
2014-03-20 18:04:23 +04:00
{
2016-06-10 15:43:21 +03:00
unsigned long long on_time_div ;
2016-07-04 18:36:27 +03:00
unsigned long c = lpwm - > info - > clk_rate , base_unit_range ;
2015-11-17 12:20:15 +03:00
unsigned long long base_unit , freq = NSEC_PER_SEC ;
2014-03-20 18:04:23 +04:00
u32 ctrl ;
do_div ( freq , period_ns ) ;
2015-11-17 12:20:15 +03:00
/*
* The equation is :
2016-06-01 17:31:12 +03:00
* base_unit = round ( base_unit_range * freq / c )
2015-11-17 12:20:15 +03:00
*/
2017-01-28 18:10:39 +03:00
base_unit_range = BIT ( lpwm - > info - > base_unit_bits ) - 1 ;
2016-06-01 17:31:12 +03:00
freq * = base_unit_range ;
2014-03-20 18:04:23 +04:00
2016-06-01 17:31:12 +03:00
base_unit = DIV_ROUND_CLOSEST_ULL ( freq , c ) ;
2014-03-20 18:04:23 +04:00
2016-06-10 15:43:21 +03:00
on_time_div = 255ULL * duty_ns ;
do_div ( on_time_div , period_ns ) ;
on_time_div = 255ULL - on_time_div ;
2014-03-20 18:04:23 +04:00
2015-10-20 16:53:05 +03:00
ctrl = pwm_lpss_read ( pwm ) ;
2015-11-17 12:20:15 +03:00
ctrl & = ~ PWM_ON_TIME_DIV_MASK ;
2017-01-28 18:10:39 +03:00
ctrl & = ~ ( base_unit_range < < PWM_BASE_UNIT_SHIFT ) ;
base_unit & = base_unit_range ;
2015-11-17 12:20:15 +03:00
ctrl | = ( u32 ) base_unit < < PWM_BASE_UNIT_SHIFT ;
2014-03-20 18:04:23 +04:00
ctrl | = on_time_div ;
2015-10-20 16:53:05 +03:00
pwm_lpss_write ( pwm , ctrl ) ;
2014-03-20 18:04:23 +04:00
}
2017-01-28 18:10:41 +03:00
static int pwm_lpss_apply ( struct pwm_chip * chip , struct pwm_device * pwm ,
struct pwm_state * state )
2014-03-20 18:04:23 +04:00
{
2017-01-28 18:10:41 +03:00
struct pwm_lpss_chip * lpwm = to_lpwm ( chip ) ;
2017-01-28 18:10:42 +03:00
int ret ;
2015-11-18 14:25:18 +03:00
2017-01-28 18:10:41 +03:00
if ( state - > enabled ) {
if ( ! pwm_is_enabled ( pwm ) ) {
pm_runtime_get_sync ( chip - > dev ) ;
2017-01-28 18:10:42 +03:00
ret = pwm_lpss_is_updating ( pwm ) ;
if ( ret ) {
pm_runtime_put ( chip - > dev ) ;
return ret ;
}
2017-01-28 18:10:41 +03:00
pwm_lpss_prepare ( lpwm , pwm , state - > duty_cycle , state - > period ) ;
2017-01-28 18:10:42 +03:00
ret = pwm_lpss_update ( pwm ) ;
if ( ret ) {
pm_runtime_put ( chip - > dev ) ;
return ret ;
}
2017-01-28 18:10:41 +03:00
pwm_lpss_write ( pwm , pwm_lpss_read ( pwm ) | PWM_ENABLE ) ;
} else {
2017-01-28 18:10:42 +03:00
ret = pwm_lpss_is_updating ( pwm ) ;
if ( ret )
return ret ;
2017-01-28 18:10:41 +03:00
pwm_lpss_prepare ( lpwm , pwm , state - > duty_cycle , state - > period ) ;
2017-01-28 18:10:42 +03:00
return pwm_lpss_update ( pwm ) ;
2017-01-28 18:10:41 +03:00
}
} else if ( pwm_is_enabled ( pwm ) ) {
pwm_lpss_write ( pwm , pwm_lpss_read ( pwm ) & ~ PWM_ENABLE ) ;
pm_runtime_put ( chip - > dev ) ;
}
2014-03-20 18:04:23 +04:00
2017-01-28 18:10:41 +03:00
return 0 ;
2014-03-20 18:04:23 +04:00
}
static const struct pwm_ops pwm_lpss_ops = {
2017-01-28 18:10:41 +03:00
. apply = pwm_lpss_apply ,
2014-03-20 18:04:23 +04:00
. owner = THIS_MODULE ,
} ;
2014-08-19 20:17:35 +04:00
struct pwm_lpss_chip * pwm_lpss_probe ( struct device * dev , struct resource * r ,
const struct pwm_lpss_boardinfo * info )
2014-03-20 18:04:23 +04:00
{
struct pwm_lpss_chip * lpwm ;
2016-07-04 18:36:27 +03:00
unsigned long c ;
2014-03-20 18:04:23 +04:00
int ret ;
2014-04-18 15:17:40 +04:00
lpwm = devm_kzalloc ( dev , sizeof ( * lpwm ) , GFP_KERNEL ) ;
2014-03-20 18:04:23 +04:00
if ( ! lpwm )
2014-04-18 15:17:40 +04:00
return ERR_PTR ( - ENOMEM ) ;
2014-03-20 18:04:23 +04:00
2014-04-18 15:17:40 +04:00
lpwm - > regs = devm_ioremap_resource ( dev , r ) ;
2014-03-20 18:04:23 +04:00
if ( IS_ERR ( lpwm - > regs ) )
2014-05-07 12:27:57 +04:00
return ERR_CAST ( lpwm - > regs ) ;
2014-04-18 15:17:40 +04:00
2015-11-17 12:20:15 +03:00
lpwm - > info = info ;
2016-07-04 18:36:27 +03:00
c = lpwm - > info - > clk_rate ;
if ( ! c )
return ERR_PTR ( - EINVAL ) ;
2014-04-18 15:17:40 +04:00
lpwm - > chip . dev = dev ;
2014-03-20 18:04:23 +04:00
lpwm - > chip . ops = & pwm_lpss_ops ;
lpwm - > chip . base = - 1 ;
2015-10-20 16:53:05 +03:00
lpwm - > chip . npwm = info - > npwm ;
2014-03-20 18:04:23 +04:00
ret = pwmchip_add ( & lpwm - > chip ) ;
if ( ret ) {
2014-04-18 15:17:40 +04:00
dev_err ( dev , " failed to add PWM chip: %d \n " , ret ) ;
return ERR_PTR ( ret ) ;
2014-03-20 18:04:23 +04:00
}
2014-04-18 15:17:40 +04:00
return lpwm ;
2014-03-20 18:04:23 +04:00
}
2014-08-19 20:17:35 +04:00
EXPORT_SYMBOL_GPL ( pwm_lpss_probe ) ;
2014-03-20 18:04:23 +04:00
2014-08-19 20:17:35 +04:00
int pwm_lpss_remove ( struct pwm_lpss_chip * lpwm )
2014-03-20 18:04:23 +04:00
{
return pwmchip_remove ( & lpwm - > chip ) ;
}
2014-08-19 20:17:35 +04:00
EXPORT_SYMBOL_GPL ( pwm_lpss_remove ) ;
2014-03-20 18:04:23 +04:00
MODULE_DESCRIPTION ( " PWM driver for Intel LPSS " ) ;
MODULE_AUTHOR ( " Mika Westerberg <mika.westerberg@linux.intel.com> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;