253 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Apple Onboard Audio pmf GPIOs4 *5 * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>6 */7 8#include <linux/slab.h>9#include <asm/pmac_feature.h>10#include <asm/pmac_pfunc.h>11#include "../aoa.h"12 13#define PMF_GPIO(name, bit) \14static void pmf_gpio_set_##name(struct gpio_runtime *rt, int on)\15{ \16 struct pmf_args args = { .count = 1, .u[0].v = !on }; \17 int rc; \18 \19 if (unlikely(!rt)) return; \20 rc = pmf_call_function(rt->node, #name "-mute", &args); \21 if (rc && rc != -ENODEV) \22 printk(KERN_WARNING "pmf_gpio_set_" #name \23 " failed, rc: %d\n", rc); \24 rt->implementation_private &= ~(1<<bit); \25 rt->implementation_private |= (!!on << bit); \26} \27static int pmf_gpio_get_##name(struct gpio_runtime *rt) \28{ \29 if (unlikely(!rt)) return 0; \30 return (rt->implementation_private>>bit)&1; \31}32 33PMF_GPIO(headphone, 0);34PMF_GPIO(amp, 1);35PMF_GPIO(lineout, 2);36 37static void pmf_gpio_set_hw_reset(struct gpio_runtime *rt, int on)38{39 struct pmf_args args = { .count = 1, .u[0].v = !!on };40 int rc;41 42 if (unlikely(!rt)) return;43 rc = pmf_call_function(rt->node, "hw-reset", &args);44 if (rc)45 printk(KERN_WARNING "pmf_gpio_set_hw_reset"46 " failed, rc: %d\n", rc);47}48 49static void pmf_gpio_all_amps_off(struct gpio_runtime *rt)50{51 int saved;52 53 if (unlikely(!rt)) return;54 saved = rt->implementation_private;55 pmf_gpio_set_headphone(rt, 0);56 pmf_gpio_set_amp(rt, 0);57 pmf_gpio_set_lineout(rt, 0);58 rt->implementation_private = saved;59}60 61static void pmf_gpio_all_amps_restore(struct gpio_runtime *rt)62{63 int s;64 65 if (unlikely(!rt)) return;66 s = rt->implementation_private;67 pmf_gpio_set_headphone(rt, (s>>0)&1);68 pmf_gpio_set_amp(rt, (s>>1)&1);69 pmf_gpio_set_lineout(rt, (s>>2)&1);70}71 72static void pmf_handle_notify(struct work_struct *work)73{74 struct gpio_notification *notif =75 container_of(work, struct gpio_notification, work.work);76 77 mutex_lock(¬if->mutex);78 if (notif->notify)79 notif->notify(notif->data);80 mutex_unlock(¬if->mutex);81}82 83static void pmf_gpio_init(struct gpio_runtime *rt)84{85 pmf_gpio_all_amps_off(rt);86 rt->implementation_private = 0;87 INIT_DELAYED_WORK(&rt->headphone_notify.work, pmf_handle_notify);88 INIT_DELAYED_WORK(&rt->line_in_notify.work, pmf_handle_notify);89 INIT_DELAYED_WORK(&rt->line_out_notify.work, pmf_handle_notify);90 mutex_init(&rt->headphone_notify.mutex);91 mutex_init(&rt->line_in_notify.mutex);92 mutex_init(&rt->line_out_notify.mutex);93}94 95static void pmf_gpio_exit(struct gpio_runtime *rt)96{97 pmf_gpio_all_amps_off(rt);98 rt->implementation_private = 0;99 100 if (rt->headphone_notify.gpio_private)101 pmf_unregister_irq_client(rt->headphone_notify.gpio_private);102 if (rt->line_in_notify.gpio_private)103 pmf_unregister_irq_client(rt->line_in_notify.gpio_private);104 if (rt->line_out_notify.gpio_private)105 pmf_unregister_irq_client(rt->line_out_notify.gpio_private);106 107 /* make sure no work is pending before freeing108 * all things */109 cancel_delayed_work_sync(&rt->headphone_notify.work);110 cancel_delayed_work_sync(&rt->line_in_notify.work);111 cancel_delayed_work_sync(&rt->line_out_notify.work);112 113 mutex_destroy(&rt->headphone_notify.mutex);114 mutex_destroy(&rt->line_in_notify.mutex);115 mutex_destroy(&rt->line_out_notify.mutex);116 117 kfree(rt->headphone_notify.gpio_private);118 kfree(rt->line_in_notify.gpio_private);119 kfree(rt->line_out_notify.gpio_private);120}121 122static void pmf_handle_notify_irq(void *data)123{124 struct gpio_notification *notif = data;125 126 schedule_delayed_work(¬if->work, 0);127}128 129static int pmf_set_notify(struct gpio_runtime *rt,130 enum notify_type type,131 notify_func_t notify,132 void *data)133{134 struct gpio_notification *notif;135 notify_func_t old;136 struct pmf_irq_client *irq_client;137 char *name;138 int err = -EBUSY;139 140 switch (type) {141 case AOA_NOTIFY_HEADPHONE:142 notif = &rt->headphone_notify;143 name = "headphone-detect";144 break;145 case AOA_NOTIFY_LINE_IN:146 notif = &rt->line_in_notify;147 name = "linein-detect";148 break;149 case AOA_NOTIFY_LINE_OUT:150 notif = &rt->line_out_notify;151 name = "lineout-detect";152 break;153 default:154 return -EINVAL;155 }156 157 mutex_lock(¬if->mutex);158 159 old = notif->notify;160 161 if (!old && !notify) {162 err = 0;163 goto out_unlock;164 }165 166 if (old && notify) {167 if (old == notify && notif->data == data)168 err = 0;169 goto out_unlock;170 }171 172 if (old && !notify) {173 irq_client = notif->gpio_private;174 pmf_unregister_irq_client(irq_client);175 kfree(irq_client);176 notif->gpio_private = NULL;177 }178 if (!old && notify) {179 irq_client = kzalloc(sizeof(struct pmf_irq_client),180 GFP_KERNEL);181 if (!irq_client) {182 err = -ENOMEM;183 goto out_unlock;184 }185 irq_client->data = notif;186 irq_client->handler = pmf_handle_notify_irq;187 irq_client->owner = THIS_MODULE;188 err = pmf_register_irq_client(rt->node,189 name,190 irq_client);191 if (err) {192 printk(KERN_ERR "snd-aoa: gpio layer failed to"193 " register %s irq (%d)\n", name, err);194 kfree(irq_client);195 goto out_unlock;196 }197 notif->gpio_private = irq_client;198 }199 notif->notify = notify;200 notif->data = data;201 202 err = 0;203 out_unlock:204 mutex_unlock(¬if->mutex);205 return err;206}207 208static int pmf_get_detect(struct gpio_runtime *rt,209 enum notify_type type)210{211 char *name;212 int err = -EBUSY, ret;213 struct pmf_args args = { .count = 1, .u[0].p = &ret };214 215 switch (type) {216 case AOA_NOTIFY_HEADPHONE:217 name = "headphone-detect";218 break;219 case AOA_NOTIFY_LINE_IN:220 name = "linein-detect";221 break;222 case AOA_NOTIFY_LINE_OUT:223 name = "lineout-detect";224 break;225 default:226 return -EINVAL;227 }228 229 err = pmf_call_function(rt->node, name, &args);230 if (err)231 return err;232 return ret;233}234 235static struct gpio_methods methods = {236 .init = pmf_gpio_init,237 .exit = pmf_gpio_exit,238 .all_amps_off = pmf_gpio_all_amps_off,239 .all_amps_restore = pmf_gpio_all_amps_restore,240 .set_headphone = pmf_gpio_set_headphone,241 .set_speakers = pmf_gpio_set_amp,242 .set_lineout = pmf_gpio_set_lineout,243 .set_hw_reset = pmf_gpio_set_hw_reset,244 .get_headphone = pmf_gpio_get_headphone,245 .get_speakers = pmf_gpio_get_amp,246 .get_lineout = pmf_gpio_get_lineout,247 .set_notify = pmf_set_notify,248 .get_detect = pmf_get_detect,249};250 251struct gpio_methods *pmf_gpio_methods = &methods;252EXPORT_SYMBOL_GPL(pmf_gpio_methods);253