This patchset brings the support for the TS-5500 platform.
The first patch adds the base support for the board in
/arch/x86/platform/ts5500, and a documentation file in
Documentation/ABI/testing/sysfs-platform-ts5500.
The second patch adds support for GPIO.
The third patch adds support for the on-board LED.
The fourth patch adds support to hwmon for the Maxim MAX197 A/D converter,
which is embedded on the TS-5500 platform, as suggested by Guenter Roeck.
The fifth patch brings support for the A/D converter.
Jerome Oufella (1):
x86/platform: (TS-5500) add GPIO support
Jonas Fonseca (1):
x86/platform: (TS-5500) add LED support
Vivien Didelot (3):
x86/platform: (TS-5500) add platform base support
hwmon: add MAX197 support
x86/platform: (TS-5500) add ADC support
Documentation/ABI/testing/sysfs-platform-ts5500 | 46 +++
Documentation/hwmon/max197 | 54 +++
MAINTAINERS | 5 +
arch/x86/Kconfig | 2 +
arch/x86/platform/Makefile | 1 +
arch/x86/platform/ts5500/Kconfig | 29 ++
arch/x86/platform/ts5500/Makefile | 4 +
arch/x86/platform/ts5500/ts5500.c | 299 ++++++++++++++
arch/x86/platform/ts5500/ts5500.h | 35 ++
arch/x86/platform/ts5500/ts5500_adc.c | 104 +++++
arch/x86/platform/ts5500/ts5500_gpio.c | 478 +++++++++++++++++++++++
arch/x86/platform/ts5500/ts5500_gpio.h | 60 +++
arch/x86/platform/ts5500/ts5500_led.c | 179 +++++++++
drivers/hwmon/Kconfig | 9 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/max197.c | 403 +++++++++++++++++++
include/linux/max197.h | 22 +
17 files changed, 1731 insertions(+), 0 deletions(-)
create mode 100644 Documentation/ABI/testing/sysfs-platform-ts5500
create mode 100644 Documentation/hwmon/max197
create mode 100644 arch/x86/platform/ts5500/Kconfig
create mode 100644 arch/x86/platform/ts5500/Makefile
create mode 100644 arch/x86/platform/ts5500/ts5500.c
create mode 100644 arch/x86/platform/ts5500/ts5500.h
create mode 100644 arch/x86/platform/ts5500/ts5500_adc.c
create mode 100644 arch/x86/platform/ts5500/ts5500_gpio.c
create mode 100644 arch/x86/platform/ts5500/ts5500_gpio.h
create mode 100644 arch/x86/platform/ts5500/ts5500_led.c
create mode 100644 drivers/hwmon/max197.c
create mode 100644 include/linux/max197.h
--
1.7.6.5
Signed-off-by: Vivien Didelot <[email protected]>
---
Documentation/ABI/testing/sysfs-platform-ts5500 | 46 ++++
MAINTAINERS | 5 +
arch/x86/Kconfig | 2 +
arch/x86/platform/Makefile | 1 +
arch/x86/platform/ts5500/Kconfig | 8 +
arch/x86/platform/ts5500/Makefile | 1 +
arch/x86/platform/ts5500/ts5500.c | 299 +++++++++++++++++++++++
arch/x86/platform/ts5500/ts5500.h | 35 +++
8 files changed, 397 insertions(+), 0 deletions(-)
create mode 100644 Documentation/ABI/testing/sysfs-platform-ts5500
create mode 100644 arch/x86/platform/ts5500/Kconfig
create mode 100644 arch/x86/platform/ts5500/Makefile
create mode 100644 arch/x86/platform/ts5500/ts5500.c
create mode 100644 arch/x86/platform/ts5500/ts5500.h
diff --git a/Documentation/ABI/testing/sysfs-platform-ts5500 b/Documentation/ABI/testing/sysfs-platform-ts5500
new file mode 100644
index 0000000..4b5c2dc
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-platform-ts5500
@@ -0,0 +1,46 @@
+What: /sys/devices/platform/ts5500/id
+Date: February 2012
+KernelVersion: 3.2
+Contact: "Savoir-faire Linux, Inc." <[email protected]>
+Description:
+ Product ID of the TS board. TS-5500 ID is 0x60.
+
+What: /sys/devices/platform/ts5500/sram
+Date: February 2012
+KernelVersion: 3.2
+Contact: "Savoir-faire Linux, Inc." <[email protected]>
+Description:
+ Say if the SRAM feature is present. If it is enabled, it will
+ display "1", otherwise "0".
+
+What: /sys/devices/platform/ts5500/ereset
+Date: February 2012
+KernelVersion: 3.2
+Contact: "Savoir-faire Linux, Inc." <[email protected]>
+Description:
+ Say if an external reset is present. If it is present, it will
+ display "1", otherwise "0".
+
+What: /sys/devices/platform/ts5500/jp{1,2,3,4,5,6}
+Date: February 2012
+KernelVersion: 3.2
+Contact: "Savoir-faire Linux, Inc." <[email protected]>
+Description:
+ Show the state of a plugged jumper. If it is present, it will
+ display "1", otherwise "0".
+
+What: /sys/devices/platform/ts5500/adc
+Date: February 2012
+KernelVersion: 3.2
+Contact: "Savoir-faire Linux, Inc." <[email protected]>
+Description:
+ Say if the A/D Converter is present. If it is enabled,
+ it will display "1", otherwise "0".
+
+What: /sys/devices/platform/ts5500/rs485
+Date: February 2012
+KernelVersion: 3.2
+Contact: "Savoir-faire Linux, Inc." <[email protected]>
+Description:
+ Say if the RS485 feature is present. If it is enabled, it
+ will display "1", otherwise "0".
diff --git a/MAINTAINERS b/MAINTAINERS
index 62f1cd3..c494156 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -6500,6 +6500,11 @@ W: http://tcp-lp-mod.sourceforge.net/
S: Maintained
F: net/ipv4/tcp_lp.c
+TECHNOLOGIC SYSTEMS TS-5500 MACHINE SUPPORT
+M: Savoir-faire Linux Inc. <[email protected]>
+S: Maintained
+F: arch/x86/platform/ts5500/*
+
TEGRA SUPPORT
M: Colin Cross <[email protected]>
M: Olof Johansson <[email protected]>
diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index efb4294..5204f62 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -2090,6 +2090,8 @@ config ALIX
Note: You have to set alix.force=1 for boards with Award BIOS.
+source "arch/x86/platform/ts5500/Kconfig"
+
endif # X86_32
config AMD_NB
diff --git a/arch/x86/platform/Makefile b/arch/x86/platform/Makefile
index 8d87439..2d17fd3 100644
--- a/arch/x86/platform/Makefile
+++ b/arch/x86/platform/Makefile
@@ -9,3 +9,4 @@ obj-y += scx200/
obj-y += sfi/
obj-y += visws/
obj-y += uv/
+obj-y += ts5500/
diff --git a/arch/x86/platform/ts5500/Kconfig b/arch/x86/platform/ts5500/Kconfig
new file mode 100644
index 0000000..929b617
--- /dev/null
+++ b/arch/x86/platform/ts5500/Kconfig
@@ -0,0 +1,8 @@
+config TS5500
+ bool "Technologic Systems TS-5500 Single Board Computer support"
+ depends on MELAN
+ select CHECK_SIGNATURE
+ help
+ Add support for the Technologic Systems TS-5500 platform.
+
+ If you have a TS-5500, say Y here.
diff --git a/arch/x86/platform/ts5500/Makefile b/arch/x86/platform/ts5500/Makefile
new file mode 100644
index 0000000..0a689a7
--- /dev/null
+++ b/arch/x86/platform/ts5500/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_TS5500) += ts5500.o
diff --git a/arch/x86/platform/ts5500/ts5500.c b/arch/x86/platform/ts5500/ts5500.c
new file mode 100644
index 0000000..7c01c46
--- /dev/null
+++ b/arch/x86/platform/ts5500/ts5500.c
@@ -0,0 +1,299 @@
+/*
+ * Technologic Systems TS-5500 board - SBC info layer
+ *
+ * Copyright (c) 2010-2012 Savoir-faire Linux Inc.
+ * Alexandre Savard <[email protected]>
+ * Jonas Fonseca <[email protected]>
+ * Vivien Didelot <[email protected]>
+ *
+ * Portions originate from ts_sbcinfo.c (c) Technologic Systems
+ * Liberty Young <[email protected]>
+ *
+ * These functions add sysfs platform entries to display information about
+ * the Technologic Systems TS-5500 Single Board Computer (SBC).
+ *
+ * For further information about sysfs entries, see
+ * Documentation/ABI/testing/sysfs-platform-ts5500
+ */
+
+#include <linux/module.h>
+#include <linux/ioport.h>
+#include <linux/platform_device.h>
+#include <linux/kernel.h>
+#include <linux/mutex.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <asm/processor.h>
+#include "ts5500.h"
+
+/* Hardware info for pre-detection */
+#define AMD_ELAN_FAMILY 4
+#define AMD_ELAN_SC520 9
+
+/* Product code register */
+#define TS5500_PRODUCT_CODE_REG 0x74
+#define TS5500_PRODUCT_CODE 0x60 /* TS-5500 product code */
+
+/**
+ * struct ts5500_sbc - TS-5500 SBC main structure
+ * @lock: Read/Write mutex.
+ * @board_id: Board name.
+ * @sram: Check SRAM option.
+ * @rs485: Check RS-485 option.
+ * @adc: Check Analog/Digital converter option.
+ * @ereset: Check External Reset option.
+ * @itr: Check Industrial Temperature Range option.
+ * @jumpers: States of jumpers 1-7.
+ */
+struct ts5500_sbc {
+ struct mutex lock;
+ int board_id;
+ bool sram;
+ bool rs485;
+ bool adc;
+ bool ereset;
+ bool itr;
+ u8 jumpers;
+};
+
+/* Current platform */
+struct ts5500_sbc *ts5500;
+
+/**
+ * ts5500_pre_detect_hw() - check for TS-5500 specific hardware
+ */
+static __init int ts5500_pre_detect_hw(void)
+{
+ /* Check for AMD ElanSC520 Microcontroller */
+ if (cpu_info.x86_vendor != X86_VENDOR_AMD ||
+ cpu_info.x86 != AMD_ELAN_FAMILY ||
+ cpu_info.x86_model != AMD_ELAN_SC520)
+ return -ENODEV;
+
+ return 0;
+}
+
+/* BIOS signatures */
+static struct {
+ const unsigned char *string;
+ const ssize_t offset;
+} signatures[] __initdata = {
+ {"TS-5x00 AMD Elan", 0xb14}
+};
+
+/**
+ * ts5500_bios_signature() - find board signature in BIOS shadow RAM.
+ */
+static __init int ts5500_bios_signature(void)
+{
+ void __iomem *bios = ioremap(0xF0000, 0x10000);
+ int i, ret = 0;
+
+ for (i = 0; i < ARRAY_SIZE(signatures); i++)
+ if (check_signature(bios + signatures[i].offset,
+ signatures[i].string,
+ strlen(signatures[i].string)))
+ goto found;
+ else
+ pr_notice("Technologic Systems BIOS signature "
+ "'%s' not found at offset %zd\n",
+ signatures[i].string, signatures[i].offset);
+ ret = -ENODEV;
+found:
+ iounmap(bios);
+ return ret;
+}
+
+/**
+ * ts5500_detect_config() - detect the TS board
+ * @sbc: Structure in which the detected board's details will be stored.
+ */
+static int ts5500_detect_config(struct ts5500_sbc *sbc)
+{
+ u8 tmp;
+ int ret = 0;
+
+ if (!request_region(TS5500_PRODUCT_CODE_REG, 4, "ts5500"))
+ return -EBUSY;
+
+ mutex_lock(&ts5500->lock);
+ tmp = inb(TS5500_PRODUCT_CODE_REG);
+ if (tmp != TS5500_PRODUCT_CODE) {
+ pr_err("This platform is not a TS-5500 (found ID 0x%x)\n", tmp);
+ ret = -ENODEV;
+ goto cleanup;
+ }
+ sbc->board_id = tmp;
+
+ tmp = inb(TS5500_SRAM_RS485_ADC_REG);
+ ts5500->sram = !!(tmp & TS5500_SRAM_OPT);
+ ts5500->rs485 = !!(tmp & TS5500_RS485_OPT);
+ ts5500->adc = !!(tmp & TS5500_ADC_OPT);
+
+ tmp = inb(TS5500_ERESET_ITR_REG);
+ ts5500->ereset = !!(tmp & TS5500_ERESET_OPT);
+ ts5500->itr = !!(tmp & TS5500_ITR_OPT);
+
+ tmp = inb(TS5500_LED_JMPRS_REG);
+ sbc->jumpers = tmp & 0xFE; /* All bits except the first (LED) */
+
+cleanup:
+ mutex_unlock(&ts5500->lock);
+ release_region(TS5500_PRODUCT_CODE_REG, 4);
+ return ret;
+}
+
+#define TS5500_IS_JP_SET(sbc, jmp) (!!(sbc->jumpers & TS5500_JP##jmp))
+
+static ssize_t ts5500_show_id(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ts5500_sbc *sbc = dev_get_drvdata(dev);
+
+ return sprintf(buf, "0x%x\n", sbc->board_id);
+}
+
+static ssize_t ts5500_show_sram(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ts5500_sbc *sbc = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%d\n", sbc->sram);
+}
+
+static ssize_t ts5500_show_rs485(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ts5500_sbc *sbc = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%d\n", sbc->rs485);
+}
+
+static ssize_t ts5500_show_adc(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ts5500_sbc *sbc = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%d\n", sbc->adc);
+}
+
+static ssize_t ts5500_show_ereset(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct ts5500_sbc *sbc = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%d\n", sbc->ereset);
+}
+
+static ssize_t ts5500_show_itr(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct ts5500_sbc *sbc = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%d\n", sbc->itr);
+}
+
+#define TS5500_SHOW_JP(jp) \
+ static ssize_t ts5500_show_jp##jp(struct device *dev, \
+ struct device_attribute *attr,\
+ char *buf) \
+ { \
+ struct ts5500_sbc *sbc = dev_get_drvdata(dev); \
+ return sprintf(buf, "%d\n", TS5500_IS_JP_SET(sbc, jp)); \
+ }
+
+TS5500_SHOW_JP(1)
+TS5500_SHOW_JP(2)
+TS5500_SHOW_JP(3)
+TS5500_SHOW_JP(4)
+TS5500_SHOW_JP(5)
+TS5500_SHOW_JP(6)
+
+static DEVICE_ATTR(id, S_IRUGO, ts5500_show_id, NULL);
+static DEVICE_ATTR(sram, S_IRUGO, ts5500_show_sram, NULL);
+static DEVICE_ATTR(rs485, S_IRUGO, ts5500_show_rs485, NULL);
+static DEVICE_ATTR(adc, S_IRUGO, ts5500_show_adc, NULL);
+static DEVICE_ATTR(ereset, S_IRUGO, ts5500_show_ereset, NULL);
+static DEVICE_ATTR(itr, S_IRUGO, ts5500_show_itr, NULL);
+static DEVICE_ATTR(jp1, S_IRUGO, ts5500_show_jp1, NULL);
+static DEVICE_ATTR(jp2, S_IRUGO, ts5500_show_jp2, NULL);
+static DEVICE_ATTR(jp3, S_IRUGO, ts5500_show_jp3, NULL);
+static DEVICE_ATTR(jp4, S_IRUGO, ts5500_show_jp4, NULL);
+static DEVICE_ATTR(jp5, S_IRUGO, ts5500_show_jp5, NULL);
+static DEVICE_ATTR(jp6, S_IRUGO, ts5500_show_jp6, NULL);
+
+static struct attribute *ts5500_attributes[] = {
+ &dev_attr_id.attr,
+ &dev_attr_sram.attr,
+ &dev_attr_rs485.attr,
+ &dev_attr_adc.attr,
+ &dev_attr_ereset.attr,
+ &dev_attr_itr.attr,
+ &dev_attr_jp1.attr,
+ &dev_attr_jp2.attr,
+ &dev_attr_jp3.attr,
+ &dev_attr_jp4.attr,
+ &dev_attr_jp5.attr,
+ &dev_attr_jp6.attr,
+ NULL
+};
+
+static const struct attribute_group ts5500_attr_group = {
+ .attrs = ts5500_attributes
+};
+
+static int __init ts5500_init(void)
+{
+ int ret;
+ struct platform_device *pdev;
+
+ /*
+ * There is no DMI available, or PCI bridge subvendor info,
+ * only the BIOS provides a 16-bit identification call.
+ * It is safer to check for a TS-5500 specific hardware
+ * such as the processor, then find a signature in the BIOS.
+ */
+ ret = ts5500_pre_detect_hw();
+ if (ret)
+ return ret;
+
+ ret = ts5500_bios_signature();
+ if (ret)
+ return ret;
+
+ ts5500 = kzalloc(sizeof(struct ts5500_sbc), GFP_KERNEL);
+ if (!ts5500)
+ return -ENOMEM;
+ mutex_init(&ts5500->lock);
+
+ ret = ts5500_detect_config(ts5500);
+ if (ret)
+ goto release_mem;
+
+ pdev = platform_device_register_simple("ts5500", -1, NULL, 0);
+ if (IS_ERR(pdev)) {
+ ret = PTR_ERR(pdev);
+ goto release_mem;
+ }
+ platform_set_drvdata(pdev, ts5500);
+
+ ret = sysfs_create_group(&pdev->dev.kobj,
+ &ts5500_attr_group);
+ if (ret)
+ goto release_pdev;
+
+ return 0;
+
+release_pdev:
+ platform_device_unregister(pdev);
+release_mem:
+ kfree(ts5500);
+
+ return ret;
+}
+postcore_initcall(ts5500_init);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Technologic Systems TS-5500 Board's platform driver");
diff --git a/arch/x86/platform/ts5500/ts5500.h b/arch/x86/platform/ts5500/ts5500.h
new file mode 100644
index 0000000..f4f1bc4
--- /dev/null
+++ b/arch/x86/platform/ts5500/ts5500.h
@@ -0,0 +1,35 @@
+/*
+ * Technologic Systems TS-5500 board
+ *
+ * Copyright (c) 2010-2012 Savoir-faire Linux Inc.
+ * Vivien Didelot <[email protected]>
+ */
+
+#ifndef _TS5500_H
+#define _TS5500_H
+
+/* SRAM/RS-485/ADC options, and RS-485 RTS/Automatic RS-485 flags register */
+#define TS5500_SRAM_RS485_ADC_REG 0x75
+#define TS5500_SRAM_OPT 0x01 /* SRAM option */
+#define TS5500_RS485_OPT 0x02 /* RS-485 option */
+#define TS5500_ADC_OPT 0x04 /* A/D converter option */
+#define TS5500_RS485_RTS_MASK 0x40 /* RTS for RS-485 */
+#define TS5500_RS485_AUTO_MASK 0x80 /* Automatic RS-485 */
+
+/* External Reset/Industrial Temperature Range options register */
+#define TS5500_ERESET_ITR_REG 0x76
+#define TS5500_ERESET_OPT 0x01 /* External Reset option */
+#define TS5500_ITR_OPT 0x02 /* Indust. Temp. Range option */
+
+/* LED/Jumpers register */
+#define TS5500_LED_JMPRS_REG 0x77
+#define TS5500_LED_MASK 0x01 /* LED flag */
+#define TS5500_JP1 0x02 /* Automatic CMOS */
+#define TS5500_JP2 0x04 /* Enable Serial Console */
+#define TS5500_JP3 0x08 /* Write Enable Drive A */
+#define TS5500_JP4 0x10 /* Fast Console (115K baud) */
+#define TS5500_JP5 0x20 /* User Jumper */
+#define TS5500_JP6 0x40 /* Console on COM1 (req. JP2) */
+#define TS5500_JP7 0x80 /* Undocumented (Unused) */
+
+#endif
--
1.7.6.5
Signed-off-by: Vivien Didelot <[email protected]>
---
Documentation/hwmon/max197 | 54 ++++++
drivers/hwmon/Kconfig | 9 +
drivers/hwmon/Makefile | 1 +
drivers/hwmon/max197.c | 403 ++++++++++++++++++++++++++++++++++++++++++++
include/linux/max197.h | 22 +++
5 files changed, 489 insertions(+), 0 deletions(-)
create mode 100644 Documentation/hwmon/max197
create mode 100644 drivers/hwmon/max197.c
create mode 100644 include/linux/max197.h
diff --git a/Documentation/hwmon/max197 b/Documentation/hwmon/max197
new file mode 100644
index 0000000..aa0934f
--- /dev/null
+++ b/Documentation/hwmon/max197
@@ -0,0 +1,54 @@
+Maxim MAX197 driver
+===================
+
+Author:
+ * Vivien Didelot <[email protected]>
+
+Supported chips:
+ * Maxim MAX197
+ Prefix: 'max197'
+ Datasheet: http://datasheets.maxim-ic.com/en/ds/MAX197.pdf
+
+ * Maxim MAX199
+ Prefix: 'max199'
+ Datasheet: http://datasheets.maxim-ic.com/en/ds/MAX199.pdf
+
+Description
+-----------
+
+The A/D converters MAX197 and MAX199 are both 8-Channel, Multi-Range, 5V,
+12-Bit DAS with 8+4 Bus Interface and Fault Protection.
+
+The available ranges for the MAX197 are {0,-5V} to 5V, and {0,-10V} to 10V,
+while they are {0,-2V} to 2V, and {0,-4V} to 4V on the MAX199.
+
+Platform data
+-------------
+
+The MAX197 platform data (defined in linux/max197.h) should be filled with two
+function pointers:
+
+* start:
+ The function which writes the control byte to start a new conversion.
+* read:
+ The function used to read the raw value from the chip.
+
+Control-Byte Format:
+
+Bit Name Description
+7,6 PD1,PD0 Clock and Power-Down modes
+5 ACQMOD Internal or External Controlled Acquisition
+4 RNG Full-scale voltage magnitude at the input
+3 BIP Unipolar or Bipolar conversion mode
+2,1,0 A2,A1,A0 Channel
+
+Sysfs interface
+---------------
+
+* in[0-7]_input: The conversion value for the corresponding channel.
+* in[0-7]_min: The lower limit (in mV) for the corresponding channel.
+ It can be one value in -10000, -5000, -4000, -2000, 0,
+ depending on the chip.
+* in[0-7]_max: The higher limit (in mV) for the corresponding channel.
+ It can be one value in 2000, 4000, 5000, 10000,
+ depending on the chip.
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 91be41f..ccdf59b 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1360,6 +1360,15 @@ config SENSORS_MC13783_ADC
help
Support for the A/D converter on MC13783 PMIC.
+config SENSORS_MAX197
+ tristate "Maxim MAX197 and compatibles."
+ help
+ If you say yes here you get support for the Maxim MAX197,
+ and MAX199 A/D converters.
+
+ This driver can also be built as a module. If so, the module
+ will be called max197.
+
if ACPI
comment "ACPI drivers"
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 8251ce8..dfb73d9 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -125,6 +125,7 @@ obj-$(CONFIG_SENSORS_W83L785TS) += w83l785ts.o
obj-$(CONFIG_SENSORS_W83L786NG) += w83l786ng.o
obj-$(CONFIG_SENSORS_WM831X) += wm831x-hwmon.o
obj-$(CONFIG_SENSORS_WM8350) += wm8350-hwmon.o
+obj-$(CONFIG_SENSORS_MAX197) += max197.o
obj-$(CONFIG_PMBUS) += pmbus/
diff --git a/drivers/hwmon/max197.c b/drivers/hwmon/max197.c
new file mode 100644
index 0000000..985615c
--- /dev/null
+++ b/drivers/hwmon/max197.c
@@ -0,0 +1,403 @@
+/*
+ * Maxim MAX197 A/D Converter Driver
+ *
+ * Copyright (c) 2012 Savoir-faire Linux Inc.
+ * Vivien Didelot <[email protected]>
+ *
+ * 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.
+ *
+ * For further information, see the Documentation/hwmon/max197 file.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/sysfs.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/max197.h>
+
+#define MAX199_LIMIT 4000 /* 4V */
+#define MAX197_LIMIT 10000 /* 10V */
+
+#define MAX197_NUM_CH 8 /* 8 Analog Input Channels */
+
+/* Control byte format */
+#define MAX197_A0 0x01 /* Channel bit 0 */
+#define MAX197_A1 0x02 /* Channel bit 1 */
+#define MAX197_A2 0x04 /* Channel bit 2 */
+#define MAX197_BIP 0x08 /* Bipolarity */
+#define MAX197_RNG 0x10 /* Full range */
+#define MAX197_ACQMOD 0x20 /* Internal/External controlled acquisition */
+#define MAX197_PD0 0x40 /* Clock/Power-Down modes bit 1 */
+#define MAX197_PD1 0x80 /* Clock/Power-Down modes bit 2 */
+
+#define MAX197_SCALE 12207 /* Scale coefficient for raw data */
+
+/**
+ * struct max197_chip - device instance specific data
+ * @pdata: Platform data.
+ * @hwmon_dev: The hwmon device.
+ * @lock: Read/Write mutex.
+ * @limit: Max range value (10V for MAX197, 4V for MAX199).
+ * @scale: Need to scale.
+ * @ctrl_bytes: Channels control byte.
+ */
+struct max197_chip {
+ struct max197_platform_data *pdata;
+ struct device *hwmon_dev;
+ struct mutex lock;
+ int limit;
+ bool scale;
+ u8 ctrl_bytes[MAX197_NUM_CH];
+};
+
+static inline void max197_set_unipolarity(struct max197_chip *chip, int channel)
+{
+ chip->ctrl_bytes[channel] &= ~MAX197_BIP;
+}
+
+static inline void max197_set_bipolarity(struct max197_chip *chip, int channel)
+{
+ chip->ctrl_bytes[channel] |= MAX197_BIP;
+}
+
+static inline void max197_set_half_range(struct max197_chip *chip, int channel)
+{
+ chip->ctrl_bytes[channel] &= ~MAX197_RNG;
+}
+
+static inline void max197_set_full_range(struct max197_chip *chip, int channel)
+{
+ chip->ctrl_bytes[channel] |= MAX197_RNG;
+}
+
+static inline bool max197_is_bipolar(struct max197_chip *chip, int channel)
+{
+ return !!(chip->ctrl_bytes[channel] & MAX197_BIP);
+}
+
+static inline bool max197_is_full_range(struct max197_chip *chip, int channel)
+{
+ return !!(chip->ctrl_bytes[channel] & MAX197_RNG);
+}
+
+/**
+ * max197_show_range() - Display range on user output
+ *
+ * Function called on read access on in{0,1,2,3,4,5,6,7}_{min,max}
+ */
+static ssize_t max197_show_range(struct device *dev,
+ struct device_attribute *devattr, char *buf)
+{
+ struct max197_chip *chip = dev_get_drvdata(dev);
+ struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr);
+ int channel = attr->index;
+ bool is_min = attr->nr;
+ int range;
+
+ if (mutex_lock_interruptible(&chip->lock))
+ return -ERESTARTSYS;
+
+ range = max197_is_full_range(chip, channel) ?
+ chip->limit : chip->limit / 2;
+ if (is_min) {
+ if (max197_is_bipolar(chip, channel))
+ range = -range;
+ else
+ range = 0;
+ }
+
+ mutex_unlock(&chip->lock);
+
+ return sprintf(buf, "%d\n", range);
+}
+
+/**
+ * max197_store_range() - Write range from user input
+ *
+ * Function called on write access on in{0,1,2,3,4,5,6,7}_{min,max}
+ */
+static ssize_t max197_store_range(struct device *dev,
+ struct device_attribute *devattr,
+ const char *buf, size_t count)
+{
+ struct max197_chip *chip = dev_get_drvdata(dev);
+ struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr);
+ int channel = attr->index;
+ bool is_min = attr->nr;
+ long value;
+ int half = chip->limit / 2;
+ int full = chip->limit;
+
+ if (kstrtol(buf, 10, &value))
+ return -EINVAL;
+
+ if (is_min) {
+ if ((value != 0) && (value != -half) && (value != -full))
+ return -EINVAL;
+ } else {
+ if ((value != half) && (value != full))
+ return -EINVAL;
+ }
+
+ if (mutex_lock_interruptible(&chip->lock))
+ return -ERESTARTSYS;
+
+ if (value == 0) {
+ /* We can deduce only the polarity */
+ max197_set_unipolarity(chip, channel);
+ } else if (value == -half) {
+ max197_set_bipolarity(chip, channel);
+ max197_set_half_range(chip, channel);
+ } else if (value == -full) {
+ max197_set_bipolarity(chip, channel);
+ max197_set_full_range(chip, channel);
+ } else if (value == half) {
+ /* We can deduce only the range */
+ max197_set_half_range(chip, channel);
+ } else if (value == full) {
+ /* We can deduce only the range */
+ max197_set_full_range(chip, channel);
+ }
+
+ mutex_unlock(&chip->lock);
+
+ return count;
+}
+
+/**
+ * max197_show_input() - Show channel input
+ *
+ * Function called on read access on in{0,1,2,3,4,5,6,7}_input
+ */
+static ssize_t max197_show_input(struct device *dev,
+ struct device_attribute *devattr,
+ char *buf)
+{
+ struct max197_chip *chip = dev_get_drvdata(dev);
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+ int channel = attr->index;
+ u16 raw;
+ s32 scaled;
+ int ret;
+
+ if (mutex_lock_interruptible(&chip->lock))
+ return -ERESTARTSYS;
+
+ ret = chip->pdata->start(chip->ctrl_bytes[channel]);
+ if (ret) {
+ dev_err(dev, "start conversion failed\n");
+ goto unlock;
+ }
+
+ ret = chip->pdata->read(&raw);
+ if (ret) {
+ dev_err(dev, "raw read failed\n");
+ goto unlock;
+ }
+
+ /*
+ * Coefficient to apply on raw value.
+ * See Table 1. Full Scale and Zero Scale in the MAX197 datasheet.
+ */
+ scaled = raw;
+ if (chip->scale) {
+ scaled *= MAX197_SCALE;
+ if (max197_is_full_range(chip, channel))
+ scaled *= 2;
+ scaled /= 10000;
+ }
+
+ ret = sprintf(buf, "%d\n", scaled);
+
+unlock:
+ mutex_unlock(&chip->lock);
+ return ret;
+}
+
+static ssize_t max197_show_name(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ return sprintf(buf, "%s\n", pdev->name);
+}
+
+#define MAX197_SENSOR_DEVICE_ATTR_CH(chan) \
+ static SENSOR_DEVICE_ATTR(in##chan##_input, S_IRUGO, \
+ max197_show_input, NULL, chan); \
+ static SENSOR_DEVICE_ATTR_2(in##chan##_min, S_IRUGO | S_IWUSR, \
+ max197_show_range, \
+ max197_store_range, \
+ true, chan); \
+ static SENSOR_DEVICE_ATTR_2(in##chan##_max, S_IRUGO | S_IWUSR, \
+ max197_show_range, \
+ max197_store_range, \
+ false, chan)
+
+#define MAX197_SENSOR_DEV_ATTR_IN(chan) \
+ &sensor_dev_attr_in##chan##_input.dev_attr.attr, \
+ &sensor_dev_attr_in##chan##_max.dev_attr.attr, \
+ &sensor_dev_attr_in##chan##_min.dev_attr.attr
+
+static DEVICE_ATTR(name, S_IRUGO, max197_show_name, NULL);
+
+MAX197_SENSOR_DEVICE_ATTR_CH(0);
+MAX197_SENSOR_DEVICE_ATTR_CH(1);
+MAX197_SENSOR_DEVICE_ATTR_CH(2);
+MAX197_SENSOR_DEVICE_ATTR_CH(3);
+MAX197_SENSOR_DEVICE_ATTR_CH(4);
+MAX197_SENSOR_DEVICE_ATTR_CH(5);
+MAX197_SENSOR_DEVICE_ATTR_CH(6);
+MAX197_SENSOR_DEVICE_ATTR_CH(7);
+
+static const struct attribute_group max197_sysfs_group = {
+ .attrs = (struct attribute *[]) {
+ &dev_attr_name.attr,
+ MAX197_SENSOR_DEV_ATTR_IN(0),
+ MAX197_SENSOR_DEV_ATTR_IN(1),
+ MAX197_SENSOR_DEV_ATTR_IN(2),
+ MAX197_SENSOR_DEV_ATTR_IN(3),
+ MAX197_SENSOR_DEV_ATTR_IN(4),
+ MAX197_SENSOR_DEV_ATTR_IN(5),
+ MAX197_SENSOR_DEV_ATTR_IN(6),
+ MAX197_SENSOR_DEV_ATTR_IN(7),
+ NULL
+ }
+};
+
+static int __devinit max197_probe(struct platform_device *pdev)
+{
+ struct max197_chip *chip = kzalloc(sizeof *chip, GFP_KERNEL);
+ int ch, ret;
+
+ if (chip == NULL) {
+ ret = -ENOMEM;
+ goto error_ret;
+ }
+ platform_set_drvdata(pdev, chip);
+
+ mutex_init(&chip->lock);
+
+ if (pdev->dev.platform_data == NULL) {
+ dev_err(&pdev->dev, "no platform data supplied\n");
+ ret = -EINVAL;
+ goto error_free_chip;
+ }
+ chip->pdata = pdev->dev.platform_data;
+
+ if (chip->pdata->start == NULL) {
+ dev_err(&pdev->dev, "no start function supplied\n");
+ ret = -EINVAL;
+ goto error_free_chip;
+ }
+
+ if (chip->pdata->read == NULL) {
+ dev_err(&pdev->dev, "no read function supplied\n");
+ ret = -EINVAL;
+ goto error_free_chip;
+ }
+
+ if (strcmp("max197", pdev->name) == 0) {
+ chip->limit = MAX197_LIMIT;
+ chip->scale = true;
+ } else {
+ chip->limit = MAX199_LIMIT;
+ chip->scale = false;
+ }
+
+ for (ch = 0; ch < MAX197_NUM_CH; ch++)
+ chip->ctrl_bytes[ch] = (u8) ch;
+
+ ret = sysfs_create_group(&pdev->dev.kobj, &max197_sysfs_group);
+ if (ret) {
+ dev_err(&pdev->dev, "sysfs create group failed\n");
+ goto error_free_chip;
+ }
+
+ chip->hwmon_dev = hwmon_device_register(&pdev->dev);
+ if (IS_ERR(chip->hwmon_dev)) {
+ dev_err(&pdev->dev, "hwmon device register failed\n");
+ ret = PTR_ERR(chip->hwmon_dev);
+ goto error_release_sysfs_group;
+ }
+
+ return 0;
+
+error_release_sysfs_group:
+ sysfs_remove_group(&pdev->dev.kobj, &max197_sysfs_group);
+error_free_chip:
+ kfree(chip);
+error_ret:
+ return ret;
+}
+
+static int __devexit max197_remove(struct platform_device *pdev)
+{
+ struct max197_chip *chip = platform_get_drvdata(pdev);
+
+ hwmon_device_unregister(chip->hwmon_dev);
+ sysfs_remove_group(&pdev->dev.kobj, &max197_sysfs_group);
+ kfree(chip);
+
+ return 0;
+}
+
+static struct platform_driver __refdata maxim_drivers[] = {
+ {
+ .driver = {
+ .name = "max197",
+ .owner = THIS_MODULE,
+ },
+ .probe = max197_probe,
+ .remove = __devexit_p(max197_remove),
+ }, {
+ .driver = {
+ .name = "max199",
+ .owner = THIS_MODULE,
+ },
+ .probe = max197_probe,
+ .remove = __devexit_p(max197_remove),
+ }
+};
+
+static int __init max197_init(void)
+{
+ int ret;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(maxim_drivers); i++) {
+ ret = platform_driver_register(&maxim_drivers[i]);
+ if (ret)
+ goto error_unregister;
+ }
+
+ return 0;
+
+error_unregister:
+ while (--i >= 0)
+ platform_driver_unregister(&maxim_drivers[i]);
+
+ return ret;
+}
+module_init(max197_init);
+
+static void __exit max197_exit(void)
+{
+ int i;
+ for (i = ARRAY_SIZE(maxim_drivers) - 1; i >= 0; i--)
+ platform_driver_unregister(&maxim_drivers[i]);
+}
+module_exit(max197_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Maxim MAX197 ADC driver");
diff --git a/include/linux/max197.h b/include/linux/max197.h
new file mode 100644
index 0000000..25ec203
--- /dev/null
+++ b/include/linux/max197.h
@@ -0,0 +1,22 @@
+/*
+ * Maxim MAX197 A/D Converter Driver
+ *
+ * Copyright (c) 2012 Savoir-faire Linux Inc.
+ * Vivien Didelot <[email protected]>
+ *
+ * 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.
+ *
+ * For further information, see the Documentation/hwmon/max197 file.
+ */
+
+/**
+ * struct max197_platform_data - MAX197 connectivity info
+ * @start: function pointer to start a conversion.
+ * @read: function pointer to read a raw conversion result.
+ */
+struct max197_platform_data {
+ int (*start)(u8 ctrl_byte);
+ int (*read)(u16 *raw);
+};
--
1.7.6.5
Signed-off-by: Vivien Didelot <[email protected]>
---
arch/x86/platform/ts5500/Kconfig | 7 ++
arch/x86/platform/ts5500/Makefile | 1 +
arch/x86/platform/ts5500/ts5500_adc.c | 104 +++++++++++++++++++++++++++++++++
3 files changed, 112 insertions(+), 0 deletions(-)
create mode 100644 arch/x86/platform/ts5500/ts5500_adc.c
diff --git a/arch/x86/platform/ts5500/Kconfig b/arch/x86/platform/ts5500/Kconfig
index 76f777f..d6f5a8a 100644
--- a/arch/x86/platform/ts5500/Kconfig
+++ b/arch/x86/platform/ts5500/Kconfig
@@ -20,3 +20,10 @@ config TS5500_LED
default y
help
This option enables support for the on-chip LED.
+
+config TS5500_ADC
+ tristate "TS-5500 ADC"
+ depends on TS5500 && SENSORS_MAX197
+ default y
+ help
+ Support for the A/D converter on Technologic Systems TS-5500 SBCs.
diff --git a/arch/x86/platform/ts5500/Makefile b/arch/x86/platform/ts5500/Makefile
index 88eccc9..dcf46d8 100644
--- a/arch/x86/platform/ts5500/Makefile
+++ b/arch/x86/platform/ts5500/Makefile
@@ -1,3 +1,4 @@
obj-$(CONFIG_TS5500) += ts5500.o
obj-$(CONFIG_TS5500_GPIO) += ts5500_gpio.o
obj-$(CONFIG_TS5500_LED) += ts5500_led.o
+obj-$(CONFIG_TS5500_ADC) += ts5500_adc.o
diff --git a/arch/x86/platform/ts5500/ts5500_adc.c b/arch/x86/platform/ts5500/ts5500_adc.c
new file mode 100644
index 0000000..c55a283
--- /dev/null
+++ b/arch/x86/platform/ts5500/ts5500_adc.c
@@ -0,0 +1,104 @@
+/*
+ * Technologic Systems TS-5500 boards - Mapped MAX197 ADC driver
+ *
+ * Copyright (c) 2010-2012 Savoir-faire Linux Inc.
+ * Jonas Fonseca <[email protected]>
+ * Vivien Didelot <[email protected]>
+ *
+ * The TS-5500 uses a CPLD to abstract the interface to the MAX197.
+ * This driver should work unchanged with the MAX199 chip.
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/sysfs.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/max197.h>
+
+#define TS5500_ADC_CTRL_REG 0x195 /* Conversion state register */
+#define TS5500_ADC_INIT_LSB_REG 0x196 /* Init conv. / LSB register */
+#define TS5500_ADC_MSB_REG 0x197 /* MSB register */
+#define TS5500_ADC_READ_DELAY 12 /* usec */
+#define TS5500_ADC_READ_BUSY_MASK 0x01
+
+static int ts5500_adc_start(u8 ctrl)
+{
+ /* Ensure the 3 MSB are set to 0 */
+ outb(ctrl & 0x1F, TS5500_ADC_INIT_LSB_REG);
+
+ return 0;
+}
+
+static int ts5500_adc_read(u16 *raw)
+{
+ u8 lsb, msb;
+
+ udelay(TS5500_ADC_READ_DELAY);
+ lsb = inb(TS5500_ADC_CTRL_REG);
+
+ if (lsb & TS5500_ADC_READ_BUSY_MASK)
+ return -EBUSY;
+
+ lsb = inb(TS5500_ADC_INIT_LSB_REG);
+ msb = inb(TS5500_ADC_MSB_REG);
+ *raw = (msb << 8) | lsb;
+
+ return 0;
+}
+
+static struct max197_platform_data ts5500_adc_platform_data = {
+ .start = ts5500_adc_start,
+ .read = ts5500_adc_read
+};
+
+static void ts5500_adc_release(struct device *dev)
+{
+ /* noop */
+}
+
+static struct platform_device ts5500_adc_device = {
+ .name = "max197",
+ .id = -1,
+ .dev = {
+ .platform_data = &ts5500_adc_platform_data,
+ .release = ts5500_adc_release
+ }
+};
+
+static int __init ts5500_adc_init(void)
+{
+ return platform_device_register(&ts5500_adc_device);
+}
+module_init(ts5500_adc_init);
+
+static void __exit ts5500_adc_exit(void)
+{
+ platform_device_unregister(&ts5500_adc_device);
+}
+module_exit(ts5500_adc_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("TS-5500 mapped MAX197 ADC device driver");
--
1.7.6.5
From: Jonas Fonseca <[email protected]>
Signed-off-by: Jonas Fonseca <[email protected]>
Signed-off-by: Vivien Didelot <[email protected]>
---
arch/x86/platform/ts5500/Kconfig | 7 ++
arch/x86/platform/ts5500/Makefile | 1 +
arch/x86/platform/ts5500/ts5500_led.c | 179 +++++++++++++++++++++++++++++++++
3 files changed, 187 insertions(+), 0 deletions(-)
create mode 100644 arch/x86/platform/ts5500/ts5500_led.c
diff --git a/arch/x86/platform/ts5500/Kconfig b/arch/x86/platform/ts5500/Kconfig
index 62095c9..76f777f 100644
--- a/arch/x86/platform/ts5500/Kconfig
+++ b/arch/x86/platform/ts5500/Kconfig
@@ -13,3 +13,10 @@ config TS5500_GPIO
default y
help
This enables support for the DIO headers for GPIO usage.
+
+config TS5500_LED
+ tristate "TS-5500 LED Support"
+ depends on TS5500 && LEDS_CLASS
+ default y
+ help
+ This option enables support for the on-chip LED.
diff --git a/arch/x86/platform/ts5500/Makefile b/arch/x86/platform/ts5500/Makefile
index 71c1398..88eccc9 100644
--- a/arch/x86/platform/ts5500/Makefile
+++ b/arch/x86/platform/ts5500/Makefile
@@ -1,2 +1,3 @@
obj-$(CONFIG_TS5500) += ts5500.o
obj-$(CONFIG_TS5500_GPIO) += ts5500_gpio.o
+obj-$(CONFIG_TS5500_LED) += ts5500_led.o
diff --git a/arch/x86/platform/ts5500/ts5500_led.c b/arch/x86/platform/ts5500/ts5500_led.c
new file mode 100644
index 0000000..f4562fb
--- /dev/null
+++ b/arch/x86/platform/ts5500/ts5500_led.c
@@ -0,0 +1,179 @@
+/*
+ * Technologic Systems TS-5500 boards - LED driver
+ *
+ * Copyright (c) 2010-2012 Savoir-faire Linux Inc.
+ * Jonas Fonseca <[email protected]>
+ *
+ * Portions Copyright (c) 2008 Compulab, Ltd.
+ * Mike Rapoport <[email protected]>
+ *
+ * Portions Copyright (c) 2006-2008 Marvell International Ltd.
+ * Eric Miao <[email protected]>
+ *
+ * Based on drivers/leds/leds-da903x.c from linux-2.6.32.8.
+ *
+ * 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/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/leds.h>
+#include "ts5500.h"
+
+/**
+ * struct ts5500_led - LED structure
+ * @cdev: LED class device structure.
+ * @ioaddr: LED I/O address.
+ */
+struct ts5500_led {
+ struct led_classdev cdev;
+ int ioaddr;
+ int bit;
+};
+
+static void ts5500_led_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct ts5500_led *led = container_of(led_cdev, struct ts5500_led,
+ cdev);
+ outb(!!value, led->ioaddr);
+}
+
+static struct led_info ts5500_led_info = {
+ .name = "ts5500_led",
+ .default_trigger = "ts5500_led",
+ .flags = TS5500_LED_MASK
+};
+
+static struct led_platform_data ts5500_led_platform_data = {
+ .num_leds = 1,
+ .leds = &ts5500_led_info
+};
+
+static struct resource ts5500_led_resources[] = {
+ {
+ .name = "ts5500_led",
+ .start = TS5500_LED_JMPRS_REG,
+ .end = TS5500_LED_JMPRS_REG,
+ .flags = IORESOURCE_IO
+ }
+};
+
+static void ts5500_led_release(struct device *dev)
+{
+ /* noop */
+}
+
+static struct platform_device ts5500_led_device = {
+ .name = "ts5500_led",
+ .resource = ts5500_led_resources,
+ .num_resources = ARRAY_SIZE(ts5500_led_resources),
+ .id = -1,
+ .dev = {
+ .platform_data = &ts5500_led_platform_data,
+ .release = ts5500_led_release
+ }
+};
+
+static int __devinit ts5500_led_probe(struct platform_device *pdev)
+{
+ struct led_platform_data *pdata = pdev->dev.platform_data;
+ struct ts5500_led *led;
+ struct resource *res;
+ int ret;
+
+ if (pdata == NULL || !pdata->num_leds) {
+ dev_err(&pdev->dev, "No platform data available\n");
+ return -ENODEV;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_IO, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "Failed to get I/O resource\n");
+ return -EBUSY;
+ }
+
+ led = kzalloc(sizeof(struct ts5500_led), GFP_KERNEL);
+ if (led == NULL) {
+ dev_err(&pdev->dev, "Failed to alloc memory for LED device\n");
+ return -ENOMEM;
+ }
+
+ led->cdev.name = pdata->leds[0].name;
+ led->cdev.default_trigger = pdata->leds[0].default_trigger;
+ led->cdev.brightness_set = ts5500_led_set;
+ led->cdev.brightness = LED_OFF;
+
+ led->ioaddr = res->start;
+ led->bit = pdata->leds[0].flags;
+
+ ret = led_classdev_register(pdev->dev.parent, &led->cdev);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to register LED\n");
+ goto err;
+ }
+
+ platform_set_drvdata(pdev, led);
+ return 0;
+
+err:
+ kfree(led);
+ return ret;
+}
+
+static int __devexit ts5500_led_remove(struct platform_device *pdev)
+{
+ struct ts5500_led *led = platform_get_drvdata(pdev);
+
+ platform_set_drvdata(pdev, NULL);
+ led_classdev_unregister(&led->cdev);
+ kfree(led);
+
+ return 0;
+}
+
+static struct platform_driver ts5500_led_driver = {
+ .driver = {
+ .name = "ts5500_led",
+ .owner = THIS_MODULE
+ },
+ .probe = ts5500_led_probe,
+ .remove = __devexit_p(ts5500_led_remove)
+};
+
+static int __init ts5500_led_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&ts5500_led_driver);
+ if (ret)
+ goto error_out;
+
+ ret = platform_device_register(&ts5500_led_device);
+ if (ret)
+ goto error_device_register;
+
+ return 0;
+
+error_device_register:
+ platform_driver_unregister(&ts5500_led_driver);
+error_out:
+ return ret;
+}
+module_init(ts5500_led_init);
+
+static void __exit ts5500_led_exit(void)
+{
+ platform_driver_unregister(&ts5500_led_driver);
+ platform_device_unregister(&ts5500_led_device);
+}
+module_exit(ts5500_led_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("LED driver for Technologic Systems TS-5500");
--
1.7.6.5
From: Jerome Oufella <[email protected]>
Signed-off-by: Jerome Oufella <[email protected]>
Signed-off-by: Vivien Didelot <[email protected]>
---
arch/x86/platform/ts5500/Kconfig | 7 +
arch/x86/platform/ts5500/Makefile | 1 +
arch/x86/platform/ts5500/ts5500_gpio.c | 478 ++++++++++++++++++++++++++++++++
arch/x86/platform/ts5500/ts5500_gpio.h | 60 ++++
4 files changed, 546 insertions(+), 0 deletions(-)
create mode 100644 arch/x86/platform/ts5500/ts5500_gpio.c
create mode 100644 arch/x86/platform/ts5500/ts5500_gpio.h
diff --git a/arch/x86/platform/ts5500/Kconfig b/arch/x86/platform/ts5500/Kconfig
index 929b617..62095c9 100644
--- a/arch/x86/platform/ts5500/Kconfig
+++ b/arch/x86/platform/ts5500/Kconfig
@@ -6,3 +6,10 @@ config TS5500
Add support for the Technologic Systems TS-5500 platform.
If you have a TS-5500, say Y here.
+
+config TS5500_GPIO
+ tristate "TS-5500 GPIO support"
+ depends on TS5500 && GENERIC_GPIO
+ default y
+ help
+ This enables support for the DIO headers for GPIO usage.
diff --git a/arch/x86/platform/ts5500/Makefile b/arch/x86/platform/ts5500/Makefile
index 0a689a7..71c1398 100644
--- a/arch/x86/platform/ts5500/Makefile
+++ b/arch/x86/platform/ts5500/Makefile
@@ -1 +1,2 @@
obj-$(CONFIG_TS5500) += ts5500.o
+obj-$(CONFIG_TS5500_GPIO) += ts5500_gpio.o
diff --git a/arch/x86/platform/ts5500/ts5500_gpio.c b/arch/x86/platform/ts5500/ts5500_gpio.c
new file mode 100644
index 0000000..241ac80
--- /dev/null
+++ b/arch/x86/platform/ts5500/ts5500_gpio.c
@@ -0,0 +1,478 @@
+/*
+ * GPIO (DIO) driver for Technologic Systems TS-5500
+ *
+ * Copyright (c) 2010-2012 Savoir-faire Linux Inc.
+ * Jerome Oufella <[email protected]>
+ *
+ * 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.
+ *
+ * TS-5500 board has 38 GPIOs referred to as DIOs in the product's literature.
+ */
+
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/gpio.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include "ts5500_gpio.h"
+
+static void port_bit_set(u8 addr, int bit)
+{
+ u8 var;
+ var = inb(addr);
+ var |= (1 << bit);
+ outb(var, addr);
+}
+
+static void port_bit_clear(u8 addr, int bit)
+{
+ u8 var;
+ var = inb(addr);
+ var &= ~(1 << bit);
+ outb(var, addr);
+}
+
+/* "DIO" line to IO port mapping table for line's value */
+static const unsigned long line_to_port_map[] = {
+ 0x7B, 0x7B, 0x7B, 0x7B, 0x7B, 0x7B, 0x7B, 0x7B, /* DIO1_0~7 */
+ 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, /* DIO1_8~13 */
+ 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E, /* DIO2_0~7 */
+ 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, /* DIO2_8~13 */
+ 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, /* LCD_0~7 */
+ 0x73, 0x73, 0x73 /* LCD_EN, LCD_RS, LCD_WR */
+};
+
+/* "DIO" line to IO port's bit map for line's value */
+static const int line_to_bit_map[] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, /* DIO1_0~7 */
+ 0, 1, 2, 3, 4, 5, /* DIO1_8~13 */
+ 0, 1, 2, 3, 4, 5, 6, 7, /* DIO2_0~7 */
+ 0, 1, 2, 3, 4, 5, /* DIO2_8~13 */
+ 0, 1, 2, 3, 4, 5, 6, 7, /* LCD_0~7 */
+ 0, 7, 6 /* LCD_EN, LCD_RS, LCD_WR */
+};
+
+/* "DIO" line's direction control mapping table */
+static const unsigned long line_to_dir_map[] = {
+ 0x7A, 0x7A, 0x7A, 0x7A, 0x7A, 0x7A, 0x7A, 0x7A, /* DIO1_0~7 */
+ 0x7A, 0x7A, 0x7A, 0x7A, 0, 0, /* DIO1_8~13 */
+ 0x7D, 0x7D, 0x7D, 0x7D, 0x7D, 0x7D, 0x7D, 0x7D, /* DIO2_0~7 */
+ 0x7D, 0x7D, 0x7D, 0x7D, 0, 0, /* DIO2_8~13 */
+ 0x7D, 0x7D, 0x7D, 0x7D, 0x7D, 0x7D, 0x7D, 0x7D, /* LCD_0~7 */
+ 0, 0, 0 /* LCD_EN, LCD_RS, LCD_WR */
+};
+
+/* "DIO" line's direction control bit-mapping table */
+static const int line_to_dir_bit_map[] = {
+ 0, 0, 0, 0, 1, 1, 1, 1, /* DIO1_0~7 */
+ 5, 5, 5, 5, -1, -1, /* DIO1_8~13 */
+ 0, 0, 0, 0, 1, 1, 1, 1, /* DIO2_0~7 */
+ 5, 5, 5, 5, -1, -1, /* DIO2_8~13 */
+ 2, 2, 2, 2, 3, 3, 3, 3, /* LCD_0~7 */
+ -1, -1, -1 /* LCD_EN, LCD_RS, LCD_WR */
+};
+
+/* This array is used to track requests for our GPIO lines */
+static int requested_gpios[TS5500_LCD_WR + 1];
+
+static int dio1_irq = 1;
+module_param(dio1_irq, int, 0644);
+MODULE_PARM_DESC(dio1_irq,
+ "Enable usage of IRQ7 for any DIO1 line (default 1).");
+
+static int dio2_irq = 0;
+module_param(dio2_irq, int, 0644);
+MODULE_PARM_DESC(dio2_irq,
+ "Enable usage of IRQ6 for any DIO2 line (default 0).");
+
+static int lcd_irq = 0;
+module_param(lcd_irq, int, 0644);
+MODULE_PARM_DESC(lcd_irq, "Enable usage of IRQ1 for any LCD line (default 0).");
+
+static int use_lcdio = 0;
+module_param(use_lcdio, int, 0644);
+MODULE_PARM_DESC(use_lcdio, "Enable usage of the LCD header for DIO operation"
+ " (default 0).");
+
+/**
+ * struct ts5500_drvdata - Driver data
+ * @master: Device.
+ * @gpio_chip: GPIO chip.
+ * @gpio_lock: Read/Write Mutex.
+ */
+struct ts5500_drvdata {
+ struct device *master;
+ struct gpio_chip gpio_chip;
+ struct mutex gpio_lock;
+};
+
+static int ts5500_gpio_request(struct gpio_chip *chip, unsigned offset)
+{
+ struct ts5500_drvdata *drvdata;
+
+ drvdata = container_of(chip, struct ts5500_drvdata, gpio_chip);
+
+ mutex_lock(&drvdata->gpio_lock);
+ if (requested_gpios[offset]) {
+ mutex_unlock(&drvdata->gpio_lock);
+ return -EBUSY;
+ }
+ requested_gpios[offset] = 1;
+ mutex_unlock(&drvdata->gpio_lock);
+
+ return 0;
+}
+
+static void ts5500_gpio_free(struct gpio_chip *chip, unsigned offset)
+{
+ struct ts5500_drvdata *drvdata;
+
+ drvdata = container_of(chip, struct ts5500_drvdata, gpio_chip);
+
+ mutex_lock(&drvdata->gpio_lock);
+ requested_gpios[offset] = 0;
+ mutex_unlock(&drvdata->gpio_lock);
+}
+
+static int ts5500_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+ unsigned long ioaddr;
+ u8 byte;
+ int bitno;
+ struct ts5500_drvdata *drvdata;
+
+ drvdata = container_of(chip, struct ts5500_drvdata, gpio_chip);
+
+ /* Some lines are output-only and cannot be read */
+ if (offset == TS5500_LCD_EN || offset > chip->ngpio)
+ return -ENXIO;
+
+ ioaddr = line_to_port_map[offset];
+ bitno = line_to_bit_map[offset];
+ byte = inb(ioaddr);
+
+ return (byte >> bitno) & 0x1;
+}
+
+static void ts5500_gpio_set(struct gpio_chip *chip, unsigned offset, int val)
+{
+ int bitno;
+ unsigned long ioaddr;
+ struct ts5500_drvdata *drvdata;
+
+ drvdata = container_of(chip, struct ts5500_drvdata, gpio_chip);
+
+ /* Some lines just can't be set */
+ switch (offset) {
+ case TS5500_DIO1_12:
+ case TS5500_DIO1_13:
+ case TS5500_DIO2_13:
+ case TS5500_LCD_RS:
+ case TS5500_LCD_WR:
+ return;
+ default:
+ if (offset > chip->ngpio)
+ return;
+ break;
+ }
+
+ /* Get io port and bit for 'offset' */
+ ioaddr = line_to_port_map[offset];
+ bitno = line_to_bit_map[offset];
+
+ mutex_lock(&drvdata->gpio_lock);
+ if (val == 0)
+ port_bit_clear(ioaddr, bitno);
+ else
+ port_bit_set(ioaddr, bitno);
+ mutex_unlock(&drvdata->gpio_lock);
+}
+
+static int ts5500_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
+{
+ /* Only a few lines are IRQ-Capable */
+ switch (offset) {
+ case TS5500_DIO1_13:
+ return TS5500_DIO1_13_IRQ;
+ case TS5500_DIO2_13:
+ return TS5500_DIO2_13_IRQ;
+ case TS5500_LCD_RS:
+ return TS5500_LCD_RS_IRQ;
+ default:
+ break;
+ }
+
+ /*
+ * Handle the case where the user bridged the IRQ line with another
+ * DIO line from the same header.
+ */
+ if (dio1_irq && offset >= TS5500_DIO1_0 && offset < TS5500_DIO1_13)
+ return TS5500_DIO1_13_IRQ;
+
+ if (dio2_irq && offset >= TS5500_DIO2_0 && offset < TS5500_DIO2_13)
+ return TS5500_DIO2_13_IRQ;
+
+ if (lcd_irq && offset >= TS5500_LCD_0 && offset <= TS5500_LCD_WR)
+ return TS5500_LCD_RS_IRQ;
+
+ return -ENXIO;
+}
+
+static int ts5500_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
+{
+ unsigned long dir_reg;
+ int dir_bit;
+ struct ts5500_drvdata *drvdata;
+
+ drvdata = container_of(chip, struct ts5500_drvdata, gpio_chip);
+
+ /* Some lines cannot be set as inputs */
+ switch (offset) {
+ case TS5500_LCD_EN:
+ return -ENXIO;
+ default:
+ if (offset > chip->ngpio)
+ return -ENXIO;
+ break;
+ }
+
+ dir_reg = line_to_dir_map[offset];
+ dir_bit = line_to_dir_bit_map[offset];
+
+ mutex_lock(&drvdata->gpio_lock);
+ port_bit_clear(dir_reg, dir_bit);
+ mutex_unlock(&drvdata->gpio_lock);
+
+ return 0;
+}
+
+static int ts5500_gpio_direction_output(struct gpio_chip *chip, unsigned offset,
+ int val)
+{
+ unsigned long dir_reg, ioaddr;
+ int dir_bit, bitno;
+ struct ts5500_drvdata *drvdata;
+
+ drvdata = container_of(chip, struct ts5500_drvdata, gpio_chip);
+
+ /* Some lines cannot be set as outputs */
+ switch (offset) {
+ case TS5500_DIO1_12:
+ case TS5500_DIO1_13:
+ case TS5500_DIO2_13:
+ case TS5500_LCD_RS:
+ case TS5500_LCD_WR:
+ return -ENXIO;
+ default:
+ if (offset > chip->ngpio)
+ return -ENXIO;
+ break;
+ }
+
+ /* Get direction and value registers infos */
+ dir_reg = line_to_dir_map[offset];
+ dir_bit = line_to_dir_bit_map[offset];
+ ioaddr = line_to_port_map[offset];
+ bitno = line_to_bit_map[offset];
+
+ mutex_lock(&drvdata->gpio_lock);
+ if (val == 0)
+ port_bit_clear(ioaddr, bitno); /* Set initial line value */
+ else
+ port_bit_set(ioaddr, bitno);
+ port_bit_set(dir_reg, dir_bit); /* Set output direction for line */
+
+ /*
+ * Confirm initial line output value
+ * (might have been changed by input)
+ */
+ if (val == 0)
+ port_bit_clear(ioaddr, bitno);
+ else
+ port_bit_set(ioaddr, bitno);
+ mutex_unlock(&drvdata->gpio_lock);
+
+ return 0;
+}
+
+static int __devinit ts5500_gpio_probe(struct platform_device *pdev)
+{
+ struct ts5500_drvdata *drvdata;
+ struct gpio_chip *chip;
+ int ret;
+
+ if (pdev == NULL) {
+ dev_err(&pdev->dev, "Platform device not available!\n");
+ return -ENODEV;
+ }
+
+ /* Request DIO1 */
+ if (!request_region(0x7A, 3, "ts5500-gpio-DIO1")) {
+ dev_err(&pdev->dev, "Cannot request I/O port 0x7A-7C\n");
+ goto err_req_dio1;
+ }
+
+ /* Request DIO2 */
+ if (!request_region(0x7D, 3, "ts5500-gpio-DIO2")) {
+ dev_err(&pdev->dev, "Cannot request I/O port 0x7D-7F\n");
+ goto err_req_dio2;
+ }
+
+ /* Request LCDIO if wanted */
+ if (use_lcdio && !request_region(0x72, 2, "ts5500-gpio-LCD")) {
+ dev_err(&pdev->dev, "Cannot request I/O port 0x72-73\n");
+ goto err_req_lcdio;
+ }
+
+ /* Setup the gpio_chip structure */
+ drvdata = kzalloc(sizeof(struct ts5500_drvdata), GFP_KERNEL);
+ if (drvdata == NULL)
+ goto err_alloc_dev;
+
+ memset(requested_gpios, 0, sizeof(requested_gpios));
+ mutex_init(&drvdata->gpio_lock);
+
+ drvdata->master = pdev->dev.parent;
+ chip = &drvdata->gpio_chip;
+ chip->request = ts5500_gpio_request;
+ chip->free = ts5500_gpio_free;
+ chip->to_irq = ts5500_gpio_to_irq;
+ chip->direction_input = ts5500_gpio_direction_input;
+ chip->direction_output = ts5500_gpio_direction_output;
+ chip->get = ts5500_gpio_get;
+ chip->set = ts5500_gpio_set;
+ chip->can_sleep = 0;
+ chip->base = TS5500_DIO1_0;
+ chip->label = pdev->name;
+ chip->ngpio = (use_lcdio ? TS5500_LCD_WR + 1 : TS5500_DIO2_13 + 1);
+
+ /* Enable IRQ generation */
+ mutex_lock(&drvdata->gpio_lock);
+ port_bit_set(0x7A, 7); /* DIO1_13 on IRQ7 */
+ port_bit_set(0x7D, 7); /* DIO2_13 on IRQ6 */
+ if (use_lcdio) {
+ port_bit_clear(0x7D, 4); /* Enable LCD header usage as DIO */
+ port_bit_set(0x7D, 6); /* LCD_RS on IRQ1 */
+ }
+ mutex_unlock(&drvdata->gpio_lock);
+
+ /* Register chip */
+ ret = gpiochip_add(&drvdata->gpio_chip);
+ if (ret)
+ goto err_gpiochip_add;
+
+ platform_set_drvdata(pdev, drvdata);
+
+ return 0;
+
+err_gpiochip_add:
+ dev_err(&pdev->dev, "Failed to register the gpio chip.\n");
+ kfree(drvdata);
+
+err_alloc_dev:
+ if (use_lcdio)
+ release_region(0x72, 2); /* Release LCD's region */
+
+err_req_lcdio:
+ release_region(0x7D, 3); /* Release DIO2's region */
+
+err_req_dio2:
+ release_region(0x7A, 3); /* Release DIO1's region */
+
+err_req_dio1:
+ ret = -EBUSY;
+
+ return ret;
+}
+
+/* Callback for releasing resources */
+static void ts5500_gpio_device_release(struct device *dev)
+{
+ /* noop */
+}
+
+static struct platform_device ts5500_gpio_device = {
+ .name = "ts5500_gpio",
+ .id = -1,
+ .dev = {
+ .release = ts5500_gpio_device_release,
+ }
+};
+
+static int __devexit ts5500_gpio_remove(struct platform_device *pdev)
+{
+ struct ts5500_drvdata *drvdata;
+ int ret, i;
+
+ drvdata = platform_get_drvdata(pdev);
+
+ /* Release GPIO lines */
+ for (i = 0; i < ARRAY_SIZE(requested_gpios); i++) {
+ if (requested_gpios[i])
+ gpio_free(i);
+ }
+
+ mutex_lock(&drvdata->gpio_lock);
+ /* Disable IRQs generation */
+ port_bit_clear(0x7A, 7);
+ port_bit_clear(0x7D, 7);
+ if (use_lcdio)
+ port_bit_clear(0x7D, 6);
+
+ /* Release IO regions */
+ release_region(0x7A, 3);
+ release_region(0x7D, 3);
+ if (use_lcdio)
+ release_region(0x72, 2);
+ mutex_unlock(&drvdata->gpio_lock);
+
+ ret = gpiochip_remove(&drvdata->gpio_chip);
+ if (ret)
+ dev_err(&pdev->dev, "Failed to remove the gpio chip\n");
+
+ kfree(drvdata);
+ return ret;
+}
+
+static struct platform_driver ts5500_gpio_driver = {
+ .driver = {
+ .name = "ts5500_gpio",
+ .owner = THIS_MODULE,
+ },
+ .probe = ts5500_gpio_probe,
+ .remove = __devexit_p(ts5500_gpio_remove)
+};
+
+static int __init ts5500_gpio_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&ts5500_gpio_driver);
+ if (ret)
+ goto error_out;
+
+ ret = platform_device_register(&ts5500_gpio_device);
+ if (ret)
+ goto error_device_register;
+
+ return 0;
+
+error_device_register:
+ platform_driver_unregister(&ts5500_gpio_driver);
+error_out:
+ return ret;
+}
+module_init(ts5500_gpio_init);
+
+static void __exit ts5500_gpio_exit(void)
+{
+ platform_driver_unregister(&ts5500_gpio_driver);
+ platform_device_unregister(&ts5500_gpio_device);
+}
+module_exit(ts5500_gpio_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Technologic Systems TS-5500, GPIO/DIO driver");
diff --git a/arch/x86/platform/ts5500/ts5500_gpio.h b/arch/x86/platform/ts5500/ts5500_gpio.h
new file mode 100644
index 0000000..eecd4fc
--- /dev/null
+++ b/arch/x86/platform/ts5500/ts5500_gpio.h
@@ -0,0 +1,60 @@
+/*
+ * GPIO (DIO) driver for Technologic Systems TS-5500
+ *
+ * Copyright (c) 2010-2012 Savoir-faire Linux Inc.
+ * Jerome Oufella <[email protected]>
+ *
+ * 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.
+ */
+
+#ifndef _TS5500_GPIO_H
+#define _TS5500_GPIO_H
+
+#define TS5500_DIO1_0 0
+#define TS5500_DIO1_1 1
+#define TS5500_DIO1_2 2
+#define TS5500_DIO1_3 3
+#define TS5500_DIO1_4 4
+#define TS5500_DIO1_5 5
+#define TS5500_DIO1_6 6
+#define TS5500_DIO1_7 7
+#define TS5500_DIO1_8 8
+#define TS5500_DIO1_9 9
+#define TS5500_DIO1_10 10
+#define TS5500_DIO1_11 11
+#define TS5500_DIO1_12 12
+#define TS5500_DIO1_13 13
+#define TS5500_DIO2_0 14
+#define TS5500_DIO2_1 15
+#define TS5500_DIO2_2 16
+#define TS5500_DIO2_3 17
+#define TS5500_DIO2_4 18
+#define TS5500_DIO2_5 19
+#define TS5500_DIO2_6 20
+#define TS5500_DIO2_7 21
+#define TS5500_DIO2_8 22
+#define TS5500_DIO2_9 23
+#define TS5500_DIO2_10 24
+#define TS5500_DIO2_11 25
+/* #define TS5500_DIO2_12 - Keep commented out as it simply doesn't exist. */
+#define TS5500_DIO2_13 26
+#define TS5500_LCD_0 27
+#define TS5500_LCD_1 28
+#define TS5500_LCD_2 29
+#define TS5500_LCD_3 30
+#define TS5500_LCD_4 31
+#define TS5500_LCD_5 32
+#define TS5500_LCD_6 33
+#define TS5500_LCD_7 34
+#define TS5500_LCD_EN 35
+#define TS5500_LCD_RS 36
+#define TS5500_LCD_WR 37
+
+/* Lines that can trigger IRQs */
+#define TS5500_DIO1_13_IRQ 7
+#define TS5500_DIO2_13_IRQ 6
+#define TS5500_LCD_RS_IRQ 1
+
+#endif /* _TS5500_GPIO_H */
--
1.7.6.5
> +obj-$(CONFIG_TS5500_GPIO) += ts5500_gpio.o
Wants to be gpi-ts5500 and in the drivers/gpio directory.
Nothing else particularly strikes me abou tit
Hi Vivien,
On Wed, 2012-02-01 at 16:05 -0500, Vivien Didelot wrote:
> Signed-off-by: Vivien Didelot <[email protected]>
Nicely done. Looks pretty good. Couple of minor comments below.
> ---
> Documentation/hwmon/max197 | 54 ++++++
> drivers/hwmon/Kconfig | 9 +
> drivers/hwmon/Makefile | 1 +
> drivers/hwmon/max197.c | 403 ++++++++++++++++++++++++++++++++++++++++++++
> include/linux/max197.h | 22 +++
> 5 files changed, 489 insertions(+), 0 deletions(-)
> create mode 100644 Documentation/hwmon/max197
> create mode 100644 drivers/hwmon/max197.c
> create mode 100644 include/linux/max197.h
>
> diff --git a/Documentation/hwmon/max197 b/Documentation/hwmon/max197
> new file mode 100644
> index 0000000..aa0934f
> --- /dev/null
> +++ b/Documentation/hwmon/max197
> @@ -0,0 +1,54 @@
> +Maxim MAX197 driver
> +===================
> +
> +Author:
> + * Vivien Didelot <[email protected]>
> +
> +Supported chips:
> + * Maxim MAX197
> + Prefix: 'max197'
> + Datasheet: http://datasheets.maxim-ic.com/en/ds/MAX197.pdf
> +
> + * Maxim MAX199
> + Prefix: 'max199'
> + Datasheet: http://datasheets.maxim-ic.com/en/ds/MAX199.pdf
> +
> +Description
> +-----------
> +
> +The A/D converters MAX197 and MAX199 are both 8-Channel, Multi-Range, 5V,
> +12-Bit DAS with 8+4 Bus Interface and Fault Protection.
> +
> +The available ranges for the MAX197 are {0,-5V} to 5V, and {0,-10V} to 10V,
> +while they are {0,-2V} to 2V, and {0,-4V} to 4V on the MAX199.
> +
> +Platform data
> +-------------
> +
> +The MAX197 platform data (defined in linux/max197.h) should be filled with two
> +function pointers:
> +
> +* start:
> + The function which writes the control byte to start a new conversion.
> +* read:
> + The function used to read the raw value from the chip.
> +
> +Control-Byte Format:
> +
> +Bit Name Description
> +7,6 PD1,PD0 Clock and Power-Down modes
> +5 ACQMOD Internal or External Controlled Acquisition
> +4 RNG Full-scale voltage magnitude at the input
> +3 BIP Unipolar or Bipolar conversion mode
> +2,1,0 A2,A1,A0 Channel
> +
> +Sysfs interface
> +---------------
> +
> +* in[0-7]_input: The conversion value for the corresponding channel.
> +* in[0-7]_min: The lower limit (in mV) for the corresponding channel.
> + It can be one value in -10000, -5000, -4000, -2000, 0,
> + depending on the chip.
> +* in[0-7]_max: The higher limit (in mV) for the corresponding channel.
> + It can be one value in 2000, 4000, 5000, 10000,
> + depending on the chip.
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index 91be41f..ccdf59b 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -1360,6 +1360,15 @@ config SENSORS_MC13783_ADC
> help
> Support for the A/D converter on MC13783 PMIC.
>
> +config SENSORS_MAX197
> + tristate "Maxim MAX197 and compatibles."
> + help
> + If you say yes here you get support for the Maxim MAX197,
> + and MAX199 A/D converters.
> +
> + This driver can also be built as a module. If so, the module
> + will be called max197.
> +
> if ACPI
>
> comment "ACPI drivers"
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index 8251ce8..dfb73d9 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -125,6 +125,7 @@ obj-$(CONFIG_SENSORS_W83L785TS) += w83l785ts.o
> obj-$(CONFIG_SENSORS_W83L786NG) += w83l786ng.o
> obj-$(CONFIG_SENSORS_WM831X) += wm831x-hwmon.o
> obj-$(CONFIG_SENSORS_WM8350) += wm8350-hwmon.o
> +obj-$(CONFIG_SENSORS_MAX197) += max197.o
>
> obj-$(CONFIG_PMBUS) += pmbus/
>
> diff --git a/drivers/hwmon/max197.c b/drivers/hwmon/max197.c
> new file mode 100644
> index 0000000..985615c
> --- /dev/null
> +++ b/drivers/hwmon/max197.c
> @@ -0,0 +1,403 @@
> +/*
> + * Maxim MAX197 A/D Converter Driver
> + *
> + * Copyright (c) 2012 Savoir-faire Linux Inc.
> + * Vivien Didelot <[email protected]>
> + *
> + * 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.
> + *
> + * For further information, see the Documentation/hwmon/max197 file.
> + */
> +
> +#include <linux/init.h>
> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/device.h>
> +#include <linux/delay.h>
> +#include <linux/platform_device.h>
> +#include <linux/err.h>
> +#include <linux/sysfs.h>
> +#include <linux/hwmon.h>
> +#include <linux/hwmon-sysfs.h>
> +#include <linux/mutex.h>
> +#include <linux/slab.h>
> +#include <linux/io.h>
> +#include <linux/max197.h>
> +
> +#define MAX199_LIMIT 4000 /* 4V */
> +#define MAX197_LIMIT 10000 /* 10V */
> +
> +#define MAX197_NUM_CH 8 /* 8 Analog Input Channels */
> +
> +/* Control byte format */
> +#define MAX197_A0 0x01 /* Channel bit 0 */
> +#define MAX197_A1 0x02 /* Channel bit 1 */
> +#define MAX197_A2 0x04 /* Channel bit 2 */
> +#define MAX197_BIP 0x08 /* Bipolarity */
> +#define MAX197_RNG 0x10 /* Full range */
> +#define MAX197_ACQMOD 0x20 /* Internal/External controlled acquisition */
> +#define MAX197_PD0 0x40 /* Clock/Power-Down modes bit 1 */
> +#define MAX197_PD1 0x80 /* Clock/Power-Down modes bit 2 */
> +
> +#define MAX197_SCALE 12207 /* Scale coefficient for raw data */
> +
> +/**
> + * struct max197_chip - device instance specific data
> + * @pdata: Platform data.
> + * @hwmon_dev: The hwmon device.
> + * @lock: Read/Write mutex.
> + * @limit: Max range value (10V for MAX197, 4V for MAX199).
> + * @scale: Need to scale.
> + * @ctrl_bytes: Channels control byte.
> + */
> +struct max197_chip {
> + struct max197_platform_data *pdata;
> + struct device *hwmon_dev;
> + struct mutex lock;
> + int limit;
> + bool scale;
> + u8 ctrl_bytes[MAX197_NUM_CH];
> +};
> +
> +static inline void max197_set_unipolarity(struct max197_chip *chip, int channel)
> +{
> + chip->ctrl_bytes[channel] &= ~MAX197_BIP;
> +}
> +
> +static inline void max197_set_bipolarity(struct max197_chip *chip, int channel)
> +{
> + chip->ctrl_bytes[channel] |= MAX197_BIP;
> +}
> +
> +static inline void max197_set_half_range(struct max197_chip *chip, int channel)
> +{
> + chip->ctrl_bytes[channel] &= ~MAX197_RNG;
> +}
> +
> +static inline void max197_set_full_range(struct max197_chip *chip, int channel)
> +{
> + chip->ctrl_bytes[channel] |= MAX197_RNG;
> +}
> +
> +static inline bool max197_is_bipolar(struct max197_chip *chip, int channel)
> +{
> + return !!(chip->ctrl_bytes[channel] & MAX197_BIP);
> +}
> +
> +static inline bool max197_is_full_range(struct max197_chip *chip, int channel)
> +{
> + return !!(chip->ctrl_bytes[channel] & MAX197_RNG);
> +}
> +
Interestingly enough, you don't need the !!() above. The C language
defines that the result of an operation assigned to a bool is always
converted to either 0 or 1. Not that it matters, really - I personally
actually prefer you style.
> +/**
> + * max197_show_range() - Display range on user output
> + *
> + * Function called on read access on in{0,1,2,3,4,5,6,7}_{min,max}
> + */
> +static ssize_t max197_show_range(struct device *dev,
> + struct device_attribute *devattr, char *buf)
> +{
> + struct max197_chip *chip = dev_get_drvdata(dev);
> + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr);
> + int channel = attr->index;
> + bool is_min = attr->nr;
> + int range;
> +
> + if (mutex_lock_interruptible(&chip->lock))
> + return -ERESTARTSYS;
> +
> + range = max197_is_full_range(chip, channel) ?
> + chip->limit : chip->limit / 2;
> + if (is_min) {
> + if (max197_is_bipolar(chip, channel))
> + range = -range;
> + else
> + range = 0;
> + }
> +
> + mutex_unlock(&chip->lock);
> +
> + return sprintf(buf, "%d\n", range);
> +}
> +
> +/**
> + * max197_store_range() - Write range from user input
> + *
> + * Function called on write access on in{0,1,2,3,4,5,6,7}_{min,max}
> + */
> +static ssize_t max197_store_range(struct device *dev,
> + struct device_attribute *devattr,
> + const char *buf, size_t count)
> +{
> + struct max197_chip *chip = dev_get_drvdata(dev);
> + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(devattr);
> + int channel = attr->index;
> + bool is_min = attr->nr;
> + long value;
> + int half = chip->limit / 2;
> + int full = chip->limit;
> +
> + if (kstrtol(buf, 10, &value))
> + return -EINVAL;
> +
> + if (is_min) {
> + if ((value != 0) && (value != -half) && (value != -full))
> + return -EINVAL;
> + } else {
> + if ((value != half) && (value != full))
> + return -EINVAL;
> + }
> +
Technically ok, except for the unnecessary ( ). Would be great if you
could remove those.
Since the actual limits are chip dependent, and not easily to figure out
for the ordinary user, it would be nice to either accept any number and
adjust it to one of the accepted values, or to explicitly spell out the
accepted the per-chip accepted values in the documentation (and not just
say "depending on the chip"). I'll leave it up to you to decide which
way to go.
Not too difficult - if you change the code, something like
if (is_min) {
if (value <= -full)
value = -full;
else if (value < 0)
value = -half;
else value = 0;
} else {
if (value >= full)
value = full;
else
value = half;
}
would be good enough.
Thanks,
Guenter
On Wed, 2012-02-01 at 16:30 -0500, Alan Cox wrote:
> > +obj-$(CONFIG_TS5500_GPIO) += ts5500_gpio.o
>
> Wants to be gpi-ts5500 and in the drivers/gpio directory.
>
I would agree, though there seem to be some disagreements about where
such platform specific drivers should be located. From an earlier
exchange I had with Vivien about this matter:
> > > would be the appropriate location for a driver like this. As
> > > mentioned before, my strong preference is drivers/hwmon, but I
> > > would like to hear from others.
> >
> > We should either split every driver into corresponding
> > subdirectories, or put everything in a common platform directory.
> > My first RFC patches set has every driver separated. As they are
> > really specific to the platform, people seem to agree with grouping
> > them, mainly because they won't be shared. I changed that in the
> > following patches sets, and X86 maintainers seemed to be ok with
> > that.
> >
> > I'm ok with both solutions, but we should all agree on one.
> > Maybe we should have other maintainers view on this?
> >
> That is what I had asked for. I thought the whole point of per-module
> directories was to have all drivers there. If that is no longer true,
> fine with me; who am I to argue about something like that.
> I'd just like to know.
>
It looks like things are going back and forth a bit. If I were Vivien, I
would be a bit frustrated by now and be close to giving up (Vivien, I
really commend you for your patience).
Is there a written guideline or policy people can look and point to
if/when something like this comes up ? Otherwise we may have the
LED/GPIO/xxx maintainers point one way, the X86 maintainers point the
other way, and thus may have reached a complete deadlock.
Guenter
On Wed, Feb 01, 2012 at 04:05:41PM -0500, Vivien Didelot wrote:
> arch/x86/platform/ts5500/Kconfig | 7 +
> arch/x86/platform/ts5500/Makefile | 1 +
> arch/x86/platform/ts5500/ts5500_gpio.c | 478 ++++++++++++++++++++++++++++++++
> arch/x86/platform/ts5500/ts5500_gpio.h | 60 ++++
As previously pointed out rather than hiding these drivers in arch/
directories there's a push to ship them in drivers/ where they benefit
from better subsystem review.
> +static int ts5500_gpio_request(struct gpio_chip *chip, unsigned offset)
> +{
> + struct ts5500_drvdata *drvdata;
> +
> + drvdata = container_of(chip, struct ts5500_drvdata, gpio_chip);
> +
> + mutex_lock(&drvdata->gpio_lock);
> + if (requested_gpios[offset]) {
> + mutex_unlock(&drvdata->gpio_lock);
> + return -EBUSY;
> + }
> + requested_gpios[offset] = 1;
> + mutex_unlock(&drvdata->gpio_lock);
This is all redundant, gpiolib will check this for you.
> +static void ts5500_gpio_set(struct gpio_chip *chip, unsigned offset, int val)
> +{
This (and get()) should be a _cansleep() operation because...
> + mutex_lock(&drvdata->gpio_lock);
> + if (val == 0)
> + port_bit_clear(ioaddr, bitno);
> + else
> + port_bit_set(ioaddr, bitno);
> + mutex_unlock(&drvdata->gpio_lock);
...you take a mutex which needs process context.
> +static int ts5500_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
> +{
> + /* Only a few lines are IRQ-Capable */
> + switch (offset) {
> + case TS5500_DIO1_13:
> + return TS5500_DIO1_13_IRQ;
> + case TS5500_DIO2_13:
> + return TS5500_DIO2_13_IRQ;
> + case TS5500_LCD_RS:
> + return TS5500_LCD_RS_IRQ;
Why are these numbers compile time constants?
> + /* Setup the gpio_chip structure */
> + drvdata = kzalloc(sizeof(struct ts5500_drvdata), GFP_KERNEL);
> + if (drvdata == NULL)
> + goto err_alloc_dev;
Looks like you'd benefit from using devm_ functions for most if not all
of the allocation.
> +/* Callback for releasing resources */
> +static void ts5500_gpio_device_release(struct device *dev)
> +{
> + /* noop */
> +}
> +
> +static struct platform_device ts5500_gpio_device = {
> + .name = "ts5500_gpio",
> + .id = -1,
> + .dev = {
> + .release = ts5500_gpio_device_release,
> + }
> +};
I'm not sure what this is all about but it looks fairly obviously wrong.
> +static int __devexit ts5500_gpio_remove(struct platform_device *pdev)
> +{
> + struct ts5500_drvdata *drvdata;
> + int ret, i;
> +
> + drvdata = platform_get_drvdata(pdev);
> +
> + /* Release GPIO lines */
> + for (i = 0; i < ARRAY_SIZE(requested_gpios); i++) {
> + if (requested_gpios[i])
> + gpio_free(i);
> + }
You shouldn't be doing this, if it was sensible then gpiolib ought to be
doing it for you.
On Thu, Feb 02, 2012 at 11:33:56AM -0800, Guenter Roeck wrote:
> On Wed, 2012-02-01 at 16:30 -0500, Alan Cox wrote:
> > > My first RFC patches set has every driver separated. As they are
> > > really specific to the platform, people seem to agree with grouping
> > > them, mainly because they won't be shared. I changed that in the
> > > following patches sets, and X86 maintainers seemed to be ok with
> > > that.
> It looks like things are going back and forth a bit. If I were Vivien, I
> would be a bit frustrated by now and be close to giving up (Vivien, I
> really commend you for your patience).
OTOH I just looked back and saw that some of the review comments I just
made were also made against the first version of this driver I noticed
(v2) but appear to have been ignored, the request tracking stands out.
> Is there a written guideline or policy people can look and point to
> if/when something like this comes up ? Otherwise we may have the
> LED/GPIO/xxx maintainers point one way, the X86 maintainers point the
> other way, and thus may have reached a complete deadlock.
I'm not sure I'm seeing much conflict here TBH, looking at the above and
the history I have to hand I'd say it's reading like the x86 maintainers
aren't fussed either way and the people doing kernel wide work on things
like this prefer getting stuff integrated into the frameworks.
Le Wed, 1 Feb 2012 13:35:38 -0800,
Guenter Roeck <[email protected]> a ?crit :
> Hi Vivien,
>
> On Wed, 2012-02-01 at 16:05 -0500, Vivien Didelot wrote:
> > Signed-off-by: Vivien Didelot <[email protected]>
>
> Nicely done. Looks pretty good. Couple of minor comments below.
>
> > ---
> > Documentation/hwmon/max197 | 54 ++++++
> > drivers/hwmon/Kconfig | 9 +
> > drivers/hwmon/Makefile | 1 +
> > drivers/hwmon/max197.c | 403
> > ++++++++++++++++++++++++++++++++++++++++++++
> > include/linux/max197.h | 22 +++ 5 files changed, 489
> > insertions(+), 0 deletions(-) create mode 100644
> > Documentation/hwmon/max197 create mode 100644 drivers/hwmon/max197.c
> > create mode 100644 include/linux/max197.h
> >
> > diff --git a/Documentation/hwmon/max197 b/Documentation/hwmon/max197
> > new file mode 100644
> > index 0000000..aa0934f
> > --- /dev/null
> > +++ b/Documentation/hwmon/max197
> > @@ -0,0 +1,54 @@
> > +Maxim MAX197 driver
> > +===================
> > +
> > +Author:
> > + * Vivien Didelot <[email protected]>
> > +
> > +Supported chips:
> > + * Maxim MAX197
> > + Prefix: 'max197'
> > + Datasheet: http://datasheets.maxim-ic.com/en/ds/MAX197.pdf
> > +
> > + * Maxim MAX199
> > + Prefix: 'max199'
> > + Datasheet: http://datasheets.maxim-ic.com/en/ds/MAX199.pdf
> > +
> > +Description
> > +-----------
> > +
> > +The A/D converters MAX197 and MAX199 are both 8-Channel,
> > Multi-Range, 5V, +12-Bit DAS with 8+4 Bus Interface and Fault
> > Protection. +
> > +The available ranges for the MAX197 are {0,-5V} to 5V, and
> > {0,-10V} to 10V, +while they are {0,-2V} to 2V, and {0,-4V} to 4V
> > on the MAX199. +
> > +Platform data
> > +-------------
> > +
> > +The MAX197 platform data (defined in linux/max197.h) should be
> > filled with two +function pointers:
> > +
> > +* start:
> > + The function which writes the control byte to start a new
> > conversion. +* read:
> > + The function used to read the raw value from the chip.
> > +
> > +Control-Byte Format:
> > +
> > +Bit Name Description
> > +7,6 PD1,PD0 Clock and Power-Down modes
> > +5 ACQMOD Internal or External Controlled Acquisition
> > +4 RNG Full-scale voltage magnitude at the input
> > +3 BIP Unipolar or Bipolar conversion mode
> > +2,1,0 A2,A1,A0 Channel
> > +
> > +Sysfs interface
> > +---------------
> > +
> > +* in[0-7]_input: The conversion value for the corresponding
> > channel. +* in[0-7]_min: The lower limit (in mV) for the
> > corresponding channel.
> > + It can be one value in -10000, -5000, -4000,
> > -2000, 0,
> > + depending on the chip.
> > +* in[0-7]_max: The higher limit (in mV) for the corresponding
> > channel.
> > + It can be one value in 2000, 4000, 5000, 10000,
> > + depending on the chip.
> > diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> > index 91be41f..ccdf59b 100644
> > --- a/drivers/hwmon/Kconfig
> > +++ b/drivers/hwmon/Kconfig
> > @@ -1360,6 +1360,15 @@ config SENSORS_MC13783_ADC
> > help
> > Support for the A/D converter on MC13783 PMIC.
> >
> > +config SENSORS_MAX197
> > + tristate "Maxim MAX197 and compatibles."
> > + help
> > + If you say yes here you get support for the Maxim MAX197,
> > + and MAX199 A/D converters.
> > +
> > + This driver can also be built as a module. If so, the
> > module
> > + will be called max197.
> > +
> > if ACPI
> >
> > comment "ACPI drivers"
> > diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> > index 8251ce8..dfb73d9 100644
> > --- a/drivers/hwmon/Makefile
> > +++ b/drivers/hwmon/Makefile
> > @@ -125,6 +125,7 @@ obj-$(CONFIG_SENSORS_W83L785TS) +=
> > w83l785ts.o obj-$(CONFIG_SENSORS_W83L786NG) += w83l786ng.o
> > obj-$(CONFIG_SENSORS_WM831X) += wm831x-hwmon.o
> > obj-$(CONFIG_SENSORS_WM8350) += wm8350-hwmon.o
> > +obj-$(CONFIG_SENSORS_MAX197) += max197.o
> >
> > obj-$(CONFIG_PMBUS) += pmbus/
> >
> > diff --git a/drivers/hwmon/max197.c b/drivers/hwmon/max197.c
> > new file mode 100644
> > index 0000000..985615c
> > --- /dev/null
> > +++ b/drivers/hwmon/max197.c
> > @@ -0,0 +1,403 @@
> > +/*
> > + * Maxim MAX197 A/D Converter Driver
> > + *
> > + * Copyright (c) 2012 Savoir-faire Linux Inc.
> > + * Vivien Didelot <[email protected]>
> > + *
> > + * 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.
> > + *
> > + * For further information, see the Documentation/hwmon/max197
> > file.
> > + */
> > +
> > +#include <linux/init.h>
> > +#include <linux/module.h>
> > +#include <linux/kernel.h>
> > +#include <linux/device.h>
> > +#include <linux/delay.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/err.h>
> > +#include <linux/sysfs.h>
> > +#include <linux/hwmon.h>
> > +#include <linux/hwmon-sysfs.h>
> > +#include <linux/mutex.h>
> > +#include <linux/slab.h>
> > +#include <linux/io.h>
> > +#include <linux/max197.h>
> > +
> > +#define MAX199_LIMIT 4000 /* 4V */
> > +#define MAX197_LIMIT 10000 /* 10V */
> > +
> > +#define MAX197_NUM_CH 8 /* 8 Analog Input Channels */
> > +
> > +/* Control byte format */
> > +#define MAX197_A0 0x01 /* Channel bit 0 */
> > +#define MAX197_A1 0x02 /* Channel bit 1 */
> > +#define MAX197_A2 0x04 /* Channel bit 2 */
> > +#define MAX197_BIP 0x08 /* Bipolarity */
> > +#define MAX197_RNG 0x10 /* Full range */
> > +#define MAX197_ACQMOD 0x20 /* Internal/External controlled
> > acquisition */ +#define MAX197_PD0 0x40 /* Clock/Power-Down
> > modes bit 1 */ +#define MAX197_PD1 0x80 /* Clock/Power-Down
> > modes bit 2 */ +
> > +#define MAX197_SCALE 12207 /* Scale coefficient for raw data */
> > +
> > +/**
> > + * struct max197_chip - device instance specific data
> > + * @pdata: Platform data.
> > + * @hwmon_dev: The hwmon device.
> > + * @lock: Read/Write mutex.
> > + * @limit: Max range value (10V for MAX197, 4V for
> > MAX199).
> > + * @scale: Need to scale.
> > + * @ctrl_bytes: Channels control byte.
> > + */
> > +struct max197_chip {
> > + struct max197_platform_data *pdata;
> > + struct device *hwmon_dev;
> > + struct mutex lock;
> > + int limit;
> > + bool scale;
> > + u8 ctrl_bytes[MAX197_NUM_CH];
> > +};
> > +
> > +static inline void max197_set_unipolarity(struct max197_chip
> > *chip, int channel) +{
> > + chip->ctrl_bytes[channel] &= ~MAX197_BIP;
> > +}
> > +
> > +static inline void max197_set_bipolarity(struct max197_chip *chip,
> > int channel) +{
> > + chip->ctrl_bytes[channel] |= MAX197_BIP;
> > +}
> > +
> > +static inline void max197_set_half_range(struct max197_chip *chip,
> > int channel) +{
> > + chip->ctrl_bytes[channel] &= ~MAX197_RNG;
> > +}
> > +
> > +static inline void max197_set_full_range(struct max197_chip *chip,
> > int channel) +{
> > + chip->ctrl_bytes[channel] |= MAX197_RNG;
> > +}
> > +
> > +static inline bool max197_is_bipolar(struct max197_chip *chip, int
> > channel) +{
> > + return !!(chip->ctrl_bytes[channel] & MAX197_BIP);
> > +}
> > +
> > +static inline bool max197_is_full_range(struct max197_chip *chip,
> > int channel) +{
> > + return !!(chip->ctrl_bytes[channel] & MAX197_RNG);
> > +}
> > +
> Interestingly enough, you don't need the !!() above. The C language
> defines that the result of an operation assigned to a bool is always
> converted to either 0 or 1. Not that it matters, really - I personally
> actually prefer you style.
Thanks for the tip, I'll get rid of it.
>
> > +/**
> > + * max197_show_range() - Display range on user output
> > + *
> > + * Function called on read access on in{0,1,2,3,4,5,6,7}_{min,max}
> > + */
> > +static ssize_t max197_show_range(struct device *dev,
> > + struct device_attribute *devattr,
> > char *buf) +{
> > + struct max197_chip *chip = dev_get_drvdata(dev);
> > + struct sensor_device_attribute_2 *attr =
> > to_sensor_dev_attr_2(devattr);
> > + int channel = attr->index;
> > + bool is_min = attr->nr;
> > + int range;
> > +
> > + if (mutex_lock_interruptible(&chip->lock))
> > + return -ERESTARTSYS;
> > +
> > + range = max197_is_full_range(chip, channel) ?
> > + chip->limit : chip->limit / 2;
> > + if (is_min) {
> > + if (max197_is_bipolar(chip, channel))
> > + range = -range;
> > + else
> > + range = 0;
> > + }
> > +
> > + mutex_unlock(&chip->lock);
> > +
> > + return sprintf(buf, "%d\n", range);
> > +}
> > +
> > +/**
> > + * max197_store_range() - Write range from user input
> > + *
> > + * Function called on write access on in{0,1,2,3,4,5,6,7}_{min,max}
> > + */
> > +static ssize_t max197_store_range(struct device *dev,
> > + struct device_attribute *devattr,
> > + const char *buf, size_t count)
> > +{
> > + struct max197_chip *chip = dev_get_drvdata(dev);
> > + struct sensor_device_attribute_2 *attr =
> > to_sensor_dev_attr_2(devattr);
> > + int channel = attr->index;
> > + bool is_min = attr->nr;
> > + long value;
> > + int half = chip->limit / 2;
> > + int full = chip->limit;
> > +
> > + if (kstrtol(buf, 10, &value))
> > + return -EINVAL;
> > +
> > + if (is_min) {
> > + if ((value != 0) && (value != -half) && (value !=
> > -full))
> > + return -EINVAL;
> > + } else {
> > + if ((value != half) && (value != full))
> > + return -EINVAL;
> > + }
> > +
> Technically ok, except for the unnecessary ( ). Would be great if you
> could remove those.
>
> Since the actual limits are chip dependent, and not easily to figure
> out for the ordinary user, it would be nice to either accept any
> number and adjust it to one of the accepted values, or to explicitly
> spell out the accepted the per-chip accepted values in the
> documentation (and not just say "depending on the chip"). I'll leave
> it up to you to decide which way to go.
>
> Not too difficult - if you change the code, something like
>
> if (is_min) {
> if (value <= -full)
> value = -full;
> else if (value < 0)
> value = -half;
> else value = 0;
> } else {
> if (value >= full)
> value = full;
> else
> value = half;
> }
It sounds better to accept any value and adjust it depending on the chip.
>
> would be good enough.
>
> Thanks,
> Guenter
>
>
BTW, about the TS-5500 ADC part, is a platform ts5500_adc.c file the
better solution, or should the device be declared in the ts5500.c
platform code?
Thanks!
--
Vivien Didelot
Savoir-faire Linux Inc.
Tel: (514) 276-5468 #149
Le Mon, 6 Feb 2012 15:37:42 +0000,
Mark Brown <[email protected]> a ?crit :
> On Thu, Feb 02, 2012 at 11:33:56AM -0800, Guenter Roeck wrote:
> > On Wed, 2012-02-01 at 16:30 -0500, Alan Cox wrote:
>
> > > > My first RFC patches set has every driver separated. As they are
> > > > really specific to the platform, people seem to agree with
> > > > grouping them, mainly because they won't be shared. I changed
> > > > that in the following patches sets, and X86 maintainers seemed
> > > > to be ok with that.
>
> > It looks like things are going back and forth a bit. If I were
> > Vivien, I would be a bit frustrated by now and be close to giving
> > up (Vivien, I really commend you for your patience).
>
> OTOH I just looked back and saw that some of the review comments I
> just made were also made against the first version of this driver I
> noticed (v2) but appear to have been ignored, the request tracking
> stands out.
>
> > Is there a written guideline or policy people can look and point to
> > if/when something like this comes up ? Otherwise we may have the
> > LED/GPIO/xxx maintainers point one way, the X86 maintainers point
> > the other way, and thus may have reached a complete deadlock.
>
> I'm not sure I'm seeing much conflict here TBH, looking at the above
> and the history I have to hand I'd say it's reading like the x86
> maintainers aren't fussed either way and the people doing kernel wide
> work on things like this prefer getting stuff integrated into the
> frameworks.
Thanks for the comments. I'll then move the GPIO driver back to
drivers/gpio and fix what Mark pointed out.
I Cc Richard Purdie, to have his maintainer view on the platform LED
declaration. Is it ok in the ts5500_led.c platform file, or should it
better be moved into drivers/leds/leds-ts5500.c, or maybe directly
declared in the main ts5500.c platform code?
Thanks,
Vivien.
--
Vivien Didelot
Savoir-faire Linux Inc.
Tel: (514) 276-5468 #149
Hi Vivien,
On Mon, 2012-02-06 at 15:15 -0500, Vivien Didelot wrote:
[ ... ]
>
> BTW, about the TS-5500 ADC part, is a platform ts5500_adc.c file the
> better solution, or should the device be declared in the ts5500.c
> platform code?
>
I would suggest to declare it in the ts5500.c platform code. That seems
to be the common approach as far as I can see.
platform_add_devices() works pretty well for this. It saves you from
having to call platform_device_register() for each device separately.
Obviously that only works if all devices are declared in a single file.
Thanks,
Guenter
On Mon, Feb 06, 2012 at 03:23:07PM -0500, Vivien Didelot wrote:
> I Cc Richard Purdie, to have his maintainer view on the platform LED
> declaration. Is it ok in the ts5500_led.c platform file, or should it
> better be moved into drivers/leds/leds-ts5500.c, or maybe directly
> declared in the main ts5500.c platform code?
It would normally be done in arch/ code. I'm not sure if Richard will
get back to you, he's been inactive lately (working on Yocto stuff
instead).
Hi,
On Mon, 6 Feb 2012 12:46:04 -0800,
Guenter Roeck <[email protected]> wrote:
> On Mon, 2012-02-06 at 15:15 -0500, Vivien Didelot wrote:
> [ ... ]
> >
> > BTW, about the TS-5500 ADC part, is a platform ts5500_adc.c file the
> > better solution, or should the device be declared in the ts5500.c
> > platform code?
> >
> I would suggest to declare it in the ts5500.c platform code. That
> seems to be the common approach as far as I can see.
>
> platform_add_devices() works pretty well for this. It saves you from
> having to call platform_device_register() for each device separately.
> Obviously that only works if all devices are declared in a single
> file.
As the LED is registered using the leds_class, I think
platform_add_devices() couldn't be used here.
Lots of platform codes don't check the returned
value of platform_add_devices(). Should we care about a LED or ADC
registration failure (is the following snippet OK?)?
static int __init ts5500_init(void)
{
[...]
pdev = platform_device_register_simple("ts5500", -1, NULL, 0);
if (IS_ERR(pdev)) {
ret = PTR_ERR(pdev);
goto release_mem;
}
platform_set_drvdata(pdev, ts5500);
ret = sysfs_create_group(&pdev->dev.kobj,
&ts5500_attr_group);
if (ret)
goto release_pdev;
led_classdev_register(&pdev->dev, &ts5500_led_cdev);
if (ts5500->adc) {
ts5500_adc_pdev.dev.parent = &pdev->dev;
platform_device_register(&ts5500_adc_pdev);
}
return 0;
release_pdev:
platform_device_unregister(pdev);
release_mem:
kfree(ts5500);
return ret;
}
device_initcall(ts5500_init);
Thanks,
--
Vivien Didelot
Savoir-faire Linux Inc.
Tel: (514) 276-5468 #149
Vivien,
On Fri, Feb 10, 2012 at 11:07:55AM -0500, Vivien Didelot wrote:
> Hi,
>
> On Mon, 6 Feb 2012 12:46:04 -0800,
> Guenter Roeck <[email protected]> wrote:
>
> > On Mon, 2012-02-06 at 15:15 -0500, Vivien Didelot wrote:
> > [ ... ]
> > >
> > > BTW, about the TS-5500 ADC part, is a platform ts5500_adc.c file the
> > > better solution, or should the device be declared in the ts5500.c
> > > platform code?
> > >
> > I would suggest to declare it in the ts5500.c platform code. That
> > seems to be the common approach as far as I can see.
> >
> > platform_add_devices() works pretty well for this. It saves you from
> > having to call platform_device_register() for each device separately.
> > Obviously that only works if all devices are declared in a single
> > file.
>
> As the LED is registered using the leds_class, I think
> platform_add_devices() couldn't be used here.
>
> Lots of platform codes don't check the returned
> value of platform_add_devices(). Should we care about a LED or ADC
> registration failure (is the following snippet OK?)?
>
> static int __init ts5500_init(void)
> {
> [...]
> pdev = platform_device_register_simple("ts5500", -1, NULL, 0);
> if (IS_ERR(pdev)) {
> ret = PTR_ERR(pdev);
> goto release_mem;
> }
> platform_set_drvdata(pdev, ts5500);
>
> ret = sysfs_create_group(&pdev->dev.kobj,
> &ts5500_attr_group);
> if (ret)
> goto release_pdev;
>
> led_classdev_register(&pdev->dev, &ts5500_led_cdev);
> if (ts5500->adc) {
> ts5500_adc_pdev.dev.parent = &pdev->dev;
> platform_device_register(&ts5500_adc_pdev);
> }
>
I didn't look at other code, but personally I try to be consistent.
Why do you check the return value from platform_device_register_simple() above,
but not the return code from platform_device_register() ?
That does not seem to be very consistent to me.
Thanks,
Guenter
> return 0;
>
> release_pdev:
> platform_device_unregister(pdev);
> release_mem:
> kfree(ts5500);
>
> return ret;
> }
> device_initcall(ts5500_init);
>
>
> Thanks,
>
>
> --
> Vivien Didelot
> Savoir-faire Linux Inc.
> Tel: (514) 276-5468 #149
Le Fri, 10 Feb 2012 08:15:38 -0800,
Guenter Roeck <[email protected]> a ?crit :
> Vivien,
>
> On Fri, Feb 10, 2012 at 11:07:55AM -0500, Vivien Didelot wrote:
> > Hi,
> >
> > On Mon, 6 Feb 2012 12:46:04 -0800,
> > Guenter Roeck <[email protected]> wrote:
> >
> > > On Mon, 2012-02-06 at 15:15 -0500, Vivien Didelot wrote:
> > > [ ... ]
> > > >
> > > > BTW, about the TS-5500 ADC part, is a platform ts5500_adc.c
> > > > file the better solution, or should the device be declared in
> > > > the ts5500.c platform code?
> > > >
> > > I would suggest to declare it in the ts5500.c platform code. That
> > > seems to be the common approach as far as I can see.
> > >
> > > platform_add_devices() works pretty well for this. It saves you
> > > from having to call platform_device_register() for each device
> > > separately. Obviously that only works if all devices are declared
> > > in a single file.
> >
> > As the LED is registered using the leds_class, I think
> > platform_add_devices() couldn't be used here.
> >
> > Lots of platform codes don't check the returned
> > value of platform_add_devices(). Should we care about a LED or ADC
> > registration failure (is the following snippet OK?)?
> >
> > static int __init ts5500_init(void)
> > {
> > [...]
> > pdev = platform_device_register_simple("ts5500", -1, NULL,
> > 0); if (IS_ERR(pdev)) {
> > ret = PTR_ERR(pdev);
> > goto release_mem;
> > }
> > platform_set_drvdata(pdev, ts5500);
> >
> > ret = sysfs_create_group(&pdev->dev.kobj,
> > &ts5500_attr_group);
> > if (ret)
> > goto release_pdev;
> >
> > led_classdev_register(&pdev->dev, &ts5500_led_cdev);
> > if (ts5500->adc) {
> > ts5500_adc_pdev.dev.parent = &pdev->dev;
> > platform_device_register(&ts5500_adc_pdev);
> > }
> >
> I didn't look at other code, but personally I try to be consistent.
> Why do you check the return value from
> platform_device_register_simple() above, but not the return code from
> platform_device_register() ? That does not seem to be very consistent
> to me.
I check the platform_device_register_simple() returned value because it
is the platform itself, while the others are on-board devices. I
thought that it is not a big deal if their registrations failed but the
platform registration succeeded. Maybe I'm wrong and I should check
everything.
Thanks,
Vivien.
>
> Thanks,
> Guenter
>
> > return 0;
> >
> > release_pdev:
> > platform_device_unregister(pdev);
> > release_mem:
> > kfree(ts5500);
> >
> > return ret;
> > }
> > device_initcall(ts5500_init);
> >
> >
> > Thanks,
> >
> >
> > --
> > Vivien Didelot
> > Savoir-faire Linux Inc.
> > Tel: (514) 276-5468 #149
--
Vivien Didelot
Savoir-faire Linux Inc.
Tel: (514) 276-5468 #149
On Fri, Feb 10, 2012 at 01:14:06PM -0500, Vivien Didelot wrote:
> Le Fri, 10 Feb 2012 08:15:38 -0800,
> Guenter Roeck <[email protected]> a ?crit :
>
> > Vivien,
> >
> > On Fri, Feb 10, 2012 at 11:07:55AM -0500, Vivien Didelot wrote:
> > > Hi,
> > >
> > > On Mon, 6 Feb 2012 12:46:04 -0800,
> > > Guenter Roeck <[email protected]> wrote:
> > >
> > > > On Mon, 2012-02-06 at 15:15 -0500, Vivien Didelot wrote:
> > > > [ ... ]
> > > > >
> > > > > BTW, about the TS-5500 ADC part, is a platform ts5500_adc.c
> > > > > file the better solution, or should the device be declared in
> > > > > the ts5500.c platform code?
> > > > >
> > > > I would suggest to declare it in the ts5500.c platform code. That
> > > > seems to be the common approach as far as I can see.
> > > >
> > > > platform_add_devices() works pretty well for this. It saves you
> > > > from having to call platform_device_register() for each device
> > > > separately. Obviously that only works if all devices are declared
> > > > in a single file.
> > >
> > > As the LED is registered using the leds_class, I think
> > > platform_add_devices() couldn't be used here.
> > >
> > > Lots of platform codes don't check the returned
> > > value of platform_add_devices(). Should we care about a LED or ADC
> > > registration failure (is the following snippet OK?)?
> > >
> > > static int __init ts5500_init(void)
> > > {
> > > [...]
> > > pdev = platform_device_register_simple("ts5500", -1, NULL,
> > > 0); if (IS_ERR(pdev)) {
> > > ret = PTR_ERR(pdev);
> > > goto release_mem;
> > > }
> > > platform_set_drvdata(pdev, ts5500);
> > >
> > > ret = sysfs_create_group(&pdev->dev.kobj,
> > > &ts5500_attr_group);
> > > if (ret)
> > > goto release_pdev;
> > >
> > > led_classdev_register(&pdev->dev, &ts5500_led_cdev);
> > > if (ts5500->adc) {
> > > ts5500_adc_pdev.dev.parent = &pdev->dev;
> > > platform_device_register(&ts5500_adc_pdev);
> > > }
> > >
> > I didn't look at other code, but personally I try to be consistent.
> > Why do you check the return value from
> > platform_device_register_simple() above, but not the return code from
> > platform_device_register() ? That does not seem to be very consistent
> > to me.
>
> I check the platform_device_register_simple() returned value because it
> is the platform itself, while the others are on-board devices. I
> thought that it is not a big deal if their registrations failed but the
> platform registration succeeded. Maybe I'm wrong and I should check
> everything.
>
Hmm .. seems to make sense. Ok with me. Only question is if you would want
to have it fail silently or issue a log message (possibly debug) to report
the failure.
Guenter
On Mon, 20 Feb 2012 10:27:02 -0800,
Guenter Roeck <[email protected]> wrote:
> > I check the platform_device_register_simple() returned value
> > because it is the platform itself, while the others are on-board
> > devices. I thought that it is not a big deal if their registrations
> > failed but the platform registration succeeded. Maybe I'm wrong and
> > I should check everything.
> >
> Hmm .. seems to make sense. Ok with me. Only question is if you would
> want to have it fail silently or issue a log message (possibly debug)
> to report the failure.
Ok, I'll go for something like the following:
if (led_classdev_register(&pdev->dev, &ts5500_led_cdev))
dev_warn(ts5500_led_cdev.dev, "failed to register the LED\n");
if (ts5500->adc) {
ts5500_adc_pdev.dev.parent = &pdev->dev;
if (platform_device_register(&ts5500_adc_pdev))
dev_warn(&ts5500_adc_pdev.dev,
"failed to register the A/D converter\n");
}
Thanks,
--
Vivien Didelot
Savoir-faire Linux Inc.
Tel: (514) 276-5468 #149