This patch adds a new PWM vibrator driver that supports various
Qualcomm MSM SOCs. It is intended to be wired into the pwm-vibra driver
in the input/misc/ subsystem via device tree. Driver was tested on a
LG Nexus 5 (hammerhead) phone.

Signed-off-by: Brian Masney <[email protected]>
---
 drivers/pwm/Kconfig            |   9 ++
 drivers/pwm/Makefile           |   1 +
 drivers/pwm/pwm-msm-vibrator.c | 227 +++++++++++++++++++++++++++++++++
 3 files changed, 237 insertions(+)
 create mode 100644 drivers/pwm/pwm-msm-vibrator.c

diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index 504d252716f2..49dbcfd60f50 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -273,6 +273,15 @@ config PWM_MESON
          To compile this driver as a module, choose M here: the module
          will be called pwm-meson.
 
+config PWM_MSM_VIBRATOR
+       tristate "Qualcomm PWM driver for the MSM vibrator"
+       help
+         PWM support for the vibrator that is found on various Qualcomm
+          MSM SOCs.
+
+         To compile this driver as a module, choose M here: the module
+         will be called pwm-msm-vibrator.
+
 config PWM_MTK_DISP
        tristate "MediaTek display PWM driver"
        depends on ARCH_MEDIATEK || COMPILE_TEST
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 9c676a0dadf5..60fd9f9b0fbb 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -27,6 +27,7 @@ obj-$(CONFIG_PWM_LPSS_PCI)    += pwm-lpss-pci.o
 obj-$(CONFIG_PWM_LPSS_PLATFORM)        += pwm-lpss-platform.o
 obj-$(CONFIG_PWM_MESON)                += pwm-meson.o
 obj-$(CONFIG_PWM_MEDIATEK)     += pwm-mediatek.o
+obj-$(CONFIG_PWM_MSM_VIBRATOR) += pwm-msm-vibrator.o
 obj-$(CONFIG_PWM_MTK_DISP)     += pwm-mtk-disp.o
 obj-$(CONFIG_PWM_MXS)          += pwm-mxs.o
 obj-$(CONFIG_PWM_OMAP_DMTIMER) += pwm-omap-dmtimer.o
diff --git a/drivers/pwm/pwm-msm-vibrator.c b/drivers/pwm/pwm-msm-vibrator.c
new file mode 100644
index 000000000000..00ec40885eb4
--- /dev/null
+++ b/drivers/pwm/pwm-msm-vibrator.c
@@ -0,0 +1,227 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Qualcomm PWM driver for the MSM vibrator
+ *
+ * Copyright (c) 2018 Brian Masney <[email protected]>
+ *
+ * Based on qcom,pwm-vibrator.c from:
+ * Copyright (c) 2018 Jonathan Marek <[email protected]>
+ *
+ * Based on msm_pwm_vibrator.c from downstream Android sources:
+ * Copyright (C) 2009-2014 LGE, Inc.
+ */
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/regulator/consumer.h>
+
+#define REG_CMD_RCGR           0x00
+#define REG_CFG_RCGR           0x04
+#define REG_M                  0x08
+#define REG_N                  0x0C
+#define REG_D                  0x10
+#define REG_CBCR               0x24
+#define MMSS_CC_M_DEFAULT      1
+
+struct msm_vibra_pwm {
+       struct pwm_chip chip;
+       struct device *dev;
+       void __iomem *base;
+       struct regulator *vcc;
+       struct clk *clk;
+       struct gpio_desc *enable_gpio;
+       bool enabled;
+};
+
+#define to_msm_vibra_pwm(pwm_chip) \
+       container_of(pwm_chip, struct msm_vibra_pwm, chip)
+
+#define msm_vibra_pwm_write(msm_pwm, offset, value) \
+       writel((value), (void __iomem *)((msm_pwm)->base + (offset)))
+
+static int msm_vibra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
+                               int duty_ns, int period_ns)
+{
+       struct msm_vibra_pwm *msm_pwm = to_msm_vibra_pwm(chip);
+       int d_reg_val;
+
+       d_reg_val = 127 - (((duty_ns / 1000) * 126) / (period_ns / 1000));
+
+       msm_vibra_pwm_write(msm_pwm, REG_CFG_RCGR,
+                           (2 << 12) | /* dual edge mode */
+                           (0 << 8) |  /* cxo */
+                           (7 << 0));
+       msm_vibra_pwm_write(msm_pwm, REG_M, 1);
+       msm_vibra_pwm_write(msm_pwm, REG_N, 128);
+       msm_vibra_pwm_write(msm_pwm, REG_D, d_reg_val);
+       msm_vibra_pwm_write(msm_pwm, REG_CMD_RCGR, 1);
+       msm_vibra_pwm_write(msm_pwm, REG_CBCR, 1);
+
+       return 0;
+}
+
+static int msm_vibra_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+       struct msm_vibra_pwm *msm_pwm = to_msm_vibra_pwm(chip);
+       int ret;
+
+       ret = clk_set_rate(msm_pwm->clk, 24000);
+       if (ret) {
+               dev_err(msm_pwm->dev, "Failed to set clock rate: %d\n", ret);
+               return ret;
+       }
+
+       ret = clk_prepare_enable(msm_pwm->clk);
+       if (ret) {
+               dev_err(msm_pwm->dev, "Failed to enable clock: %d\n", ret);
+               return ret;
+       }
+
+       ret = regulator_enable(msm_pwm->vcc);
+       if (ret) {
+               dev_err(msm_pwm->dev, "Failed to enable regulator: %d\n", ret);
+               return ret;
+       }
+
+       gpiod_set_value_cansleep(msm_pwm->enable_gpio, 1);
+       msm_pwm->enabled = true;
+
+       return 0;
+}
+
+static void msm_vibra_pwm_disable(struct pwm_chip *chip, struct pwm_device 
*pwm)
+{
+       struct msm_vibra_pwm *msm_pwm = to_msm_vibra_pwm(chip);
+
+       gpiod_set_value_cansleep(msm_pwm->enable_gpio, 0);
+       regulator_disable(msm_pwm->vcc);
+       clk_disable_unprepare(msm_pwm->clk);
+       msm_pwm->enabled = false;
+}
+
+static const struct pwm_ops msm_vibra_pwm_ops = {
+       .config = msm_vibra_pwm_config,
+       .enable = msm_vibra_pwm_enable,
+       .disable = msm_vibra_pwm_disable,
+       .owner = THIS_MODULE,
+};
+
+static int msm_vibra_pwm_probe(struct platform_device *pdev)
+{
+       struct msm_vibra_pwm *msm_pwm;
+       struct resource *res;
+
+       msm_pwm = devm_kzalloc(&pdev->dev, sizeof(*msm_pwm), GFP_KERNEL);
+       if (!msm_pwm)
+               return -ENOMEM;
+
+       msm_pwm->dev = &pdev->dev;
+
+       msm_pwm->vcc = devm_regulator_get(&pdev->dev, "vcc");
+       if (IS_ERR(msm_pwm->vcc)) {
+               if (PTR_ERR(msm_pwm->vcc) != -EPROBE_DEFER)
+                       dev_err(&pdev->dev, "Failed to get regulator: %ld\n",
+                               PTR_ERR(msm_pwm->vcc));
+               return PTR_ERR(msm_pwm->vcc);
+       }
+
+       msm_pwm->enable_gpio = devm_gpiod_get_optional(&pdev->dev, "enable",
+                                                      GPIOD_OUT_LOW);
+       if (IS_ERR(msm_pwm->enable_gpio)) {
+               dev_err(&pdev->dev, "Failed to get enable gpio: %ld\n",
+                       PTR_ERR(msm_pwm->enable_gpio));
+               return PTR_ERR(msm_pwm->enable_gpio);
+       }
+
+       msm_pwm->clk = devm_clk_get(&pdev->dev, "pwm");
+       if (IS_ERR(msm_pwm->clk)) {
+               dev_err(&pdev->dev, "Failed to lookup pwm clock: %ld\n",
+                       PTR_ERR(msm_pwm->clk));
+               return PTR_ERR(msm_pwm->clk);
+       }
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!res) {
+               dev_err(&pdev->dev, "Failed to get platform resource\n");
+               return -ENODEV;
+       }
+
+       msm_pwm->base = devm_ioremap(&pdev->dev, res->start,
+                                    resource_size(res));
+       if (IS_ERR(msm_pwm->base)) {
+               dev_err(&pdev->dev, "Failed to iomap resource: %ld\n",
+                       PTR_ERR(msm_pwm->base));
+               return PTR_ERR(msm_pwm->base);
+       }
+
+       msm_pwm->chip.dev = &pdev->dev;
+       msm_pwm->chip.ops = &msm_vibra_pwm_ops;
+       msm_pwm->enabled = false;
+       msm_pwm->chip.npwm = 1;
+       msm_pwm->chip.of_xlate = of_pwm_xlate_with_flags;
+       msm_pwm->chip.of_pwm_n_cells = 3;
+
+       platform_set_drvdata(pdev, msm_pwm);
+
+       return pwmchip_add(&msm_pwm->chip);
+}
+
+static __maybe_unused int msm_vibra_pwm_suspend(struct device *dev)
+{
+       struct msm_vibra_pwm *msm_pwm = dev_get_drvdata(dev);
+       struct pwm_device *pwm = msm_pwm->chip.pwms;
+
+       if (msm_pwm->enabled)
+               msm_vibra_pwm_disable(&msm_pwm->chip, pwm);
+
+       return 0;
+}
+
+static __maybe_unused int msm_vibra_pwm_resume(struct device *dev)
+{
+       return 0;
+}
+
+static int msm_vibra_pwm_remove(struct platform_device *pdev)
+{
+       struct msm_vibra_pwm *msm_pwm = platform_get_drvdata(pdev);
+       struct pwm_device *pwm = msm_pwm->chip.pwms;
+
+       if (msm_pwm->enabled)
+               msm_vibra_pwm_disable(&msm_pwm->chip, pwm);
+
+       return pwmchip_remove(&msm_pwm->chip);
+}
+
+static const struct of_device_id msm_vibra_pwm_of_match[] = {
+       { .compatible = "qcom,msm8226-pwm-vibrator" },
+       { .compatible = "qcom,msm8974-pwm-vibrator" },
+       { }
+};
+MODULE_DEVICE_TABLE(of, msm_vibra_pwm_of_match);
+
+static const struct dev_pm_ops msm_vibra_pwm_pm_ops = {
+       .suspend = msm_vibra_pwm_suspend,
+       .resume  = msm_vibra_pwm_resume,
+};
+
+static struct platform_driver msm_vibra_pwm_driver = {
+       .driver = {
+               .name = "pwm-msm-vibrator",
+               .of_match_table = msm_vibra_pwm_of_match,
+               .pm = &msm_vibra_pwm_pm_ops,
+       },
+       .probe = msm_vibra_pwm_probe,
+       .remove = msm_vibra_pwm_remove,
+};
+module_platform_driver(msm_vibra_pwm_driver);
+
+MODULE_AUTHOR("Brian Masney <[email protected]>");
+MODULE_DESCRIPTION("Qualcomm PWM driver for the MSM vibrator");
+MODULE_LICENSE("GPL");
-- 
2.17.1

Reply via email to