blob: 45f96850c4909c782f266b586de626e933b18efd [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2019 Amlogic, Inc. All rights reserved.
*
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/of_device.h>
#include <linux/pm_wakeirq.h>
//#include <linux/amlogic/pm.h>
#include <sound/pcm.h>
#include "pwrdet.h"
#include "pwrdet_hw.h"
#include "ddr_mngr.h"
#define DRV_NAME "power-detect"
static struct aml_pwrdet *s_pwrdet;
static irqreturn_t pwrdet_isr_handler(int irq, void *data)
{
int det_val = audiobus_read
(EE_AUDIO_POW_DET_VALUE + s_pwrdet->chipinfo->address_shift);
int acc_data = det_val & 0x03ffffff;
pr_info("%s, Status:%d, acc_data:0x%x, status2:0x%x\n",
__func__, (det_val & 0x80000000) >> 31,
acc_data, audiobus_read(EE_AUDIO_TODDR_A_STATUS2));
return IRQ_HANDLED;
}
static void pwrdet_free_irq(struct aml_pwrdet *p_pwrdet)
{
free_irq(p_pwrdet->irq, p_pwrdet);
}
static int pwrdet_request_irq(struct aml_pwrdet *p_pwrdet)
{
int ret;
ret = request_irq(p_pwrdet->irq, pwrdet_isr_handler,
IRQF_SHARED, "power-detect", p_pwrdet);
if (ret) {
pr_err("failed to claim irq %u, ret: %d\n", p_pwrdet->irq, ret);
return ret;
}
return ret;
}
void pwrdet_set(bool enable)
{
pwrdet_threshold(s_pwrdet->chipinfo->address_shift,
s_pwrdet->hi_th, s_pwrdet->lo_th);
pwrdet_downsample_ctrl(true, true, 3, 0x20);
aml_pwrdet_enable(enable, PDMIN);
pr_info("pwrdet,ctrl:0x%x, hi:0x%x, lo:0x%x\n",
audiobus_read(EE_AUDIO_POW_DET_CTRL0),
audiobus_read(EE_AUDIO_POW_DET_TH_HI),
audiobus_read(EE_AUDIO_POW_DET_TH_LO));
}
static ssize_t pwrdet_enable_show(struct class *cla,
struct class_attribute *attr, char *buf)
{
int enable = 1;
pwrdet_set(true);
/* power detect irq */
pwrdet_request_irq(s_pwrdet);
return sprintf(buf, "%d\n", enable);
}
static struct class_attribute pwrdet_attrs[] = {
__ATTR_RO(pwrdet_enable),
__ATTR_NULL
};
static struct class pwrdet_class = {
.name = DRV_NAME,
.class_attrs = pwrdet_attrs,
};
static struct pwrdet_chipinfo axg_pwrdet_chipinfo = {
.address_shift = 0,
};
static struct pwrdet_chipinfo g12a_pwrdet_chipinfo = {
.address_shift = 1,
.matchid_fn = true,
};
static const struct of_device_id aml_pwrdet_device_id[] = {
{
.compatible = "amlogic, axg-power-detect",
.data = &axg_pwrdet_chipinfo,
},
{
.compatible = "amlogic, g12a-power-detect",
.data = &g12a_pwrdet_chipinfo,
},
{},
};
MODULE_DEVICE_TABLE(of, aml_pwrdet_device_id);
static int aml_pwrdet_platform_probe(struct platform_device *pdev)
{
struct aml_pwrdet *p_pwrdet;
struct device *dev = &pdev->dev;
struct device_node *node = dev->of_node;
struct pwrdet_chipinfo *p_chipinfo;
int ret;
p_pwrdet = devm_kzalloc(&pdev->dev,
sizeof(struct aml_pwrdet),
GFP_KERNEL);
if (!p_pwrdet) {
/*dev_err(&pdev->dev, "Can't allocate pcm_p\n");*/
ret = -ENOMEM;
goto fail;
}
/* match data */
p_chipinfo = (struct pwrdet_chipinfo *)
of_device_get_match_data(dev);
if (!p_chipinfo)
dev_warn_once(dev, "check to update power detect chipinfo\n");
p_pwrdet->chipinfo = p_chipinfo;
/* irq */
p_pwrdet->irq = of_irq_get_byname(node, "pwrdet_irq");
if (p_pwrdet->irq < 0) {
pr_err("Can't get power detect irq number\n");
ret = -EINVAL;
goto fail;
}
ret = of_property_read_u32(node, "pwrdet_src",
&p_pwrdet->det_src);
if (ret) {
pr_err("failed to get det_src\n");
ret = -EINVAL;
goto fail;
}
ret = of_property_read_u32(node, "hi_th",
&p_pwrdet->hi_th);
if (ret) {
pr_err("failed to get hi_th\n");
ret = -EINVAL;
goto fail;
}
ret = of_property_read_u32(node, "lo_th",
&p_pwrdet->lo_th);
if (ret) {
pr_err("failed to get lo_th\n");
ret = -EINVAL;
goto fail;
}
dev_set_drvdata(dev, p_pwrdet);
s_pwrdet = p_pwrdet;
pr_info("parse power det src:%d, \thi_th:0x%x, lo_th:0x%x\n",
p_pwrdet->det_src,
p_pwrdet->hi_th,
p_pwrdet->lo_th);
ret = class_register(&pwrdet_class);
if (ret < 0) {
pr_err("Create pwrdet class failed\n");
ret = -EEXIST;
goto fail;
}
return 0;
fail:
return ret;
}
static int aml_pwrdet_platform_remove(struct platform_device *pdev)
{
return 0;
}
static int aml_pwrdet_platform_suspend(struct platform_device *pdev,
pm_message_t state)
{
struct device *dev = &pdev->dev;
struct aml_pwrdet *p_pwrdet = dev_get_drvdata(dev);
pr_info("%s entry freeze:%d\n", __func__, is_pm_freeze_mode());
/*whether in freeze*/
if (!is_pm_freeze_mode())
return 0;
pwrdet_set(true);
/* power detect irq */
pwrdet_request_irq(p_pwrdet);
/* pm would wakeup kernel */
device_init_wakeup(dev, 1);
dev_pm_set_wake_irq(dev, p_pwrdet->irq);
return 0;
}
static int aml_pwrdet_platform_resume(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct aml_pwrdet *p_pwrdet = dev_get_drvdata(dev);
pr_info("%s freeze:%d\n", __func__, is_pm_freeze_mode());
/* pm clean irq */
dev_pm_clear_wake_irq(dev);
/* free irq */
pwrdet_free_irq(p_pwrdet);
return 0;
}
struct platform_driver aml_pwrdet = {
.driver = {
.name = DRV_NAME,
.of_match_table = aml_pwrdet_device_id,
},
.probe = aml_pwrdet_platform_probe,
.remove = aml_pwrdet_platform_remove,
.suspend = aml_pwrdet_platform_suspend,
.resume = aml_pwrdet_platform_resume,
};
int __init pwrdet_init(void)
{
return platform_driver_register(&aml_pwrdet);
}
void __exit pwrdet_exit(void)
{
platform_driver_unregister(&aml_pwrdet);
}
#ifndef MODULE
module_init(pwrdet_init);
module_exit(pwrdet_exit);
/* Module information */
MODULE_AUTHOR("Amlogic, Inc.");
MODULE_DESCRIPTION("ALSA Soc Aml Audio Power detect");
MODULE_LICENSE("GPL v2");
#endif