162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Apple Motion Sensor driver 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2005 Stelian Pop (stelian@popies.net) 662306a36Sopenharmony_ci * Copyright (C) 2006 Michael Hanselmann (linux-kernel@hansmi.ch) 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/module.h> 1062306a36Sopenharmony_ci#include <linux/types.h> 1162306a36Sopenharmony_ci#include <linux/errno.h> 1262306a36Sopenharmony_ci#include <linux/init.h> 1362306a36Sopenharmony_ci#include <linux/of_platform.h> 1462306a36Sopenharmony_ci#include <asm/pmac_pfunc.h> 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci#include "ams.h" 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci/* There is only one motion sensor per machine */ 1962306a36Sopenharmony_cistruct ams ams_info; 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_cistatic bool verbose; 2262306a36Sopenharmony_cimodule_param(verbose, bool, 0644); 2362306a36Sopenharmony_ciMODULE_PARM_DESC(verbose, "Show free falls and shocks in kernel output"); 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci/* Call with ams_info.lock held! */ 2662306a36Sopenharmony_civoid ams_sensors(s8 *x, s8 *y, s8 *z) 2762306a36Sopenharmony_ci{ 2862306a36Sopenharmony_ci u32 orient = ams_info.vflag? ams_info.orient1 : ams_info.orient2; 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci if (orient & 0x80) 3162306a36Sopenharmony_ci /* X and Y swapped */ 3262306a36Sopenharmony_ci ams_info.get_xyz(y, x, z); 3362306a36Sopenharmony_ci else 3462306a36Sopenharmony_ci ams_info.get_xyz(x, y, z); 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci if (orient & 0x04) 3762306a36Sopenharmony_ci *z = ~(*z); 3862306a36Sopenharmony_ci if (orient & 0x02) 3962306a36Sopenharmony_ci *y = ~(*y); 4062306a36Sopenharmony_ci if (orient & 0x01) 4162306a36Sopenharmony_ci *x = ~(*x); 4262306a36Sopenharmony_ci} 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_cistatic ssize_t ams_show_current(struct device *dev, 4562306a36Sopenharmony_ci struct device_attribute *attr, char *buf) 4662306a36Sopenharmony_ci{ 4762306a36Sopenharmony_ci s8 x, y, z; 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci mutex_lock(&ams_info.lock); 5062306a36Sopenharmony_ci ams_sensors(&x, &y, &z); 5162306a36Sopenharmony_ci mutex_unlock(&ams_info.lock); 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci return sysfs_emit(buf, "%d %d %d\n", x, y, z); 5462306a36Sopenharmony_ci} 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_cistatic DEVICE_ATTR(current, S_IRUGO, ams_show_current, NULL); 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_cistatic void ams_handle_irq(void *data) 5962306a36Sopenharmony_ci{ 6062306a36Sopenharmony_ci enum ams_irq irq = *((enum ams_irq *)data); 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci spin_lock(&ams_info.irq_lock); 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci ams_info.worker_irqs |= irq; 6562306a36Sopenharmony_ci schedule_work(&ams_info.worker); 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci spin_unlock(&ams_info.irq_lock); 6862306a36Sopenharmony_ci} 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_cistatic enum ams_irq ams_freefall_irq_data = AMS_IRQ_FREEFALL; 7162306a36Sopenharmony_cistatic struct pmf_irq_client ams_freefall_client = { 7262306a36Sopenharmony_ci .owner = THIS_MODULE, 7362306a36Sopenharmony_ci .handler = ams_handle_irq, 7462306a36Sopenharmony_ci .data = &ams_freefall_irq_data, 7562306a36Sopenharmony_ci}; 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_cistatic enum ams_irq ams_shock_irq_data = AMS_IRQ_SHOCK; 7862306a36Sopenharmony_cistatic struct pmf_irq_client ams_shock_client = { 7962306a36Sopenharmony_ci .owner = THIS_MODULE, 8062306a36Sopenharmony_ci .handler = ams_handle_irq, 8162306a36Sopenharmony_ci .data = &ams_shock_irq_data, 8262306a36Sopenharmony_ci}; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci/* Once hard disk parking is implemented in the kernel, this function can 8562306a36Sopenharmony_ci * trigger it. 8662306a36Sopenharmony_ci */ 8762306a36Sopenharmony_cistatic void ams_worker(struct work_struct *work) 8862306a36Sopenharmony_ci{ 8962306a36Sopenharmony_ci unsigned long flags; 9062306a36Sopenharmony_ci u8 irqs_to_clear; 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci mutex_lock(&ams_info.lock); 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci spin_lock_irqsave(&ams_info.irq_lock, flags); 9562306a36Sopenharmony_ci irqs_to_clear = ams_info.worker_irqs; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci if (ams_info.worker_irqs & AMS_IRQ_FREEFALL) { 9862306a36Sopenharmony_ci if (verbose) 9962306a36Sopenharmony_ci printk(KERN_INFO "ams: freefall detected!\n"); 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci ams_info.worker_irqs &= ~AMS_IRQ_FREEFALL; 10262306a36Sopenharmony_ci } 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci if (ams_info.worker_irqs & AMS_IRQ_SHOCK) { 10562306a36Sopenharmony_ci if (verbose) 10662306a36Sopenharmony_ci printk(KERN_INFO "ams: shock detected!\n"); 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci ams_info.worker_irqs &= ~AMS_IRQ_SHOCK; 10962306a36Sopenharmony_ci } 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci spin_unlock_irqrestore(&ams_info.irq_lock, flags); 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci ams_info.clear_irq(irqs_to_clear); 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci mutex_unlock(&ams_info.lock); 11662306a36Sopenharmony_ci} 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci/* Call with ams_info.lock held! */ 11962306a36Sopenharmony_ciint ams_sensor_attach(void) 12062306a36Sopenharmony_ci{ 12162306a36Sopenharmony_ci int result; 12262306a36Sopenharmony_ci const u32 *prop; 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci /* Get orientation */ 12562306a36Sopenharmony_ci prop = of_get_property(ams_info.of_node, "orientation", NULL); 12662306a36Sopenharmony_ci if (!prop) 12762306a36Sopenharmony_ci return -ENODEV; 12862306a36Sopenharmony_ci ams_info.orient1 = *prop; 12962306a36Sopenharmony_ci ams_info.orient2 = *(prop + 1); 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci /* Register freefall interrupt handler */ 13262306a36Sopenharmony_ci result = pmf_register_irq_client(ams_info.of_node, 13362306a36Sopenharmony_ci "accel-int-1", 13462306a36Sopenharmony_ci &ams_freefall_client); 13562306a36Sopenharmony_ci if (result < 0) 13662306a36Sopenharmony_ci return -ENODEV; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci /* Reset saved irqs */ 13962306a36Sopenharmony_ci ams_info.worker_irqs = 0; 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci /* Register shock interrupt handler */ 14262306a36Sopenharmony_ci result = pmf_register_irq_client(ams_info.of_node, 14362306a36Sopenharmony_ci "accel-int-2", 14462306a36Sopenharmony_ci &ams_shock_client); 14562306a36Sopenharmony_ci if (result < 0) 14662306a36Sopenharmony_ci goto release_freefall; 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci /* Create device */ 14962306a36Sopenharmony_ci ams_info.of_dev = of_platform_device_create(ams_info.of_node, "ams", NULL); 15062306a36Sopenharmony_ci if (!ams_info.of_dev) { 15162306a36Sopenharmony_ci result = -ENODEV; 15262306a36Sopenharmony_ci goto release_shock; 15362306a36Sopenharmony_ci } 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci /* Create attributes */ 15662306a36Sopenharmony_ci result = device_create_file(&ams_info.of_dev->dev, &dev_attr_current); 15762306a36Sopenharmony_ci if (result) 15862306a36Sopenharmony_ci goto release_of; 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci ams_info.vflag = !!(ams_info.get_vendor() & 0x10); 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci /* Init input device */ 16362306a36Sopenharmony_ci result = ams_input_init(); 16462306a36Sopenharmony_ci if (result) 16562306a36Sopenharmony_ci goto release_device_file; 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci return result; 16862306a36Sopenharmony_cirelease_device_file: 16962306a36Sopenharmony_ci device_remove_file(&ams_info.of_dev->dev, &dev_attr_current); 17062306a36Sopenharmony_cirelease_of: 17162306a36Sopenharmony_ci of_device_unregister(ams_info.of_dev); 17262306a36Sopenharmony_cirelease_shock: 17362306a36Sopenharmony_ci pmf_unregister_irq_client(&ams_shock_client); 17462306a36Sopenharmony_cirelease_freefall: 17562306a36Sopenharmony_ci pmf_unregister_irq_client(&ams_freefall_client); 17662306a36Sopenharmony_ci return result; 17762306a36Sopenharmony_ci} 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_cistatic int __init ams_init(void) 18062306a36Sopenharmony_ci{ 18162306a36Sopenharmony_ci struct device_node *np; 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci spin_lock_init(&ams_info.irq_lock); 18462306a36Sopenharmony_ci mutex_init(&ams_info.lock); 18562306a36Sopenharmony_ci INIT_WORK(&ams_info.worker, ams_worker); 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci#ifdef CONFIG_SENSORS_AMS_I2C 18862306a36Sopenharmony_ci np = of_find_node_by_name(NULL, "accelerometer"); 18962306a36Sopenharmony_ci if (np && of_device_is_compatible(np, "AAPL,accelerometer_1")) 19062306a36Sopenharmony_ci /* Found I2C motion sensor */ 19162306a36Sopenharmony_ci return ams_i2c_init(np); 19262306a36Sopenharmony_ci#endif 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci#ifdef CONFIG_SENSORS_AMS_PMU 19562306a36Sopenharmony_ci np = of_find_node_by_name(NULL, "sms"); 19662306a36Sopenharmony_ci if (np && of_device_is_compatible(np, "sms")) 19762306a36Sopenharmony_ci /* Found PMU motion sensor */ 19862306a36Sopenharmony_ci return ams_pmu_init(np); 19962306a36Sopenharmony_ci#endif 20062306a36Sopenharmony_ci return -ENODEV; 20162306a36Sopenharmony_ci} 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_civoid ams_sensor_detach(void) 20462306a36Sopenharmony_ci{ 20562306a36Sopenharmony_ci /* Remove input device */ 20662306a36Sopenharmony_ci ams_input_exit(); 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci /* Remove attributes */ 20962306a36Sopenharmony_ci device_remove_file(&ams_info.of_dev->dev, &dev_attr_current); 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci /* Flush interrupt worker 21262306a36Sopenharmony_ci * 21362306a36Sopenharmony_ci * We do this after ams_info.exit(), because an interrupt might 21462306a36Sopenharmony_ci * have arrived before disabling them. 21562306a36Sopenharmony_ci */ 21662306a36Sopenharmony_ci flush_work(&ams_info.worker); 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci /* Remove device */ 21962306a36Sopenharmony_ci of_device_unregister(ams_info.of_dev); 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci /* Remove handler */ 22262306a36Sopenharmony_ci pmf_unregister_irq_client(&ams_shock_client); 22362306a36Sopenharmony_ci pmf_unregister_irq_client(&ams_freefall_client); 22462306a36Sopenharmony_ci} 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_cistatic void __exit ams_exit(void) 22762306a36Sopenharmony_ci{ 22862306a36Sopenharmony_ci /* Shut down implementation */ 22962306a36Sopenharmony_ci ams_info.exit(); 23062306a36Sopenharmony_ci} 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ciMODULE_AUTHOR("Stelian Pop, Michael Hanselmann"); 23362306a36Sopenharmony_ciMODULE_DESCRIPTION("Apple Motion Sensor driver"); 23462306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_cimodule_init(ams_init); 23762306a36Sopenharmony_cimodule_exit(ams_exit); 238