| /****************************************************************************** |
| * |
| * (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"); |