[PATCH 15/19] sony-laptop: add the ALS interface via SNC

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: Javier Achirica <jachirica@xxxxxxxxx>

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

[marco@xxxxxxxxxx: extend support to recent models, sysfs interface]

Signed-off-by: Javier Achirica <achirica@xxxxxxxxx>
Signed-off-by: Marco Chiappero <marco@xxxxxxxxxx>
Signed-off-by: Mattia Dongili <malattia@xxxxxxxx>
---
 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 majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Kernel Development]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux