18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Apple Onboard Audio feature call GPIO control 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright 2006 Johannes Berg <johannes@sipsolutions.net> 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * This file contains the GPIO control routines for 88c2ecf20Sopenharmony_ci * direct (through feature calls) access to the GPIO 98c2ecf20Sopenharmony_ci * registers. 108c2ecf20Sopenharmony_ci */ 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_ci#include <linux/of_irq.h> 138c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 148c2ecf20Sopenharmony_ci#include <asm/pmac_feature.h> 158c2ecf20Sopenharmony_ci#include "../aoa.h" 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_ci/* TODO: these are lots of global variables 188c2ecf20Sopenharmony_ci * that aren't used on most machines... 198c2ecf20Sopenharmony_ci * Move them into a dynamically allocated 208c2ecf20Sopenharmony_ci * structure and use that. 218c2ecf20Sopenharmony_ci */ 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci/* these are the GPIO numbers (register addresses as offsets into 248c2ecf20Sopenharmony_ci * the GPIO space) */ 258c2ecf20Sopenharmony_cistatic int headphone_mute_gpio; 268c2ecf20Sopenharmony_cistatic int master_mute_gpio; 278c2ecf20Sopenharmony_cistatic int amp_mute_gpio; 288c2ecf20Sopenharmony_cistatic int lineout_mute_gpio; 298c2ecf20Sopenharmony_cistatic int hw_reset_gpio; 308c2ecf20Sopenharmony_cistatic int lineout_detect_gpio; 318c2ecf20Sopenharmony_cistatic int headphone_detect_gpio; 328c2ecf20Sopenharmony_cistatic int linein_detect_gpio; 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci/* see the SWITCH_GPIO macro */ 358c2ecf20Sopenharmony_cistatic int headphone_mute_gpio_activestate; 368c2ecf20Sopenharmony_cistatic int master_mute_gpio_activestate; 378c2ecf20Sopenharmony_cistatic int amp_mute_gpio_activestate; 388c2ecf20Sopenharmony_cistatic int lineout_mute_gpio_activestate; 398c2ecf20Sopenharmony_cistatic int hw_reset_gpio_activestate; 408c2ecf20Sopenharmony_cistatic int lineout_detect_gpio_activestate; 418c2ecf20Sopenharmony_cistatic int headphone_detect_gpio_activestate; 428c2ecf20Sopenharmony_cistatic int linein_detect_gpio_activestate; 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_ci/* node pointers that we save when getting the GPIO number 458c2ecf20Sopenharmony_ci * to get the interrupt later */ 468c2ecf20Sopenharmony_cistatic struct device_node *lineout_detect_node; 478c2ecf20Sopenharmony_cistatic struct device_node *linein_detect_node; 488c2ecf20Sopenharmony_cistatic struct device_node *headphone_detect_node; 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_cistatic int lineout_detect_irq; 518c2ecf20Sopenharmony_cistatic int linein_detect_irq; 528c2ecf20Sopenharmony_cistatic int headphone_detect_irq; 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_cistatic struct device_node *get_gpio(char *name, 558c2ecf20Sopenharmony_ci char *altname, 568c2ecf20Sopenharmony_ci int *gpioptr, 578c2ecf20Sopenharmony_ci int *gpioactiveptr) 588c2ecf20Sopenharmony_ci{ 598c2ecf20Sopenharmony_ci struct device_node *np, *gpio; 608c2ecf20Sopenharmony_ci const u32 *reg; 618c2ecf20Sopenharmony_ci const char *audio_gpio; 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci *gpioptr = -1; 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci /* check if we can get it the easy way ... */ 668c2ecf20Sopenharmony_ci np = of_find_node_by_name(NULL, name); 678c2ecf20Sopenharmony_ci if (!np) { 688c2ecf20Sopenharmony_ci /* some machines have only gpioX/extint-gpioX nodes, 698c2ecf20Sopenharmony_ci * and an audio-gpio property saying what it is ... 708c2ecf20Sopenharmony_ci * So what we have to do is enumerate all children 718c2ecf20Sopenharmony_ci * of the gpio node and check them all. */ 728c2ecf20Sopenharmony_ci gpio = of_find_node_by_name(NULL, "gpio"); 738c2ecf20Sopenharmony_ci if (!gpio) 748c2ecf20Sopenharmony_ci return NULL; 758c2ecf20Sopenharmony_ci while ((np = of_get_next_child(gpio, np))) { 768c2ecf20Sopenharmony_ci audio_gpio = of_get_property(np, "audio-gpio", NULL); 778c2ecf20Sopenharmony_ci if (!audio_gpio) 788c2ecf20Sopenharmony_ci continue; 798c2ecf20Sopenharmony_ci if (strcmp(audio_gpio, name) == 0) 808c2ecf20Sopenharmony_ci break; 818c2ecf20Sopenharmony_ci if (altname && (strcmp(audio_gpio, altname) == 0)) 828c2ecf20Sopenharmony_ci break; 838c2ecf20Sopenharmony_ci } 848c2ecf20Sopenharmony_ci of_node_put(gpio); 858c2ecf20Sopenharmony_ci /* still not found, assume not there */ 868c2ecf20Sopenharmony_ci if (!np) 878c2ecf20Sopenharmony_ci return NULL; 888c2ecf20Sopenharmony_ci } 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci reg = of_get_property(np, "reg", NULL); 918c2ecf20Sopenharmony_ci if (!reg) { 928c2ecf20Sopenharmony_ci of_node_put(np); 938c2ecf20Sopenharmony_ci return NULL; 948c2ecf20Sopenharmony_ci } 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci *gpioptr = *reg; 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_ci /* this is a hack, usually the GPIOs 'reg' property 998c2ecf20Sopenharmony_ci * should have the offset based from the GPIO space 1008c2ecf20Sopenharmony_ci * which is at 0x50, but apparently not always... */ 1018c2ecf20Sopenharmony_ci if (*gpioptr < 0x50) 1028c2ecf20Sopenharmony_ci *gpioptr += 0x50; 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci reg = of_get_property(np, "audio-gpio-active-state", NULL); 1058c2ecf20Sopenharmony_ci if (!reg) 1068c2ecf20Sopenharmony_ci /* Apple seems to default to 1, but 1078c2ecf20Sopenharmony_ci * that doesn't seem right at least on most 1088c2ecf20Sopenharmony_ci * machines. So until proven that the opposite 1098c2ecf20Sopenharmony_ci * is necessary, we default to 0 1108c2ecf20Sopenharmony_ci * (which, incidentally, snd-powermac also does...) */ 1118c2ecf20Sopenharmony_ci *gpioactiveptr = 0; 1128c2ecf20Sopenharmony_ci else 1138c2ecf20Sopenharmony_ci *gpioactiveptr = *reg; 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci return np; 1168c2ecf20Sopenharmony_ci} 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_cistatic void get_irq(struct device_node * np, int *irqptr) 1198c2ecf20Sopenharmony_ci{ 1208c2ecf20Sopenharmony_ci if (np) 1218c2ecf20Sopenharmony_ci *irqptr = irq_of_parse_and_map(np, 0); 1228c2ecf20Sopenharmony_ci else 1238c2ecf20Sopenharmony_ci *irqptr = 0; 1248c2ecf20Sopenharmony_ci} 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci/* 0x4 is outenable, 0x1 is out, thus 4 or 5 */ 1278c2ecf20Sopenharmony_ci#define SWITCH_GPIO(name, v, on) \ 1288c2ecf20Sopenharmony_ci (((v)&~1) | ((on)? \ 1298c2ecf20Sopenharmony_ci (name##_gpio_activestate==0?4:5): \ 1308c2ecf20Sopenharmony_ci (name##_gpio_activestate==0?5:4))) 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci#define FTR_GPIO(name, bit) \ 1338c2ecf20Sopenharmony_cistatic void ftr_gpio_set_##name(struct gpio_runtime *rt, int on)\ 1348c2ecf20Sopenharmony_ci{ \ 1358c2ecf20Sopenharmony_ci int v; \ 1368c2ecf20Sopenharmony_ci \ 1378c2ecf20Sopenharmony_ci if (unlikely(!rt)) return; \ 1388c2ecf20Sopenharmony_ci \ 1398c2ecf20Sopenharmony_ci if (name##_mute_gpio < 0) \ 1408c2ecf20Sopenharmony_ci return; \ 1418c2ecf20Sopenharmony_ci \ 1428c2ecf20Sopenharmony_ci v = pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, \ 1438c2ecf20Sopenharmony_ci name##_mute_gpio, \ 1448c2ecf20Sopenharmony_ci 0); \ 1458c2ecf20Sopenharmony_ci \ 1468c2ecf20Sopenharmony_ci /* muted = !on... */ \ 1478c2ecf20Sopenharmony_ci v = SWITCH_GPIO(name##_mute, v, !on); \ 1488c2ecf20Sopenharmony_ci \ 1498c2ecf20Sopenharmony_ci pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL, \ 1508c2ecf20Sopenharmony_ci name##_mute_gpio, v); \ 1518c2ecf20Sopenharmony_ci \ 1528c2ecf20Sopenharmony_ci rt->implementation_private &= ~(1<<bit); \ 1538c2ecf20Sopenharmony_ci rt->implementation_private |= (!!on << bit); \ 1548c2ecf20Sopenharmony_ci} \ 1558c2ecf20Sopenharmony_cistatic int ftr_gpio_get_##name(struct gpio_runtime *rt) \ 1568c2ecf20Sopenharmony_ci{ \ 1578c2ecf20Sopenharmony_ci if (unlikely(!rt)) return 0; \ 1588c2ecf20Sopenharmony_ci return (rt->implementation_private>>bit)&1; \ 1598c2ecf20Sopenharmony_ci} 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ciFTR_GPIO(headphone, 0); 1628c2ecf20Sopenharmony_ciFTR_GPIO(amp, 1); 1638c2ecf20Sopenharmony_ciFTR_GPIO(lineout, 2); 1648c2ecf20Sopenharmony_ciFTR_GPIO(master, 3); 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_cistatic void ftr_gpio_set_hw_reset(struct gpio_runtime *rt, int on) 1678c2ecf20Sopenharmony_ci{ 1688c2ecf20Sopenharmony_ci int v; 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci if (unlikely(!rt)) return; 1718c2ecf20Sopenharmony_ci if (hw_reset_gpio < 0) 1728c2ecf20Sopenharmony_ci return; 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci v = pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, 1758c2ecf20Sopenharmony_ci hw_reset_gpio, 0); 1768c2ecf20Sopenharmony_ci v = SWITCH_GPIO(hw_reset, v, on); 1778c2ecf20Sopenharmony_ci pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL, 1788c2ecf20Sopenharmony_ci hw_reset_gpio, v); 1798c2ecf20Sopenharmony_ci} 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_cistatic struct gpio_methods methods; 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_cistatic void ftr_gpio_all_amps_off(struct gpio_runtime *rt) 1848c2ecf20Sopenharmony_ci{ 1858c2ecf20Sopenharmony_ci int saved; 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ci if (unlikely(!rt)) return; 1888c2ecf20Sopenharmony_ci saved = rt->implementation_private; 1898c2ecf20Sopenharmony_ci ftr_gpio_set_headphone(rt, 0); 1908c2ecf20Sopenharmony_ci ftr_gpio_set_amp(rt, 0); 1918c2ecf20Sopenharmony_ci ftr_gpio_set_lineout(rt, 0); 1928c2ecf20Sopenharmony_ci if (methods.set_master) 1938c2ecf20Sopenharmony_ci ftr_gpio_set_master(rt, 0); 1948c2ecf20Sopenharmony_ci rt->implementation_private = saved; 1958c2ecf20Sopenharmony_ci} 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_cistatic void ftr_gpio_all_amps_restore(struct gpio_runtime *rt) 1988c2ecf20Sopenharmony_ci{ 1998c2ecf20Sopenharmony_ci int s; 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci if (unlikely(!rt)) return; 2028c2ecf20Sopenharmony_ci s = rt->implementation_private; 2038c2ecf20Sopenharmony_ci ftr_gpio_set_headphone(rt, (s>>0)&1); 2048c2ecf20Sopenharmony_ci ftr_gpio_set_amp(rt, (s>>1)&1); 2058c2ecf20Sopenharmony_ci ftr_gpio_set_lineout(rt, (s>>2)&1); 2068c2ecf20Sopenharmony_ci if (methods.set_master) 2078c2ecf20Sopenharmony_ci ftr_gpio_set_master(rt, (s>>3)&1); 2088c2ecf20Sopenharmony_ci} 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_cistatic void ftr_handle_notify(struct work_struct *work) 2118c2ecf20Sopenharmony_ci{ 2128c2ecf20Sopenharmony_ci struct gpio_notification *notif = 2138c2ecf20Sopenharmony_ci container_of(work, struct gpio_notification, work.work); 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci mutex_lock(¬if->mutex); 2168c2ecf20Sopenharmony_ci if (notif->notify) 2178c2ecf20Sopenharmony_ci notif->notify(notif->data); 2188c2ecf20Sopenharmony_ci mutex_unlock(¬if->mutex); 2198c2ecf20Sopenharmony_ci} 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_cistatic void gpio_enable_dual_edge(int gpio) 2228c2ecf20Sopenharmony_ci{ 2238c2ecf20Sopenharmony_ci int v; 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ci if (gpio == -1) 2268c2ecf20Sopenharmony_ci return; 2278c2ecf20Sopenharmony_ci v = pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, gpio, 0); 2288c2ecf20Sopenharmony_ci v |= 0x80; /* enable dual edge */ 2298c2ecf20Sopenharmony_ci pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL, gpio, v); 2308c2ecf20Sopenharmony_ci} 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_cistatic void ftr_gpio_init(struct gpio_runtime *rt) 2338c2ecf20Sopenharmony_ci{ 2348c2ecf20Sopenharmony_ci get_gpio("headphone-mute", NULL, 2358c2ecf20Sopenharmony_ci &headphone_mute_gpio, 2368c2ecf20Sopenharmony_ci &headphone_mute_gpio_activestate); 2378c2ecf20Sopenharmony_ci get_gpio("amp-mute", NULL, 2388c2ecf20Sopenharmony_ci &_mute_gpio, 2398c2ecf20Sopenharmony_ci &_mute_gpio_activestate); 2408c2ecf20Sopenharmony_ci get_gpio("lineout-mute", NULL, 2418c2ecf20Sopenharmony_ci &lineout_mute_gpio, 2428c2ecf20Sopenharmony_ci &lineout_mute_gpio_activestate); 2438c2ecf20Sopenharmony_ci get_gpio("hw-reset", "audio-hw-reset", 2448c2ecf20Sopenharmony_ci &hw_reset_gpio, 2458c2ecf20Sopenharmony_ci &hw_reset_gpio_activestate); 2468c2ecf20Sopenharmony_ci if (get_gpio("master-mute", NULL, 2478c2ecf20Sopenharmony_ci &master_mute_gpio, 2488c2ecf20Sopenharmony_ci &master_mute_gpio_activestate)) { 2498c2ecf20Sopenharmony_ci methods.set_master = ftr_gpio_set_master; 2508c2ecf20Sopenharmony_ci methods.get_master = ftr_gpio_get_master; 2518c2ecf20Sopenharmony_ci } 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_ci headphone_detect_node = get_gpio("headphone-detect", NULL, 2548c2ecf20Sopenharmony_ci &headphone_detect_gpio, 2558c2ecf20Sopenharmony_ci &headphone_detect_gpio_activestate); 2568c2ecf20Sopenharmony_ci /* go Apple, and thanks for giving these different names 2578c2ecf20Sopenharmony_ci * across the board... */ 2588c2ecf20Sopenharmony_ci lineout_detect_node = get_gpio("lineout-detect", "line-output-detect", 2598c2ecf20Sopenharmony_ci &lineout_detect_gpio, 2608c2ecf20Sopenharmony_ci &lineout_detect_gpio_activestate); 2618c2ecf20Sopenharmony_ci linein_detect_node = get_gpio("linein-detect", "line-input-detect", 2628c2ecf20Sopenharmony_ci &linein_detect_gpio, 2638c2ecf20Sopenharmony_ci &linein_detect_gpio_activestate); 2648c2ecf20Sopenharmony_ci 2658c2ecf20Sopenharmony_ci gpio_enable_dual_edge(headphone_detect_gpio); 2668c2ecf20Sopenharmony_ci gpio_enable_dual_edge(lineout_detect_gpio); 2678c2ecf20Sopenharmony_ci gpio_enable_dual_edge(linein_detect_gpio); 2688c2ecf20Sopenharmony_ci 2698c2ecf20Sopenharmony_ci get_irq(headphone_detect_node, &headphone_detect_irq); 2708c2ecf20Sopenharmony_ci get_irq(lineout_detect_node, &lineout_detect_irq); 2718c2ecf20Sopenharmony_ci get_irq(linein_detect_node, &linein_detect_irq); 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_ci ftr_gpio_all_amps_off(rt); 2748c2ecf20Sopenharmony_ci rt->implementation_private = 0; 2758c2ecf20Sopenharmony_ci INIT_DELAYED_WORK(&rt->headphone_notify.work, ftr_handle_notify); 2768c2ecf20Sopenharmony_ci INIT_DELAYED_WORK(&rt->line_in_notify.work, ftr_handle_notify); 2778c2ecf20Sopenharmony_ci INIT_DELAYED_WORK(&rt->line_out_notify.work, ftr_handle_notify); 2788c2ecf20Sopenharmony_ci mutex_init(&rt->headphone_notify.mutex); 2798c2ecf20Sopenharmony_ci mutex_init(&rt->line_in_notify.mutex); 2808c2ecf20Sopenharmony_ci mutex_init(&rt->line_out_notify.mutex); 2818c2ecf20Sopenharmony_ci} 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_cistatic void ftr_gpio_exit(struct gpio_runtime *rt) 2848c2ecf20Sopenharmony_ci{ 2858c2ecf20Sopenharmony_ci ftr_gpio_all_amps_off(rt); 2868c2ecf20Sopenharmony_ci rt->implementation_private = 0; 2878c2ecf20Sopenharmony_ci if (rt->headphone_notify.notify) 2888c2ecf20Sopenharmony_ci free_irq(headphone_detect_irq, &rt->headphone_notify); 2898c2ecf20Sopenharmony_ci if (rt->line_in_notify.gpio_private) 2908c2ecf20Sopenharmony_ci free_irq(linein_detect_irq, &rt->line_in_notify); 2918c2ecf20Sopenharmony_ci if (rt->line_out_notify.gpio_private) 2928c2ecf20Sopenharmony_ci free_irq(lineout_detect_irq, &rt->line_out_notify); 2938c2ecf20Sopenharmony_ci cancel_delayed_work_sync(&rt->headphone_notify.work); 2948c2ecf20Sopenharmony_ci cancel_delayed_work_sync(&rt->line_in_notify.work); 2958c2ecf20Sopenharmony_ci cancel_delayed_work_sync(&rt->line_out_notify.work); 2968c2ecf20Sopenharmony_ci mutex_destroy(&rt->headphone_notify.mutex); 2978c2ecf20Sopenharmony_ci mutex_destroy(&rt->line_in_notify.mutex); 2988c2ecf20Sopenharmony_ci mutex_destroy(&rt->line_out_notify.mutex); 2998c2ecf20Sopenharmony_ci} 3008c2ecf20Sopenharmony_ci 3018c2ecf20Sopenharmony_cistatic irqreturn_t ftr_handle_notify_irq(int xx, void *data) 3028c2ecf20Sopenharmony_ci{ 3038c2ecf20Sopenharmony_ci struct gpio_notification *notif = data; 3048c2ecf20Sopenharmony_ci 3058c2ecf20Sopenharmony_ci schedule_delayed_work(¬if->work, 0); 3068c2ecf20Sopenharmony_ci 3078c2ecf20Sopenharmony_ci return IRQ_HANDLED; 3088c2ecf20Sopenharmony_ci} 3098c2ecf20Sopenharmony_ci 3108c2ecf20Sopenharmony_cistatic int ftr_set_notify(struct gpio_runtime *rt, 3118c2ecf20Sopenharmony_ci enum notify_type type, 3128c2ecf20Sopenharmony_ci notify_func_t notify, 3138c2ecf20Sopenharmony_ci void *data) 3148c2ecf20Sopenharmony_ci{ 3158c2ecf20Sopenharmony_ci struct gpio_notification *notif; 3168c2ecf20Sopenharmony_ci notify_func_t old; 3178c2ecf20Sopenharmony_ci int irq; 3188c2ecf20Sopenharmony_ci char *name; 3198c2ecf20Sopenharmony_ci int err = -EBUSY; 3208c2ecf20Sopenharmony_ci 3218c2ecf20Sopenharmony_ci switch (type) { 3228c2ecf20Sopenharmony_ci case AOA_NOTIFY_HEADPHONE: 3238c2ecf20Sopenharmony_ci notif = &rt->headphone_notify; 3248c2ecf20Sopenharmony_ci name = "headphone-detect"; 3258c2ecf20Sopenharmony_ci irq = headphone_detect_irq; 3268c2ecf20Sopenharmony_ci break; 3278c2ecf20Sopenharmony_ci case AOA_NOTIFY_LINE_IN: 3288c2ecf20Sopenharmony_ci notif = &rt->line_in_notify; 3298c2ecf20Sopenharmony_ci name = "linein-detect"; 3308c2ecf20Sopenharmony_ci irq = linein_detect_irq; 3318c2ecf20Sopenharmony_ci break; 3328c2ecf20Sopenharmony_ci case AOA_NOTIFY_LINE_OUT: 3338c2ecf20Sopenharmony_ci notif = &rt->line_out_notify; 3348c2ecf20Sopenharmony_ci name = "lineout-detect"; 3358c2ecf20Sopenharmony_ci irq = lineout_detect_irq; 3368c2ecf20Sopenharmony_ci break; 3378c2ecf20Sopenharmony_ci default: 3388c2ecf20Sopenharmony_ci return -EINVAL; 3398c2ecf20Sopenharmony_ci } 3408c2ecf20Sopenharmony_ci 3418c2ecf20Sopenharmony_ci if (!irq) 3428c2ecf20Sopenharmony_ci return -ENODEV; 3438c2ecf20Sopenharmony_ci 3448c2ecf20Sopenharmony_ci mutex_lock(¬if->mutex); 3458c2ecf20Sopenharmony_ci 3468c2ecf20Sopenharmony_ci old = notif->notify; 3478c2ecf20Sopenharmony_ci 3488c2ecf20Sopenharmony_ci if (!old && !notify) { 3498c2ecf20Sopenharmony_ci err = 0; 3508c2ecf20Sopenharmony_ci goto out_unlock; 3518c2ecf20Sopenharmony_ci } 3528c2ecf20Sopenharmony_ci 3538c2ecf20Sopenharmony_ci if (old && notify) { 3548c2ecf20Sopenharmony_ci if (old == notify && notif->data == data) 3558c2ecf20Sopenharmony_ci err = 0; 3568c2ecf20Sopenharmony_ci goto out_unlock; 3578c2ecf20Sopenharmony_ci } 3588c2ecf20Sopenharmony_ci 3598c2ecf20Sopenharmony_ci if (old && !notify) 3608c2ecf20Sopenharmony_ci free_irq(irq, notif); 3618c2ecf20Sopenharmony_ci 3628c2ecf20Sopenharmony_ci if (!old && notify) { 3638c2ecf20Sopenharmony_ci err = request_irq(irq, ftr_handle_notify_irq, 0, name, notif); 3648c2ecf20Sopenharmony_ci if (err) 3658c2ecf20Sopenharmony_ci goto out_unlock; 3668c2ecf20Sopenharmony_ci } 3678c2ecf20Sopenharmony_ci 3688c2ecf20Sopenharmony_ci notif->notify = notify; 3698c2ecf20Sopenharmony_ci notif->data = data; 3708c2ecf20Sopenharmony_ci 3718c2ecf20Sopenharmony_ci err = 0; 3728c2ecf20Sopenharmony_ci out_unlock: 3738c2ecf20Sopenharmony_ci mutex_unlock(¬if->mutex); 3748c2ecf20Sopenharmony_ci return err; 3758c2ecf20Sopenharmony_ci} 3768c2ecf20Sopenharmony_ci 3778c2ecf20Sopenharmony_cistatic int ftr_get_detect(struct gpio_runtime *rt, 3788c2ecf20Sopenharmony_ci enum notify_type type) 3798c2ecf20Sopenharmony_ci{ 3808c2ecf20Sopenharmony_ci int gpio, ret, active; 3818c2ecf20Sopenharmony_ci 3828c2ecf20Sopenharmony_ci switch (type) { 3838c2ecf20Sopenharmony_ci case AOA_NOTIFY_HEADPHONE: 3848c2ecf20Sopenharmony_ci gpio = headphone_detect_gpio; 3858c2ecf20Sopenharmony_ci active = headphone_detect_gpio_activestate; 3868c2ecf20Sopenharmony_ci break; 3878c2ecf20Sopenharmony_ci case AOA_NOTIFY_LINE_IN: 3888c2ecf20Sopenharmony_ci gpio = linein_detect_gpio; 3898c2ecf20Sopenharmony_ci active = linein_detect_gpio_activestate; 3908c2ecf20Sopenharmony_ci break; 3918c2ecf20Sopenharmony_ci case AOA_NOTIFY_LINE_OUT: 3928c2ecf20Sopenharmony_ci gpio = lineout_detect_gpio; 3938c2ecf20Sopenharmony_ci active = lineout_detect_gpio_activestate; 3948c2ecf20Sopenharmony_ci break; 3958c2ecf20Sopenharmony_ci default: 3968c2ecf20Sopenharmony_ci return -EINVAL; 3978c2ecf20Sopenharmony_ci } 3988c2ecf20Sopenharmony_ci 3998c2ecf20Sopenharmony_ci if (gpio == -1) 4008c2ecf20Sopenharmony_ci return -ENODEV; 4018c2ecf20Sopenharmony_ci 4028c2ecf20Sopenharmony_ci ret = pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, gpio, 0); 4038c2ecf20Sopenharmony_ci if (ret < 0) 4048c2ecf20Sopenharmony_ci return ret; 4058c2ecf20Sopenharmony_ci return ((ret >> 1) & 1) == active; 4068c2ecf20Sopenharmony_ci} 4078c2ecf20Sopenharmony_ci 4088c2ecf20Sopenharmony_cistatic struct gpio_methods methods = { 4098c2ecf20Sopenharmony_ci .init = ftr_gpio_init, 4108c2ecf20Sopenharmony_ci .exit = ftr_gpio_exit, 4118c2ecf20Sopenharmony_ci .all_amps_off = ftr_gpio_all_amps_off, 4128c2ecf20Sopenharmony_ci .all_amps_restore = ftr_gpio_all_amps_restore, 4138c2ecf20Sopenharmony_ci .set_headphone = ftr_gpio_set_headphone, 4148c2ecf20Sopenharmony_ci .set_speakers = ftr_gpio_set_amp, 4158c2ecf20Sopenharmony_ci .set_lineout = ftr_gpio_set_lineout, 4168c2ecf20Sopenharmony_ci .set_hw_reset = ftr_gpio_set_hw_reset, 4178c2ecf20Sopenharmony_ci .get_headphone = ftr_gpio_get_headphone, 4188c2ecf20Sopenharmony_ci .get_speakers = ftr_gpio_get_amp, 4198c2ecf20Sopenharmony_ci .get_lineout = ftr_gpio_get_lineout, 4208c2ecf20Sopenharmony_ci .set_notify = ftr_set_notify, 4218c2ecf20Sopenharmony_ci .get_detect = ftr_get_detect, 4228c2ecf20Sopenharmony_ci}; 4238c2ecf20Sopenharmony_ci 4248c2ecf20Sopenharmony_cistruct gpio_methods *ftr_gpio_methods = &methods; 4258c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(ftr_gpio_methods); 426