forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
252 lines
6.1 KiB
252 lines
6.1 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* |
|
* Apple Onboard Audio pmf GPIOs |
|
* |
|
* Copyright 2006 Johannes Berg <[email protected]> |
|
*/ |
|
|
|
#include <linux/slab.h> |
|
#include <asm/pmac_feature.h> |
|
#include <asm/pmac_pfunc.h> |
|
#include "../aoa.h" |
|
|
|
#define PMF_GPIO(name, bit) \ |
|
static void pmf_gpio_set_##name(struct gpio_runtime *rt, int on)\ |
|
{ \ |
|
struct pmf_args args = { .count = 1, .u[0].v = !on }; \ |
|
int rc; \ |
|
\ |
|
if (unlikely(!rt)) return; \ |
|
rc = pmf_call_function(rt->node, #name "-mute", &args); \ |
|
if (rc && rc != -ENODEV) \ |
|
printk(KERN_WARNING "pmf_gpio_set_" #name \ |
|
" failed, rc: %d\n", rc); \ |
|
rt->implementation_private &= ~(1<<bit); \ |
|
rt->implementation_private |= (!!on << bit); \ |
|
} \ |
|
static int pmf_gpio_get_##name(struct gpio_runtime *rt) \ |
|
{ \ |
|
if (unlikely(!rt)) return 0; \ |
|
return (rt->implementation_private>>bit)&1; \ |
|
} |
|
|
|
PMF_GPIO(headphone, 0); |
|
PMF_GPIO(amp, 1); |
|
PMF_GPIO(lineout, 2); |
|
|
|
static void pmf_gpio_set_hw_reset(struct gpio_runtime *rt, int on) |
|
{ |
|
struct pmf_args args = { .count = 1, .u[0].v = !!on }; |
|
int rc; |
|
|
|
if (unlikely(!rt)) return; |
|
rc = pmf_call_function(rt->node, "hw-reset", &args); |
|
if (rc) |
|
printk(KERN_WARNING "pmf_gpio_set_hw_reset" |
|
" failed, rc: %d\n", rc); |
|
} |
|
|
|
static void pmf_gpio_all_amps_off(struct gpio_runtime *rt) |
|
{ |
|
int saved; |
|
|
|
if (unlikely(!rt)) return; |
|
saved = rt->implementation_private; |
|
pmf_gpio_set_headphone(rt, 0); |
|
pmf_gpio_set_amp(rt, 0); |
|
pmf_gpio_set_lineout(rt, 0); |
|
rt->implementation_private = saved; |
|
} |
|
|
|
static void pmf_gpio_all_amps_restore(struct gpio_runtime *rt) |
|
{ |
|
int s; |
|
|
|
if (unlikely(!rt)) return; |
|
s = rt->implementation_private; |
|
pmf_gpio_set_headphone(rt, (s>>0)&1); |
|
pmf_gpio_set_amp(rt, (s>>1)&1); |
|
pmf_gpio_set_lineout(rt, (s>>2)&1); |
|
} |
|
|
|
static void pmf_handle_notify(struct work_struct *work) |
|
{ |
|
struct gpio_notification *notif = |
|
container_of(work, struct gpio_notification, work.work); |
|
|
|
mutex_lock(¬if->mutex); |
|
if (notif->notify) |
|
notif->notify(notif->data); |
|
mutex_unlock(¬if->mutex); |
|
} |
|
|
|
static void pmf_gpio_init(struct gpio_runtime *rt) |
|
{ |
|
pmf_gpio_all_amps_off(rt); |
|
rt->implementation_private = 0; |
|
INIT_DELAYED_WORK(&rt->headphone_notify.work, pmf_handle_notify); |
|
INIT_DELAYED_WORK(&rt->line_in_notify.work, pmf_handle_notify); |
|
INIT_DELAYED_WORK(&rt->line_out_notify.work, pmf_handle_notify); |
|
mutex_init(&rt->headphone_notify.mutex); |
|
mutex_init(&rt->line_in_notify.mutex); |
|
mutex_init(&rt->line_out_notify.mutex); |
|
} |
|
|
|
static void pmf_gpio_exit(struct gpio_runtime *rt) |
|
{ |
|
pmf_gpio_all_amps_off(rt); |
|
rt->implementation_private = 0; |
|
|
|
if (rt->headphone_notify.gpio_private) |
|
pmf_unregister_irq_client(rt->headphone_notify.gpio_private); |
|
if (rt->line_in_notify.gpio_private) |
|
pmf_unregister_irq_client(rt->line_in_notify.gpio_private); |
|
if (rt->line_out_notify.gpio_private) |
|
pmf_unregister_irq_client(rt->line_out_notify.gpio_private); |
|
|
|
/* make sure no work is pending before freeing |
|
* all things */ |
|
cancel_delayed_work_sync(&rt->headphone_notify.work); |
|
cancel_delayed_work_sync(&rt->line_in_notify.work); |
|
cancel_delayed_work_sync(&rt->line_out_notify.work); |
|
|
|
mutex_destroy(&rt->headphone_notify.mutex); |
|
mutex_destroy(&rt->line_in_notify.mutex); |
|
mutex_destroy(&rt->line_out_notify.mutex); |
|
|
|
kfree(rt->headphone_notify.gpio_private); |
|
kfree(rt->line_in_notify.gpio_private); |
|
kfree(rt->line_out_notify.gpio_private); |
|
} |
|
|
|
static void pmf_handle_notify_irq(void *data) |
|
{ |
|
struct gpio_notification *notif = data; |
|
|
|
schedule_delayed_work(¬if->work, 0); |
|
} |
|
|
|
static int pmf_set_notify(struct gpio_runtime *rt, |
|
enum notify_type type, |
|
notify_func_t notify, |
|
void *data) |
|
{ |
|
struct gpio_notification *notif; |
|
notify_func_t old; |
|
struct pmf_irq_client *irq_client; |
|
char *name; |
|
int err = -EBUSY; |
|
|
|
switch (type) { |
|
case AOA_NOTIFY_HEADPHONE: |
|
notif = &rt->headphone_notify; |
|
name = "headphone-detect"; |
|
break; |
|
case AOA_NOTIFY_LINE_IN: |
|
notif = &rt->line_in_notify; |
|
name = "linein-detect"; |
|
break; |
|
case AOA_NOTIFY_LINE_OUT: |
|
notif = &rt->line_out_notify; |
|
name = "lineout-detect"; |
|
break; |
|
default: |
|
return -EINVAL; |
|
} |
|
|
|
mutex_lock(¬if->mutex); |
|
|
|
old = notif->notify; |
|
|
|
if (!old && !notify) { |
|
err = 0; |
|
goto out_unlock; |
|
} |
|
|
|
if (old && notify) { |
|
if (old == notify && notif->data == data) |
|
err = 0; |
|
goto out_unlock; |
|
} |
|
|
|
if (old && !notify) { |
|
irq_client = notif->gpio_private; |
|
pmf_unregister_irq_client(irq_client); |
|
kfree(irq_client); |
|
notif->gpio_private = NULL; |
|
} |
|
if (!old && notify) { |
|
irq_client = kzalloc(sizeof(struct pmf_irq_client), |
|
GFP_KERNEL); |
|
if (!irq_client) { |
|
err = -ENOMEM; |
|
goto out_unlock; |
|
} |
|
irq_client->data = notif; |
|
irq_client->handler = pmf_handle_notify_irq; |
|
irq_client->owner = THIS_MODULE; |
|
err = pmf_register_irq_client(rt->node, |
|
name, |
|
irq_client); |
|
if (err) { |
|
printk(KERN_ERR "snd-aoa: gpio layer failed to" |
|
" register %s irq (%d)\n", name, err); |
|
kfree(irq_client); |
|
goto out_unlock; |
|
} |
|
notif->gpio_private = irq_client; |
|
} |
|
notif->notify = notify; |
|
notif->data = data; |
|
|
|
err = 0; |
|
out_unlock: |
|
mutex_unlock(¬if->mutex); |
|
return err; |
|
} |
|
|
|
static int pmf_get_detect(struct gpio_runtime *rt, |
|
enum notify_type type) |
|
{ |
|
char *name; |
|
int err = -EBUSY, ret; |
|
struct pmf_args args = { .count = 1, .u[0].p = &ret }; |
|
|
|
switch (type) { |
|
case AOA_NOTIFY_HEADPHONE: |
|
name = "headphone-detect"; |
|
break; |
|
case AOA_NOTIFY_LINE_IN: |
|
name = "linein-detect"; |
|
break; |
|
case AOA_NOTIFY_LINE_OUT: |
|
name = "lineout-detect"; |
|
break; |
|
default: |
|
return -EINVAL; |
|
} |
|
|
|
err = pmf_call_function(rt->node, name, &args); |
|
if (err) |
|
return err; |
|
return ret; |
|
} |
|
|
|
static struct gpio_methods methods = { |
|
.init = pmf_gpio_init, |
|
.exit = pmf_gpio_exit, |
|
.all_amps_off = pmf_gpio_all_amps_off, |
|
.all_amps_restore = pmf_gpio_all_amps_restore, |
|
.set_headphone = pmf_gpio_set_headphone, |
|
.set_speakers = pmf_gpio_set_amp, |
|
.set_lineout = pmf_gpio_set_lineout, |
|
.set_hw_reset = pmf_gpio_set_hw_reset, |
|
.get_headphone = pmf_gpio_get_headphone, |
|
.get_speakers = pmf_gpio_get_amp, |
|
.get_lineout = pmf_gpio_get_lineout, |
|
.set_notify = pmf_set_notify, |
|
.get_detect = pmf_get_detect, |
|
}; |
|
|
|
struct gpio_methods *pmf_gpio_methods = &methods; |
|
EXPORT_SYMBOL_GPL(pmf_gpio_methods);
|
|
|