1433 lines
		
	
	
		
			38 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1433 lines
		
	
	
		
			38 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * aw87xxx.c  aw87xxx pa module
 | |
|  *
 | |
|  * Copyright (c) 2021 AWINIC Technology CO., LTD
 | |
|  *
 | |
|  * Author: Barry <zhaozhongbo@awinic.com>
 | |
|  *
 | |
|  * This program is free software; you can redistribute  it and/or modify it
 | |
|  * under  the terms of  the GNU General  Public License as published by the
 | |
|  * Free Software Foundation;  either version 2 of the  License, or (at your
 | |
|  * option) any later version.
 | |
|  *
 | |
|  */
 | |
| 
 | |
| #include <linux/i2c.h>
 | |
| #include <sound/pcm.h>
 | |
| #include <sound/pcm_params.h>
 | |
| #include <linux/gpio.h>
 | |
| #include <linux/of_gpio.h>
 | |
| #include <linux/interrupt.h>
 | |
| #include <linux/delay.h>
 | |
| #include <linux/module.h>
 | |
| #include <linux/kernel.h>
 | |
| #include <linux/device.h>
 | |
| #include <linux/irq.h>
 | |
| #include <linux/firmware.h>
 | |
| #include <linux/platform_device.h>
 | |
| #include <linux/slab.h>
 | |
| #include <linux/fs.h>
 | |
| #include <linux/proc_fs.h>
 | |
| #include <linux/uaccess.h>
 | |
| #include <linux/io.h>
 | |
| #include <linux/init.h>
 | |
| #include <linux/pci.h>
 | |
| #include <linux/dma-mapping.h>
 | |
| #include <linux/gameport.h>
 | |
| #include <linux/moduleparam.h>
 | |
| #include <linux/mutex.h>
 | |
| #include <linux/timer.h>
 | |
| #include <linux/workqueue.h>
 | |
| #include <linux/hrtimer.h>
 | |
| #include <linux/ktime.h>
 | |
| #include <linux/kthread.h>
 | |
| #include <uapi/sound/asound.h>
 | |
| #include <sound/control.h>
 | |
| #include <sound/soc.h>
 | |
| #include "aw87xxx.h"
 | |
| #include "aw_device.h"
 | |
| #include "aw_log.h"
 | |
| #include "aw_monitor.h"
 | |
| #include "aw_acf_bin.h"
 | |
| #include "aw_bin_parse.h"
 | |
| 
 | |
| /*****************************************************************
 | |
| * aw87xxx marco
 | |
| ******************************************************************/
 | |
| #define AW87XXX_I2C_NAME	"aw87xxx_pa"
 | |
| #define AW87XXX_DRIVER_VERSION	"v2.2.0"
 | |
| #define AW87XXX_FW_BIN_NAME	"aw87xxx_acf.bin"
 | |
| 
 | |
| /*************************************************************************
 | |
|  * aw87xxx variable
 | |
|  ************************************************************************/
 | |
| static LIST_HEAD(g_aw87xxx_list);
 | |
| static DEFINE_MUTEX(g_aw87xxx_mutex_lock);
 | |
| unsigned int g_aw87xxx_dev_cnt = 0;
 | |
| 
 | |
| #ifdef AW_KERNEL_VER_OVER_4_19_1
 | |
| static struct aw_componet_codec_ops aw_componet_codec_ops = {
 | |
| 	.add_codec_controls = snd_soc_add_component_controls,
 | |
| 	.unregister_codec = snd_soc_unregister_component,
 | |
| };
 | |
| #else
 | |
| static struct aw_componet_codec_ops aw_componet_codec_ops = {
 | |
| 	.add_codec_controls = snd_soc_add_codec_controls,
 | |
| 	.unregister_codec = snd_soc_unregister_codec,
 | |
| };
 | |
| #endif
 | |
| 
 | |
| 
 | |
| /************************************************************************
 | |
|  *
 | |
|  * aw87xxx device update profile
 | |
|  *
 | |
|  ************************************************************************/
 | |
| static int aw87xxx_update_off_prof(struct aw87xxx *aw87xxx, char *profile)
 | |
| {
 | |
| 	int ret = 0;
 | |
| 	struct aw_prof_desc *prof_desc = NULL;
 | |
| 	struct aw_data_container *data_container = NULL;
 | |
| 	struct aw_device *aw_dev = &aw87xxx->aw_dev;
 | |
| 
 | |
| 	AW_DEV_LOGD(aw87xxx->dev, "enter");
 | |
| 
 | |
| 	mutex_lock(&aw87xxx->reg_lock);
 | |
| 
 | |
| 	aw_monitor_stop(&aw87xxx->monitor);
 | |
| 
 | |
| 	prof_desc = aw_acf_get_prof_desc_form_name(aw87xxx->dev, &aw87xxx->acf_info, profile);
 | |
| 	if (prof_desc == NULL)
 | |
| 		goto no_bin_pwr_off;
 | |
| 
 | |
| 	if (!prof_desc->prof_st)
 | |
| 		goto no_bin_pwr_off;
 | |
| 
 | |
| 
 | |
| 	data_container = &prof_desc->data_container;
 | |
| 	AW_DEV_LOGD(aw87xxx->dev, "get profile[%s] data len [%d]",
 | |
| 			profile, data_container->len);
 | |
| 
 | |
| 	if (aw_dev->hwen_status == AW_DEV_HWEN_OFF) {
 | |
| 		AW_DEV_LOGI(aw87xxx->dev, "profile[%s] has already load ", profile);
 | |
| 	} else {
 | |
| 		if (aw_dev->ops.pwr_off_func) {
 | |
| 			ret = aw_dev->ops.pwr_off_func(aw_dev, data_container);
 | |
| 			if (ret < 0) {
 | |
| 				AW_DEV_LOGE(aw87xxx->dev, "load profile[%s] failed ", profile);
 | |
| 				goto pwr_off_failed;
 | |
| 			}
 | |
| 		} else {
 | |
| 			ret = aw_dev_default_pwr_off(aw_dev, data_container);
 | |
| 			if (ret < 0) {
 | |
| 				AW_DEV_LOGE(aw87xxx->dev, "load profile[%s] failed ", profile);
 | |
| 				goto pwr_off_failed;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	aw87xxx->current_profile = prof_desc->prof_name;
 | |
| 	mutex_unlock(&aw87xxx->reg_lock);
 | |
| 
 | |
| 	return 0;
 | |
| 
 | |
| pwr_off_failed:
 | |
| no_bin_pwr_off:
 | |
| 	aw_dev_hw_pwr_ctrl(&aw87xxx->aw_dev, false);
 | |
| 	aw87xxx->current_profile = aw87xxx->prof_off_name;
 | |
| 	mutex_unlock(&aw87xxx->reg_lock);
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| int aw87xxx_update_profile(struct aw87xxx *aw87xxx, char *profile)
 | |
| {
 | |
| 	int ret = -EINVAL;
 | |
| 	struct aw_prof_desc *prof_desc = NULL;
 | |
| 	struct aw_prof_info *prof_info = &aw87xxx->acf_info.prof_info;
 | |
| 	struct aw_data_container *data_container = NULL;
 | |
| 	struct aw_device *aw_dev = &aw87xxx->aw_dev;
 | |
| 
 | |
| 	AW_DEV_LOGD(aw87xxx->dev, "enter");
 | |
| 
 | |
| 	if (!prof_info->status) {
 | |
| 		AW_DEV_LOGE(aw87xxx->dev, "profile_cfg not load");
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	if (0 == strncmp(profile, aw87xxx->prof_off_name, AW_PROFILE_STR_MAX))
 | |
| 		return aw87xxx_update_off_prof(aw87xxx, profile);
 | |
| 
 | |
| 	mutex_lock(&aw87xxx->reg_lock);
 | |
| 
 | |
| 	prof_desc = aw_acf_get_prof_desc_form_name(aw87xxx->dev, &aw87xxx->acf_info, profile);
 | |
| 	if (prof_desc == NULL) {
 | |
| 		AW_DEV_LOGE(aw87xxx->dev, "not found [%s] parameter", profile);
 | |
| 		mutex_unlock(&aw87xxx->reg_lock);
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	if (!prof_desc->prof_st) {
 | |
| 		AW_DEV_LOGE(aw87xxx->dev, "not found data container");
 | |
| 		mutex_unlock(&aw87xxx->reg_lock);
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	data_container = &prof_desc->data_container;
 | |
| 	AW_DEV_LOGD(aw87xxx->dev, "get profile[%s] data len [%d]",
 | |
| 			profile, data_container->len);
 | |
| 
 | |
| 	aw_monitor_stop(&aw87xxx->monitor);
 | |
| 
 | |
| 	if (aw_dev->ops.pwr_on_func) {
 | |
| 		ret = aw_dev->ops.pwr_on_func(aw_dev, data_container);
 | |
| 		if (ret < 0) {
 | |
| 			AW_DEV_LOGE(aw87xxx->dev, "load profile[%s] failed ",
 | |
| 				profile);
 | |
| 			mutex_unlock(&aw87xxx->reg_lock);
 | |
| 			return aw87xxx_update_off_prof(aw87xxx, aw87xxx->prof_off_name);
 | |
| 		}
 | |
| 	} else {
 | |
| 		ret = aw_dev_default_pwr_on(aw_dev, data_container);
 | |
| 		if (ret < 0) {
 | |
| 			AW_DEV_LOGE(aw87xxx->dev, "load profile[%s] failed ",
 | |
| 				profile);
 | |
| 			mutex_unlock(&aw87xxx->reg_lock);
 | |
| 			return aw87xxx_update_off_prof(aw87xxx, aw87xxx->prof_off_name);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	aw87xxx->current_profile = prof_desc->prof_name;
 | |
| 	aw_monitor_start(&aw87xxx->monitor);
 | |
| 	mutex_unlock(&aw87xxx->reg_lock);
 | |
| 
 | |
| 	AW_DEV_LOGD(aw87xxx->dev, "load profile[%s] succeed", profile);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| char *aw87xxx_show_current_profile(int dev_index)
 | |
| {
 | |
| 	struct list_head *pos = NULL;
 | |
| 	struct aw87xxx *aw87xxx = NULL;
 | |
| 
 | |
| 	list_for_each(pos, &g_aw87xxx_list) {
 | |
| 		aw87xxx = list_entry(pos, struct aw87xxx, list);
 | |
| 		if (aw87xxx == NULL) {
 | |
| 			AW_LOGE("struct aw87xxx not ready");
 | |
| 			return NULL;
 | |
| 		}
 | |
| 
 | |
| 		if (aw87xxx->dev_index == dev_index) {
 | |
| 			AW_DEV_LOGI(aw87xxx->dev, "current profile is [%s]",
 | |
| 				aw87xxx->current_profile);
 | |
| 			return aw87xxx->current_profile;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	AW_LOGE("not found struct aw87xxx, dev_index = [%d]", dev_index);
 | |
| 	return NULL;
 | |
| }
 | |
| EXPORT_SYMBOL(aw87xxx_show_current_profile);
 | |
| 
 | |
| int aw87xxx_set_profile(int dev_index, char *profile)
 | |
| {
 | |
| 	struct list_head *pos = NULL;
 | |
| 	struct aw87xxx *aw87xxx = NULL;
 | |
| 
 | |
| 	list_for_each(pos, &g_aw87xxx_list) {
 | |
| 		aw87xxx = list_entry(pos, struct aw87xxx, list);
 | |
| 		if (aw87xxx == NULL) {
 | |
| 			AW_LOGE("struct aw87xxx not ready");
 | |
| 			return -EINVAL;
 | |
| 		}
 | |
| 
 | |
| 		if (profile && aw87xxx->dev_index == dev_index)
 | |
| 			return aw87xxx_update_profile(aw87xxx, profile);
 | |
| 	}
 | |
| 
 | |
| 	AW_LOGE("not found struct aw87xxx, dev_index = [%d]", dev_index);
 | |
| 	return -EINVAL;
 | |
| }
 | |
| EXPORT_SYMBOL(aw87xxx_set_profile);
 | |
| 
 | |
| /************************************************************************
 | |
|  *
 | |
|  * aw87xxx esd update profile
 | |
|  *
 | |
|  ************************************************************************/
 | |
| static int aw87xxx_esd_update_off_prof(struct aw87xxx *aw87xxx, char *profile)
 | |
| {
 | |
| 	int ret = 0;
 | |
| 	struct aw_prof_desc *prof_desc = NULL;
 | |
| 	struct aw_data_container *data_container = NULL;
 | |
| 	struct aw_device *aw_dev = &aw87xxx->aw_dev;
 | |
| 
 | |
| 	AW_DEV_LOGD(aw87xxx->dev, "enter");
 | |
| 	prof_desc = aw_acf_get_prof_desc_form_name(aw87xxx->dev, &aw87xxx->acf_info, profile);
 | |
| 	if (prof_desc == NULL)
 | |
| 		goto no_bin_pwr_off;
 | |
| 
 | |
| 	if (!prof_desc->prof_st)
 | |
| 		goto no_bin_pwr_off;
 | |
| 
 | |
| 	data_container = &prof_desc->data_container;
 | |
| 	AW_DEV_LOGD(aw87xxx->dev, "get profile[%s] data len [%d]",
 | |
| 			profile, data_container->len);
 | |
| 
 | |
| 	if (aw_dev->hwen_status == AW_DEV_HWEN_OFF) {
 | |
| 		AW_DEV_LOGI(aw87xxx->dev, "profile[%s] has already load ", profile);
 | |
| 	} else {
 | |
| 		if (aw_dev->ops.pwr_off_func) {
 | |
| 			ret = aw_dev->ops.pwr_off_func(aw_dev, data_container);
 | |
| 			if (ret < 0) {
 | |
| 				AW_DEV_LOGE(aw87xxx->dev, "load profile[%s] failed ", profile);
 | |
| 				goto pwr_off_failed;
 | |
| 			}
 | |
| 		} else {
 | |
| 			ret = aw_dev_default_pwr_off(aw_dev, data_container);
 | |
| 			if (ret < 0) {
 | |
| 				AW_DEV_LOGE(aw87xxx->dev, "load profile[%s] failed ", profile);
 | |
| 				goto pwr_off_failed;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	aw87xxx->current_profile = prof_desc->prof_name;
 | |
| 	return 0;
 | |
| 
 | |
| pwr_off_failed:
 | |
| no_bin_pwr_off:
 | |
| 	aw_dev_hw_pwr_ctrl(&aw87xxx->aw_dev, false);
 | |
| 	aw87xxx->current_profile = aw87xxx->prof_off_name;
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| int aw87xxx_esd_update_profile(struct aw87xxx *aw87xxx, char *profile)
 | |
| {
 | |
| 	int ret = -EINVAL;
 | |
| 	struct aw_prof_desc *prof_desc = NULL;
 | |
| 	struct aw_prof_info *prof_info = &aw87xxx->acf_info.prof_info;
 | |
| 	struct aw_data_container *data_container = NULL;
 | |
| 	struct aw_device *aw_dev = &aw87xxx->aw_dev;
 | |
| 
 | |
| 	AW_DEV_LOGD(aw87xxx->dev, "enter");
 | |
| 
 | |
| 	if (!prof_info->status) {
 | |
| 		AW_DEV_LOGE(aw87xxx->dev, "profile_cfg not load");
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	if (0 == strncmp(profile, aw87xxx->prof_off_name, AW_PROFILE_STR_MAX))
 | |
| 		return aw87xxx_esd_update_off_prof(aw87xxx, profile);
 | |
| 
 | |
| 	prof_desc = aw_acf_get_prof_desc_form_name(aw87xxx->dev, &aw87xxx->acf_info,
 | |
| 					profile);
 | |
| 	if (prof_desc == NULL) {
 | |
| 		AW_DEV_LOGE(aw87xxx->dev, "not found [%s] parameter", profile);
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	if (!prof_desc->prof_st) {
 | |
| 		AW_DEV_LOGE(aw87xxx->dev, "not found data container");
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	data_container = &prof_desc->data_container;
 | |
| 	AW_DEV_LOGD(aw87xxx->dev, "get profile[%s] data len [%d]",
 | |
| 			profile, data_container->len);
 | |
| 
 | |
| 	if (aw_dev->ops.pwr_on_func) {
 | |
| 		ret = aw_dev->ops.pwr_on_func(aw_dev, data_container);
 | |
| 		if (ret < 0) {
 | |
| 			AW_DEV_LOGE(aw87xxx->dev, "load profile[%s] failed ",
 | |
| 				profile);
 | |
| 			return ret;
 | |
| 		}
 | |
| 	} else {
 | |
| 		ret = aw_dev_default_pwr_on(aw_dev, data_container);
 | |
| 		if (ret < 0) {
 | |
| 			AW_DEV_LOGE(aw87xxx->dev, "load profile[%s] failed ",
 | |
| 				profile);
 | |
| 			return ret;
 | |
| 		}
 | |
| 	}
 | |
| 	aw87xxx->current_profile = prof_desc->prof_name;
 | |
| 	AW_DEV_LOGD(aw87xxx->dev, "recover load profile[%s] succeed", profile);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /****************************************************************************
 | |
|  *
 | |
|  * aw87xxx Kcontrols
 | |
|  *
 | |
|  ****************************************************************************/
 | |
| static int aw87xxx_profile_switch_info(struct snd_kcontrol *kcontrol,
 | |
| 			struct snd_ctl_elem_info *uinfo)
 | |
| {
 | |
| 	int count = 0;
 | |
| 	char *name = NULL;
 | |
| 	char *profile_name = NULL;
 | |
| 	struct aw87xxx *aw87xxx = (struct aw87xxx *)kcontrol->private_value;
 | |
| 
 | |
| 	if (aw87xxx == NULL) {
 | |
| 		AW_LOGE("get struct aw87xxx failed");
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
 | |
| 	uinfo->count = 1;
 | |
| 
 | |
| 	/*make sure have prof */
 | |
| 	count = aw_acf_get_profile_count(aw87xxx->dev, &aw87xxx->acf_info);
 | |
| 	if (count <= 0) {
 | |
| 		uinfo->value.enumerated.items = 0;
 | |
| 		AW_DEV_LOGE(aw87xxx->dev, "get count[%d] failed", count);
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	uinfo->value.enumerated.items = count;
 | |
| 	if (uinfo->value.enumerated.item >= count)
 | |
| 		uinfo->value.enumerated.item = count - 1;
 | |
| 
 | |
| 	name = uinfo->value.enumerated.name;
 | |
| 	count = uinfo->value.enumerated.item;
 | |
| 	profile_name = aw_acf_get_prof_name_form_index(aw87xxx->dev,
 | |
| 		&aw87xxx->acf_info, count);
 | |
| 	if (profile_name == NULL) {
 | |
| 		strlcpy(uinfo->value.enumerated.name, "NULL",
 | |
| 			strlen("NULL") + 1);
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	strlcpy(name, profile_name, sizeof(uinfo->value.enumerated.name));
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int aw87xxx_profile_switch_put(struct snd_kcontrol *kcontrol,
 | |
| 			struct snd_ctl_elem_value *ucontrol)
 | |
| {
 | |
| 	int ret = -1;
 | |
| 	char *profile_name = NULL;
 | |
| 	int index = ucontrol->value.integer.value[0];
 | |
| 	struct aw87xxx *aw87xxx = (struct aw87xxx *)kcontrol->private_value;
 | |
| 	struct acf_bin_info *acf_info = &aw87xxx->acf_info;
 | |
| 
 | |
| 	if (aw87xxx == NULL) {
 | |
| 		AW_LOGE("get struct aw87xxx failed");
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	profile_name = aw_acf_get_prof_name_form_index(aw87xxx->dev, acf_info, index);
 | |
| 	if (!profile_name) {
 | |
| 		AW_DEV_LOGE(aw87xxx->dev, "not found profile name,index=[%d]",
 | |
| 				index);
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	AW_DEV_LOGI(aw87xxx->dev, "set profile [%s]", profile_name);
 | |
| 
 | |
| 	ret = aw87xxx_update_profile(aw87xxx, profile_name);
 | |
| 	if (ret < 0) {
 | |
| 		AW_DEV_LOGE(aw87xxx->dev, "set dev_index[%d] profile failed, profile = %s",
 | |
| 			aw87xxx->dev_index, profile_name);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int aw87xxx_profile_switch_get(struct snd_kcontrol *kcontrol,
 | |
| 			struct snd_ctl_elem_value *ucontrol)
 | |
| {
 | |
| 	int index = 0;
 | |
| 	char *profile;
 | |
| 	struct aw87xxx *aw87xxx = (struct aw87xxx *)kcontrol->private_value;
 | |
| 
 | |
| 	if (aw87xxx == NULL) {
 | |
| 		AW_LOGE("get struct aw87xxx failed");
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	if (!aw87xxx->current_profile) {
 | |
| 		AW_DEV_LOGE(aw87xxx->dev, "profile not init");
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	profile = aw87xxx->current_profile;
 | |
| 	AW_DEV_LOGI(aw87xxx->dev, "current profile:[%s]",
 | |
| 		aw87xxx->current_profile);
 | |
| 
 | |
| 
 | |
| 	index = aw_acf_get_prof_index_form_name(aw87xxx->dev,
 | |
| 		&aw87xxx->acf_info, aw87xxx->current_profile);
 | |
| 	if (index < 0) {
 | |
| 		AW_DEV_LOGE(aw87xxx->dev, "get profile index failed");
 | |
| 		return index;
 | |
| 	}
 | |
| 
 | |
| 	ucontrol->value.integer.value[0] = index;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int aw87xxx_vmax_get_info(struct snd_kcontrol *kcontrol,
 | |
| 			struct snd_ctl_elem_info *uinfo)
 | |
| {
 | |
| 	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
 | |
| 	uinfo->count = 1;
 | |
| 	uinfo->value.integer.min = INT_MIN;
 | |
| 	uinfo->value.integer.max = AW_VMAX_MAX;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int aw87xxxx_vmax_get(struct snd_kcontrol *kcontrol,
 | |
| 			struct snd_ctl_elem_value *ucontrol)
 | |
| {
 | |
| 	int ret = -1;
 | |
| 	int vmax_val = 0;
 | |
| 	struct aw87xxx *aw87xxx = (struct aw87xxx *)kcontrol->private_value;
 | |
| 
 | |
| 	if (aw87xxx == NULL) {
 | |
| 		AW_LOGE("get struct aw87xxx failed");
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	ret = aw_monitor_no_dsp_get_vmax(&aw87xxx->monitor, &vmax_val);
 | |
| 	if (ret < 0)
 | |
| 		return ret;
 | |
| 
 | |
| 	ucontrol->value.integer.value[0] = vmax_val;
 | |
| 	AW_DEV_LOGI(aw87xxx->dev, "get vmax = [0x%x]", vmax_val);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int aw87xxx_kcontrol_dynamic_create(struct aw87xxx *aw87xxx,
 | |
| 						void *codec)
 | |
| {
 | |
| 	struct snd_kcontrol_new *aw87xxx_kcontrol = NULL;
 | |
| 	aw_snd_soc_codec_t *soc_codec = (aw_snd_soc_codec_t *)codec;
 | |
| 	char *kctl_name[AW87XXX_KCONTROL_NUM];
 | |
| 	int kcontrol_num = AW87XXX_KCONTROL_NUM;
 | |
| 	int ret = -1;
 | |
| 
 | |
| 	AW_DEV_LOGD(aw87xxx->dev, "enter");
 | |
| 	aw87xxx->codec = soc_codec;
 | |
| 
 | |
| 	aw87xxx_kcontrol = devm_kzalloc(aw87xxx->dev,
 | |
| 			sizeof(struct snd_kcontrol_new) * kcontrol_num,
 | |
| 			GFP_KERNEL);
 | |
| 	if (aw87xxx_kcontrol == NULL) {
 | |
| 		AW_DEV_LOGE(aw87xxx->dev, "aw87xxx_kcontrol devm_kzalloc failed");
 | |
| 		return -ENOMEM;
 | |
| 	}
 | |
| 
 | |
| 	kctl_name[0] = devm_kzalloc(aw87xxx->dev, AW_NAME_BUF_MAX,
 | |
| 			GFP_KERNEL);
 | |
| 	if (kctl_name[0] == NULL)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	snprintf(kctl_name[0], AW_NAME_BUF_MAX, "aw87xxx_profile_switch_%d",
 | |
| 			aw87xxx->dev_index);
 | |
| 
 | |
| 	aw87xxx_kcontrol[0].name = kctl_name[0];
 | |
| 	aw87xxx_kcontrol[0].iface = SNDRV_CTL_ELEM_IFACE_MIXER;
 | |
| 	aw87xxx_kcontrol[0].info = aw87xxx_profile_switch_info;
 | |
| 	aw87xxx_kcontrol[0].get = aw87xxx_profile_switch_get;
 | |
| 	aw87xxx_kcontrol[0].put = aw87xxx_profile_switch_put;
 | |
| 	aw87xxx_kcontrol[0].private_value = (unsigned long)aw87xxx;
 | |
| 
 | |
| 	kctl_name[1] = devm_kzalloc(aw87xxx->codec->dev, AW_NAME_BUF_MAX,
 | |
| 			GFP_KERNEL);
 | |
| 	if (kctl_name[1] == NULL)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	snprintf(kctl_name[1], AW_NAME_BUF_MAX, "aw87xxx_vmax_get_%d",
 | |
| 			aw87xxx->dev_index);
 | |
| 
 | |
| 	aw87xxx_kcontrol[1].name = kctl_name[1];
 | |
| 	aw87xxx_kcontrol[1].iface = SNDRV_CTL_ELEM_IFACE_MIXER;
 | |
| 	aw87xxx_kcontrol[1].access = SNDRV_CTL_ELEM_ACCESS_READ;
 | |
| 	aw87xxx_kcontrol[1].info = aw87xxx_vmax_get_info;
 | |
| 	aw87xxx_kcontrol[1].get = aw87xxxx_vmax_get;
 | |
| 	aw87xxx_kcontrol[1].private_value = (unsigned long)aw87xxx;
 | |
| 
 | |
| 	ret = aw_componet_codec_ops.add_codec_controls(aw87xxx->codec,
 | |
| 				aw87xxx_kcontrol, kcontrol_num);
 | |
| 	if (ret < 0) {
 | |
| 		AW_DEV_LOGE(aw87xxx->dev, "add codec controls failed, ret = %d",
 | |
| 			ret);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	AW_DEV_LOGI(aw87xxx->dev, "add codec controls[%s,%s]",
 | |
| 		aw87xxx_kcontrol[0].name,
 | |
| 		aw87xxx_kcontrol[1].name);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /****************************************************************************
 | |
|  *
 | |
|  *aw87xxx kcontrol create
 | |
|  *
 | |
|  ****************************************************************************/
 | |
| int aw87xxx_add_codec_controls(void *codec)
 | |
| {
 | |
| 	struct list_head *pos = NULL;
 | |
| 	struct aw87xxx *aw87xxx = NULL;
 | |
| 	int ret = -1;
 | |
| 
 | |
| 	list_for_each(pos, &g_aw87xxx_list) {
 | |
| 		aw87xxx = list_entry(pos, struct aw87xxx, list);
 | |
| 		if (aw87xxx == NULL) {
 | |
| 			AW_LOGE("struct aw87xxx not ready");
 | |
| 			return ret;
 | |
| 		}
 | |
| 
 | |
| 		ret = aw87xxx_kcontrol_dynamic_create(aw87xxx, codec);
 | |
| 		if (ret < 0)
 | |
| 			return ret;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| EXPORT_SYMBOL(aw87xxx_add_codec_controls);
 | |
| 
 | |
| 
 | |
| /****************************************************************************
 | |
|  *
 | |
|  * aw87xxx firmware cfg load
 | |
|  *
 | |
|  ***************************************************************************/
 | |
| static void aw87xxx_fw_cfg_free(struct aw87xxx *aw87xxx)
 | |
| {
 | |
| 	AW_DEV_LOGD(aw87xxx->dev, "enter");
 | |
| 	aw_acf_profile_free(aw87xxx->dev, &aw87xxx->acf_info);
 | |
| 	aw_monitor_cfg_free(&aw87xxx->monitor);
 | |
| }
 | |
| 
 | |
| static int aw87xxx_init_default_prof(struct aw87xxx *aw87xxx)
 | |
| {
 | |
| 	char *profile = NULL;
 | |
| 
 | |
| 	profile = aw_acf_get_prof_off_name(aw87xxx->dev, &aw87xxx->acf_info);
 | |
| 	if (profile == NULL) {
 | |
| 		AW_DEV_LOGE(aw87xxx->dev, "get profile off name failed");
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	snprintf(aw87xxx->prof_off_name, AW_PROFILE_STR_MAX, "%s", profile);
 | |
| 	aw87xxx->current_profile = profile;
 | |
| 	AW_DEV_LOGI(aw87xxx->dev, "init profile name [%s]",
 | |
| 		aw87xxx->current_profile);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static void aw87xxx_fw_load_retry(struct aw87xxx *aw87xxx)
 | |
| {
 | |
| 	struct acf_bin_info *acf_info = &aw87xxx->acf_info;
 | |
| 	int ram_timer_val = 2000;
 | |
| 
 | |
| 	AW_DEV_LOGD(aw87xxx->dev, "failed to read [%s]",
 | |
| 			aw87xxx->fw_name);
 | |
| 
 | |
| 	if (acf_info->load_count < AW_LOAD_FW_RETRIES) {
 | |
| 		AW_DEV_LOGD(aw87xxx->dev,
 | |
| 			"restart hrtimer to load firmware");
 | |
| 		schedule_delayed_work(&aw87xxx->fw_load_work,
 | |
| 			msecs_to_jiffies(ram_timer_val));
 | |
| 	} else {
 | |
| 		acf_info->load_count = 0;
 | |
| 		AW_DEV_LOGE(aw87xxx->dev,
 | |
| 			"can not load firmware,please check name or file exists");
 | |
| 		return;
 | |
| 	}
 | |
| 	acf_info->load_count++;
 | |
| }
 | |
| 
 | |
| static void aw87xxx_fw_load(const struct firmware *fw, void *context)
 | |
| {
 | |
| 	int ret = -1;
 | |
| 	struct aw87xxx *aw87xxx = context;
 | |
| 	struct acf_bin_info *acf_info = &aw87xxx->acf_info;
 | |
| 
 | |
| 	AW_DEV_LOGD(aw87xxx->dev, "enter");
 | |
| 
 | |
| 	if (!fw) {
 | |
| 		aw87xxx_fw_load_retry(aw87xxx);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	AW_DEV_LOGD(aw87xxx->dev, "loaded %s - size: %ld",
 | |
| 		aw87xxx->fw_name, (u_long)(fw ? fw->size : 0));
 | |
| 
 | |
| 	mutex_lock(&aw87xxx->reg_lock);
 | |
| 	acf_info->fw_data = vmalloc(fw->size);
 | |
| 	if (!acf_info->fw_data) {
 | |
| 		AW_DEV_LOGE(aw87xxx->dev, "fw_data kzalloc memory failed");
 | |
| 		goto exit_vmalloc_failed;
 | |
| 	}
 | |
| 	memset(acf_info->fw_data, 0, fw->size);
 | |
| 	memcpy(acf_info->fw_data, fw->data, fw->size);
 | |
| 	acf_info->fw_size = fw->size;
 | |
| 
 | |
| 	ret = aw_acf_parse(aw87xxx->dev, &aw87xxx->acf_info);
 | |
| 	if (ret < 0) {
 | |
| 		AW_DEV_LOGE(aw87xxx->dev, "fw_data parse failed");
 | |
| 		goto exit_acf_parse_failed;
 | |
| 	}
 | |
| 
 | |
| 	ret = aw87xxx_init_default_prof(aw87xxx);
 | |
| 	if (ret < 0) {
 | |
| 		aw87xxx_fw_cfg_free(aw87xxx);
 | |
| 		goto exit_acf_parse_failed;
 | |
| 	}
 | |
| 
 | |
| 	AW_DEV_LOGI(aw87xxx->dev, "acf parse succeed");
 | |
| 	mutex_unlock(&aw87xxx->reg_lock);
 | |
| 	release_firmware(fw);
 | |
| 	return;
 | |
| 
 | |
| exit_acf_parse_failed:
 | |
| exit_vmalloc_failed:
 | |
| 	release_firmware(fw);
 | |
| 	mutex_unlock(&aw87xxx->reg_lock);
 | |
| }
 | |
| 
 | |
| static void aw87xxx_fw_load_work_routine(struct work_struct *work)
 | |
| {
 | |
| 	struct aw87xxx *aw87xxx = container_of(work,
 | |
| 			struct aw87xxx, fw_load_work.work);
 | |
| 	struct aw_prof_info *prof_info = &aw87xxx->acf_info.prof_info;
 | |
| 
 | |
| 	AW_DEV_LOGD(aw87xxx->dev, "enter");
 | |
| 
 | |
| 	if (prof_info->status == AW_ACF_WAIT) {
 | |
| 		const struct firmware *fw;
 | |
| 		int ret;
 | |
| 
 | |
| 		ret = request_firmware(&fw, aw87xxx->fw_name, aw87xxx->dev);
 | |
| 		if (!ret) {
 | |
| 			AW_DEV_LOGD(aw87xxx->dev, "loader firmware %s success\n", aw87xxx->fw_name);
 | |
| 			aw87xxx_fw_load(fw, aw87xxx);
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void aw87xxx_fw_load_init(struct aw87xxx *aw87xxx)
 | |
| {
 | |
| #ifdef AW_CFG_UPDATE_DELAY
 | |
| 	int cfg_timer_val = AW_CFG_UPDATE_DELAY_TIMER;
 | |
| #else
 | |
| 	int cfg_timer_val = 0;
 | |
| #endif
 | |
| 	AW_DEV_LOGI(aw87xxx->dev, "enter");
 | |
| 	snprintf(aw87xxx->fw_name, AW87XXX_FW_NAME_MAX, "%s", AW87XXX_FW_BIN_NAME);
 | |
| 	aw_acf_init(&aw87xxx->aw_dev, &aw87xxx->acf_info, aw87xxx->dev_index);
 | |
| 
 | |
| 	INIT_DELAYED_WORK(&aw87xxx->fw_load_work, aw87xxx_fw_load_work_routine);
 | |
| 	schedule_delayed_work(&aw87xxx->fw_load_work,
 | |
| 			msecs_to_jiffies(cfg_timer_val));
 | |
| }
 | |
| 
 | |
| /****************************************************************************
 | |
|  *
 | |
|  *aw87xxx attribute node
 | |
|  *
 | |
|  ****************************************************************************/
 | |
| static ssize_t aw87xxx_attr_get_reg(struct device *dev,
 | |
| 			struct device_attribute *attr, char *buf)
 | |
| {
 | |
| 	ssize_t len = 0;
 | |
| 	int ret = 0;
 | |
| 	unsigned int i = 0;
 | |
| 	unsigned char reg_val = 0;
 | |
| 	struct aw87xxx *aw87xxx = dev_get_drvdata(dev);
 | |
| 	struct aw_device *aw_dev = &aw87xxx->aw_dev;
 | |
| 
 | |
| 	mutex_lock(&aw87xxx->reg_lock);
 | |
| 	for (i = 0; i < aw_dev->reg_max_addr; i++) {
 | |
| 		if (!(aw_dev->reg_access[i] & AW_DEV_REG_RD_ACCESS))
 | |
| 			continue;
 | |
| 		ret = aw_dev_i2c_read_byte(&aw87xxx->aw_dev, i, ®_val);
 | |
| 		if (ret < 0) {
 | |
| 			len += snprintf(buf + len, PAGE_SIZE - len,
 | |
| 					"read reg [0x%x] failed\n", i);
 | |
| 			AW_DEV_LOGE(aw87xxx->dev, "read reg [0x%x] failed", i);
 | |
| 		} else {
 | |
| 			len += snprintf(buf + len, PAGE_SIZE - len,
 | |
| 					"reg:0x%02X=0x%02X\n", i, reg_val);
 | |
| 			AW_DEV_LOGD(aw87xxx->dev, "reg:0x%02X=0x%02X",
 | |
| 					i, reg_val);
 | |
| 		}
 | |
| 	}
 | |
| 	mutex_unlock(&aw87xxx->reg_lock);
 | |
| 
 | |
| 	return len;
 | |
| }
 | |
| 
 | |
| static ssize_t aw87xxx_attr_set_reg(struct device *dev,
 | |
| 			struct device_attribute *attr, const char *buf,
 | |
| 			size_t len)
 | |
| {
 | |
| 	unsigned int databuf[2] = { 0 };
 | |
| 	int ret = 0;
 | |
| 	struct aw87xxx *aw87xxx = dev_get_drvdata(dev);
 | |
| 
 | |
| 	mutex_lock(&aw87xxx->reg_lock);
 | |
| 	if (sscanf(buf, "0x%x 0x%x", &databuf[0], &databuf[1]) == 2) {
 | |
| 		if (databuf[0] >= aw87xxx->aw_dev.reg_max_addr) {
 | |
| 			AW_DEV_LOGE(aw87xxx->dev, "set reg[0x%x] error,is out of reg_addr_max[0x%x]",
 | |
| 				databuf[0], aw87xxx->aw_dev.reg_max_addr);
 | |
| 			mutex_unlock(&aw87xxx->reg_lock);
 | |
| 			return -EINVAL;
 | |
| 		}
 | |
| 
 | |
| 		ret = aw_dev_i2c_write_byte(&aw87xxx->aw_dev,
 | |
| 					databuf[0], databuf[1]);
 | |
| 		if (ret < 0)
 | |
| 			AW_DEV_LOGE(aw87xxx->dev, "set [0x%x]=0x%x failed",
 | |
| 				databuf[0], databuf[1]);
 | |
| 		else
 | |
| 			AW_DEV_LOGD(aw87xxx->dev, "set [0x%x]=0x%x succeed",
 | |
| 				databuf[0], databuf[1]);
 | |
| 	} else {
 | |
| 		AW_DEV_LOGE(aw87xxx->dev, "i2c write cmd input error");
 | |
| 	}
 | |
| 	mutex_unlock(&aw87xxx->reg_lock);
 | |
| 
 | |
| 	return len;
 | |
| }
 | |
| 
 | |
| static ssize_t aw87xxx_attr_get_profile(struct device *dev,
 | |
| 			struct device_attribute *attr, char *buf)
 | |
| {
 | |
| 	ssize_t len = 0;
 | |
| 	unsigned int i = 0;
 | |
| 	struct aw87xxx *aw87xxx = dev_get_drvdata(dev);
 | |
| 	struct aw_prof_info *prof_info = &aw87xxx->acf_info.prof_info;
 | |
| 
 | |
| 	if (!prof_info->status) {
 | |
| 		len += snprintf(buf + len, PAGE_SIZE - len,
 | |
| 				"profile_cfg not load\n");
 | |
| 		return len;
 | |
| 	}
 | |
| 
 | |
| 	AW_DEV_LOGI(aw87xxx->dev, "current profile:[%s]", aw87xxx->current_profile);
 | |
| 
 | |
| 	for (i = 0; i < prof_info->count; i++) {
 | |
| 		if (!strncmp(aw87xxx->current_profile, prof_info->prof_name_list[i],
 | |
| 				AW_PROFILE_STR_MAX))
 | |
| 			len += snprintf(buf + len, PAGE_SIZE - len,
 | |
| 				">%s\n", prof_info->prof_name_list[i]);
 | |
| 		else
 | |
| 			len += snprintf(buf + len, PAGE_SIZE - len,
 | |
| 				" %s\n", prof_info->prof_name_list[i]);
 | |
| 	}
 | |
| 
 | |
| 	return len;
 | |
| }
 | |
| 
 | |
| static ssize_t aw87xxx_attr_set_profile(struct device *dev,
 | |
| 			struct device_attribute *attr, const char *buf,
 | |
| 			size_t len)
 | |
| {
 | |
| 	char profile[AW_PROFILE_STR_MAX] = {0};
 | |
| 	int ret = 0;
 | |
| 	struct aw87xxx *aw87xxx = dev_get_drvdata(dev);
 | |
| 
 | |
| 	if (sscanf(buf, "%s", profile) == 1) {
 | |
| 		AW_DEV_LOGD(aw87xxx->dev, "set profile [%s]", profile);
 | |
| 		ret = aw87xxx_update_profile(aw87xxx, profile);
 | |
| 		if (ret < 0) {
 | |
| 			AW_DEV_LOGE(aw87xxx->dev, "set profile[%s] failed",
 | |
| 				profile);
 | |
| 			return ret;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return len;
 | |
| }
 | |
| 
 | |
| static ssize_t aw87xxx_attr_get_hwen(struct device *dev,
 | |
| 				struct device_attribute *attr, char *buf)
 | |
| {
 | |
| 	ssize_t len = 0;
 | |
| 	struct aw87xxx *aw87xxx = dev_get_drvdata(dev);
 | |
| 	int hwen = aw87xxx->aw_dev.hwen_status;
 | |
| 
 | |
| 	if (hwen >= AW_DEV_HWEN_INVALID)
 | |
| 		len += snprintf(buf + len, PAGE_SIZE - len, "hwen_status: invalid\n");
 | |
| 	else if (hwen == AW_DEV_HWEN_ON)
 | |
| 		len += snprintf(buf + len, PAGE_SIZE - len, "hwen_status: on\n");
 | |
| 	else if (hwen == AW_DEV_HWEN_OFF)
 | |
| 		len += snprintf(buf + len, PAGE_SIZE - len, "hwen_status: off\n");
 | |
| 
 | |
| 	return len;
 | |
| }
 | |
| 
 | |
| static ssize_t aw87xxx_attr_set_hwen(struct device *dev,
 | |
| 				struct device_attribute *attr, const char *buf,
 | |
| 				size_t len)
 | |
| {
 | |
| 	int ret = -1;
 | |
| 	unsigned int state;
 | |
| 	struct aw87xxx *aw87xxx = dev_get_drvdata(dev);
 | |
| 
 | |
| 	ret = kstrtouint(buf, 0, &state);
 | |
| 	if (ret) {
 | |
| 		AW_DEV_LOGE(aw87xxx->dev, "fail to channelge str to int");
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	mutex_lock(&aw87xxx->reg_lock);
 | |
| 	if (state == AW_DEV_HWEN_OFF)
 | |
| 		aw_dev_hw_pwr_ctrl(&aw87xxx->aw_dev, false); /*OFF*/
 | |
| 	else if (state == AW_DEV_HWEN_ON)
 | |
| 		aw_dev_hw_pwr_ctrl(&aw87xxx->aw_dev, true); /*ON*/
 | |
| 	else
 | |
| 		AW_DEV_LOGE(aw87xxx->dev, "input [%d] error, hwen_on=[%d],hwen_off=[%d]",
 | |
| 			state, AW_DEV_HWEN_ON, AW_DEV_HWEN_OFF);
 | |
| 	mutex_unlock(&aw87xxx->reg_lock);
 | |
| 	return len;
 | |
| }
 | |
| 
 | |
| int aw87xxx_awrw_write(struct aw87xxx *aw87xxx,
 | |
| 			const char *buf, size_t count)
 | |
| {
 | |
| 	int i = 0, ret = -1;
 | |
| 	char *data_buf = NULL;
 | |
| 	int buf_len = 0;
 | |
| 	int temp_data = 0;
 | |
| 	int data_str_size = 0;
 | |
| 	char *reg_data;
 | |
| 	struct aw_i2c_packet *packet = &aw87xxx->i2c_packet;
 | |
| 
 | |
| 	AW_DEV_LOGD(aw87xxx->dev, "enter");
 | |
| 	/* one addr or one data string Composition of Contains two bytes of symbol(0X)*/
 | |
| 	/* and two byte of hexadecimal data*/
 | |
| 	data_str_size = 2 + 2 * AWRW_DATA_BYTES;
 | |
| 
 | |
| 	/* The buf includes the first address of the register to be written and all data */
 | |
| 	buf_len = AWRW_ADDR_BYTES + packet->reg_num * AWRW_DATA_BYTES;
 | |
| 	AW_DEV_LOGI(aw87xxx->dev, "buf_len = %d,reg_num = %d", buf_len, packet->reg_num);
 | |
| 	data_buf = vmalloc(buf_len);
 | |
| 	if (data_buf == NULL) {
 | |
| 		AW_DEV_LOGE(aw87xxx->dev, "alloc memory failed");
 | |
| 		return -ENOMEM;
 | |
| 	}
 | |
| 	memset(data_buf, 0, buf_len);
 | |
| 
 | |
| 	data_buf[0] = packet->reg_addr;
 | |
| 	reg_data = data_buf + 1;
 | |
| 
 | |
| 	AW_DEV_LOGD(aw87xxx->dev, "reg_addr: 0x%02x", data_buf[0]);
 | |
| 
 | |
| 	/*ag:0x00 0x01 0x01 0x01 0x01 0x00\x0a*/
 | |
| 	for (i = 0; i < packet->reg_num; i++) {
 | |
| 		ret = sscanf(buf + AWRW_HDR_LEN + 1 + i * (data_str_size + 1),
 | |
| 			"0x%x", &temp_data);
 | |
| 		if (ret != 1) {
 | |
| 			AW_DEV_LOGE(aw87xxx->dev, "sscanf failed,ret=%d", ret);
 | |
| 			return ret;
 | |
| 		}
 | |
| 		reg_data[i] = temp_data;
 | |
| 		AW_DEV_LOGD(aw87xxx->dev, "[%d] : 0x%02x", i, reg_data[i]);
 | |
| 	}
 | |
| 
 | |
| 	mutex_lock(&aw87xxx->reg_lock);
 | |
| 	ret = i2c_master_send(aw87xxx->aw_dev.i2c, data_buf, buf_len);
 | |
| 	if (ret < 0) {
 | |
| 		AW_DEV_LOGE(aw87xxx->dev, "write failed");
 | |
| 		vfree(data_buf);
 | |
| 		data_buf = NULL;
 | |
| 		mutex_unlock(&aw87xxx->reg_lock);
 | |
| 		return -EFAULT;
 | |
| 	}
 | |
| 	mutex_unlock(&aw87xxx->reg_lock);
 | |
| 
 | |
| 	vfree(data_buf);
 | |
| 	data_buf = NULL;
 | |
| 
 | |
| 	AW_DEV_LOGD(aw87xxx->dev, "down");
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int aw87xxx_awrw_data_check(struct aw87xxx *aw87xxx,
 | |
| 			int *data, size_t count)
 | |
| {
 | |
| 	struct aw_i2c_packet *packet = &aw87xxx->i2c_packet;
 | |
| 	int req_data_len = 0;
 | |
| 	int act_data_len = 0;
 | |
| 	int data_str_size = 0;
 | |
| 
 | |
| 	if ((data[AWRW_HDR_ADDR_BYTES] != AWRW_ADDR_BYTES) ||
 | |
| 		(data[AWRW_HDR_DATA_BYTES] != AWRW_DATA_BYTES)) {
 | |
| 		AW_DEV_LOGE(aw87xxx->dev, "addr_bytes [%d] or data_bytes [%d] unsupport",
 | |
| 			data[AWRW_HDR_ADDR_BYTES], data[AWRW_HDR_DATA_BYTES]);
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	/* one data string Composition of Contains two bytes of symbol(0x)*/
 | |
| 	/* and two byte of hexadecimal data*/
 | |
| 	data_str_size = 2 + 2 * AWRW_DATA_BYTES;
 | |
| 	act_data_len = count - AWRW_HDR_LEN - 1;
 | |
| 
 | |
| 	/* There is a comma(,) or space between each piece of data */
 | |
| 	if (data[AWRW_HDR_WR_FLAG] == AWRW_FLAG_WRITE) {
 | |
| 		/*ag:0x00 0x01 0x01 0x01 0x01 0x00\x0a*/
 | |
| 		req_data_len = (data_str_size + 1) * packet->reg_num;
 | |
| 		if (req_data_len > act_data_len) {
 | |
| 			AW_DEV_LOGE(aw87xxx->dev, "data_len checkfailed,requeset data_len [%d],actaul data_len [%d]",
 | |
| 				req_data_len, act_data_len);
 | |
| 			return -EINVAL;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /* flag addr_bytes data_bytes reg_num reg_addr*/
 | |
| static int aw87xxx_awrw_parse_buf(struct aw87xxx *aw87xxx,
 | |
| 			const char *buf, size_t count, int *wr_status)
 | |
| {
 | |
| 	int data[AWRW_HDR_MAX] = {0};
 | |
| 	struct aw_i2c_packet *packet = &aw87xxx->i2c_packet;
 | |
| 	int ret = -1;
 | |
| 
 | |
| 	if (sscanf(buf, "0x%02x 0x%02x 0x%02x 0x%02x 0x%02x",
 | |
| 		&data[AWRW_HDR_WR_FLAG], &data[AWRW_HDR_ADDR_BYTES],
 | |
| 		&data[AWRW_HDR_DATA_BYTES], &data[AWRW_HDR_REG_NUM],
 | |
| 		&data[AWRW_HDR_REG_ADDR]) == 5) {
 | |
| 
 | |
| 		packet->reg_addr = data[AWRW_HDR_REG_ADDR];
 | |
| 		packet->reg_num = data[AWRW_HDR_REG_NUM];
 | |
| 		*wr_status = data[AWRW_HDR_WR_FLAG];
 | |
| 		ret = aw87xxx_awrw_data_check(aw87xxx, data, count);
 | |
| 		if (ret < 0)
 | |
| 			return ret;
 | |
| 
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	return -EINVAL;
 | |
| }
 | |
| 
 | |
| static ssize_t aw87xxx_attr_awrw_store(struct device *dev,
 | |
| 	struct device_attribute *attr, const char *buf, size_t count)
 | |
| {
 | |
| 	struct aw87xxx *aw87xxx = dev_get_drvdata(dev);
 | |
| 	struct aw_i2c_packet *packet = &aw87xxx->i2c_packet;
 | |
| 	int wr_status = 0;
 | |
| 	int ret = -1;
 | |
| 
 | |
| 	if (count < AWRW_HDR_LEN) {
 | |
| 		AW_DEV_LOGE(aw87xxx->dev, "data count too smaller, please check write format");
 | |
| 		AW_DEV_LOGE(aw87xxx->dev, "string %s,count=%ld",
 | |
| 			buf, (u_long)count);
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	AW_DEV_LOGI(aw87xxx->dev, "string:[%s],count=%ld", buf, (u_long)count);
 | |
| 	ret = aw87xxx_awrw_parse_buf(aw87xxx, buf, count, &wr_status);
 | |
| 	if (ret < 0) {
 | |
| 		AW_DEV_LOGE(aw87xxx->dev, "can not parse string");
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	if (wr_status == AWRW_FLAG_WRITE) {
 | |
| 		ret = aw87xxx_awrw_write(aw87xxx, buf, count);
 | |
| 		if (ret < 0)
 | |
| 			return ret;
 | |
| 	} else if (wr_status == AWRW_FLAG_READ) {
 | |
| 		packet->status = AWRW_I2C_ST_READ;
 | |
| 		AW_DEV_LOGI(aw87xxx->dev, "read_cmd:reg_addr[0x%02x], reg_num[%d]",
 | |
| 			packet->reg_addr, packet->reg_num);
 | |
| 	} else {
 | |
| 		AW_DEV_LOGE(aw87xxx->dev, "please check str format, unsupport read_write_status: %d",
 | |
| 			wr_status);
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	return count;
 | |
| }
 | |
| 
 | |
| static ssize_t aw87xxx_attr_awrw_show(struct device *dev,
 | |
| 	struct device_attribute *attr, char *buf)
 | |
| {
 | |
| 	struct aw87xxx *aw87xxx = dev_get_drvdata(dev);
 | |
| 	struct aw_i2c_packet *packet = &aw87xxx->i2c_packet;
 | |
| 	int data_len = 0;
 | |
| 	size_t len = 0;
 | |
| 	int ret = -1, i = 0;
 | |
| 	char *reg_data = NULL;
 | |
| 
 | |
| 	if (packet->status != AWRW_I2C_ST_READ) {
 | |
| 		AW_DEV_LOGE(aw87xxx->dev, "please write read cmd first");
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	data_len = AWRW_DATA_BYTES * packet->reg_num;
 | |
| 	reg_data = (char *)vmalloc(data_len);
 | |
| 	if (reg_data == NULL) {
 | |
| 		AW_DEV_LOGE(aw87xxx->dev, "memory alloc failed");
 | |
| 		ret = -EINVAL;
 | |
| 		goto exit;
 | |
| 	}
 | |
| 
 | |
| 	mutex_lock(&aw87xxx->reg_lock);
 | |
| 	ret = aw_dev_i2c_read_msg(&aw87xxx->aw_dev, packet->reg_addr,
 | |
| 				(char *)reg_data, data_len);
 | |
| 	if (ret < 0) {
 | |
| 		ret = -EFAULT;
 | |
| 		mutex_unlock(&aw87xxx->reg_lock);
 | |
| 		goto exit;
 | |
| 	}
 | |
| 	mutex_unlock(&aw87xxx->reg_lock);
 | |
| 
 | |
| 	AW_DEV_LOGI(aw87xxx->dev, "reg_addr 0x%02x, reg_num %d",
 | |
| 		packet->reg_addr, packet->reg_num);
 | |
| 
 | |
| 	for (i = 0; i < data_len; i++) {
 | |
| 		len += snprintf(buf + len, PAGE_SIZE - len,
 | |
| 			"0x%02x,", reg_data[i]);
 | |
| 		AW_DEV_LOGI(aw87xxx->dev, "0x%02x", reg_data[i]);
 | |
| 	}
 | |
| 
 | |
| 	ret = len;
 | |
| 
 | |
| exit:
 | |
| 	if (reg_data) {
 | |
| 		vfree(reg_data);
 | |
| 		reg_data = NULL;
 | |
| 	}
 | |
| 	packet->status = AWRW_I2C_ST_NONE;
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static ssize_t aw87xxx_drv_ver_show(struct device *dev,
 | |
| 	struct device_attribute *attr, char *buf)
 | |
| {
 | |
| 	ssize_t len = 0;
 | |
| 
 | |
| 	len += snprintf(buf + len, PAGE_SIZE - len,
 | |
| 		"driver_ver: %s \n", AW87XXX_DRIVER_VERSION);
 | |
| 
 | |
| 	return len;
 | |
| }
 | |
| 
 | |
| static DEVICE_ATTR(reg, S_IWUSR | S_IRUGO,
 | |
| 		aw87xxx_attr_get_reg, aw87xxx_attr_set_reg);
 | |
| static DEVICE_ATTR(profile, S_IWUSR | S_IRUGO,
 | |
| 		aw87xxx_attr_get_profile, aw87xxx_attr_set_profile);
 | |
| static DEVICE_ATTR(hwen, S_IWUSR | S_IRUGO,
 | |
| 		aw87xxx_attr_get_hwen, aw87xxx_attr_set_hwen);
 | |
| static DEVICE_ATTR(awrw, S_IWUSR | S_IRUGO,
 | |
| 	aw87xxx_attr_awrw_show, aw87xxx_attr_awrw_store);
 | |
| static DEVICE_ATTR(drv_ver, S_IRUGO, aw87xxx_drv_ver_show, NULL);
 | |
| 
 | |
| static struct attribute *aw87xxx_attributes[] = {
 | |
| 	&dev_attr_reg.attr,
 | |
| 	&dev_attr_profile.attr,
 | |
| 	&dev_attr_hwen.attr,
 | |
| 	&dev_attr_awrw.attr,
 | |
| 	&dev_attr_drv_ver.attr,
 | |
| 	NULL
 | |
| };
 | |
| 
 | |
| static struct attribute_group aw87xxx_attribute_group = {
 | |
| 	.attrs = aw87xxx_attributes
 | |
| };
 | |
| 
 | |
| /****************************************************************************
 | |
|  *
 | |
|  *aw87xxx device probe
 | |
|  *
 | |
|  ****************************************************************************/
 | |
| 
 | |
| int aw87xxx_dtsi_dev_index_check(struct aw87xxx *cur_aw87xxx)
 | |
| {
 | |
| 	struct list_head *pos = NULL;
 | |
| 	struct aw87xxx *list_aw87xxx = NULL;
 | |
| 
 | |
| 	list_for_each(pos, &g_aw87xxx_list) {
 | |
| 		list_aw87xxx = list_entry(pos, struct aw87xxx, list);
 | |
| 		if (list_aw87xxx == NULL)
 | |
| 			continue;
 | |
| 
 | |
| 		if (list_aw87xxx->dev_index == cur_aw87xxx->dev_index) {
 | |
| 			AW_DEV_LOGE(cur_aw87xxx->dev, "dev_index has already existing,check failed");
 | |
| 			return -EINVAL;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int aw87xxx_dtsi_parse(struct aw87xxx *aw87xxx,
 | |
| 				struct device_node *dev_node)
 | |
| {
 | |
| 	int ret = -1;
 | |
| 	int32_t dev_index = -EINVAL;
 | |
| 
 | |
| 	ret = of_property_read_u32(dev_node, "dev_index", &dev_index);
 | |
| 	if (ret < 0) {
 | |
| 		AW_DEV_LOGI(aw87xxx->dev, "dev_index parse failed, user default[%d], ret=%d",
 | |
| 				g_aw87xxx_dev_cnt, ret);
 | |
| 		aw87xxx->dev_index = g_aw87xxx_dev_cnt;
 | |
| 	} else {
 | |
| 		aw87xxx->dev_index = dev_index;
 | |
| 		AW_DEV_LOGI(aw87xxx->dev, "parse dev_index=[%d]",
 | |
| 				aw87xxx->dev_index);
 | |
| 	}
 | |
| 
 | |
| 	ret = aw87xxx_dtsi_dev_index_check(aw87xxx);
 | |
| 	if (ret < 0)
 | |
| 		return ret;
 | |
| 
 | |
| 	ret = of_get_named_gpio(dev_node, "reset-gpio", 0);
 | |
| 	if (ret < 0) {
 | |
| 		AW_DEV_LOGI(aw87xxx->dev, "no reset gpio provided, hardware reset unavailable");
 | |
| 		aw87xxx->aw_dev.rst_gpio = AW_NO_RESET_GPIO;
 | |
| 		aw87xxx->aw_dev.hwen_status = AW_DEV_HWEN_INVALID;
 | |
| 	} else {
 | |
| 		aw87xxx->aw_dev.rst_gpio = ret;
 | |
| 		aw87xxx->aw_dev.hwen_status = AW_DEV_HWEN_OFF;
 | |
| 		AW_DEV_LOGI(aw87xxx->dev, "reset gpio[%d] parse succeed", ret);
 | |
| 		if (gpio_is_valid(aw87xxx->aw_dev.rst_gpio)) {
 | |
| 			ret = devm_gpio_request_one(aw87xxx->dev,
 | |
| 					aw87xxx->aw_dev.rst_gpio,
 | |
| 					GPIOF_OUT_INIT_LOW, "aw87xxx_reset");
 | |
| 			if (ret < 0) {
 | |
| 				AW_DEV_LOGE(aw87xxx->dev, "reset request failed");
 | |
| 				return ret;
 | |
| 			}
 | |
| 		}
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	/* In order to compatible with stereo share only one reset gpio */
 | |
| 	ret = of_get_named_gpio(dev_node, "reset-shared-gpio", 0);
 | |
| 	if (ret < 0) {
 | |
| 		AW_DEV_LOGI(aw87xxx->dev, "no reset shared gpio provided, hardware reset unavailable");
 | |
| 		aw87xxx->aw_dev.rst_shared_gpio = AW_NO_RESET_GPIO;
 | |
| 	} else {
 | |
| 		aw87xxx->aw_dev.rst_shared_gpio = ret;
 | |
| 		AW_DEV_LOGI(aw87xxx->dev, "reset shared gpio[%d] parse succeed", ret);
 | |
| 		if (gpio_is_valid(aw87xxx->aw_dev.rst_shared_gpio)) {
 | |
| 			ret = devm_gpio_request_one(aw87xxx->dev,
 | |
| 					aw87xxx->aw_dev.rst_shared_gpio,
 | |
| 					GPIOF_OUT_INIT_LOW, "aw87xxx_reset-shared");
 | |
| 			if (ret < 0) {
 | |
| 				AW_DEV_LOGE(aw87xxx->dev, "aw87xxx_reset-shared reset request failed");
 | |
| 				return ret;
 | |
| 			}
 | |
| 			msleep(20);
 | |
| 			gpio_set_value_cansleep(aw87xxx->aw_dev.rst_shared_gpio, AW_GPIO_HIGHT_LEVEL);
 | |
| 		}
 | |
| 	}
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static struct aw87xxx *aw87xxx_malloc_init(struct i2c_client *client)
 | |
| {
 | |
| 	struct aw87xxx *aw87xxx = NULL;
 | |
| 
 | |
| 	aw87xxx = devm_kzalloc(&client->dev, sizeof(struct aw87xxx),
 | |
| 			GFP_KERNEL);
 | |
| 	if (aw87xxx == NULL) {
 | |
| 		AW_DEV_LOGE(&client->dev, "failed to devm_kzalloc aw87xxx");
 | |
| 		return NULL;
 | |
| 	}
 | |
| 	memset(aw87xxx, 0, sizeof(struct aw87xxx));
 | |
| 
 | |
| 	aw87xxx->dev = &client->dev;
 | |
| 	aw87xxx->aw_dev.dev = &client->dev;
 | |
| 	aw87xxx->aw_dev.i2c_bus = client->adapter->nr;
 | |
| 	aw87xxx->aw_dev.i2c_addr = client->addr;
 | |
| 	aw87xxx->aw_dev.i2c = client;
 | |
| 	aw87xxx->aw_dev.hwen_status = false;
 | |
| 	aw87xxx->aw_dev.reg_access = NULL;
 | |
| 	aw87xxx->aw_dev.hwen_status = AW_DEV_HWEN_INVALID;
 | |
| 	aw87xxx->off_bin_status = AW87XXX_NO_OFF_BIN;
 | |
| 	aw87xxx->codec = NULL;
 | |
| 	aw87xxx->current_profile = aw87xxx->prof_off_name;
 | |
| 
 | |
| 	mutex_init(&aw87xxx->reg_lock);
 | |
| 
 | |
| 	AW_DEV_LOGI(&client->dev, "struct aw87xxx devm_kzalloc and init down");
 | |
| 	return aw87xxx;
 | |
| }
 | |
| 
 | |
| static int aw87xxx_probe(struct snd_soc_component *component)
 | |
| {
 | |
| 	struct aw87xxx *aw87xxx = dev_get_drvdata(component->dev);
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = aw87xxx_kcontrol_dynamic_create(aw87xxx, component);
 | |
| 	if (ret < 0) {
 | |
| 		dev_err(component->dev, "%s: aw87xxx_add_codec_controls failed, ret= %d\n",
 | |
| 			__func__, ret);
 | |
| 		return ret;
 | |
| 	};
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static const struct snd_soc_component_driver aw87xxx_component_driver = {
 | |
| 	.probe = aw87xxx_probe,
 | |
| };
 | |
| 
 | |
| static int aw87xxx_i2c_probe(struct i2c_client *client,
 | |
| 				const struct i2c_device_id *id)
 | |
| {
 | |
| 	struct device_node *dev_node = client->dev.of_node;
 | |
| 	struct aw87xxx *aw87xxx = NULL;
 | |
| 	int ret = -1;
 | |
| 
 | |
| 	dev_info(&client->dev, "%s\n", __func__);
 | |
| 	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
 | |
| 		AW_DEV_LOGE(&client->dev, "check_functionality failed");
 | |
| 		return -ENODEV;
 | |
| 	}
 | |
| 
 | |
| 	/* aw87xxx i2c_dev struct init */
 | |
| 	aw87xxx = aw87xxx_malloc_init(client);
 | |
| 	if (aw87xxx == NULL)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	i2c_set_clientdata(client, aw87xxx);
 | |
| 
 | |
| 	/* aw87xxx dev_node parse */
 | |
| 	ret = aw87xxx_dtsi_parse(aw87xxx, dev_node);
 | |
| 	if (ret < 0)
 | |
| 		goto exit;
 | |
| 
 | |
| 	/*hw power on PA*/
 | |
| 	aw_dev_hw_pwr_ctrl(&aw87xxx->aw_dev, true);
 | |
| 
 | |
| 	/* aw87xxx devices private attributes init */
 | |
| 	ret = aw_dev_init(&aw87xxx->aw_dev);
 | |
| 	if (ret < 0)
 | |
| 		goto exit;
 | |
| 
 | |
| 	ret = devm_snd_soc_register_component(aw87xxx->dev, &aw87xxx_component_driver,
 | |
| 					      NULL, 0);
 | |
| 	if (ret < 0) {
 | |
| 		dev_err(aw87xxx->dev, "%s() register codec error %d\n",
 | |
| 			__func__, ret);
 | |
| 		goto exit;
 | |
| 	}
 | |
| 
 | |
| 	/*product register reset */
 | |
| 	aw_dev_soft_reset(&aw87xxx->aw_dev);
 | |
| 
 | |
| 	/*hw power off */
 | |
| 	aw_dev_hw_pwr_ctrl(&aw87xxx->aw_dev, false);
 | |
| 
 | |
| 	/* create debug attrbute nodes */
 | |
| 	ret = sysfs_create_group(&aw87xxx->dev->kobj, &aw87xxx_attribute_group);
 | |
| 	if (ret < 0)
 | |
| 		AW_DEV_LOGE(aw87xxx->dev, "failed to create sysfs nodes, will not allowed to use");
 | |
| 
 | |
| 	/* cfg_load init */
 | |
| 	aw87xxx_fw_load_init(aw87xxx);
 | |
| 
 | |
| 	/*monitor init*/
 | |
| 	aw_monitor_init(aw87xxx->dev, &aw87xxx->monitor, dev_node);
 | |
| 
 | |
| 	/*add device to total list */
 | |
| 	mutex_lock(&g_aw87xxx_mutex_lock);
 | |
| 	g_aw87xxx_dev_cnt++;
 | |
| 	list_add(&aw87xxx->list, &g_aw87xxx_list);
 | |
| 	mutex_unlock(&g_aw87xxx_mutex_lock);
 | |
| 
 | |
| 	AW_DEV_LOGI(aw87xxx->dev, "succeed");
 | |
| 
 | |
| 	return 0;
 | |
| exit:
 | |
| 	AW_DEV_LOGE(aw87xxx->dev, "pa init failed");
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static void aw87xxx_i2c_remove(struct i2c_client *client)
 | |
| {
 | |
| 	struct aw87xxx *aw87xxx = i2c_get_clientdata(client);
 | |
| 
 | |
| 	aw_monitor_exit(&aw87xxx->monitor);
 | |
| 
 | |
| 	/*rm attr node*/
 | |
| 	sysfs_remove_group(&aw87xxx->dev->kobj, &aw87xxx_attribute_group);
 | |
| 
 | |
| 	aw87xxx_fw_cfg_free(aw87xxx);
 | |
| 
 | |
| 	mutex_lock(&g_aw87xxx_mutex_lock);
 | |
| 	g_aw87xxx_dev_cnt--;
 | |
| 	list_del(&aw87xxx->list);
 | |
| 	mutex_unlock(&g_aw87xxx_mutex_lock);
 | |
| }
 | |
| 
 | |
| static void aw87xxx_i2c_shutdown(struct i2c_client *client)
 | |
| {
 | |
| 	struct aw87xxx *aw87xxx = i2c_get_clientdata(client);
 | |
| 
 | |
| 	AW_DEV_LOGI(&client->dev, "enter");
 | |
| 
 | |
| 	/*soft and hw power off*/
 | |
| 	aw87xxx_update_profile(aw87xxx, aw87xxx->prof_off_name);
 | |
| }
 | |
| 
 | |
| 
 | |
| static const struct i2c_device_id aw87xxx_i2c_id[] = {
 | |
| 	{AW87XXX_I2C_NAME, 0},
 | |
| 	{},
 | |
| };
 | |
| 
 | |
| static const struct of_device_id extpa_of_match[] = {
 | |
| 	{.compatible = "awinic,aw87xxx_pa"},
 | |
| 	{},
 | |
| };
 | |
| 
 | |
| static struct i2c_driver aw87xxx_i2c_driver = {
 | |
| 	.driver = {
 | |
| 		.owner = THIS_MODULE,
 | |
| 		.name = AW87XXX_I2C_NAME,
 | |
| 		.of_match_table = extpa_of_match,
 | |
| 		},
 | |
| 	.probe = aw87xxx_i2c_probe,
 | |
| 	.remove = aw87xxx_i2c_remove,
 | |
| 	.shutdown = aw87xxx_i2c_shutdown,
 | |
| 	.id_table = aw87xxx_i2c_id,
 | |
| };
 | |
| 
 | |
| static int __init aw87xxx_pa_init(void)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	AW_LOGI("driver version: %s", AW87XXX_DRIVER_VERSION);
 | |
| 
 | |
| 	ret = i2c_add_driver(&aw87xxx_i2c_driver);
 | |
| 	if (ret < 0) {
 | |
| 		AW_LOGE("Unable to register driver, ret= %d", ret);
 | |
| 		return ret;
 | |
| 	}
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static void __exit aw87xxx_pa_exit(void)
 | |
| {
 | |
| 	AW_LOGI("enter");
 | |
| 	i2c_del_driver(&aw87xxx_i2c_driver);
 | |
| }
 | |
| 
 | |
| module_init(aw87xxx_pa_init);
 | |
| module_exit(aw87xxx_pa_exit);
 | |
| 
 | |
| MODULE_AUTHOR("<zhaozhongbo@awinic.com>");
 | |
| MODULE_DESCRIPTION("awinic aw87xxx pa driver");
 | |
| MODULE_LICENSE("GPL v2");
 |