18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Apple Motion Sensor driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2005 Stelian Pop (stelian@popies.net) 68c2ecf20Sopenharmony_ci * Copyright (C) 2006 Michael Hanselmann (linux-kernel@hansmi.ch) 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/module.h> 108c2ecf20Sopenharmony_ci#include <linux/types.h> 118c2ecf20Sopenharmony_ci#include <linux/errno.h> 128c2ecf20Sopenharmony_ci#include <linux/init.h> 138c2ecf20Sopenharmony_ci#include <linux/of_platform.h> 148c2ecf20Sopenharmony_ci#include <asm/pmac_pfunc.h> 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci#include "ams.h" 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci/* There is only one motion sensor per machine */ 198c2ecf20Sopenharmony_cistruct ams ams_info; 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_cistatic bool verbose; 228c2ecf20Sopenharmony_cimodule_param(verbose, bool, 0644); 238c2ecf20Sopenharmony_ciMODULE_PARM_DESC(verbose, "Show free falls and shocks in kernel output"); 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci/* Call with ams_info.lock held! */ 268c2ecf20Sopenharmony_civoid ams_sensors(s8 *x, s8 *y, s8 *z) 278c2ecf20Sopenharmony_ci{ 288c2ecf20Sopenharmony_ci u32 orient = ams_info.vflag? ams_info.orient1 : ams_info.orient2; 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci if (orient & 0x80) 318c2ecf20Sopenharmony_ci /* X and Y swapped */ 328c2ecf20Sopenharmony_ci ams_info.get_xyz(y, x, z); 338c2ecf20Sopenharmony_ci else 348c2ecf20Sopenharmony_ci ams_info.get_xyz(x, y, z); 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ci if (orient & 0x04) 378c2ecf20Sopenharmony_ci *z = ~(*z); 388c2ecf20Sopenharmony_ci if (orient & 0x02) 398c2ecf20Sopenharmony_ci *y = ~(*y); 408c2ecf20Sopenharmony_ci if (orient & 0x01) 418c2ecf20Sopenharmony_ci *x = ~(*x); 428c2ecf20Sopenharmony_ci} 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_cistatic ssize_t ams_show_current(struct device *dev, 458c2ecf20Sopenharmony_ci struct device_attribute *attr, char *buf) 468c2ecf20Sopenharmony_ci{ 478c2ecf20Sopenharmony_ci s8 x, y, z; 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci mutex_lock(&ams_info.lock); 508c2ecf20Sopenharmony_ci ams_sensors(&x, &y, &z); 518c2ecf20Sopenharmony_ci mutex_unlock(&ams_info.lock); 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci return snprintf(buf, PAGE_SIZE, "%d %d %d\n", x, y, z); 548c2ecf20Sopenharmony_ci} 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_cistatic DEVICE_ATTR(current, S_IRUGO, ams_show_current, NULL); 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_cistatic void ams_handle_irq(void *data) 598c2ecf20Sopenharmony_ci{ 608c2ecf20Sopenharmony_ci enum ams_irq irq = *((enum ams_irq *)data); 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci spin_lock(&ams_info.irq_lock); 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci ams_info.worker_irqs |= irq; 658c2ecf20Sopenharmony_ci schedule_work(&ams_info.worker); 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci spin_unlock(&ams_info.irq_lock); 688c2ecf20Sopenharmony_ci} 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_cistatic enum ams_irq ams_freefall_irq_data = AMS_IRQ_FREEFALL; 718c2ecf20Sopenharmony_cistatic struct pmf_irq_client ams_freefall_client = { 728c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 738c2ecf20Sopenharmony_ci .handler = ams_handle_irq, 748c2ecf20Sopenharmony_ci .data = &ams_freefall_irq_data, 758c2ecf20Sopenharmony_ci}; 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_cistatic enum ams_irq ams_shock_irq_data = AMS_IRQ_SHOCK; 788c2ecf20Sopenharmony_cistatic struct pmf_irq_client ams_shock_client = { 798c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 808c2ecf20Sopenharmony_ci .handler = ams_handle_irq, 818c2ecf20Sopenharmony_ci .data = &ams_shock_irq_data, 828c2ecf20Sopenharmony_ci}; 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci/* Once hard disk parking is implemented in the kernel, this function can 858c2ecf20Sopenharmony_ci * trigger it. 868c2ecf20Sopenharmony_ci */ 878c2ecf20Sopenharmony_cistatic void ams_worker(struct work_struct *work) 888c2ecf20Sopenharmony_ci{ 898c2ecf20Sopenharmony_ci unsigned long flags; 908c2ecf20Sopenharmony_ci u8 irqs_to_clear; 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci mutex_lock(&ams_info.lock); 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci spin_lock_irqsave(&ams_info.irq_lock, flags); 958c2ecf20Sopenharmony_ci irqs_to_clear = ams_info.worker_irqs; 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci if (ams_info.worker_irqs & AMS_IRQ_FREEFALL) { 988c2ecf20Sopenharmony_ci if (verbose) 998c2ecf20Sopenharmony_ci printk(KERN_INFO "ams: freefall detected!\n"); 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci ams_info.worker_irqs &= ~AMS_IRQ_FREEFALL; 1028c2ecf20Sopenharmony_ci } 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci if (ams_info.worker_irqs & AMS_IRQ_SHOCK) { 1058c2ecf20Sopenharmony_ci if (verbose) 1068c2ecf20Sopenharmony_ci printk(KERN_INFO "ams: shock detected!\n"); 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci ams_info.worker_irqs &= ~AMS_IRQ_SHOCK; 1098c2ecf20Sopenharmony_ci } 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&ams_info.irq_lock, flags); 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci ams_info.clear_irq(irqs_to_clear); 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci mutex_unlock(&ams_info.lock); 1168c2ecf20Sopenharmony_ci} 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci/* Call with ams_info.lock held! */ 1198c2ecf20Sopenharmony_ciint ams_sensor_attach(void) 1208c2ecf20Sopenharmony_ci{ 1218c2ecf20Sopenharmony_ci int result; 1228c2ecf20Sopenharmony_ci const u32 *prop; 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci /* Get orientation */ 1258c2ecf20Sopenharmony_ci prop = of_get_property(ams_info.of_node, "orientation", NULL); 1268c2ecf20Sopenharmony_ci if (!prop) 1278c2ecf20Sopenharmony_ci return -ENODEV; 1288c2ecf20Sopenharmony_ci ams_info.orient1 = *prop; 1298c2ecf20Sopenharmony_ci ams_info.orient2 = *(prop + 1); 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci /* Register freefall interrupt handler */ 1328c2ecf20Sopenharmony_ci result = pmf_register_irq_client(ams_info.of_node, 1338c2ecf20Sopenharmony_ci "accel-int-1", 1348c2ecf20Sopenharmony_ci &ams_freefall_client); 1358c2ecf20Sopenharmony_ci if (result < 0) 1368c2ecf20Sopenharmony_ci return -ENODEV; 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci /* Reset saved irqs */ 1398c2ecf20Sopenharmony_ci ams_info.worker_irqs = 0; 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci /* Register shock interrupt handler */ 1428c2ecf20Sopenharmony_ci result = pmf_register_irq_client(ams_info.of_node, 1438c2ecf20Sopenharmony_ci "accel-int-2", 1448c2ecf20Sopenharmony_ci &ams_shock_client); 1458c2ecf20Sopenharmony_ci if (result < 0) 1468c2ecf20Sopenharmony_ci goto release_freefall; 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci /* Create device */ 1498c2ecf20Sopenharmony_ci ams_info.of_dev = of_platform_device_create(ams_info.of_node, "ams", NULL); 1508c2ecf20Sopenharmony_ci if (!ams_info.of_dev) { 1518c2ecf20Sopenharmony_ci result = -ENODEV; 1528c2ecf20Sopenharmony_ci goto release_shock; 1538c2ecf20Sopenharmony_ci } 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci /* Create attributes */ 1568c2ecf20Sopenharmony_ci result = device_create_file(&ams_info.of_dev->dev, &dev_attr_current); 1578c2ecf20Sopenharmony_ci if (result) 1588c2ecf20Sopenharmony_ci goto release_of; 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci ams_info.vflag = !!(ams_info.get_vendor() & 0x10); 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci /* Init input device */ 1638c2ecf20Sopenharmony_ci result = ams_input_init(); 1648c2ecf20Sopenharmony_ci if (result) 1658c2ecf20Sopenharmony_ci goto release_device_file; 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci return result; 1688c2ecf20Sopenharmony_cirelease_device_file: 1698c2ecf20Sopenharmony_ci device_remove_file(&ams_info.of_dev->dev, &dev_attr_current); 1708c2ecf20Sopenharmony_cirelease_of: 1718c2ecf20Sopenharmony_ci of_device_unregister(ams_info.of_dev); 1728c2ecf20Sopenharmony_cirelease_shock: 1738c2ecf20Sopenharmony_ci pmf_unregister_irq_client(&ams_shock_client); 1748c2ecf20Sopenharmony_cirelease_freefall: 1758c2ecf20Sopenharmony_ci pmf_unregister_irq_client(&ams_freefall_client); 1768c2ecf20Sopenharmony_ci return result; 1778c2ecf20Sopenharmony_ci} 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ciint __init ams_init(void) 1808c2ecf20Sopenharmony_ci{ 1818c2ecf20Sopenharmony_ci struct device_node *np; 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci spin_lock_init(&ams_info.irq_lock); 1848c2ecf20Sopenharmony_ci mutex_init(&ams_info.lock); 1858c2ecf20Sopenharmony_ci INIT_WORK(&ams_info.worker, ams_worker); 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ci#ifdef CONFIG_SENSORS_AMS_I2C 1888c2ecf20Sopenharmony_ci np = of_find_node_by_name(NULL, "accelerometer"); 1898c2ecf20Sopenharmony_ci if (np && of_device_is_compatible(np, "AAPL,accelerometer_1")) 1908c2ecf20Sopenharmony_ci /* Found I2C motion sensor */ 1918c2ecf20Sopenharmony_ci return ams_i2c_init(np); 1928c2ecf20Sopenharmony_ci#endif 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci#ifdef CONFIG_SENSORS_AMS_PMU 1958c2ecf20Sopenharmony_ci np = of_find_node_by_name(NULL, "sms"); 1968c2ecf20Sopenharmony_ci if (np && of_device_is_compatible(np, "sms")) 1978c2ecf20Sopenharmony_ci /* Found PMU motion sensor */ 1988c2ecf20Sopenharmony_ci return ams_pmu_init(np); 1998c2ecf20Sopenharmony_ci#endif 2008c2ecf20Sopenharmony_ci return -ENODEV; 2018c2ecf20Sopenharmony_ci} 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_civoid ams_sensor_detach(void) 2048c2ecf20Sopenharmony_ci{ 2058c2ecf20Sopenharmony_ci /* Remove input device */ 2068c2ecf20Sopenharmony_ci ams_input_exit(); 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci /* Remove attributes */ 2098c2ecf20Sopenharmony_ci device_remove_file(&ams_info.of_dev->dev, &dev_attr_current); 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci /* Flush interrupt worker 2128c2ecf20Sopenharmony_ci * 2138c2ecf20Sopenharmony_ci * We do this after ams_info.exit(), because an interrupt might 2148c2ecf20Sopenharmony_ci * have arrived before disabling them. 2158c2ecf20Sopenharmony_ci */ 2168c2ecf20Sopenharmony_ci flush_work(&ams_info.worker); 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_ci /* Remove device */ 2198c2ecf20Sopenharmony_ci of_device_unregister(ams_info.of_dev); 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci /* Remove handler */ 2228c2ecf20Sopenharmony_ci pmf_unregister_irq_client(&ams_shock_client); 2238c2ecf20Sopenharmony_ci pmf_unregister_irq_client(&ams_freefall_client); 2248c2ecf20Sopenharmony_ci} 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_cistatic void __exit ams_exit(void) 2278c2ecf20Sopenharmony_ci{ 2288c2ecf20Sopenharmony_ci /* Shut down implementation */ 2298c2ecf20Sopenharmony_ci ams_info.exit(); 2308c2ecf20Sopenharmony_ci} 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ciMODULE_AUTHOR("Stelian Pop, Michael Hanselmann"); 2338c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Apple Motion Sensor driver"); 2348c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_cimodule_init(ams_init); 2378c2ecf20Sopenharmony_cimodule_exit(ams_exit); 238