summaryrefslogtreecommitdiff
path: root/drivers/misc
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/misc')
-rw-r--r--drivers/misc/Kconfig43
-rw-r--r--drivers/misc/Makefile5
-rw-r--r--drivers/misc/fsl_dcm.c770
-rw-r--r--drivers/misc/fsl_dcm.h129
4 files changed, 938 insertions, 9 deletions
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 8dacd4c..4dc11ca 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -56,7 +56,7 @@ config ATMEL_PWM
depends on HAVE_CLK
help
This option enables device driver support for the PWM channels
- on certain Atmel processors. Pulse Width Modulation is used for
+ on certain Atmel processors. Pulse Width Modulation is used for
purposes including software controlled power-efficient backlights
on LCD displays, motor control, and waveform generation.
@@ -219,6 +219,35 @@ config ENCLOSURE_SERVICES
driver (SCSI/ATA) which supports enclosures
or a SCSI enclosure device (SES) to use these services.
+config FSL_DCM
+ tristate "Freescale Data Collection Manager (DCM) driver"
+ depends on FSL_SOC
+ help
+ Inside the FPGA of some Freescale QorIQ (PowerPC) reference boards
+ is a microcontroller called the General Purpose Processor (GSMA).
+ Running on the GSMA is the Data Collection Manager (DCM), which is
+ used to periodically read and tally voltage, current, and temperature
+ measurements from the on-board sensors. You can use this feature to
+ measure power consumption while running tests, without having the
+ host CPU perform those measurements.
+
+config FSL_USDPAA
+ bool "Freescale USDPAA process driver"
+ depends on FSL_DPA
+ default y
+ help
+ This driver provides user-space access to kernel-managed
+ resource interfaces for USDPAA applications, on the assumption
+ that each process will open this device once. Specifically, this
+ device exposes functionality that would be awkward if exposed
+ via the portal devices - ie. this device exposes functionality
+ that is inherently process-wide rather than portal-specific.
+ This device is necessary for obtaining access to DMA memory and
+ for allocation of Qman and Bman resources. In short, if you wish
+ to use USDPAA applications, you need this.
+
+ If unsure, say Y.
+
config SGI_XP
tristate "Support communication between SGI SSIs"
depends on NET
@@ -354,14 +383,14 @@ config SENSORS_BH1780
will be called bh1780gli.
config SENSORS_BH1770
- tristate "BH1770GLC / SFH7770 combined ALS - Proximity sensor"
- depends on I2C
- ---help---
- Say Y here if you want to build a driver for BH1770GLC (ROHM) or
+ tristate "BH1770GLC / SFH7770 combined ALS - Proximity sensor"
+ depends on I2C
+ ---help---
+ Say Y here if you want to build a driver for BH1770GLC (ROHM) or
SFH7770 (Osram) combined ambient light and proximity sensor chip.
- To compile this driver as a module, choose M here: the
- module will be called bh1770glc. If unsure, say N here.
+ To compile this driver as a module, choose M here: the
+ module will be called bh1770glc. If unsure, say N here.
config SENSORS_APDS990X
tristate "APDS990X combined als and proximity sensors"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index c235d5b..20a9129 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -16,8 +16,8 @@ obj-$(CONFIG_BMP085_SPI) += bmp085-spi.o
obj-$(CONFIG_DUMMY_IRQ) += dummy-irq.o
obj-$(CONFIG_ICS932S401) += ics932s401.o
obj-$(CONFIG_LKDTM) += lkdtm.o
-obj-$(CONFIG_TIFM_CORE) += tifm_core.o
-obj-$(CONFIG_TIFM_7XX1) += tifm_7xx1.o
+obj-$(CONFIG_TIFM_CORE) += tifm_core.o
+obj-$(CONFIG_TIFM_7XX1) += tifm_7xx1.o
obj-$(CONFIG_PHANTOM) += phantom.o
obj-$(CONFIG_SENSORS_BH1780) += bh1780gli.o
obj-$(CONFIG_SENSORS_BH1770) += bh1770glc.o
@@ -53,3 +53,4 @@ obj-$(CONFIG_INTEL_MEI) += mei/
obj-$(CONFIG_VMWARE_VMCI) += vmw_vmci/
obj-$(CONFIG_LATTICE_ECP3_CONFIG) += lattice-ecp3-config.o
obj-$(CONFIG_SRAM) += sram.o
+obj-$(CONFIG_FSL_DCM) += fsl_dcm.o
diff --git a/drivers/misc/fsl_dcm.c b/drivers/misc/fsl_dcm.c
new file mode 100644
index 0000000..1af2800
--- /dev/null
+++ b/drivers/misc/fsl_dcm.c
@@ -0,0 +1,770 @@
+/*
+ * Freescale Data Collection Manager (DCM) device driver
+ *
+ * Copyright (C) 2011 Freescale Semiconductor, Inc.
+ * Author: Timur Tabi <timur@freescale.com>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ *
+ * Inside the FPGA of some Freescale QorIQ (PowerPC) reference boards is a
+ * microprocessor called the General Purpose Processor (GSMA). Running on
+ * the GSMA is the Data Collection Manager (DCM), which is used to
+ * periodically read and tally voltage, current, and temperature measurements
+ * from the on-board sensors. You can use this feature to measure power
+ * consumption while running tests, without having the host CPU perform those
+ * measurements.
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include "fsl_dcm.h"
+
+/*
+ * Converts a 16-bit VOUT value from the Zilker ZL6100 into a voltage value,
+ * in millivolts.
+ */
+static unsigned int voltage_from_zl6100(u16 vout)
+{
+ return (1000UL * vout) / (1 << 13);
+}
+
+/* unit is mv */
+static unsigned int voltage_from_ina220(u16 vout)
+{
+ return (vout >> 3) * 4;
+}
+
+/*
+ * Converts a 16-bit IOUT from the Texas Instruments INA220 chip into a
+ * current value, in milliamps. 'shunt' is a board-specific shunt.
+ */
+static unsigned int current_from_ina220(u16 sv, unsigned int shunt)
+{
+ unsigned long c;
+
+ /*
+ * Current = ShuntVoltage * CalibrationRegister / 4096
+ * = ShuntVoltage * 40,960,000 / shunt(uOhms) / 4096
+ * = ShuntVoltage * 10000 / shunt
+ */
+ c = sv * 10000;
+ c /= shunt;
+
+ return c;
+}
+
+/*
+ * Converts a 16-bit TOUT value from the sensor device into a temperature
+ * value, in degrees Celsius.
+ */
+static unsigned int temp_from_u16(u16 tout)
+{
+ return tout;
+}
+
+/*
+ * Write a byte to an address in SRAM
+ */
+static void write_sram(struct fsl_dcm_data *dcm, u8 offset, u8 v)
+{
+ out_8(dcm->addr, offset);
+ out_8(dcm->data, v);
+}
+
+/*
+ * Read a byte from an address in SRAM
+ */
+static u8 read_sram(struct fsl_dcm_data *dcm, u8 offset)
+{
+ out_8(dcm->addr, offset);
+
+ return in_8(dcm->data);
+}
+
+/*
+ * True TRUE if we can read/write SRAM, FALSE otherwise.
+ *
+ * If the SRAM is unavailable, it's probably because the DCM is busy with it.
+ */
+static int is_sram_available(struct fsl_dcm_data *dcm)
+{
+ u8 ack, cmd;
+
+ cmd = in_8(dcm->ocmd);
+ ack = in_8(dcm->mack);
+
+ if ((cmd & PX_OCMD_MSG) || (ack & PX_OACK_ACK)) {
+ dev_dbg(dcm->dev, "dcm is not ready (cmd=%02X mack=%02X)\n",
+ cmd, ack);
+ return 0;
+ }
+
+ return 1;
+}
+
+/*
+ * Loads and program into SRAM, then tells the DCM to run it, and then waits
+ * for it to finish.
+ */
+static int run_program(struct fsl_dcm_data *dcm, u8 addr,
+ unsigned int len, ...)
+{
+ u8 v, n;
+ va_list args;
+
+ if (addr + len > 0xff) {
+ dev_err(dcm->dev, "address/length of %u/%u is out of bounds\n",
+ addr, len);
+ return -EBUSY;
+ }
+
+ /* load the program into SRAM */
+ va_start(args, len);
+ for (n = addr; n < addr + len; n++) {
+ v = va_arg(args, int);
+ write_sram(dcm, n, v);
+ }
+ va_end(args);
+
+ /* start the DCM */
+ out_8(dcm->omsg, addr);
+ out_8(dcm->ocmd, PX_OCMD_MSG);
+
+ /* wait for ack or error */
+ v = spin_event_timeout(in_8(dcm->mack) & (PX_OACK_ERR | PX_OACK_ACK),
+ 50000, 1000);
+ if ((!v) || (v & PX_OACK_ERR)) {
+ dev_err(dcm->dev, "timeout or error waiting for start ack\n");
+ return -EBUSY;
+ }
+
+ /* 4. allow the host to read SRAM */
+ out_8(dcm->ocmd, 0);
+
+ /* 5. wait for DCM to stop (ack == 0) or error (err == 1) */
+ spin_event_timeout(
+ ((v = in_8(dcm->mack)) & (PX_OACK_ERR | PX_OACK_ACK))
+ != PX_OACK_ACK, 50000, 1000);
+
+ /* 6. check for error or timeout */
+ if (v & (PX_OACK_ERR | PX_OACK_ACK)) {
+ dev_err(dcm->dev, "timeout or error waiting for stop ack\n");
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+#define TRATE0 241122 /* t-rate if prescale==0, in millihertz */
+#define TRATE1 38579330 /* t-rate if prescale==1, in millihertz */
+/*
+ * Empirical tests show that any frequency higher than 48Hz is unreliable.
+ */
+static int set_dcm_frequency(struct fsl_dcm_data *dcm,
+ unsigned long frequency)
+{
+ unsigned long timer;
+
+ if (!is_sram_available(dcm)) {
+ dev_err(dcm->dev, "dcm is busy\n");
+ return -EBUSY;
+ }
+
+ /* Restrict the frequency to a supported range. */
+ frequency = clamp_t(unsigned long, frequency, 1, MAX_FREQUENCY);
+
+ /* We only support prescale == 0 */
+ timer = TRATE0 / frequency;
+ dcm->timer = ((timer / 1000) - 1) & 0xff;
+
+ return run_program(dcm, 0, 6, OM_TIMER, 0, dcm->timer, 0, 0, OM_END);
+}
+
+static int copy_from_sram(struct fsl_dcm_data *dcm, unsigned int addr,
+ void *buf, unsigned int len)
+{
+ u8 *p = buf;
+ unsigned int i;
+
+ if (addr + len > 0xff) {
+ dev_err(dcm->dev, "address/length of %u/%u is out of bounds\n",
+ addr, len);
+ return -EBUSY;
+ }
+
+ for (i = 0; i < len; i++)
+ p[i] = read_sram(dcm, addr + i);
+
+ return 0;
+}
+
+/*
+ * Tells the DCM which channels to collect data on.
+ */
+static int select_dcm_channels(struct fsl_dcm_data *dcm, u16 mask)
+{
+ if (!is_sram_available(dcm)) {
+ dev_err(dcm->dev, "dcm is busy\n");
+ return -EBUSY;
+ }
+
+ return run_program(dcm, 0, 4, OM_ENABLE,
+ ((mask >> 8) & 0xFF), mask & 0xFF, OM_END);
+}
+
+/*
+ * Tells the DCM to start data collection. If the DCM is currently running,
+ * it is restarted. Any currently collected data is cleared.
+ */
+int start_data_collection(struct fsl_dcm_data *dcm)
+{
+ if (!is_sram_available(dcm)) {
+ dev_err(dcm->dev, "dcm is busy\n");
+ return -EBUSY;
+ }
+
+ if (dcm->running)
+ dev_dbg(dcm->dev, "restarting\n");
+
+ dcm->running = true;
+
+ return run_program(dcm, 0, 4, OM_STOP, OM_SCLR, OM_START, OM_END);
+}
+
+/* Tells the DCM to stop data collection. */
+int stop_data_collection(struct fsl_dcm_data *dcm)
+{
+ if (!dcm->running) {
+ dev_dbg(dcm->dev, "dcm is already stopped\n");
+ return 0;
+ }
+
+ if (!is_sram_available(dcm)) {
+ dev_err(dcm->dev, "dcm is busy\n");
+ return -EBUSY;
+ }
+
+ if (run_program(dcm, 0, 2, OM_STOP, OM_END)) {
+ dev_err(dcm->dev, "could not stop monitoring\n");
+ return -EBUSY;
+ }
+
+ dcm->running = false;
+
+ return 0;
+}
+
+static ssize_t dcm_get_info(struct device *dev)
+{
+ struct fsl_dcm_data *dcm = dev_get_drvdata(dev);
+
+ if (!is_sram_available(dcm)) {
+ dev_err(dev, "dcm is busy\n");
+ return -EBUSY;
+ }
+
+ if (run_program(dcm, 0, 3, OM_INFO, DATA_ADDR, OM_END)) {
+ dev_err(dev, "could not run 'info' program\n");
+ return -EBUSY;
+ }
+
+ if (copy_from_sram(dcm, DATA_ADDR, &dcm->board.info,
+ sizeof(struct om_info))) {
+ dev_err(dev, "could not copy 'info' data\n");
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+ssize_t fsl_dcm_sysfs_control_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct fsl_dcm_data *dcm = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%s\n", dcm->running ? "running" : "stoppped");
+}
+
+ssize_t fsl_dcm_sysfs_control_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct fsl_dcm_data *dcm = dev_get_drvdata(dev);
+ unsigned long pm_cmd;
+ int ret;
+
+ ret = kstrtoul(buf, 10, &pm_cmd);
+ if (ret)
+ return ret;
+
+ switch (pm_cmd) {
+ case SYSFS_DCM_CMD_START:
+ ret = start_data_collection(dcm);
+ if (ret)
+ dev_err(dev, "failed to start power monitoring.\n");
+ break;
+ case SYSFS_DCM_CMD_STOP:
+ ret = stop_data_collection(dcm);
+ if (ret)
+ dev_err(dev, "failed to stop power monitoring\n");
+ break;
+ default:
+ return -EIO;
+ }
+
+ return count;
+}
+
+static int get_crecords(struct fsl_dcm_data *dcm)
+{
+ int len;
+ u8 addr1, addr2;
+ struct crecord *crec = dcm->board.crec;
+ struct om_info *info = &dcm->board.info;
+
+ /* get CRECORDs from ocm sram */
+ if (!is_sram_available(dcm)) {
+ dev_err(dcm->dev, "dcm is busy\n");
+ return -EBUSY;
+ }
+
+ len = sizeof(struct crecord) * info->count;
+ addr1 = (info->addr >> 8) & 0xff;
+ addr2 = info->addr & 0xff;
+
+ if (run_program(dcm, 0, 6, OM_GETMEMX, addr1,
+ addr2, len, DATA_ADDR, OM_END)) {
+ dev_err(dcm->dev, "could not stop monitoring\n");
+ return -EBUSY;
+ }
+
+ if (copy_from_sram(dcm, DATA_ADDR, crec, len)) {
+ dev_err(dcm->dev, "could not copy sensor data\n");
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+/* Calculate the average, even if 'count' is zero */
+#define AVG(sum, count) ((sum) / ((count) ?: 1))
+#define MAX_RECORD 9
+static ssize_t fsl_dcm_sysfs_result(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct fsl_dcm_data *dcm = dev_get_drvdata(dev);
+ struct record *rec = dcm->board.rec;
+ struct crecord *crec = dcm->board.crec;
+ ssize_t len;
+ int i, avg, num, val;
+
+ if (get_crecords(dcm))
+ return sprintf(buf, "Get record error\n");
+
+ len = sprintf(buf,
+ "Name Average\n"
+ "==================== ================\n");
+
+ for (i = 0; i < dcm->board.info.count; i++) {
+ num = (crec->num1 << 8) | (crec->num2);
+ val = AVG(crec->accum, num);
+
+ if (crec->ctl & CRCTL_GET_V) {
+ avg = voltage_from_zl6100(val);
+ } else if (crec->ctl & CRCTL_GET_T) {
+ avg = temp_from_u16(val);
+ } else if (crec->ctl & CRCTL_GET_V2) {
+ avg = voltage_from_ina220(val);
+ } else if (crec->ctl & CRCTL_GET_I2) {
+ avg = current_from_ina220(val, dcm->board.shunt);
+ } else {
+ dev_err(dev, "Unknown record\n");
+ return -EBUSY;
+ }
+
+ len += sprintf(buf + len,
+ "%-8s %-6d %s\n",
+ rec->name, avg, rec->unit);
+ crec++;
+ rec++;
+ }
+
+ return len;
+}
+
+ssize_t fsl_dcm_sysfs_info(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct fsl_dcm_data *dcm = dev_get_drvdata(dev);
+ struct om_info *info = &dcm->board.info;
+ ssize_t len;
+
+ len = sprintf(buf, "DCM Version: 0x%02x\n", info->ver);
+ len += sprintf(buf + len, "Prescale: %u\n", info->prescale);
+ len += sprintf(buf + len, "Timer: %u\n", info->timer);
+ len += sprintf(buf + len, "Number of CRECORDs: %u\n", info->count);
+ len += sprintf(buf + len, "CRECORD Address: 0x%04x\n", info->addr);
+
+ return len;
+}
+
+ssize_t fsl_dcm_sysfs_frequency_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct fsl_dcm_data *dcm = dev_get_drvdata(dev);
+ unsigned long frequency;
+
+ frequency = TRATE0 / (dcm->timer + 1);
+
+ return sprintf(buf, "%lu Hz\n", frequency / 1000);
+}
+
+ssize_t fsl_dcm_sysfs_frequency_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct fsl_dcm_data *dcm = dev_get_drvdata(dev);
+ unsigned long frequency;
+ int ret;
+
+ ret = kstrtoul(buf, 10, &frequency);
+ if (ret)
+ return ret;
+
+ set_dcm_frequency(dcm, frequency);
+
+ return count;
+}
+
+static DEVICE_ATTR(control, 0666, fsl_dcm_sysfs_control_show,
+ fsl_dcm_sysfs_control_store);
+static DEVICE_ATTR(result, 0444, fsl_dcm_sysfs_result, NULL);
+static DEVICE_ATTR(info, 0444, fsl_dcm_sysfs_info, NULL);
+static DEVICE_ATTR(frequency, 0666, fsl_dcm_sysfs_frequency_show,
+ fsl_dcm_sysfs_frequency_store);
+
+static const struct attribute_group fsl_dcm_attr_group = {
+ .attrs = (struct attribute * []) {
+ &dev_attr_control.attr,
+ &dev_attr_result.attr,
+ &dev_attr_info.attr,
+ &dev_attr_frequency.attr,
+ NULL,
+ },
+};
+
+static int board_data_init(struct fsl_dcm_data *dcm)
+{
+ int i, len;
+ u8 addr1, addr2;
+ struct record *rec = dcm->board.rec;
+ struct crecord *crec = dcm->board.crec;
+ struct om_info *info = &dcm->board.info;
+ struct om_xinfo xinfo;
+ char name[MAX_NAME_LEN + 1];
+
+ /* 1. get CRECORD array from ocm */
+ if (get_crecords(dcm))
+ return -EBUSY;
+
+ /* 2. get xinfo located in CRECORD.xinfo_addr */
+ for (i = 0; i < info->count; i++) {
+ if (!is_sram_available(dcm)) {
+ dev_err(dcm->dev, "dcm is busy\n");
+ return -EBUSY;
+ }
+
+ addr1 = (crec->xinfo_addr >> 8) & 0xff;
+ addr2 = (crec->xinfo_addr) & 0xff;
+ len = sizeof(struct om_xinfo);
+
+ if (run_program(dcm, 0, 6, OM_GETMEMX, addr1,
+ addr2, len, DATA_ADDR, OM_END)) {
+ dev_err(dcm->dev, "get xinfo error\n");
+ return -EBUSY;
+ }
+
+ if (copy_from_sram(dcm, DATA_ADDR, &xinfo, len)) {
+ dev_err(dcm->dev, "could not copy xinfo data\n");
+ return -EBUSY;
+ }
+
+ /* 3. get record name in struct xinfo */
+ if (!is_sram_available(dcm)) {
+ dev_err(dcm->dev, "dcm is busy\n");
+ return -EBUSY;
+ }
+
+ addr1 = (xinfo.name_addr >> 8) & 0xff;
+ addr2 = (xinfo.name_addr) & 0xff;
+ len = MAX_NAME_LEN;
+
+ if (run_program(dcm, 0, 6, OM_GETMEMX, addr1,
+ addr2, len, DATA_ADDR, OM_END)) {
+ dev_err(dcm->dev, "\n");
+ return -EBUSY;
+ }
+
+ if (copy_from_sram(dcm, DATA_ADDR, name, len)) {
+ dev_err(dcm->dev, "could not copy record name data\n");
+ return -EBUSY;
+ }
+
+ name[MAX_NAME_LEN] = 0;
+ rec->name = kstrdup(name, GFP_KERNEL);
+
+ /* assign the unit according record type */
+ if ((crec->ctl & CRCTL_GET_V) || (crec->ctl & CRCTL_GET_V2)) {
+ rec->unit = "mV";
+ } else if (crec->ctl & CRCTL_GET_T) {
+ rec->unit = "C ";
+ } else if (crec->ctl & CRCTL_GET_I2) {
+ rec->unit = "mA";
+ } else {
+ dev_err(dcm->dev, "Unknown record\n");
+ return -EBUSY;
+ }
+
+ /* deal with next record */
+ rec++;
+ crec++;
+ }
+
+ return 0;
+}
+
+/* .data is shunt value(in uOhms) of ina220 */
+static struct of_device_id fsl_dcm_ids[] = {
+ { .compatible = "fsl,p1022ds-fpga", .data = 0},
+ { .compatible = "fsl,p5020ds-fpga", .data = (void *)2106},
+ { .compatible = "fsl,tetra-fpga", .data = (void *)1000},
+ {}
+};
+
+static int fsl_dcm_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct fsl_dcm_data *dcm;
+ const struct of_device_id *match;
+ int ret;
+ u8 ver;
+
+ dcm = kzalloc(sizeof(*dcm), GFP_KERNEL);
+ if (!dcm)
+ return -ENOMEM;
+
+ dcm->base = of_iomap(np, 0);
+ if (!dcm->base) {
+ dev_err(&pdev->dev, "could not map fpga node\n");
+ ret = -ENOMEM;
+ goto err_kzalloc;
+ }
+
+ /*
+ * Get GMSA version
+ *
+ * write 0x1F to GDC register then read GDD register
+ * 0x00: v1 -> pixis
+ * 0x01: v2 -> qixis
+ */
+ out_8(dcm->base + 0x16, 0x1F);
+ ver = in_8(dcm->base + 0x17);
+ if (ver == 0x0) {
+ dcm->addr = dcm->base + 0x0a;
+ dcm->data = dcm->base + 0x0d;
+ } else if (ver == 0x01) {
+ dcm->addr = dcm->base + 0x12;
+ dcm->data = dcm->base + 0x13;
+ }
+ dcm->ocmd = dcm->base + 0x14;
+ dcm->omsg = dcm->base + 0x15;
+ dcm->mack = dcm->base + 0x18;
+
+ /* Check to make sure the DCM is enable and working */
+ if (!is_sram_available(dcm)) {
+ dev_err(&pdev->dev, "dcm is not responding\n");
+ ret = -EBUSY;
+ goto err_iomap;
+ }
+
+ dcm->dev = &pdev->dev;
+ dev_set_drvdata(&pdev->dev, dcm);
+
+ /* get the struct om_info */
+ if (dcm_get_info(&pdev->dev)) {
+ dev_err(&pdev->dev, "could not get struct om_info\n");
+ ret = -EBUSY;
+ goto err_iomap;
+ }
+
+ /* only support v41 or later */
+ if (dcm->board.info.ver < 41 || dcm->board.info.ver == 0xff) {
+ dev_err(&pdev->dev, "dcm is invalid or needs to update\n");
+ ret = -ENODEV;
+ goto err_iomap;
+ }
+
+ dcm->board.rec = kzalloc(sizeof(*dcm->board.rec) *
+ dcm->board.info.count, GFP_KERNEL);
+ if (!dcm->board.rec) {
+ dev_err(&pdev->dev, "no memory\n");
+ ret = -ENOMEM;
+ goto err_iomap;
+ }
+
+ dcm->board.crec = kzalloc(sizeof(*dcm->board.crec) *
+ dcm->board.info.count, GFP_KERNEL);
+ if (!dcm->board.crec) {
+ dev_err(&pdev->dev, "no memory\n");
+ ret = -ENOMEM;
+ goto err_kzall;
+ }
+
+ /* get the shunt value */
+ match = of_match_node(fsl_dcm_ids, np);
+ dcm->board.shunt = (long)match->data;
+
+ /* enable all the channel */
+ dcm->board.mask = 0x1FF;
+
+ /* init all the board specific data */
+ ret = board_data_init(dcm);
+ if (ret) {
+ dev_err(&pdev->dev, "could not create sysfs group\n");
+ ret = -ENODEV;
+ goto err_kzall2;
+ }
+
+ /* enable all the channel */
+ if (select_dcm_channels(dcm, dcm->board.mask)) {
+ dev_err(&pdev->dev, "could not set crecord mask\n");
+ ret = -ENODEV;
+ goto err_kzall2;
+ }
+
+ /* Set the timer to the 1 Hz */
+ if (set_dcm_frequency(dcm, 1)) {
+ dev_err(&pdev->dev, "could not set frequency\n");
+ ret = -ENODEV;
+ goto err_kzall2;
+ }
+
+ /* create sysfs interface */
+ ret = sysfs_create_group(&pdev->dev.kobj, &fsl_dcm_attr_group);
+ if (ret) {
+ dev_err(&pdev->dev, "could not create sysfs group\n");
+ goto err_kzall2;
+ }
+
+ return 0;
+
+err_kzall2:
+ kfree(dcm->board.crec);
+
+err_kzall:
+ kfree(dcm->board.rec);
+
+err_iomap:
+ iounmap(dcm->base);
+
+err_kzalloc:
+ kfree(dcm);
+
+ return ret;
+}
+
+static int fsl_dcm_remove(struct platform_device *pdev)
+{
+ struct fsl_dcm_data *dcm = dev_get_drvdata(&pdev->dev);
+
+ stop_data_collection(dcm);
+ sysfs_remove_group(&pdev->dev.kobj, &fsl_dcm_attr_group);
+ iounmap(dcm->base);
+ kfree(dcm->board.rec);
+ kfree(dcm->board.crec);
+ kfree(dcm);
+
+ return 0;
+}
+
+static struct platform_driver fsl_dcm_driver = {
+ .driver = {
+ .name = "fsl-dcm-driver",
+ .owner = THIS_MODULE,
+ },
+ .probe = fsl_dcm_probe,
+ .remove = fsl_dcm_remove,
+};
+
+static int __init fsl_dcm_init(void)
+{
+ struct device_node *np;
+ struct platform_device *pdev;
+ int ret;
+
+ np = of_find_matching_node(NULL, fsl_dcm_ids);
+ if (!np)
+ return -ENODEV;
+
+ /* We found a supported platform, so register a platform driver */
+ ret = platform_driver_register(&fsl_dcm_driver);
+ if (ret) {
+ pr_err("fsl-dcm: could not register platform driver\n");
+ goto err_np;
+ }
+
+ /* We need to create a device and add the data for this platform */
+ pdev = platform_device_alloc(fsl_dcm_driver.driver.name, 0);
+ if (!pdev) {
+ ret = -ENOMEM;
+ goto err_drv;
+ }
+
+ /* Pass the device_node pointer to the probe function */
+ pdev->dev.of_node = np;
+
+ /* This will call the probe function */
+ ret = platform_device_add(pdev);
+ if (ret) {
+ pr_err("fsl-dcm: could not register platform driver\n");
+ goto err_dev;
+ }
+
+ pr_info("Freescale Data Collection Module is installed.\n");
+
+ return 0;
+
+err_dev:
+ platform_device_unregister(pdev);
+
+err_drv:
+ platform_driver_unregister(&fsl_dcm_driver);
+
+err_np:
+ of_node_put(np);
+
+ return ret;
+}
+
+static void __exit fsl_dcm_exit(void)
+{
+ platform_driver_unregister(&fsl_dcm_driver);
+}
+
+MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
+MODULE_DESCRIPTION("Freescale Data Collection Manager driver");
+MODULE_LICENSE("GPL v2");
+
+module_init(fsl_dcm_init);
+module_exit(fsl_dcm_exit);
diff --git a/drivers/misc/fsl_dcm.h b/drivers/misc/fsl_dcm.h
new file mode 100644
index 0000000..722ac28
--- /dev/null
+++ b/drivers/misc/fsl_dcm.h
@@ -0,0 +1,129 @@
+#ifndef _FSL_DCM_H
+#define _FSL_DCM_H
+
+/* sysfs commands for 'control' */
+#define SYSFS_DCM_CMD_STOP 0
+#define SYSFS_DCM_CMD_START 1
+
+#define MAX_NAME_LEN 10
+#define MAX_FREQUENCY 48 /* max freq the DCM supports */
+#define DATA_ADDR 0x20 /* data address in DCM SRAM */
+#define CHAN_MASK 0x1FF /* 9 channel is enabled */
+
+/* PIXIS register status bits */
+#define PX_OCMD_MSG (1 << 0)
+#define PX_OACK_ERR (1 << 1)
+#define PX_OACK_ACK (1 << 0) /* OACK is sometimes called MACK */
+
+/* DCM commands */
+#define OM_END 0x00
+#define OM_SETDLY 0x01
+#define OM_RST0 0x02
+#define OM_RST1 0x03
+#define OM_CHKDLY 0x04
+#define OM_PWR 0x05
+#define OM_WAKE 0x07
+#define OM_GETMEM 0x08
+#define OM_SETMEM 0x09
+#define OM_SCLR 0x10
+#define OM_START 0x11
+#define OM_STOP 0x12
+#define OM_GET 0x13
+#define OM_ENABLE 0x14
+#define OM_TIMER 0x15
+#define OM_SNAP 0x16
+#define OM_GETMEMX 0x18
+#define OM_AVGOFF 0x19
+#define OM_AVGON 0x1A
+#define OM_SETV 0x30
+#define OM_INFO 0x31
+#define OM_XINFO 0x32
+
+/* Support definitions */
+#define CRCTLBIT_ENA (7) /* Set to enable collection */
+#define CRCTLBIT_IGNORE (6) /* Ignore record for voltage mangement */
+#define CRCTLBIT_FILTER (5) /* Set to enable data filtering */
+#define CRCTLBIT_GET_V2 (4) /* Record type: voltage via INA220 */
+#define CRCTLBIT_GET_I2 (3) /* Record type: current via INA220 */
+#define CRCTLBIT_GET_T (2) /* Record type: temperature via ADT7461 */
+#define CRCTLBIT_GET_I (1) /* Record type: current via PMBus device */
+#define CRCTLBIT_GET_V (0) /* Record type: voltage via PMBus device */
+
+/* Bitmasks */
+#define CRCTL_ENA (1 << CRCTLBIT_ENA)
+#define CRCTL_IGNORE (1 << CRCTLBIT_IGNORE)
+#define CRCTL_FILTER (1 << CRCTLBIT_FILTER)
+#define CRCTL_GET_V2 (1 << CRCTLBIT_GET_V2)
+#define CRCTL_GET_I2 (1 << CRCTLBIT_GET_I2)
+#define CRCTL_GET_T (1 << CRCTLBIT_GET_T)
+#define CRCTL_GET_I (1 << CRCTLBIT_GET_I)
+#define CRCTL_GET_V (1 << CRCTLBIT_GET_V)
+
+/* Handy definitions */
+#define CR_VOLT_PMB (CRCTL_ENA|CRCTL_FILTER|CRCTL_GET_V)
+#define CR_VOLT_PMB_X (CRCTL_ENA|CRCTL_FILTER|CRCTL_GET_V|CRCTL_IGNORE)
+#define CR_CURR_PMB (CRCTL_ENA|CRCTL_FILTER|CRCTL_GET_I)
+#define CR_VOLT_INA (CRCTL_ENA|CRCTL_GET_V2)
+#define CR_CURR_INA (CRCTL_ENA|CRCTL_GET_I2)
+#define CR_TEMP (CRCTL_ENA|CRCTL_FILTER|CRCTL_GET_T)
+
+struct crecord {
+ u8 ctl; /* enabled, data type */
+ u8 i2c_addr; /* target i2c address */
+ u8 port; /* target port(channel) */
+ __be16 curr; /* most recent sample */
+ __be16 max; /* maximum value */
+ __be16 num1; /* number of samples taken */
+ u8 num2;
+ __be32 accum; /* sum of samples */
+ __be16 xinfo_addr; /* pointer to XINFO structure */
+} __packed;
+
+struct om_info {
+ __be16 ver; /* DCM version number */
+ u8 prescale; /* prescale value used */
+ u8 timer; /* timer */
+ u8 count; /* number of CRECORDS */
+ __be16 addr; /* address of CRECORD array in SRAM */
+ u8 res[3];
+} __packed;
+
+struct om_xinfo {
+ __be16 name_addr; /* descriptive name */
+ __be16 def; /* SW-defined default */
+ __be16 curr; /* SW-obtained value */
+ __be16 cal; /* calibration factor */
+ u8 flags;
+ u8 method;
+ __be16 get; /* get current value */
+ __be16 set; /* set voltage to value */
+ __be16 init; /* initialize device */
+} __packed;
+
+struct record {
+ char *name; /* record name */
+ char *unit; /* record unit */
+};
+
+struct dcm_board {
+ struct om_info info;
+ struct record *rec; /* per record struct */
+ struct crecord *crec; /* all the collected records */
+ u32 shunt; /* shunt for ina220 current */
+ u32 mask; /* channel mask */
+};
+
+struct fsl_dcm_data {
+ void __iomem *base; /* PIXIS/QIXIS base address */
+ struct device *dev;
+ u8 __iomem *addr; /* SRAM address */
+ u8 __iomem *data; /* SRAM data */
+ u8 __iomem *ocmd; /* DCM command/status */
+ u8 __iomem *omsg; /* DCM message */
+ u8 __iomem *mack; /* DCM acknowledge */
+ u8 timer;
+ int running;
+ struct dcm_board board;
+};
+
+#endif