Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Linux mxs power and regulator patches
From d4437fcdf16cfc74bb2388d02cf754461eef42fd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=B6rg=20Krause?= <joerg.krause@embedded.rocks>
Date: Tue, 22 Mar 2016 10:35:05 +0100
Subject: [PATCH 100/102] mxs: add power
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Jörg Krause <joerg.krause@embedded.rocks>
---
drivers/power/Kconfig | 10 ++
drivers/power/Makefile | 1 +
drivers/power/mxs-power-debug.c | 388 ++++++++++++++++++++++++++++++++++++++++
drivers/power/mxs-power.c | 277 ++++++++++++++++++++++++++++
include/linux/power/mxs_power.h | 99 ++++++++++
5 files changed, 775 insertions(+)
create mode 100644 drivers/power/mxs-power-debug.c
create mode 100644 drivers/power/mxs-power.c
create mode 100644 include/linux/power/mxs_power.h
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index 237d7aa..5fc5f93 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -43,6 +43,16 @@ config MAX8925_POWER
Say Y here to enable support for the battery charger in the Maxim
MAX8925 PMIC.
+config MXS_POWER
+ tristate "Freescale MXS power subsystem support"
+ depends on ARCH_MXS || COMPILE_TEST
+ depends on IIO
+ depends on MXS_LRADC
+ help
+ Say Y here to enable support for the Freescale i.MX23/i.MX28
+ power subsystem. This is a requirement to get access to on-chip
+ regulators, battery charger and many more.
+
config WM831X_BACKUP
tristate "WM831X backup battery charger support"
depends on MFD_WM831X
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index b656638..04af411 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -11,6 +11,7 @@ obj-$(CONFIG_PDA_POWER) += pda_power.o
obj-$(CONFIG_APM_POWER) += apm_power.o
obj-$(CONFIG_AXP20X_POWER) += axp20x_usb_power.o
obj-$(CONFIG_MAX8925_POWER) += max8925_power.o
+obj-$(CONFIG_MXS_POWER) += mxs-power.o mxs-power-debug.o
obj-$(CONFIG_WM831X_BACKUP) += wm831x_backup.o
obj-$(CONFIG_WM831X_POWER) += wm831x_power.o
obj-$(CONFIG_WM8350_POWER) += wm8350_power.o
diff --git a/drivers/power/mxs-power-debug.c b/drivers/power/mxs-power-debug.c
new file mode 100644
index 0000000..c5c2724
--- /dev/null
+++ b/drivers/power/mxs-power-debug.c
@@ -0,0 +1,388 @@
+/*
+ * Freescale MXS power debug
+ *
+ * Copyright (c) 2015 Stefan Wahren
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/debugfs.h>
+#include <linux/power/mxs_power.h>
+#include <linux/regmap.h>
+#include <linux/seq_file.h>
+#include <linux/types.h>
+
+#ifdef CONFIG_DEBUG_FS
+
+static int
+mxs_power_ctrl_mxs_show(struct seq_file *s, void *what)
+{
+ struct mxs_power_data *data = s->private;
+ u32 value;
+ int ret = regmap_read(data->regmap, HW_POWER_5VCTRL, &value);
+
+ if (ret)
+ return ret;
+
+ /* only MX23 */
+ seq_printf(s, "CLKGATE: %x\n", (value >> 30) & 1);
+
+ seq_printf(s, "PSWITCH_MID_TRAN: %x\n", (value >> 27) & 1);
+ seq_printf(s, "DCDC4P2_BO_IRQ: %x\n", (value >> 24) & 1);
+ seq_printf(s, "ENIRQ_DCDC4P2_BO: %x\n", (value >> 23) & 1);
+ seq_printf(s, "VDD5V_DROOP_IRQ: %x\n", (value >> 22) & 1);
+ seq_printf(s, "ENIRQ_VDD5V_DROOP: %x\n", (value >> 21) & 1);
+ seq_printf(s, "PSWITCH_IRQ: %x\n", (value >> 20) & 1);
+ seq_printf(s, "PSWITCH_IRQ_SRC: %x\n", (value >> 19) & 1);
+ seq_printf(s, "POLARITY_PSWITCH: %x\n", (value >> 18) & 1);
+ seq_printf(s, "ENIRQ_PSWITCH: %x\n", (value >> 17) & 1);
+ seq_printf(s, "POLARITY_DC_OK: %x\n", (value >> 16) & 1);
+ seq_printf(s, "DC_OK_IRQ: %x\n", (value >> 15) & 1);
+ seq_printf(s, "ENIRQ_DC_OK: %x\n", (value >> 14) & 1);
+ seq_printf(s, "BATT_BO_IRQ: %x\n", (value >> 13) & 1);
+ seq_printf(s, "ENIRQBATT_BO: %x\n", (value >> 12) & 1);
+ seq_printf(s, "VDDIO_BO_IRQ: %x\n", (value >> 11) & 1);
+ seq_printf(s, "ENIRQ_VDDIO_BO: %x\n", (value >> 10) & 1);
+ seq_printf(s, "VDDA_BO_IRQ: %x\n", (value >> 9) & 1);
+ seq_printf(s, "ENIRQ_VDDA_BO: %x\n", (value >> 8) & 1);
+ seq_printf(s, "VDDD_BO_IRQ: %x\n", (value >> 7) & 1);
+ seq_printf(s, "ENIRQ_VDDD_BO: %x\n", (value >> 6) & 1);
+ seq_printf(s, "POLARITY_VBUSVALID: %x\n", (value >> 5) & 1);
+ seq_printf(s, "VBUSVALID_IRQ: %x\n", (value >> 4) & 1);
+ seq_printf(s, "ENIRQ_VBUS_VALID: %x\n", (value >> 3) & 1);
+ seq_printf(s, "POLARITY_VDD5V_GT_VDDIO: %x\n", (value >> 2) & 1);
+ seq_printf(s, "VDD5V_GT_VDDIO_IRQ: %x\n", (value >> 1) & 1);
+ seq_printf(s, "ENIRQ_VDD5V_GT_VDDIO: %x\n", value & 1);
+
+ return 0;
+}
+
+static int
+mxs_power_5vctrl_mxs_show(struct seq_file *s, void *what)
+{
+ struct mxs_power_data *data = s->private;
+ u32 value;
+ int ret = regmap_read(data->regmap, HW_POWER_5VCTRL, &value);
+
+ if (ret)
+ return ret;
+
+ seq_printf(s, "VBUSDROOP_TRSH: %x\n", (value >> 28) & 3);
+ seq_printf(s, "HEADROOM_ADJ: %x\n", (value >> 24) & 7);
+ seq_printf(s, "PWD_CHARGE_4P2: %x\n", (value >> 20) & 1);
+ seq_printf(s, "CHARGE_4P2_ILIMIT: %x\n", (value >> 12) & 0x3F);
+ seq_printf(s, "VBUSVALID_TRSH: %x\n", (value >> 8) & 7);
+ seq_printf(s, "PWDN_5VBRNOUT: %x\n", (value >> 7) & 1);
+ seq_printf(s, "ENABLE_LINREG_ILIMIT: %x\n", (value >> 6) & 1);
+ seq_printf(s, "DCDC_XFER: %x\n", (value >> 5) & 1);
+ seq_printf(s, "VBUSVALID_5VDETECT: %x\n", (value >> 4) & 1);
+ seq_printf(s, "VBUSVALID_TO_B: %x\n", (value >> 3) & 1);
+ seq_printf(s, "ILIMIT_EQ_ZERO: %x\n", (value >> 2) & 1);
+ seq_printf(s, "PWRUP_VBUS_CMPS: %x\n", (value >> 1) & 1);
+ seq_printf(s, "ENABLE_DCDC: %x\n", value & 1);
+
+ return 0;
+}
+
+static int
+mxs_power_vddd_mxs_show(struct seq_file *s, void *what)
+{
+ struct mxs_power_data *data = s->private;
+ u32 value;
+ int ret = regmap_read(data->regmap, HW_POWER_VDDDCTRL, &value);
+
+ if (ret)
+ return ret;
+
+ seq_printf(s, "ADJTN: %x\n", (value >> 28) & 0xf);
+ seq_printf(s, "PWDN_BRNOUT: %x\n", (value >> 23) & 1);
+ seq_printf(s, "DISABLE_STEPPING: %x\n", (value >> 22) & 1);
+ seq_printf(s, "ENABLE_LINREG: %x\n", (value >> 21) & 1);
+ seq_printf(s, "DISABLE_FET: %x\n", (value >> 20) & 1);
+ seq_printf(s, "LINREG_OFFSET: %x\n", (value >> 16) & 3);
+ seq_printf(s, "BO_OFFSET: %x\n", (value >> 8) & 7);
+ seq_printf(s, "TRG: %x\n", value & 0x1f);
+
+ return 0;
+}
+
+static int
+mxs_power_vdda_mxs_show(struct seq_file *s, void *what)
+{
+ struct mxs_power_data *data = s->private;
+ u32 value;
+ int ret = regmap_read(data->regmap, HW_POWER_VDDACTRL, &value);
+
+ if (ret)
+ return ret;
+
+ seq_printf(s, "PWDN_BRNOUT: %x\n", (value >> 19) & 1);
+ seq_printf(s, "DISABLE_STEPPING: %x\n", (value >> 18) & 1);
+ seq_printf(s, "ENABLE_LINREG: %x\n", (value >> 17) & 1);
+ seq_printf(s, "DISABLE_FET: %x\n", (value >> 16) & 1);
+ seq_printf(s, "LINREG_OFFSET: %x\n", (value >> 12) & 3);
+ seq_printf(s, "BO_OFFSET: %x\n", (value >> 8) & 7);
+ seq_printf(s, "TRG: %x\n", value & 0x1f);
+
+ return 0;
+}
+
+static int
+mxs_power_vddio_mxs_show(struct seq_file *s, void *what)
+{
+ struct mxs_power_data *data = s->private;
+ u32 value;
+ int ret = regmap_read(data->regmap, HW_POWER_VDDIOCTRL, &value);
+
+ if (ret)
+ return ret;
+
+ seq_printf(s, "ADJTN: %x\n", (value >> 20) & 0xf);
+ seq_printf(s, "PWDN_BRNOUT: %x\n", (value >> 18) & 1);
+ seq_printf(s, "DISABLE_STEPPING: %x\n", (value >> 17) & 1);
+ seq_printf(s, "DISABLE_FET: %x\n", (value >> 16) & 1);
+ seq_printf(s, "LINREG_OFFSET: %x\n", (value >> 12) & 3);
+ seq_printf(s, "BO_OFFSET: %x\n", (value >> 8) & 7);
+ seq_printf(s, "TRG: %x\n", value & 0x1f);
+
+ return 0;
+}
+
+static int
+mxs_power_dcdc4p2_mxs_show(struct seq_file *s, void *what)
+{
+ struct mxs_power_data *data = s->private;
+ u32 value;
+ int ret = regmap_read(data->regmap, HW_POWER_DCDC4P2, &value);
+
+ if (ret)
+ return ret;
+
+ seq_printf(s, "DROPOUT_CTRL: %x\n", (value >> 28) & 0xf);
+ seq_printf(s, "ENABLE_4P2: %x\n", (value >> 23) & 1);
+ seq_printf(s, "ENABLE_DCDC: %x\n", (value >> 22) & 1);
+ seq_printf(s, "HYST_DIR: %x\n", (value >> 21) & 1);
+ seq_printf(s, "HYST_THRESH: %x\n", (value >> 20) & 3);
+ seq_printf(s, "TRG: %x\n", (value >> 16) & 7);
+ seq_printf(s, "BO: %x\n", (value >> 8) & 0x1f);
+ seq_printf(s, "CMPTRIP: %x\n", value & 0x1f);
+
+ return 0;
+}
+
+static int
+mxs_power_misc_mxs_show(struct seq_file *s, void *what)
+{
+ struct mxs_power_data *data = s->private;
+ u32 value;
+ int ret = regmap_read(data->regmap, HW_POWER_MISC, &value);
+
+ if (ret)
+ return ret;
+
+ seq_printf(s, "FREQSEL %x\n", (value >> 4) & 0x7);
+
+ /* only MX28 */
+ seq_printf(s, "DISABLEFET_BO_LOGIC %x\n", (value >> 3) & 1);
+
+ seq_printf(s, "DELAY_TIMING %x\n", (value >> 2) & 1);
+ seq_printf(s, "SEL_PLLCLK %x\n", value & 1);
+
+ return 0;
+}
+
+static int
+mxs_power_sts_mxs_show(struct seq_file *s, void *what)
+{
+ struct mxs_power_data *data = s->private;
+ u32 value;
+ int ret = regmap_read(data->regmap, HW_POWER_STS, &value);
+
+ if (ret)
+ return ret;
+
+ seq_printf(s, "PWRUP_SOURCE %x\n", (value >> 24) & 0x3F);
+ seq_printf(s, "PSWITCH %x\n", (value >> 20) & 3);
+ seq_printf(s, "THERMAL_WARNING %x\n", (value >> 19) & 1);
+ seq_printf(s, "VDDMEM_BO %x\n", (value >> 18) & 1);
+ seq_printf(s, "AVALID0_STATUS %x\n", (value >> 17) & 1);
+ seq_printf(s, "BVALID0_STATUS %x\n", (value >> 16) & 1);
+ seq_printf(s, "VBUSVALID0_STATUS %x\n", (value >> 15) & 1);
+ seq_printf(s, "SESSEND0_STATUS %x\n", (value >> 14) & 1);
+ seq_printf(s, "BATT_BO %x\n", (value >> 13) & 1);
+ seq_printf(s, "VDD5V_FAULT %x\n", (value >> 12) & 1);
+ seq_printf(s, "CHRGSTS %x\n", (value >> 11) & 1);
+ seq_printf(s, "DCDC_4P2_BO %x\n", (value >> 10) & 1);
+ seq_printf(s, "DC_OK %x\n", (value >> 9) & 1);
+ seq_printf(s, "VDDIO_BO %x\n", (value >> 8) & 1);
+ seq_printf(s, "VDDA_BO %x\n", (value >> 7) & 1);
+ seq_printf(s, "VDDD_BO %x\n", (value >> 6) & 1);
+ seq_printf(s, "VDD5V_GT_VDDIO %x\n", (value >> 5) & 1);
+ seq_printf(s, "VDD5V_DROOP %x\n", (value >> 4) & 1);
+ seq_printf(s, "AVALID0 %x\n", (value >> 3) & 1);
+ seq_printf(s, "BVALID0 %x\n", (value >> 2) & 1);
+ seq_printf(s, "VBUSVALID0 %x\n", (value >> 1) & 1);
+ seq_printf(s, "SESSEND0 %x\n", value & 1);
+
+ return 0;
+}
+
+static int
+mxs_power_ctrl_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, mxs_power_ctrl_mxs_show, inode->i_private);
+}
+
+static int
+mxs_power_5vctrl_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, mxs_power_5vctrl_mxs_show, inode->i_private);
+}
+
+static int
+mxs_power_vddd_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, mxs_power_vddd_mxs_show, inode->i_private);
+}
+
+static int
+mxs_power_vdda_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, mxs_power_vdda_mxs_show, inode->i_private);
+}
+
+static int
+mxs_power_vddio_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, mxs_power_vddio_mxs_show, inode->i_private);
+}
+
+static int
+mxs_power_dcdc4p2_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, mxs_power_dcdc4p2_mxs_show, inode->i_private);
+}
+
+static int
+mxs_power_misc_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, mxs_power_misc_mxs_show, inode->i_private);
+}
+
+static int
+mxs_power_sts_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, mxs_power_sts_mxs_show, inode->i_private);
+}
+
+static const struct file_operations mxs_power_ctrl_ops = {
+ .open = mxs_power_ctrl_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static const struct file_operations mxs_power_5vctrl_ops = {
+ .open = mxs_power_5vctrl_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static const struct file_operations mxs_power_vddd_ops = {
+ .open = mxs_power_vddd_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static const struct file_operations mxs_power_vdda_ops = {
+ .open = mxs_power_vdda_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static const struct file_operations mxs_power_vddio_ops = {
+ .open = mxs_power_vddio_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static const struct file_operations mxs_power_dcdc4p2_ops = {
+ .open = mxs_power_dcdc4p2_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static const struct file_operations mxs_power_misc_ops = {
+ .open = mxs_power_misc_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static const struct file_operations mxs_power_sts_ops = {
+ .open = mxs_power_sts_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+void
+mxs_power_init_device_debugfs(struct mxs_power_data *data)
+{
+ struct dentry *device_root;
+
+ device_root = debugfs_create_dir("mxs_power", NULL);
+ data->device_root = device_root;
+
+ if (IS_ERR(device_root) || !device_root) {
+ pr_warn("failed to create debugfs directory for %s\n",
+ "mxs_power");
+ return;
+ }
+ debugfs_create_file("ctrl", S_IFREG | S_IRUGO, device_root, data,
+ &mxs_power_ctrl_ops);
+ debugfs_create_file("5vctrl", S_IFREG | S_IRUGO, device_root, data,
+ &mxs_power_5vctrl_ops);
+ debugfs_create_file("vddd", S_IFREG | S_IRUGO, device_root, data,
+ &mxs_power_vddd_ops);
+ debugfs_create_file("vdda", S_IFREG | S_IRUGO, device_root, data,
+ &mxs_power_vdda_ops);
+ debugfs_create_file("vddio", S_IFREG | S_IRUGO, device_root, data,
+ &mxs_power_vddio_ops);
+ debugfs_create_file("dcdc4p2", S_IFREG | S_IRUGO, device_root, data,
+ &mxs_power_dcdc4p2_ops);
+ debugfs_create_file("misc", S_IFREG | S_IRUGO, device_root, data,
+ &mxs_power_misc_ops);
+ debugfs_create_file("sts", S_IFREG | S_IRUGO, device_root, data,
+ &mxs_power_sts_ops);
+}
+
+void
+mxs_power_remove_device_debugfs(struct mxs_power_data *data)
+{
+ debugfs_remove_recursive(data->device_root);
+}
+
+#else /* CONFIG_DEBUG_FS */
+
+void
+mxs_power_init_device_debugfs(struct mxs_power_data *data)
+{
+}
+
+void
+mxs_power_remove_device_debugfs(struct mxs_power_data *data)
+{
+}
+
+#endif
diff --git a/drivers/power/mxs-power.c b/drivers/power/mxs-power.c
new file mode 100644
index 0000000..a9e64c4
--- /dev/null
+++ b/drivers/power/mxs-power.c
@@ -0,0 +1,277 @@
+/*
+ * Freescale MXS power subsystem
+ *
+ * Copyright (C) 2014 Stefan Wahren
+ *
+ * Inspired by imx-bootlets
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/power/mxs_power.h>
+#include <linux/stmp_device.h>
+#include <linux/types.h>
+
+#define BM_POWER_CTRL_POLARITY_VBUSVALID BIT(5)
+#define BM_POWER_CTRL_VBUSVALID_IRQ BIT(4)
+#define BM_POWER_CTRL_ENIRQ_VBUS_VALID BIT(3)
+
+#define BM_POWER_5VCTRL_VBUSVALID_THRESH (7 << 8)
+#define BM_POWER_5VCTRL_PWDN_5VBRNOUT BIT(7)
+#define BM_POWER_5VCTRL_ENABLE_LINREG_ILIMIT BIT(6)
+#define BM_POWER_5VCTRL_VBUSVALID_5VDETECT BIT(4)
+
+#define HW_POWER_5VCTRL_VBUSVALID_THRESH_4_40V (5 << 8)
+
+#define BM_POWER_STS_VBUSVALID0_STATUS BIT(15)
+#define BM_POWER_STS_VDD5V_DROOP BIT(4)
+
+#define STATUS_5V_CONNECTION BIT(0)
+#define STATUS_5V_NEW BIT(1)
+
+#define STATUS_NEW_5V_CONNECTION (STATUS_5V_NEW | STATUS_5V_CONNECTION)
+#define STATUS_NEW_5V_DISCONNECTION STATUS_5V_NEW
+#define STATUS_EXISTING_5V_CONNECTION STATUS_5V_CONNECTION
+#define STATUS_EXISTING_5V_DISCONNECTION 0
+
+static enum power_supply_property mxs_power_ac_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN,
+};
+
+static int mxs_power_5v_status(struct regmap *map)
+{
+ u32 ctrl = 0;
+ u32 status = 0;
+ int ret;
+
+ ret = regmap_read(map, HW_POWER_CTRL, &ctrl);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(map, HW_POWER_STS, &status);
+ if (ret)
+ return ret;
+
+ if (ctrl & BM_POWER_CTRL_POLARITY_VBUSVALID) {
+ if ((ctrl & BM_POWER_CTRL_VBUSVALID_IRQ) ||
+ (status & BM_POWER_STS_VBUSVALID0_STATUS))
+ return STATUS_NEW_5V_CONNECTION;
+
+ return STATUS_EXISTING_5V_DISCONNECTION;
+ }
+
+ if ((ctrl & BM_POWER_CTRL_VBUSVALID_IRQ) ||
+ !(status & BM_POWER_STS_VBUSVALID0_STATUS) ||
+ (status & BM_POWER_STS_VDD5V_DROOP))
+ return STATUS_NEW_5V_DISCONNECTION;
+
+ return STATUS_EXISTING_5V_CONNECTION;
+}
+
+static void mxs_5v_work_func(struct work_struct *work)
+{
+ struct mxs_power_data *data =
+ container_of(work, struct mxs_power_data, poll_5v.work);
+
+ switch (mxs_power_5v_status(data->regmap)) {
+ case STATUS_NEW_5V_CONNECTION:
+ case STATUS_EXISTING_5V_CONNECTION:
+ mxs_regmap_clr(data->regmap, HW_POWER_CTRL,
+ BM_POWER_CTRL_POLARITY_VBUSVALID);
+ break;
+ case STATUS_NEW_5V_DISCONNECTION:
+ case STATUS_EXISTING_5V_DISCONNECTION:
+ mxs_regmap_set(data->regmap, HW_POWER_CTRL,
+ BM_POWER_CTRL_POLARITY_VBUSVALID);
+ break;
+ default:
+ return;
+ }
+
+ mxs_regmap_clr(data->regmap, HW_POWER_CTRL,
+ BM_POWER_CTRL_VBUSVALID_IRQ);
+
+ mxs_regmap_set(data->regmap, HW_POWER_CTRL,
+ BM_POWER_CTRL_ENIRQ_VBUS_VALID);
+}
+
+static irqreturn_t mxs_irq_vdd5v(int irq, void *cookie)
+{
+ struct mxs_power_data *data = (struct mxs_power_data *)cookie;
+
+ switch (mxs_power_5v_status(data->regmap)) {
+ case STATUS_NEW_5V_CONNECTION:
+ pr_info("New 5v connection detected\n");
+ break;
+ case STATUS_NEW_5V_DISCONNECTION:
+ pr_info("New 5v disconnection detected\n");
+ break;
+ default:
+ return IRQ_HANDLED;
+ }
+
+ mxs_regmap_clr(data->regmap, HW_POWER_CTRL,
+ BM_POWER_CTRL_ENIRQ_VBUS_VALID);
+
+ schedule_delayed_work(&data->poll_5v, msecs_to_jiffies(10));
+
+ return IRQ_HANDLED;
+}
+
+static int mxs_power_ac_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct mxs_power_data *data = power_supply_get_drvdata(psy);
+ u32 v5ctrl = 0;
+ int thresh;
+ int ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ ret = mxs_power_5v_status(data->regmap);
+ if (ret < 0)
+ return ret;
+
+ val->intval = (ret & STATUS_5V_CONNECTION) ? 1 : 0;
+ ret = 0;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+ ret = regmap_read(data->regmap, HW_POWER_5VCTRL, &v5ctrl);
+ if (ret)
+ return ret;
+
+ thresh = (v5ctrl & BM_POWER_5VCTRL_VBUSVALID_THRESH) >> 8;
+
+ if (thresh)
+ val->intval = (39 + thresh) * 100000;
+ else
+ val->intval = 2900000;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static const struct of_device_id of_mxs_power_match[] = {
+ { .compatible = "fsl,imx23-power" },
+ { .compatible = "fsl,imx28-power" },
+ { /* end */ }
+};
+MODULE_DEVICE_TABLE(of, of_mxs_power_match);
+
+static const struct power_supply_desc ac_desc = {
+ .properties = mxs_power_ac_props,
+ .num_properties = ARRAY_SIZE(mxs_power_ac_props),
+ .get_property = mxs_power_ac_get_property,
+ .name = "ac",
+ .type = POWER_SUPPLY_TYPE_MAINS,
+};
+
+static int mxs_power_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct mxs_power_data *data;
+ struct power_supply_config psy_cfg = {};
+ int ret;
+ unsigned int irq;
+
+ if (!np) {
+ dev_err(dev, "missing device tree\n");
+ return -EINVAL;
+ }
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->regmap = syscon_node_to_regmap(np);
+ if (IS_ERR(data->regmap))
+ return PTR_ERR(data->regmap);
+
+ /* Make sure the current limit of the linregs are disabled. */
+ mxs_regmap_clr(data->regmap, HW_POWER_5VCTRL,
+ BM_POWER_5VCTRL_ENABLE_LINREG_ILIMIT);
+
+ psy_cfg.drv_data = data;
+ platform_set_drvdata(pdev, data);
+
+ INIT_DELAYED_WORK(&data->poll_5v, mxs_5v_work_func);
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_err(dev, "No IRQ resource!\n");
+ return -EINVAL;
+ }
+
+ ret = devm_request_irq(dev, irq, mxs_irq_vdd5v, IRQF_SHARED,
+ "mxs-power", data);
+ if (ret)
+ return ret;
+
+ data->ac = devm_power_supply_register(dev, &ac_desc, &psy_cfg);
+ if (IS_ERR(data->ac))
+ return PTR_ERR(data->ac);
+
+ switch (mxs_power_5v_status(data->regmap)) {
+ case STATUS_NEW_5V_CONNECTION:
+ case STATUS_EXISTING_5V_CONNECTION:
+ dev_info(dev, "5V = connected\n");
+ break;
+ case STATUS_NEW_5V_DISCONNECTION:
+ case STATUS_EXISTING_5V_DISCONNECTION:
+ dev_info(dev, "5V = disconnected\n");
+ break;
+ }
+
+ mxs_power_init_device_debugfs(data);
+
+ return of_platform_populate(np, NULL, NULL, dev);
+}
+
+static int mxs_power_remove(struct platform_device *pdev)
+{
+ struct mxs_power_data *data = platform_get_drvdata(pdev);
+
+ mxs_power_remove_device_debugfs(data);
+ cancel_delayed_work(&data->poll_5v);
+
+ return 0;
+}
+
+static struct platform_driver mxs_power_driver = {
+ .driver = {
+ .name = "mxs_power",
+ .of_match_table = of_mxs_power_match,
+ },
+ .probe = mxs_power_probe,
+ .remove = mxs_power_remove,
+};
+
+module_platform_driver(mxs_power_driver);
+
+MODULE_AUTHOR("Stefan Wahren <stefan.wahren@i2se.com>");
+MODULE_DESCRIPTION("Freescale MXS power subsystem");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/power/mxs_power.h b/include/linux/power/mxs_power.h
new file mode 100644
index 0000000..014e855
--- /dev/null
+++ b/include/linux/power/mxs_power.h
@@ -0,0 +1,99 @@
+/*
+ * Freescale MXS power subsystem defines
+ *
+ * Copyright (C) 2015 Stefan Wahren
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#ifndef __POWER_MXS_POWER_H
+#define __POWER_MXS_POWER_H
+
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+#include <linux/stmp_device.h>
+
+/* Regulator IDs */
+#define MXS_POWER_DCDC 1
+#define MXS_POWER_VDDIO 2
+#define MXS_POWER_VDDA 3
+#define MXS_POWER_VDDD 4
+
+/* MXS power register address offset */
+#define HW_POWER_CTRL 0x0000
+#define HW_POWER_5VCTRL 0x0010
+#define HW_POWER_VDDDCTRL 0x0040
+#define HW_POWER_VDDACTRL 0x0050
+#define HW_POWER_VDDIOCTRL 0x0060
+#define HW_POWER_DCDC4P2 0x0080
+#define HW_POWER_MISC 0x0090
+#define HW_POWER_STS 0x00c0
+#define HW_POWER_RESET 0x0100
+
+/* Powered by linear regulator. DCDC output is gated off and
+ the linreg output is equal to the target. */
+#define HW_POWER_LINREG_DCDC_OFF 1
+
+/* Powered by linear regulator. DCDC output is not gated off
+ and is ready for the automatic hardware transistion after a 5V
+ event. The converters are not enabled when 5V is present. LinReg output
+ is 25mV below target. */
+#define HW_POWER_LINREG_DCDC_READY 2
+
+/* Powered by DCDC converter and the LinReg is on. LinReg output
+ is 25mV below target. */
+#define HW_POWER_DCDC_LINREG_ON 3
+
+/* Powered by DCDC converter and the LinReg is off. LinReg output
+ is 25mV below target. */
+#define HW_POWER_DCDC_LINREG_OFF 4
+
+/* Powered by DCDC converter and the LinReg is ready for the
+ automatic hardware transfer. The LinReg output is not enabled and
+ depends on the 5V presence to enable the LinRegs. LinReg offset is 25mV
+ below target. */
+#define HW_POWER_DCDC_LINREG_READY 5
+
+/* Powered by an external source when 5V is present. This does not
+ necessarily mean the external source is powered by 5V,but the chip needs
+ to be aware that 5V is present. */
+#define HW_POWER_EXTERNAL_SOURCE_5V 6
+
+/* Powered by an external source when 5V is not present. This doesn't
+ necessarily mean the external source is powered by the battery, but the
+ chip needs to be aware that the battery is present */
+#define HW_POWER_EXTERNAL_SOURCE_BATTERY 7
+
+/* Unknown configuration. This is an error. */
+#define HW_POWER_UNKNOWN_SOURCE 8
+
+static inline int mxs_regmap_set(struct regmap *map, unsigned int reg, unsigned int val)
+{
+ return regmap_write(map, reg + STMP_OFFSET_REG_SET, val);
+}
+
+static inline int mxs_regmap_clr(struct regmap *map, unsigned int reg, unsigned int val)
+{
+ return regmap_write(map, reg + STMP_OFFSET_REG_CLR, val);
+}
+
+struct mxs_power_data {
+ struct power_supply *ac;
+ struct regmap *regmap;
+ struct delayed_work poll_5v;
+
+#ifdef CONFIG_DEBUG_FS
+ struct dentry *device_root;
+#endif
+};
+
+void mxs_power_init_device_debugfs(struct mxs_power_data *data);
+
+void mxs_power_remove_device_debugfs(struct mxs_power_data *data);
+
+#endif
--
2.7.4
From 44eda9939d096f56b515562bf95d2ad8bc4ab364 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=B6rg=20Krause?= <joerg.krause@embedded.rocks>
Date: Tue, 22 Mar 2016 10:35:35 +0100
Subject: [PATCH 101/102] mxs: add regulator
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Jörg Krause <joerg.krause@embedded.rocks>
---
drivers/regulator/Kconfig | 8 +
drivers/regulator/Makefile | 2 +
drivers/regulator/mxs-regulator-dcdc.c | 189 +++++++++++++
drivers/regulator/mxs-regulator-ldo.c | 498 +++++++++++++++++++++++++++++++++
4 files changed, 697 insertions(+)
create mode 100644 drivers/regulator/mxs-regulator-dcdc.c
create mode 100644 drivers/regulator/mxs-regulator-ldo.c
diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index 8df0b0e..cdfff93 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -461,6 +461,14 @@ config REGULATOR_MT6397
This driver supports the control of different power rails of device
through regulator interface.
+config REGULATOR_MXS
+ tristate "Freescale MXS on-chip regulators"
+ depends on MXS_POWER || COMPILE_TEST
+ help
+ Say y here to support Freescale MXS on-chip regulators.
+ It is recommended that this option be enabled on i.MX23,
+ i.MX28 platform.
+
config REGULATOR_PALMAS
tristate "TI Palmas PMIC Regulators"
depends on MFD_PALMAS
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 0f81749..2547359 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -61,6 +61,8 @@ obj-$(CONFIG_REGULATOR_MC13892) += mc13892-regulator.o
obj-$(CONFIG_REGULATOR_MC13XXX_CORE) += mc13xxx-regulator-core.o
obj-$(CONFIG_REGULATOR_MT6311) += mt6311-regulator.o
obj-$(CONFIG_REGULATOR_MT6397) += mt6397-regulator.o
+obj-$(CONFIG_REGULATOR_MXS) += mxs-regulator-dcdc.o
+obj-$(CONFIG_REGULATOR_MXS) += mxs-regulator-ldo.o
obj-$(CONFIG_REGULATOR_QCOM_RPM) += qcom_rpm-regulator.o
obj-$(CONFIG_REGULATOR_QCOM_SMD_RPM) += qcom_smd-regulator.o
obj-$(CONFIG_REGULATOR_QCOM_SPMI) += qcom_spmi-regulator.o
diff --git a/drivers/regulator/mxs-regulator-dcdc.c b/drivers/regulator/mxs-regulator-dcdc.c
new file mode 100644
index 0000000..4ae9c4c
--- /dev/null
+++ b/drivers/regulator/mxs-regulator-dcdc.c
@@ -0,0 +1,189 @@
+/*
+ * Freescale MXS on-chip DC-DC driver
+ *
+ * Embedded Alley Solutions, Inc <source@embeddedalley.com>
+ *
+ * Copyright (C) 2015 Stefan Wahren
+ *
+ * Inspired by imx-bootlets
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/power/mxs_power.h>
+#include <linux/regmap.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/slab.h>
+
+#define SHIFT_FREQSEL 4
+
+#define BM_POWER_MISC_FREQSEL (7 << SHIFT_FREQSEL)
+
+/* Recommended DC-DC clock source values */
+#define HW_POWER_MISC_FREQSEL_20000_KHZ 1
+#define HW_POWER_MISC_FREQSEL_24000_KHZ 2
+#define HW_POWER_MISC_FREQSEL_19200_KHZ 3
+
+#define HW_POWER_MISC_SEL_PLLCLK BIT(0)
+
+struct mxs_dcdc_info {
+ /* regulator descriptor */
+ struct regulator_desc desc;
+
+ /* regulator control register */
+ int ctrl_reg;
+};
+
+static int mxs_set_dcdc_freq(struct regulator_dev *reg, u32 hz)
+{
+ u32 val;
+ int ret;
+
+ ret = regmap_read(reg->regmap, HW_POWER_MISC, &val);
+ if (ret)
+ return ret;
+
+ val &= ~BM_POWER_MISC_FREQSEL;
+ val &= ~HW_POWER_MISC_SEL_PLLCLK;
+
+ /*
+ * Select the PLL/PFD based frequency that the DC-DC converter uses.
+ * The actual switching frequency driving the power inductor is
+ * DCDC_CLK/16. Accept only values recommend by Freescale.
+ */
+ switch (hz) {
+ case 1200000:
+ val |= HW_POWER_MISC_FREQSEL_19200_KHZ << SHIFT_FREQSEL;
+ break;
+ case 1250000:
+ val |= HW_POWER_MISC_FREQSEL_20000_KHZ << SHIFT_FREQSEL;
+ break;
+ case 1500000:
+ val |= HW_POWER_MISC_FREQSEL_24000_KHZ << SHIFT_FREQSEL;
+ break;
+ default:
+ dev_warn(&reg->dev, "Switching freq: %u Hz not supported\n",
+ hz);
+ return -EINVAL;
+ }
+
+ /* First program FREQSEL */
+ ret = regmap_write(reg->regmap, HW_POWER_MISC, val);
+ if (ret)
+ return ret;
+
+ /* then set PLL as clock for DC-DC converter */
+ val |= HW_POWER_MISC_SEL_PLLCLK;
+
+ return regmap_write(reg->regmap, HW_POWER_MISC, val);
+}
+
+static struct regulator_ops mxs_dcdc_ops = {
+ .is_enabled = regulator_is_enabled_regmap,
+};
+
+static const struct mxs_dcdc_info mxs_dcdc = {
+ .desc = {
+ .name = "dcdc",
+ .id = MXS_POWER_DCDC,
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+ .ops = &mxs_dcdc_ops,
+ .enable_reg = HW_POWER_STS,
+ .enable_mask = (1 << 0),
+ },
+};
+
+static const struct of_device_id of_mxs_regulator_dcdc_match[] = {
+ { .compatible = "fsl,imx23-dcdc", .data = &mxs_dcdc },
+ { .compatible = "fsl,imx28-dcdc", .data = &mxs_dcdc },
+ { /* end */ }
+};
+MODULE_DEVICE_TABLE(of, of_mxs_regulator_dcdc_match);
+
+static int mxs_regulator_dcdc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ const struct of_device_id *match;
+ struct device_node *parent_np;
+ struct regulator_dev *rdev = NULL;
+ struct mxs_dcdc_info *info;
+ struct regulator_init_data *initdata;
+ struct regulator_config config = { };
+ u32 switch_freq;
+
+ match = of_match_device(of_mxs_regulator_dcdc_match, dev);
+ if (!match) {
+ /* We do not expect this to happen */
+ dev_err(dev, "%s: Unable to match device\n", __func__);
+ return -ENODEV;
+ }
+
+ info = devm_kmemdup(dev, match->data, sizeof(struct mxs_dcdc_info),
+ GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ initdata = of_get_regulator_init_data(dev, dev->of_node, &info->desc);
+
+ parent_np = of_get_parent(dev->of_node);
+ if (!parent_np)
+ return -ENODEV;
+ config.regmap = syscon_node_to_regmap(parent_np);
+ of_node_put(parent_np);
+ if (IS_ERR(config.regmap))
+ return PTR_ERR(config.regmap);
+
+ config.dev = dev;
+ config.init_data = initdata;
+ config.driver_data = info;
+ config.of_node = dev->of_node;
+
+ if (!of_property_read_u32(dev->of_node, "switching-frequency",
+ &switch_freq))
+ mxs_set_dcdc_freq(rdev, switch_freq);
+
+ rdev = devm_regulator_register(dev, &info->desc, &config);
+ if (IS_ERR(rdev)) {
+ int ret = PTR_ERR(rdev);
+
+ dev_err(dev, "%s: failed to register regulator(%d)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct platform_driver mxs_regulator_dcdc_driver = {
+ .driver = {
+ .name = "mxs_regulator_dcdc",
+ .of_match_table = of_mxs_regulator_dcdc_match,
+ },
+ .probe = mxs_regulator_dcdc_probe,
+};
+
+module_platform_driver(mxs_regulator_dcdc_driver);
+
+MODULE_AUTHOR("Stefan Wahren <stefan.wahren@i2se.com>");
+MODULE_DESCRIPTION("Freescale MXS on-chip DC-DC driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:mxs_regulator_dcdc");
diff --git a/drivers/regulator/mxs-regulator-ldo.c b/drivers/regulator/mxs-regulator-ldo.c
new file mode 100644
index 0000000..afbb0ed
--- /dev/null
+++ b/drivers/regulator/mxs-regulator-ldo.c
@@ -0,0 +1,498 @@
+/*
+ * Freescale MXS on-chip LDO driver
+ *
+ * Embedded Alley Solutions, Inc <source@embeddedalley.com>
+ *
+ * Copyright (C) 2014 Stefan Wahren
+ * Copyright (C) 2010 Freescale Semiconductor, Inc.
+ * Copyright (C) 2008 Embedded Alley Solutions, Inc All Rights Reserved.
+ *
+ * Inspired by imx-bootlets
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/power/mxs_power.h>
+#include <linux/regmap.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/slab.h>
+
+#define BM_POWER_STS_VBUSVALID0_STATUS BIT(15)
+#define BM_POWER_STS_DC_OK BIT(9)
+
+#define BM_POWER_5VCTRL_ILIMIT_EQ_ZERO BIT(2)
+#define BM_POWER_5VCTRL_ENABLE_DCDC BIT(0)
+
+#define BM_POWER_LINREG_OFFSET_DCDC_MODE BIT(1)
+struct mxs_ldo_info;
+
+struct mxs_ldo_info {
+ /* regulator descriptor */
+ struct regulator_desc desc;
+
+ struct regmap *regmap;
+
+ /* regulator control register */
+ unsigned int ctrl_reg;
+
+ /* disable DC-DC output */
+ unsigned int disable_fet_mask;
+
+ unsigned int disable_stepping_mask;
+
+ /* steps between linreg output and DC-DC target */
+ unsigned int linreg_offset_mask;
+ u8 linreg_offset_shift;
+
+ /* brownout voltage offset */
+ unsigned int bo_offset_mask;
+ u8 bo_offset_shift;
+
+ /* brownout interrupt status */
+ unsigned int irq_bo;
+
+ /* brownout enable interrupt */
+ unsigned int enirq_bo;
+
+ /* function which determine power source */
+ u8 (*get_power_source)(struct mxs_ldo_info *);
+};
+
+static int mxs_ldo_set_bo_offset(struct regulator_dev *reg, unsigned int offset)
+{
+ struct mxs_ldo_info *ldo = rdev_get_drvdata(reg);
+
+ if (offset > 7)
+ return -EINVAL;
+
+ offset <<= ldo->bo_offset_shift;
+
+ return regmap_update_bits(ldo->regmap, ldo->ctrl_reg,
+ ldo->bo_offset_mask, offset);
+}
+
+static inline u8 get_linreg_offset(struct mxs_ldo_info *ldo, u32 regs)
+{
+ return (regs & ldo->linreg_offset_mask) >> ldo->linreg_offset_shift;
+}
+
+static u8 get_vddio_power_source(struct mxs_ldo_info *ldo)
+{
+ u32 v5ctrl, status, base;
+ u8 offset;
+
+ if (regmap_read(ldo->regmap, HW_POWER_5VCTRL, &v5ctrl))
+ return HW_POWER_UNKNOWN_SOURCE;
+
+ if (regmap_read(ldo->regmap, HW_POWER_STS, &status))
+ return HW_POWER_UNKNOWN_SOURCE;
+
+ if (regmap_read(ldo->regmap, ldo->ctrl_reg, &base))
+ return HW_POWER_UNKNOWN_SOURCE;
+
+ offset = get_linreg_offset(ldo, base);
+
+ /* If VBUS valid then 5 V power supply present */
+ if (status & BM_POWER_STS_VBUSVALID0_STATUS) {
+ /* Powered by Linreg, DC-DC is off */
+ if ((base & ldo->disable_fet_mask) &&
+ !(offset & BM_POWER_LINREG_OFFSET_DCDC_MODE)) {
+ return HW_POWER_LINREG_DCDC_OFF;
+ }
+
+ if (v5ctrl & BM_POWER_5VCTRL_ENABLE_DCDC) {
+ /* Powered by DC-DC, Linreg is on */
+ if (offset & BM_POWER_LINREG_OFFSET_DCDC_MODE)
+ return HW_POWER_DCDC_LINREG_ON;
+ } else {
+ /* Powered by Linreg, DC-DC is ready */
+ if (offset & BM_POWER_LINREG_OFFSET_DCDC_MODE)
+ return HW_POWER_LINREG_DCDC_READY;
+
+ /* Powered by Linreg, DC-DC is off */
+ return HW_POWER_LINREG_DCDC_OFF;
+ }
+ } else {
+ /* Powered by DC-DC, Linreg is on */
+ if (offset & BM_POWER_LINREG_OFFSET_DCDC_MODE)
+ return HW_POWER_DCDC_LINREG_ON;
+ }
+
+ return HW_POWER_UNKNOWN_SOURCE;
+}
+
+static u8 get_vdda_vddd_power_source(struct mxs_ldo_info *ldo)
+{
+ struct regulator_desc *desc = &ldo->desc;
+ u32 v5ctrl, status, base;
+ u8 offset;
+
+ if (regmap_read(ldo->regmap, HW_POWER_5VCTRL, &v5ctrl))
+ return HW_POWER_UNKNOWN_SOURCE;
+
+ if (regmap_read(ldo->regmap, HW_POWER_STS, &status))
+ return HW_POWER_UNKNOWN_SOURCE;
+
+ if (regmap_read(ldo->regmap, ldo->ctrl_reg, &base))
+ return HW_POWER_UNKNOWN_SOURCE;
+
+ offset = get_linreg_offset(ldo, base);
+
+ /* DC-DC output is disabled */
+ if (base & ldo->disable_fet_mask) {
+ /* Powered by Linreg, DC-DC is off */
+ if (!(offset & BM_POWER_LINREG_OFFSET_DCDC_MODE))
+ return HW_POWER_LINREG_DCDC_OFF;
+ }
+
+ /* If VBUS valid then 5 V power supply present */
+ if (status & BM_POWER_STS_VBUSVALID0_STATUS) {
+ /* Powered by DC-DC, Linreg is on */
+ if (v5ctrl & BM_POWER_5VCTRL_ENABLE_DCDC)
+ return HW_POWER_DCDC_LINREG_ON;
+
+ /* Powered by Linreg, DC-DC is ready */
+ if (offset & BM_POWER_LINREG_OFFSET_DCDC_MODE)
+ return HW_POWER_LINREG_DCDC_READY;
+
+ /* Powered by Linreg, DC-DC is off */
+ return HW_POWER_LINREG_DCDC_OFF;
+ }
+
+ /* DC-DC is on */
+ if (offset & BM_POWER_LINREG_OFFSET_DCDC_MODE) {
+ /* Powered by DC-DC, Linreg is on */
+ if (base & desc->enable_mask)
+ return HW_POWER_DCDC_LINREG_ON;
+
+ /* Powered by DC-DC, Linreg is off */
+ return HW_POWER_DCDC_LINREG_OFF;
+ }
+
+ return HW_POWER_UNKNOWN_SOURCE;
+}
+
+static int mxs_ldo_set_voltage_sel(struct regulator_dev *reg, unsigned sel)
+{
+ struct mxs_ldo_info *ldo = rdev_get_drvdata(reg);
+ struct regulator_desc *desc = &ldo->desc;
+ u32 status = 0;
+ u32 ctrl;
+ int timeout;
+ int ret;
+
+ ret = regmap_read(reg->regmap, HW_POWER_CTRL, &ctrl);
+ if (ret)
+ return ret;
+
+ ret = mxs_regmap_clr(reg->regmap, HW_POWER_CTRL, ldo->enirq_bo);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(reg->regmap, desc->vsel_reg, desc->vsel_mask,
+ sel);
+ if (ret)
+ goto restore_bo;
+
+ if (ldo->get_power_source) {
+ switch (ldo->get_power_source(ldo)) {
+ case HW_POWER_LINREG_DCDC_OFF:
+ case HW_POWER_LINREG_DCDC_READY:
+ /*
+ * Since the DC-DC converter is off we can't
+ * trigger on DC_OK. So wait at least 1 ms
+ * for stabilization.
+ */
+ usleep_range(1000, 2000);
+ ret = 0;
+ goto restore_bo;
+ }
+ }
+
+ /* Make sure DC_OK has changed */
+ usleep_range(15, 20);
+
+ for (timeout = 0; timeout < 20; timeout++) {
+ ret = regmap_read(reg->regmap, HW_POWER_STS, &status);
+
+ if (ret)
+ break;
+
+ /* DC-DC converter control loop has stabilized */
+ if (status & BM_POWER_STS_DC_OK)
+ goto restore_bo;
+
+ udelay(1);
+ }
+
+ if (!ret) {
+ ret = -ETIMEDOUT;
+ dev_warn_ratelimited(&reg->dev, "%s: timeout status=0x%08x\n",
+ __func__, status);
+ }
+
+ msleep(20);
+
+restore_bo:
+
+ mxs_regmap_clr(reg->regmap, HW_POWER_CTRL, ldo->irq_bo);
+
+ if (ctrl & ldo->enirq_bo)
+ mxs_regmap_set(reg->regmap, HW_POWER_CTRL, ldo->enirq_bo);
+
+ return ret;
+}
+
+static int mxs_ldo_get_status(struct regulator_dev *reg)
+{
+ struct mxs_ldo_info *ldo = rdev_get_drvdata(reg);
+
+ if (ldo->get_power_source) {
+ switch (ldo->get_power_source(ldo)) {
+ case HW_POWER_LINREG_DCDC_OFF:
+ case HW_POWER_LINREG_DCDC_READY:
+ case HW_POWER_DCDC_LINREG_ON:
+ return REGULATOR_STATUS_ON;
+ case HW_POWER_DCDC_LINREG_OFF:
+ case HW_POWER_DCDC_LINREG_READY:
+ return REGULATOR_STATUS_OFF;
+ }
+ }
+
+ return REGULATOR_STATUS_UNDEFINED;
+}
+
+static struct regulator_ops mxs_vddio_ops = {
+ .list_voltage = regulator_list_voltage_linear,
+ .map_voltage = regulator_map_voltage_linear,
+ .set_voltage_sel = mxs_ldo_set_voltage_sel,
+ .get_voltage_sel = regulator_get_voltage_sel_regmap,
+ .is_enabled = regulator_is_enabled_regmap,
+ .get_status = mxs_ldo_get_status,
+};
+
+static struct regulator_ops mxs_vdda_vddd_ops = {
+ .list_voltage = regulator_list_voltage_linear,
+ .map_voltage = regulator_map_voltage_linear,
+ .set_voltage_sel = mxs_ldo_set_voltage_sel,
+ .get_voltage_sel = regulator_get_voltage_sel_regmap,
+};
+
+static const struct mxs_ldo_info imx23_info_vddio = {
+ .desc = {
+ .name = "vddio",
+ .id = MXS_POWER_VDDIO,
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+ .n_voltages = 0x20,
+ .uV_step = 25000,
+ .linear_min_sel = 0,
+ .min_uV = 2800000,
+ .vsel_reg = HW_POWER_VDDIOCTRL,
+ .vsel_mask = 0x1f,
+ .ops = &mxs_vddio_ops,
+ .enable_reg = HW_POWER_5VCTRL,
+ .enable_mask = 1 << 2,
+ .enable_is_inverted = true,
+ },
+ .ctrl_reg = HW_POWER_VDDIOCTRL,
+ .disable_fet_mask = 1 << 16,
+ .disable_stepping_mask = 1 << 17,
+ .linreg_offset_mask = 3 << 12,
+ .linreg_offset_shift = 12,
+ .bo_offset_mask = 7 << 8,
+ .bo_offset_shift = 8,
+ .irq_bo = 1 << 11,
+ .enirq_bo = 1 << 10,
+ .get_power_source = get_vddio_power_source,
+};
+
+static const struct mxs_ldo_info imx28_info_vddio = {
+ .desc = {
+ .name = "vddio",
+ .id = MXS_POWER_VDDIO,
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+ .n_voltages = 0x11,
+ .uV_step = 50000,
+ .linear_min_sel = 0,
+ .min_uV = 2800000,
+ .vsel_reg = HW_POWER_VDDIOCTRL,
+ .vsel_mask = 0x1f,
+ .ops = &mxs_vddio_ops,
+ .enable_reg = HW_POWER_5VCTRL,
+ .enable_mask = 1 << 2,
+ .enable_is_inverted = true,
+ },
+ .ctrl_reg = HW_POWER_VDDIOCTRL,
+ .disable_fet_mask = 1 << 16,
+ .disable_stepping_mask = 1 << 17,
+ .linreg_offset_mask = 3 << 12,
+ .linreg_offset_shift = 12,
+ .bo_offset_mask = 7 << 8,
+ .bo_offset_shift = 8,
+ .irq_bo = 1 << 11,
+ .enirq_bo = 1 << 10,
+ .get_power_source = get_vddio_power_source,
+};
+
+static const struct mxs_ldo_info mxs_info_vdda = {
+ .desc = {
+ .name = "vdda",
+ .id = MXS_POWER_VDDA,
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+ .n_voltages = 0x20,
+ .uV_step = 25000,
+ .linear_min_sel = 0,
+ .min_uV = 1500000,
+ .vsel_reg = HW_POWER_VDDACTRL,
+ .vsel_mask = 0x1f,
+ .ops = &mxs_vdda_vddd_ops,
+ .enable_reg = HW_POWER_VDDACTRL,
+ .enable_mask = 1 << 17,
+ },
+ .ctrl_reg = HW_POWER_VDDACTRL,
+ .disable_fet_mask = 1 << 16,
+ .disable_stepping_mask = 1 << 18,
+ .linreg_offset_mask = 3 << 12,
+ .linreg_offset_shift = 12,
+ .bo_offset_mask = 7 << 8,
+ .bo_offset_shift = 8,
+ .irq_bo = 1 << 9,
+ .enirq_bo = 1 << 8,
+ .get_power_source = get_vdda_vddd_power_source,
+};
+
+static const struct mxs_ldo_info mxs_info_vddd = {
+ .desc = {
+ .name = "vddd",
+ .id = MXS_POWER_VDDD,
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+ .n_voltages = 0x20,
+ .uV_step = 25000,
+ .linear_min_sel = 0,
+ .min_uV = 800000,
+ .vsel_reg = HW_POWER_VDDDCTRL,
+ .vsel_mask = 0x1f,
+ .ops = &mxs_vdda_vddd_ops,
+ .enable_reg = HW_POWER_VDDDCTRL,
+ .enable_mask = 1 << 21,
+ },
+ .ctrl_reg = HW_POWER_VDDDCTRL,
+ .disable_fet_mask = 1 << 20,
+ .disable_stepping_mask = 1 << 22,
+ .linreg_offset_mask = 3 << 16,
+ .linreg_offset_shift = 16,
+ .bo_offset_mask = 7 << 8,
+ .bo_offset_shift = 8,
+ .irq_bo = 1 << 7,
+ .enirq_bo = 1 << 6,
+ .get_power_source = get_vdda_vddd_power_source,
+};
+
+static const struct of_device_id of_mxs_regulator_ldo_match[] = {
+ { .compatible = "fsl,imx23-vddio", .data = &imx23_info_vddio },
+ { .compatible = "fsl,imx23-vdda", .data = &mxs_info_vdda },
+ { .compatible = "fsl,imx23-vddd", .data = &mxs_info_vddd },
+ { .compatible = "fsl,imx28-vddio", .data = &imx28_info_vddio },
+ { .compatible = "fsl,imx28-vdda", .data = &mxs_info_vdda },
+ { .compatible = "fsl,imx28-vddd", .data = &mxs_info_vddd },
+ { /* end */ }
+};
+MODULE_DEVICE_TABLE(of, of_mxs_regulator_ldo_match);
+
+static int mxs_regulator_ldo_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ const struct of_device_id *match;
+ struct device_node *parent_np;
+ struct regulator_dev *rdev = NULL;
+ struct mxs_ldo_info *info;
+ struct regulator_init_data *initdata;
+ struct regulator_config config = { };
+
+ match = of_match_device(of_mxs_regulator_ldo_match, dev);
+ if (!match) {
+ /* We do not expect this to happen */
+ dev_err(dev, "%s: Unable to match device\n", __func__);
+ return -ENODEV;
+ }
+
+ info = devm_kmemdup(dev, match->data, sizeof(struct mxs_ldo_info),
+ GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ initdata = of_get_regulator_init_data(dev, dev->of_node, &info->desc);
+
+ parent_np = of_get_parent(dev->of_node);
+ if (!parent_np)
+ return -ENODEV;
+ config.regmap = syscon_node_to_regmap(parent_np);
+ of_node_put(parent_np);
+ if (IS_ERR(config.regmap))
+ return PTR_ERR(config.regmap);
+
+ info->regmap = config.regmap;
+ config.dev = dev;
+ config.init_data = initdata;
+ config.driver_data = info;
+ config.of_node = dev->of_node;
+
+ rdev = devm_regulator_register(dev, &info->desc, &config);
+ if (IS_ERR(rdev)) {
+ int ret = PTR_ERR(rdev);
+
+ dev_err(dev, "%s: failed to register regulator(%d)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ if (info->get_power_source) {
+ u8 source = info->get_power_source(info);
+
+ if (source == HW_POWER_UNKNOWN_SOURCE)
+ dev_warn(dev, "%s: Invalid power source\n",
+ info->desc.name);
+ else
+ dev_info(dev, "%s: Current power source (%u)\n",
+ info->desc.name, source);
+ }
+
+ return 0;
+}
+
+static struct platform_driver mxs_regulator_ldo_driver = {
+ .driver = {
+ .name = "mxs_regulator_ldo",
+ .of_match_table = of_mxs_regulator_ldo_match,
+ },
+ .probe = mxs_regulator_ldo_probe,
+};
+
+module_platform_driver(mxs_regulator_ldo_driver);
+
+MODULE_AUTHOR("Stefan Wahren <stefan.wahren@i2se.com>");
+MODULE_DESCRIPTION("Freescale MXS on-chip LDO driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:mxs_regulator_ldo");
--
2.7.4
From 9ceee681aba63681ada6ac58123f57ae2f48d511 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=B6rg=20Krause?= <joerg.krause@embedded.rocks>
Date: Tue, 22 Mar 2016 10:36:46 +0100
Subject: [PATCH 102/102] mxs: dt: enable regulator
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Jörg Krause <joerg.krause@embedded.rocks>
---
arch/arm/boot/dts/imx28.dtsi | 56 +++++++++++++++++++++++++++++++++++++++++---
1 file changed, 53 insertions(+), 3 deletions(-)
diff --git a/arch/arm/boot/dts/imx28.dtsi b/arch/arm/boot/dts/imx28.dtsi
index c5b57d4..f08a667 100644
--- a/arch/arm/boot/dts/imx28.dtsi
+++ b/arch/arm/boot/dts/imx28.dtsi
@@ -38,12 +38,24 @@
};
cpus {
- #address-cells = <0>;
+ #address-cells = <1>;
#size-cells = <0>;
- cpu {
+ cpu@0 {
compatible = "arm,arm926ej-s";
device_type = "cpu";
+ reg = <0x0>;
+ operating-points = <
+ /* kHz uV */
+ 64000 1300000
+ 261819 1350000
+ 360000 1350000
+ 392728 1450000
+ 454737 1550000
+ >;
+ clocks = <&clks 21>;
+ clock-latency = <61036>; /* two CLK32 periods */
+ cpu-supply = <&reg_vddd>;
};
};
@@ -1037,8 +1049,46 @@
};
power: power@80044000 {
+ compatible = "fsl,imx28-power", "syscon";
reg = <0x80044000 0x2000>;
- status = "disabled";
+ interrupts = <6>;
+
+ reg_vddd: regulator@1 {
+ compatible = "fsl,imx28-vddd";
+ regulator-name = "vddd";
+ regulator-min-microvolt = <1300000>;
+ regulator-max-microvolt = <1550000>;
+ vddd-supply = <&reg_vdda>;
+ regulator-boot-on;
+ regulator-always-on;
+ };
+
+ reg_vdda: regulator@2 {
+ compatible = "fsl,imx28-vdda";
+ regulator-name = "vdda";
+ regulator-min-microvolt = <1725000>;
+ regulator-max-microvolt = <1950000>;
+ vdda-supply = <&reg_vddio>;
+ regulator-boot-on;
+ regulator-always-on;
+ };
+
+ reg_vddio: regulator@3 {
+ compatible = "fsl,imx28-vddio";
+ regulator-name = "vddio";
+ regulator-min-microvolt = <3000000>;
+ regulator-max-microvolt = <3550000>;
+ regulator-microvolt-offset = <80000>;
+ regulator-boot-on;
+ regulator-always-on;
+ };
+
+ dcdc: regulator@4 {
+ compatible = "fsl,imx28-dcdc";
+ regulator-name = "dcdc";
+ regulator-boot-on;
+ regulator-always-on;
+ };
};
saif1: saif@80046000 {
--
2.7.4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.