253 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 * Azoteq IQS620A PWM Generator4 *5 * Copyright (C) 2019 Jeff LaBundy <jeff@labundy.com>6 *7 * Limitations:8 * - The period is fixed to 1 ms and is generated continuously despite changes9 * to the duty cycle or enable/disable state.10 * - Changes to the duty cycle or enable/disable state take effect immediately11 * and may result in a glitch during the period in which the change is made.12 * - The device cannot generate a 0% duty cycle. For duty cycles below 1 / 25613 * ms, the output is disabled and relies upon an external pull-down resistor14 * to hold the GPIO3/LTX pin low.15 */16 17#include <linux/device.h>18#include <linux/kernel.h>19#include <linux/mfd/iqs62x.h>20#include <linux/module.h>21#include <linux/mutex.h>22#include <linux/notifier.h>23#include <linux/platform_device.h>24#include <linux/pwm.h>25#include <linux/regmap.h>26#include <linux/slab.h>27 28#define IQS620_PWR_SETTINGS 0xd229#define IQS620_PWR_SETTINGS_PWM_OUT BIT(7)30 31#define IQS620_PWM_DUTY_CYCLE 0xd832 33#define IQS620_PWM_PERIOD_NS 100000034 35struct iqs620_pwm_private {36 struct iqs62x_core *iqs62x;37 struct device *dev;38 struct notifier_block notifier;39 struct mutex lock;40 unsigned int duty_scale;41};42 43static inline struct iqs620_pwm_private *iqs620_pwm_from_chip(struct pwm_chip *chip)44{45 return pwmchip_get_drvdata(chip);46}47 48static int iqs620_pwm_init(struct iqs620_pwm_private *iqs620_pwm,49 unsigned int duty_scale)50{51 struct iqs62x_core *iqs62x = iqs620_pwm->iqs62x;52 int ret;53 54 if (!duty_scale)55 return regmap_clear_bits(iqs62x->regmap, IQS620_PWR_SETTINGS,56 IQS620_PWR_SETTINGS_PWM_OUT);57 58 ret = regmap_write(iqs62x->regmap, IQS620_PWM_DUTY_CYCLE,59 duty_scale - 1);60 if (ret)61 return ret;62 63 return regmap_set_bits(iqs62x->regmap, IQS620_PWR_SETTINGS,64 IQS620_PWR_SETTINGS_PWM_OUT);65}66 67static int iqs620_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,68 const struct pwm_state *state)69{70 struct iqs620_pwm_private *iqs620_pwm;71 unsigned int duty_cycle;72 unsigned int duty_scale;73 int ret;74 75 if (state->polarity != PWM_POLARITY_NORMAL)76 return -EINVAL;77 78 if (state->period < IQS620_PWM_PERIOD_NS)79 return -EINVAL;80 81 iqs620_pwm = iqs620_pwm_from_chip(chip);82 83 /*84 * The duty cycle generated by the device is calculated as follows:85 *86 * duty_cycle = (IQS620_PWM_DUTY_CYCLE + 1) / 256 * 1 ms87 *88 * ...where IQS620_PWM_DUTY_CYCLE is a register value between 0 and 25589 * (inclusive). Therefore the lowest duty cycle the device can generate90 * while the output is enabled is 1 / 256 ms.91 *92 * For lower duty cycles (e.g. 0), the PWM output is simply disabled to93 * allow an external pull-down resistor to hold the GPIO3/LTX pin low.94 */95 duty_cycle = min_t(u64, state->duty_cycle, IQS620_PWM_PERIOD_NS);96 duty_scale = duty_cycle * 256 / IQS620_PWM_PERIOD_NS;97 98 if (!state->enabled)99 duty_scale = 0;100 101 mutex_lock(&iqs620_pwm->lock);102 103 ret = iqs620_pwm_init(iqs620_pwm, duty_scale);104 if (!ret)105 iqs620_pwm->duty_scale = duty_scale;106 107 mutex_unlock(&iqs620_pwm->lock);108 109 return ret;110}111 112static int iqs620_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,113 struct pwm_state *state)114{115 struct iqs620_pwm_private *iqs620_pwm;116 117 iqs620_pwm = iqs620_pwm_from_chip(chip);118 119 mutex_lock(&iqs620_pwm->lock);120 121 /*122 * Since the device cannot generate a 0% duty cycle, requests to do so123 * cause subsequent calls to iqs620_pwm_get_state to report the output124 * as disabled. This is not ideal, but is the best compromise based on125 * the capabilities of the device.126 */127 state->enabled = iqs620_pwm->duty_scale > 0;128 state->duty_cycle = DIV_ROUND_UP(iqs620_pwm->duty_scale *129 IQS620_PWM_PERIOD_NS, 256);130 131 mutex_unlock(&iqs620_pwm->lock);132 133 state->period = IQS620_PWM_PERIOD_NS;134 state->polarity = PWM_POLARITY_NORMAL;135 136 return 0;137}138 139static int iqs620_pwm_notifier(struct notifier_block *notifier,140 unsigned long event_flags, void *context)141{142 struct iqs620_pwm_private *iqs620_pwm;143 int ret;144 145 if (!(event_flags & BIT(IQS62X_EVENT_SYS_RESET)))146 return NOTIFY_DONE;147 148 iqs620_pwm = container_of(notifier, struct iqs620_pwm_private,149 notifier);150 151 mutex_lock(&iqs620_pwm->lock);152 153 /*154 * The parent MFD driver already prints an error message in the event155 * of a device reset, so nothing else is printed here unless there is156 * an additional failure.157 */158 ret = iqs620_pwm_init(iqs620_pwm, iqs620_pwm->duty_scale);159 160 mutex_unlock(&iqs620_pwm->lock);161 162 if (ret) {163 dev_err(iqs620_pwm->dev,164 "Failed to re-initialize device: %d\n", ret);165 return NOTIFY_BAD;166 }167 168 return NOTIFY_OK;169}170 171static const struct pwm_ops iqs620_pwm_ops = {172 .apply = iqs620_pwm_apply,173 .get_state = iqs620_pwm_get_state,174};175 176static void iqs620_pwm_notifier_unregister(void *context)177{178 struct iqs620_pwm_private *iqs620_pwm = context;179 int ret;180 181 ret = blocking_notifier_chain_unregister(&iqs620_pwm->iqs62x->nh,182 &iqs620_pwm->notifier);183 if (ret)184 dev_err(iqs620_pwm->dev,185 "Failed to unregister notifier: %d\n", ret);186}187 188static int iqs620_pwm_probe(struct platform_device *pdev)189{190 struct iqs62x_core *iqs62x = dev_get_drvdata(pdev->dev.parent);191 struct pwm_chip *chip;192 struct iqs620_pwm_private *iqs620_pwm;193 unsigned int val;194 int ret;195 196 chip = devm_pwmchip_alloc(&pdev->dev, 1, sizeof(*iqs620_pwm));197 if (IS_ERR(chip))198 return PTR_ERR(chip);199 200 iqs620_pwm = iqs620_pwm_from_chip(chip);201 iqs620_pwm->dev = &pdev->dev;202 iqs620_pwm->iqs62x = iqs62x;203 204 ret = regmap_read(iqs62x->regmap, IQS620_PWR_SETTINGS, &val);205 if (ret)206 return ret;207 208 if (val & IQS620_PWR_SETTINGS_PWM_OUT) {209 ret = regmap_read(iqs62x->regmap, IQS620_PWM_DUTY_CYCLE, &val);210 if (ret)211 return ret;212 213 iqs620_pwm->duty_scale = val + 1;214 }215 216 chip->ops = &iqs620_pwm_ops;217 218 mutex_init(&iqs620_pwm->lock);219 220 iqs620_pwm->notifier.notifier_call = iqs620_pwm_notifier;221 ret = blocking_notifier_chain_register(&iqs620_pwm->iqs62x->nh,222 &iqs620_pwm->notifier);223 if (ret) {224 dev_err(&pdev->dev, "Failed to register notifier: %d\n", ret);225 return ret;226 }227 228 ret = devm_add_action_or_reset(&pdev->dev,229 iqs620_pwm_notifier_unregister,230 iqs620_pwm);231 if (ret)232 return ret;233 234 ret = devm_pwmchip_add(&pdev->dev, chip);235 if (ret)236 dev_err(&pdev->dev, "Failed to add device: %d\n", ret);237 238 return ret;239}240 241static struct platform_driver iqs620_pwm_platform_driver = {242 .driver = {243 .name = "iqs620a-pwm",244 },245 .probe = iqs620_pwm_probe,246};247module_platform_driver(iqs620_pwm_platform_driver);248 249MODULE_AUTHOR("Jeff LaBundy <jeff@labundy.com>");250MODULE_DESCRIPTION("Azoteq IQS620A PWM Generator");251MODULE_LICENSE("GPL");252MODULE_ALIAS("platform:iqs620a-pwm");253