linux/drivers/input/misc/pwm-beeper.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 *  Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de>
   4 *  PWM beeper driver
   5 */
   6
   7#include <linux/input.h>
   8#include <linux/regulator/consumer.h>
   9#include <linux/module.h>
  10#include <linux/kernel.h>
  11#include <linux/of.h>
  12#include <linux/platform_device.h>
  13#include <linux/property.h>
  14#include <linux/pwm.h>
  15#include <linux/slab.h>
  16#include <linux/workqueue.h>
  17
  18struct pwm_beeper {
  19        struct input_dev *input;
  20        struct pwm_device *pwm;
  21        struct regulator *amplifier;
  22        struct work_struct work;
  23        unsigned long period;
  24        unsigned int bell_frequency;
  25        bool suspended;
  26        bool amplifier_on;
  27};
  28
  29#define HZ_TO_NANOSECONDS(x) (1000000000UL/(x))
  30
  31static int pwm_beeper_on(struct pwm_beeper *beeper, unsigned long period)
  32{
  33        struct pwm_state state;
  34        int error;
  35
  36        pwm_get_state(beeper->pwm, &state);
  37
  38        state.enabled = true;
  39        state.period = period;
  40        pwm_set_relative_duty_cycle(&state, 50, 100);
  41
  42        error = pwm_apply_state(beeper->pwm, &state);
  43        if (error)
  44                return error;
  45
  46        if (!beeper->amplifier_on) {
  47                error = regulator_enable(beeper->amplifier);
  48                if (error) {
  49                        pwm_disable(beeper->pwm);
  50                        return error;
  51                }
  52
  53                beeper->amplifier_on = true;
  54        }
  55
  56        return 0;
  57}
  58
  59static void pwm_beeper_off(struct pwm_beeper *beeper)
  60{
  61        if (beeper->amplifier_on) {
  62                regulator_disable(beeper->amplifier);
  63                beeper->amplifier_on = false;
  64        }
  65
  66        pwm_disable(beeper->pwm);
  67}
  68
  69static void pwm_beeper_work(struct work_struct *work)
  70{
  71        struct pwm_beeper *beeper = container_of(work, struct pwm_beeper, work);
  72        unsigned long period = READ_ONCE(beeper->period);
  73
  74        if (period)
  75                pwm_beeper_on(beeper, period);
  76        else
  77                pwm_beeper_off(beeper);
  78}
  79
  80static int pwm_beeper_event(struct input_dev *input,
  81                            unsigned int type, unsigned int code, int value)
  82{
  83        struct pwm_beeper *beeper = input_get_drvdata(input);
  84
  85        if (type != EV_SND || value < 0)
  86                return -EINVAL;
  87
  88        switch (code) {
  89        case SND_BELL:
  90                value = value ? beeper->bell_frequency : 0;
  91                break;
  92        case SND_TONE:
  93                break;
  94        default:
  95                return -EINVAL;
  96        }
  97
  98        if (value == 0)
  99                beeper->period = 0;
 100        else
 101                beeper->period = HZ_TO_NANOSECONDS(value);
 102
 103        if (!beeper->suspended)
 104                schedule_work(&beeper->work);
 105
 106        return 0;
 107}
 108
 109static void pwm_beeper_stop(struct pwm_beeper *beeper)
 110{
 111        cancel_work_sync(&beeper->work);
 112        pwm_beeper_off(beeper);
 113}
 114
 115static void pwm_beeper_close(struct input_dev *input)
 116{
 117        struct pwm_beeper *beeper = input_get_drvdata(input);
 118
 119        pwm_beeper_stop(beeper);
 120}
 121
 122static int pwm_beeper_probe(struct platform_device *pdev)
 123{
 124        struct device *dev = &pdev->dev;
 125        struct pwm_beeper *beeper;
 126        struct pwm_state state;
 127        u32 bell_frequency;
 128        int error;
 129
 130        beeper = devm_kzalloc(dev, sizeof(*beeper), GFP_KERNEL);
 131        if (!beeper)
 132                return -ENOMEM;
 133
 134        beeper->pwm = devm_pwm_get(dev, NULL);
 135        if (IS_ERR(beeper->pwm)) {
 136                error = PTR_ERR(beeper->pwm);
 137                if (error != -EPROBE_DEFER)
 138                        dev_err(dev, "Failed to request PWM device: %d\n",
 139                                error);
 140                return error;
 141        }
 142
 143        /* Sync up PWM state and ensure it is off. */
 144        pwm_init_state(beeper->pwm, &state);
 145        state.enabled = false;
 146        error = pwm_apply_state(beeper->pwm, &state);
 147        if (error) {
 148                dev_err(dev, "failed to apply initial PWM state: %d\n",
 149                        error);
 150                return error;
 151        }
 152
 153        beeper->amplifier = devm_regulator_get(dev, "amp");
 154        if (IS_ERR(beeper->amplifier)) {
 155                error = PTR_ERR(beeper->amplifier);
 156                if (error != -EPROBE_DEFER)
 157                        dev_err(dev, "Failed to get 'amp' regulator: %d\n",
 158                                error);
 159                return error;
 160        }
 161
 162        INIT_WORK(&beeper->work, pwm_beeper_work);
 163
 164        error = device_property_read_u32(dev, "beeper-hz", &bell_frequency);
 165        if (error) {
 166                bell_frequency = 1000;
 167                dev_dbg(dev,
 168                        "failed to parse 'beeper-hz' property, using default: %uHz\n",
 169                        bell_frequency);
 170        }
 171
 172        beeper->bell_frequency = bell_frequency;
 173
 174        beeper->input = devm_input_allocate_device(dev);
 175        if (!beeper->input) {
 176                dev_err(dev, "Failed to allocate input device\n");
 177                return -ENOMEM;
 178        }
 179
 180        beeper->input->name = "pwm-beeper";
 181        beeper->input->phys = "pwm/input0";
 182        beeper->input->id.bustype = BUS_HOST;
 183        beeper->input->id.vendor = 0x001f;
 184        beeper->input->id.product = 0x0001;
 185        beeper->input->id.version = 0x0100;
 186
 187        input_set_capability(beeper->input, EV_SND, SND_TONE);
 188        input_set_capability(beeper->input, EV_SND, SND_BELL);
 189
 190        beeper->input->event = pwm_beeper_event;
 191        beeper->input->close = pwm_beeper_close;
 192
 193        input_set_drvdata(beeper->input, beeper);
 194
 195        error = input_register_device(beeper->input);
 196        if (error) {
 197                dev_err(dev, "Failed to register input device: %d\n", error);
 198                return error;
 199        }
 200
 201        platform_set_drvdata(pdev, beeper);
 202
 203        return 0;
 204}
 205
 206static int __maybe_unused pwm_beeper_suspend(struct device *dev)
 207{
 208        struct pwm_beeper *beeper = dev_get_drvdata(dev);
 209
 210        /*
 211         * Spinlock is taken here is not to protect write to
 212         * beeper->suspended, but to ensure that pwm_beeper_event
 213         * does not re-submit work once flag is set.
 214         */
 215        spin_lock_irq(&beeper->input->event_lock);
 216        beeper->suspended = true;
 217        spin_unlock_irq(&beeper->input->event_lock);
 218
 219        pwm_beeper_stop(beeper);
 220
 221        return 0;
 222}
 223
 224static int __maybe_unused pwm_beeper_resume(struct device *dev)
 225{
 226        struct pwm_beeper *beeper = dev_get_drvdata(dev);
 227
 228        spin_lock_irq(&beeper->input->event_lock);
 229        beeper->suspended = false;
 230        spin_unlock_irq(&beeper->input->event_lock);
 231
 232        /* Let worker figure out if we should resume beeping */
 233        schedule_work(&beeper->work);
 234
 235        return 0;
 236}
 237
 238static SIMPLE_DEV_PM_OPS(pwm_beeper_pm_ops,
 239                         pwm_beeper_suspend, pwm_beeper_resume);
 240
 241#ifdef CONFIG_OF
 242static const struct of_device_id pwm_beeper_match[] = {
 243        { .compatible = "pwm-beeper", },
 244        { },
 245};
 246MODULE_DEVICE_TABLE(of, pwm_beeper_match);
 247#endif
 248
 249static struct platform_driver pwm_beeper_driver = {
 250        .probe  = pwm_beeper_probe,
 251        .driver = {
 252                .name   = "pwm-beeper",
 253                .pm     = &pwm_beeper_pm_ops,
 254                .of_match_table = of_match_ptr(pwm_beeper_match),
 255        },
 256};
 257module_platform_driver(pwm_beeper_driver);
 258
 259MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
 260MODULE_DESCRIPTION("PWM beeper driver");
 261MODULE_LICENSE("GPL");
 262MODULE_ALIAS("platform:pwm-beeper");
 263