/*
 * Copyright (C) ST-Ericsson 2010 - 2013
 * Author: Martin Persson <martin.persson@stericsson.com>
 *         Hongbo Zhang <hongbo.zhang@linaro.org>
 * License Terms: GNU General Public License v2
 *
 * ABX500 does not provide auto ADC, so to monitor the required temperatures,
 * a periodic work is used. It is more important to not wake up the CPU than
 * to perform this job, hence the use of a deferred delay.
 *
 * A deferred delay for thermal monitor is considered safe because:
 * If the chip gets too hot during a sleep state it's most likely due to
 * external factors, such as the surrounding temperature. I.e. no SW decisions
 * will make any difference.
 */

#include <linux/err.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/interrupt.h>
#include <linux/jiffies.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/slab.h>
#include <linux/sysfs.h>
#include <linux/workqueue.h>
#include "abx500.h"

#define DEFAULT_MONITOR_DELAY	HZ
#define DEFAULT_MAX_TEMP	130

static inline void schedule_monitor(struct abx500_temp *data)
{
	data->work_active = true;
	schedule_delayed_work(&data->work, DEFAULT_MONITOR_DELAY);
}

static void threshold_updated(struct abx500_temp *data)
{
	int i;
	for (i = 0; i < data->monitored_sensors; i++)
		if (data->max[i] != 0 || data->min[i] != 0) {
			schedule_monitor(data);
			return;
		}

	dev_dbg(&data->pdev->dev, "No active thresholds.\n");
	cancel_delayed_work_sync(&data->work);
	data->work_active = false;
}

static void gpadc_monitor(struct work_struct *work)
{
	int temp, i, ret;
	char alarm_node[30];
	bool updated_min_alarm, updated_max_alarm;
	struct abx500_temp *data;

	data = container_of(work, struct abx500_temp, work.work);
	mutex_lock(&data->lock);

	for (i = 0; i < data->monitored_sensors; i++) {
		/* Thresholds are considered inactive if set to 0 */
		if (data->max[i] == 0 && data->min[i] == 0)
			continue;

		if (data->max[i] < data->min[i])
			continue;

		ret = data->ops.read_sensor(data, data->gpadc_addr[i], &temp);
		if (ret < 0) {
			dev_err(&data->pdev->dev, "GPADC read failed\n");
			continue;
		}

		updated_min_alarm = false;
		updated_max_alarm = false;

		if (data->min[i] != 0) {
			if (temp < data->min[i]) {
				if (data->min_alarm[i] == false) {
					data->min_alarm[i] = true;
					updated_min_alarm = true;
				}
			} else {
				if (data->min_alarm[i] == true) {
					data->min_alarm[i] = false;
					updated_min_alarm = true;
				}
			}
		}
		if (data->max[i] != 0) {
			if (temp > data->max[i]) {
				if (data->max_alarm[i] == false) {
					data->max_alarm[i] = true;
					updated_max_alarm = true;
				}
			} else if (temp < data->max[i] - data->max_hyst[i]) {
				if (data->max_alarm[i] == true) {
					data->max_alarm[i] = false;
					updated_max_alarm = true;
				}
			}
		}

		if (updated_min_alarm) {
			ret = sprintf(alarm_node, "temp%d_min_alarm", i + 1);
			sysfs_notify(&data->pdev->dev.kobj, NULL, alarm_node);
		}
		if (updated_max_alarm) {
			ret = sprintf(alarm_node, "temp%d_max_alarm", i + 1);
			sysfs_notify(&data->pdev->dev.kobj, NULL, alarm_node);
		}
	}

	schedule_monitor(data);
	mutex_unlock(&data->lock);
}

/* HWMON sysfs interfaces */
static ssize_t show_name(struct device *dev, struct device_attribute *devattr,
			 char *buf)
{
	struct abx500_temp *data = dev_get_drvdata(dev);
	/* Show chip name */
	return data->ops.show_name(dev, devattr, buf);
}

static ssize_t show_label(struct device *dev,
			  struct device_attribute *devattr, char *buf)
{
	struct abx500_temp *data = dev_get_drvdata(dev);
	/* Show each sensor label */
	return data->ops.show_label(dev, devattr, buf);
}

static ssize_t show_input(struct device *dev,
			  struct device_attribute *devattr, char *buf)
{
	int ret, temp;
	struct abx500_temp *data = dev_get_drvdata(dev);
	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
	u8 gpadc_addr = data->gpadc_addr[attr->index];

	ret = data->ops.read_sensor(data, gpadc_addr, &temp);
	if (ret < 0)
		return ret;

	return sprintf(buf, "%d\n", temp);
}

/* Set functions (RW nodes) */
static ssize_t set_min(struct device *dev, struct device_attribute *devattr,
		       const char *buf, size_t count)
{
	unsigned long val;
	struct abx500_temp *data = dev_get_drvdata(dev);
	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
	int res = kstrtol(buf, 10, &val);
	if (res < 0)
		return res;

	val = clamp_val(val, 0, DEFAULT_MAX_TEMP);

	mutex_lock(&data->lock);
	data->min[attr->index] = val;
	threshold_updated(data);
	mutex_unlock(&data->lock);

	return count;
}

static ssize_t set_max(struct device *dev, struct device_attribute *devattr,
		       const char *buf, size_t count)
{
	unsigned long val;
	struct abx500_temp *data = dev_get_drvdata(dev);
	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
	int res = kstrtol(buf, 10, &val);
	if (res < 0)
		return res;

	val = clamp_val(val, 0, DEFAULT_MAX_TEMP);

	mutex_lock(&data->lock);
	data->max[attr->index] = val;
	threshold_updated(data);
	mutex_unlock(&data->lock);

	return count;
}

static ssize_t set_max_hyst(struct device *dev,
			    struct device_attribute *devattr,
			    const char *buf, size_t count)
{
	unsigned long val;
	struct abx<style>.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */</style><div class="highlight"><pre><span></span>From: Fuel OPNFV &lt;fuel@opnfv.org&gt;
Date: Mon, 13 Jun 2016 22:23:57 +0200
Subject: [PATCH] OPNFV: showmenu=yes in isolinux.cfg

<span class="gd">---</span>
 iso/isolinux/isolinux.cfg | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

<span class="gh">diff --git a/iso/isolinux/isolinux.cfg b/iso/isolinux/isolinux.cfg</span>
<span class="gh">index c6b1ed9..77a4b18 100644</span>
<span class="gd">--- a/iso/isolinux/isolinux.cfg</span>
<span class="gi">+++ b/iso/isolinux/isolinux.cfg</span>
<span class="gu">@@ -19,9 +19,9 @@ label nailgun</span>
   menu label ^1. Fuel Install (Static IP)
   menu default
   kernel vmlinuz
<span class="gd">-  append initrd=initrd.img net.ifnames=0 biosdevname=0 inst.repo=cdrom:LABEL=will_be_substituted_with_ISO_VOLUME_ID:/ inst.ks=cdrom:LABEL=will_be_substituted_with_ISO_VOLUME_ID:/ks.cfg ip=10.20.0.2::10.20.0.1:255.255.255.0:fuel.domain.tld:eth0:off::: nameserver=10.20.0.1</span>
<span class="gi">+  append initrd=initrd.img net.ifnames=0 biosdevname=0 inst.repo=cdrom:LABEL=will_be_substituted_with_ISO_VOLUME_ID:/ inst.ks=cdrom:LABEL=will_be_substituted_with_ISO_VOLUME_ID:/ks.cfg ip=10.20.0.2::10.20.0.1:255.255.255.0:fuel.domain.tld:eth0:off::: nameserver=10.20.0.1 showmenu=yes</span>

 label nailgunifname
   menu label ^2. Fuel Advanced Install (Static IP)
   kernel vmlinuz
<span class="gd">-  append initrd=initrd.img inst.repo=cdrom:LABEL=will_be_substituted_with_ISO_VOLUME_ID:/ inst.ks=cdrom:LABEL=will_be_substituted_with_ISO_VOLUME_ID:/ks.cfg ip=10.20.0.2::10.20.0.1:255.255.255.0:fuel.domain.tld:adminif:off::: nameserver=10.20.0.1 ifname=adminif:XX:XX:XX:XX:XX:XX</span>
<span class="gi">+  append initrd=initrd.img inst.repo=cdrom:LABEL=will_be_substituted_with_ISO_VOLUME_ID:/ inst.ks=cdrom:LABEL=will_be_substituted_with_ISO_VOLUME_ID:/ks.cfg ip=10.20.0.2::10.20.0.1:255.255.255.0:fuel.domain.tld:adminif:off::: nameserver=10.20.0.1 ifname=adminif:XX:XX:XX:XX:XX:XX showmenu=yes</span>
</pre></div>
</code></pre></td></tr></table>
</div> <!-- class=content -->
<div id="lfcollabprojects-footer">
  <div class="gray-diagonal">
    <div class="footer-inner">
      <p>
        &copy; 2015
        <a href="https://opnfv.org/">Open Platform for NFV Project, Inc</a>., a
        Linux Foundation Collaborative Project. All Rights Reserved.
      </p>
      <p>
        Open Platform for NFV and OPNFV are trademarks of the Open Platform for
        NFV Project, Inc.
      </p>
      <p>
        Linux Foundation is a registered trademark of The Linux Foundation.
        Linux is a registered
        <a
          href="http://www.linuxfoundation.org/programs/legal/trademark"
          title="Linux Mark Institute"
          >trademark</a
        >
        of Linus Torvalds.
      </p>
      <p>
        Please see our
        <a href="https://opnfv.org/about/bylaws-and-policies/terms-use"
          >terms of use</a
        >,
        <a href="https://opnfv.org/about/bylaws-and-policies/trademarks"
          >trademark policy</a
        >, and
        <a href="https://opnfv.org/about/bylaws-and-policies/privacy-policy"
          >privacy policy</a
        >.
      </p>
    </div>
  </div>
</div>
</div> <!-- id=cgit -->
</body>
</html>
bj, &abx500_temp_group);

	return 0;
}

static int abx500_temp_suspend(struct platform_device *pdev,
			       pm_message_t state)
{
	struct abx500_temp *data = platform_get_drvdata(pdev);

	if (data->work_active)
		cancel_delayed_work_sync(&data->work);

	return 0;
}

static int abx500_temp_resume(struct platform_device *pdev)
{
	struct abx500_temp *data = platform_get_drvdata(pdev);

	if (data->work_active)
		schedule_monitor(data);

	return 0;
}

#ifdef CONFIG_OF
static const struct of_device_id abx500_temp_match[] = {
	{ .compatible = "stericsson,abx500-temp" },
	{},
};
MODULE_DEVICE_TABLE(of, abx500_temp_match);
#endif

static struct platform_driver abx500_temp_driver = {
	.driver = {
		.name = "abx500-temp",
		.of_match_table = of_match_ptr(abx500_temp_match),
	},
	.suspend = abx500_temp_suspend,
	.resume = abx500_temp_resume,
	.probe = abx500_temp_probe,
	.remove = abx500_temp_remove,
};

module_platform_driver(abx500_temp_driver);

MODULE_AUTHOR("Martin Persson <martin.persson@stericsson.com>");
MODULE_DESCRIPTION("ABX500 temperature driver");
MODULE_LICENSE("GPL");