diff mbox series

[v2,2/3] pwm: msm-vibrator: new driver for the vibrator found on various MSM SOCs

Message ID 20180926235112.25710-3-masneyb@onstation.org (mailing list archive)
State Not Applicable, archived
Delegated to: Andy Gross
Headers show
Series treewide: add vibrator support for various MSM SOCs | expand

Commit Message

Brian Masney Sept. 26, 2018, 11:51 p.m. UTC
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 <masneyb@onstation.org>
---
 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 mbox series

Patch

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 <masneyb@onstation.org>
+ *
+ * Based on qcom,pwm-vibrator.c from:
+ * Copyright (c) 2018 Jonathan Marek <jonathan@marek.ca>
+ *
+ * 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 <masneyb@onstation.org>");
+MODULE_DESCRIPTION("Qualcomm PWM driver for the MSM vibrator");
+MODULE_LICENSE("GPL");