summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard Zhu <r65037@freescale.com>2011-07-19 13:42:29 +0800
committerRichard Zhu <r65037@freescale.com>2011-07-22 16:30:26 +0800
commitbba25cc6940b12814bb7b4fe23ce61c12709f794 (patch)
treef15d7474758945f1fcc8915c4fc49fffbbdcd35b
parent82ce038afd90cb8f058101987e16a005161f74b9 (diff)
ENGR00153275-1 ahci L2638 add the standalone ahci temperature monitor
based on the 2.6.38 kernel mainline, refer to linux lm-sensors architeture, add the standalone ahci temperature monitor driver on fsl i.mx53 platforms. Less than half sencond is used in one temperature read operation. usage: Use the following cmd to cat the i.mx53 soc temperature after boot up i.mx53 system in user space. for example: ...$ cat /sys/class/hwmon/hwmon1/device/temp1_input 61000 or run the following cmd after configure the lm-sensors ...$ sensors imx-ahci-hwmon-isa-0000 Adapter: ISA adapter temp1: +58.0 C Signed-off-by: Richard Zhu <r65037@freescale.com>
-rw-r--r--drivers/hwmon/Kconfig7
-rw-r--r--drivers/hwmon/Makefile1
-rw-r--r--drivers/hwmon/imx_ahci_hwmon.c338
3 files changed, 346 insertions, 0 deletions
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 010625a26bbc..1b7f2f3f7947 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1208,6 +1208,13 @@ config SENSORS_MC13783_ADC
help
Support for the A/D converter on MC13783 PMIC.
+config SENSORS_IMX_AHCI
+ tristate "Freescale built-in AHCI temperature monitor"
+ depends on (ARCH_MX53 && IMX_HAVE_PLATFORM_AHCI)
+ help
+ If you say yes here you get support for the built-in AHCI of
+ the Freescale i.MX53 SoC
+
if ACPI
comment "ACPI drivers"
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index f3d50745e832..24b55ad69084 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -115,6 +115,7 @@ obj-$(CONFIG_SENSORS_WM8350) += wm8350-hwmon.o
obj-$(CONFIG_MXC_MMA8450) += mxc_mma8450.o
obj-$(CONFIG_MXC_MMA8451) += mxc_mma8451.o
obj-$(CONFIG_SENSORS_DA9052) += da9052-adc.o
+obj-$(CONFIG_SENSORS_IMX_AHCI) += imx_ahci_hwmon.o
ifeq ($(CONFIG_HWMON_DEBUG_CHIP),y)
EXTRA_CFLAGS += -DDEBUG
diff --git a/drivers/hwmon/imx_ahci_hwmon.c b/drivers/hwmon/imx_ahci_hwmon.c
new file mode 100644
index 000000000000..073a2d1697dc
--- /dev/null
+++ b/drivers/hwmon/imx_ahci_hwmon.c
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2011 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <linux/hwmon.h>
+
+#include <mach/mx53.h>
+#include <mach/ahci_sata.h>
+
+/**
+ * struct imx_ahci_hwmon - hwmon information
+ * @lock: Access lock to serialise the conversions.
+ * @hwmon_dev: The hwmon device we created.
+*/
+struct imx_ahci_hwmon {
+ struct mutex lock;
+ struct device *hwmon_dev;
+};
+
+static ssize_t imx_ahci_hwmon_name_show(struct device *dev,
+ struct device_attribute *devattr, char *buf)
+{
+ return sprintf(buf, "imx-ahci-hwmon\n");
+}
+
+/**
+ * imx_ahci_hwmon_temp_show - show value of the temperature
+ * @dev: The device that the attribute belongs to.
+ * @attr: The attribute being read.
+ * @buf: The result buffer.
+ *
+ * Read a value from the IMX AHCI temperature monitor.
+ */
+static ssize_t imx_ahci_hwmon_temp_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ void __iomem *mmio;
+ u32 mpll_test_reg, rtune_ctl_reg, dac_ctl_reg, adc_out_reg;
+ u32 str1, str2, str3, str4, read_sum, index;
+ int m1, m2, a, temp, ret;
+ struct clk *sata_clk, *sata_ref_clk;
+ struct imx_ahci_hwmon *hwmon;
+
+ hwmon = platform_get_drvdata(to_platform_device(dev));
+
+ ret = mutex_lock_interruptible(&hwmon->lock);
+ if (ret < 0)
+ return ret;
+
+ /* initialize the HW.(kinds of clocks) */
+ sata_clk = clk_get(NULL, "imx_sata_clk");
+ if (IS_ERR(sata_clk)) {
+ dev_err(dev, "no sata clock.\n");
+ return PTR_ERR(sata_clk);
+ }
+ clk_enable(sata_clk);
+ sata_ref_clk = clk_get(NULL, "usb_phy1_clk");
+ if (IS_ERR(sata_ref_clk)) {
+ dev_err(dev, "no sata clock.\n");
+ return PTR_ERR(sata_ref_clk);
+ }
+ clk_enable(sata_ref_clk);
+
+ /* map the IO addr */
+ mmio = ioremap(MX53_SATA_BASE_ADDR, SZ_2K);
+ if (mmio == NULL) {
+ dev_err(dev, "Failed to map SATA REGS\n");
+ return -1;
+ }
+
+ /* check rd-wr to reg */
+ read_sum = 0;
+ sata_phy_cr_addr(SATA_PHY_CR_CLOCK_CRCMP_LT_LIMIT, mmio);
+ sata_phy_cr_write(read_sum, mmio);
+ sata_phy_cr_read(&read_sum, mmio);
+ if ((read_sum & 0xffff) != 0)
+ dev_err(dev, "Read/Write REG error, 0x%x!\n", read_sum);
+
+ sata_phy_cr_write(0x5A5A, mmio);
+ sata_phy_cr_read(&read_sum, mmio);
+ if ((read_sum & 0xffff) != 0x5A5A)
+ dev_err(dev, "Read/Write REG error, 0x%x!\n", read_sum);
+
+ sata_phy_cr_write(0x1234, mmio);
+ sata_phy_cr_read(&read_sum, mmio);
+ if ((read_sum & 0xffff) != 0x1234)
+ dev_err(dev, "Read/Write REG error, 0x%x!\n", read_sum);
+
+ /* stat temperature test */
+ sata_phy_cr_addr(SATA_PHY_CR_CLOCK_MPLL_TST, mmio);
+ sata_phy_cr_read(&mpll_test_reg, mmio);
+ sata_phy_cr_addr(SATA_PHY_CR_CLOCK_RTUNE_CTL, mmio);
+ sata_phy_cr_read(&rtune_ctl_reg, mmio);
+ sata_phy_cr_addr(SATA_PHY_CR_CLOCK_DAC_CTL, mmio);
+ sata_phy_cr_read(&dac_ctl_reg, mmio);
+
+ /* mpll_tst.meas_iv ([12:2]) */
+ str1 = (mpll_test_reg >> 2) & 0x7FF;
+ /* rtune_ctl.mode ([1:0]) */
+ str2 = (rtune_ctl_reg) & 0x3;
+ /* dac_ctl.dac_mode ([14:12]) */
+ str3 = (dac_ctl_reg >> 12) & 0x7;
+ /* rtune_ctl.sel_atbp ([4]) */
+ str4 = (rtune_ctl_reg >> 4);
+
+ /* Caculate the m1 */
+ /* mpll_tst.meas_iv */
+ mpll_test_reg = (mpll_test_reg & 0xE03) | (512) << 2;
+ /* rtune_ctl.mode */
+ rtune_ctl_reg = (rtune_ctl_reg & 0xFFC) | (1);
+ /* dac_ctl.dac_mode */
+ dac_ctl_reg = (dac_ctl_reg & 0x8FF) | (4) << 12;
+ /* rtune_ctl.sel_atbp */
+ rtune_ctl_reg = (rtune_ctl_reg & 0xFEF) | (0) << 4;
+
+ sata_phy_cr_addr(SATA_PHY_CR_CLOCK_MPLL_TST, mmio);
+ sata_phy_cr_write(mpll_test_reg, mmio);
+ sata_phy_cr_addr(SATA_PHY_CR_CLOCK_DAC_CTL, mmio);
+ sata_phy_cr_write(dac_ctl_reg, mmio);
+ sata_phy_cr_addr(SATA_PHY_CR_CLOCK_RTUNE_CTL, mmio);
+ sata_phy_cr_write(rtune_ctl_reg, mmio);
+
+ /* two dummy read */
+ index = 0;
+ read_sum = 0;
+ adc_out_reg = 0;
+ sata_phy_cr_addr(SATA_PHY_CR_CLOCK_ADC_OUT, mmio);
+
+ while (index < 2) {
+ sata_phy_cr_read(&adc_out_reg, mmio);
+ /* check if valid */
+ if (adc_out_reg & 0x400)
+ index = index + 1;
+ read_sum++;
+ if (read_sum > 100000) {
+ dev_err(dev, "Read REG more than 100000 times!\n");
+ break;
+ }
+ }
+
+ index = 0;
+ read_sum = 0;
+ while (index < 80) {
+ sata_phy_cr_read(&adc_out_reg, mmio);
+ if (adc_out_reg & 0x400) {
+ read_sum = read_sum + (adc_out_reg & 0x3FF);
+ index = index + 1;
+ }
+ }
+ /* Use the U32 to make 1000 precision */
+ m1 = (read_sum * 1000) / 80;
+
+ /* Caculate the m2 */
+ /* rtune_ctl.sel_atbp */
+ rtune_ctl_reg = (rtune_ctl_reg & 0xFEF) | (1) << 4;
+ sata_phy_cr_addr(SATA_PHY_CR_CLOCK_RTUNE_CTL, mmio);
+ sata_phy_cr_write(rtune_ctl_reg, mmio);
+
+ /* two dummy read */
+ index = 0;
+ read_sum = 0;
+ sata_phy_cr_addr(SATA_PHY_CR_CLOCK_ADC_OUT, mmio);
+ while (index < 2) {
+ sata_phy_cr_read(&adc_out_reg, mmio);
+ /* check if valid */
+ if (adc_out_reg & 0x400)
+ index = index + 1;
+ read_sum++;
+ if (read_sum > 100000) {
+ dev_err(dev, "Read REG more than 100000 times!\n");
+ break;
+ }
+ }
+
+ index = 0;
+ read_sum = 0;
+ while (index < 80) {
+ /* FIX ME dead loop protection??? */
+ sata_phy_cr_read(&adc_out_reg, mmio);
+ if (adc_out_reg & 0x400) {
+ read_sum = read_sum + (adc_out_reg & 0x3FF);
+ index = index + 1;
+ }
+ }
+ /* Use the U32 to make 1000 precision */
+ m2 = (read_sum * 1000) / 80;
+
+ /* restore the status */
+ /* mpll_tst.meas_iv */
+ mpll_test_reg = (mpll_test_reg & 0xE03) | (str1) << 2;
+ /* rtune_ctl.mode */
+ rtune_ctl_reg = (rtune_ctl_reg & 0xFFC) | (str2);
+ /* dac_ctl.dac_mode */
+ dac_ctl_reg = (dac_ctl_reg & 0x8FF) | (str3) << 12;
+ /* rtune_ctl.sel_atbp */
+ rtune_ctl_reg = (rtune_ctl_reg & 0xFEF) | (str4) << 4;
+
+ sata_phy_cr_addr(SATA_PHY_CR_CLOCK_MPLL_TST, mmio);
+ sata_phy_cr_write(mpll_test_reg, mmio);
+ sata_phy_cr_addr(SATA_PHY_CR_CLOCK_DAC_CTL, mmio);
+ sata_phy_cr_write(dac_ctl_reg, mmio);
+ sata_phy_cr_addr(SATA_PHY_CR_CLOCK_RTUNE_CTL, mmio);
+ sata_phy_cr_write(rtune_ctl_reg, mmio);
+
+ /* Compute temperature */
+ if (!(m2 / 1000))
+ m2 = 1000;
+ a = (m2 - m1) / (m2 / 1000);
+ temp = ((((-559) * a) / 1000) * a) / 1000 + (1379) * a / 1000 + (-458);
+
+ iounmap(mmio);
+
+ /* Release the clocks */
+ clk_disable(sata_ref_clk);
+ clk_put(sata_ref_clk);
+ clk_disable(sata_clk);
+ clk_put(sata_clk);
+ mutex_unlock(&hwmon->lock);
+
+ return sprintf(buf, "%d\n", temp * 1000);
+}
+
+static DEVICE_ATTR(name, S_IRUGO, imx_ahci_hwmon_name_show, NULL);
+static DEVICE_ATTR(temp1_input, S_IRUGO, imx_ahci_hwmon_temp_show, NULL);
+
+static struct attribute *imx_ahci_hwmon_attrs[] = {
+ &dev_attr_name.attr,
+ &dev_attr_temp1_input.attr,
+ NULL
+};
+
+static struct attribute_group imx_ahci_hwmon_attrgroup = {
+ .attrs = imx_ahci_hwmon_attrs,
+};
+
+/**
+ * imx_ahci_hwmon_probe - device probe entry.
+ * @dev: The device being probed.
+*/
+static int __devinit imx_ahci_hwmon_probe(struct platform_device *dev)
+{
+ struct imx_ahci_hwmon *hwmon;
+ int ret = 0;
+
+ hwmon = kzalloc(sizeof(struct imx_ahci_hwmon), GFP_KERNEL);
+ if (hwmon == NULL) {
+ dev_err(&dev->dev, "no memory\n");
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(dev, hwmon);
+
+ /* add attributes for device. */
+ ret = sysfs_create_group(&dev->dev.kobj, &imx_ahci_hwmon_attrgroup);
+ if (ret)
+ goto err_mem;
+
+ /* register with the hwmon core */
+
+ hwmon->hwmon_dev = hwmon_device_register(&dev->dev);
+ if (IS_ERR(hwmon->hwmon_dev)) {
+ dev_err(&dev->dev, "error registering with hwmon\n");
+ ret = PTR_ERR(hwmon->hwmon_dev);
+ goto err_raw_attribute;
+ }
+
+ mutex_init(&hwmon->lock);
+
+ return 0;
+
+err_raw_attribute:
+ sysfs_remove_group(&dev->dev.kobj, &imx_ahci_hwmon_attrgroup);
+
+err_mem:
+ platform_set_drvdata(dev, NULL);
+ kfree(hwmon);
+ return ret;
+}
+
+static int __devexit imx_ahci_hwmon_remove(struct platform_device *dev)
+{
+ struct imx_ahci_hwmon *hwmon = platform_get_drvdata(dev);
+
+ mutex_destroy(&hwmon->lock);
+ sysfs_remove_group(&dev->dev.kobj, &imx_ahci_hwmon_attrgroup);
+ hwmon_device_unregister(hwmon->hwmon_dev);
+ platform_set_drvdata(dev, NULL);
+ kfree(hwmon);
+
+ return 0;
+}
+
+static struct platform_driver imx_ahci_hwmon_driver = {
+ .driver = {
+ .name = "imx-ahci-hwmon",
+ .owner = THIS_MODULE,
+ },
+ .probe = imx_ahci_hwmon_probe,
+ .remove = __devexit_p(imx_ahci_hwmon_remove),
+};
+
+static int __init imx_ahci_hwmon_init(void)
+{
+ return platform_driver_register(&imx_ahci_hwmon_driver);
+}
+
+static void __exit imx_ahci_hwmon_exit(void)
+{
+ platform_driver_unregister(&imx_ahci_hwmon_driver);
+}
+
+module_init(imx_ahci_hwmon_init);
+module_exit(imx_ahci_hwmon_exit);
+
+MODULE_DESCRIPTION("FSL IMX AHCI HWMon driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:imx-ahci-hwmon");