linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Milo Kim <milo.kim@ti.com>
To: <devicetree@vger.kernel.org>, <lee.jones@linaro.org>,
	<linux-kernel@vger.kernel.org>
Cc: Milo Kim <milo.kim@ti.com>, Guenter Roeck <linux@roeck-us.net>,
	Jean Delvare <jdelvare@suse.com>, <lm-sensors@lm-sensors.org>
Subject: [PATCH RESEND 14/16] hwmon: add TI LMU hardware fault monitoring driver
Date: Mon, 2 Nov 2015 14:24:33 +0900	[thread overview]
Message-ID: <1446441875-1256-15-git-send-email-milo.kim@ti.com> (raw)
In-Reply-To: <1446441875-1256-1-git-send-email-milo.kim@ti.com>

LM3633 and LM3697 are TI LMU MFD device.
Those device have hardware monitoring feature which detects opened or
shorted circuit case.

Attributes
----------
  Two attributes are registered.
    open_fault:  check backlight output channel is opened or not
    short_fault: check backlight output channel is shorted or not

  With register R/W operations, LMU HWMON driver checks the
  status of backlight output channels.
  LM3633 and LM3697 have same sequence to check channels, so common
  functions are used.

Operations
----------
  Two devices have common control flow but register addresses are different.
  The structure, 'ti_lmu_reg' is used for device configuration.

HWMON notifier
--------------
  After LMU HWMON operation is done, backlight device should be
  reinitialized. LMU HWMON driver notifies an event as soon as
  the monitoring is done. Then, LM3633 and LM3697 backlight driver handles
  this event.

Cc: Guenter Roeck <linux@roeck-us.net>
Cc: Jean Delvare <jdelvare@suse.com>
Cc: Lee Jones <lee.jones@linaro.org>
Cc: devicetree@vger.kernel.org
Cc: lm-sensors@lm-sensors.org
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Milo Kim <milo.kim@ti.com>
---
 drivers/hwmon/Kconfig        |  10 ++
 drivers/hwmon/Makefile       |   1 +
 drivers/hwmon/ti-lmu-hwmon.c | 393 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 404 insertions(+)
 create mode 100644 drivers/hwmon/ti-lmu-hwmon.c

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index e13c902..989e1fa 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1483,6 +1483,16 @@ config SENSORS_THMC50
 	  This driver can also be built as a module.  If so, the module
 	  will be called thmc50.
 
+config SENSORS_TI_LMU
+	tristate "Texas Instruments LMU Hardware Fault Monitoring Driver"
+	depends on MFD_TI_LMU
+	help
+	  Say Y here to include support for the TI LMU opened and shorted
+	  circuit fault detection.
+
+	  This driver can also be built as a module. If so the module
+	  will be called ti-lmu-hwmon.
+
 config SENSORS_TMP102
 	tristate "Texas Instruments TMP102"
 	depends on I2C
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 9e0f3dd..de11b74 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -143,6 +143,7 @@ obj-$(CONFIG_SENSORS_SMSC47M192)+= smsc47m192.o
 obj-$(CONFIG_SENSORS_AMC6821)	+= amc6821.o
 obj-$(CONFIG_SENSORS_TC74)	+= tc74.o
 obj-$(CONFIG_SENSORS_THMC50)	+= thmc50.o
+obj-$(CONFIG_SENSORS_TI_LMU)	+= ti-lmu-hwmon.o
 obj-$(CONFIG_SENSORS_TMP102)	+= tmp102.o
 obj-$(CONFIG_SENSORS_TMP103)	+= tmp103.o
 obj-$(CONFIG_SENSORS_TMP401)	+= tmp401.o
diff --git a/drivers/hwmon/ti-lmu-hwmon.c b/drivers/hwmon/ti-lmu-hwmon.c
new file mode 100644
index 0000000..c297cf9
--- /dev/null
+++ b/drivers/hwmon/ti-lmu-hwmon.c
@@ -0,0 +1,393 @@
+/*
+ * TI LMU(Lighting Management Unit) Hardware Fault Monitoring Driver
+ *
+ * Copyright 2015 Texas Instruments
+ *
+ * Author: Milo Kim <milo.kim@ti.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/hwmon.h>
+#include <linux/kernel.h>
+#include <linux/mfd/ti-lmu.h>
+#include <linux/mfd/ti-lmu-register.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+
+#define LMU_BANK_MASK(n)		(~BIT(n) & 0x07)
+#define LMU_BL_MAX_CHANNELS		3
+#define LMU_DEFAULT_BANK		0
+#define LMU_DELAY_STARTUP		500
+#define LMU_DELAY_FEEDBACK		5
+#define LMU_ENABLE_FEEDBACK		(BIT(0) | BIT(1) | BIT(2))
+#define LMU_MAX_BRIGHTNESS		0xFF
+#define LMU_NO_RAMP			0
+
+enum ti_lmu_hwmon_id {
+	LMU_HWMON_OPEN,
+	LMU_HWMON_SHORT,
+};
+
+/**
+ * struct ti_lmu_reg
+ *
+ * @monitor:		Enable monitoring register
+ * @bank:		Bank configuration register
+ * @ramp:		Ramp(speed) configuration register
+ * @imax:		Current limit setting register
+ * @feedback:		Feedback enable register
+ * @brightness:		Brightness register
+ * @enable:		Bank enable register
+ * @open_fault:		Detect opened circuit status register
+ * @short_fault:	Detect shorted circuit status register
+ *
+ * To detect hardware fault, several registers are used.
+ * Device specific register addresses are configured in this structure.
+ */
+struct ti_lmu_reg {
+	u8 monitor;
+	u8 bank;
+	u8 ramp;
+	u8 imax;
+	u8 feedback;
+	u8 brightness;
+	u8 enable;
+	u8 open_fault;
+	u8 short_fault;
+};
+
+struct ti_lmu_hwmon {
+	struct ti_lmu *lmu;
+	struct device *dev;	/* hwmon dev */
+	const struct ti_lmu_reg *regs;
+};
+
+struct ti_lmu_hwmon_data {
+	const char *name;
+	const struct attribute_group **groups;
+	const struct ti_lmu_reg *regs;
+};
+
+static void ti_lmu_hwmon_reset_device(struct ti_lmu_hwmon *hwmon)
+{
+	unsigned int en_gpio = hwmon->lmu->en_gpio;
+
+	/* POR(power on reset) by enable pin control */
+	gpio_set_value(en_gpio, 0);
+	msleep(LMU_DELAY_STARTUP);
+
+	gpio_set_value(en_gpio, 1);
+	msleep(LMU_DELAY_STARTUP);
+}
+
+static int ti_lmu_hwmon_enable_monitoring(struct ti_lmu_hwmon *hwmon,
+					  enum ti_lmu_hwmon_id id)
+{
+	struct ti_lmu *lmu = hwmon->lmu;
+	u8 reg = hwmon->regs->monitor;
+
+	if (id == LMU_HWMON_OPEN)
+		return ti_lmu_write_byte(lmu, reg, BIT(0));
+	else if (id == LMU_HWMON_SHORT)
+		return ti_lmu_write_byte(lmu, reg, BIT(1));
+	else
+		return -EINVAL;
+}
+
+static int ti_lmu_hwmon_assign_bank(struct ti_lmu_hwmon *hwmon, u8 val)
+{
+	return ti_lmu_write_byte(hwmon->lmu, hwmon->regs->bank, val);
+}
+
+static int ti_lmu_hwmon_channel_config(struct ti_lmu_hwmon *hwmon)
+{
+	struct ti_lmu *lmu = hwmon->lmu;
+	const struct ti_lmu_reg *reg = hwmon->regs;
+	int ret;
+
+	/* Set ramp time to the fatest setting */
+	ret = ti_lmu_write_byte(lmu, reg->ramp, LMU_NO_RAMP);
+	if (ret)
+		return ret;
+
+	/* Set max current to 20mA */
+	ret = ti_lmu_write_byte(lmu, reg->imax, LMU_IMAX_20mA);
+	if (ret)
+		return ret;
+
+	/* Enable feedback */
+	ret = ti_lmu_write_byte(lmu, reg->feedback, LMU_ENABLE_FEEDBACK);
+	if (ret)
+		return ret;
+
+	/* Set max brightness */
+	ret = ti_lmu_write_byte(lmu, reg->brightness, LMU_MAX_BRIGHTNESS);
+	if (ret)
+		return ret;
+
+	/* Enable a bank */
+	ret = ti_lmu_write_byte(lmu, reg->enable, 1);
+	if (ret)
+		return ret;
+
+	/* Wait until device completes fault detection */
+	msleep(LMU_DELAY_FEEDBACK);
+
+	return 0;
+}
+
+static int ti_lmu_hwmon_get_open_fault_result(struct ti_lmu_hwmon *hwmon,
+					      char *result)
+{
+	int ret, channel, len, offset = 0;
+	u8 status = 0;
+
+	ret = ti_lmu_read_byte(hwmon->lmu, hwmon->regs->open_fault, &status);
+	if (ret)
+		return ret;
+
+	for (channel = 0; channel < LMU_BL_MAX_CHANNELS; channel++) {
+		if (BIT(channel) & status)
+			len = sprintf(&result[offset], "Channel %d is opened\n",
+				      channel);
+		else
+			len = sprintf(&result[offset], "Channel %d works\n",
+				      channel);
+
+		offset += len;
+	}
+
+	return 0;
+}
+
+static ssize_t ti_lmu_hwmon_get_short_fault_result(struct ti_lmu_hwmon *hwmon,
+						   char *buf, int channel)
+{
+	int ret;
+	u8 status = 0;
+
+	ret = ti_lmu_read_byte(hwmon->lmu, hwmon->regs->short_fault, &status);
+	if (ret)
+		return ret;
+
+	if (BIT(channel) & status)
+		return sprintf(buf, "Channel %d is shorted\n", channel);
+	else
+		return sprintf(buf, "Channel %d works\n", channel);
+}
+
+static int ti_lmu_hwmon_disable_all_banks(struct ti_lmu_hwmon *hwmon)
+{
+	return ti_lmu_write_byte(hwmon->lmu, hwmon->regs->enable, 0);
+}
+
+static int ti_lmu_hwmon_notifier_call_chain(struct ti_lmu_hwmon *hwmon)
+{
+	int ret;
+
+	ti_lmu_hwmon_reset_device(hwmon);
+
+	ret = blocking_notifier_call_chain(&hwmon->lmu->notifier,
+					   LMU_EVENT_HWMON_DONE, NULL);
+	if (ret == NOTIFY_OK || ret == NOTIFY_DONE)
+		return 0;
+	else
+		return -EINVAL;
+}
+
+static ssize_t ti_lmu_hwmon_open_fault_show(struct device *dev,
+					    struct device_attribute *attr,
+					    char *buffer)
+{
+	struct ti_lmu_hwmon *hwmon = dev_get_drvdata(dev);
+	char result[100];
+	int ret;
+
+	/* Device should be reset prior to fault detection */
+	ti_lmu_hwmon_reset_device(hwmon);
+
+	ret = ti_lmu_hwmon_enable_monitoring(hwmon, LMU_HWMON_OPEN);
+	if (ret)
+		return ret;
+
+	ret = ti_lmu_hwmon_assign_bank(hwmon, LMU_DEFAULT_BANK);
+	if (ret)
+		return ret;
+
+	ret = ti_lmu_hwmon_channel_config(hwmon);
+	if (ret)
+		return ret;
+
+	memset(result, 0, sizeof(result));
+	ret = ti_lmu_hwmon_get_open_fault_result(hwmon, result);
+	if (ret)
+		return ret;
+
+	/* Notify an event */
+	ret = ti_lmu_hwmon_notifier_call_chain(hwmon);
+	if (ret)
+		dev_warn(dev, "Notify hwmon err\n");
+
+	return snprintf(buffer, PAGE_SIZE, "%s\n", result);
+}
+
+static ssize_t ti_lmu_hwmon_short_fault_show(struct device *dev,
+					     struct device_attribute *attr,
+					     char *buffer)
+{
+	struct ti_lmu_hwmon *hwmon = dev_get_drvdata(dev);
+	int ret, i, len, offset = 0;
+	char result[100];
+
+	/* Device should be reset prior to fault detection */
+	ti_lmu_hwmon_reset_device(hwmon);
+
+	ret = ti_lmu_hwmon_enable_monitoring(hwmon, LMU_HWMON_SHORT);
+	if (ret)
+		return ret;
+
+	memset(result, 0, sizeof(result));
+
+	/* Shorted circuit detection is done by checking the bank one by one */
+	for (i = 0; i < LMU_BL_MAX_CHANNELS; i++) {
+		ret = ti_lmu_hwmon_assign_bank(hwmon, LMU_BANK_MASK(i));
+		if (ret)
+			return ret;
+
+		ret = ti_lmu_hwmon_channel_config(hwmon);
+		if (ret)
+			return ret;
+
+		len = ti_lmu_hwmon_get_short_fault_result(hwmon,
+							  &result[offset], i);
+		if (len < 0)
+			return len;
+
+		offset += len;
+
+		ret = ti_lmu_hwmon_disable_all_banks(hwmon);
+		if (ret)
+			return ret;
+	}
+
+	/* Notify an event */
+	ret = ti_lmu_hwmon_notifier_call_chain(hwmon);
+	if (ret)
+		dev_warn(dev, "Notify hwmon err\n");
+
+	return snprintf(buffer, PAGE_SIZE, "%s\n", result);
+}
+
+static DEVICE_ATTR(open_fault, S_IRUGO, ti_lmu_hwmon_open_fault_show, NULL);
+static DEVICE_ATTR(short_fault, S_IRUGO, ti_lmu_hwmon_short_fault_show, NULL);
+
+static struct attribute *ti_lmu_hwmon_attrs[] = {
+	&dev_attr_open_fault.attr,
+	&dev_attr_short_fault.attr,
+	NULL,
+};
+ATTRIBUTE_GROUPS(ti_lmu_hwmon);
+
+/*
+ * Operations are dependent on the device.
+ * Device registers configuration is required.
+ */
+static const struct ti_lmu_reg lm3633_regs = {
+	.monitor	= LM3633_REG_MONITOR_ENABLE,
+	.bank		= LM3633_REG_HVLED_OUTPUT_CFG,
+	.ramp		= LM3633_REG_BL0_RAMPUP,
+	.imax		= LM3633_REG_IMAX_HVLED_A,
+	.feedback	= LM3633_REG_BL_FEEDBACK_ENABLE,
+	.brightness	= LM3633_REG_BRT_HVLED_A_MSB,
+	.enable		= LM3633_REG_ENABLE,
+	.open_fault	= LM3633_REG_BL_OPEN_FAULT_STATUS,
+	.short_fault	= LM3633_REG_BL_SHORT_FAULT_STATUS,
+};
+
+static const struct ti_lmu_reg lm3697_regs = {
+	.monitor	= LM3697_REG_MONITOR_ENABLE,
+	.bank		= LM3697_REG_HVLED_OUTPUT_CFG,
+	.ramp		= LM3697_REG_BL0_RAMPUP,
+	.imax		= LM3697_REG_IMAX_A,
+	.feedback	= LM3697_REG_FEEDBACK_ENABLE,
+	.brightness	= LM3697_REG_BRT_A_MSB,
+	.enable		= LM3697_REG_ENABLE,
+	.open_fault	= LM3697_REG_OPEN_FAULT_STATUS,
+	.short_fault	= LM3697_REG_SHORT_FAULT_STATUS,
+};
+
+static const struct ti_lmu_hwmon_data lm3633_hwmon_data = {
+	.name = "lm3633_fault_status",
+	.groups = ti_lmu_hwmon_groups,
+	.regs = &lm3633_regs,
+};
+
+static const struct ti_lmu_hwmon_data lm3697_hwmon_data = {
+	.name = "lm3697_fault_status",
+	.groups = ti_lmu_hwmon_groups,
+	.regs = &lm3697_regs,
+};
+
+static const struct of_device_id ti_lmu_hwmon_of_match[] = {
+	{ .compatible = "ti,lm3633-hwmon", .data = &lm3633_hwmon_data },
+	{ .compatible = "ti,lm3697-hwmon", .data = &lm3697_hwmon_data },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, ti_lmu_hwmon_of_match);
+
+static int ti_lmu_hwmon_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct ti_lmu *lmu = dev_get_drvdata(dev->parent);
+	const struct of_device_id *match;
+	struct ti_lmu_hwmon *hwmon;
+	const struct ti_lmu_hwmon_data *data;
+
+	match = of_match_device(ti_lmu_hwmon_of_match, dev);
+	if (!match)
+		return -ENODEV;
+
+	/* To monitor hardware fault, enable pin control should be required. */
+	if (!gpio_is_valid(lmu->en_gpio))
+		return -EINVAL;
+
+	hwmon = devm_kzalloc(dev, sizeof(*hwmon), GFP_KERNEL);
+	if (!hwmon)
+		return -ENOMEM;
+
+	/*
+	 * Get device specific data(name, groups and registers) from
+	 * of_match table.
+	 */
+	data = (struct ti_lmu_hwmon_data *)match->data;
+	hwmon->lmu = lmu;
+	hwmon->regs = data->regs;
+
+	hwmon->dev = devm_hwmon_device_register_with_groups(dev, data->name,
+							hwmon, data->groups);
+
+	return PTR_ERR_OR_ZERO(hwmon->dev);
+}
+
+static struct platform_driver ti_lmu_hwmon_driver = {
+	.probe = ti_lmu_hwmon_probe,
+	.driver = {
+		.name = "ti-lmu-hwmon",
+		.of_match_table = ti_lmu_hwmon_of_match,
+	},
+};
+
+module_platform_driver(ti_lmu_hwmon_driver);
+
+MODULE_DESCRIPTION("TI LMU Hardware Fault Monitoring Driver");
+MODULE_AUTHOR("Milo Kim");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:ti-lmu-hwmon");
-- 
1.9.1


  parent reply	other threads:[~2015-11-02  5:26 UTC|newest]

Thread overview: 58+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2015-11-02  5:24 [PATCH RESEND 00/16] Support TI LMU devices Milo Kim
2015-11-02  5:24 ` [PATCH RESEND 01/16] Documentation: dt-bindings: mfd: add TI LMU device binding information Milo Kim
2015-11-06  2:00   ` Rob Herring
2015-11-11  9:49   ` Lee Jones
2015-11-12  0:05     ` Kim, Milo
2015-11-02  5:24 ` [PATCH RESEND 02/16] Documentation: dt-bindings: backlight: add TI LMU backlight " Milo Kim
2015-11-02 15:02   ` Rob Herring
2015-11-03  7:13     ` Kim, Milo
2015-11-03 15:32       ` Rob Herring
2015-11-02  5:24 ` [PATCH RESEND 03/16] Documentation: dt-bindings: hwmon: add TI LMU HWMON " Milo Kim
2015-11-06  1:57   ` Rob Herring
2015-11-06  3:48     ` Kim, Milo
2015-11-02  5:24 ` [PATCH RESEND 04/16] Documentation: dt-bindings: leds: add LM3633 LED " Milo Kim
2015-11-03 16:15   ` Jacek Anaszewski
2015-11-10  7:01     ` Kim, Milo
2015-11-02  5:24 ` [PATCH RESEND 05/16] Documentation: dt-bindings: regulator: add LM363x regulator " Milo Kim
2015-11-02  5:24 ` [PATCH RESEND 06/16] mfd: add TI LMU driver Milo Kim
2015-11-23 10:30   ` Lee Jones
2015-11-24  2:35     ` Kim, Milo
2015-11-24  6:39       ` Kim, Milo
2015-11-24  8:18         ` Lee Jones
2015-11-25  8:10           ` Kim, Milo
2015-11-25  8:15             ` Lee Jones
2015-11-02  5:24 ` [PATCH RESEND 07/16] backlight: add TI LMU backlight common driver Milo Kim
2015-11-02  5:24 ` [PATCH RESEND 08/16] backlight: ti-lmu-backlight: add LM3532 driver Milo Kim
2015-11-02  5:37   ` kbuild test robot
2015-11-02  7:33     ` Kim, Milo
2015-11-02  5:24 ` [PATCH RESEND 09/16] backlight: ti-lmu-backlight: add LM3631 driver Milo Kim
2015-11-02  5:24 ` [PATCH RESEND 10/16] backlight: ti-lmu-backlight: add LM3632 driver Milo Kim
2015-11-02  5:24 ` [PATCH RESEND 11/16] backlight: ti-lmu-backlight: add LM3633 driver Milo Kim
2015-11-02  5:24 ` [PATCH RESEND 12/16] backlight: ti-lmu-backlight: add LM3695 driver Milo Kim
2015-11-02  5:24 ` [PATCH RESEND 13/16] backlight: ti-lmu-backlight: add LM3697 driver Milo Kim
2015-11-02  5:24 ` Milo Kim [this message]
2015-11-02 14:27   ` [PATCH RESEND 14/16] hwmon: add TI LMU hardware fault monitoring driver Guenter Roeck
2015-11-03  7:01     ` Kim, Milo
2015-11-02  5:24 ` [PATCH RESEND 15/16] leds: add LM3633 driver Milo Kim
2015-11-03 16:15   ` Jacek Anaszewski
2015-11-10  7:38     ` Kim, Milo
2015-11-10 13:44       ` Jacek Anaszewski
2015-11-11  2:16         ` Kim, Milo
2015-11-12  9:04           ` Jacek Anaszewski
2015-11-20  9:22       ` Jacek Anaszewski
2015-11-22 23:40         ` Kim, Milo
2015-11-23 11:17           ` Jacek Anaszewski
2015-11-02  5:24 ` [PATCH RESEND 16/16] regulator: add LM363X driver Milo Kim
2015-11-02 12:26   ` Mark Brown
2015-11-03  6:59     ` Kim, Milo
2015-11-04 13:59   ` Mark Brown
2015-11-10  7:54     ` Kim, Milo
2015-11-02  8:59 ` [PATCH RESEND 00/16] Support TI LMU devices Lee Jones
2015-11-03  6:52   ` Kim, Milo
2015-11-03  8:33     ` Lee Jones
2015-11-03  9:08       ` Kim, Milo
2015-11-25  8:51       ` Kim, Milo
2015-11-25  9:05         ` Lee Jones
2015-11-02  9:00 ` Lee Jones
2015-11-03  6:56   ` Kim, Milo
2015-11-03  8:35     ` Lee Jones

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1446441875-1256-15-git-send-email-milo.kim@ti.com \
    --to=milo.kim@ti.com \
    --cc=devicetree@vger.kernel.org \
    --cc=jdelvare@suse.com \
    --cc=lee.jones@linaro.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux@roeck-us.net \
    --cc=lm-sensors@lm-sensors.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).