703 lines
17 KiB
C
703 lines
17 KiB
C
|
/*
|
||
|
* drivers/misc/inter_tegra_spi.c
|
||
|
*
|
||
|
* Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
|
||
|
*
|
||
|
* This software is licensed under the terms of the GNU General Public
|
||
|
* License version 2, as published by the Free Software Foundation, and
|
||
|
* may be copied, distributed, and modified under those terms.
|
||
|
*
|
||
|
* 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.
|
||
|
*/
|
||
|
|
||
|
#include <linux/types.h>
|
||
|
#include <linux/device.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/of.h>
|
||
|
#include <linux/of_device.h>
|
||
|
#include <linux/kthread.h>
|
||
|
#include <linux/spi/spi.h>
|
||
|
#include <linux/jiffies.h>
|
||
|
#include <linux/thermal.h>
|
||
|
|
||
|
#include "inter_tegra_spi.h"
|
||
|
|
||
|
#define RX_RETRY_CNT_MAX 3
|
||
|
|
||
|
#define dev_inter_tegra_err(err_enable, dev, format, arg...) \
|
||
|
({ \
|
||
|
if (err_enable) \
|
||
|
dev_err(dev, format, ##arg); \
|
||
|
else \
|
||
|
dev_info(dev, format, ##arg); \
|
||
|
})
|
||
|
|
||
|
static int inter_tegra_spi_xfer(struct spi_device *spi,
|
||
|
u8 *txbuf, u8 *rxbuf, int count, int timeout)
|
||
|
{
|
||
|
struct spi_message m;
|
||
|
struct spi_transfer t = {
|
||
|
.tx_buf = txbuf,
|
||
|
.rx_buf = rxbuf,
|
||
|
.len = count,
|
||
|
.delay_usecs = timeout,
|
||
|
};
|
||
|
ssize_t status = 0;
|
||
|
|
||
|
spi_message_init(&m);
|
||
|
spi_message_add_tail(&t, &m);
|
||
|
status = spi_sync(spi, &m);
|
||
|
if (status < 0)
|
||
|
return (int)status;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static u8 make_checksum(void *data, int num)
|
||
|
{
|
||
|
int i;
|
||
|
unsigned int sum = 0;
|
||
|
u8 *ptr = data;
|
||
|
|
||
|
for (i = 0; i < num; i++)
|
||
|
sum += *ptr++;
|
||
|
|
||
|
return (u8)((~sum + 1) & 0xFF);
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
inter_tegra_send(struct inter_tegra_data *inter_tegra, int type)
|
||
|
{
|
||
|
struct inter_tegra_pkt txbuf;
|
||
|
struct inter_tegra_pkt rxbuf;
|
||
|
int err = 0;
|
||
|
int pkt_length = 0;
|
||
|
u8 rx_check_sum;
|
||
|
|
||
|
memset(&txbuf, 0, sizeof(txbuf));
|
||
|
memset(&rxbuf, 0, sizeof(rxbuf));
|
||
|
|
||
|
pkt_length = sizeof(txbuf);
|
||
|
|
||
|
txbuf.head = HEAD;
|
||
|
switch (type) {
|
||
|
case TYPE_TEMP:
|
||
|
txbuf.cmd = CMD_TEMP;
|
||
|
txbuf.pkt_data.value = inter_tegra->tx_temp;
|
||
|
break;
|
||
|
default:
|
||
|
dev_inter_tegra_err(inter_tegra->err_log_enable,
|
||
|
&inter_tegra->spi->dev,
|
||
|
"cmd err!\n");
|
||
|
return -1;
|
||
|
}
|
||
|
txbuf.len = sizeof(txbuf.pkt_data);
|
||
|
txbuf.check_sum = make_checksum(&txbuf.pkt_data,
|
||
|
sizeof(txbuf.pkt_data));
|
||
|
|
||
|
err = inter_tegra_spi_xfer(inter_tegra->spi,
|
||
|
(u8 *)&txbuf, (u8 *)&rxbuf,
|
||
|
pkt_length, 0);
|
||
|
if (err) {
|
||
|
dev_inter_tegra_err(inter_tegra->err_log_enable,
|
||
|
&inter_tegra->spi->dev,
|
||
|
"tx spi err!\n");
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
if (rxbuf.head != HEAD || rxbuf.cmd != CMD_SUCCESS) {
|
||
|
dev_inter_tegra_err(inter_tegra->err_log_enable,
|
||
|
&inter_tegra->spi->dev,
|
||
|
"tx receive packet err!\n");
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
rx_check_sum = make_checksum(&rxbuf.pkt_data, sizeof(rxbuf.pkt_data));
|
||
|
|
||
|
if (rxbuf.check_sum != rx_check_sum) {
|
||
|
dev_inter_tegra_err(inter_tegra->err_log_enable,
|
||
|
&inter_tegra->spi->dev,
|
||
|
"tx receive checksum err!\n");
|
||
|
return -1;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
#define USE_SLAVE_WAIT_EXTRA_BYTE 1
|
||
|
|
||
|
static int
|
||
|
inter_tegra_receive(struct inter_tegra_data *inter_tegra)
|
||
|
{
|
||
|
struct inter_tegra_pkt txbuf;
|
||
|
struct inter_tegra_pkt rxbuf;
|
||
|
int err = 0;
|
||
|
int pkt_length = 0;
|
||
|
u8 rx_check_sum;
|
||
|
#if (USE_SLAVE_WAIT_EXTRA_BYTE)
|
||
|
/*
|
||
|
* Wait extra 4 more byte to prevent get RDY irq before CS_INACTIVE irq
|
||
|
* in case of using variable_length_transfer
|
||
|
*/
|
||
|
u8 temp_txbuf[sizeof(struct inter_tegra_pkt) + 4];
|
||
|
u8 temp_rxbuf[sizeof(struct inter_tegra_pkt) + 4];
|
||
|
|
||
|
memset(temp_txbuf, 0, sizeof(struct inter_tegra_pkt) + 4);
|
||
|
memset(temp_rxbuf, 0, sizeof(struct inter_tegra_pkt) + 4);
|
||
|
|
||
|
pkt_length = sizeof(struct inter_tegra_pkt) + 4;
|
||
|
#else
|
||
|
pkt_length = sizeof(struct inter_tegra_pkt);
|
||
|
#endif
|
||
|
memset(&txbuf, 0, sizeof(struct inter_tegra_pkt));
|
||
|
memset(&rxbuf, 0, sizeof(struct inter_tegra_pkt));
|
||
|
|
||
|
txbuf.head = HEAD;
|
||
|
txbuf.cmd = CMD_SUCCESS;
|
||
|
txbuf.len = sizeof(txbuf.pkt_data);
|
||
|
txbuf.pkt_data.value = 0;
|
||
|
txbuf.check_sum = make_checksum(&txbuf.pkt_data,
|
||
|
sizeof(txbuf.pkt_data));
|
||
|
#if (USE_SLAVE_WAIT_EXTRA_BYTE)
|
||
|
/* copy to temp_txbuf buf from txbuf */
|
||
|
memcpy(temp_txbuf, &txbuf, sizeof(struct inter_tegra_pkt));
|
||
|
|
||
|
err = inter_tegra_spi_xfer(inter_tegra->spi,
|
||
|
temp_txbuf, temp_rxbuf,
|
||
|
pkt_length,
|
||
|
inter_tegra->receive_timeout_ms);
|
||
|
if (err) {
|
||
|
dev_inter_tegra_err(inter_tegra->err_log_enable,
|
||
|
&inter_tegra->spi->dev, "rx spi err!\n");
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
/* copy to rxbuf buf from temp_txbuf */
|
||
|
memcpy(&rxbuf, temp_rxbuf, sizeof(struct inter_tegra_pkt));
|
||
|
#else
|
||
|
err = inter_tegra_spi_xfer(inter_tegra->spi,
|
||
|
(u8 *)&txbuf, (u8 *)&rxbuf,
|
||
|
pkt_length,
|
||
|
inter_tegra->receive_timeout_ms);
|
||
|
if (err) {
|
||
|
dev_inter_tegra_err(inter_tegra->err_log_enable,
|
||
|
&inter_tegra->spi->dev, "rx spi err!\n");
|
||
|
return -1;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
if (rxbuf.head != HEAD) {
|
||
|
dev_inter_tegra_err(inter_tegra->err_log_enable,
|
||
|
&inter_tegra->spi->dev,
|
||
|
"rx receive packet err!\n");
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
rx_check_sum = make_checksum(&rxbuf.pkt_data, sizeof(rxbuf.pkt_data));
|
||
|
|
||
|
if (rxbuf.check_sum != rx_check_sum) {
|
||
|
dev_inter_tegra_err(inter_tegra->err_log_enable,
|
||
|
&inter_tegra->spi->dev,
|
||
|
"rx receive checksum err!\n");
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
switch (rxbuf.cmd) {
|
||
|
case CMD_TEMP:
|
||
|
inter_tegra->rx_temp = rxbuf.pkt_data.value;
|
||
|
break;
|
||
|
default:
|
||
|
dev_inter_tegra_err(inter_tegra->err_log_enable,
|
||
|
&inter_tegra->spi->dev,
|
||
|
"cmd err!\n");
|
||
|
return -1;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int inter_tegra_read_thread(void *data)
|
||
|
{
|
||
|
struct inter_tegra_data *inter_tegra =
|
||
|
(struct inter_tegra_data *)data;
|
||
|
int err = 0;
|
||
|
|
||
|
while (!kthread_should_stop()) {
|
||
|
err = inter_tegra_receive(inter_tegra);
|
||
|
if (!err) {
|
||
|
inter_tegra->rx_retry_cnt = 0;
|
||
|
dev_dbg(&inter_tegra->spi->dev,
|
||
|
"[inter_tegra_read_thread] rx_temp:%d\n",
|
||
|
inter_tegra->rx_temp);
|
||
|
} else
|
||
|
inter_tegra->rx_retry_cnt++;
|
||
|
|
||
|
if (inter_tegra->rx_retry_cnt >= RX_RETRY_CNT_MAX) {
|
||
|
inter_tegra->rx_temp = 0;
|
||
|
dev_inter_tegra_err(inter_tegra->err_log_enable,
|
||
|
&inter_tegra->spi->dev,
|
||
|
"spi receive failed over %d times\n"
|
||
|
"reset rx_temp to zero\n",
|
||
|
RX_RETRY_CNT_MAX);
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void inter_tegra_send_work(struct work_struct *work)
|
||
|
{
|
||
|
struct inter_tegra_data *inter_tegra =
|
||
|
container_of(work, struct inter_tegra_data, work.work);
|
||
|
int err = 0;
|
||
|
|
||
|
mutex_lock(&inter_tegra->tx_mutex);
|
||
|
|
||
|
err = inter_tegra_send(inter_tegra, TYPE_TEMP);
|
||
|
if (!err)
|
||
|
dev_dbg(&inter_tegra->spi->dev,
|
||
|
"[inter_tegra_send_work] tx_temp:%d\n",
|
||
|
inter_tegra->tx_temp);
|
||
|
|
||
|
mutex_unlock(&inter_tegra->tx_mutex);
|
||
|
|
||
|
schedule_delayed_work(&inter_tegra->work,
|
||
|
msecs_to_jiffies(inter_tegra->send_period_ms));
|
||
|
}
|
||
|
|
||
|
static int inter_tegra_match(struct thermal_zone_device *thz, void *data)
|
||
|
{
|
||
|
return (strcmp((char *)data, thz->type) == 0);
|
||
|
}
|
||
|
|
||
|
static int inter_tegra_get_tx_temp(void *data, int *temp)
|
||
|
{
|
||
|
struct inter_tegra_data *inter_tegra = data;
|
||
|
struct thermal_zone_device *thz;
|
||
|
int read_temp = 0;
|
||
|
|
||
|
thz = thermal_zone_device_find((void *)inter_tegra->send_tz_type,
|
||
|
inter_tegra_match);
|
||
|
|
||
|
if (!thz || thz->ops->get_temp == NULL ||
|
||
|
thz->ops->get_temp(thz, &read_temp))
|
||
|
read_temp = 0;
|
||
|
|
||
|
*temp = read_temp;
|
||
|
inter_tegra->tx_temp = read_temp;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static struct thermal_zone_of_device_ops inter_tegra_tx_ops = {
|
||
|
.get_temp = inter_tegra_get_tx_temp,
|
||
|
.get_trend = NULL,
|
||
|
};
|
||
|
|
||
|
static int inter_tegra_get_rx_temp(void *data, int *temp)
|
||
|
{
|
||
|
struct inter_tegra_data *inter_tegra = data;
|
||
|
|
||
|
*temp = inter_tegra->rx_temp;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static struct thermal_zone_of_device_ops inter_tegra_rx_ops = {
|
||
|
.get_temp = inter_tegra_get_rx_temp,
|
||
|
.get_trend = NULL,
|
||
|
};
|
||
|
|
||
|
static ssize_t err_log_enable_show(struct device *dev,
|
||
|
struct device_attribute *attr, char *buf)
|
||
|
{
|
||
|
struct inter_tegra_data *inter_tegra = dev_get_drvdata(dev);
|
||
|
|
||
|
return sprintf(buf, "%d\n", inter_tegra->err_log_enable);
|
||
|
}
|
||
|
|
||
|
static ssize_t err_log_enable_store(struct device *dev,
|
||
|
struct device_attribute *attr,
|
||
|
const char *buf, size_t count)
|
||
|
{
|
||
|
struct inter_tegra_data *inter_tegra = dev_get_drvdata(dev);
|
||
|
bool is_enable = false;
|
||
|
int ret;
|
||
|
|
||
|
ret = strtobool(buf, &is_enable);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
if (is_enable) {
|
||
|
inter_tegra->err_log_enable = 1;
|
||
|
} else {
|
||
|
inter_tegra->err_log_enable = 0;
|
||
|
}
|
||
|
|
||
|
return count;
|
||
|
}
|
||
|
|
||
|
static DEVICE_ATTR(err_log_enable, (S_IRUGO | (S_IWUSR | S_IWGRP)),
|
||
|
err_log_enable_show, err_log_enable_store);
|
||
|
|
||
|
static ssize_t send_enable_show(struct device *dev,
|
||
|
struct device_attribute *attr, char *buf)
|
||
|
{
|
||
|
struct inter_tegra_data *inter_tegra = dev_get_drvdata(dev);
|
||
|
|
||
|
return sprintf(buf, "%d\n", inter_tegra->send_enable);
|
||
|
}
|
||
|
|
||
|
static ssize_t send_enable_store(struct device *dev,
|
||
|
struct device_attribute *attr,
|
||
|
const char *buf, size_t count)
|
||
|
{
|
||
|
struct inter_tegra_data *inter_tegra = dev_get_drvdata(dev);
|
||
|
bool is_enable = false;
|
||
|
int ret;
|
||
|
|
||
|
ret = strtobool(buf, &is_enable);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
if (is_enable) {
|
||
|
mutex_lock(&inter_tegra->mutex);
|
||
|
if (inter_tegra->send_enable) {
|
||
|
dev_info(dev, "tx work already enabled\n");
|
||
|
mutex_unlock(&inter_tegra->mutex);
|
||
|
return count;
|
||
|
}
|
||
|
schedule_delayed_work(&inter_tegra->work,
|
||
|
msecs_to_jiffies(inter_tegra->send_period_ms));
|
||
|
inter_tegra->send_enable = 1;
|
||
|
mutex_unlock(&inter_tegra->mutex);
|
||
|
} else {
|
||
|
mutex_lock(&inter_tegra->mutex);
|
||
|
cancel_delayed_work_sync(&inter_tegra->work);
|
||
|
inter_tegra->send_enable = 0;
|
||
|
mutex_unlock(&inter_tegra->mutex);
|
||
|
}
|
||
|
|
||
|
return count;
|
||
|
}
|
||
|
|
||
|
static DEVICE_ATTR(send_enable, (S_IRUGO | (S_IWUSR | S_IWGRP)),
|
||
|
send_enable_show, send_enable_store);
|
||
|
|
||
|
static struct attribute *inter_tegra_tx_attributes[] = {
|
||
|
&dev_attr_send_enable.attr,
|
||
|
&dev_attr_err_log_enable.attr,
|
||
|
NULL
|
||
|
};
|
||
|
|
||
|
static const struct attribute_group inter_tegra_tx_attr_group = {
|
||
|
.attrs = inter_tegra_tx_attributes,
|
||
|
};
|
||
|
|
||
|
static ssize_t receive_enable_show(struct device *dev,
|
||
|
struct device_attribute *attr, char *buf)
|
||
|
{
|
||
|
struct inter_tegra_data *inter_tegra = dev_get_drvdata(dev);
|
||
|
|
||
|
return sprintf(buf, "%d\n", inter_tegra->receive_enable);
|
||
|
}
|
||
|
|
||
|
static ssize_t receive_enable_store(struct device *dev,
|
||
|
struct device_attribute *attr,
|
||
|
const char *buf, size_t count)
|
||
|
{
|
||
|
struct inter_tegra_data *inter_tegra = dev_get_drvdata(dev);
|
||
|
bool is_enable = false;
|
||
|
int ret;
|
||
|
|
||
|
ret = strtobool(buf, &is_enable);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
if (is_enable) {
|
||
|
mutex_lock(&inter_tegra->mutex);
|
||
|
if (inter_tegra->receive_enable) {
|
||
|
dev_info(dev, "rx thread already enabled\n");
|
||
|
mutex_unlock(&inter_tegra->mutex);
|
||
|
return count;
|
||
|
}
|
||
|
inter_tegra->thread = kthread_run(inter_tegra_read_thread,
|
||
|
(void *)inter_tegra, "inter_tegra_read_thread");
|
||
|
if (IS_ERR_OR_NULL(inter_tegra->thread)) {
|
||
|
inter_tegra->thread = NULL;
|
||
|
mutex_unlock(&inter_tegra->mutex);
|
||
|
return count;
|
||
|
}
|
||
|
inter_tegra->receive_enable = 1;
|
||
|
mutex_unlock(&inter_tegra->mutex);
|
||
|
} else {
|
||
|
mutex_lock(&inter_tegra->mutex);
|
||
|
if (!inter_tegra->receive_enable) {
|
||
|
dev_info(dev, "rx thread does not created.\n");
|
||
|
mutex_unlock(&inter_tegra->mutex);
|
||
|
return count;
|
||
|
}
|
||
|
if (inter_tegra->thread) {
|
||
|
kthread_stop(inter_tegra->thread);
|
||
|
inter_tegra->thread = NULL;
|
||
|
}
|
||
|
inter_tegra->receive_enable = 0;
|
||
|
mutex_unlock(&inter_tegra->mutex);
|
||
|
}
|
||
|
|
||
|
return count;
|
||
|
}
|
||
|
|
||
|
static DEVICE_ATTR(receive_enable, (S_IRUGO | (S_IWUSR | S_IWGRP)),
|
||
|
receive_enable_show, receive_enable_store);
|
||
|
|
||
|
static struct attribute *inter_tegra_rx_attributes[] = {
|
||
|
&dev_attr_receive_enable.attr,
|
||
|
&dev_attr_err_log_enable.attr,
|
||
|
NULL
|
||
|
};
|
||
|
|
||
|
static const struct attribute_group inter_tegra_rx_attr_group = {
|
||
|
.attrs = inter_tegra_rx_attributes,
|
||
|
};
|
||
|
|
||
|
static int inter_tegra_spi_probe(struct spi_device *spi)
|
||
|
{
|
||
|
struct device_node *np = spi->dev.of_node;
|
||
|
struct inter_tegra_data *inter_tegra;
|
||
|
int err;
|
||
|
u32 value;
|
||
|
struct thermal_zone_device *thz = NULL;
|
||
|
|
||
|
if (!np) {
|
||
|
dev_err(&spi->dev, "dev of_node NULL\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
/* Allocate driver data */
|
||
|
inter_tegra = devm_kzalloc(&spi->dev,
|
||
|
sizeof(*inter_tegra), GFP_KERNEL);
|
||
|
if (IS_ERR_OR_NULL(inter_tegra))
|
||
|
return -ENOMEM;
|
||
|
|
||
|
/* Initialize the driver inter_tegra */
|
||
|
inter_tegra->spi = spi;
|
||
|
|
||
|
err = spi_setup(spi);
|
||
|
if (err < 0) {
|
||
|
dev_err(&spi->dev, "spi_setup failed!\n");
|
||
|
goto error;
|
||
|
}
|
||
|
|
||
|
spi_set_drvdata(spi, inter_tegra);
|
||
|
|
||
|
inter_tegra->err_log_enable = 0;
|
||
|
|
||
|
if (of_property_read_bool(np, "is-master"))
|
||
|
inter_tegra->is_master = 1;
|
||
|
|
||
|
mutex_init(&inter_tegra->mutex);
|
||
|
|
||
|
if (inter_tegra->is_master) {
|
||
|
mutex_init(&inter_tegra->tx_mutex);
|
||
|
|
||
|
err = of_property_read_string(np, "send_tz_type",
|
||
|
&inter_tegra->send_tz_type);
|
||
|
if (err < 0) {
|
||
|
dev_info(&spi->dev,
|
||
|
"no send_tz_type: %d\n", err);
|
||
|
goto error;
|
||
|
}
|
||
|
dev_info(&spi->dev, "send_tz_type: %s\n",
|
||
|
inter_tegra->send_tz_type);
|
||
|
|
||
|
thz = devm_thermal_zone_of_sensor_register(&spi->dev, 0,
|
||
|
inter_tegra,
|
||
|
&inter_tegra_tx_ops);
|
||
|
if (IS_ERR(thz)) {
|
||
|
dev_err(&spi->dev,
|
||
|
"Failed to register thermal zone device\n");
|
||
|
err = -EINVAL;
|
||
|
goto error;
|
||
|
}
|
||
|
inter_tegra->tx_thz = thz;
|
||
|
|
||
|
/* SPI master send period time. */
|
||
|
err = of_property_read_u32(np, "send_period_ms", &value);
|
||
|
if (err < 0) {
|
||
|
dev_info(&spi->dev,
|
||
|
"no send_period_ms: %d\n", err);
|
||
|
goto error;
|
||
|
}
|
||
|
|
||
|
inter_tegra->send_period_ms = (int)value;
|
||
|
dev_info(&spi->dev, "send_period_ms: %d\n",
|
||
|
inter_tegra->send_period_ms);
|
||
|
|
||
|
INIT_DELAYED_WORK(&(inter_tegra->work),
|
||
|
inter_tegra_send_work);
|
||
|
|
||
|
err = sysfs_create_group(&spi->dev.kobj,
|
||
|
&inter_tegra_tx_attr_group);
|
||
|
if (err < 0) {
|
||
|
dev_info(&spi->dev,
|
||
|
"sysfs create err=%d\n", err);
|
||
|
goto error;
|
||
|
}
|
||
|
dev_info(&spi->dev,
|
||
|
"registered inter_tegra_spi driver as master\n");
|
||
|
} else {
|
||
|
thz = devm_thermal_zone_of_sensor_register(&spi->dev, 0,
|
||
|
inter_tegra,
|
||
|
&inter_tegra_rx_ops);
|
||
|
if (IS_ERR(thz)) {
|
||
|
dev_err(&spi->dev,
|
||
|
"Failed to register thermal zone device\n");
|
||
|
err = -EINVAL;
|
||
|
goto error;
|
||
|
}
|
||
|
inter_tegra->rx_thz = thz;
|
||
|
|
||
|
/* SPI slave receive timeout value. This value need to set
|
||
|
larger than send_period_ms of master, 0 is infinite wait.*/
|
||
|
err = of_property_read_u32(np, "receive_timeout_ms", &value);
|
||
|
if (err < 0) {
|
||
|
dev_info(&spi->dev,
|
||
|
"no receive_timeout_ms: %d\n", err);
|
||
|
goto error;
|
||
|
}
|
||
|
|
||
|
inter_tegra->receive_timeout_ms = (int)value;
|
||
|
dev_info(&spi->dev, "receive_timeout_ms: %d\n",
|
||
|
inter_tegra->receive_timeout_ms);
|
||
|
|
||
|
err = sysfs_create_group(&spi->dev.kobj,
|
||
|
&inter_tegra_rx_attr_group);
|
||
|
if (err < 0) {
|
||
|
dev_info(&spi->dev,
|
||
|
"sysfs create err=%d\n", err);
|
||
|
goto error;
|
||
|
}
|
||
|
dev_info(&spi->dev,
|
||
|
"registered inter_tegra_spi driver as slave\n");
|
||
|
}
|
||
|
return 0;
|
||
|
error:
|
||
|
devm_kfree(&spi->dev, (void *)inter_tegra);
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
static int inter_tegra_spi_remove(struct spi_device *spi)
|
||
|
{
|
||
|
struct inter_tegra_data *inter_tegra = spi_get_drvdata(spi);
|
||
|
|
||
|
if (inter_tegra->is_master) {
|
||
|
cancel_delayed_work_sync(&inter_tegra->work);
|
||
|
sysfs_remove_group(&spi->dev.kobj, &inter_tegra_tx_attr_group);
|
||
|
mutex_destroy(&inter_tegra->tx_mutex);
|
||
|
} else {
|
||
|
if (inter_tegra->thread)
|
||
|
kthread_stop(inter_tegra->thread);
|
||
|
sysfs_remove_group(&spi->dev.kobj, &inter_tegra_rx_attr_group);
|
||
|
inter_tegra->thread = NULL;
|
||
|
}
|
||
|
mutex_destroy(&inter_tegra->mutex);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void inter_tegra_shutdown(struct spi_device *spi)
|
||
|
{
|
||
|
struct inter_tegra_data *inter_tegra = spi_get_drvdata(spi);
|
||
|
|
||
|
if (inter_tegra->is_master) {
|
||
|
cancel_delayed_work_sync(&inter_tegra->work);
|
||
|
} else {
|
||
|
if (inter_tegra->thread)
|
||
|
kthread_stop(inter_tegra->thread);
|
||
|
inter_tegra->thread = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_PM_SLEEP
|
||
|
static int inter_tegra_suspend(struct device *dev)
|
||
|
{
|
||
|
struct inter_tegra_data *inter_tegra = dev_get_drvdata(dev);
|
||
|
|
||
|
mutex_lock(&inter_tegra->mutex);
|
||
|
if (inter_tegra->is_master) {
|
||
|
cancel_delayed_work_sync(&inter_tegra->work);
|
||
|
} else {
|
||
|
if (inter_tegra->receive_enable) {
|
||
|
if (inter_tegra->thread)
|
||
|
kthread_stop(inter_tegra->thread);
|
||
|
inter_tegra->thread = NULL;
|
||
|
}
|
||
|
}
|
||
|
mutex_unlock(&inter_tegra->mutex);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int inter_tegra_resume(struct device *dev)
|
||
|
{
|
||
|
struct inter_tegra_data *inter_tegra = dev_get_drvdata(dev);
|
||
|
|
||
|
mutex_lock(&inter_tegra->mutex);
|
||
|
if (inter_tegra->is_master) {
|
||
|
if (inter_tegra->send_enable)
|
||
|
schedule_delayed_work(&inter_tegra->work,
|
||
|
msecs_to_jiffies(inter_tegra->send_period_ms));
|
||
|
} else {
|
||
|
if (inter_tegra->receive_enable) {
|
||
|
inter_tegra->thread =
|
||
|
kthread_run(inter_tegra_read_thread,
|
||
|
(void *)inter_tegra, "inter_tegra_read_thread");
|
||
|
if (IS_ERR_OR_NULL(inter_tegra->thread)) {
|
||
|
inter_tegra->thread = NULL;
|
||
|
dev_err(&inter_tegra->spi->dev,
|
||
|
"fail to create thread\n");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
mutex_unlock(&inter_tegra->mutex);
|
||
|
return 0;
|
||
|
}
|
||
|
#endif /* CONFIG_PM */
|
||
|
|
||
|
static SIMPLE_DEV_PM_OPS(inter_tegra_pm_ops,
|
||
|
inter_tegra_suspend,
|
||
|
inter_tegra_resume);
|
||
|
|
||
|
static const struct of_device_id inter_tegra_ids[] = {
|
||
|
{ .compatible = "inter-tegra-spi", },
|
||
|
{}
|
||
|
};
|
||
|
MODULE_DEVICE_TABLE(of, inter_tegra_ids);
|
||
|
|
||
|
static struct spi_driver inter_tegra_spi_driver = {
|
||
|
.driver = {
|
||
|
.owner = THIS_MODULE,
|
||
|
.name = "inter_tegra_spi",
|
||
|
.of_match_table = of_match_ptr(inter_tegra_ids),
|
||
|
.pm = &inter_tegra_pm_ops,
|
||
|
},
|
||
|
.probe = inter_tegra_spi_probe,
|
||
|
.remove = inter_tegra_spi_remove,
|
||
|
.shutdown = inter_tegra_shutdown,
|
||
|
};
|
||
|
|
||
|
static int __init inter_tegra_spi_init(void)
|
||
|
{
|
||
|
return spi_register_driver(&inter_tegra_spi_driver);
|
||
|
}
|
||
|
|
||
|
static void __exit inter_tegra_spi_exit(void)
|
||
|
{
|
||
|
spi_unregister_driver(&inter_tegra_spi_driver);
|
||
|
}
|
||
|
|
||
|
module_init(inter_tegra_spi_init);
|
||
|
module_exit(inter_tegra_spi_exit);
|
||
|
|
||
|
MODULE_AUTHOR("Hyong Bin Kim <hyongbink@nvidia.com>");
|
||
|
MODULE_DESCRIPTION("Interconnected Tegra SPI driver");
|
||
|
MODULE_LICENSE("GPL");
|
||
|
MODULE_ALIAS("spi:inter_tegra_spi");
|