301 lines
9.5 KiB
C
301 lines
9.5 KiB
C
/*
|
|
* SPDX-FileCopyrightText: Copyright (c) 2005-2019 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.
|
|
*/
|
|
|
|
#define __NO_VERSION__
|
|
|
|
#include <linux/i2c.h>
|
|
|
|
#include "os-interface.h"
|
|
#include "nv-linux.h"
|
|
|
|
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
|
|
|
|
static int nv_i2c_algo_master_xfer(struct i2c_adapter *adapter, struct i2c_msg msgs[], int num)
|
|
{
|
|
nv_state_t *nv = (nv_state_t *)adapter->algo_data;
|
|
unsigned int i = 0;
|
|
int rc;
|
|
NV_STATUS rmStatus = NV_OK;
|
|
nvidia_stack_t *sp = NULL;
|
|
const unsigned int supported_i2c_flags = I2C_M_RD
|
|
#if defined(I2C_M_DMA_SAFE)
|
|
| I2C_M_DMA_SAFE
|
|
#endif
|
|
;
|
|
|
|
rc = nv_kmem_cache_alloc_stack(&sp);
|
|
if (rc != 0)
|
|
{
|
|
return rc;
|
|
}
|
|
|
|
rc = -EIO;
|
|
|
|
for (i = 0; ((i < (unsigned int)num) && (rmStatus == NV_OK)); i++)
|
|
{
|
|
if (msgs[i].flags & ~supported_i2c_flags)
|
|
{
|
|
/* we only support basic I2C reads/writes, reject any other commands */
|
|
rc = -EINVAL;
|
|
nv_printf(NV_DBG_ERRORS, "NVRM: Unsupported I2C flags used. (flags:0x%08x)\n",
|
|
msgs[i].flags);
|
|
rmStatus = NV_ERR_INVALID_ARGUMENT;
|
|
}
|
|
else
|
|
{
|
|
rmStatus = rm_i2c_transfer(sp, nv, (void *)adapter,
|
|
(msgs[i].flags & I2C_M_RD) ?
|
|
NV_I2C_CMD_READ : NV_I2C_CMD_WRITE,
|
|
(NvU8)(msgs[i].addr & 0x7f), 0,
|
|
(NvU32)(msgs[i].len & 0xffffUL),
|
|
(NvU8 *)msgs[i].buf);
|
|
}
|
|
}
|
|
|
|
nv_kmem_cache_free_stack(sp);
|
|
|
|
return (rmStatus != NV_OK) ? rc : num;
|
|
}
|
|
|
|
static int nv_i2c_algo_smbus_xfer(
|
|
struct i2c_adapter *adapter,
|
|
u16 addr,
|
|
unsigned short flags,
|
|
char read_write,
|
|
u8 command,
|
|
int size,
|
|
union i2c_smbus_data *data
|
|
)
|
|
{
|
|
nv_state_t *nv = (nv_state_t *)adapter->algo_data;
|
|
int rc;
|
|
NV_STATUS rmStatus = NV_OK;
|
|
nvidia_stack_t *sp = NULL;
|
|
|
|
rc = nv_kmem_cache_alloc_stack(&sp);
|
|
if (rc != 0)
|
|
{
|
|
return rc;
|
|
}
|
|
|
|
rc = -EIO;
|
|
|
|
switch (size)
|
|
{
|
|
case I2C_SMBUS_QUICK:
|
|
rmStatus = rm_i2c_transfer(sp, nv, (void *)adapter,
|
|
(read_write == I2C_SMBUS_READ) ?
|
|
NV_I2C_CMD_SMBUS_QUICK_READ :
|
|
NV_I2C_CMD_SMBUS_QUICK_WRITE,
|
|
(NvU8)(addr & 0x7f), 0, 0, NULL);
|
|
break;
|
|
|
|
case I2C_SMBUS_BYTE:
|
|
if (read_write == I2C_SMBUS_READ)
|
|
{
|
|
rmStatus = rm_i2c_transfer(sp, nv, (void *)adapter,
|
|
NV_I2C_CMD_READ,
|
|
(NvU8)(addr & 0x7f), 0, 1,
|
|
(NvU8 *)&data->byte);
|
|
}
|
|
else
|
|
{
|
|
u8 data = command;
|
|
rmStatus = rm_i2c_transfer(sp, nv, (void *)adapter,
|
|
NV_I2C_CMD_WRITE,
|
|
(NvU8)(addr & 0x7f), 0, 1,
|
|
(NvU8 *)&data);
|
|
}
|
|
break;
|
|
|
|
case I2C_SMBUS_BYTE_DATA:
|
|
rmStatus = rm_i2c_transfer(sp, nv, (void *)adapter,
|
|
(read_write == I2C_SMBUS_READ) ?
|
|
NV_I2C_CMD_SMBUS_READ :
|
|
NV_I2C_CMD_SMBUS_WRITE,
|
|
(NvU8)(addr & 0x7f), (NvU8)command, 1,
|
|
(NvU8 *)&data->byte);
|
|
break;
|
|
|
|
case I2C_SMBUS_WORD_DATA:
|
|
if (read_write != I2C_SMBUS_READ)
|
|
{
|
|
u16 word = data->word;
|
|
data->block[1] = (word & 0xff);
|
|
data->block[2] = (word >> 8);
|
|
}
|
|
|
|
rmStatus = rm_i2c_transfer(sp, nv, (void *)adapter,
|
|
(read_write == I2C_SMBUS_READ) ?
|
|
NV_I2C_CMD_SMBUS_READ :
|
|
NV_I2C_CMD_SMBUS_WRITE,
|
|
(NvU8)(addr & 0x7f), (NvU8)command, 2,
|
|
(NvU8 *)&data->block[1]);
|
|
|
|
if (read_write == I2C_SMBUS_READ)
|
|
{
|
|
data->word = ((NvU16)data->block[1]) |
|
|
((NvU16)data->block[2] << 8);
|
|
}
|
|
break;
|
|
|
|
case I2C_SMBUS_BLOCK_DATA:
|
|
rmStatus = rm_i2c_transfer(sp, nv, (void *)adapter,
|
|
(read_write == I2C_SMBUS_READ) ?
|
|
NV_I2C_CMD_SMBUS_BLOCK_READ :
|
|
NV_I2C_CMD_SMBUS_BLOCK_WRITE,
|
|
(NvU8)(addr & 0x7f), (NvU8)command,
|
|
sizeof(data->block),
|
|
(NvU8 *)data->block);
|
|
break;
|
|
|
|
case I2C_SMBUS_I2C_BLOCK_DATA:
|
|
rmStatus = rm_i2c_transfer(sp, nv, (void *)adapter,
|
|
(read_write == I2C_SMBUS_READ) ?
|
|
NV_I2C_CMD_BLOCK_READ :
|
|
NV_I2C_CMD_BLOCK_WRITE,
|
|
(NvU8)(addr & 0x7f), (NvU8)command,
|
|
(NvU8)data->block[0],
|
|
(NvU8 *)&data->block[1]);
|
|
break;
|
|
|
|
default:
|
|
rc = -EINVAL;
|
|
rmStatus = NV_ERR_INVALID_ARGUMENT;
|
|
}
|
|
|
|
nv_kmem_cache_free_stack(sp);
|
|
|
|
return (rmStatus != NV_OK) ? rc : 0;
|
|
}
|
|
|
|
static u32 nv_i2c_algo_functionality(struct i2c_adapter *adapter)
|
|
{
|
|
nv_state_t *nv = (nv_state_t *)adapter->algo_data;
|
|
u32 ret = I2C_FUNC_I2C;
|
|
nvidia_stack_t *sp = NULL;
|
|
|
|
if (nv_kmem_cache_alloc_stack(&sp) != 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (rm_i2c_is_smbus_capable(sp, nv, adapter))
|
|
{
|
|
ret |= (I2C_FUNC_SMBUS_QUICK |
|
|
I2C_FUNC_SMBUS_BYTE |
|
|
I2C_FUNC_SMBUS_BYTE_DATA |
|
|
I2C_FUNC_SMBUS_WORD_DATA |
|
|
I2C_FUNC_SMBUS_BLOCK_DATA |
|
|
I2C_FUNC_SMBUS_I2C_BLOCK);
|
|
}
|
|
|
|
nv_kmem_cache_free_stack(sp);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct i2c_algorithm nv_i2c_algo = {
|
|
.master_xfer = nv_i2c_algo_master_xfer,
|
|
.smbus_xfer = nv_i2c_algo_smbus_xfer,
|
|
.functionality = nv_i2c_algo_functionality,
|
|
};
|
|
|
|
struct i2c_adapter nv_i2c_adapter_prototype = {
|
|
.owner = THIS_MODULE,
|
|
.algo = &nv_i2c_algo,
|
|
.algo_data = NULL,
|
|
};
|
|
|
|
void* NV_API_CALL nv_i2c_add_adapter(nv_state_t *nv, NvU32 port)
|
|
{
|
|
NV_STATUS rmStatus;
|
|
nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv);
|
|
struct i2c_adapter *pI2cAdapter = NULL;
|
|
int osstatus = 0;
|
|
|
|
// get a i2c adapter
|
|
rmStatus = os_alloc_mem((void **)&pI2cAdapter,sizeof(struct i2c_adapter));
|
|
|
|
if (rmStatus != NV_OK)
|
|
return NULL;
|
|
|
|
// fill in with default structure
|
|
os_mem_copy(pI2cAdapter, &nv_i2c_adapter_prototype, sizeof(struct i2c_adapter));
|
|
|
|
pI2cAdapter->dev.parent = nvl->dev;
|
|
|
|
if (nvl->pci_dev != NULL)
|
|
{
|
|
snprintf(pI2cAdapter->name, sizeof(pI2cAdapter->name),
|
|
"NVIDIA i2c adapter %u at %x:%02x.%u", port, nv->pci_info.bus,
|
|
nv->pci_info.slot, PCI_FUNC(nvl->pci_dev->devfn));
|
|
}
|
|
else
|
|
{
|
|
snprintf(pI2cAdapter->name, sizeof(pI2cAdapter->name),
|
|
"NVIDIA SOC i2c adapter %u", port);
|
|
}
|
|
|
|
// add our data to the structure
|
|
pI2cAdapter->algo_data = (void *)nv;
|
|
|
|
// attempt to register with the kernel
|
|
osstatus = i2c_add_adapter(pI2cAdapter);
|
|
|
|
if (osstatus)
|
|
{
|
|
// free the memory and NULL the ptr
|
|
os_free_mem(pI2cAdapter);
|
|
|
|
pI2cAdapter = NULL;
|
|
}
|
|
|
|
return ((void *)pI2cAdapter);
|
|
}
|
|
|
|
void NV_API_CALL nv_i2c_del_adapter(nv_state_t *nv, void *data)
|
|
{
|
|
struct i2c_adapter *pI2cAdapter = (struct i2c_adapter *)data;
|
|
|
|
if (pI2cAdapter)
|
|
{
|
|
// release with the OS
|
|
i2c_del_adapter(pI2cAdapter);
|
|
os_free_mem(pI2cAdapter);
|
|
}
|
|
}
|
|
|
|
#else // defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
|
|
|
|
void NV_API_CALL nv_i2c_del_adapter(nv_state_t *nv, void *data)
|
|
{
|
|
}
|
|
|
|
void* NV_API_CALL nv_i2c_add_adapter(nv_state_t *nv, NvU32 port)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
#endif // defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
|