1/* 2 * Intel Low Power Subsystem PWM controller driver 3 * 4 * Copyright (C) 2014, Intel Corporation 5 * Author: Mika Westerberg <mika.westerberg@linux.intel.com> 6 * Author: Chew Kean Ho <kean.ho.chew@intel.com> 7 * Author: Chang Rebecca Swee Fun <rebecca.swee.fun.chang@intel.com> 8 * Author: Chew Chiau Ee <chiau.ee.chew@intel.com> 9 * Author: Alan Cox <alan@linux.intel.com> 10 * 11 * This program is free software; you can redistribute it and/or modify 12 * it under the terms of the GNU General Public License version 2 as 13 * published by the Free Software Foundation. 14 */ 15 16#include <linux/io.h> 17#include <linux/kernel.h> 18#include <linux/module.h> 19 20#include "pwm-lpss.h" 21 22#define PWM 0x00000000 23#define PWM_ENABLE BIT(31) 24#define PWM_SW_UPDATE BIT(30) 25#define PWM_BASE_UNIT_SHIFT 8 26#define PWM_BASE_UNIT_MASK 0x00ffff00 27#define PWM_ON_TIME_DIV_MASK 0x000000ff 28#define PWM_DIVISION_CORRECTION 0x2 29#define PWM_LIMIT (0x8000 + PWM_DIVISION_CORRECTION) 30#define NSECS_PER_SEC 1000000000UL 31 32struct pwm_lpss_chip { 33 struct pwm_chip chip; 34 void __iomem *regs; 35 unsigned long clk_rate; 36}; 37 38/* BayTrail */ 39const struct pwm_lpss_boardinfo pwm_lpss_byt_info = { 40 .clk_rate = 25000000 41}; 42EXPORT_SYMBOL_GPL(pwm_lpss_byt_info); 43 44/* Braswell */ 45const struct pwm_lpss_boardinfo pwm_lpss_bsw_info = { 46 .clk_rate = 19200000 47}; 48EXPORT_SYMBOL_GPL(pwm_lpss_bsw_info); 49 50static inline struct pwm_lpss_chip *to_lpwm(struct pwm_chip *chip) 51{ 52 return container_of(chip, struct pwm_lpss_chip, chip); 53} 54 55static int pwm_lpss_config(struct pwm_chip *chip, struct pwm_device *pwm, 56 int duty_ns, int period_ns) 57{ 58 struct pwm_lpss_chip *lpwm = to_lpwm(chip); 59 u8 on_time_div; 60 unsigned long c; 61 unsigned long long base_unit, freq = NSECS_PER_SEC; 62 u32 ctrl; 63 64 do_div(freq, period_ns); 65 66 /* The equation is: base_unit = ((freq / c) * 65536) + correction */ 67 base_unit = freq * 65536; 68 69 c = lpwm->clk_rate; 70 if (!c) 71 return -EINVAL; 72 73 do_div(base_unit, c); 74 base_unit += PWM_DIVISION_CORRECTION; 75 if (base_unit > PWM_LIMIT) 76 return -EINVAL; 77 78 if (duty_ns <= 0) 79 duty_ns = 1; 80 on_time_div = 255 - (255 * duty_ns / period_ns); 81 82 ctrl = readl(lpwm->regs + PWM); 83 ctrl &= ~(PWM_BASE_UNIT_MASK | PWM_ON_TIME_DIV_MASK); 84 ctrl |= (u16) base_unit << PWM_BASE_UNIT_SHIFT; 85 ctrl |= on_time_div; 86 /* request PWM to update on next cycle */ 87 ctrl |= PWM_SW_UPDATE; 88 writel(ctrl, lpwm->regs + PWM); 89 90 return 0; 91} 92 93static int pwm_lpss_enable(struct pwm_chip *chip, struct pwm_device *pwm) 94{ 95 struct pwm_lpss_chip *lpwm = to_lpwm(chip); 96 u32 ctrl; 97 98 ctrl = readl(lpwm->regs + PWM); 99 writel(ctrl | PWM_ENABLE, lpwm->regs + PWM); 100 101 return 0; 102} 103 104static void pwm_lpss_disable(struct pwm_chip *chip, struct pwm_device *pwm) 105{ 106 struct pwm_lpss_chip *lpwm = to_lpwm(chip); 107 u32 ctrl; 108 109 ctrl = readl(lpwm->regs + PWM); 110 writel(ctrl & ~PWM_ENABLE, lpwm->regs + PWM); 111} 112 113static const struct pwm_ops pwm_lpss_ops = { 114 .config = pwm_lpss_config, 115 .enable = pwm_lpss_enable, 116 .disable = pwm_lpss_disable, 117 .owner = THIS_MODULE, 118}; 119 120struct pwm_lpss_chip *pwm_lpss_probe(struct device *dev, struct resource *r, 121 const struct pwm_lpss_boardinfo *info) 122{ 123 struct pwm_lpss_chip *lpwm; 124 int ret; 125 126 lpwm = devm_kzalloc(dev, sizeof(*lpwm), GFP_KERNEL); 127 if (!lpwm) 128 return ERR_PTR(-ENOMEM); 129 130 lpwm->regs = devm_ioremap_resource(dev, r); 131 if (IS_ERR(lpwm->regs)) 132 return ERR_CAST(lpwm->regs); 133 134 lpwm->clk_rate = info->clk_rate; 135 lpwm->chip.dev = dev; 136 lpwm->chip.ops = &pwm_lpss_ops; 137 lpwm->chip.base = -1; 138 lpwm->chip.npwm = 1; 139 140 ret = pwmchip_add(&lpwm->chip); 141 if (ret) { 142 dev_err(dev, "failed to add PWM chip: %d\n", ret); 143 return ERR_PTR(ret); 144 } 145 146 return lpwm; 147} 148EXPORT_SYMBOL_GPL(pwm_lpss_probe); 149 150int pwm_lpss_remove(struct pwm_lpss_chip *lpwm) 151{ 152 u32 ctrl; 153 154 ctrl = readl(lpwm->regs + PWM); 155 writel(ctrl & ~PWM_ENABLE, lpwm->regs + PWM); 156 157 return pwmchip_remove(&lpwm->chip); 158} 159EXPORT_SYMBOL_GPL(pwm_lpss_remove); 160 161MODULE_DESCRIPTION("PWM driver for Intel LPSS"); 162MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>"); 163MODULE_LICENSE("GPL v2"); 164