|
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 |