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>
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
} ;
/* BayTrail */
2014-08-19 20:17:35 +04:00
const struct pwm_lpss_boardinfo pwm_lpss_byt_info = {
2015-10-20 16:53:05 +03:00
. clk_rate = 25000000 ,
. npwm = 1 ,
2015-11-17 12:20:15 +03:00
. base_unit_bits = 16 ,
2014-03-20 18:04:23 +04:00
} ;
2014-08-19 20:17:35 +04:00
EXPORT_SYMBOL_GPL ( pwm_lpss_byt_info ) ;
2014-03-20 18:04:23 +04:00
2014-08-19 18:18:29 +04:00
/* Braswell */
2014-08-19 20:17:35 +04:00
const struct pwm_lpss_boardinfo pwm_lpss_bsw_info = {
2015-10-20 16:53:05 +03:00
. clk_rate = 19200000 ,
. npwm = 1 ,
2015-11-17 12:20:15 +03:00
. base_unit_bits = 16 ,
2014-08-19 18:18:29 +04:00
} ;
2014-08-19 20:17:35 +04:00
EXPORT_SYMBOL_GPL ( pwm_lpss_bsw_info ) ;
2014-08-19 18:18:29 +04:00
2015-10-20 16:53:06 +03:00
/* Broxton */
const struct pwm_lpss_boardinfo pwm_lpss_bxt_info = {
. clk_rate = 19200000 ,
. npwm = 4 ,
2015-11-17 12:20:15 +03:00
. base_unit_bits = 22 ,
2015-10-20 16:53:06 +03:00
} ;
EXPORT_SYMBOL_GPL ( pwm_lpss_bxt_info ) ;
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 ) ;
}
2015-11-18 14:25:18 +03:00
static void pwm_lpss_update ( struct pwm_device * pwm )
{
pwm_lpss_write ( pwm , pwm_lpss_read ( pwm ) | PWM_SW_UPDATE ) ;
/* Give it some time to propagate */
usleep_range ( 10 , 50 ) ;
}
2014-03-20 18:04:23 +04:00
static int pwm_lpss_config ( struct pwm_chip * chip , struct pwm_device * pwm ,
int duty_ns , int period_ns )
{
struct pwm_lpss_chip * lpwm = to_lpwm ( chip ) ;
2016-06-10 15:43:21 +03:00
unsigned long long on_time_div ;
2015-11-17 12:20:15 +03:00
unsigned long c , base_unit_range ;
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
*/
base_unit_range = BIT ( lpwm - > info - > base_unit_bits ) ;
2016-06-01 17:31:12 +03:00
freq * = base_unit_range ;
2014-03-20 18:04:23 +04:00
2015-11-17 12:20:15 +03:00
c = lpwm - > info - > clk_rate ;
2014-03-20 18:04:23 +04:00
if ( ! c )
return - EINVAL ;
2016-06-01 17:31:12 +03:00
base_unit = DIV_ROUND_CLOSEST_ULL ( freq , c ) ;
2014-03-20 18:04:23 +04:00
if ( duty_ns < = 0 )
duty_ns = 1 ;
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-26 13:58:27 +03:00
pm_runtime_get_sync ( chip - > dev ) ;
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 ;
ctrl & = ~ ( ( base_unit_range - 1 ) < < PWM_BASE_UNIT_SHIFT ) ;
base_unit & = ( base_unit_range - 1 ) ;
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
2015-11-18 14:25:18 +03:00
/*
* If the PWM is already enabled we need to notify the hardware
* about the change by setting PWM_SW_UPDATE .
*/
if ( pwm_is_enabled ( pwm ) )
pwm_lpss_update ( pwm ) ;
2015-10-26 13:58:27 +03:00
pm_runtime_put ( chip - > dev ) ;
2014-03-20 18:04:23 +04:00
return 0 ;
}
static int pwm_lpss_enable ( struct pwm_chip * chip , struct pwm_device * pwm )
{
2015-10-26 13:58:27 +03:00
pm_runtime_get_sync ( chip - > dev ) ;
2015-11-18 14:25:18 +03:00
/*
* Hardware must first see PWM_SW_UPDATE before the PWM can be
* enabled .
*/
pwm_lpss_update ( pwm ) ;
2015-10-20 16:53:05 +03:00
pwm_lpss_write ( pwm , pwm_lpss_read ( pwm ) | PWM_ENABLE ) ;
2014-03-20 18:04:23 +04:00
return 0 ;
}
static void pwm_lpss_disable ( struct pwm_chip * chip , struct pwm_device * pwm )
{
2015-10-20 16:53:05 +03:00
pwm_lpss_write ( pwm , pwm_lpss_read ( pwm ) & ~ PWM_ENABLE ) ;
2015-10-26 13:58:27 +03:00
pm_runtime_put ( chip - > dev ) ;
2014-03-20 18:04:23 +04:00
}
static const struct pwm_ops pwm_lpss_ops = {
. config = pwm_lpss_config ,
. enable = pwm_lpss_enable ,
. disable = pwm_lpss_disable ,
. 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 ;
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 ;
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 " ) ;