From: Javier Achirica <[email protected]>

SNC provides an interface to the ALS chip present on a number of recent
Vaio models.

[[email protected]: extend support to recent models, sysfs interface]

Signed-off-by: Javier Achirica <[email protected]>
Signed-off-by: Marco Chiappero <[email protected]>
Signed-off-by: Mattia Dongili <[email protected]>
---
 drivers/platform/x86/sony-laptop.c |  922 ++++++++++++++++++++++++++++++++++++
 1 file changed, 922 insertions(+)

diff --git a/drivers/platform/x86/sony-laptop.c 
b/drivers/platform/x86/sony-laptop.c
index 7f8de3f..4ffcc1f 100644
--- a/drivers/platform/x86/sony-laptop.c
+++ b/drivers/platform/x86/sony-laptop.c
@@ -186,6 +186,42 @@ static int sony_nc_rfkill_setup(struct acpi_device *device,
 static void sony_nc_rfkill_cleanup(void);
 static void sony_nc_rfkill_update(void);
 
+#define ALS_TABLE_SIZE 25
+
+struct als_device_ops {
+       int (*init)(const u8 defaults[]);
+       int (*exit)(void);
+       int (*event_handler)(void);
+       int (*set_power)(unsigned int);
+       int (*get_power)(unsigned int *);
+       int (*get_lux)(unsigned int *, unsigned int *);
+       int (*get_kelvin)(unsigned int *);
+};
+
+struct als_device {
+       int handle;
+       unsigned int power;
+       unsigned int managed;
+
+       unsigned int levels_num;
+       u8 *levels;
+       unsigned int defaults_num;
+       u8 *defaults;
+       u8 parameters[ALS_TABLE_SIZE];
+
+       /* common device operations */
+       const struct als_device_ops *ops;
+
+       /* basic ALS sys interface */
+       unsigned int attrs_num;
+       struct device_attribute attrs[7];
+};
+static struct als_device *als_handle;
+
+static int sony_nc_als_setup(struct platform_device *pd, unsigned int handle);
+static void sony_nc_als_cleanup(struct platform_device *pd);
+static void sony_nc_als_resume(void);
+
 /*********** Input Devices ***********/
 
 #define SONY_LAPTOP_BUF_SIZE   128
@@ -1235,6 +1271,31 @@ static void sony_nc_notify(struct acpi_device *device, 
u32 event)
                        ev_type = 3;
                        break;
 
+               case 0x0143:
+                       sony_call_snc_handle(handle, 0x2000, &result);
+                       /* event reasons are inverted ? */
+                       real_ev = (result & 0x03) == 1 ? 2 : 1;
+                       dprintk("ALS event received (%s change)\n",
+                                       real_ev == 1 ?
+                                       "light" : "backlight");
+
+                       ev_type = 4;
+                       break;
+
+               case 0x012f:
+               case 0x0137:
+                       sony_call_snc_handle(handle, 0x0800, &result);
+                       real_ev = result & 0x03;
+                       dprintk("ALS event received (%s change)\n",
+                                       real_ev == 1 ?
+                                       "light" : "backlight");
+                       if (real_ev == 1 &&
+                                       als_handle->ops->event_handler)
+                               als_handle->ops->event_handler();
+
+                       ev_type = 4;
+                       break;
+
                default:
                        dprintk("Unknown event 0x%x for handle 0x%x\n",
                                        event, handle);
@@ -1323,6 +1384,11 @@ static void sony_nc_function_setup(struct acpi_device 
*device,
                                pr_err("couldn't set up thermal profile 
function (%d)\n",
                                                result);
                        break;
+               case 0x012f:
+                       result = sony_nc_als_setup(pf_device, handle);
+                       if (result)
+                               pr_err("couldn't set up ALS (%d)\n", result);
+                       break;
                case 0x0131:
                        result = sony_nc_highspeed_charging_setup(pf_device);
                        if (result)
@@ -1349,6 +1415,9 @@ static void sony_nc_function_setup(struct acpi_device 
*device,
                        if (result)
                                pr_err("couldn't set up keyboard backlight 
function (%d)\n",
                                                result);
+                       result = sony_nc_als_setup(pf_device, handle);
+                       if (result)
+                               pr_err("couldn't set up ALS (%d)\n", result);
                        break;
                default:
                        continue;
@@ -1390,6 +1459,9 @@ static void sony_nc_function_cleanup(struct 
platform_device *pd)
                case 0x0122:
                        sony_nc_thermal_cleanup(pd);
                        break;
+               case 0x012f:
+                       sony_nc_als_cleanup(pd);
+                       break;
                case 0x0131:
                        sony_nc_highspeed_charging_cleanup(pd);
                        break;
@@ -1404,6 +1476,7 @@ static void sony_nc_function_cleanup(struct 
platform_device *pd)
                case 0x0137:
                case 0x0143:
                        sony_nc_kbd_backlight_cleanup(pd);
+                       sony_nc_als_cleanup(pd);
                        break;
                default:
                        continue;
@@ -1444,9 +1517,13 @@ static void sony_nc_function_resume(void)
                case 0x0135:
                        sony_nc_rfkill_update();
                        break;
+               case 0x012f:
+                       sony_nc_als_resume();
+                       break;
                case 0x0137:
                case 0x0143:
                        sony_nc_kbd_backlight_resume();
+                       sony_nc_als_resume();
                        break;
                default:
                        continue;
@@ -1667,6 +1744,851 @@ static int sony_nc_rfkill_setup(struct acpi_device 
*device,
        return 0;
 }
 
+/* ALS */
+
+/* model specific ALS data and controls
+ * TAOS TSL256x device data
+ */
+#define LUX_SHIFT_BITS         16      /* for non-floating point math */
+/* scale 100000 multiplied fractional coefficients rounding the values */
+#define SCALE(u)       ((((((u64) u) << LUX_SHIFT_BITS) / 10000) + 5) / 10)
+
+#define TSL256X_REG_CTRL       0x00
+#define TSL256X_REG_TIMING     0x01
+#define TSL256X_REG_TLOW       0x02
+#define TSL256X_REG_THIGH      0x04
+#define TSL256X_REG_INT                0x06
+#define TSL256X_REG_ID         0x0a
+#define TSL256X_REG_DATA0      0x0c
+#define TSL256X_REG_DATA1      0x0e
+
+#define TSL256X_POWER_ON       0x03
+#define TSL256X_POWER_OFF      0x00
+
+#define TSL256X_POWER_MASK     0x03
+#define TSL256X_INT_MASK       0x10
+
+struct tsl256x_coeff {
+       u32 ratio;
+       u32 ch0;
+       u32 ch1;
+       u32 ka;
+       s32 kb;
+};
+
+struct tsl256x_data {
+       unsigned int gaintime;
+       unsigned int periods;
+       u8 *defaults;
+       struct tsl256x_coeff const *coeff_table;
+};
+static struct tsl256x_data *tsl256x_handle;
+
+static const struct tsl256x_coeff tsl256x_coeff_fn[] = {
+       {
+               .ratio  = SCALE(12500), /* 0.125 * 2^LUX_SHIFT_BITS  */
+               .ch0    = SCALE(3040),  /* 0.0304 * 2^LUX_SHIFT_BITS */
+               .ch1    = SCALE(2720),  /* 0.0272 * 2^LUX_SHIFT_BITS */
+               .ka     = SCALE(313550000),
+               .kb     = -10651,
+       }, {
+               .ratio  = SCALE(25000), /* 0.250 * 2^LUX_SHIFT_BITS  */
+               .ch0    = SCALE(3250),  /* 0.0325 * 2^LUX_SHIFT_BITS */
+               .ch1    = SCALE(4400),  /* 0.0440 * 2^LUX_SHIFT_BITS */
+               .ka     = SCALE(203390000),
+               .kb     = -2341,
+       }, {
+               .ratio  = SCALE(37500), /* 0.375 * 2^LUX_SHIFT_BITS  */
+               .ch0    = SCALE(3510),  /* 0.0351 * 2^LUX_SHIFT_BITS */
+               .ch1    = SCALE(5440),  /* 0.0544 * 2^LUX_SHIFT_BITS */
+               .ka     = SCALE(152180000),
+               .kb     = 157,
+       }, {
+               .ratio  = SCALE(50000), /* 0.50 * 2^LUX_SHIFT_BITS   */
+               .ch0    = SCALE(3810),  /* 0.0381 * 2^LUX_SHIFT_BITS */
+               .ch1    = SCALE(6240),  /* 0.0624 * 2^LUX_SHIFT_BITS */
+               .ka     = SCALE(163580000),
+               .kb     = -145,
+       }, {
+               .ratio  = SCALE(61000), /* 0.61 * 2^LUX_SHIFT_BITS   */
+               .ch0    = SCALE(2240),  /* 0.0224 * 2^LUX_SHIFT_BITS */
+               .ch1    = SCALE(3100),  /* 0.0310 * 2^LUX_SHIFT_BITS */
+               .ka     = SCALE(180800000),
+               .kb     = -495,
+       }, {
+               .ratio  = SCALE(80000), /* 0.80 * 2^LUX_SHIFT_BITS   */
+               .ch0    = SCALE(1280),  /* 0.0128 * 2^LUX_SHIFT_BITS */
+               .ch1    = SCALE(1530),  /* 0.0153 * 2^LUX_SHIFT_BITS */
+               .ka     = SCALE(197340000),
+               .kb     = -765
+       }, {
+               .ratio  = SCALE(130000),/* 1.3 * 2^LUX_SHIFT_BITS     */
+               .ch0    = SCALE(146),   /* 0.00146 * 2^LUX_SHIFT_BITS */
+               .ch1    = SCALE(112),   /* 0.00112 * 2^LUX_SHIFT_BITS */
+               .ka     = SCALE(182900000),
+               .kb     = -608,
+       }, {
+               .ratio  = UINT_MAX,     /* for higher ratios */
+               .ch0    = 0,
+               .ch1    = 0,
+               .ka     = 0,
+               .kb     = 830,
+       }
+};
+
+static const struct tsl256x_coeff tsl256x_coeff_cs[] = {
+       {
+               .ratio  = SCALE(13000), /* 0.130 * 2^LUX_SHIFT_BITS  */
+               .ch0    = SCALE(3150),  /* 0.0315 * 2^LUX_SHIFT_BITS */
+               .ch1    = SCALE(2620),  /* 0.0262 * 2^LUX_SHIFT_BITS */
+               .ka     = SCALE(300370000),
+               .kb     = -9587,
+       }, {
+               .ratio  = SCALE(26000), /* 0.260 * 2^LUX_SHIFT_BITS  */
+               .ch0    = SCALE(3370),  /* 0.0337 * 2^LUX_SHIFT_BITS */
+               .ch1    = SCALE(4300),  /* 0.0430 * 2^LUX_SHIFT_BITS */
+               .ka     = SCALE(194270000),
+               .kb     = -1824,
+       }, {
+               .ratio  = SCALE(39000), /* 0.390 * 2^LUX_SHIFT_BITS  */
+               .ch0    = SCALE(3630),  /* 0.0363 * 2^LUX_SHIFT_BITS */
+               .ch1    = SCALE(5290),  /* 0.0529 * 2^LUX_SHIFT_BITS */
+               .ka     = SCALE(152520000),
+               .kb     = 145,
+       }, {
+               .ratio  = SCALE(52000), /* 0.520 * 2^LUX_SHIFT_BITS  */
+               .ch0    = SCALE(3920),  /* 0.0392 * 2^LUX_SHIFT_BITS */
+               .ch1    = SCALE(6050),  /* 0.0605 * 2^LUX_SHIFT_BITS */
+               .ka     = SCALE(165960000),
+               .kb     = -200,
+       }, {
+               .ratio  = SCALE(65000), /* 0.650 * 2^LUX_SHIFT_BITS  */
+               .ch0    = SCALE(2290),  /* 0.0229 * 2^LUX_SHIFT_BITS */
+               .ch1    = SCALE(2910),  /* 0.0291 * 2^LUX_SHIFT_BITS */
+               .ka     = SCALE(184800000),
+               .kb     = -566,
+       }, {
+               .ratio  = SCALE(80000), /* 0.800 * 2^LUX_SHIFT_BITS  */
+               .ch0    = SCALE(1570),  /* 0.0157 * 2^LUX_SHIFT_BITS */
+               .ch1    = SCALE(1800),  /* 0.0180 * 2^LUX_SHIFT_BITS */
+               .ka     = SCALE(199220000),
+               .kb     = -791,
+       }, {
+               .ratio  = SCALE(130000),/* 0.130 * 2^LUX_SHIFT_BITS  */
+               .ch0    = SCALE(338),   /* 0.00338 * 2^LUX_SHIFT_BITS */
+               .ch1    = SCALE(260),   /* 0.00260 * 2^LUX_SHIFT_BITS */
+               .ka     = SCALE(182900000),
+               .kb     = -608,
+       }, {
+               .ratio  = UINT_MAX,     /* for higher ratios */
+               .ch0    = 0,
+               .ch1    = 0,
+               .ka     = 0,
+               .kb     = 830,
+       }
+};
+
+/* TAOS helper & control functions */
+static inline int tsl256x_exec_writebyte(unsigned int reg,
+                                               unsigned int const *value)
+{
+       unsigned int result;
+
+       return (sony_call_snc_handle(als_handle->handle, (*value << 0x18) |
+               (reg << 0x10) | 0x800500, &result) || !(result & 0x01))
+               ? -EIO : 0;
+}
+
+static inline int tsl256x_exec_writeword(unsigned int reg,
+                                               unsigned int const *value)
+{
+       u8 result[1];
+       int offset = sony_find_snc_handle(als_handle->handle);
+       u64 arg = (*value << 0x18) | (reg << 0x10) | 0xA00700 | offset;
+
+       /* using sony_call_snc_handle_buffer due to possible input overflows */
+       if (sony_nc_buffer_call(sony_nc_acpi_handle, "SN06", &arg,
+                               result, 1) < 0)
+               return -EIO;
+
+       return result[0] & 0x01;
+}
+
+static inline int tsl256x_exec_readbyte(unsigned int reg, unsigned int *result)
+{
+       int arg = (reg << 0x10) | 0x800400;
+
+       if (sony_call_snc_handle(als_handle->handle, arg, result) < 0)
+               return -EIO;
+
+       if (!(*result & 0x01))
+               return -EINVAL;
+
+       *result = (*result >> 0x08) & 0xFF;
+
+       return 0;
+}
+
+static inline int tsl256x_exec_readword(unsigned int reg, unsigned int *result)
+{
+       int arg = (reg << 0x10) | 0xA00600;
+
+       if (sony_call_snc_handle(als_handle->handle, arg, result) < 0)
+               return -EIO;
+
+       if (!(*result & 0x01))
+               return -EINVAL;
+
+       *result = (*result >> 0x08) & 0xFFFF;
+
+       return 0;
+}
+
+static int tsl256x_interrupt_ctrls(unsigned int *interrupt,
+                                       unsigned int *periods)
+{
+       unsigned int value, result;
+
+       /* if no interrupt parameter, retrieve interrupt status */
+       if (!interrupt) {
+               if (tsl256x_exec_readbyte(TSL256X_REG_INT, &result))
+                       return -EIO;
+
+               value = (result & TSL256X_INT_MASK);
+       } else {
+               value = *interrupt << 0x04;
+       }
+
+       /* if no periods provided use the last one set */
+       value |= (periods ? *periods : tsl256x_handle->periods);
+
+       if (tsl256x_exec_writebyte(TSL256X_REG_INT, &value))
+               return -EIO;
+
+       if (periods)
+               tsl256x_handle->periods = *periods;
+
+       return 0;
+}
+
+static int tsl256x_setup(void)
+{
+       unsigned int interr = 1, zero = 0;
+
+       /*
+        *   reset the threshold settings to trigger an event as soon
+        *   as the event goes on, forcing a backlight adaptation to
+        *   the current lighting conditions
+        */
+       tsl256x_exec_writeword(TSL256X_REG_TLOW, &zero);
+       tsl256x_exec_writeword(TSL256X_REG_THIGH, &zero);
+
+       /* set gain and time */
+       if (tsl256x_exec_writebyte(TSL256X_REG_TIMING,
+                               &tsl256x_handle->gaintime))
+               return -EIO;
+
+       /* restore persistence value and enable the interrupt generation */
+       if (tsl256x_interrupt_ctrls(&interr, &tsl256x_handle->periods))
+               return -EIO;
+
+       return 0;
+}
+
+static int tsl256x_set_power(unsigned int status)
+{
+       int ret;
+
+       if (status) {
+               ret = tsl256x_setup();
+               if (ret)
+                       return ret;
+       }
+
+       status = status ? TSL256X_POWER_ON : TSL256X_POWER_OFF;
+       ret = tsl256x_exec_writebyte(TSL256X_REG_CTRL, &status);
+
+       return ret;
+}
+
+static int tsl256x_get_power(unsigned int *status)
+{
+       if (tsl256x_exec_readbyte(TSL256X_REG_CTRL, status))
+               return -EIO;
+
+       *status = ((*status & TSL256X_POWER_MASK) == TSL256X_POWER_ON) ? 1 : 0;
+
+       return 0;
+}
+
+static int tsl256x_get_raw_data(unsigned int *ch0, unsigned int *ch1)
+{
+       if (!ch0)
+               return -1;
+
+       if (tsl256x_exec_readword(TSL256X_REG_DATA0, ch0))
+               return -EIO;
+
+       if (ch1) {
+               if (tsl256x_exec_readword(TSL256X_REG_DATA1, ch1))
+                       return -EIO;
+       }
+
+       return 0;
+}
+
+static int tsl256x_set_thresholds(const unsigned int *ch0)
+{
+       unsigned int tlow, thigh;
+
+       tlow = (*ch0 * tsl256x_handle->defaults[0]) / 100;
+       thigh = ((*ch0 * tsl256x_handle->defaults[1]) / 100) + 1;
+
+       if (thigh > 0xffff)
+               thigh = 0xffff;
+
+       if (tsl256x_exec_writeword(TSL256X_REG_TLOW, &tlow) ||
+               tsl256x_exec_writeword(TSL256X_REG_THIGH, &thigh))
+               return -EIO;
+
+       return 0;
+}
+
+#define MAX_LUX 1500
+static void tsl256x_calculate_lux(const u32 ch0, const u32 ch1,
+                               unsigned int *integ, unsigned int *fract)
+{
+       /* the raw output from the sensor is just a "count" value, as it is the
+        * result of the integration of the analog sensor signal, the
+        * counts-to-lux curve (and its approximation can be found on the
+        * datasheet.
+        */
+       const struct tsl256x_coeff *coeff = tsl256x_handle->coeff_table;
+       u32 ratio, temp, integer, fractional;
+
+       if (ch0 >= 65535 || ch1 >= 65535)
+               goto saturation;
+
+       /* STEP 1: ratio calculation, for ch0 & ch1 coeff selection */
+
+       /* protect against division by 0 */
+       ratio = ch0 ? ((ch1 << (LUX_SHIFT_BITS + 1)) / ch0) : UINT_MAX;
+       /* round the ratio value */
+       ratio = (ratio + 1) >> 1;
+
+       /* coeff selection rule */
+       while (coeff->ratio < ratio)
+               coeff++;
+
+       /* STEP 2: lux calculation formula using the right coeffcients */
+       temp = (ch0 * coeff->ch0) - (ch1 * coeff->ch1);
+       /* the sensor is placed under a plastic or glass cover which filters
+          a certain ammount of light (depending on that particular material).
+          To have an accurate reading, we need to compensate for this loss,
+          multiplying for compensation parameter, taken from the DSDT.
+       */
+       temp *= tsl256x_handle->defaults[3] / 10;
+
+       /* STEP 3: separate integer and fractional part */
+       /* remove the integer part and multiply for the 10^N, N decimals  */
+       fractional = (temp % (1 << LUX_SHIFT_BITS)) * 100; /* two decimals */
+       /* scale down the value */
+       fractional >>= LUX_SHIFT_BITS;
+
+       /* strip off fractional portion to obtain the integer part */
+       integer = temp >> LUX_SHIFT_BITS;
+
+       if (integer > MAX_LUX)
+               goto saturation;
+
+       *integ = integer;
+       *fract = fractional;
+
+       return;
+
+saturation:
+       *integ = MAX_LUX;
+       *fract = 0;
+}
+
+static void tsl256x_calculate_kelvin(const u32 *ch0, const u32 *ch1,
+                                       unsigned int *temperature)
+{
+       const struct tsl256x_coeff *coeff = tsl256x_handle->coeff_table;
+       u32 ratio;
+
+       /* protect against division by 0 */
+       ratio = *ch0 ? ((*ch1 << (LUX_SHIFT_BITS + 1)) / *ch0) : UINT_MAX;
+       /* round the ratio value */
+       ratio = (ratio + 1) >> 1;
+
+       /* coeff selection rule */
+       while (coeff->ratio < ratio)
+               coeff++;
+
+       *temperature = ratio ? coeff->ka / ratio + coeff->kb : 0;
+}
+
+static int tsl256x_get_lux(unsigned int *integ, unsigned int *fract)
+{
+       int ret = 0;
+       unsigned int ch0, ch1;
+
+       if (!integ || !fract)
+               return -1;
+
+       ret = tsl256x_get_raw_data(&ch0, &ch1);
+       if (!ret)
+               tsl256x_calculate_lux(ch0, ch1, integ, fract);
+
+       return ret;
+}
+
+static int tsl256x_get_kelvin(unsigned int *temperature)
+{
+       int ret = -1;
+       unsigned int ch0, ch1;
+
+       if (!temperature)
+               return ret;
+
+       ret = tsl256x_get_raw_data(&ch0, &ch1);
+       if (!ret)
+               tsl256x_calculate_kelvin(&ch0, &ch1, temperature);
+
+       return ret;
+}
+
+static int tsl256x_get_id(char *model, unsigned int *id, bool *cs)
+{
+       int ret;
+       unsigned int result;
+       char *name = NULL;
+       bool unknown = false;
+       bool type_cs = false;
+
+       ret = tsl256x_exec_readbyte(TSL256X_REG_ID, &result);
+       if (ret)
+               return ret;
+
+       switch ((result >> 0x04) & 0x0F) {
+       case 11:
+               name = "TAOS TSL2569";
+               break;
+       case 5:
+               name = "TAOS TSL2561";
+               break;
+       case 3:
+               name = "TAOS TSL2563";
+               break;
+       case 0:
+               type_cs = true;
+               name = "TAOS TSL2560CS";
+               break;
+       default:
+               unknown = true;
+               break;
+       }
+
+       if (id)
+               *id = result;
+       if (cs)
+               *cs = type_cs;
+       if (model && name)
+               strcpy(model, name);
+
+       return unknown;
+}
+
+static int tsl256x_event_handler(void)
+{
+       unsigned int ch0, interr = 1;
+
+       /* clear the notification */
+       sony_call_snc_handle(als_handle->handle, 0x04C60500, &interr);
+
+       /* read the raw data */
+       tsl256x_get_raw_data(&ch0, NULL);
+
+       /* set the thresholds */
+       tsl256x_set_thresholds(&ch0);
+
+       /* enable interrupt */
+       tsl256x_interrupt_ctrls(&interr, NULL);
+
+       return 0;
+}
+
+static int tsl256x_init(const u8 defaults[])
+{
+       unsigned int id = 0;
+       int ret = 0;
+       bool cs = false; /* if CS package choose CS coefficients */
+       char model[64];
+
+       /* detect the device */
+       ret = tsl256x_get_id(model, &id, &cs);
+       if (ret < 0)
+               return ret;
+       if (ret) {
+               dprintk("unsupported ALS model %u rev%u\n", id >> 4, id & 0x0F);
+               return ret;
+       } else {
+               dprintk("ALS model %u rev%u (%s)\n", id >> 4, id & 0x0F, model);
+       }
+
+       tsl256x_handle = kzalloc(sizeof(struct tsl256x_data), GFP_KERNEL);
+       if (!tsl256x_handle)
+               return -ENOMEM;
+
+       tsl256x_handle->defaults = kzalloc(sizeof(u8) * 4, GFP_KERNEL);
+       if (!tsl256x_handle->defaults) {
+               kfree(tsl256x_handle);
+               return -ENOMEM;
+       }
+
+       /* populate the device data */
+       tsl256x_handle->defaults[0] = defaults[3];  /* low threshold % */
+       tsl256x_handle->defaults[1] = defaults[4];  /* high threshold % */
+       tsl256x_handle->defaults[2] = defaults[9];  /* sensor interrupt rate */
+       tsl256x_handle->defaults[3] = defaults[10]; /* light compensat. rate */
+       tsl256x_handle->gaintime = 0x12;
+       tsl256x_handle->periods = defaults[9];
+       tsl256x_handle->coeff_table = cs ? tsl256x_coeff_cs : tsl256x_coeff_fn;
+
+       ret = tsl256x_setup();
+
+       return ret;
+}
+
+static int tsl256x_exit(void)
+{
+       unsigned int interr = 0, periods = tsl256x_handle->defaults[2];
+
+       /* disable the interrupt generation, restore defaults */
+       tsl256x_interrupt_ctrls(&interr, &periods);
+
+       tsl256x_handle->coeff_table = NULL;
+       kfree(tsl256x_handle->defaults);
+       tsl256x_handle->defaults = NULL;
+       kfree(tsl256x_handle);
+
+       return 0;
+}
+
+/* TAOS TSL256x specific ops */
+static const struct als_device_ops tsl256x_ops = {
+       .init = tsl256x_init,
+       .exit = tsl256x_exit,
+       .event_handler = tsl256x_event_handler,
+       .set_power = tsl256x_set_power,
+       .get_power = tsl256x_get_power,
+       .get_lux = tsl256x_get_lux,
+       .get_kelvin = tsl256x_get_kelvin,
+};
+
+/* unknown ALS sensors controlled by the EC present on newer Vaios */
+static inline int ngals_get_raw_data(unsigned int *data)
+{
+       if (sony_call_snc_handle(als_handle->handle, 0x1000, data))
+               return -EIO;
+
+       return 0;
+}
+
+static int ngals_get_lux(unsigned int *integ, unsigned int *fract)
+{
+       unsigned int data;
+
+       if (sony_call_snc_handle(als_handle->handle, 0x1000, &data))
+               return -EIO;
+
+       /* if we have a valid lux data */
+       if (!!(data & 0xff0000) == 0x01) {
+               *integ = 0xffff & data;
+               *fract = 0;
+       } else {
+               return -1;
+       }
+
+       return 0;
+}
+
+static const struct als_device_ops ngals_ops = {
+       .init = NULL,
+       .exit = NULL,
+       .event_handler = NULL,
+       .set_power = NULL,
+       .get_power = NULL,
+       .get_lux = ngals_get_lux,
+       .get_kelvin = NULL,
+};
+
+static int __sony_nc_als_power_set(unsigned int status)
+{
+       unsigned int cmd, result;
+
+       if (als_handle->ops->set_power) {
+               if (als_handle->ops->set_power(status))
+                       return -EIO;
+               else
+                       als_handle->power = status;
+       }
+
+
+       /*  turn on/off the event notification */
+       cmd = als_handle->handle == 0x0143 ? 0x2200 : 0x0900;
+       if (sony_call_snc_handle(als_handle->handle,
+                               (status << 0x10) | cmd, &result))
+               return -EIO;
+
+       return 0;
+}
+
+/* ALS sys interface */
+static ssize_t sony_nc_als_power_show(struct device *dev,
+               struct device_attribute *attr, char *buffer)
+{
+       ssize_t count = 0;
+       int status;
+
+       if (!als_handle->ops->get_power)
+               status = 1;
+       /* TODO: can't use the cached value instead? */
+       else if (als_handle->ops->get_power(&status))
+               return -EIO;
+
+       count = snprintf(buffer, PAGE_SIZE, "%d\n", status);
+
+       return count;
+}
+
+static ssize_t sony_nc_als_power_store(struct device *dev,
+               struct device_attribute *attr,
+               const char *buffer, size_t count)
+{
+       int ret;
+       unsigned long value;
+
+       if (count > 31)
+               return -EINVAL;
+
+       if (kstrtoul(buffer, 10, &value) || value > 1)
+               return -EINVAL;
+
+       /* no action if already set */
+       if (value == als_handle->power)
+               return count;
+
+       ret = __sony_nc_als_power_set(value);
+       if (ret)
+               return ret;
+
+       return count;
+}
+
+static ssize_t sony_nc_als_lux_show(struct device *dev,
+               struct device_attribute *attr, char *buffer)
+{
+       ssize_t count = 0;
+       unsigned int integ = 0, fract = 0;
+
+       if (als_handle->power)
+               /* als_handle->ops->get_lux is mandatory, no check */
+               als_handle->ops->get_lux(&integ, &fract);
+
+       count = snprintf(buffer, PAGE_SIZE, "%u.%.2u\n", integ, fract);
+
+       return count;
+}
+
+static ssize_t sony_nc_als_parameters_show(struct device *dev,
+               struct device_attribute *attr, char *buffer)
+{
+       ssize_t count = 0;
+       unsigned int i, num;
+       u8 *list;
+
+       if (!strcmp(attr->attr.name, "als_defaults")) {
+               list = als_handle->defaults;
+               num = als_handle->defaults_num;
+       } else {
+               /* als_backlight_levels */
+               list = als_handle->levels;
+               num = als_handle->levels_num;
+       }
+
+       for (i = 0; i < num; i++)
+               count += snprintf(buffer + count, PAGE_SIZE - count,
+                               "0x%.2x ", list[i]);
+
+       count += snprintf(buffer + count, PAGE_SIZE - count, "\n");
+
+       return count;
+}
+
+static ssize_t sony_nc_als_kelvin_show(struct device *dev,
+               struct device_attribute *attr, char *buffer)
+{
+       ssize_t count = 0;
+       unsigned int kelvin = 0;
+
+       if (als_handle->ops->get_kelvin && als_handle->power)
+               als_handle->ops->get_kelvin(&kelvin);
+
+       count = snprintf(buffer, PAGE_SIZE, "%d\n", kelvin);
+
+       return count;
+}
+
+
+/* ALS attach/detach functions */
+static int sony_nc_als_setup(struct platform_device *pd, unsigned int handle)
+{
+       unsigned int result;
+       int i = 0;
+       u64 arg;
+
+       /* check the device presence */
+       if (handle == 0x0137) {
+               if (sony_call_snc_handle(als_handle->handle, 0xB00, &result))
+                       return -EIO;
+
+               if (!(result & 0x01))
+                       /* no ALS controls */
+                       return 0;
+       }
+
+       als_handle = kzalloc(sizeof(struct als_device), GFP_KERNEL);
+       if (!als_handle)
+               return -ENOMEM;
+
+       als_handle->handle = handle;
+
+       /* set model specific data
+        * if handle 0x012f or 0x0137 use tsl256x_ops, else new als controls
+        */
+       if (handle == 0x0143) {
+               als_handle->ops = &ngals_ops;
+               als_handle->levels_num = 16;
+               als_handle->defaults_num = 9;
+       } else {
+               als_handle->ops = &tsl256x_ops;
+               als_handle->levels_num = 9;
+               als_handle->defaults_num = 13;
+       }
+       /* backlight levels are the first levels_num values, the remaining
+        * defaults_num values are default settings for als regulation
+        */
+       als_handle->levels = als_handle->parameters;
+       als_handle->defaults = als_handle->parameters + als_handle->levels_num;
+
+       /* get power state */
+       if (als_handle->ops->get_power) {
+               if (als_handle->ops->get_power(&als_handle->power))
+                       pr_warn("unable to retrieve the power status\n");
+       }
+
+       /* get ALS parameters */
+       arg = sony_find_snc_handle(handle);
+       if (sony_nc_buffer_call(sony_nc_acpi_handle, "SN06", &arg,
+               als_handle->parameters, ALS_TABLE_SIZE) < 0)
+               goto nosensor;
+
+       /* initial device configuration */
+       if (als_handle->ops->init) {
+               result = als_handle->ops->init(als_handle->defaults);
+               if (result)
+                       goto nosensor;
+       }
+
+       /* set up the sys interface */
+
+       /* lux equivalent value */
+       sysfs_attr_init(&als_handle->attrs[0].attr);
+       als_handle->attrs[0].attr.name = "als_lux";
+       als_handle->attrs[0].attr.mode = S_IRUGO;
+       als_handle->attrs[0].show = sony_nc_als_lux_show;
+       als_handle->attrs_num++;
+
+       /* ALS default parameters */
+       sysfs_attr_init(&als_handle->attrs[1].attr);
+       als_handle->attrs[1].attr.name = "als_defaults";
+       als_handle->attrs[1].attr.mode = S_IRUGO;
+       als_handle->attrs[1].show = sony_nc_als_parameters_show;
+       als_handle->attrs_num++;
+
+       if (als_handle->ops->get_power || als_handle->ops->set_power) {
+               /* als power control */
+               sysfs_attr_init(&als_handle->attrs[als_handle->attrs_num].attr);
+               als_handle->attrs[als_handle->attrs_num].attr.name =
+                       "als_power";
+               als_handle->attrs[als_handle->attrs_num].attr.mode =
+                       S_IRUGO | S_IWUSR;
+               als_handle->attrs[als_handle->attrs_num].show =
+                       sony_nc_als_power_show;
+               als_handle->attrs[als_handle->attrs_num].store =
+                       sony_nc_als_power_store;
+               als_handle->attrs_num++;
+       }
+
+       if (als_handle->ops->get_kelvin) {
+               /* light temperature */
+               sysfs_attr_init(&als_handle->attrs[als_handle->attrs_num].attr);
+               als_handle->attrs[als_handle->attrs_num].attr.name =
+                       "als_kelvin";
+               als_handle->attrs[als_handle->attrs_num].attr.mode = S_IRUGO;
+               als_handle->attrs[als_handle->attrs_num].show =
+                       sony_nc_als_kelvin_show;
+               als_handle->attrs_num++;
+       }
+
+       for (; i < als_handle->attrs_num; i++) {
+               result = device_create_file(&pd->dev, &als_handle->attrs[i]);
+               if (result)
+                       goto attrserror;
+       }
+
+       return 0;
+
+attrserror:
+       for (; i > 0; i--)
+               device_remove_file(&pd->dev, &als_handle->attrs[i]);
+nosensor:
+       kfree(als_handle);
+       als_handle = NULL;
+
+       return result;
+}
+
+static void sony_nc_als_resume(void)
+{
+       if (als_handle->power)
+               __sony_nc_als_power_set(1);
+}
+
+static void sony_nc_als_cleanup(struct platform_device *pd)
+{
+       if (als_handle) {
+               int i;
+
+               for (i = 0; i < als_handle->attrs_num; i++)
+                       device_remove_file(&pd->dev, &als_handle->attrs[i]);
+
+               if (als_handle->power)
+                       if (__sony_nc_als_power_set(0))
+                               pr_info("ALS power off failed\n");
+
+               if (als_handle->ops->exit)
+                       if (als_handle->ops->exit())
+                               pr_info("ALS device clean-up failed\n");
+
+               kfree(als_handle);
+               als_handle = NULL;
+       }
+}
+/* end ALS code */
+
 /* Keyboard backlight feature */
 struct kbd_backlight {
        unsigned int handle;
-- 
1.7.10

--
To unsubscribe from this list: send the line "unsubscribe platform-driver-x86" 
in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to