Initial Contribution
msm-2.6.38: tag AU_LINUX_ANDROID_GINGERBREAD.02.03.04.00.142
Signed-off-by: Bryan Huntsman <bryanh@codeaurora.org>
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 5f888f7..ef3cb2e 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -28,6 +28,14 @@
tristate
default n
+config SENSORS_WPCE775X
+ bool "Winbond WPCE775X"
+ depends on I2C
+ default n
+ help
+ This driver provides support for the Winbond WPCE775XX Embedded
+ Controller, which provides lcd backlight, LEDs, and Battery control.
+
config HWMON_DEBUG_CHIP
bool "Hardware Monitoring Chip debugging messages"
default n
@@ -767,6 +775,19 @@
This driver can also be built as a module. If so, the module
will be called max6650.
+config SENSORS_MSM_ADC
+ tristate "MSM ADC Driver for current measurement"
+ depends on ARCH_MSM7X30 || ARCH_MSM8X60 || ARCH_FSM9XXX
+ default n
+ help
+ Provides interface for measuring the ADC's on AMUX channels of XOADC,
+ MPP's and the XOTHERM on pmic8058 for msm8x60 and provides post processing
+ of the channel for the ADC Raw Data. For reading LTC and EPM ADC channels
+ say yes here to include support for measuring current in real-time
+ from various power-rails on the Fluid board. The ADC circuit
+ internally uses an array of LTC2499 and EPM ADCs in a differential
+ configuration to provide a flat set of channels that can be addressed.
+
config SENSORS_PC87360
tristate "National Semiconductor PC87360 family"
select HWMON_VID
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 28061cf..eacfcb5 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -118,6 +118,8 @@
obj-$(CONFIG_SENSORS_W83L786NG) += w83l786ng.o
obj-$(CONFIG_SENSORS_WM831X) += wm831x-hwmon.o
obj-$(CONFIG_SENSORS_WM8350) += wm8350-hwmon.o
+obj-$(CONFIG_SENSORS_WPCE775X) += wpce775x.o
+obj-$(CONFIG_SENSORS_MSM_ADC) += msm_adc.o m_adcproc.o
# PMBus drivers
obj-$(CONFIG_PMBUS) += pmbus_core.o
diff --git a/drivers/hwmon/m_adcproc.c b/drivers/hwmon/m_adcproc.c
new file mode 100644
index 0000000..70e505e
--- /dev/null
+++ b/drivers/hwmon/m_adcproc.c
@@ -0,0 +1,469 @@
+/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/kernel.h>
+
+#include <linux/msm_adc.h>
+
+#define KELVINMIL_DEGMIL 273160
+
+static const struct adc_map_pt adcmap_batttherm[] = {
+ {2020, -30},
+ {1923, -20},
+ {1796, -10},
+ {1640, 0},
+ {1459, 10},
+ {1260, 20},
+ {1159, 25},
+ {1059, 30},
+ {871, 40},
+ {706, 50},
+ {567, 60},
+ {453, 70},
+ {364, 80}
+};
+
+static const struct adc_map_pt adcmap_msmtherm[] = {
+ {2150, -30},
+ {2107, -20},
+ {2037, -10},
+ {1929, 0},
+ {1776, 10},
+ {1579, 20},
+ {1467, 25},
+ {1349, 30},
+ {1108, 40},
+ {878, 50},
+ {677, 60},
+ {513, 70},
+ {385, 80},
+ {287, 90},
+ {215, 100},
+ {186, 110},
+ {107, 120}
+};
+
+static const struct adc_map_pt adcmap_ntcg104ef104fb[] = {
+ {696483, -40960},
+ {649148, -39936},
+ {605368, -38912},
+ {564809, -37888},
+ {527215, -36864},
+ {492322, -35840},
+ {460007, -34816},
+ {429982, -33792},
+ {402099, -32768},
+ {376192, -31744},
+ {352075, -30720},
+ {329714, -29696},
+ {308876, -28672},
+ {289480, -27648},
+ {271417, -26624},
+ {254574, -25600},
+ {238903, -24576},
+ {224276, -23552},
+ {210631, -22528},
+ {197896, -21504},
+ {186007, -20480},
+ {174899, -19456},
+ {164521, -18432},
+ {154818, -17408},
+ {145744, -16384},
+ {137265, -15360},
+ {129307, -14336},
+ {121866, -13312},
+ {114896, -12288},
+ {108365, -11264},
+ {102252, -10240},
+ {96499, -9216},
+ {91111, -8192},
+ {86055, -7168},
+ {81308, -6144},
+ {76857, -5120},
+ {72660, -4096},
+ {68722, -3072},
+ {65020, -2048},
+ {61538, -1024},
+ {58261, 0},
+ {55177, 1024},
+ {52274, 2048},
+ {49538, 3072},
+ {46962, 4096},
+ {44531, 5120},
+ {42243, 6144},
+ {40083, 7168},
+ {38045, 8192},
+ {36122, 9216},
+ {34308, 10240},
+ {32592, 11264},
+ {30972, 12288},
+ {29442, 13312},
+ {27995, 14336},
+ {26624, 15360},
+ {25333, 16384},
+ {24109, 17408},
+ {22951, 18432},
+ {21854, 19456},
+ {20807, 20480},
+ {19831, 21504},
+ {18899, 22528},
+ {18016, 23552},
+ {17178, 24576},
+ {16384, 25600},
+ {15631, 26624},
+ {14916, 27648},
+ {14237, 28672},
+ {13593, 29696},
+ {12976, 30720},
+ {12400, 31744},
+ {11848, 32768},
+ {11324, 33792},
+ {10825, 34816},
+ {10354, 35840},
+ {9900, 36864},
+ {9471, 37888},
+ {9062, 38912},
+ {8674, 39936},
+ {8306, 40960},
+ {7951, 41984},
+ {7616, 43008},
+ {7296, 44032},
+ {6991, 45056},
+ {6701, 46080},
+ {6424, 47104},
+ {6160, 48128},
+ {5908, 49152},
+ {5667, 50176},
+ {5439, 51200},
+ {5219, 52224},
+ {5010, 53248},
+ {4810, 54272},
+ {4619, 55296},
+ {4440, 56320},
+ {4263, 57344},
+ {4097, 58368},
+ {3938, 59392},
+ {3785, 60416},
+ {3637, 61440},
+ {3501, 62464},
+ {3368, 63488},
+ {3240, 64512},
+ {3118, 65536},
+ {2998, 66560},
+ {2889, 67584},
+ {2782, 68608},
+ {2680, 69632},
+ {2581, 70656},
+ {2490, 71680},
+ {2397, 72704},
+ {2310, 73728},
+ {2227, 74752},
+ {2147, 75776},
+ {2064, 76800},
+ {1998, 77824},
+ {1927, 78848},
+ {1860, 79872},
+ {1795, 80896},
+ {1736, 81920},
+ {1673, 82944},
+ {1615, 83968},
+ {1560, 84992},
+ {1507, 86016},
+ {1456, 87040},
+ {1407, 88064},
+ {1360, 89088},
+ {1314, 90112},
+ {1271, 91136},
+ {1228, 92160},
+ {1189, 93184},
+ {1150, 94208},
+ {1112, 95232},
+ {1076, 96256},
+ {1042, 97280},
+ {1008, 98304},
+ {976, 99328},
+ {945, 100352},
+ {915, 101376},
+ {886, 102400},
+ {859, 103424},
+ {832, 104448},
+ {807, 105472},
+ {782, 106496},
+ {756, 107520},
+ {735, 108544},
+ {712, 109568},
+ {691, 110592},
+ {670, 111616},
+ {650, 112640},
+ {631, 113664},
+ {612, 114688},
+ {594, 115712},
+ {577, 116736},
+ {560, 117760},
+ {544, 118784},
+ {528, 119808},
+ {513, 120832},
+ {498, 121856},
+ {483, 122880},
+ {470, 123904},
+ {457, 124928},
+ {444, 125952},
+ {431, 126976},
+ {419, 128000}
+};
+
+static int32_t
+ adc_map_linear(const struct adc_map_pt *pts,
+ uint32_t tablesize, int32_t input, int64_t *output)
+{
+ bool descending = 1;
+ uint32_t i = 0;
+
+ if ((pts == NULL) || (output == NULL))
+ return -EINVAL;
+
+ /* Check if table is descending or ascending */
+ if (tablesize > 1) {
+ if (pts[0].x < pts[1].x)
+ descending = 0;
+ }
+
+ while (i < tablesize) {
+ if ((descending == 1) && (pts[i].x < input)) {
+ /* table entry is less than measured
+ value and table is descending, stop */
+ break;
+ } else if ((descending == 0) &&
+ (pts[i].x > input)) {
+ /* table entry is greater than measured
+ value and table is ascending, stop */
+ break;
+ } else
+ i++;
+ }
+
+ if (i == 0)
+ *output = pts[0].y;
+ else if (i == tablesize)
+ *output = pts[tablesize-1].y;
+ else {
+ /* result is between search_index and search_index-1 */
+ /* interpolate linearly */
+ *output = (((int32_t) ((pts[i].y - pts[i-1].y)*
+ (input - pts[i-1].x))/
+ (pts[i].x - pts[i-1].x))+
+ pts[i-1].y);
+ }
+
+ return 0;
+}
+
+int32_t scale_default(int32_t adc_code,
+ const struct adc_properties *adc_properties,
+ const struct chan_properties *chan_properties,
+ struct adc_chan_result *adc_chan_result)
+{
+ bool negative_rawfromoffset = 0;
+ int32_t rawfromoffset = adc_code - chan_properties->adc_graph->offset;
+
+ if (!chan_properties->gain_numerator ||
+ !chan_properties->gain_denominator)
+ return -EINVAL;
+
+ adc_chan_result->adc_code = adc_code;
+ if (rawfromoffset < 0) {
+ if (adc_properties->bipolar) {
+ rawfromoffset = (rawfromoffset ^ -1) + 1;
+ negative_rawfromoffset = 1;
+ } else
+ rawfromoffset = 0;
+ }
+
+ if (rawfromoffset >= 1 << adc_properties->bitresolution)
+ rawfromoffset = (1 << adc_properties->bitresolution) - 1;
+
+ adc_chan_result->measurement = (int64_t)rawfromoffset*
+ chan_properties->adc_graph->dx*
+ chan_properties->gain_denominator;
+
+ /* do_div only perform positive integer division! */
+ do_div(adc_chan_result->measurement, chan_properties->adc_graph->dy*
+ chan_properties->gain_numerator);
+
+ if (negative_rawfromoffset)
+ adc_chan_result->measurement =
+ (adc_chan_result->measurement ^ -1) + 1;
+
+ /* Note: adc_chan_result->measurement is in the unit of
+ * adc_properties.adc_reference. For generic channel processing,
+ * channel measurement is a scale/ratio relative to the adc
+ * reference input */
+ adc_chan_result->physical = (int32_t) adc_chan_result->measurement;
+
+ return 0;
+}
+
+int32_t scale_batt_therm(int32_t adc_code,
+ const struct adc_properties *adc_properties,
+ const struct chan_properties *chan_properties,
+ struct adc_chan_result *adc_chan_result)
+{
+ scale_default(adc_code, adc_properties, chan_properties,
+ adc_chan_result);
+ /* convert mV ---> degC using the table */
+ return adc_map_linear(
+ adcmap_batttherm,
+ sizeof(adcmap_batttherm)/sizeof(adcmap_batttherm[0]),
+ adc_chan_result->physical,
+ &adc_chan_result->physical);
+}
+
+int32_t scale_msm_therm(int32_t adc_code,
+ const struct adc_properties *adc_properties,
+ const struct chan_properties *chan_properties,
+ struct adc_chan_result *adc_chan_result)
+{
+ scale_default(adc_code, adc_properties, chan_properties,
+ adc_chan_result);
+ /* convert mV ---> degC using the table */
+ return adc_map_linear(
+ adcmap_msmtherm,
+ sizeof(adcmap_msmtherm)/sizeof(adcmap_msmtherm[0]),
+ adc_chan_result->physical,
+ &adc_chan_result->physical);
+}
+
+int32_t scale_pmic_therm(int32_t adc_code,
+ const struct adc_properties *adc_properties,
+ const struct chan_properties *chan_properties,
+ struct adc_chan_result *adc_chan_result)
+{
+ /* 2mV/K */
+ int32_t rawfromoffset = adc_code - chan_properties->adc_graph->offset;
+
+ if (!chan_properties->gain_numerator ||
+ !chan_properties->gain_denominator)
+ return -EINVAL;
+
+ adc_chan_result->adc_code = adc_code;
+ if (rawfromoffset > 0) {
+ if (rawfromoffset >= 1 << adc_properties->bitresolution)
+ rawfromoffset = (1 << adc_properties->bitresolution)
+ - 1;
+ adc_chan_result->measurement = (int64_t)rawfromoffset*
+ chan_properties->adc_graph->dx*
+ chan_properties->gain_denominator*1000;
+ do_div(adc_chan_result->measurement,
+ chan_properties->adc_graph->dy*
+ chan_properties->gain_numerator*2);
+ } else {
+ adc_chan_result->measurement = 0;
+ }
+ /* Note: adc_chan_result->measurement is in the unit of
+ adc_properties.adc_reference */
+ adc_chan_result->physical = (int32_t)adc_chan_result->measurement;
+ /* Change to .001 deg C */
+ adc_chan_result->physical -= KELVINMIL_DEGMIL;
+ adc_chan_result->measurement <<= 1;
+
+ return 0;
+}
+
+/* Scales the ADC code to 0.001 degrees C using the map
+ * table for the XO thermistor.
+ */
+int32_t tdkntcgtherm(int32_t adc_code,
+ const struct adc_properties *adc_properties,
+ const struct chan_properties *chan_properties,
+ struct adc_chan_result *adc_chan_result)
+{
+ int32_t offset = chan_properties->adc_graph->offset,
+ dy = chan_properties->adc_graph->dy,
+ dx = chan_properties->adc_graph->dx,
+ fullscale_calibrated_adc_code;
+
+ uint32_t rt_r25;
+ uint32_t num1, num2, denom;
+
+ adc_chan_result->adc_code = adc_code;
+ fullscale_calibrated_adc_code = dy + offset;
+ /* The above is a short cut in math that would reduce a lot of
+ computation whereas the below expression
+ (adc_properties->adc_reference*dy+dx*offset+(dx>>1))/dx
+ is a more generic formula when the 2 reference voltages are
+ different than 0 and full scale voltage. */
+
+ if ((dy == 0) || (dx == 0) ||
+ (offset >= fullscale_calibrated_adc_code)) {
+ return -EINVAL;
+ } else {
+ if (adc_code >= fullscale_calibrated_adc_code) {
+ rt_r25 = (uint32_t)-1;
+ } else if (adc_code <= offset) {
+ rt_r25 = 0;
+ } else {
+ /* The formula used is (adc_code of current reading - offset)/
+ * (the calibrated fullscale adc code - adc_code of current reading).
+ * For this channel, at this time, chan_properties->gain_numerator =
+ * chan_properties->gain_denominator = 1, so no need to incorporate
+ * into the formula even though we could and multiply/divide by 1
+ * which yields the same result but expensive on computation. */
+ num1 = (adc_code - offset) << 14;
+ num2 = (fullscale_calibrated_adc_code - adc_code) >> 1;
+ denom = fullscale_calibrated_adc_code - adc_code;
+
+ if ((int)denom <= 0)
+ rt_r25 = 0x7FFFFFFF;
+ else
+ rt_r25 = (num1 + num2) / denom;
+ }
+
+ if (rt_r25 > 0x7FFFFFFF)
+ rt_r25 = 0x7FFFFFFF;
+
+ adc_map_linear(adcmap_ntcg104ef104fb,
+ sizeof(adcmap_ntcg104ef104fb)/sizeof(adcmap_ntcg104ef104fb[0]),
+ (int32_t)rt_r25, &adc_chan_result->physical);
+ }
+
+ return 0;
+}
+
+int32_t scale_xtern_chgr_cur(int32_t adc_code,
+ const struct adc_properties *adc_properties,
+ const struct chan_properties *chan_properties,
+ struct adc_chan_result *adc_chan_result)
+{
+ int32_t rawfromoffset = adc_code - chan_properties->adc_graph->offset;
+
+ if (!chan_properties->gain_numerator ||
+ !chan_properties->gain_denominator)
+ return -EINVAL;
+
+ adc_chan_result->adc_code = adc_code;
+ if (rawfromoffset > 0) {
+ if (rawfromoffset >= 1 << adc_properties->bitresolution)
+ rawfromoffset = (1 << adc_properties->bitresolution)
+ - 1;
+ adc_chan_result->measurement = ((int64_t)rawfromoffset * 5)*
+ chan_properties->adc_graph->dx*
+ chan_properties->gain_denominator;
+ do_div(adc_chan_result->measurement,
+ chan_properties->adc_graph->dy*
+ chan_properties->gain_numerator);
+ } else {
+ adc_chan_result->measurement = 0;
+ }
+ adc_chan_result->physical = (int32_t) adc_chan_result->measurement;
+
+ return 0;
+}
diff --git a/drivers/hwmon/msm_adc.c b/drivers/hwmon/msm_adc.c
new file mode 100644
index 0000000..42bcd07
--- /dev/null
+++ b/drivers/hwmon/msm_adc.c
@@ -0,0 +1,1538 @@
+/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/miscdevice.h>
+#include <linux/fs.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/uaccess.h>
+#include <linux/msm_adc.h>
+#include <linux/pmic8058-xoadc.h>
+#include <linux/slab.h>
+#include <linux/semaphore.h>
+
+#include <mach/dal.h>
+
+#define MSM_ADC_DRIVER_NAME "msm_adc"
+#define MSM_ADC_MAX_FNAME 15
+
+#define MSM_ADC_DALRPC_DEVICEID 0x02000067
+#define MSM_ADC_DALRPC_PORT_NAME "DAL00"
+#define MSM_ADC_DALRPC_CPU SMD_APPS_MODEM
+
+#define MSM_ADC_DALRPC_CMD_REQ_CONV 9
+#define MSM_ADC_DALRPC_CMD_INPUT_PROP 11
+
+#define MSM_ADC_DALRC_CONV_TIMEOUT (5 * HZ) /* 5 seconds */
+
+enum dal_error {
+ DAL_ERROR_INVALID_DEVICE_IDX = 1,
+ DAL_ERROR_INVALID_CHANNEL_IDX,
+ DAL_ERROR_NULL_POINTER,
+ DAL_ERROR_DEVICE_QUEUE_FULL,
+ DAL_ERROR_INVALID_PROPERTY_LENGTH,
+ DAL_ERROR_REMOTE_EVENT_POOL_FULL
+};
+
+enum dal_result_status {
+ DAL_RESULT_STATUS_INVALID,
+ DAL_RESULT_STATUS_VALID
+};
+
+struct dal_conv_state {
+ struct dal_conv_slot context[MSM_ADC_DEV_MAX_INFLIGHT];
+ struct list_head slots;
+ struct mutex list_lock;
+ struct semaphore slot_count;
+};
+
+struct adc_dev {
+ char *name;
+ uint32_t nchans;
+ struct dal_conv_state conv;
+ struct dal_translation transl;
+ struct sensor_device_attribute *sens_attr;
+ char **fnames;
+};
+
+struct msm_adc_drv {
+ /* Common to both XOADC and EPM */
+ struct platform_device *pdev;
+ struct device *hwmon;
+ struct miscdevice misc;
+ /* XOADC variables */
+ struct sensor_device_attribute *sens_attr;
+ struct workqueue_struct *wq;
+ atomic_t online;
+ atomic_t total_outst;
+ wait_queue_head_t total_outst_wait;
+
+ /* EPM variables */
+ void *dev_h;
+ struct adc_dev *devs[MSM_ADC_MAX_NUM_DEVS];
+ struct mutex prop_lock;
+ atomic_t rpc_online;
+ atomic_t rpc_total_outst;
+ wait_queue_head_t rpc_total_outst_wait;
+};
+
+static bool epm_init;
+static bool epm_fluid_enabled;
+
+/* Needed to support file_op interfaces */
+static struct msm_adc_drv *msm_adc_drv;
+
+static ssize_t msm_adc_show_curr(struct device *dev,
+ struct device_attribute *devattr, char *buf);
+
+static int msm_rpc_adc_blocking_conversion(struct msm_adc_drv *msm_adc,
+ uint32_t chan, struct adc_chan_result *result);
+
+static int msm_adc_blocking_conversion(struct msm_adc_drv *msm_adc,
+ uint32_t chan, struct adc_chan_result *result);
+
+static int msm_adc_open(struct inode *inode, struct file *file)
+{
+ struct msm_client_data *client;
+ struct msm_adc_drv *msm_adc = msm_adc_drv;
+ struct platform_device *pdev = msm_adc->pdev;
+
+ client = kzalloc(sizeof(struct msm_client_data), GFP_KERNEL);
+ if (!client) {
+ dev_err(&pdev->dev, "Unable to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ if (!try_module_get(THIS_MODULE)) {
+ kfree(client);
+ return -EACCES;
+ }
+
+ mutex_init(&client->lock);
+ INIT_LIST_HEAD(&client->complete_list);
+ init_waitqueue_head(&client->data_wait);
+ init_waitqueue_head(&client->outst_wait);
+
+ client->online = 1;
+
+ file->private_data = client;
+
+ return nonseekable_open(inode, file);
+}
+
+static inline void msm_adc_restore_slot(struct dal_conv_state *conv_s,
+ struct dal_conv_slot *slot)
+{
+ mutex_lock(&conv_s->list_lock);
+ list_add(&slot->list, &conv_s->slots);
+ mutex_unlock(&conv_s->list_lock);
+
+ up(&conv_s->slot_count);
+}
+
+static int no_pending_client_requests(struct msm_client_data *client)
+{
+ mutex_lock(&client->lock);
+
+ if (client->num_outstanding == 0) {
+ mutex_unlock(&client->lock);
+ return 1;
+ }
+
+ mutex_unlock(&client->lock);
+
+ return 0;
+}
+
+static int data_avail(struct msm_client_data *client, uint32_t *pending)
+{
+ uint32_t completed;
+
+ mutex_lock(&client->lock);
+ completed = client->num_complete;
+ mutex_unlock(&client->lock);
+
+ if (completed > 0) {
+ if (pending != NULL)
+ *pending = completed;
+ return 1;
+ }
+
+ return 0;
+}
+
+static int msm_adc_release(struct inode *inode, struct file *file)
+{
+ struct msm_client_data *client = file->private_data;
+ struct adc_conv_slot *slot, *tmp;
+ int rc;
+ struct msm_adc_platform_data *pdata =
+ msm_adc_drv->pdev->dev.platform_data;
+ struct msm_adc_channels *channel = pdata->channel;
+
+ module_put(THIS_MODULE);
+
+ mutex_lock(&client->lock);
+
+ /* prevent any further requests while we teardown the client */
+ client->online = 0;
+
+ mutex_unlock(&client->lock);
+
+ /*
+ * We may still have outstanding transactions in flight from this
+ * client that have not completed. Make sure they're completed
+ * before removing the client.
+ */
+ rc = wait_event_interruptible(client->outst_wait,
+ no_pending_client_requests(client));
+ if (rc) {
+ pr_err("%s: wait_event_interruptible failed rc = %d\n",
+ __func__, rc);
+ return rc;
+ }
+
+ /*
+ * All transactions have completed. Add slot resources back to the
+ * appropriate devices.
+ */
+ list_for_each_entry_safe(slot, tmp, &client->complete_list, list) {
+ slot->client = NULL;
+ list_del(&slot->list);
+ channel[slot->conv.result.chan].adc_access_fn->adc_restore_slot(
+ channel[slot->conv.result.chan].adc_dev_instance, slot);
+ }
+
+ kfree(client);
+
+ return 0;
+}
+
+static int msm_adc_translate_dal_to_hwmon(struct msm_adc_drv *msm_adc,
+ uint32_t chan,
+ struct adc_dev_spec *dest)
+{
+ struct dal_translation *transl;
+ struct msm_adc_platform_data *pdata = msm_adc->pdev->dev.platform_data;
+ int i;
+
+ for (i = 0; i < pdata->num_adc; i++) {
+ transl = &msm_adc->devs[i]->transl;
+ if (chan >= transl->hwmon_start &&
+ chan <= transl->hwmon_end) {
+ dest->dal.dev_idx = transl->dal_dev_idx;
+ dest->hwmon_dev_idx = transl->hwmon_dev_idx;
+ dest->dal.chan_idx = chan - transl->hwmon_start;
+ return 0;
+ }
+ }
+ return -EINVAL;
+}
+
+static int msm_adc_translate_hwmon_to_dal(struct msm_adc_drv *msm_adc,
+ struct adc_dev_spec *source,
+ uint32_t *chan)
+{
+ struct msm_adc_platform_data *pdata = msm_adc->pdev->dev.platform_data;
+ struct dal_translation *transl;
+ int i;
+
+ for (i = 0; i < pdata->num_adc; i++) {
+ transl = &msm_adc->devs[i]->transl;
+ if (source->dal.dev_idx != transl->dal_dev_idx)
+ continue;
+ *chan = transl->hwmon_start + source->dal.chan_idx;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int msm_adc_getinputproperties(struct msm_adc_drv *msm_adc,
+ const char *lookup_name,
+ struct adc_dev_spec *result)
+{
+ struct device *dev = &msm_adc->pdev->dev;
+ int rc;
+
+ mutex_lock(&msm_adc->prop_lock);
+
+ rc = dalrpc_fcn_8(MSM_ADC_DALRPC_CMD_INPUT_PROP, msm_adc->dev_h,
+ lookup_name, strlen(lookup_name) + 1,
+ &result->dal, sizeof(struct dal_dev_spec));
+ if (rc) {
+ dev_err(dev, "DAL getprop request failed: rc = %d\n", rc);
+ mutex_unlock(&msm_adc->prop_lock);
+ return -EIO;
+ }
+
+ mutex_unlock(&msm_adc->prop_lock);
+ return rc;
+}
+
+static int msm_adc_lookup(struct msm_adc_drv *msm_adc,
+ struct msm_adc_lookup *lookup)
+{
+ struct msm_adc_platform_data *pdata = msm_adc->pdev->dev.platform_data;
+ struct adc_dev_spec target;
+ int rc = 0, i = 0;
+ uint32_t len = 0;
+
+ len = strnlen(lookup->name, MSM_ADC_MAX_CHAN_STR);
+ while (i < pdata->num_chan_supported) {
+ if (strncmp(lookup->name, pdata->channel[i].name, len))
+ i++;
+ else
+ break;
+ }
+
+ if (pdata->num_chan_supported > 0 && i < pdata->num_chan_supported) {
+ lookup->chan_idx = i;
+ } else if (msm_adc->dev_h) {
+ rc = msm_adc_getinputproperties(msm_adc, lookup->name, &target);
+ if (rc) {
+ pr_err("%s: Lookup failed for %s\n", __func__,
+ lookup->name);
+ return rc;
+ }
+ rc = msm_adc_translate_hwmon_to_dal(msm_adc, &target,
+ &lookup->chan_idx);
+ if (rc)
+ pr_err("%s: Translation failed for %s\n", __func__,
+ lookup->name);
+ } else {
+ pr_err("%s: Lookup failed for %s\n", __func__, lookup->name);
+ rc = -EINVAL;
+ }
+ return rc;
+}
+
+static int msm_adc_aio_conversion(struct msm_adc_drv *msm_adc,
+ struct adc_chan_result *request,
+ struct msm_client_data *client)
+{
+ struct msm_adc_platform_data *pdata =
+ msm_adc_drv->pdev->dev.platform_data;
+ struct msm_adc_channels *channel = &pdata->channel[request->chan];
+ struct adc_conv_slot *slot;
+
+ /* we could block here, but only for a bounded time */
+ channel->adc_access_fn->adc_slot_request(channel->adc_dev_instance,
+ &slot);
+
+ if (slot) {
+ atomic_inc(&msm_adc->total_outst);
+ mutex_lock(&client->lock);
+ client->num_outstanding++;
+ mutex_unlock(&client->lock);
+
+ /* indicates non blocking request to callback handler */
+ slot->blocking = 0;
+ slot->compk = NULL;/*For kernel space usage; n/a for usr space*/
+ slot->conv.result.chan = client->adc_chan = request->chan;
+ slot->client = client;
+ slot->adc_request = START_OF_CONV;
+ slot->chan_path = channel->chan_path_type;
+ slot->chan_adc_config = channel->adc_config_type;
+ slot->chan_adc_calib = channel->adc_calib_type;
+ queue_work(msm_adc->wq, &slot->work);
+ return 0;
+ }
+ return -EBUSY;
+}
+
+static int msm_adc_fluid_hw_deinit(struct msm_adc_drv *msm_adc)
+{
+ struct msm_adc_platform_data *pdata = msm_adc->pdev->dev.platform_data;
+
+ if (!epm_init)
+ return -EINVAL;
+
+ if (pdata->gpio_config == APROC_CONFIG &&
+ epm_fluid_enabled && pdata->adc_fluid_disable != NULL) {
+ pdata->adc_fluid_disable();
+ epm_fluid_enabled = false;
+ }
+
+ return 0;
+}
+
+static int msm_adc_fluid_hw_init(struct msm_adc_drv *msm_adc)
+{
+ struct msm_adc_platform_data *pdata = msm_adc->pdev->dev.platform_data;
+
+ if (!epm_init)
+ return -EINVAL;
+
+ if (!pdata->adc_fluid_enable)
+ return -ENODEV;
+
+ printk(KERN_DEBUG "msm_adc_fluid_hw_init: Calling adc_fluid_enable.\n");
+
+ if (pdata->gpio_config == APROC_CONFIG && !epm_fluid_enabled) {
+ pdata->adc_fluid_enable();
+ epm_fluid_enabled = true;
+ }
+
+ /* return success for now but check for errors from hw init configuration */
+ return 0;
+}
+
+static int msm_adc_poll_complete(struct msm_adc_drv *msm_adc,
+ struct msm_client_data *client, uint32_t *pending)
+{
+ int rc;
+
+ /*
+ * Don't proceed if there there's nothing queued on this client.
+ * We could deadlock otherwise in a single threaded scenario.
+ */
+ if (no_pending_client_requests(client) && !data_avail(client, pending))
+ return -EDEADLK;
+
+ rc = wait_event_interruptible(client->data_wait,
+ data_avail(client, pending));
+ if (rc)
+ return rc;
+
+ return 0;
+}
+
+static int msm_adc_read_result(struct msm_adc_drv *msm_adc,
+ struct msm_client_data *client,
+ struct adc_chan_result *result)
+{
+ struct msm_adc_platform_data *pdata = msm_adc->pdev->dev.platform_data;
+ struct msm_adc_channels *channel = pdata->channel;
+ struct adc_conv_slot *slot;
+ int rc = 0;
+
+ mutex_lock(&client->lock);
+
+ slot = list_first_entry(&client->complete_list,
+ struct adc_conv_slot, list);
+ if (!slot) {
+ mutex_unlock(&client->lock);
+ return -ENOMSG;
+ }
+
+ slot->client = NULL;
+ list_del(&slot->list);
+
+ client->num_complete--;
+
+ mutex_unlock(&client->lock);
+
+ *result = slot->conv.result;
+
+ /* restore this slot to reserve */
+ channel[slot->conv.result.chan].adc_access_fn->adc_restore_slot(
+ channel[slot->conv.result.chan].adc_dev_instance, slot);
+
+ return rc;
+}
+
+static long msm_adc_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct msm_client_data *client = file->private_data;
+ struct msm_adc_drv *msm_adc = msm_adc_drv;
+ struct platform_device *pdev = msm_adc->pdev;
+ struct msm_adc_platform_data *pdata = pdev->dev.platform_data;
+ uint32_t block_res = 0;
+
+ int rc;
+
+ switch (cmd) {
+ case MSM_ADC_REQUEST:
+ {
+ struct adc_chan_result conv;
+
+ if (copy_from_user(&conv, (void __user *)arg,
+ sizeof(struct adc_chan_result)))
+ return -EFAULT;
+
+ if (conv.chan < pdata->num_chan_supported) {
+ rc = msm_adc_blocking_conversion(msm_adc,
+ conv.chan, &conv);
+ } else {
+ if (!msm_adc->dev_h)
+ return -EAGAIN;
+
+ rc = msm_rpc_adc_blocking_conversion(msm_adc,
+ conv.chan, &conv);
+ }
+ if (rc) {
+ dev_dbg(&pdev->dev, "BLK conversion failed\n");
+ return rc;
+ }
+
+ if (copy_to_user((void __user *)arg, &conv,
+ sizeof(struct adc_chan_result)))
+ return -EFAULT;
+ break;
+ }
+ case MSM_ADC_AIO_REQUEST_BLOCK_RES:
+ block_res = 1;
+ case MSM_ADC_AIO_REQUEST:
+ {
+ struct adc_chan_result conv;
+
+ if (copy_from_user(&conv, (void __user *)arg,
+ sizeof(struct adc_chan_result)))
+ return -EFAULT;
+
+ if (conv.chan >= pdata->num_chan_supported)
+ return -EINVAL;
+
+ rc = msm_adc_aio_conversion(msm_adc, &conv, client);
+ if (rc) {
+ dev_dbg(&pdev->dev, "AIO conversion failed\n");
+ return rc;
+ }
+ if (copy_to_user((void __user *)arg, &conv,
+ sizeof(struct adc_chan_result)))
+ return -EFAULT;
+ break;
+ }
+ case MSM_ADC_AIO_POLL:
+ {
+ uint32_t completed;
+
+ rc = msm_adc_poll_complete(msm_adc, client, &completed);
+ if (rc) {
+ dev_dbg(&pdev->dev, "poll request failed\n");
+ return rc;
+ }
+
+ if (copy_to_user((void __user *)arg, &completed,
+ sizeof(uint32_t)))
+ return -EFAULT;
+
+ break;
+ }
+ case MSM_ADC_AIO_READ:
+ {
+ struct adc_chan_result result;
+
+ rc = msm_adc_read_result(msm_adc, client, &result);
+ if (rc) {
+ dev_dbg(&pdev->dev, "read result failed\n");
+ return rc;
+ }
+
+ if (copy_to_user((void __user *)arg, &result,
+ sizeof(struct adc_chan_result)))
+ return -EFAULT;
+ break;
+ }
+ case MSM_ADC_LOOKUP:
+ {
+ struct msm_adc_lookup lookup;
+
+ if (copy_from_user(&lookup, (void __user *)arg,
+ sizeof(struct msm_adc_lookup)))
+ return -EFAULT;
+
+ rc = msm_adc_lookup(msm_adc, &lookup);
+ if (rc) {
+ dev_dbg(&pdev->dev, "No such channel: %s\n",
+ lookup.name);
+ return rc;
+ }
+
+ if (copy_to_user((void __user *)arg, &lookup,
+ sizeof(struct msm_adc_lookup)))
+ return -EFAULT;
+ break;
+ }
+ case MSM_ADC_FLUID_INIT:
+ {
+ uint32_t result;
+
+ result = msm_adc_fluid_hw_init(msm_adc);
+
+ if (copy_to_user((void __user *)arg, &result,
+ sizeof(uint32_t))) {
+ printk(KERN_ERR "MSM_ADC_FLUID_INIT: "
+ "copy_to_user returned an error.\n");
+ return -EFAULT;
+ }
+ printk(KERN_DEBUG "MSM_ADC_FLUID_INIT: Success.\n");
+ break;
+ }
+ case MSM_ADC_FLUID_DEINIT:
+ {
+ uint32_t result;
+
+ result = msm_adc_fluid_hw_deinit(msm_adc);
+
+ if (copy_to_user((void __user *)arg, &result,
+ sizeof(uint32_t)))
+ return -EFAULT;
+ break;
+ }
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+const struct file_operations msm_adc_fops = {
+ .open = msm_adc_open,
+ .release = msm_adc_release,
+ .unlocked_ioctl = msm_adc_ioctl,
+};
+
+static ssize_t msm_adc_show_curr(struct device *dev,
+ struct device_attribute *devattr, char *buf)
+{
+ struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+ struct msm_adc_drv *msm_adc = dev_get_drvdata(dev);
+ struct msm_adc_platform_data *pdata = msm_adc->pdev->dev.platform_data;
+ struct adc_chan_result result;
+ int rc;
+
+#ifdef CONFIG_PMIC8058_XOADC
+ rc = pm8058_xoadc_registered();
+ if (rc <= 0)
+ return -ENODEV;
+#endif
+ if (attr->index < pdata->num_chan_supported) {
+ rc = msm_adc_blocking_conversion(msm_adc,
+ attr->index, &result);
+ } else {
+ if (pdata->gpio_config == APROC_CONFIG && !epm_fluid_enabled
+ && pdata->adc_fluid_enable != NULL) {
+ printk(KERN_DEBUG "This is to read ADC value for "
+ "Fluid EPM and init. Do it only once.\n");
+ pdata->adc_fluid_enable();
+ epm_fluid_enabled = true;
+ }
+ rc = msm_rpc_adc_blocking_conversion(msm_adc,
+ attr->index, &result);
+ }
+ if (rc)
+ return 0;
+
+ return sprintf(buf, "Result: %lld Raw: %d\n", result.physical,
+ result.adc_code);
+}
+
+static int msm_rpc_adc_blocking_conversion(struct msm_adc_drv *msm_adc,
+ uint32_t hwmon_chan, struct adc_chan_result *result)
+{
+ struct msm_adc_platform_data *pdata = msm_adc->pdev->dev.platform_data;
+ struct dal_conv_request params;
+ struct device *dev = &msm_adc->pdev->dev;
+ struct adc_dev *adc_dev;
+ struct dal_conv_state *conv_s;
+ struct dal_conv_slot *slot;
+ struct adc_dev_spec dest;
+ int timeout, rc = 0;
+
+ if (pdata->gpio_config == APROC_CONFIG &&
+ pdata->adc_gpio_enable != NULL)
+ pdata->adc_gpio_enable(hwmon_chan-pdata->num_chan_supported);
+
+ rc = msm_adc_translate_dal_to_hwmon(msm_adc, hwmon_chan, &dest);
+ if (rc) {
+ dev_err(dev, "%s: translation from chan %u failed\n",
+ __func__, hwmon_chan);
+ if (pdata->gpio_config == APROC_CONFIG &&
+ pdata->adc_gpio_disable != NULL)
+ pdata->adc_gpio_disable(hwmon_chan
+ -pdata->num_chan_supported);
+ return -EINVAL;
+ }
+
+ adc_dev = msm_adc->devs[dest.hwmon_dev_idx];
+ conv_s = &adc_dev->conv;
+
+ down(&conv_s->slot_count);
+
+ mutex_lock(&conv_s->list_lock);
+
+ slot = list_first_entry(&conv_s->slots, struct dal_conv_slot, list);
+ list_del(&slot->list);
+ BUG_ON(!slot);
+
+ mutex_unlock(&conv_s->list_lock);
+
+ /* indicates blocking request to callback handler */
+ slot->blocking = 1;
+
+ params.target.dev_idx = dest.dal.dev_idx;
+ params.target.chan_idx = dest.dal.chan_idx;
+ params.cb_h = slot->cb_h;
+
+ rc = dalrpc_fcn_8(MSM_ADC_DALRPC_CMD_REQ_CONV, msm_adc->dev_h,
+ ¶ms, sizeof(params), NULL, 0);
+ if (rc) {
+ dev_err(dev, "%s: Conversion for device = %u channel = %u"
+ " failed\n", __func__, params.target.dev_idx,
+ params.target.chan_idx);
+
+ rc = -EIO;
+ goto blk_conv_err;
+ }
+
+ timeout = wait_for_completion_interruptible_timeout(&slot->comp,
+ MSM_ADC_DALRC_CONV_TIMEOUT);
+ if (timeout == 0) {
+ dev_err(dev, "read for device = %u channel = %u timed out\n",
+ params.target.dev_idx, params.target.chan_idx);
+ rc = -ETIMEDOUT;
+ goto blk_conv_err;
+ } else if (timeout < 0) {
+ rc = -EINTR;
+ goto blk_conv_err;
+ }
+
+ result->physical = (int64_t)slot->result.physical;
+
+ if (slot->result.status == DAL_RESULT_STATUS_INVALID)
+ rc = -ENODATA;
+
+blk_conv_err:
+ if (pdata->gpio_config == APROC_CONFIG &&
+ pdata->adc_gpio_disable != NULL)
+ pdata->adc_gpio_disable(hwmon_chan-pdata->num_chan_supported);
+ msm_adc_restore_slot(conv_s, slot);
+
+ return rc;
+}
+
+static int msm_adc_blocking_conversion(struct msm_adc_drv *msm_adc,
+ uint32_t hwmon_chan, struct adc_chan_result *result)
+{
+ struct adc_conv_slot *slot;
+ struct msm_adc_platform_data *pdata =
+ msm_adc_drv->pdev->dev.platform_data;
+ struct msm_adc_channels *channel = &pdata->channel[hwmon_chan];
+
+ channel->adc_access_fn->adc_slot_request(channel->adc_dev_instance,
+ &slot);
+ if (slot) {
+ slot->conv.result.chan = hwmon_chan;
+ /* indicates blocking request to callback handler */
+ slot->blocking = 1;
+ slot->adc_request = START_OF_CONV;
+ slot->chan_path = channel->chan_path_type;
+ slot->chan_adc_config = channel->adc_config_type;
+ slot->chan_adc_calib = channel->adc_calib_type;
+ queue_work(msm_adc_drv->wq, &slot->work);
+
+ wait_for_completion_interruptible(&slot->comp);
+ *result = slot->conv.result;
+ channel->adc_access_fn->adc_restore_slot(
+ channel->adc_dev_instance, slot);
+ return 0;
+ }
+ return -EBUSY;
+}
+
+int32_t adc_channel_open(uint32_t channel, void **h)
+{
+ struct msm_client_data *client;
+ struct msm_adc_drv *msm_adc = msm_adc_drv;
+ struct msm_adc_platform_data *pdata;
+ struct platform_device *pdev;
+ int i = 0;
+
+ if (!msm_adc_drv)
+ return -EFAULT;
+
+#ifdef CONFIG_PMIC8058_XOADC
+ if (pm8058_xoadc_registered() <= 0)
+ return -ENODEV;
+#endif
+ pdata = msm_adc->pdev->dev.platform_data;
+ pdev = msm_adc->pdev;
+
+ while (i < pdata->num_chan_supported) {
+ if (channel == pdata->channel[i].channel_name)
+ break;
+ else
+ i++;
+ }
+
+ if (i == pdata->num_chan_supported)
+ return -EBADF; /* unknown channel */
+
+ client = kzalloc(sizeof(struct msm_client_data), GFP_KERNEL);
+ if (!client) {
+ dev_err(&pdev->dev, "Unable to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ if (!try_module_get(THIS_MODULE)) {
+ kfree(client);
+ return -EACCES;
+ }
+
+ mutex_init(&client->lock);
+ INIT_LIST_HEAD(&client->complete_list);
+ init_waitqueue_head(&client->data_wait);
+ init_waitqueue_head(&client->outst_wait);
+
+ client->online = 1;
+ client->adc_chan = i;
+ *h = (void *)client;
+ return 0;
+}
+
+int32_t adc_channel_close(void *h)
+{
+ struct msm_client_data *client = (struct msm_client_data *)h;
+
+ kfree(client);
+ return 0;
+}
+
+int32_t adc_channel_request_conv(void *h, struct completion *conv_complete_evt)
+{
+ struct msm_client_data *client = (struct msm_client_data *)h;
+ struct msm_adc_platform_data *pdata =
+ msm_adc_drv->pdev->dev.platform_data;
+ struct msm_adc_channels *channel = &pdata->channel[client->adc_chan];
+ struct adc_conv_slot *slot;
+
+ channel->adc_access_fn->adc_slot_request(channel->adc_dev_instance,
+ &slot);
+
+ if (slot) {
+ atomic_inc(&msm_adc_drv->total_outst);
+ mutex_lock(&client->lock);
+ client->num_outstanding++;
+ mutex_unlock(&client->lock);
+
+ slot->conv.result.chan = client->adc_chan;
+ slot->blocking = 0;
+ slot->compk = conv_complete_evt;
+ slot->client = client;
+ slot->adc_request = START_OF_CONV;
+ slot->chan_path = channel->chan_path_type;
+ slot->chan_adc_config = channel->adc_config_type;
+ slot->chan_adc_calib = channel->adc_calib_type;
+ queue_work(msm_adc_drv->wq, &slot->work);
+ return 0;
+ }
+ return -EBUSY;
+}
+
+int32_t adc_channel_read_result(void *h, struct adc_chan_result *chan_result)
+{
+ struct msm_client_data *client = (struct msm_client_data *)h;
+ struct msm_adc_platform_data *pdata =
+ msm_adc_drv->pdev->dev.platform_data;
+ struct msm_adc_channels *channel = pdata->channel;
+ struct adc_conv_slot *slot;
+ int rc = 0;
+
+ mutex_lock(&client->lock);
+
+ slot = list_first_entry(&client->complete_list,
+ struct adc_conv_slot, list);
+ if (!slot) {
+ mutex_unlock(&client->lock);
+ return -ENOMSG;
+ }
+
+ slot->client = NULL;
+ list_del(&slot->list);
+
+ client->num_complete--;
+
+ mutex_unlock(&client->lock);
+
+ *chan_result = slot->conv.result;
+
+ /* restore this slot to reserve */
+ channel[slot->conv.result.chan].adc_access_fn->adc_restore_slot(
+ channel[slot->conv.result.chan].adc_dev_instance, slot);
+
+ return rc;
+}
+
+int32_t adc_calib_request(void *h, struct completion *calib_complete_evt)
+{
+ struct msm_client_data *client = (struct msm_client_data *)h;
+ struct msm_adc_platform_data *pdata =
+ msm_adc_drv->pdev->dev.platform_data;
+ struct msm_adc_channels *channel = &pdata->channel[client->adc_chan];
+ struct adc_conv_slot *slot;
+ int rc, calib_status;
+
+ channel->adc_access_fn->adc_slot_request(channel->adc_dev_instance,
+ &slot);
+ if (slot) {
+ slot->conv.result.chan = client->adc_chan;
+ slot->blocking = 0;
+ slot->compk = calib_complete_evt;
+ slot->adc_request = START_OF_CALIBRATION;
+ slot->chan_path = channel->chan_path_type;
+ slot->chan_adc_config = channel->adc_config_type;
+ slot->chan_adc_calib = channel->adc_calib_type;
+ rc = channel->adc_access_fn->adc_calibrate(
+ channel->adc_dev_instance, slot, &calib_status);
+
+ if (calib_status == CALIB_NOT_REQUIRED) {
+ channel->adc_access_fn->adc_restore_slot(
+ channel->adc_dev_instance, slot);
+ /* client will always wait in case when
+ calibration is not required */
+ complete(calib_complete_evt);
+ } else {
+ atomic_inc(&msm_adc_drv->total_outst);
+ mutex_lock(&client->lock);
+ client->num_outstanding++;
+ mutex_unlock(&client->lock);
+ }
+
+ return rc;
+ }
+ return -EBUSY;
+}
+
+static void msm_rpc_adc_conv_cb(void *context, u32 param,
+ void *evt_buf, u32 len)
+{
+ struct dal_adc_result *result = evt_buf;
+ struct dal_conv_slot *slot = context;
+ struct msm_adc_drv *msm_adc = msm_adc_drv;
+
+ memcpy(&slot->result, result, sizeof(slot->result));
+
+ /* for blocking requests, signal complete */
+ if (slot->blocking)
+ complete(&slot->comp);
+
+ /* for non-blocking requests, add slot to the client completed list */
+ else {
+ struct msm_client_data *client = slot->client;
+
+ mutex_lock(&client->lock);
+
+ list_add(&slot->list, &client->complete_list);
+ client->num_complete++;
+ client->num_outstanding--;
+
+ /*
+ * if the client release has been invoked and this is call
+ * corresponds to the last request, then signal release
+ * to complete.
+ */
+ if (slot->client->online == 0 && client->num_outstanding == 0)
+ wake_up_interruptible_all(&client->outst_wait);
+
+ mutex_unlock(&client->lock);
+
+ wake_up_interruptible_all(&client->data_wait);
+
+ atomic_dec(&msm_adc->total_outst);
+
+ /* verify driver remove has not been invoked */
+ if (atomic_read(&msm_adc->online) == 0 &&
+ atomic_read(&msm_adc->total_outst) == 0)
+ wake_up_interruptible_all(&msm_adc->total_outst_wait);
+ }
+}
+
+void msm_adc_conv_cb(void *context, u32 param,
+ void *evt_buf, u32 len)
+{
+ struct adc_conv_slot *slot = context;
+ struct msm_adc_drv *msm_adc = msm_adc_drv;
+
+ switch (slot->adc_request) {
+ case START_OF_CONV:
+ slot->adc_request = END_OF_CONV;
+ break;
+ case START_OF_CALIBRATION:
+ slot->adc_request = END_OF_CALIBRATION;
+ break;
+ case END_OF_CALIBRATION:
+ case END_OF_CONV:
+ break;
+ }
+ queue_work(msm_adc->wq, &slot->work);
+}
+
+static void msm_adc_teardown_device_conv(struct platform_device *pdev,
+ struct adc_dev *adc_dev)
+{
+ struct dal_conv_state *conv_s = &adc_dev->conv;
+ struct msm_adc_drv *msm_adc = platform_get_drvdata(pdev);
+ struct dal_conv_slot *slot;
+ int i;
+
+ for (i = 0; i < MSM_ADC_DEV_MAX_INFLIGHT; i++) {
+ slot = &conv_s->context[i];
+ if (slot->cb_h) {
+ dalrpc_dealloc_cb(msm_adc->dev_h, slot->cb_h);
+ slot->cb_h = NULL;
+ }
+ }
+}
+
+static void msm_rpc_adc_teardown_device(struct platform_device *pdev,
+ struct adc_dev *adc_dev)
+{
+ struct dal_translation *transl = &adc_dev->transl;
+ int i, num_chans = transl->hwmon_end - transl->hwmon_start + 1;
+
+ if (adc_dev->sens_attr)
+ for (i = 0; i < num_chans; i++)
+ device_remove_file(&pdev->dev,
+ &adc_dev->sens_attr[i].dev_attr);
+
+ msm_adc_teardown_device_conv(pdev, adc_dev);
+
+ kfree(adc_dev->fnames);
+ kfree(adc_dev->sens_attr);
+ kfree(adc_dev);
+}
+
+static void msm_rpc_adc_teardown_devices(struct platform_device *pdev)
+{
+ struct msm_adc_platform_data *pdata = pdev->dev.platform_data;
+ struct msm_adc_drv *msm_adc = platform_get_drvdata(pdev);
+ int i, rc = 0;
+
+ for (i = 0; i < pdata->num_adc; i++) {
+ if (msm_adc->devs[i]) {
+ msm_rpc_adc_teardown_device(pdev, msm_adc->devs[i]);
+ msm_adc->devs[i] = NULL;
+ } else
+ break;
+ }
+
+ if (msm_adc->dev_h) {
+ rc = daldevice_detach(msm_adc->dev_h);
+ if (rc)
+ dev_err(&pdev->dev, "Cannot detach from dal device\n");
+ msm_adc->dev_h = NULL;
+ }
+
+}
+
+static void msm_adc_teardown_device(struct platform_device *pdev,
+ struct msm_adc_drv *msm_adc)
+{
+ struct msm_adc_platform_data *pdata = pdev->dev.platform_data;
+ int i, num_chans = pdata->num_chan_supported;
+
+ if (pdata->num_chan_supported > 0) {
+ if (msm_adc->sens_attr)
+ for (i = 0; i < num_chans; i++)
+ device_remove_file(&pdev->dev,
+ &msm_adc->sens_attr[i].dev_attr);
+ kfree(msm_adc->sens_attr);
+ }
+}
+
+static void msm_adc_teardown(struct platform_device *pdev)
+{
+ struct msm_adc_drv *msm_adc = platform_get_drvdata(pdev);
+
+ if (!msm_adc)
+ return;
+
+ misc_deregister(&msm_adc->misc);
+
+ if (msm_adc->hwmon)
+ hwmon_device_unregister(msm_adc->hwmon);
+
+ msm_rpc_adc_teardown_devices(pdev);
+ msm_adc_teardown_device(pdev, msm_adc);
+
+ kfree(msm_adc);
+ platform_set_drvdata(pdev, NULL);
+}
+
+static int __devinit msm_adc_device_conv_init(struct msm_adc_drv *msm_adc,
+ struct adc_dev *adc_dev)
+{
+ struct platform_device *pdev = msm_adc->pdev;
+ struct dal_conv_state *conv_s = &adc_dev->conv;
+ struct dal_conv_slot *slot = conv_s->context;
+ int rc, i;
+
+ sema_init(&conv_s->slot_count, MSM_ADC_DEV_MAX_INFLIGHT);
+ mutex_init(&conv_s->list_lock);
+ INIT_LIST_HEAD(&conv_s->slots);
+
+ for (i = 0; i < MSM_ADC_DEV_MAX_INFLIGHT; i++) {
+ list_add(&slot->list, &conv_s->slots);
+ slot->cb_h = dalrpc_alloc_cb(msm_adc->dev_h,
+ msm_rpc_adc_conv_cb, slot);
+ if (!slot->cb_h) {
+ dev_err(&pdev->dev, "Unable to allocate DAL callback"
+ " for slot %d\n", i);
+ rc = -ENOMEM;
+ goto dal_err_cb;
+ }
+ init_completion(&slot->comp);
+ slot->idx = i;
+ slot++;
+ }
+
+ return 0;
+
+dal_err_cb:
+ msm_adc_teardown_device_conv(pdev, adc_dev);
+
+ return rc;
+}
+
+static struct sensor_device_attribute msm_rpc_adc_curr_in_attr =
+ SENSOR_ATTR(NULL, S_IRUGO, msm_adc_show_curr, NULL, 0);
+
+static int __devinit msm_rpc_adc_device_init_hwmon(struct platform_device *pdev,
+ struct adc_dev *adc_dev)
+{
+ struct dal_translation *transl = &adc_dev->transl;
+ int i, rc, num_chans = transl->hwmon_end - transl->hwmon_start + 1;
+ const char prefix[] = "curr", postfix[] = "_input";
+ char tmpbuf[5];
+
+ adc_dev->fnames = kzalloc(num_chans * MSM_ADC_MAX_FNAME +
+ num_chans * sizeof(char *), GFP_KERNEL);
+ if (!adc_dev->fnames) {
+ dev_err(&pdev->dev, "Unable to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ adc_dev->sens_attr = kzalloc(num_chans *
+ sizeof(struct sensor_device_attribute), GFP_KERNEL);
+ if (!adc_dev->sens_attr) {
+ dev_err(&pdev->dev, "Unable to allocate memory\n");
+ rc = -ENOMEM;
+ goto hwmon_err_fnames;
+ }
+
+ for (i = 0; i < num_chans; i++) {
+ adc_dev->fnames[i] = (char *)adc_dev->fnames +
+ i * MSM_ADC_MAX_FNAME + num_chans * sizeof(char *);
+ strcpy(adc_dev->fnames[i], prefix);
+ sprintf(tmpbuf, "%d", transl->hwmon_start + i);
+ strcat(adc_dev->fnames[i], tmpbuf);
+ strcat(adc_dev->fnames[i], postfix);
+
+ msm_rpc_adc_curr_in_attr.index = transl->hwmon_start + i;
+ msm_rpc_adc_curr_in_attr.dev_attr.attr.name =
+ adc_dev->fnames[i];
+ memcpy(&adc_dev->sens_attr[i], &msm_rpc_adc_curr_in_attr,
+ sizeof(msm_rpc_adc_curr_in_attr));
+
+ rc = device_create_file(&pdev->dev,
+ &adc_dev->sens_attr[i].dev_attr);
+ if (rc) {
+ dev_err(&pdev->dev, "device_create_file failed for "
+ "dal dev %u chan %d\n",
+ adc_dev->transl.dal_dev_idx, i);
+ goto hwmon_err_sens;
+ }
+ }
+
+ return 0;
+
+hwmon_err_sens:
+ kfree(adc_dev->sens_attr);
+hwmon_err_fnames:
+ kfree(adc_dev->fnames);
+
+ return rc;
+}
+
+static int __devinit msm_rpc_adc_device_init(struct platform_device *pdev)
+{
+ struct msm_adc_platform_data *pdata = pdev->dev.platform_data;
+ struct msm_adc_drv *msm_adc = platform_get_drvdata(pdev);
+ struct adc_dev *adc_dev;
+ struct adc_dev_spec target;
+ int i, rc;
+ int hwmon_cntr = pdata->num_chan_supported;
+
+ for (i = 0; i < pdata->num_adc; i++) {
+ adc_dev = kzalloc(sizeof(struct adc_dev), GFP_KERNEL);
+ if (!adc_dev) {
+ dev_err(&pdev->dev, "Unable to allocate memory\n");
+ rc = -ENOMEM;
+ goto dev_init_err;
+ }
+
+ msm_adc->devs[i] = adc_dev;
+ adc_dev->name = pdata->dev_names[i];
+
+ rc = msm_adc_device_conv_init(msm_adc, adc_dev);
+ if (rc) {
+ dev_err(&pdev->dev, "DAL device[%s] failed conv init\n",
+ adc_dev->name);
+ goto dev_init_err;
+ }
+
+ /* DAL device lookup */
+ rc = msm_adc_getinputproperties(msm_adc, adc_dev->name,
+ &target);
+ if (rc) {
+ dev_err(&pdev->dev, "No such DAL device[%s]\n",
+ adc_dev->name);
+ goto dev_init_err;
+ }
+
+ adc_dev->transl.dal_dev_idx = target.dal.dev_idx;
+ adc_dev->transl.hwmon_dev_idx = i;
+ adc_dev->nchans = target.dal.chan_idx;
+ adc_dev->transl.hwmon_start = hwmon_cntr;
+ adc_dev->transl.hwmon_end = hwmon_cntr + adc_dev->nchans - 1;
+ hwmon_cntr += adc_dev->nchans;
+
+ rc = msm_rpc_adc_device_init_hwmon(pdev, adc_dev);
+ if (rc)
+ goto dev_init_err;
+ }
+
+ return 0;
+
+dev_init_err:
+ msm_rpc_adc_teardown_devices(pdev);
+ return rc;
+}
+
+static int __devinit msm_rpc_adc_init(struct platform_device *pdev1)
+{
+ struct msm_adc_drv *msm_adc = msm_adc_drv;
+ struct platform_device *pdev = msm_adc->pdev;
+ struct msm_adc_platform_data *pdata = pdev->dev.platform_data;
+ int rc = 0;
+
+ dev_dbg(&pdev->dev, "msm_rpc_adc_init called\n");
+
+ if (!pdata) {
+ dev_err(&pdev->dev, "no platform data?\n");
+ return -EINVAL;
+ }
+
+ mutex_init(&msm_adc->prop_lock);
+
+ rc = daldevice_attach(MSM_ADC_DALRPC_DEVICEID,
+ MSM_ADC_DALRPC_PORT_NAME,
+ MSM_ADC_DALRPC_CPU,
+ &msm_adc->dev_h);
+ if (rc) {
+ dev_err(&pdev->dev, "Cannot attach to dal device\n");
+ return rc;
+ }
+
+ dev_dbg(&pdev->dev, "Attach to dal device Succeeded\n");
+
+ rc = msm_rpc_adc_device_init(pdev);
+ if (rc) {
+ dev_err(&pdev->dev, "msm_adc_dev_init failed\n");
+ goto err_cleanup;
+ }
+
+ init_waitqueue_head(&msm_adc->rpc_total_outst_wait);
+ atomic_set(&msm_adc->rpc_online, 1);
+ atomic_set(&msm_adc->rpc_total_outst, 0);
+ epm_init = true;
+ pr_info("msm_adc successfully registered\n");
+
+ return 0;
+
+err_cleanup:
+ msm_rpc_adc_teardown_devices(pdev);
+
+ return rc;
+}
+
+/*
+ * Process the deferred job
+ */
+void msm_adc_wq_work(struct work_struct *work)
+{
+ struct adc_properties *adc_properties;
+ struct adc_conv_slot *slot = container_of(work,
+ struct adc_conv_slot, work);
+ uint32_t idx = slot->conv.result.chan;
+ struct msm_adc_platform_data *pdata =
+ msm_adc_drv->pdev->dev.platform_data;
+ struct msm_adc_channels *channel = &pdata->channel[idx];
+ int32_t adc_code;
+
+ switch (slot->adc_request) {
+ case START_OF_CONV:
+ channel->adc_access_fn->adc_select_chan_and_start_conv(
+ channel->adc_dev_instance, slot);
+ break;
+ case END_OF_CONV:
+ adc_properties = channel->adc_access_fn->adc_get_properties(
+ channel->adc_dev_instance);
+ if (channel->adc_access_fn->adc_read_adc_code)
+ channel->adc_access_fn->adc_read_adc_code(
+ channel->adc_dev_instance, &adc_code);
+ if (channel->chan_processor)
+ channel->chan_processor(adc_code, adc_properties,
+ &slot->chan_properties, &slot->conv.result);
+ /* Intentionally a fall thru here. Calibraton does not need
+ to perform channel processing, etc. However, both
+ end of conversion and end of calibration requires the below
+ fall thru code to be executed. */
+ case END_OF_CALIBRATION:
+ /* for blocking requests, signal complete */
+ if (slot->blocking)
+ complete(&slot->comp);
+ else {
+ struct msm_client_data *client = slot->client;
+
+ mutex_lock(&client->lock);
+
+ if (slot->adc_request == END_OF_CONV) {
+ list_add(&slot->list, &client->complete_list);
+ client->num_complete++;
+ }
+ client->num_outstanding--;
+
+ /*
+ * if the client release has been invoked and this is call
+ * corresponds to the last request, then signal release
+ * to complete.
+ */
+ if (slot->client->online == 0 &&
+ client->num_outstanding == 0)
+ wake_up_interruptible_all(&client->outst_wait);
+
+ mutex_unlock(&client->lock);
+
+ wake_up_interruptible_all(&client->data_wait);
+
+ atomic_dec(&msm_adc_drv->total_outst);
+
+ /* verify driver remove has not been invoked */
+ if (atomic_read(&msm_adc_drv->online) == 0 &&
+ atomic_read(&msm_adc_drv->total_outst) == 0)
+ wake_up_interruptible_all(
+ &msm_adc_drv->total_outst_wait);
+
+ if (slot->compk) /* Kernel space request */
+ complete(slot->compk);
+ if (slot->adc_request == END_OF_CALIBRATION)
+ channel->adc_access_fn->adc_restore_slot(
+ channel->adc_dev_instance, slot);
+ }
+ break;
+ case START_OF_CALIBRATION: /* code here to please code reviewers
+ to satisfy silly compiler warnings */
+ break;
+ }
+}
+
+static struct sensor_device_attribute msm_adc_curr_in_attr =
+ SENSOR_ATTR(NULL, S_IRUGO, msm_adc_show_curr, NULL, 0);
+
+static int __devinit msm_adc_init_hwmon(struct platform_device *pdev,
+ struct msm_adc_drv *msm_adc)
+{
+ struct msm_adc_platform_data *pdata = pdev->dev.platform_data;
+ struct msm_adc_channels *channel = pdata->channel;
+ int i, rc, num_chans = pdata->num_chan_supported;
+
+ if (!channel)
+ return -EINVAL;
+
+ msm_adc->sens_attr = kzalloc(num_chans *
+ sizeof(struct sensor_device_attribute), GFP_KERNEL);
+ if (!msm_adc->sens_attr) {
+ dev_err(&pdev->dev, "Unable to allocate memory\n");
+ rc = -ENOMEM;
+ goto hwmon_err_sens;
+ }
+
+ for (i = 0; i < num_chans; i++) {
+ msm_adc_curr_in_attr.index = i;
+ msm_adc_curr_in_attr.dev_attr.attr.name = channel[i].name;
+ memcpy(&msm_adc->sens_attr[i], &msm_adc_curr_in_attr,
+ sizeof(msm_adc_curr_in_attr));
+
+ rc = device_create_file(&pdev->dev,
+ &msm_adc->sens_attr[i].dev_attr);
+ if (rc) {
+ dev_err(&pdev->dev, "device_create_file failed for "
+ "dal dev %s\n",
+ channel[i].name);
+ goto hwmon_err_sens;
+ }
+ }
+
+ return 0;
+
+hwmon_err_sens:
+ kfree(msm_adc->sens_attr);
+
+ return rc;
+}
+
+static struct platform_driver msm_adc_rpcrouter_remote_driver = {
+ .probe = msm_rpc_adc_init,
+ .driver = {
+ .name = MSM_ADC_DALRPC_PORT_NAME,
+ .owner = THIS_MODULE,
+ },
+};
+
+static int msm_adc_probe(struct platform_device *pdev)
+{
+ struct msm_adc_platform_data *pdata = pdev->dev.platform_data;
+ struct msm_adc_drv *msm_adc;
+ int rc = 0;
+
+ if (!pdata) {
+ dev_err(&pdev->dev, "no platform data?\n");
+ return -EINVAL;
+ }
+
+ msm_adc = kzalloc(sizeof(struct msm_adc_drv), GFP_KERNEL);
+ if (!msm_adc) {
+ dev_err(&pdev->dev, "Unable to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(pdev, msm_adc);
+ msm_adc_drv = msm_adc;
+ msm_adc->pdev = pdev;
+
+ if (pdata->target_hw == MSM_8x60 || pdata->target_hw == FSM_9xxx) {
+ rc = msm_adc_init_hwmon(pdev, msm_adc);
+ if (rc) {
+ dev_err(&pdev->dev, "msm_adc_dev_init failed\n");
+ goto err_cleanup;
+ }
+ }
+
+ msm_adc->hwmon = hwmon_device_register(&pdev->dev);
+ if (IS_ERR(msm_adc->hwmon)) {
+ dev_err(&pdev->dev, "hwmon_device_register failed\n");
+ rc = PTR_ERR(msm_adc->hwmon);
+ goto err_cleanup;
+ }
+
+ msm_adc->misc.name = MSM_ADC_DRIVER_NAME;
+ msm_adc->misc.minor = MISC_DYNAMIC_MINOR;
+ msm_adc->misc.fops = &msm_adc_fops;
+
+ if (misc_register(&msm_adc->misc)) {
+ dev_err(&pdev->dev, "Unable to register misc device!\n");
+ goto err_cleanup;
+ }
+
+ init_waitqueue_head(&msm_adc->total_outst_wait);
+ atomic_set(&msm_adc->online, 1);
+ atomic_set(&msm_adc->total_outst, 0);
+
+ msm_adc->wq = create_singlethread_workqueue("msm_adc");
+ if (!msm_adc->wq)
+ goto err_cleanup;
+
+ if (pdata->num_adc > 0) {
+ if (pdata->target_hw == MSM_8x60)
+ platform_driver_register(
+ &msm_adc_rpcrouter_remote_driver);
+ else
+ msm_rpc_adc_init(pdev);
+ }
+
+ pr_info("msm_adc successfully registered\n");
+
+ return 0;
+
+err_cleanup:
+ msm_adc_teardown(pdev);
+
+ return rc;
+}
+
+static int __devexit msm_adc_remove(struct platform_device *pdev)
+{
+ int rc;
+
+ struct msm_adc_drv *msm_adc = platform_get_drvdata(pdev);
+
+ atomic_set(&msm_adc->online, 0);
+
+ atomic_set(&msm_adc->rpc_online, 0);
+
+ misc_deregister(&msm_adc->misc);
+
+ hwmon_device_unregister(msm_adc->hwmon);
+ msm_adc->hwmon = NULL;
+
+ /*
+ * We may still have outstanding transactions in flight that have not
+ * completed. Make sure they're completed before tearing down.
+ */
+ rc = wait_event_interruptible(msm_adc->total_outst_wait,
+ atomic_read(&msm_adc->total_outst) == 0);
+ if (rc) {
+ pr_err("%s: wait_event_interruptible failed rc = %d\n",
+ __func__, rc);
+ return rc;
+ }
+
+ rc = wait_event_interruptible(msm_adc->rpc_total_outst_wait,
+ atomic_read(&msm_adc->rpc_total_outst) == 0);
+ if (rc) {
+ pr_err("%s: wait_event_interruptible failed rc = %d\n",
+ __func__, rc);
+ return rc;
+ }
+
+ msm_adc_teardown(pdev);
+
+ pr_info("msm_adc unregistered\n");
+
+ return 0;
+}
+
+static struct platform_driver msm_adc_driver = {
+ .probe = msm_adc_probe,
+ .remove = __devexit_p(msm_adc_remove),
+ .driver = {
+ .name = MSM_ADC_DRIVER_NAME,
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init msm_adc_init(void)
+{
+ return platform_driver_register(&msm_adc_driver);
+}
+module_init(msm_adc_init);
+
+static void __exit msm_adc_exit(void)
+{
+ platform_driver_unregister(&msm_adc_driver);
+}
+module_exit(msm_adc_exit);
+
+MODULE_DESCRIPTION("MSM ADC Driver");
+MODULE_ALIAS("platform:msm_adc");
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION("0.1");
diff --git a/drivers/hwmon/wpce775x.c b/drivers/hwmon/wpce775x.c
new file mode 100644
index 0000000..2d00700
--- /dev/null
+++ b/drivers/hwmon/wpce775x.c
@@ -0,0 +1,167 @@
+/* Quanta EC driver for the Winbond Embedded Controller
+ *
+ * Copyright (C) 2009 Quanta Computer Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+
+#define EC_ID_NAME "qci-i2cec"
+#define EC_BUFFER_LEN 16
+#define EC_CMD_POWER_OFF 0xAC
+#define EC_CMD_RESTART 0xAB
+
+static struct i2c_client *g_i2cec_client;
+
+/* General structure to hold the driver data */
+struct i2cec_drv_data {
+ struct i2c_client *i2cec_client;
+ struct work_struct work;
+ char ec_data[EC_BUFFER_LEN+1];
+};
+
+static int __devinit wpce_probe(struct i2c_client *client,
+ const struct i2c_device_id *id);
+static int __devexit wpce_remove(struct i2c_client *kbd);
+
+#ifdef CONFIG_PM
+static int wpce_suspend(struct device *dev)
+{
+ return 0;
+}
+
+static int wpce_resume(struct device *dev)
+{
+ return 0;
+}
+#endif
+
+#ifdef CONFIG_PM
+static struct dev_pm_ops wpce_pm_ops = {
+ .suspend = wpce_suspend,
+ .resume = wpce_resume,
+};
+#endif
+
+static const struct i2c_device_id wpce_idtable[] = {
+ { EC_ID_NAME, 0 },
+ { }
+};
+
+static struct i2c_driver wpce_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = EC_ID_NAME,
+#ifdef CONFIG_PM
+ .pm = &wpce_pm_ops,
+#endif
+ },
+ .probe = wpce_probe,
+ .remove = __devexit_p(wpce_remove),
+ .id_table = wpce_idtable,
+};
+
+static int __devinit wpce_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int err = -ENOMEM;
+ struct i2cec_drv_data *context = 0;
+
+ /* there is no need to call i2c_check_functionality() since it is the
+ client's job to use the interface (I2C vs SMBUS) appropriate for it. */
+ client->driver = &wpce_driver;
+ context = kzalloc(sizeof(struct i2cec_drv_data), GFP_KERNEL);
+ if (!context)
+ return err;
+
+ context->i2cec_client = client;
+ g_i2cec_client = client;
+ i2c_set_clientdata(context->i2cec_client, context);
+
+ return 0;
+}
+
+static int __devexit wpce_remove(struct i2c_client *dev)
+{
+ struct i2cec_drv_data *context = i2c_get_clientdata(dev);
+ g_i2cec_client = NULL;
+ kfree(context);
+
+ return 0;
+}
+
+static int __init wpce_init(void)
+{
+ return i2c_add_driver(&wpce_driver);
+}
+
+static void __exit wpce_exit(void)
+{
+ i2c_del_driver(&wpce_driver);
+}
+
+struct i2c_client *wpce_get_i2c_client(void)
+{
+ return g_i2cec_client;
+}
+EXPORT_SYMBOL_GPL(wpce_get_i2c_client);
+
+void wpce_poweroff(void)
+{
+ if (g_i2cec_client == NULL)
+ return;
+ i2c_smbus_write_byte(g_i2cec_client, EC_CMD_POWER_OFF);
+}
+EXPORT_SYMBOL_GPL(wpce_poweroff);
+
+void wpce_restart(void)
+{
+ if (g_i2cec_client == NULL)
+ return;
+ i2c_smbus_write_byte(g_i2cec_client, EC_CMD_RESTART);
+}
+EXPORT_SYMBOL_GPL(wpce_restart);
+
+int wpce_i2c_transfer(struct i2c_msg *msg)
+{
+ if (g_i2cec_client == NULL)
+ return -1;
+ msg->addr = g_i2cec_client->addr;
+ return i2c_transfer(g_i2cec_client->adapter, msg, 1);
+}
+EXPORT_SYMBOL_GPL(wpce_i2c_transfer);
+
+int wpce_smbus_write_word_data(u8 command, u16 value)
+{
+ if (g_i2cec_client == NULL)
+ return -1;
+ return i2c_smbus_write_word_data(g_i2cec_client, command, value);
+}
+EXPORT_SYMBOL_GPL(wpce_smbus_write_word_data);
+
+int wpce_smbus_write_byte_data(u8 command, u8 value)
+{
+ if (g_i2cec_client == NULL)
+ return -1;
+ return i2c_smbus_write_byte_data(g_i2cec_client, command, value);
+}
+EXPORT_SYMBOL_GPL(wpce_smbus_write_byte_data);
+
+module_init(wpce_init);
+module_exit(wpce_exit);
+
+MODULE_AUTHOR("Quanta Computer Inc.");
+MODULE_DESCRIPTION("Quanta Embedded Controller I2C Bridge Driver");
+MODULE_LICENSE("GPL v2");