blob: 92a9017ca8725a8c44da270416b4707a87f3f4c5 [file] [log] [blame]
/*
* sound/soc/codecs/88pm886_headset.c
*
* headset & hook detect driver for pm886
*
* Copyright (C) 2014, Marvell Corporation
* Author: Zhao Ye <zhaoy@marvell.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/platform_device.h>
#include <linux/mfd/88pm8xxx-headset.h>
#include <linux/regulator/consumer.h>
#include <linux/workqueue.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/of.h>
#include <linux/mutex.h>
#include <linux/switch.h>
#include <linux/mfd/88pm88x.h>
#include <linux/mfd/88pm886.h>
#define PM886_HEADSET_CNTRL (0x38)
#define PM886_HEADSET_DET_EN (1 << 7)
#define PM886_HSDET_SLP (1 << 1)
#define PM886_MIC_DET_TH 300
#define PM886_PRESS_RELEASE_TH 300
#define PM886_HOOK_PRESS_TH 20
#define PM886_VOL_UP_PRESS_TH 60
#define PM886_VOL_DOWN_PRESS_TH 110
#define PM886_HK_DELTA 10
#define PM886_HS_DET_INVERT 1
#define PM886_MIC_CNTRL (0x39)
#define PM886_MICDET_EN (1 << 0)
#define PM886_HEADSET_DET_MASK (1 << 7)
#define PM886_GPADC_MEAS_EN2 (0x02)
#define PM886_MIC_DET_MEAS_EN (1 << 6)
#define PM860_SHORT_DETECT_REG (0x37)
#define PM860_SHORT_DETECT_BIT (0x3)
#define PM880_HEADSET_CNTRL3 (0x3C)
#define PM880_MIC_BIAS_AUTO_MUTE_EN (1 << 4)
#define PM886_GPADC_AVG1 (0xAC)
#define PM886_GPADC_MIN1 (0x89)
#define PM886_GPADC_MAX1 (0x99)
#define PM886_GPADC_MEAS1 (0x5c)
#define PM886_GPADC_CONFIG5 (0x05)
#define PM886_INT_ENA_3 (0x0C)
#define PM886_MIC_INT_EN (1 << 4)
#define PM886_MIC_DET_RDIV_DIS (1 << 6)
#define PM886_GPADC_LOW_TH (0x24)
#define PM886_GPADC_UPP_TH (0x34)
#define MIC_DET_DBS (3 << 1)
#define MIC_DET_PRD (3 << 3)
#define MIC_DET_DBS_32MS (3 << 1)
#define MIC_DET_PRD_CTN (3 << 3)
struct headset_switch_data {
struct switch_dev sdev;
};
struct pm886_hs_info {
struct pm88x_chip *chip;
struct device *dev;
struct regmap *map;
struct regmap *map_gpadc;
struct regmap *map_codec;
int irq_headset;
int irq_hook;
struct work_struct work_release, work_init;
struct headset_switch_data *psw_data_headset;
struct snd_soc_jack *hs_jack, *hk_jack;
struct regulator *mic_bias;
int mic_bias_volt;
int mic_bias_range;
int headset_flag;
int hook_press_th;
int vol_up_press_th;
int vol_down_press_th;
int mic_det_th;
int press_release_th;
int hs_status, hk_status;
unsigned int hk_avg, hk_num;
u32 hook_count;
struct mutex hs_mutex;
struct timer_list hook_timer;
struct device *hsdetect_dev;
};
enum {
VOLTAGE_AVG,
VOLTAGE_MIN,
VOLTAGE_MAX,
VOLTAGE_INS,
};
static struct pm886_hs_info *hs_info;
/* currently we only support button 0, 1, 2 */
static char *pm886_jk_btn[] = {
"hook",
"vol up",
"vol down",
};
static int gpadc_measure_voltage(struct pm886_hs_info *info, int which)
{
unsigned char buf[2];
int sum = 0, ret = -1;
switch (which) {
case VOLTAGE_AVG:
ret = regmap_raw_read(info->map_gpadc,
PM886_GPADC_AVG1, buf, 2);
break;
case VOLTAGE_MIN:
ret = regmap_raw_read(info->map_gpadc,
PM886_GPADC_MIN1, buf, 2);
break;
case VOLTAGE_MAX:
ret = regmap_raw_read(info->map_gpadc,
PM886_GPADC_MAX1, buf, 2);
break;
case VOLTAGE_INS:
ret = regmap_raw_read(info->map_gpadc,
PM886_GPADC_MEAS1, buf, 2);
break;
default:
break;
}
if (ret < 0)
return 0;
/* GPADC4_dir = 1, measure(mv) = value *1.4 *1000/(2^12) */
sum = ((buf[0] & 0xFF) << 4) | (buf[1] & 0x0F);
sum = ((sum & 0xFFF) * info->mic_bias_range) >> 12;
return sum;
}
static void gpadc_set_threshold(struct pm886_hs_info *info, int min,
int max)
{
unsigned int data;
if (min <= 0)
data = 0;
else
data = ((min << 12) / info->mic_bias_range) >> 4;
regmap_write(info->map_gpadc,
PM886_GPADC_LOW_TH, data);
if (max <= 0)
data = 0xFF;
else
data = ((max << 12) / info->mic_bias_range) >> 4;
regmap_write(info->map_gpadc,
PM886_GPADC_UPP_TH, data);
}
static int pm886_handle_voltage(struct pm886_hs_info *info, int voltage)
{
int report = 0, button = 0, hk_avg;
int mask = SND_JACK_BTN_0 | SND_JACK_BTN_1 | SND_JACK_BTN_2;
if (voltage < info->press_release_th) {
/* press event */
if (info->hk_status == 0) {
if (voltage < info->hook_press_th) {
report = SND_JACK_BTN_0;
button = 0;
/* for telephony */
kobject_uevent(&info->hsdetect_dev->kobj,
KOBJ_ONLINE);
/* remember hook voltage */
hk_avg = info->hk_avg * info->hk_num + voltage;
info->hk_num = info->hk_num + 1;
info->hk_avg = hk_avg / info->hk_num;
} else if (voltage < info->vol_up_press_th) {
report = SND_JACK_BTN_1;
button = 1;
} else if (voltage < info->vol_down_press_th) {
report = SND_JACK_BTN_2;
button = 2;
} else
return -EINVAL;
snd_soc_jack_report(info->hk_jack, report, mask);
info->hk_status = report;
gpadc_set_threshold(info, 0, info->press_release_th);
} else
return -EINVAL;
} else {
/* release event */
if (info->hk_status) {
switch (info->hk_status) {
case SND_JACK_BTN_0:
info->hk_jack->jack->type = SND_JACK_BTN_0;
button = 0;
/* for telephony */
kobject_uevent(&info->hsdetect_dev->kobj,
KOBJ_ONLINE);
break;
case SND_JACK_BTN_1:
info->hk_jack->jack->type = SND_JACK_BTN_1;
button = 1;
break;
case SND_JACK_BTN_2:
info->hk_jack->jack->type = SND_JACK_BTN_2;
button = 2;
break;
default:
return -EINVAL;
}
snd_soc_jack_report(info->hk_jack, report, mask);
info->hk_jack->jack->type = mask;
info->hk_status = report;
gpadc_set_threshold(info, info->press_release_th, 0);
} else
return -EINVAL;
}
dev_dbg(info->dev, "%s to %d\n", pm886_jk_btn[button], (report > 0));
return 0;
}
static void mic_set_volt_micbias(int volt_mv, struct pm886_hs_info *info)
{
int ret = 0;
ret = regulator_set_voltage(info->mic_bias,
volt_mv * 1000,
volt_mv * 1000);
if (ret) {
dev_err(info->dev,
"failed to set voltage to regulator: %d\n", ret);
return;
}
}
static void mic_set_power(int on, struct pm886_hs_info *info)
{
int ret = 0;
if (on)
ret = regulator_enable(info->mic_bias);
else
ret = regulator_disable(info->mic_bias);
if (ret) {
dev_err(info->dev,
"failed to enable/disable regulator: %d\n", ret);
return;
}
}
static void pm886_hook_int(struct pm886_hs_info *info, int enable)
{
mutex_lock(&info->hs_mutex);
if (enable && info->hook_count == 0) {
enable_irq(info->irq_hook);
info->hook_count = 1;
} else if (!enable && info->hook_count == 1) {
disable_irq(info->irq_hook);
info->hook_count = 0;
}
mutex_unlock(&info->hs_mutex);
}
static void pm886_hook_timer_handler(unsigned long data)
{
struct pm886_hs_info *info = (struct pm886_hs_info *)data;
queue_work(system_wq, &info->work_release);
}
static void pm886_hook_release_work(struct work_struct *work)
{
struct pm886_hs_info *info =
container_of(work, struct pm886_hs_info, work_release);
unsigned int voltage;
if (info->hk_status) {
if (info->hs_status == 0) {
pm886_handle_voltage(info,
info->press_release_th + 1);
return;
}
voltage = gpadc_measure_voltage(info, VOLTAGE_AVG);
if (voltage >= info->press_release_th)
pm886_handle_voltage(info, voltage);
}
}
static void pm886_hook_work(struct pm886_hs_info *info)
{
int ret;
unsigned int value, voltage;
msleep(50);
ret = regmap_read(info->map, PM886_MIC_CNTRL, &value);
if (ret < 0) {
dev_err(info->dev,
"Failed to read PM886_MIC_CTRL: 0x%x\n", ret);
return;
}
value &= PM886_HEADSET_DET_MASK;
if (info->headset_flag == PM886_HS_DET_INVERT)
value = !value;
if (!value) {
/* in case of headset unpresent, do nothing */
dev_dbg(info->dev, "hook: false exit\n");
goto FAKE_HOOK;
}
voltage = gpadc_measure_voltage(info, VOLTAGE_AVG);
dev_dbg(info->dev, "voltage %d avg %d\n", voltage, info->hk_avg);
/*
* below numbers are got by experience:
* SS hs: hook vol = ~50; false hook = ~35, so set false_th = 40
* iphone hs: hook vol = 5; false_hook = ~33.
* these value can be adjust for specific hs
* on pxa1908 hook voltage is ~27.
*/
if (voltage < info->hook_press_th) {
if (info->hk_avg
&& (abs(info->hk_avg - voltage) > PM886_HK_DELTA))
goto FAKE_HOOK;
else if (!info->hk_avg)
if (voltage > 35 && voltage < info->hook_press_th - 40)
goto FAKE_HOOK;
}
pm886_handle_voltage(info, voltage);
regmap_update_bits(info->map, PM886_INT_ENA_3,
PM886_MIC_INT_EN,
PM886_MIC_INT_EN);
return;
FAKE_HOOK:
if (value)
regmap_update_bits(info->map, PM886_INT_ENA_3,
PM886_MIC_INT_EN,
PM886_MIC_INT_EN);
dev_dbg(info->dev, "fake hook interupt\n");
}
static void check_headset_short(struct pm886_hs_info *info)
{
unsigned int value;
regmap_read(info->map_codec, PM860_SHORT_DETECT_REG, &value);
if (!(value & PM860_SHORT_DETECT_BIT))
return;
else
regmap_update_bits(info->map_codec, PM860_SHORT_DETECT_REG,
PM860_SHORT_DETECT_BIT, 0);
}
static void pm886_headset_work(struct pm886_hs_info *info)
{
unsigned int value, voltage;
int ret, report = 0;
if (info == NULL)
return;
ret = regmap_read(info->map, PM886_MIC_CNTRL, &value);
if (ret < 0) {
dev_err(info->dev,
"Failed to read PM886_MIC_CTRL: 0x%x\n", ret);
return;
}
value &= PM886_HEADSET_DET_MASK;
if (info->headset_flag == PM886_HS_DET_INVERT)
value = !value;
if (value) {
report = SND_JACK_HEADSET;
/* for telephony */
kobject_uevent(&info->hsdetect_dev->kobj, KOBJ_ADD);
if (info->mic_bias) {
mic_set_power(1, info);
/* for enable iphone headset, it need higher voltage */
mic_set_volt_micbias(2500, info);
}
/* enable MIC detection also enable measurement */
regmap_update_bits(info->map, PM886_MIC_CNTRL, PM886_MICDET_EN,
PM886_MICDET_EN);
regmap_update_bits(info->map_gpadc, PM886_GPADC_MEAS_EN2,
PM886_MIC_DET_MEAS_EN, PM886_MIC_DET_MEAS_EN);
msleep(200);
voltage = gpadc_measure_voltage(info, VOLTAGE_AVG);
if (voltage < info->mic_det_th) {
report = SND_JACK_HEADPHONE;
if (info->mic_bias)
mic_set_power(0, info);
/* disable MIC detection and measurement */
regmap_update_bits(info->map, PM886_MIC_CNTRL,
PM886_MICDET_EN, 0);
regmap_update_bits(info->map_gpadc,
PM886_GPADC_MEAS_EN2,
PM886_MIC_DET_MEAS_EN, 0);
} else
gpadc_set_threshold(info, info->press_release_th, 0);
info->hs_status = report;
} else {
/* we already disable mic power when it is headphone */
if ((info->hs_status == SND_JACK_HEADSET) && info->mic_bias)
mic_set_power(0, info);
/* disable MIC detection and measurement */
regmap_update_bits(info->map, PM886_MIC_CNTRL, PM886_MICDET_EN,
0);
regmap_update_bits(info->map_gpadc, PM886_GPADC_MEAS_EN2,
PM886_MIC_DET_MEAS_EN, 0);
info->hs_status = report;
/* for telephony */
kobject_uevent(&info->hsdetect_dev->kobj, KOBJ_REMOVE);
/* clear the hook voltage record */
info->hk_avg = 0;
info->hk_num = 0;
/* headset plug out, we can check the headset short status */
check_headset_short(info);
}
mic_set_volt_micbias(info->mic_bias_volt, info);
snd_soc_jack_report(info->hs_jack, report, SND_JACK_HEADSET);
switch_set_state(&info->psw_data_headset->sdev, report);
dev_dbg(info->dev, "hs status: %d\n", report);
/* enable hook irq if headset is present */
if (info->hs_status == SND_JACK_HEADSET)
pm886_hook_int(info, 1);
}
static irqreturn_t pm886_headset_handler(int irq, void *data)
{
struct pm886_hs_info *info = data;
if (info->hs_jack != NULL) {
/*
* in any case headset interrupt comes,
* hook interrupt is not welcomed. so just
* disable it.
*/
pm886_hook_int(info, 0);
pm886_headset_work(info);
}
return IRQ_HANDLED;
}
static irqreturn_t pm886_hook_handler(int irq, void *data)
{
struct pm886_hs_info *info = data;
mod_timer(&info->hook_timer, jiffies + msecs_to_jiffies(150));
if (info->hk_jack != NULL) {
/* disable hook interrupt anyway */
regmap_update_bits(info->map, PM886_INT_ENA_3,
PM886_MIC_INT_EN, 0);
pm886_hook_work(info);
}
return IRQ_HANDLED;
}
/*
* separate hs & hk, since snd_jack_report reports both key & switch. just to
* avoid confusion in upper layer. when reporting hs, only headset/headphone/
* mic status are reported. when reporting hk, hook/vol up/vol down status are
* reported.
*/
int pm886_headset_detect(struct snd_soc_jack *jack)
{
if (!hs_info)
return -EINVAL;
/* Store the configuration */
hs_info->hs_jack = jack;
/* detect headset status during boot up */
queue_work(system_wq, &hs_info->work_init);
return 0;
}
EXPORT_SYMBOL_GPL(pm886_headset_detect);
int pm886_hook_detect(struct snd_soc_jack *jack)
{
if (!hs_info)
return -EINVAL;
/* Store the configuration */
hs_info->hk_jack = jack;
return 0;
}
EXPORT_SYMBOL_GPL(pm886_hook_detect);
static void pm886_init_work(struct work_struct *work)
{
struct pm886_hs_info *info =
container_of(work, struct pm886_hs_info, work_init);
/* headset status is not stable when boot up. wait 200ms */
msleep(200);
pm886_headset_handler(info->irq_headset, info);
}
static int pm886_headset_suspend(struct platform_device *pdev,
pm_message_t state)
{
struct pm886_hs_info *info = platform_get_drvdata(pdev);
/* it's not proper to use "pm80x_dev_suspend" here */
if (device_may_wakeup(&pdev->dev)) {
enable_irq_wake(info->irq_headset);
if (info->hs_status == SND_JACK_HEADSET)
enable_irq_wake(info->irq_hook);
}
regmap_update_bits(hs_info->map, PM886_HEADSET_CNTRL,
PM886_HSDET_SLP,
PM886_HSDET_SLP);
return 0;
}
static int pm886_headset_resume(struct platform_device *pdev)
{
struct pm886_hs_info *info = platform_get_drvdata(pdev);
if (device_may_wakeup(&pdev->dev)) {
disable_irq_wake(info->irq_headset);
if (info->hs_status == SND_JACK_HEADSET)
disable_irq_wake(info->irq_hook);
}
regmap_update_bits(hs_info->map, PM886_HEADSET_CNTRL,
PM886_HSDET_SLP,
0);
return 0;
}
static struct regmap *pm80x_get_companion(void)
{
return get_codec_companion();
}
static int pm886_headset_probe(struct platform_device *pdev)
{
struct pm88x_chip *chip = dev_get_drvdata(pdev->dev.parent);
int irq_headset, irq_hook, ret = 0;
struct resource *headset_resource, *mic_resource;
headset_resource = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!headset_resource) {
dev_err(&pdev->dev, "No resource for headset!\n");
return -EINVAL;
}
mic_resource = platform_get_resource(pdev, IORESOURCE_IRQ, 1);
if (!mic_resource) {
dev_err(&pdev->dev, "No resource for mic!\n");
return -EINVAL;
}
irq_headset = platform_get_irq(pdev, 0);
if (irq_headset < 0) {
dev_err(&pdev->dev, "No IRQ resource for headset!\n");
return -EINVAL;
}
irq_hook = platform_get_irq(pdev, 1);
if (irq_hook < 0) {
dev_err(&pdev->dev, "No IRQ resource for hook/mic!\n");
return -EINVAL;
}
hs_info =
devm_kzalloc(&pdev->dev, sizeof(struct pm886_hs_info),
GFP_KERNEL);
if (!hs_info)
return -ENOMEM;
hs_info->chip = chip;
hs_info->mic_bias = regulator_get(&pdev->dev, "marvell,micbias");
hs_info->psw_data_headset =
devm_kzalloc(&pdev->dev, sizeof(struct headset_switch_data),
GFP_KERNEL);
if (!hs_info->psw_data_headset)
return -ENOMEM;
if (IS_ERR(hs_info->mic_bias)) {
hs_info->mic_bias = NULL;
dev_err(&pdev->dev, "Get regulator error\n");
}
if (IS_ENABLED(CONFIG_OF)) {
if (of_property_read_u32(pdev->dev.of_node,
"marvell,headset-flag", &hs_info->headset_flag))
dev_dbg(&pdev->dev, "Do not get headset flag\n");
if (of_property_read_u32(pdev->dev.of_node,
"marvell,hook-press-th", &hs_info->hook_press_th))
dev_dbg(&pdev->dev,
"Do not get hook press threshold\n");
if (of_property_read_u32(pdev->dev.of_node,
"marvell,vol-up-press-th", &hs_info->vol_up_press_th))
dev_dbg(&pdev->dev, "Do not get vol up threshold\n");
if (of_property_read_u32(pdev->dev.of_node,
"marvell,vol-down-press-th",
&hs_info->vol_down_press_th))
dev_dbg(&pdev->dev, "Do not get vol down threshold\n");
if (of_property_read_u32(pdev->dev.of_node,
"marvell,mic-det-th", &hs_info->mic_det_th))
dev_dbg(&pdev->dev,
"Do not get mic detect threshold\n");
if (of_property_read_u32(pdev->dev.of_node,
"marvell,press-release-th", &hs_info->press_release_th))
dev_dbg(&pdev->dev,
"Do not get press release threshold\n");
if (of_property_read_u32(pdev->dev.of_node,
"marvell,micbias-volt", &hs_info->mic_bias_volt))
dev_dbg(&pdev->dev, "Do not get micbias voltage\n");
} else {
hs_info->hook_press_th = PM886_HOOK_PRESS_TH;
hs_info->vol_up_press_th = PM886_VOL_UP_PRESS_TH;
hs_info->vol_down_press_th = PM886_VOL_DOWN_PRESS_TH;
hs_info->mic_det_th = PM886_MIC_DET_TH;
hs_info->press_release_th = PM886_PRESS_RELEASE_TH;
hs_info->mic_bias_volt = 1700;
}
/*
* When MIC_DET_RDIV_DIS=1, mic bias measure range is 0~1.4V.
* When MIC_DET_RDIV_DIS=0, LDO16_VSET[2]=0,
* mic bias measure range is 0~1.85V.
* When MIC_DET_RDIV_DIS=0, LDO16_VSET[2]=1,
* mic bias measure range is 0~3.1V.
*/
if (hs_info->mic_bias_volt <= 2000)
hs_info->mic_bias_range = 1850;
else
hs_info->mic_bias_range = 3100;
hs_info->map = chip->base_regmap;
hs_info->map_gpadc = chip->gpadc_regmap;
hs_info->map_codec = pm80x_get_companion();
hs_info->irq_headset = irq_headset;
hs_info->irq_hook = irq_hook;
hs_info->dev = &pdev->dev;
hs_info->hk_status = 0;
hs_info->hk_avg = 0;
hs_info->hk_num = 0;
mutex_init(&hs_info->hs_mutex);
INIT_WORK(&hs_info->work_release, pm886_hook_release_work);
INIT_WORK(&hs_info->work_init, pm886_init_work);
/* init timer for hook release */
setup_timer(&hs_info->hook_timer, pm886_hook_timer_handler,
(unsigned long)hs_info);
ret = devm_request_threaded_irq(&pdev->dev, hs_info->irq_headset,
NULL, pm886_headset_handler,
IRQF_ONESHOT | IRQF_NO_SUSPEND, "headset", hs_info);
if (ret < 0) {
dev_err(chip->dev, "Failed to request IRQ: #%d: %d\n",
hs_info->irq_headset, ret);
goto err_out;
}
ret = devm_request_threaded_irq(&pdev->dev, hs_info->irq_hook,
NULL, pm886_hook_handler,
IRQF_ONESHOT | IRQF_NO_SUSPEND, "hook", hs_info);
if (ret < 0) {
dev_err(chip->dev, "Failed to request IRQ: #%d: %d\n",
hs_info->irq_hook, ret);
goto err_out;
}
/*
* disable hook irq to ensure this irq will be enabled
* after plugging in headset
*/
hs_info->hook_count = 1;
pm886_hook_int(hs_info, 0);
platform_set_drvdata(pdev, hs_info);
hs_info->hsdetect_dev = &pdev->dev;
/* Hook:32 ms debounce time */
regmap_update_bits(hs_info->map, PM886_MIC_CNTRL, MIC_DET_DBS,
MIC_DET_DBS_32MS);
/* Hook:continue duty cycle */
regmap_update_bits(hs_info->map, PM886_MIC_CNTRL, MIC_DET_PRD,
MIC_DET_PRD_CTN);
disable_irq(hs_info->irq_headset);
regmap_update_bits(hs_info->map, PM886_HEADSET_CNTRL,
PM886_HEADSET_DET_EN, PM886_HEADSET_DET_EN);
enable_irq(hs_info->irq_headset);
if (chip->type == PM880) {
/* enable MIC bias auto mechanism */
regmap_update_bits(hs_info->map, PM880_HEADSET_CNTRL3,
PM880_MIC_BIAS_AUTO_MUTE_EN, PM880_MIC_BIAS_AUTO_MUTE_EN);
}
device_init_wakeup(&pdev->dev, 1);
headset_detect = pm886_headset_detect;
hook_detect = pm886_hook_detect;
hs_info->psw_data_headset->sdev.name = "h2w";
ret = switch_dev_register(&hs_info->psw_data_headset->sdev);
if (ret < 0)
dev_err(&pdev->dev, "Failed to register switch %s\n",
hs_info->psw_data_headset->sdev.name);
return 0;
err_out:
return ret;
}
static int pm886_headset_remove(struct platform_device *pdev)
{
struct pm886_hs_info *hs_info = platform_get_drvdata(pdev);
/* disable headset detection on GPIO */
regmap_update_bits(hs_info->map, PM886_HEADSET_CNTRL,
PM886_HEADSET_DET_EN, 0);
cancel_work_sync(&hs_info->work_release);
cancel_work_sync(&hs_info->work_init);
switch_dev_unregister(&hs_info->psw_data_headset->sdev);
devm_kfree(&pdev->dev, hs_info);
return 0;
}
static void pm886_headset_shutdown(struct platform_device *pdev)
{
devm_free_irq(&pdev->dev, hs_info->irq_headset, hs_info);
devm_free_irq(&pdev->dev, hs_info->irq_hook, hs_info);
pm886_headset_remove(pdev);
return;
}
static struct platform_driver pm886_headset_driver = {
.probe = pm886_headset_probe,
.remove = pm886_headset_remove,
.shutdown = pm886_headset_shutdown,
.suspend = pm886_headset_suspend,
.resume = pm886_headset_resume,
.driver = {
.name = "88pm88x-headset",
.owner = THIS_MODULE,
},
};
module_platform_driver(pm886_headset_driver);
MODULE_DESCRIPTION("Marvell 88PM886 Headset driver");
MODULE_AUTHOR("Zhao Ye <zhaoy@marvell.com>");
MODULE_LICENSE("GPL");