blob: 61f773b2692fd2125bd5c7dc508a8b91df777969 [file] [log] [blame]
/******************************************************************************
*
* (C)Copyright 2022 ASRMicro. All Rights Reserved.
*
* THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF ASRMicro.
* The copyright notice above does not evidence any actual or intended
* publication of such source code.
* This Module contains Proprietary Information of ASRMicro and should be
* treated as Confidential.
* The information in this file is provided for the exclusive use of the
* licensees of ASRMicro.
* Such users have the right to use, modify, and incorporate this code into
* products for purposes authorized by the license agreement provided they
* include this notice and the associated copyright notice with any such
* product.
* The information in this file is provided "AS IS" without warranty.
*
******************************************************************************/
/******************************************************************************
* Include files
******************************************************************************/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/of_device.h>
#include <linux/uaccess.h>
#include <linux/proc_fs.h>
#include <linux/fs.h>
#include <linux/debugfs.h>
#include <linux/gpio.h>
#include <linux/regulator/consumer.h>
#include <linux/pinctrl/consumer.h>
#include <linux/platform_device.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <asm/io.h>
#include <linux/interrupt.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/ioctl.h>
/******************************************************************************
* Macro definitions
******************************************************************************/
#define AUDIO_PA_MAGIC 'y'
#define PA_ENABLE _IO(AUDIO_PA_MAGIC, 'a')
#define PA_DISABLE _IO(AUDIO_PA_MAGIC, 'b')
/******************************************************************************
* Local variable definitions
******************************************************************************/
static int gpio_pa = -1;
static struct dentry *audio_pa_control = NULL;
static char msg[10];
static dev_t audio_pa_dev = 0;
static struct class *audio_pa_class;
static struct cdev audio_pa_cdev;
//When AP boot up, AP PA driver is initialized first, and the PA is controlled by the AP at first.
//After CP starts, AP audiostub attempts to transfer control of the PA to CP.
static bool pa_by_cp = false;
/******************************************************************************
* function definitions
******************************************************************************/
int audio_pa_get_gpio_pin(void)
{
if (gpio_pa >= 0) {
printk(KERN_INFO "%s/L%d, Audio PA GPIO pin is %d.\n", __FUNCTION__, __LINE__, gpio_pa);
return gpio_pa;
}
else {
printk(KERN_INFO "%s/L%d, Audio PA not ready.\n", __FUNCTION__, __LINE__);
return -1;
}
}
int audio_pa_controlled_by_cp(u16 on_off)
{
//printk(KERN_INFO "%s/L%d on_off=%d.\n", __FUNCTION__, __LINE__, on_off);
if (on_off) {
pa_by_cp = true;
printk(KERN_INFO "%s/L%d, Audio PA controlled by CP.\n", __FUNCTION__, __LINE__);
return 0;
}
else {
if (gpio_pa >= 0) {
pa_by_cp = false;
printk(KERN_INFO "%s/L%d, Audio PA controlled by AP.\n", __FUNCTION__, __LINE__);
return 0;
}
else {
printk(KERN_INFO "%s/L%d, Audio PA not ready.\n", __FUNCTION__, __LINE__);
return -1;
}
}
}
EXPORT_SYMBOL_GPL(audio_pa_get_gpio_pin);
EXPORT_SYMBOL_GPL(audio_pa_controlled_by_cp);
/******************************************************************************
* functions for debugfs
******************************************************************************/
static ssize_t debugfs_read(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos)
{
printk(KERN_INFO "%s/L%d.\n", __FUNCTION__, __LINE__);
return 0;
}
static ssize_t debugfs_write(struct file *file,
const char __user *user_buf,
size_t count, loff_t *ppos)
{
int ret = 0;
size_t tmp_count = 0;
memset(msg, 0x00, sizeof(msg));
tmp_count = count;
if (tmp_count >= sizeof(msg)) {
tmp_count = sizeof(msg) - 1;
}
/* copy the content from user space to kernel space */
ret = copy_from_user(msg, user_buf, tmp_count);
if (ret) {
printk(KERN_ALERT "copy from user fail \n");
return -EFAULT;
}
switch (msg[0]) {
case '0':/* input command# echo 0 > /sys/kernel/debug/audio_pa */
printk(KERN_INFO "input %c to disable audio pa\n", msg[0]);
if (gpio_pa >= 0) {
gpio_direction_output(gpio_pa, 0);
}
break;
case '1':/* input command# echo 1 > /sys/kernel/debug/audio_pa */
printk(KERN_INFO "input %c to enable audio pa\n", msg[0]);
if (gpio_pa >= 0) {
gpio_direction_output(gpio_pa, 1);
}
break;
default:/* input command# */
printk(KERN_INFO "input invalid.\n");
break;
}
return tmp_count;
}
static const struct file_operations debugfs_ops = {
.owner = THIS_MODULE,
.open = simple_open,
.read = debugfs_read,
.write = debugfs_write,
};
static inline int audio_pa_debugfs_init(void)
{
audio_pa_control = debugfs_create_file("audio_pa", S_IRUGO | S_IFREG, NULL, NULL, &debugfs_ops);
if (audio_pa_control == NULL) {
pr_err("create audio_pa debugfs error!\n");
return -ENOENT;
}
else if (audio_pa_control == ERR_PTR(-ENODEV)) {
pr_err("CONFIG_DEBUG_FS is not enabled!\n");
return -ENOENT;
}
return 0;
}
static void audio_pa_debugfs_remove(void)
{
if (NULL != audio_pa_control) {
debugfs_remove_recursive(audio_pa_control);
}
return;
}
/******************************************************************************
* functions for cdev
******************************************************************************/
static long cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
//printk(KERN_INFO "%s/L%d, cmd = %d\n", __FUNCTION__, __LINE__, cmd);
if (pa_by_cp) {
return 0;
}
switch (cmd) {
case PA_ENABLE:
if (gpio_pa >= 0) {
printk(KERN_INFO "enable audio pa\n");
gpio_direction_output(gpio_pa, 1);
}
break;
case PA_DISABLE:
if (gpio_pa >= 0) {
printk(KERN_INFO "disable audio pa\n");
gpio_direction_output(gpio_pa, 0);
}
break;
default:
pr_info("Default\n");
break;
}
return 0;
}
static struct file_operations cdev_fops =
{
.owner = THIS_MODULE,
.open = simple_open,
.unlocked_ioctl = cdev_ioctl,
};
static int audio_pa_cdev_init(void)
{
/*Allocating Major number*/
if ((alloc_chrdev_region(&audio_pa_dev, 0, 1, "audio_pa_Dev")) < 0) {
pr_err("Cannot allocate major number\n");
return -1;
}
printk(KERN_INFO "%s/L%d, Major = %d, Minor = %d\n", __FUNCTION__, __LINE__, MAJOR(audio_pa_dev), MINOR(audio_pa_dev));
/*Creating cdev structure*/
cdev_init(&audio_pa_cdev, &cdev_fops);
/*Adding character device to the system*/
if ((cdev_add(&audio_pa_cdev, audio_pa_dev, 1)) < 0) {
pr_err("Cannot add the device to the system\n");
unregister_chrdev_region(audio_pa_dev, 1);
return -1;
}
/*Creating struct class*/
if ((audio_pa_class = class_create(THIS_MODULE, "audio_pa_class")) == NULL) {
pr_err("Cannot create the struct class\n");
unregister_chrdev_region(audio_pa_dev, 1);
return -2;
}
/*Creating device*/
if ((device_create(audio_pa_class, NULL, audio_pa_dev, NULL, "audio_pa")) == NULL) {
pr_err("Cannot create the Device 1\n");
class_destroy(audio_pa_class);
unregister_chrdev_region(audio_pa_dev, 1);
return -1;
}
return 0;
}
static void audio_pa_cdev_exit(void)
{
device_destroy(audio_pa_class, audio_pa_dev);
class_destroy(audio_pa_class);
cdev_del(&audio_pa_cdev);
unregister_chrdev_region(audio_pa_dev, 1);
pr_info("Device Driver Remove...Done!!!\n");
}
/******************************************************************************
* functions for platform_device
******************************************************************************/
static int audio_pa_probe(struct platform_device *pdev)
{
struct pinctrl *audio_pa_pinctrl = NULL;
struct pinctrl_state *pin_state = NULL;
struct device_node *audio_pa_node = NULL;
if (NULL == pdev) {
printk(KERN_INFO "Please check pdev input parameter for client.\n");
return 0;
}
audio_pa_pinctrl = devm_pinctrl_get(&pdev->dev);
if (NULL == audio_pa_pinctrl) {
printk(KERN_INFO "Please check codec input parameter for audio_pa_pinctrl.\n");
return 0;
}
pin_state = pinctrl_lookup_state(audio_pa_pinctrl, "default");
pinctrl_select_state(audio_pa_pinctrl, pin_state);
audio_pa_node = pdev->dev.of_node;
gpio_pa = of_get_named_gpio(audio_pa_node, "pa-gpio", 0);
if (gpio_pa < 0) {
printk(KERN_INFO "%s: pa-gpio of_get_named_gpio failed.\n", __FUNCTION__);
gpio_pa = -1;
}
printk(KERN_INFO "%s/L%d, gpio_pa = %d.\n", __FUNCTION__, __LINE__, gpio_pa);
/* PA */
if (gpio_pa >= 0) {
if (gpio_request(gpio_pa, "PA-switch")) {
printk(KERN_INFO "%s: pa-gpio gpio_request failed.\n", __FUNCTION__);
gpio_pa = -1;
}
else {
gpio_direction_output(gpio_pa, 0);
}
}
audio_pa_cdev_init();
audio_pa_debugfs_init();
return 0;
}
static int audio_pa_remove(struct platform_device *pdev)
{
printk(KERN_INFO "%s/L%d.\n", __FUNCTION__, __LINE__);
/* free gpio resource. */
if (gpio_pa >= 0) {
gpio_free(gpio_pa);
}
audio_pa_cdev_exit();
audio_pa_debugfs_remove();
return 0;
}
static const struct of_device_id audio_pa_dt_ids[] = {
{ .compatible = "asrmicro,audio-pa", },
{},
};
MODULE_DEVICE_TABLE(of, audio_pa_dt_ids);
static struct platform_driver audio_pa_driver = {
.driver = {
.name = "audio-pa",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(audio_pa_dt_ids),
},
.probe = audio_pa_probe,
.remove = audio_pa_remove,
};
/******************************************************************************
* functions for module
******************************************************************************/
static int audio_pa_init(void)
{
return platform_driver_register(&audio_pa_driver);
}
static void audio_pa_exit(void)
{
platform_driver_unregister(&audio_pa_driver);
}
module_init(audio_pa_init);
module_exit(audio_pa_exit);
MODULE_DESCRIPTION("Driver for Audio PA");
MODULE_AUTHOR("yjgwang@asrmicro.com");
MODULE_LICENSE("GPL");