blob: 3b87274e6c1960bfd35a88fd765a93d103131953 [file] [log] [blame]
/*
* drivers/amlogic/amaudio/amaudio.c
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
* 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.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
*/
#define pr_fmt(fmt) "amaudio: " fmt
#include <linux/version.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/string.h>
#include <linux/io.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/mutex.h>
#include <linux/device.h>
#include <linux/timer.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/platform_device.h>
#include <linux/uaccess.h>
#ifdef CONFIG_COMPAT
#include <linux/compat.h>
#endif
/* Amlogic headers */
#include <linux/amlogic/major.h>
#include <linux/amlogic/iomap.h>
#include <linux/amlogic/media/sound/aiu_regs.h>
#include <linux/amlogic/media/sound/audin_regs.h>
#include <linux/amlogic/media/sound/audio_iomap.h>
#include "amaudio.h"
#define AMAUDIO_DEVICE_COUNT ARRAY_SIZE(amaudio_ports)
static int amaudio_open(struct inode *inode, struct file *file);
static int amaudio_release(struct inode *inode, struct file *file);
static long amaudio_ioctl(struct file *file, unsigned int cmd,
unsigned long arg);
static int amaudio_utils_open(struct inode *inode, struct file *file);
static int amaudio_utils_release(struct inode *inode, struct file *file);
static long amaudio_utils_ioctl(struct file *file, unsigned int cmd,
unsigned long arg);
#ifdef CONFIG_COMPAT
static long amaudio_compat_ioctl(struct file *file, unsigned int cmd,
ulong arg);
static long amaudio_compat_utils_ioctl(struct file *file, unsigned int cmd,
ulong arg);
#endif
struct amaudio_port_t {
const char *name;
struct device *dev;
const struct file_operations *fops;
};
static unsigned int mute_left_right;
static unsigned int mute_unmute;
static struct class *amaudio_classp;
static struct device *amaudio_dev;
static const struct file_operations amaudio_ctl_fops = {
.owner = THIS_MODULE,
.open = amaudio_open,
.release = amaudio_release,
.unlocked_ioctl = amaudio_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = amaudio_compat_ioctl,
#endif
};
static const struct file_operations amaudio_utils_fops = {
.owner = THIS_MODULE,
.open = amaudio_utils_open,
.release = amaudio_utils_release,
.unlocked_ioctl = amaudio_utils_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = amaudio_compat_utils_ioctl,
#endif
};
static struct amaudio_port_t amaudio_ports[] = {
{
.name = "amaudio_ctl",
.fops = &amaudio_ctl_fops,
},
{
.name = "amaudio_utils",
.fops = &amaudio_utils_fops,
},
};
static const struct file_operations amaudio_fops = {
.owner = THIS_MODULE,
.open = amaudio_open,
.release = amaudio_release,
.unlocked_ioctl = amaudio_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = amaudio_compat_ioctl,
#endif
};
static int amaudio_open(struct inode *inode, struct file *file)
{
return 0;
}
static int amaudio_release(struct inode *inode, struct file *file)
{
return 0;
}
static long amaudio_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
s32 r = 0;
u32 reg;
switch (cmd) {
case AMAUDIO_IOC_SET_LEFT_MONO:
pr_info("set audio i2s left mono!\n");
audio_i2s_swap_left_right(1);
break;
case AMAUDIO_IOC_SET_RIGHT_MONO:
pr_info("set audio i2s right mono!\n");
audio_i2s_swap_left_right(2);
break;
case AMAUDIO_IOC_SET_STEREO:
pr_info("set audio i2s stereo!\n");
audio_i2s_swap_left_right(0);
break;
case AMAUDIO_IOC_SET_CHANNEL_SWAP:
pr_info("set audio i2s left/right swap!\n");
reg = read_i2s_mute_swap_reg();
if (reg & 0x3)
audio_i2s_swap_left_right(0);
else
audio_i2s_swap_left_right(3);
break;
case AMAUDIO_IOC_MUTE_LEFT_RIGHT_CHANNEL:
pr_info("set audio 958 mute left/right!\n");
audio_mute_left_right(arg);
break;
case AMAUDIO_IOC_MUTE_UNMUTE:
pr_info("set audio mute/unmute!\n");
if (arg == 1)
audio_i2s_mute();
else if (arg == 0)
audio_i2s_unmute();
break;
default:
break;
};
return r;
}
static int amaudio_utils_open(struct inode *inode, struct file *file)
{
return 0;
}
static int amaudio_utils_release(struct inode *inode, struct file *file)
{
return 0;
}
static long amaudio_utils_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
return 0;
}
#ifdef CONFIG_COMPAT
static long amaudio_compat_ioctl(struct file *file, unsigned int cmd, ulong arg)
{
return amaudio_ioctl(file, cmd, (ulong) compat_ptr(arg));
}
static long amaudio_compat_utils_ioctl(struct file *file,
unsigned int cmd, ulong arg)
{
return amaudio_utils_ioctl(file, cmd, (ulong) compat_ptr(arg));
}
#endif
static ssize_t show_audio_channels_mask(struct class *class,
struct class_attribute *attr, char *buf)
{
ssize_t ret = 0;
ret =
sprintf(buf,
"echo l/r/s/c to /sys/class/amaudio/audio_channels_mask\n"
"file to mask audio output.\n l : left channel mono output.\n"
"r : right channel mono output.\n s : stereo output.\n"
"c : swap left and right channels.\n");
return ret;
}
static ssize_t store_audio_channels_mask(struct class *class,
struct class_attribute *attr,
const char *buf, size_t count)
{
u32 reg;
switch (buf[0]) {
case 'l':
audio_i2s_swap_left_right(1);
break;
case 'r':
audio_i2s_swap_left_right(2);
break;
case 's':
audio_i2s_swap_left_right(0);
break;
case 'c':
reg = read_i2s_mute_swap_reg();
if (reg & 0x3)
audio_i2s_swap_left_right(0);
else
audio_i2s_swap_left_right(3);
break;
default:
pr_info("unknown command!\n");
}
return count;
}
static ssize_t dac_mute_const_show(struct class *cla,
struct class_attribute *attr, char *buf)
{
char *pbuf = buf;
pbuf += sprintf(pbuf, "dac mute const val 0x%x\n", dac_mute_const);
return pbuf - buf;
}
static ssize_t dac_mute_const_store(struct class *class,
struct class_attribute *attr,
const char *buf, size_t count)
{
int val = dac_mute_const;
if (buf[0] && kstrtoint(buf, 16, &val))
return -EINVAL;
if (val == 0 || val == 0x800000)
dac_mute_const = val;
pr_info("dac mute const val set to 0x%x\n", val);
return count;
}
static ssize_t output_enable_show(struct class *class,
struct class_attribute *attr, char *buf)
{
/*
* why limit this condition of iec958 buffer size bigger than 128,
* because we begin to use a 128 bytes zero playback mode
* of 958 output when alsa driver is not called by user space to
* avoid some noise.This mode should must separate this case
* with normal playback case to avoid one risk:
* when EOF,the last three seconds this is no audio pcm decoder
* to output.the zero playback mode is triggered,
* this cause the player has no chance to trigger the exit condition
*/
unsigned int iec958_size =
aml_aiu_read(AIU_MEM_IEC958_END_PTR) -
aml_aiu_read(AIU_MEM_IEC958_START_PTR);
/* normal spdif buffer MUST NOT less than 512 bytes */
return sprintf(buf, "%d\n", (if_audio_out_enable() ||
(if_958_audio_out_enable() && iec958_size > 512)));
}
enum {
I2SIN_MASTER_MODE = 0,
I2SIN_SLAVE_MODE = 1 << 0,
SPDIFIN_MODE = 1 << 1,
};
static ssize_t record_type_store(struct class *class,
struct class_attribute *attr, const char *buf,
size_t count)
{
if (buf[0] == '0')
audioin_mode = I2SIN_MASTER_MODE;
else if (buf[0] == '1')
audioin_mode = I2SIN_SLAVE_MODE;
else if (buf[0] == '2')
audioin_mode = SPDIFIN_MODE;
return count;
}
static ssize_t record_type_show(struct class *class,
struct class_attribute *attr, char *buf)
{
if (audioin_mode == I2SIN_MASTER_MODE) { /* mic */
return sprintf(buf, "i2s in master mode for built in mic\n");
} else if (audioin_mode == SPDIFIN_MODE) { /* spdif in mode */
return sprintf(buf, "spdif in mode\n");
} else if (audioin_mode == I2SIN_SLAVE_MODE) { /* i2s in slave */
return sprintf(buf, "i2s in slave mode\n");
} else {
return sprintf(buf, "audioin_mode can't match mode\n");
}
}
static int dtsm6_stream_type = -1;
static unsigned int dtsm6_apre_cnt;
static unsigned int dtsm6_apre_sel;
static unsigned int dtsm6_apre_assets_sel;
static unsigned int dtsm6_mulasset_hint;
static char dtsm6_apres_assets_Array[32] = { 0 };
static unsigned int dtsm6_HPS_hint;
static ssize_t store_debug(struct class *class, struct class_attribute *attr,
const char *buf, size_t count)
{
if (strncmp(buf, "chstatus_set", 12) == 0) {
aml_aiu_write(AIU_958_VALID_CTRL, 0);
aml_aiu_write(AIU_958_CHSTAT_L0, 0x1900);
aml_aiu_write(AIU_958_CHSTAT_R0, 0x1900);
} else if (strncmp(buf, "chstatus_off", 12) == 0) {
aml_aiu_write(AIU_958_VALID_CTRL, 3);
aml_aiu_write(AIU_958_CHSTAT_L0, 0x1902);
aml_aiu_write(AIU_958_CHSTAT_R0, 0x1902);
} else if (strncmp(buf, "dtsm6_stream_type_set", 21) == 0) {
if (kstrtoint(buf + 21, 10, &dtsm6_stream_type))
return -EINVAL;
} else if (strncmp(buf, "dtsm6_apre_cnt_set", 18) == 0) {
if (kstrtoint(buf + 18, 10, &dtsm6_apre_cnt))
return -EINVAL;
} else if (strncmp(buf, "dtsm6_apre_sel_set", 18) == 0) {
if (kstrtoint(buf + 18, 10, &dtsm6_apre_sel))
return -EINVAL;
} else if (strncmp(buf, "dtsm6_apres_assets_set", 22) == 0) {
if (dtsm6_apre_cnt > 32) {
pr_info("[%s %d]unvalid dtsm6_apre_cnt/%d\n", __func__,
__LINE__, dtsm6_apre_cnt);
} else {
memcpy(dtsm6_apres_assets_Array, buf + 22,
dtsm6_apre_cnt);
}
} else if (strncmp(buf, "dtsm6_apre_assets_sel_set", 25) == 0) {
if (kstrtoint(buf + 25, 10, &dtsm6_apre_assets_sel))
return -EINVAL;
} else if (strncmp(buf, "dtsm6_mulasset_hint", 19) == 0) {
if (kstrtoint(buf + 19, 10, &dtsm6_mulasset_hint))
return -EINVAL;
} else if (strncmp(buf, "dtsm6_clear_info", 16) == 0) {
dtsm6_stream_type = -1;
dtsm6_apre_cnt = 0;
dtsm6_apre_sel = 0;
dtsm6_apre_assets_sel = 0;
dtsm6_mulasset_hint = 0;
dtsm6_HPS_hint = 0;
memset(dtsm6_apres_assets_Array, 0,
sizeof(dtsm6_apres_assets_Array));
} else if (strncmp(buf, "dtsm6_hps_hint", 14) == 0) {
if (kstrtoint(buf + 14, 10, &dtsm6_HPS_hint))
return -EINVAL;
}
return count;
}
static ssize_t show_debug(struct class *class, struct class_attribute *attr,
char *buf)
{
int pos = 0;
pos += sprintf(buf + pos, "dtsM6:StreamType%d\n", dtsm6_stream_type);
pos += sprintf(buf + pos, "ApreCnt%d\n", dtsm6_apre_cnt);
pos += sprintf(buf + pos, "ApreSel%d\n", dtsm6_apre_sel);
pos += sprintf(buf + pos, "ApreAssetSel%d\n", dtsm6_apre_assets_sel);
pos += sprintf(buf + pos, "MulAssetHint%d\n", dtsm6_mulasset_hint);
pos += sprintf(buf + pos, "HPSHint%d\n", dtsm6_HPS_hint);
pos += sprintf(buf + pos, "ApresAssetsArray");
memcpy(buf + pos, dtsm6_apres_assets_Array,
sizeof(dtsm6_apres_assets_Array));
pos += sizeof(dtsm6_apres_assets_Array);
return pos;
}
static ssize_t show_mute_left_right(struct class *class,
struct class_attribute *attr, char *buf)
{
ssize_t ret = 0;
ret =
sprintf(buf, "echo l/r/s/c to /sys/class/amaudio/mute_left_right\n"
"file to mute left or right channel\n1: mute left channel\n"
"0: mute right channel\nmute_left_right: %d\n", mute_left_right);
return ret;
}
static ssize_t store_mute_left_right(struct class *class,
struct class_attribute *attr,
const char *buf, size_t count)
{
switch (buf[0]) {
case '1':
audio_mute_left_right(1);
break;
case '0':
audio_mute_left_right(0);
break;
default:
pr_info("unknown command!\n");
}
return count;
}
static ssize_t show_mute_unmute(struct class *class,
struct class_attribute *attr, char *buf)
{
ssize_t ret = 0;
ret =
sprintf(buf, " 1: mute, 0:unmute: mute_unmute:%d,\n", mute_unmute);
return ret;
}
static ssize_t store_mute_unmute(struct class *class,
struct class_attribute *attr, const char *buf,
size_t count)
{
switch (buf[0]) {
case '1':
audio_i2s_mute();
break;
case '0':
audio_i2s_unmute();
break;
default:
pr_info("unknown command!\n");
}
return count;
}
static ssize_t dts_enable_show(struct class *class,
struct class_attribute *attr, char *buf)
{
unsigned int val;
val = aml_read_aobus(0x228);
val = (val>>14)&1;
return sprintf(buf, "0x%x\n", val);
}
static ssize_t dolby_enable_show(struct class *class,
struct class_attribute *attr, char *buf)
{
unsigned int val;
val = aml_read_aobus(0x228);
val = (val>>16)&1;
return sprintf(buf, "0x%x\n", val);
}
static struct class_attribute amaudio_attrs[] = {
__ATTR(audio_channels_mask, 0664,
show_audio_channels_mask, store_audio_channels_mask),
__ATTR(dac_mute_const, 0644,
dac_mute_const_show, dac_mute_const_store),
__ATTR(record_type, 0644,
record_type_show, record_type_store),
__ATTR(debug, 0664,
show_debug, store_debug),
__ATTR(mute_left_right, 0644,
show_mute_left_right, store_mute_left_right),
__ATTR(mute_unmute, 0644,
show_mute_unmute, store_mute_unmute),
__ATTR_RO(output_enable),
__ATTR_RO(dts_enable),
__ATTR_RO(dolby_enable),
__ATTR_NULL,
};
static struct class amaudio_class = {
.name = AMAUDIO_CLASS_NAME,
.class_attrs = amaudio_attrs,
};
static int __init amaudio_init(void)
{
int ret = 0;
int i = 0;
struct amaudio_port_t *ap;
pr_info("amaudio: driver %s init!\n", AMAUDIO_DRIVER_NAME);
ret =
register_chrdev(AMAUDIO_MAJOR, AMAUDIO_DRIVER_NAME, &amaudio_fops);
if (ret < 0) {
pr_err("Can't register %s divece ", AMAUDIO_DRIVER_NAME);
ret = -ENODEV;
goto err;
}
amaudio_classp = class_create(THIS_MODULE, AMAUDIO_DEVICE_NAME);
if (IS_ERR(amaudio_classp)) {
ret = PTR_ERR(amaudio_classp);
goto err1;
}
ap = &amaudio_ports[0];
for (i = 0; i < AMAUDIO_DEVICE_COUNT; ap++, i++) {
ap->dev = device_create(amaudio_classp, NULL,
MKDEV(AMAUDIO_MAJOR, i), NULL,
amaudio_ports[i].name);
if (IS_ERR(ap->dev)) {
pr_err
("amaudio: failed to create amaudio device node\n");
goto err2;
}
}
ret = class_register(&amaudio_class);
if (ret) {
pr_err("amaudio class create fail.\n");
goto err3;
}
amaudio_dev =
device_create(&amaudio_class, NULL, MKDEV(AMAUDIO_MAJOR, 10), NULL,
AMAUDIO_CLASS_NAME);
if (IS_ERR(amaudio_dev)) {
pr_err("amaudio creat device fail.\n");
goto err4;
}
pr_info("%s - amaudio: driver %s succuess!\n",
__func__, AMAUDIO_DRIVER_NAME);
return 0;
err4:
device_destroy(&amaudio_class, MKDEV(AMAUDIO_MAJOR, 10));
err3:
class_unregister(&amaudio_class);
err2:
for (ap = &amaudio_ports[0], i = 0; i < AMAUDIO_DEVICE_COUNT; ap++, i++)
device_destroy(amaudio_classp, MKDEV(AMAUDIO_MAJOR, i));
err1:
class_destroy(amaudio_classp);
err:
unregister_chrdev(AMAUDIO_MAJOR, AMAUDIO_DRIVER_NAME);
return ret;
}
static void __exit amaudio_exit(void)
{
int i = 0;
struct amaudio_port_t *ap;
device_destroy(&amaudio_class, MKDEV(AMAUDIO_MAJOR, 10));
class_unregister(&amaudio_class);
for (ap = &amaudio_ports[0], i = 0; i < AMAUDIO_DEVICE_COUNT; ap++, i++)
device_destroy(amaudio_classp, MKDEV(AMAUDIO_MAJOR, i));
class_destroy(amaudio_classp);
unregister_chrdev(AMAUDIO_MAJOR, AMAUDIO_DRIVER_NAME);
}
module_init(amaudio_init);
module_exit(amaudio_exit);
MODULE_DESCRIPTION("AMLOGIC Audio Control Interface driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Amlogic Inc.");
MODULE_VERSION("1.0.0");