162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Apple Motion Sensor driver (PMU variant) 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2006 Michael Hanselmann (linux-kernel@hansmi.ch) 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/module.h> 962306a36Sopenharmony_ci#include <linux/types.h> 1062306a36Sopenharmony_ci#include <linux/errno.h> 1162306a36Sopenharmony_ci#include <linux/init.h> 1262306a36Sopenharmony_ci#include <linux/adb.h> 1362306a36Sopenharmony_ci#include <linux/pmu.h> 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci#include "ams.h" 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci/* Attitude */ 1862306a36Sopenharmony_ci#define AMS_X 0x00 1962306a36Sopenharmony_ci#define AMS_Y 0x01 2062306a36Sopenharmony_ci#define AMS_Z 0x02 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci/* Not exactly known, maybe chip vendor */ 2362306a36Sopenharmony_ci#define AMS_VENDOR 0x03 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci/* Freefall registers */ 2662306a36Sopenharmony_ci#define AMS_FF_CLEAR 0x04 2762306a36Sopenharmony_ci#define AMS_FF_ENABLE 0x05 2862306a36Sopenharmony_ci#define AMS_FF_LOW_LIMIT 0x06 2962306a36Sopenharmony_ci#define AMS_FF_DEBOUNCE 0x07 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci/* Shock registers */ 3262306a36Sopenharmony_ci#define AMS_SHOCK_CLEAR 0x08 3362306a36Sopenharmony_ci#define AMS_SHOCK_ENABLE 0x09 3462306a36Sopenharmony_ci#define AMS_SHOCK_HIGH_LIMIT 0x0a 3562306a36Sopenharmony_ci#define AMS_SHOCK_DEBOUNCE 0x0b 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci/* Global interrupt and power control register */ 3862306a36Sopenharmony_ci#define AMS_CONTROL 0x0c 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_cistatic u8 ams_pmu_cmd; 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_cistatic void ams_pmu_req_complete(struct adb_request *req) 4362306a36Sopenharmony_ci{ 4462306a36Sopenharmony_ci complete((struct completion *)req->arg); 4562306a36Sopenharmony_ci} 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci/* Only call this function from task context */ 4862306a36Sopenharmony_cistatic void ams_pmu_set_register(u8 reg, u8 value) 4962306a36Sopenharmony_ci{ 5062306a36Sopenharmony_ci static struct adb_request req; 5162306a36Sopenharmony_ci DECLARE_COMPLETION(req_complete); 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci req.arg = &req_complete; 5462306a36Sopenharmony_ci if (pmu_request(&req, ams_pmu_req_complete, 4, ams_pmu_cmd, 0x00, reg, value)) 5562306a36Sopenharmony_ci return; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci wait_for_completion(&req_complete); 5862306a36Sopenharmony_ci} 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci/* Only call this function from task context */ 6162306a36Sopenharmony_cistatic u8 ams_pmu_get_register(u8 reg) 6262306a36Sopenharmony_ci{ 6362306a36Sopenharmony_ci static struct adb_request req; 6462306a36Sopenharmony_ci DECLARE_COMPLETION(req_complete); 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_ci req.arg = &req_complete; 6762306a36Sopenharmony_ci if (pmu_request(&req, ams_pmu_req_complete, 3, ams_pmu_cmd, 0x01, reg)) 6862306a36Sopenharmony_ci return 0; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci wait_for_completion(&req_complete); 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci if (req.reply_len > 0) 7362306a36Sopenharmony_ci return req.reply[0]; 7462306a36Sopenharmony_ci else 7562306a36Sopenharmony_ci return 0; 7662306a36Sopenharmony_ci} 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci/* Enables or disables the specified interrupts */ 7962306a36Sopenharmony_cistatic void ams_pmu_set_irq(enum ams_irq reg, char enable) 8062306a36Sopenharmony_ci{ 8162306a36Sopenharmony_ci if (reg & AMS_IRQ_FREEFALL) { 8262306a36Sopenharmony_ci u8 val = ams_pmu_get_register(AMS_FF_ENABLE); 8362306a36Sopenharmony_ci if (enable) 8462306a36Sopenharmony_ci val |= 0x80; 8562306a36Sopenharmony_ci else 8662306a36Sopenharmony_ci val &= ~0x80; 8762306a36Sopenharmony_ci ams_pmu_set_register(AMS_FF_ENABLE, val); 8862306a36Sopenharmony_ci } 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci if (reg & AMS_IRQ_SHOCK) { 9162306a36Sopenharmony_ci u8 val = ams_pmu_get_register(AMS_SHOCK_ENABLE); 9262306a36Sopenharmony_ci if (enable) 9362306a36Sopenharmony_ci val |= 0x80; 9462306a36Sopenharmony_ci else 9562306a36Sopenharmony_ci val &= ~0x80; 9662306a36Sopenharmony_ci ams_pmu_set_register(AMS_SHOCK_ENABLE, val); 9762306a36Sopenharmony_ci } 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci if (reg & AMS_IRQ_GLOBAL) { 10062306a36Sopenharmony_ci u8 val = ams_pmu_get_register(AMS_CONTROL); 10162306a36Sopenharmony_ci if (enable) 10262306a36Sopenharmony_ci val |= 0x80; 10362306a36Sopenharmony_ci else 10462306a36Sopenharmony_ci val &= ~0x80; 10562306a36Sopenharmony_ci ams_pmu_set_register(AMS_CONTROL, val); 10662306a36Sopenharmony_ci } 10762306a36Sopenharmony_ci} 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_cistatic void ams_pmu_clear_irq(enum ams_irq reg) 11062306a36Sopenharmony_ci{ 11162306a36Sopenharmony_ci if (reg & AMS_IRQ_FREEFALL) 11262306a36Sopenharmony_ci ams_pmu_set_register(AMS_FF_CLEAR, 0x00); 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci if (reg & AMS_IRQ_SHOCK) 11562306a36Sopenharmony_ci ams_pmu_set_register(AMS_SHOCK_CLEAR, 0x00); 11662306a36Sopenharmony_ci} 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_cistatic u8 ams_pmu_get_vendor(void) 11962306a36Sopenharmony_ci{ 12062306a36Sopenharmony_ci return ams_pmu_get_register(AMS_VENDOR); 12162306a36Sopenharmony_ci} 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_cistatic void ams_pmu_get_xyz(s8 *x, s8 *y, s8 *z) 12462306a36Sopenharmony_ci{ 12562306a36Sopenharmony_ci *x = ams_pmu_get_register(AMS_X); 12662306a36Sopenharmony_ci *y = ams_pmu_get_register(AMS_Y); 12762306a36Sopenharmony_ci *z = ams_pmu_get_register(AMS_Z); 12862306a36Sopenharmony_ci} 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_cistatic void ams_pmu_exit(void) 13162306a36Sopenharmony_ci{ 13262306a36Sopenharmony_ci ams_sensor_detach(); 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci /* Disable interrupts */ 13562306a36Sopenharmony_ci ams_pmu_set_irq(AMS_IRQ_ALL, 0); 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci /* Clear interrupts */ 13862306a36Sopenharmony_ci ams_pmu_clear_irq(AMS_IRQ_ALL); 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci ams_info.has_device = 0; 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci printk(KERN_INFO "ams: Unloading\n"); 14362306a36Sopenharmony_ci} 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ciint __init ams_pmu_init(struct device_node *np) 14662306a36Sopenharmony_ci{ 14762306a36Sopenharmony_ci const u32 *prop; 14862306a36Sopenharmony_ci int result; 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci /* Set implementation stuff */ 15162306a36Sopenharmony_ci ams_info.of_node = np; 15262306a36Sopenharmony_ci ams_info.exit = ams_pmu_exit; 15362306a36Sopenharmony_ci ams_info.get_vendor = ams_pmu_get_vendor; 15462306a36Sopenharmony_ci ams_info.get_xyz = ams_pmu_get_xyz; 15562306a36Sopenharmony_ci ams_info.clear_irq = ams_pmu_clear_irq; 15662306a36Sopenharmony_ci ams_info.bustype = BUS_HOST; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci /* Get PMU command, should be 0x4e, but we can never know */ 15962306a36Sopenharmony_ci prop = of_get_property(ams_info.of_node, "reg", NULL); 16062306a36Sopenharmony_ci if (!prop) 16162306a36Sopenharmony_ci return -ENODEV; 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci ams_pmu_cmd = ((*prop) >> 8) & 0xff; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci /* Disable interrupts */ 16662306a36Sopenharmony_ci ams_pmu_set_irq(AMS_IRQ_ALL, 0); 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci /* Clear interrupts */ 16962306a36Sopenharmony_ci ams_pmu_clear_irq(AMS_IRQ_ALL); 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci result = ams_sensor_attach(); 17262306a36Sopenharmony_ci if (result < 0) 17362306a36Sopenharmony_ci return result; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci /* Set default values */ 17662306a36Sopenharmony_ci ams_pmu_set_register(AMS_FF_LOW_LIMIT, 0x15); 17762306a36Sopenharmony_ci ams_pmu_set_register(AMS_FF_ENABLE, 0x08); 17862306a36Sopenharmony_ci ams_pmu_set_register(AMS_FF_DEBOUNCE, 0x14); 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci ams_pmu_set_register(AMS_SHOCK_HIGH_LIMIT, 0x60); 18162306a36Sopenharmony_ci ams_pmu_set_register(AMS_SHOCK_ENABLE, 0x0f); 18262306a36Sopenharmony_ci ams_pmu_set_register(AMS_SHOCK_DEBOUNCE, 0x14); 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci ams_pmu_set_register(AMS_CONTROL, 0x4f); 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci /* Clear interrupts */ 18762306a36Sopenharmony_ci ams_pmu_clear_irq(AMS_IRQ_ALL); 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci ams_info.has_device = 1; 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci /* Enable interrupts */ 19262306a36Sopenharmony_ci ams_pmu_set_irq(AMS_IRQ_ALL, 1); 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci printk(KERN_INFO "ams: Found PMU based motion sensor\n"); 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci return 0; 19762306a36Sopenharmony_ci} 198