704 lines
18 KiB
C
704 lines
18 KiB
C
/*
|
|
* tegra_cpc.c - Access CPC storage blocks through i2c bus
|
|
*
|
|
* Copyright (c) 2014-2018, 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/module.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/io.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/completion.h>
|
|
|
|
/* misc device */
|
|
#include <linux/fs.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/i2c.h>
|
|
#include <asm/unaligned.h>
|
|
#include <linux/ioctl.h>
|
|
#include <linux/types.h>
|
|
#include <uapi/linux/tegra_cpc.h>
|
|
|
|
#define CPC_I2C_DEADLINE_MS 1000
|
|
#define TEGRA_CPC_DEBUG 0
|
|
|
|
/*
|
|
* Helper macro section
|
|
*/
|
|
|
|
/* returns 0 on a successful copy. Returns -ENOMEM otherwise */
|
|
inline int tegra_cpc_serialize_field_n(u8 **dest, u8 *source, size_t len,
|
|
size_t *rem_buf_size)
|
|
{
|
|
if (unlikely(*rem_buf_size < len))
|
|
return -ENOMEM;
|
|
memcpy(*dest, source, len);
|
|
(*rem_buf_size) -= len;
|
|
(*dest) = &((*dest)[len]);
|
|
return 0;
|
|
}
|
|
|
|
#define TEGRA_CPC_SERIALIZE_FIELD(dest, source, rem_buf_len) \
|
|
tegra_cpc_serialize_field_n(dest, (u8 *) &source, sizeof(source), \
|
|
rem_buf_len)
|
|
|
|
/* returns 0 on a successful copy. Returns -ENOMEM otherwise */
|
|
inline int tegra_cpc_deserialize_field_n(u8 *dest, u8 **source, size_t len,
|
|
size_t *rem_buf_size)
|
|
{
|
|
if (unlikely(*rem_buf_size < len))
|
|
return -ENOMEM;
|
|
memcpy(dest, *source, len);
|
|
(*rem_buf_size) -= len;
|
|
(*source) = &((*source)[len]);
|
|
return 0;
|
|
}
|
|
|
|
#define TEGRA_CPC_DESERIALIZE_FIELD(dest, source, rem_buf_len) \
|
|
tegra_cpc_deserialize_field_n((u8 *) &dest, source, sizeof(dest), \
|
|
rem_buf_len)
|
|
|
|
#define TEGRA_CPC_COPY_TO_VAR(var, source) \
|
|
memcpy(&var, source, sizeof(source))
|
|
|
|
inline int tegra_cpc_verify_fr_len(u8 length, struct i2c_client *i2c_client)
|
|
{
|
|
if (unlikely((length == 0) ||
|
|
(length > CPC_MAX_DATA_SIZE))) {
|
|
dev_err(&i2c_client->dev,
|
|
"The requested size is invalid: %u",
|
|
(unsigned int) length);
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Local data structure section */
|
|
struct cpc_i2c_host {
|
|
struct i2c_client *i2c_client;
|
|
struct regmap *regmap;
|
|
struct i2c_msg readback;
|
|
|
|
int irq;
|
|
struct mutex lock;
|
|
struct completion complete;
|
|
};
|
|
|
|
/* There will be at most one uC instance per board */
|
|
static atomic_t cpc_in_use;
|
|
static struct cpc_i2c_host *cpc_host;
|
|
|
|
/*
|
|
* Serialization and deserialization logic
|
|
*
|
|
* serialize functions assume buffer is enough to hold the entire fr, and
|
|
* that the content of fr has been verified already
|
|
*
|
|
* deserialize function assumes that the buffer is enough to hold the fr
|
|
*/
|
|
|
|
/* read counter */
|
|
static int cpc_serialize_read_counter(struct tegra_cpc_frame *fr, u8 buffer[],
|
|
size_t buf_size) {
|
|
|
|
size_t rem_buf_size = buf_size;
|
|
struct tegra_cpc_read_counter_data *fr_cmd = &fr->cmd_data.read_counter;
|
|
|
|
if (TEGRA_CPC_SERIALIZE_FIELD(&buffer, fr->req, &rem_buf_size) ||
|
|
TEGRA_CPC_SERIALIZE_FIELD(&buffer, fr_cmd->nonce,
|
|
&rem_buf_size) ||
|
|
TEGRA_CPC_SERIALIZE_FIELD(&buffer, fr_cmd->derivation_value,
|
|
&rem_buf_size))
|
|
return -ENOMEM;
|
|
|
|
return buf_size - rem_buf_size;
|
|
}
|
|
|
|
static inline size_t cpc_deserialize_size_read_counter(
|
|
struct tegra_cpc_frame *fr) {
|
|
struct tegra_cpc_read_counter_data *fr_cmd = &fr->cmd_data.read_counter;
|
|
|
|
return sizeof(fr_cmd->hmac) + sizeof(fr_cmd->write_counter);
|
|
}
|
|
|
|
static int cpc_deserialize_read_counter(struct tegra_cpc_frame *fr, u8 buffer[],
|
|
size_t rem_buf_size) {
|
|
struct tegra_cpc_read_counter_data *fr_cmd = &fr->cmd_data.read_counter;
|
|
|
|
if (TEGRA_CPC_DESERIALIZE_FIELD(fr_cmd->hmac, &buffer, &rem_buf_size) ||
|
|
TEGRA_CPC_DESERIALIZE_FIELD(fr_cmd->write_counter, &buffer,
|
|
&rem_buf_size))
|
|
return -ENOMEM;
|
|
return 0;
|
|
}
|
|
|
|
/* write frame */
|
|
static int cpc_serialize_write_frame(struct tegra_cpc_frame *fr, u8 buffer[],
|
|
size_t buf_size) {
|
|
|
|
size_t rem_buf_size = buf_size;
|
|
struct tegra_cpc_write_frame_data *fr_cmd = &fr->cmd_data.write_frame;
|
|
size_t oneshot_copy_size = sizeof(fr_cmd->nonce) +
|
|
sizeof(fr_cmd->write_counter) + fr_cmd->length;
|
|
|
|
if (TEGRA_CPC_SERIALIZE_FIELD(&buffer, fr->req, &rem_buf_size) ||
|
|
TEGRA_CPC_SERIALIZE_FIELD(&buffer, fr_cmd->hmac,
|
|
&rem_buf_size) ||
|
|
TEGRA_CPC_SERIALIZE_FIELD(&buffer, fr_cmd->length,
|
|
&rem_buf_size))
|
|
return -ENOMEM;
|
|
|
|
if (rem_buf_size < oneshot_copy_size)
|
|
return -ENOMEM;
|
|
|
|
rem_buf_size -= oneshot_copy_size;
|
|
|
|
memcpy(buffer, &fr_cmd->nonce, oneshot_copy_size);
|
|
return buf_size - rem_buf_size;
|
|
}
|
|
|
|
/* read frame */
|
|
static int cpc_serialize_read_frame(struct tegra_cpc_frame *fr, u8 buffer[],
|
|
size_t buf_size) {
|
|
size_t rem_buf_size = buf_size;
|
|
struct tegra_cpc_read_frame_data *fr_cmd = &fr->cmd_data.read_frame;
|
|
|
|
if (TEGRA_CPC_SERIALIZE_FIELD(&buffer, fr->req, &rem_buf_size) ||
|
|
TEGRA_CPC_SERIALIZE_FIELD(&buffer, fr_cmd->nonce,
|
|
&rem_buf_size) ||
|
|
TEGRA_CPC_SERIALIZE_FIELD(&buffer, fr_cmd->length,
|
|
&rem_buf_size))
|
|
return -ENOMEM;
|
|
|
|
return buf_size - rem_buf_size;
|
|
}
|
|
|
|
static inline size_t cpc_deserialize_size_read_frame(
|
|
struct tegra_cpc_frame *fr) {
|
|
struct tegra_cpc_read_frame_data *fr_cmd = &fr->cmd_data.read_frame;
|
|
|
|
return sizeof(fr_cmd->hmac) + fr_cmd->length;
|
|
}
|
|
|
|
static int cpc_deserialize_read_frame(struct tegra_cpc_frame *fr, u8 buffer[],
|
|
size_t rem_buf_size) {
|
|
struct tegra_cpc_read_frame_data *fr_cmd = &fr->cmd_data.read_frame;
|
|
|
|
if (TEGRA_CPC_DESERIALIZE_FIELD(fr_cmd->hmac, &buffer, &rem_buf_size) ||
|
|
tegra_cpc_deserialize_field_n(fr_cmd->data, &buffer,
|
|
fr_cmd->length, &rem_buf_size))
|
|
return -ENOMEM;
|
|
return 0;
|
|
}
|
|
|
|
/* Get Status */
|
|
static int cpc_serialize_get_status(struct tegra_cpc_frame *fr, u8 buffer[],
|
|
size_t buf_size) {
|
|
size_t rem_buf_size = buf_size;
|
|
|
|
/* get status can run without any data from the user space */
|
|
u8 req = CPC_GET_RESULT;
|
|
|
|
if (TEGRA_CPC_SERIALIZE_FIELD(&buffer, req, &rem_buf_size))
|
|
return -ENOMEM;
|
|
|
|
return buf_size - rem_buf_size;
|
|
}
|
|
|
|
static inline size_t cpc_deserialize_size_get_status(
|
|
struct tegra_cpc_frame *fr) {
|
|
return sizeof(fr->hmac) + sizeof(fr->result) +
|
|
sizeof(fr->nonce);
|
|
}
|
|
|
|
static int cpc_deserialize_get_status(struct tegra_cpc_frame *fr, u8 buffer[],
|
|
size_t rem_buf_size) {
|
|
if (TEGRA_CPC_DESERIALIZE_FIELD(fr->hmac, &buffer, &rem_buf_size) ||
|
|
TEGRA_CPC_DESERIALIZE_FIELD(fr->result, &buffer,
|
|
&rem_buf_size) ||
|
|
TEGRA_CPC_DESERIALIZE_FIELD(fr->nonce, &buffer, &rem_buf_size))
|
|
return -ENOMEM;
|
|
return 0;
|
|
}
|
|
|
|
/* Get Version */
|
|
static int cpc_serialize_get_version(struct tegra_cpc_frame *fr, u8 buffer[],
|
|
size_t buf_size) {
|
|
size_t rem_buf_size = buf_size;
|
|
struct tegra_cpc_get_version_data *fr_cmd = &fr->cmd_data.get_version;
|
|
u8 req = CPC_GET_VERSION;
|
|
|
|
if (TEGRA_CPC_SERIALIZE_FIELD(&buffer, req, &rem_buf_size) ||
|
|
TEGRA_CPC_SERIALIZE_FIELD(&buffer, fr_cmd->nonce,
|
|
&rem_buf_size))
|
|
return -ENOMEM;
|
|
|
|
return buf_size - rem_buf_size;
|
|
}
|
|
|
|
static inline size_t cpc_deserialize_size_get_version(
|
|
struct tegra_cpc_frame *fr) {
|
|
struct tegra_cpc_get_version_data *fr_cmd = &fr->cmd_data.get_version;
|
|
|
|
return sizeof(fr_cmd->major_ver) + sizeof(fr_cmd->minor_ver);
|
|
}
|
|
|
|
static int cpc_deserialize_get_version(struct tegra_cpc_frame *fr, u8 buffer[],
|
|
size_t rem_buf_size) {
|
|
struct tegra_cpc_get_version_data *fr_cmd = &fr->cmd_data.get_version;
|
|
|
|
if (TEGRA_CPC_DESERIALIZE_FIELD(fr_cmd->minor_ver, &buffer,
|
|
&rem_buf_size) ||
|
|
TEGRA_CPC_DESERIALIZE_FIELD(fr_cmd->major_ver, &buffer,
|
|
&rem_buf_size))
|
|
return -ENOMEM;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* I2C related section
|
|
*/
|
|
static int cpc_send_i2c_msg(struct i2c_client *client, struct i2c_msg *msgs,
|
|
unsigned int num_msg)
|
|
{
|
|
int err;
|
|
|
|
err = i2c_transfer(client->adapter, msgs, num_msg);
|
|
if (unlikely(err != num_msg)) {
|
|
if (err < 0)
|
|
dev_err(&client->dev,
|
|
"%s: i2c transfer failed. Error: %d\n",
|
|
__func__, err);
|
|
|
|
if (err >= 0)
|
|
dev_err(&client->dev,
|
|
"%s: i2c transfer failed. Transferred %d/%u\n",
|
|
__func__, err, num_msg);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Issues a given read command and perform deserialization of the read data
|
|
* Assumes the caller has performed all error checks on fr
|
|
*/
|
|
static int cpc_read_data(struct i2c_client *client, struct tegra_cpc_frame *fr,
|
|
int (*cpc_serialization_strategy)
|
|
(struct tegra_cpc_frame *fr, u8 buffer[], size_t buf_size),
|
|
int expected_resp_len,
|
|
int (*cpc_deserialization_strategy)
|
|
(struct tegra_cpc_frame *fr, u8 buffer[], size_t buf_size))
|
|
{
|
|
struct i2c_msg msg[2];
|
|
int ret = 0;
|
|
int packet_len;
|
|
|
|
/*
|
|
* The read command will not send any data. The uC may send more data
|
|
* then length bytes, if HMAC is involved
|
|
*
|
|
* The buffer for reading the data back has to be big enough to hold
|
|
I the maximum value which can be expressed by the size of cpc_data_len
|
|
*/
|
|
u8 buffer_req[sizeof(struct tegra_cpc_frame)];
|
|
u8 buffer_resp[sizeof(struct tegra_cpc_frame)];
|
|
|
|
if (unlikely(!client->adapter))
|
|
return -ENODEV;
|
|
|
|
/*
|
|
* First put header of CPC read command. Start with the write mode flag,
|
|
* to communicate which command we are sending. Then read the response
|
|
* on the bus
|
|
*/
|
|
packet_len = cpc_serialization_strategy(fr, buffer_req,
|
|
sizeof(buffer_req));
|
|
if (unlikely(packet_len <= 0)) {
|
|
dev_err(&client->dev, "Serialization failure: %d\n",
|
|
packet_len);
|
|
return -EINVAL;
|
|
}
|
|
|
|
msg[0].addr = client->addr;
|
|
msg[0].len = (__u16) packet_len;
|
|
msg[0].flags = 0;
|
|
msg[0].buf = buffer_req;
|
|
/*
|
|
* The second i2c packet is empty, as it is only a buffer space for
|
|
* reading the requested data back
|
|
*/
|
|
msg[1].addr = client->addr;
|
|
msg[1].len = expected_resp_len;
|
|
msg[1].flags = I2C_M_RD;
|
|
msg[1].buf = buffer_resp;
|
|
ret = cpc_send_i2c_msg(client, msg,
|
|
sizeof(msg) / sizeof(struct i2c_msg));
|
|
if (unlikely(ret))
|
|
return ret;
|
|
|
|
ret = cpc_deserialization_strategy(fr, buffer_resp, expected_resp_len);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Executes a write command.
|
|
*/
|
|
static int cpc_write_data(struct i2c_client *client, struct tegra_cpc_frame *fr,
|
|
int (*cpc_serialization_strategy)
|
|
(struct tegra_cpc_frame *fr, u8 buffer[], size_t buf_size)) {
|
|
struct i2c_msg msg[1];
|
|
int packet_len;
|
|
u8 buffer_req[sizeof(struct tegra_cpc_frame)];
|
|
|
|
if (unlikely(!client->adapter))
|
|
return -ENODEV;
|
|
|
|
packet_len = cpc_serialization_strategy(fr, buffer_req,
|
|
sizeof(buffer_req));
|
|
if (unlikely(packet_len <= 0)) {
|
|
dev_err(&client->dev, "Serialization failure: %d\n",
|
|
packet_len);
|
|
return -EINVAL;
|
|
}
|
|
msg[0].addr = client->addr;
|
|
msg[0].len = (__u16) packet_len;
|
|
msg[0].flags = 0;
|
|
msg[0].buf = buffer_req;
|
|
|
|
return cpc_send_i2c_msg(client, msg,
|
|
sizeof(msg) / sizeof(struct i2c_msg));
|
|
}
|
|
|
|
/*
|
|
* Command from user space handling section
|
|
*/
|
|
|
|
/* Error checks against the content of data struct must be done here */
|
|
static int _cpc_do_data(struct cpc_i2c_host *cpc, struct tegra_cpc_frame *fr)
|
|
{
|
|
int err;
|
|
u32 req_nonce;
|
|
u32 resp_nonce;
|
|
|
|
mutex_lock(&cpc->lock);
|
|
fr->result = CPC_RESULT_UNKNOWN_REQUEST;
|
|
|
|
/* Each command's input error checked here */
|
|
switch (fr->req) {
|
|
case CPC_READ_M_COUNT:
|
|
TEGRA_CPC_COPY_TO_VAR(req_nonce,
|
|
fr->cmd_data.read_counter.nonce);
|
|
err = cpc_read_data(cpc->i2c_client, fr,
|
|
cpc_serialize_read_counter,
|
|
cpc_deserialize_size_read_counter(fr),
|
|
cpc_deserialize_read_counter);
|
|
break;
|
|
|
|
case CPC_READ_FRAME:
|
|
if (tegra_cpc_verify_fr_len(fr->cmd_data.read_frame.length,
|
|
cpc->i2c_client)) {
|
|
fr->result = CPC_RESULT_DATA_LENGTH_MISMATCH;
|
|
err = -EINVAL;
|
|
goto fail;
|
|
}
|
|
TEGRA_CPC_COPY_TO_VAR(req_nonce, fr->cmd_data.read_frame.nonce);
|
|
err = cpc_read_data(cpc->i2c_client, fr,
|
|
cpc_serialize_read_frame,
|
|
cpc_deserialize_size_read_frame(fr),
|
|
cpc_deserialize_read_frame);
|
|
break;
|
|
|
|
case CPC_WRITE_FRAME:
|
|
if (tegra_cpc_verify_fr_len(fr->cmd_data.write_frame.length,
|
|
cpc->i2c_client)) {
|
|
fr->result = CPC_RESULT_DATA_LENGTH_MISMATCH;
|
|
err = -EINVAL;
|
|
goto fail;
|
|
}
|
|
init_completion(&cpc->complete);
|
|
TEGRA_CPC_COPY_TO_VAR(req_nonce,
|
|
fr->cmd_data.write_frame.nonce);
|
|
err = cpc_write_data(cpc->i2c_client, fr,
|
|
cpc_serialize_write_frame);
|
|
|
|
if (!err && !wait_for_completion_timeout(&cpc->complete,
|
|
msecs_to_jiffies(CPC_I2C_DEADLINE_MS))) {
|
|
dev_err(&cpc->i2c_client->dev,
|
|
"%s timeout on write complete\n", __func__);
|
|
fr->result = CPC_RESULT_WRITE_FAILURE;
|
|
err = -ETIMEDOUT;
|
|
}
|
|
break;
|
|
|
|
case CPC_GET_VERSION:
|
|
TEGRA_CPC_COPY_TO_VAR(req_nonce,
|
|
fr->cmd_data.get_version.nonce);
|
|
err = cpc_read_data(cpc->i2c_client, fr,
|
|
cpc_serialize_get_version,
|
|
cpc_deserialize_size_get_version(fr),
|
|
cpc_deserialize_get_version);
|
|
break;
|
|
|
|
default:
|
|
err = -EINVAL;
|
|
dev_err(&cpc->i2c_client->dev,
|
|
"unsupported CPC command id %08x\n", fr->req);
|
|
}
|
|
|
|
if (err)
|
|
goto fail;
|
|
|
|
/* get status should run only when a command was ran successfully */
|
|
err = cpc_read_data(cpc->i2c_client, fr,
|
|
cpc_serialize_get_status,
|
|
cpc_deserialize_size_get_status(fr),
|
|
cpc_deserialize_get_status);
|
|
|
|
if (unlikely(err)) {
|
|
dev_err(&cpc->i2c_client->dev,
|
|
"failed to run get status command: %d\n", err);
|
|
fr->result = CPC_RESULT_UNEXPECTED_CONDITION;
|
|
goto fail;
|
|
}
|
|
|
|
/*
|
|
* The request was sent to uC, and uC responded.
|
|
* Make sure the response is for the request sent.
|
|
*/
|
|
TEGRA_CPC_COPY_TO_VAR(resp_nonce, fr->nonce);
|
|
if (unlikely(req_nonce != resp_nonce)) {
|
|
dev_err(&cpc->i2c_client->dev,
|
|
"Request nonce (%08x) mismatching response nonce (%08x)\n",
|
|
req_nonce, resp_nonce);
|
|
err = -EINVAL;
|
|
fr->result = CPC_RESULT_UNEXPECTED_CONDITION;
|
|
}
|
|
|
|
fail:
|
|
mutex_unlock(&cpc->lock);
|
|
return err;
|
|
}
|
|
|
|
static int cpc_open(struct inode *inode, struct file *filp)
|
|
{
|
|
if (atomic_xchg(&cpc_in_use, 1))
|
|
return -EBUSY;
|
|
|
|
if (!cpc_host) {
|
|
pr_info("CPC is not ready\n");
|
|
return -EBADF;
|
|
}
|
|
|
|
filp->private_data = cpc_host;
|
|
|
|
return nonseekable_open(inode, filp);
|
|
}
|
|
|
|
static int cpc_release(struct inode *inode, struct file *filp)
|
|
{
|
|
WARN_ON(!atomic_xchg(&cpc_in_use, 0));
|
|
return 0;
|
|
}
|
|
|
|
static long cpc_ioctl(struct file *file,
|
|
unsigned int cmd, unsigned long arg)
|
|
{
|
|
struct cpc_i2c_host *cpc = file->private_data;
|
|
struct tegra_cpc_frame *cpc_fr = NULL;
|
|
long err = 0;
|
|
|
|
if (_IOC_TYPE(cmd) != NVCPC_IOC_MAGIC) {
|
|
err = -ENOTTY;
|
|
goto fail;
|
|
}
|
|
|
|
if (_IOC_NR(cmd) > NVCPC_IOCTL_DO_IO) {
|
|
err = -ENOTTY;
|
|
goto fail;
|
|
}
|
|
|
|
switch (cmd) {
|
|
case NVCPC_IOCTL_DO_IO:
|
|
cpc_fr = kmalloc(sizeof(struct tegra_cpc_frame), GFP_KERNEL);
|
|
if (!cpc_fr) {
|
|
pr_err("%s: failed to allocate memory\n", __func__);
|
|
err = -ENOMEM;
|
|
goto fail;
|
|
}
|
|
|
|
if (copy_from_user(cpc_fr, (const void __user *)arg,
|
|
sizeof(struct tegra_cpc_frame))) {
|
|
err = -EFAULT;
|
|
goto fail;
|
|
}
|
|
|
|
err = _cpc_do_data(cpc, cpc_fr);
|
|
|
|
if (copy_to_user((void __user *)arg, cpc_fr,
|
|
sizeof(struct tegra_cpc_frame))) {
|
|
err = -EFAULT;
|
|
goto fail;
|
|
}
|
|
break;
|
|
default:
|
|
pr_err("CPC: unsupported ioctl\n");
|
|
err = -EINVAL;
|
|
goto fail;
|
|
}
|
|
|
|
fail:
|
|
kfree(cpc_fr);
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* IRQ handling section
|
|
*/
|
|
static irqreturn_t cpc_irq(int irq, void *data)
|
|
{
|
|
struct cpc_i2c_host *cpc = data;
|
|
|
|
complete(&cpc->complete);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/*
|
|
* Driver setup section
|
|
*/
|
|
static const struct file_operations cpc_dev_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = cpc_open,
|
|
.release = cpc_release,
|
|
.unlocked_ioctl = cpc_ioctl,
|
|
#ifdef CONFIG_COMPAT
|
|
.compat_ioctl = cpc_ioctl,
|
|
#endif
|
|
};
|
|
|
|
static struct miscdevice cpc_dev = {
|
|
.minor = MISC_DYNAMIC_MINOR,
|
|
.name = "tegra_cpc",
|
|
.fops = &cpc_dev_fops,
|
|
};
|
|
|
|
static int cpc_i2c_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
int err = 0;
|
|
|
|
/* Initialize stack variables and structures */
|
|
cpc_host = kzalloc(sizeof(struct cpc_i2c_host), GFP_KERNEL);
|
|
if (!cpc_host) {
|
|
pr_err("unable to allocate memory\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Setup I2C */
|
|
cpc_host->i2c_client = client;
|
|
i2c_set_clientdata(client, cpc_host);
|
|
cpc_host->irq = client->irq;
|
|
mutex_init(&cpc_host->lock);
|
|
|
|
/* Setup IRQ */
|
|
if (cpc_host->irq < 0) {
|
|
dev_err(&client->dev,
|
|
"Unable to enable IRQ\n");
|
|
err = -EINVAL;
|
|
goto pre_irq_fail;
|
|
} else {
|
|
/*
|
|
* In case the IRQ fires before complete is initialized,
|
|
* initialize complete now and over write later
|
|
*
|
|
* Another option is to disable IRQ until the host is
|
|
* ready to handle the IRQ, but that causes recurring burden
|
|
* of re-enabling and re-disabling IRQ
|
|
*/
|
|
init_completion(&cpc_host->complete);
|
|
err = request_threaded_irq(cpc_host->irq, cpc_irq, NULL,
|
|
IRQF_TRIGGER_FALLING,
|
|
dev_name(&client->dev), cpc_host);
|
|
if (err) {
|
|
dev_err(&client->dev, "%s: request irq failed %d\n",
|
|
__func__, err);
|
|
err = -EINVAL;
|
|
goto pre_irq_fail;
|
|
}
|
|
}
|
|
|
|
/* setup misc device for userspace io */
|
|
if (misc_register(&cpc_dev)) {
|
|
pr_err("%s: misc device register failed\n", __func__);
|
|
err = -ENOMEM;
|
|
goto fail;
|
|
}
|
|
|
|
dev_info(&client->dev, "host driver for CPC\n");
|
|
return 0;
|
|
|
|
fail:
|
|
free_irq(cpc_host->irq, cpc_host);
|
|
pre_irq_fail:
|
|
dev_set_drvdata(&client->dev, NULL);
|
|
mutex_destroy(&cpc_host->lock);
|
|
kfree(cpc_host);
|
|
return err;
|
|
}
|
|
|
|
static int cpc_i2c_remove(struct i2c_client *client)
|
|
{
|
|
struct cpc_i2c_host *cpc_host = dev_get_drvdata(&client->dev);
|
|
|
|
mutex_destroy(&cpc_host->lock);
|
|
free_irq(cpc_host->irq, cpc_host);
|
|
kfree(cpc_host);
|
|
cpc_host = NULL;
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id cpc_i2c_of_match_table[] = {
|
|
{ .compatible = "nvidia,cpc", },
|
|
{},
|
|
};
|
|
|
|
static const struct i2c_device_id cpc_i2c_id[] = {
|
|
{ "cpc", 0 },
|
|
{ }
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(i2c, cpc_i2c_id);
|
|
|
|
static struct i2c_driver cpc_i2c_driver = {
|
|
.driver = {
|
|
.name = "cpc",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = cpc_i2c_of_match_table,
|
|
},
|
|
.probe = cpc_i2c_probe,
|
|
.remove = cpc_i2c_remove,
|
|
.id_table = cpc_i2c_id,
|
|
};
|
|
|
|
module_i2c_driver(cpc_i2c_driver);
|
|
|
|
MODULE_AUTHOR("Sang-Hun Lee");
|
|
MODULE_DESCRIPTION("Content protection misc driver");
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_ALIAS("i2c:cpc_i2c");
|