open-gpu-kernel-modules/kernel-open/nvidia/i2c_nvswitch.c
2022-05-09 13:18:59 -07:00

351 lines
9.1 KiB
C

/*
* SPDX-FileCopyrightText: Copyright (c) 2021 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: MIT
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include "linux_nvswitch.h"
#include <linux/i2c.h>
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
#define NVSWITCH_I2C_GET_PARENT(adapter) \
(NVSWITCH_DEV *)pci_get_drvdata(to_pci_dev((adapter)->dev.parent));
#define NVSWITCH_I2C_GET_ALGO_DATA(adapter) \
(nvswitch_i2c_algo_data *)(adapter)->algo_data;
typedef struct
{
NvU32 port;
} nvswitch_i2c_algo_data;
static int
nvswitch_i2c_algo_master_xfer
(
struct i2c_adapter *adapter,
struct i2c_msg msgs[],
int num
)
{
int rc;
int i;
NvU32 port;
NvlStatus status = NVL_SUCCESS;
nvswitch_i2c_algo_data *i2c_algo_data;
NVSWITCH_DEV *nvswitch_dev;
const unsigned int supported_i2c_flags = I2C_M_RD
#if defined (I2C_M_DMA_SAFE)
| I2C_M_DMA_SAFE
#endif
;
nvswitch_dev = NVSWITCH_I2C_GET_PARENT(adapter);
if (nvswitch_dev == NULL)
{
return -ENODEV;
}
rc = mutex_lock_interruptible(&nvswitch_dev->device_mutex);
if (rc)
{
return rc;
}
if (nvswitch_dev->unusable)
{
printk(KERN_INFO "%s: a stale fd detected\n", nvswitch_dev->name);
status = NVL_ERR_INVALID_STATE;
goto nvswitch_i2c_algo_master_xfer_exit;
}
i2c_algo_data = NVSWITCH_I2C_GET_ALGO_DATA(adapter);
if (i2c_algo_data == NULL)
{
status = NVL_ERR_INVALID_STATE;
goto nvswitch_i2c_algo_master_xfer_exit;
}
port = i2c_algo_data->port;
for (i = 0; (i < num) && (status == NVL_SUCCESS); i++)
{
if (msgs[i].flags & ~supported_i2c_flags)
{
status = NVL_ERR_NOT_SUPPORTED;
}
else
{
status = nvswitch_lib_i2c_transfer(nvswitch_dev->lib_device, port,
(msgs[i].flags & I2C_M_RD) ?
NVSWITCH_I2C_CMD_READ : NVSWITCH_I2C_CMD_WRITE,
(NvU8)(msgs[i].addr & 0x7f), 0,
(NvU32)(msgs[i].len & 0xffffUL),
(NvU8 *)msgs[i].buf);
}
}
nvswitch_i2c_algo_master_xfer_exit:
mutex_unlock(&nvswitch_dev->device_mutex);
rc = nvswitch_map_status(status);
return (rc == 0) ? num : rc;
}
static int
nvswitch_i2c_algo_smbus_xfer
(
struct i2c_adapter *adapter,
u16 addr,
unsigned short flags,
char read_write,
u8 command,
int protocol,
union i2c_smbus_data *data
)
{
int rc = -EIO;
NvU32 port;
NvU8 cmd;
NvU32 len;
NvU8 type;
NvU8 *xfer_data;
NvlStatus status = NVL_SUCCESS;
nvswitch_i2c_algo_data *i2c_algo_data;
NVSWITCH_DEV *nvswitch_dev;
nvswitch_dev = NVSWITCH_I2C_GET_PARENT(adapter);
if (nvswitch_dev == NULL)
{
return -ENODEV;
}
rc = mutex_lock_interruptible(&nvswitch_dev->device_mutex);
if (rc)
{
return rc;
}
if (nvswitch_dev->unusable)
{
printk(KERN_INFO "%s: a stale fd detected\n", nvswitch_dev->name);
status = NVL_ERR_INVALID_STATE;
goto nvswitch_i2c_algo_smbus_xfer_exit;
}
i2c_algo_data = NVSWITCH_I2C_GET_ALGO_DATA(adapter);
if (i2c_algo_data == NULL)
{
status = NVL_ERR_INVALID_STATE;
goto nvswitch_i2c_algo_smbus_xfer_exit;
}
port = i2c_algo_data->port;
switch (protocol)
{
case I2C_SMBUS_QUICK:
{
cmd = 0;
len = 0;
type = (read_write == I2C_SMBUS_READ) ?
NVSWITCH_I2C_CMD_SMBUS_QUICK_READ :
NVSWITCH_I2C_CMD_SMBUS_QUICK_WRITE;
xfer_data = NULL;
break;
}
case I2C_SMBUS_BYTE:
{
cmd = 0;
len = 1;
if (read_write == I2C_SMBUS_READ)
{
type = NVSWITCH_I2C_CMD_READ;
xfer_data = (NvU8 *)&data->byte;
}
else
{
type = NVSWITCH_I2C_CMD_WRITE;
xfer_data = &command;
}
break;
}
case I2C_SMBUS_BYTE_DATA:
{
cmd = (NvU8)command;
len = 1;
type = (read_write == I2C_SMBUS_READ) ?
NVSWITCH_I2C_CMD_SMBUS_READ :
NVSWITCH_I2C_CMD_SMBUS_WRITE;
cmd = (NvU8)command;
xfer_data = (NvU8 *)&data->byte;
break;
}
case I2C_SMBUS_WORD_DATA:
{
cmd = (NvU8)command;
len = 2;
type = (read_write == I2C_SMBUS_READ) ?
NVSWITCH_I2C_CMD_SMBUS_READ :
NVSWITCH_I2C_CMD_SMBUS_WRITE;
xfer_data = (NvU8 *)&data->word;
break;
}
default:
{
status = NVL_BAD_ARGS;
goto nvswitch_i2c_algo_smbus_xfer_exit;
}
}
status = nvswitch_lib_i2c_transfer(nvswitch_dev->lib_device, port,
type, (NvU8)(addr & 0x7f),
cmd, len, (NvU8 *)xfer_data);
nvswitch_i2c_algo_smbus_xfer_exit:
mutex_unlock(&nvswitch_dev->device_mutex);
return nvswitch_map_status(status);
}
static u32 nvswitch_i2c_algo_functionality(struct i2c_adapter *adapter)
{
return (I2C_FUNC_I2C |
I2C_FUNC_SMBUS_QUICK |
I2C_FUNC_SMBUS_BYTE |
I2C_FUNC_SMBUS_BYTE_DATA |
I2C_FUNC_SMBUS_WORD_DATA);
}
static struct i2c_algorithm nvswitch_i2c_algo = {
.master_xfer = nvswitch_i2c_algo_master_xfer,
.smbus_xfer = nvswitch_i2c_algo_smbus_xfer,
.functionality = nvswitch_i2c_algo_functionality,
};
struct i2c_adapter nvswitch_i2c_adapter_prototype = {
.owner = THIS_MODULE,
.algo = &nvswitch_i2c_algo,
.algo_data = NULL,
};
struct i2c_adapter *
nvswitch_i2c_add_adapter
(
NVSWITCH_DEV *nvswitch_dev,
NvU32 port
)
{
struct i2c_adapter *adapter = NULL;
int rc = 0;
struct pci_dev *pci_dev;
nvswitch_i2c_algo_data *i2c_algo_data = NULL;
if (nvswitch_dev == NULL)
{
printk(KERN_ERR "nvswitch_dev is NULL!\n");
return NULL;
}
adapter = nvswitch_os_malloc(sizeof(struct i2c_adapter));
if (adapter == NULL)
{
return NULL;
}
nvswitch_os_memcpy(adapter,
&nvswitch_i2c_adapter_prototype,
sizeof(struct i2c_adapter));
i2c_algo_data = nvswitch_os_malloc(sizeof(nvswitch_i2c_algo_data));
if (i2c_algo_data == NULL)
{
goto cleanup;
}
i2c_algo_data->port = port;
pci_dev = nvswitch_dev->pci_dev;
adapter->dev.parent = &pci_dev->dev;
adapter->algo_data = (void *)i2c_algo_data;
rc = nvswitch_os_snprintf(adapter->name,
sizeof(adapter->name),
"NVIDIA NVSwitch i2c adapter %u at %x:%02x.%u",
port,
NV_PCI_BUS_NUMBER(pci_dev),
NV_PCI_SLOT_NUMBER(pci_dev),
PCI_FUNC(pci_dev->devfn));
if ((rc < 0) && (rc >= sizeof(adapter->name)))
{
goto cleanup;
}
rc = i2c_add_adapter(adapter);
if (rc < 0)
{
goto cleanup;
}
return adapter;
cleanup:
nvswitch_os_free(i2c_algo_data);
nvswitch_os_free(adapter);
return NULL;
}
void
nvswitch_i2c_del_adapter
(
struct i2c_adapter *adapter
)
{
if (adapter != NULL)
{
nvswitch_os_free(adapter->algo_data);
i2c_del_adapter(adapter);
nvswitch_os_free(adapter);
}
}
#else // (defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE))
struct i2c_adapter *
nvswitch_i2c_add_adapter
(
NVSWITCH_DEV *nvswitch_dev,
NvU32 port
)
{
return NULL;
}
void
nvswitch_i2c_del_adapter
(
struct i2c_adapter *adapter
)
{
}
#endif // (defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE))