| /* | 
 |  * Apple Motion Sensor driver (PMU variant) | 
 |  * | 
 |  * Copyright (C) 2006 Michael Hanselmann (linux-kernel@hansmi.ch) | 
 |  * | 
 |  * This program is free software; you can redistribute it and/or modify | 
 |  * it under the terms of the GNU General Public License as published by | 
 |  * the Free Software Foundation; either version 2 of the License, or | 
 |  * (at your option) any later version. | 
 |  */ | 
 |  | 
 | #include <linux/module.h> | 
 | #include <linux/types.h> | 
 | #include <linux/errno.h> | 
 | #include <linux/init.h> | 
 | #include <linux/adb.h> | 
 | #include <linux/pmu.h> | 
 |  | 
 | #include "ams.h" | 
 |  | 
 | /* Attitude */ | 
 | #define AMS_X			0x00 | 
 | #define AMS_Y			0x01 | 
 | #define AMS_Z			0x02 | 
 |  | 
 | /* Not exactly known, maybe chip vendor */ | 
 | #define AMS_VENDOR		0x03 | 
 |  | 
 | /* Freefall registers */ | 
 | #define AMS_FF_CLEAR		0x04 | 
 | #define AMS_FF_ENABLE		0x05 | 
 | #define AMS_FF_LOW_LIMIT	0x06 | 
 | #define AMS_FF_DEBOUNCE		0x07 | 
 |  | 
 | /* Shock registers */ | 
 | #define AMS_SHOCK_CLEAR		0x08 | 
 | #define AMS_SHOCK_ENABLE	0x09 | 
 | #define AMS_SHOCK_HIGH_LIMIT	0x0a | 
 | #define AMS_SHOCK_DEBOUNCE	0x0b | 
 |  | 
 | /* Global interrupt and power control register */ | 
 | #define AMS_CONTROL		0x0c | 
 |  | 
 | static u8 ams_pmu_cmd; | 
 |  | 
 | static void ams_pmu_req_complete(struct adb_request *req) | 
 | { | 
 | 	complete((struct completion *)req->arg); | 
 | } | 
 |  | 
 | /* Only call this function from task context */ | 
 | static void ams_pmu_set_register(u8 reg, u8 value) | 
 | { | 
 | 	static struct adb_request req; | 
 | 	DECLARE_COMPLETION(req_complete); | 
 |  | 
 | 	req.arg = &req_complete; | 
 | 	if (pmu_request(&req, ams_pmu_req_complete, 4, ams_pmu_cmd, 0x00, reg, value)) | 
 | 		return; | 
 |  | 
 | 	wait_for_completion(&req_complete); | 
 | } | 
 |  | 
 | /* Only call this function from task context */ | 
 | static u8 ams_pmu_get_register(u8 reg) | 
 | { | 
 | 	static struct adb_request req; | 
 | 	DECLARE_COMPLETION(req_complete); | 
 |  | 
 | 	req.arg = &req_complete; | 
 | 	if (pmu_request(&req, ams_pmu_req_complete, 3, ams_pmu_cmd, 0x01, reg)) | 
 | 		return 0; | 
 |  | 
 | 	wait_for_completion(&req_complete); | 
 |  | 
 | 	if (req.reply_len > 0) | 
 | 		return req.reply[0]; | 
 | 	else | 
 | 		return 0; | 
 | } | 
 |  | 
 | /* Enables or disables the specified interrupts */ | 
 | static void ams_pmu_set_irq(enum ams_irq reg, char enable) | 
 | { | 
 | 	if (reg & AMS_IRQ_FREEFALL) { | 
 | 		u8 val = ams_pmu_get_register(AMS_FF_ENABLE); | 
 | 		if (enable) | 
 | 			val |= 0x80; | 
 | 		else | 
 | 			val &= ~0x80; | 
 | 		ams_pmu_set_register(AMS_FF_ENABLE, val); | 
 | 	} | 
 |  | 
 | 	if (reg & AMS_IRQ_SHOCK) { | 
 | 		u8 val = ams_pmu_get_register(AMS_SHOCK_ENABLE); | 
 | 		if (enable) | 
 | 			val |= 0x80; | 
 | 		else | 
 | 			val &= ~0x80; | 
 | 		ams_pmu_set_register(AMS_SHOCK_ENABLE, val); | 
 | 	} | 
 |  | 
 | 	if (reg & AMS_IRQ_GLOBAL) { | 
 | 		u8 val = ams_pmu_get_register(AMS_CONTROL); | 
 | 		if (enable) | 
 | 			val |= 0x80; | 
 | 		else | 
 | 			val &= ~0x80; | 
 | 		ams_pmu_set_register(AMS_CONTROL, val); | 
 | 	} | 
 | } | 
 |  | 
 | static void ams_pmu_clear_irq(enum ams_irq reg) | 
 | { | 
 | 	if (reg & AMS_IRQ_FREEFALL) | 
 | 		ams_pmu_set_register(AMS_FF_CLEAR, 0x00); | 
 |  | 
 | 	if (reg & AMS_IRQ_SHOCK) | 
 | 		ams_pmu_set_register(AMS_SHOCK_CLEAR, 0x00); | 
 | } | 
 |  | 
 | static u8 ams_pmu_get_vendor(void) | 
 | { | 
 | 	return ams_pmu_get_register(AMS_VENDOR); | 
 | } | 
 |  | 
 | static void ams_pmu_get_xyz(s8 *x, s8 *y, s8 *z) | 
 | { | 
 | 	*x = ams_pmu_get_register(AMS_X); | 
 | 	*y = ams_pmu_get_register(AMS_Y); | 
 | 	*z = ams_pmu_get_register(AMS_Z); | 
 | } | 
 |  | 
 | static void ams_pmu_exit(void) | 
 | { | 
 | 	ams_sensor_detach(); | 
 |  | 
 | 	/* Disable interrupts */ | 
 | 	ams_pmu_set_irq(AMS_IRQ_ALL, 0); | 
 |  | 
 | 	/* Clear interrupts */ | 
 | 	ams_pmu_clear_irq(AMS_IRQ_ALL); | 
 |  | 
 | 	ams_info.has_device = 0; | 
 |  | 
 | 	printk(KERN_INFO "ams: Unloading\n"); | 
 | } | 
 |  | 
 | int __init ams_pmu_init(struct device_node *np) | 
 | { | 
 | 	const u32 *prop; | 
 | 	int result; | 
 |  | 
 | 	/* Set implementation stuff */ | 
 | 	ams_info.of_node = np; | 
 | 	ams_info.exit = ams_pmu_exit; | 
 | 	ams_info.get_vendor = ams_pmu_get_vendor; | 
 | 	ams_info.get_xyz = ams_pmu_get_xyz; | 
 | 	ams_info.clear_irq = ams_pmu_clear_irq; | 
 | 	ams_info.bustype = BUS_HOST; | 
 |  | 
 | 	/* Get PMU command, should be 0x4e, but we can never know */ | 
 | 	prop = of_get_property(ams_info.of_node, "reg", NULL); | 
 | 	if (!prop) | 
 | 		return -ENODEV; | 
 |  | 
 | 	ams_pmu_cmd = ((*prop) >> 8) & 0xff; | 
 |  | 
 | 	/* Disable interrupts */ | 
 | 	ams_pmu_set_irq(AMS_IRQ_ALL, 0); | 
 |  | 
 | 	/* Clear interrupts */ | 
 | 	ams_pmu_clear_irq(AMS_IRQ_ALL); | 
 |  | 
 | 	result = ams_sensor_attach(); | 
 | 	if (result < 0) | 
 | 		return result; | 
 |  | 
 | 	/* Set default values */ | 
 | 	ams_pmu_set_register(AMS_FF_LOW_LIMIT, 0x15); | 
 | 	ams_pmu_set_register(AMS_FF_ENABLE, 0x08); | 
 | 	ams_pmu_set_register(AMS_FF_DEBOUNCE, 0x14); | 
 |  | 
 | 	ams_pmu_set_register(AMS_SHOCK_HIGH_LIMIT, 0x60); | 
 | 	ams_pmu_set_register(AMS_SHOCK_ENABLE, 0x0f); | 
 | 	ams_pmu_set_register(AMS_SHOCK_DEBOUNCE, 0x14); | 
 |  | 
 | 	ams_pmu_set_register(AMS_CONTROL, 0x4f); | 
 |  | 
 | 	/* Clear interrupts */ | 
 | 	ams_pmu_clear_irq(AMS_IRQ_ALL); | 
 |  | 
 | 	ams_info.has_device = 1; | 
 |  | 
 | 	/* Enable interrupts */ | 
 | 	ams_pmu_set_irq(AMS_IRQ_ALL, 1); | 
 |  | 
 | 	printk(KERN_INFO "ams: Found PMU based motion sensor\n"); | 
 |  | 
 | 	return 0; | 
 | } |