351 lines
9.1 KiB
C
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))
|