476 lines
12 KiB
C
476 lines
12 KiB
C
|
/*
|
||
|
* saf36xx.c -- SAF36XX Soc Audio driver
|
||
|
*
|
||
|
* Copyright (c) 2015-2018, NVIDIA CORPORATION. All rights reserved.
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or modify it
|
||
|
* under the terms and conditions of the GNU General Public License,
|
||
|
* version 2, as published by the Free Software Foundation.
|
||
|
*
|
||
|
* This program is distributed in the hope 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.
|
||
|
*
|
||
|
* You should have received a copy of the GNU General Public License
|
||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
|
||
|
#include <linux/ioctl.h>
|
||
|
#include <linux/spi/spi.h>
|
||
|
#include <linux/uaccess.h>
|
||
|
#include <linux/cdev.h>
|
||
|
#include <linux/fs.h>
|
||
|
#include <linux/device.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/types.h>
|
||
|
#include <linux/delay.h>
|
||
|
#include <linux/of.h>
|
||
|
#include <linux/of_gpio.h>
|
||
|
#include <linux/interrupt.h>
|
||
|
|
||
|
#define SAF36XX_WATCHDOG_TIMEOUT msecs_to_jiffies(10000)
|
||
|
|
||
|
static struct spi_device *codec_priv;
|
||
|
|
||
|
struct saf36xx_priv {
|
||
|
struct mutex mutex;
|
||
|
void *control_data;
|
||
|
int saturn_rst_gpio;
|
||
|
int sat_spi_int_gpio;
|
||
|
wait_queue_head_t wait;
|
||
|
int boot_state;
|
||
|
u8 *read_buf;
|
||
|
size_t read_buf_idx;
|
||
|
size_t read_buf_sz;
|
||
|
};
|
||
|
|
||
|
enum {
|
||
|
SAF36XX_RESET_IOCTL = _IO(0xF5, 0x01),
|
||
|
SAF36XX_GET_INTERRUPT_STATE = _IO(0xF5, 0x02),
|
||
|
SAF36XX_ENTER_BOOT_STATE = _IO(0xF5, 0x03),
|
||
|
SAF36XX_ENTER_COMM_STATE = _IO(0xF5, 0x04),
|
||
|
};
|
||
|
|
||
|
static int saturn_chip_reset(void)
|
||
|
{
|
||
|
int err = 0;
|
||
|
struct saf36xx_priv *saf36xx =
|
||
|
(struct saf36xx_priv *)spi_get_drvdata(codec_priv);
|
||
|
|
||
|
if (gpio_is_valid(saf36xx->saturn_rst_gpio)) {
|
||
|
gpio_set_value_cansleep(saf36xx->saturn_rst_gpio, 0);
|
||
|
usleep_range(7000, 8000);
|
||
|
gpio_set_value_cansleep(saf36xx->saturn_rst_gpio, 1);
|
||
|
usleep_range(10000, 11000);
|
||
|
} else {
|
||
|
pr_err("reset saturn GPIO invalid, gpio= %d",
|
||
|
saf36xx->saturn_rst_gpio);
|
||
|
err = -EBADF;
|
||
|
}
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
static void saturn_chip_get_gpios(void)
|
||
|
{
|
||
|
struct device_node *np;
|
||
|
int err;
|
||
|
struct saf36xx_priv *saf36xx =
|
||
|
(struct saf36xx_priv *)spi_get_drvdata(codec_priv);
|
||
|
|
||
|
np = of_find_compatible_node(NULL, NULL, "nvidia,tegra-saf36xx");
|
||
|
if (NULL != np) {
|
||
|
saf36xx->saturn_rst_gpio =
|
||
|
of_get_named_gpio(np, "sat-rst-gpio", 0);
|
||
|
pr_info("saf36xx: rst-gpio:%d\n", saf36xx->saturn_rst_gpio);
|
||
|
if (!gpio_is_valid(saf36xx->saturn_rst_gpio)) {
|
||
|
pr_err("reset saturn GPIO get name failed, gpio= %d\n",
|
||
|
saf36xx->saturn_rst_gpio);
|
||
|
} else {
|
||
|
err = gpio_request(saf36xx->saturn_rst_gpio,
|
||
|
"sat_rst_gpio");
|
||
|
if (err)
|
||
|
pr_err("reset saturn GPIO failed ,err=%d\n",
|
||
|
err);
|
||
|
else
|
||
|
gpio_direction_output(
|
||
|
saf36xx->saturn_rst_gpio, 1);
|
||
|
}
|
||
|
|
||
|
saf36xx->sat_spi_int_gpio =
|
||
|
of_get_named_gpio(np, "sat-spi-int-gpio", 0);
|
||
|
pr_info("saf36xx: int-gpio:%d\n", saf36xx->sat_spi_int_gpio);
|
||
|
if (!gpio_is_valid(saf36xx->sat_spi_int_gpio)) {
|
||
|
pr_err("saturn int GPIO get name failed, gpio = %d\n",
|
||
|
saf36xx->sat_spi_int_gpio);
|
||
|
} else {
|
||
|
err = gpio_request(saf36xx->sat_spi_int_gpio,
|
||
|
"sat_spi_int_gpio");
|
||
|
if (err)
|
||
|
pr_err("saturn GPIO request failed, err=%d\n",
|
||
|
err);
|
||
|
else
|
||
|
gpio_direction_input(saf36xx->sat_spi_int_gpio);
|
||
|
}
|
||
|
} else {
|
||
|
pr_err("reset saturn GPIO get node failed, np= NULL\n");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static long saf36xx_hwdep_ioctl(struct file *filp,
|
||
|
unsigned int cmd, unsigned long arg)
|
||
|
{
|
||
|
struct saf36xx_priv *saf36xx =
|
||
|
(struct saf36xx_priv *)spi_get_drvdata(codec_priv);
|
||
|
|
||
|
switch (cmd) {
|
||
|
case SAF36XX_RESET_IOCTL:
|
||
|
return saturn_chip_reset();
|
||
|
/* Used for debugging the interrupt GPIO from userspace */
|
||
|
case SAF36XX_GET_INTERRUPT_STATE:
|
||
|
return gpio_get_value_cansleep(saf36xx->sat_spi_int_gpio);
|
||
|
case SAF36XX_ENTER_BOOT_STATE:
|
||
|
saf36xx->boot_state = 1;
|
||
|
return 0;
|
||
|
case SAF36XX_ENTER_COMM_STATE:
|
||
|
saf36xx->boot_state = 0;
|
||
|
return 0;
|
||
|
default:
|
||
|
return -EFAULT;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static ssize_t saf36xx_hwdep_write(struct file *filp,
|
||
|
const char __user *_buf,
|
||
|
size_t size,
|
||
|
loff_t *off_)
|
||
|
{
|
||
|
char *data;
|
||
|
int ret = 0, mode = 0;
|
||
|
struct saf36xx_priv *saf36xx =
|
||
|
(struct saf36xx_priv *)spi_get_drvdata(codec_priv);
|
||
|
|
||
|
if ((!saf36xx->boot_state) &&
|
||
|
(gpio_get_value_cansleep(saf36xx->sat_spi_int_gpio) == 0))
|
||
|
return -EAGAIN;
|
||
|
|
||
|
data = devm_kzalloc(&codec_priv->dev, sizeof(*data) * size, GFP_KERNEL);
|
||
|
|
||
|
if (copy_from_user(data, _buf, sizeof(*data) * size)) {
|
||
|
dev_err(&codec_priv->dev, "Error copying user data");
|
||
|
goto err_copy;
|
||
|
}
|
||
|
mode = saf36xx->boot_state == 1 ? 0 : 1;
|
||
|
/* If we're in boot state, wait for GPIO to be low, otherwise high */
|
||
|
ret = wait_event_interruptible_timeout(saf36xx->wait,
|
||
|
(gpio_get_value_cansleep(saf36xx->sat_spi_int_gpio) == mode),
|
||
|
SAF36XX_WATCHDOG_TIMEOUT);
|
||
|
if (ret == 0) {
|
||
|
dev_err(&codec_priv->dev, "Saturn watchdog timeout (write)\n");
|
||
|
devm_kfree(&codec_priv->dev, data);
|
||
|
return -ETIMEDOUT;
|
||
|
}
|
||
|
mutex_lock(&saf36xx->mutex);
|
||
|
|
||
|
ret = spi_write(saf36xx->control_data, data, size);
|
||
|
|
||
|
if (ret)
|
||
|
dev_err(&codec_priv->dev, "spi_write returned: %d\n", ret);
|
||
|
|
||
|
mdelay(10);
|
||
|
mutex_unlock(&saf36xx->mutex);
|
||
|
|
||
|
err_copy:
|
||
|
devm_kfree(&codec_priv->dev, data);
|
||
|
ret = saf36xx->boot_state;
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
static ssize_t saf36xx_hwdep_read(struct file *filp,
|
||
|
char __user *_buf,
|
||
|
size_t size,
|
||
|
loff_t *off)
|
||
|
{
|
||
|
char *data;
|
||
|
int ret = 0;
|
||
|
struct saf36xx_priv *saf36xx =
|
||
|
(struct saf36xx_priv *)spi_get_drvdata(codec_priv);
|
||
|
|
||
|
/* When in boot state, wait until the interrupt pin goes low until
|
||
|
* reading */
|
||
|
if (saf36xx->boot_state == 1) {
|
||
|
data = devm_kzalloc(&codec_priv->dev,
|
||
|
sizeof(*data) * size,
|
||
|
GFP_KERNEL);
|
||
|
if (!data) {
|
||
|
dev_err(&codec_priv->dev,
|
||
|
"Failed to allocate memory for write buffer");
|
||
|
ret = -ENOMEM;
|
||
|
goto end;
|
||
|
}
|
||
|
ret = wait_event_interruptible_timeout(saf36xx->wait,
|
||
|
(gpio_get_value_cansleep(saf36xx->sat_spi_int_gpio) == 0),
|
||
|
SAF36XX_WATCHDOG_TIMEOUT);
|
||
|
if (ret == 0) {
|
||
|
dev_err(&codec_priv->dev,
|
||
|
"Saturn watchdog timeout (read)\n");
|
||
|
devm_kfree(&codec_priv->dev, data);
|
||
|
return -ETIMEDOUT;
|
||
|
}
|
||
|
mutex_lock(&saf36xx->mutex);
|
||
|
ret = spi_read(saf36xx->control_data, data, size);
|
||
|
if (ret)
|
||
|
dev_err(&codec_priv->dev, "spi_read returned: %d\n", ret);
|
||
|
|
||
|
mdelay(10);
|
||
|
mutex_unlock(&saf36xx->mutex);
|
||
|
if (ret)
|
||
|
goto err_read;
|
||
|
|
||
|
if (copy_to_user(_buf, data, sizeof(*data) * size))
|
||
|
ret = -EFAULT;
|
||
|
err_read:
|
||
|
devm_kfree(&codec_priv->dev, data);
|
||
|
} else {
|
||
|
/* When in API mode, read an entire message into saf36xx->read_buf,
|
||
|
* and read from this buffer when processing read() requests from
|
||
|
* user space. A low interrupt pin means there is more data. If
|
||
|
* there is no more data, return an error to the user to prevent
|
||
|
* blocking */
|
||
|
mutex_lock(&saf36xx->mutex);
|
||
|
/* Check if entire buffer has been read */
|
||
|
if (saf36xx->read_buf_idx >= saf36xx->read_buf_sz &&
|
||
|
saf36xx->read_buf_sz > 0) {
|
||
|
devm_kfree(&codec_priv->dev, saf36xx->read_buf);
|
||
|
saf36xx->read_buf_sz = 0;
|
||
|
saf36xx->read_buf_idx = 0;
|
||
|
}
|
||
|
|
||
|
/* Need to fill a new buffer with data from SPI */
|
||
|
if (saf36xx->read_buf_sz == 0) {
|
||
|
if (gpio_get_value_cansleep(saf36xx->sat_spi_int_gpio) == 0) {
|
||
|
static const int header_sz = 4;
|
||
|
static const int crc_sz = 2;
|
||
|
u8 header[4] = { 0 };
|
||
|
|
||
|
ret = spi_read(saf36xx->control_data,
|
||
|
header, header_sz);
|
||
|
if (ret < 0) {
|
||
|
dev_err(&codec_priv->dev,
|
||
|
"Failed to read message header\n");
|
||
|
ret = -EFAULT; /* TODO: device error here.. */
|
||
|
}
|
||
|
|
||
|
/* All messages must begin with 0xAD */
|
||
|
if (ret == 0 && header[0] != 0xAD) {
|
||
|
pr_err("Found junk data .Discarding\n");
|
||
|
ret = -EAGAIN;
|
||
|
} else {
|
||
|
u16 msg_sz = ((header[2] << 8) | header[3])
|
||
|
+ crc_sz;
|
||
|
size_t buf_sz = sizeof(u8)
|
||
|
* (header_sz + msg_sz);
|
||
|
dev_dbg(&codec_priv->dev,
|
||
|
"Allocating new buffer of size %zu\n",
|
||
|
buf_sz);
|
||
|
saf36xx->read_buf = devm_kzalloc(
|
||
|
&codec_priv->dev, buf_sz,
|
||
|
GFP_KERNEL);
|
||
|
if (saf36xx->read_buf) {
|
||
|
saf36xx->read_buf_sz = buf_sz;
|
||
|
saf36xx->read_buf_idx = 0;
|
||
|
memcpy(saf36xx->read_buf, header,
|
||
|
header_sz);
|
||
|
ret = spi_read(saf36xx->control_data,
|
||
|
saf36xx->read_buf + header_sz,
|
||
|
msg_sz);
|
||
|
} else {
|
||
|
pr_err("Error allocating buffer\n");
|
||
|
ret = -ENOMEM;
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
/* We have no data, and none available for reading */
|
||
|
ret = -EAGAIN;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* The buffer is not empty. Return buffered data */
|
||
|
if (ret == 0 && saf36xx->read_buf_sz > 0) {
|
||
|
size_t remaining;
|
||
|
size_t copy_sz;
|
||
|
|
||
|
remaining = saf36xx->read_buf_sz - saf36xx->read_buf_idx;
|
||
|
copy_sz = min(remaining, size);
|
||
|
|
||
|
if (copy_to_user(_buf,
|
||
|
saf36xx->read_buf + saf36xx->read_buf_idx,
|
||
|
sizeof(u8) * copy_sz)) {
|
||
|
ret = -EFAULT;
|
||
|
} else {
|
||
|
saf36xx->read_buf_idx += copy_sz;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
mutex_unlock(&saf36xx->mutex);
|
||
|
}
|
||
|
|
||
|
end:
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static irqreturn_t saf36xx_sat_spi_interrupt(int irq, void *data)
|
||
|
{
|
||
|
struct saf36xx_priv *saf36xx = (struct saf36xx_priv *)data;
|
||
|
|
||
|
if (gpio_get_value_cansleep(saf36xx->sat_spi_int_gpio) == 0)
|
||
|
wake_up(&saf36xx->wait);
|
||
|
|
||
|
return IRQ_HANDLED;
|
||
|
}
|
||
|
|
||
|
static int saf36xx_ioctl_open(struct inode *inp, struct file *filp)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int saf36xx_ioctl_release(struct inode *inp, struct file *filp)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int saf36xx_ioctl_major;
|
||
|
static struct cdev saf36xx_ioctl_cdev;
|
||
|
static struct class *saf36xx_ioctl_class;
|
||
|
|
||
|
static const struct file_operations saf36xx_ioctl_fops = {
|
||
|
.owner = THIS_MODULE,
|
||
|
.open = saf36xx_ioctl_open,
|
||
|
.release = saf36xx_ioctl_release,
|
||
|
.unlocked_ioctl = saf36xx_hwdep_ioctl,
|
||
|
.write = saf36xx_hwdep_write,
|
||
|
.read = saf36xx_hwdep_read,
|
||
|
};
|
||
|
|
||
|
static void saf36xx_ioctl_cleanup(void)
|
||
|
{
|
||
|
cdev_del(&saf36xx_ioctl_cdev);
|
||
|
device_destroy(saf36xx_ioctl_class, MKDEV(saf36xx_ioctl_major, 0));
|
||
|
|
||
|
if (saf36xx_ioctl_class)
|
||
|
class_destroy(saf36xx_ioctl_class);
|
||
|
|
||
|
unregister_chrdev_region(MKDEV(saf36xx_ioctl_major, 0), 1);
|
||
|
}
|
||
|
|
||
|
static int saf36xx_hwdep_create(void)
|
||
|
{
|
||
|
int result;
|
||
|
int ret = -ENODEV;
|
||
|
dev_t saf36xx_ioctl_dev;
|
||
|
|
||
|
result = alloc_chrdev_region(&saf36xx_ioctl_dev, 0,
|
||
|
1, "saf36xx_hwdep");
|
||
|
if (result < 0)
|
||
|
goto fail_err;
|
||
|
|
||
|
saf36xx_ioctl_major = MAJOR(saf36xx_ioctl_dev);
|
||
|
cdev_init(&saf36xx_ioctl_cdev, &saf36xx_ioctl_fops);
|
||
|
|
||
|
saf36xx_ioctl_cdev.owner = THIS_MODULE;
|
||
|
saf36xx_ioctl_cdev.ops = &saf36xx_ioctl_fops;
|
||
|
result = cdev_add(&saf36xx_ioctl_cdev, saf36xx_ioctl_dev, 1);
|
||
|
if (result < 0)
|
||
|
goto fail_chrdev;
|
||
|
|
||
|
saf36xx_ioctl_class = class_create(THIS_MODULE, "saf36xx_hwdep");
|
||
|
|
||
|
if (IS_ERR(saf36xx_ioctl_class)) {
|
||
|
pr_err("saf36xx_hwdep: device class file already in use.\n");
|
||
|
saf36xx_ioctl_cleanup();
|
||
|
return PTR_ERR(saf36xx_ioctl_class);
|
||
|
}
|
||
|
|
||
|
device_create(saf36xx_ioctl_class, NULL,
|
||
|
MKDEV(saf36xx_ioctl_major, 0),
|
||
|
NULL, "%s", "saf36xx_hwdep");
|
||
|
return 0;
|
||
|
|
||
|
fail_chrdev:
|
||
|
unregister_chrdev_region(saf36xx_ioctl_dev, 1);
|
||
|
|
||
|
fail_err:
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int saf36xx_hwdep_cleanup(void)
|
||
|
{
|
||
|
saf36xx_ioctl_cleanup();
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int saf36xx_spi_probe(struct spi_device *spi)
|
||
|
{
|
||
|
struct saf36xx_priv *saf36xx;
|
||
|
int irq;
|
||
|
int ret;
|
||
|
|
||
|
codec_priv = spi;
|
||
|
saf36xx = devm_kzalloc(&spi->dev, sizeof(struct saf36xx_priv),
|
||
|
GFP_KERNEL);
|
||
|
|
||
|
saf36xx->control_data = spi;
|
||
|
spi_set_drvdata(spi, saf36xx);
|
||
|
saturn_chip_get_gpios();
|
||
|
|
||
|
mutex_init(&saf36xx->mutex);
|
||
|
init_waitqueue_head(&saf36xx->wait);
|
||
|
saf36xx->boot_state = 1;
|
||
|
|
||
|
saf36xx_hwdep_create();
|
||
|
|
||
|
irq = gpio_to_irq(saf36xx->sat_spi_int_gpio);
|
||
|
ret = request_threaded_irq(irq, NULL, saf36xx_sat_spi_interrupt,
|
||
|
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
||
|
dev_name(&spi->dev), saf36xx);
|
||
|
if (ret < 0) {
|
||
|
dev_err(&spi->dev, "Cannot request irq %d for Fault (%d)\n",
|
||
|
irq, ret);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int saf36xx_spi_remove(struct spi_device *spi)
|
||
|
{
|
||
|
struct saf36xx_priv *saf36xx =
|
||
|
(struct saf36xx_priv *)spi_get_drvdata(spi);
|
||
|
|
||
|
devm_kfree(&spi->dev, saf36xx);
|
||
|
gpio_free(saf36xx->saturn_rst_gpio);
|
||
|
gpio_free(saf36xx->sat_spi_int_gpio);
|
||
|
free_irq(gpio_to_irq(saf36xx->sat_spi_int_gpio), NULL);
|
||
|
saf36xx_hwdep_cleanup();
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static struct spi_driver saf36xx_spi_driver = {
|
||
|
.driver = {
|
||
|
.name = "saf36xx",
|
||
|
.owner = THIS_MODULE,
|
||
|
},
|
||
|
.probe = saf36xx_spi_probe,
|
||
|
.remove = saf36xx_spi_remove,
|
||
|
};
|
||
|
|
||
|
module_spi_driver(saf36xx_spi_driver);
|
||
|
|
||
|
MODULE_AUTHOR("Gaurav Tendolkar <gtendolkar@nvidia.com>");
|
||
|
MODULE_DESCRIPTION("SAF36XX Soc Audio driver");
|
||
|
MODULE_LICENSE("GPL");
|
||
|
|