WSL2-Linux-Kernel/drivers/media/pci/ddbridge/ddbridge-i2c.c

174 строки
4.3 KiB
C
Исходник Обычный вид История

/*
* ddbridge.c: Digital Devices PCIe bridge driver
*
* Copyright (C) 2010-2011 Digital Devices GmbH
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 only, as published by the Free Software Foundation.
*
*
* 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.
*
* To obtain the license, point your browser to
* http://www.gnu.org/copyleft/gpl.html
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/poll.h>
#include <linux/io.h>
#include <linux/pci.h>
#include <linux/pci_ids.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <linux/swab.h>
#include <linux/vmalloc.h>
#include "ddbridge.h"
#include "ddbridge-regs.h"
#include "ddbridge-i2c.h"
/******************************************************************************/
static int ddb_i2c_cmd(struct ddb_i2c *i2c, u32 adr, u32 cmd)
{
struct ddb *dev = i2c->dev;
long stat;
u32 val;
i2c->done = 0;
ddbwritel((adr << 9) | cmd, i2c->regs + I2C_COMMAND);
stat = wait_event_timeout(i2c->wq, i2c->done == 1, HZ);
if (stat == 0) {
dev_err(&dev->pdev->dev, "I2C timeout\n");
{ /* MSI debugging*/
u32 istat = ddbreadl(INTERRUPT_STATUS);
dev_err(&dev->pdev->dev, "IRS %08x\n", istat);
ddbwritel(istat, INTERRUPT_ACK);
}
return -EIO;
}
val = ddbreadl(i2c->regs+I2C_COMMAND);
if (val & 0x70000)
return -EIO;
return 0;
}
static int ddb_i2c_master_xfer(struct i2c_adapter *adapter,
struct i2c_msg msg[], int num)
{
struct ddb_i2c *i2c = (struct ddb_i2c *)i2c_get_adapdata(adapter);
struct ddb *dev = i2c->dev;
u8 addr = 0;
if (num)
addr = msg[0].addr;
if (num == 2 && msg[1].flags & I2C_M_RD &&
!(msg[0].flags & I2C_M_RD)) {
memcpy_toio(dev->regs + I2C_TASKMEM_BASE + i2c->wbuf,
msg[0].buf, msg[0].len);
ddbwritel(msg[0].len|(msg[1].len << 16),
i2c->regs+I2C_TASKLENGTH);
if (!ddb_i2c_cmd(i2c, addr, 1)) {
memcpy_fromio(msg[1].buf,
dev->regs + I2C_TASKMEM_BASE + i2c->rbuf,
msg[1].len);
return num;
}
}
if (num == 1 && !(msg[0].flags & I2C_M_RD)) {
ddbcpyto(I2C_TASKMEM_BASE + i2c->wbuf, msg[0].buf, msg[0].len);
ddbwritel(msg[0].len, i2c->regs + I2C_TASKLENGTH);
if (!ddb_i2c_cmd(i2c, addr, 2))
return num;
}
if (num == 1 && (msg[0].flags & I2C_M_RD)) {
ddbwritel(msg[0].len << 16, i2c->regs + I2C_TASKLENGTH);
if (!ddb_i2c_cmd(i2c, addr, 3)) {
ddbcpyfrom(msg[0].buf,
I2C_TASKMEM_BASE + i2c->rbuf, msg[0].len);
return num;
}
}
return -EIO;
}
static u32 ddb_i2c_functionality(struct i2c_adapter *adap)
{
return I2C_FUNC_SMBUS_EMUL;
}
static const struct i2c_algorithm ddb_i2c_algo = {
.master_xfer = ddb_i2c_master_xfer,
.functionality = ddb_i2c_functionality,
};
void ddb_i2c_release(struct ddb *dev)
{
int i;
struct ddb_i2c *i2c;
struct i2c_adapter *adap;
for (i = 0; i < dev->info->port_num; i++) {
i2c = &dev->i2c[i];
adap = &i2c->adap;
i2c_del_adapter(adap);
}
}
int ddb_i2c_init(struct ddb *dev)
{
int i, j, stat = 0;
struct ddb_i2c *i2c;
struct i2c_adapter *adap;
for (i = 0; i < dev->info->port_num; i++) {
i2c = &dev->i2c[i];
i2c->dev = dev;
i2c->nr = i;
i2c->wbuf = i * (I2C_TASKMEM_SIZE / 4);
i2c->rbuf = i2c->wbuf + (I2C_TASKMEM_SIZE / 8);
i2c->regs = 0x80 + i * 0x20;
ddbwritel(I2C_SPEED_100, i2c->regs + I2C_TIMING);
ddbwritel((i2c->rbuf << 16) | i2c->wbuf,
i2c->regs + I2C_TASKADDRESS);
init_waitqueue_head(&i2c->wq);
adap = &i2c->adap;
i2c_set_adapdata(adap, i2c);
#ifdef I2C_ADAP_CLASS_TV_DIGITAL
adap->class = I2C_ADAP_CLASS_TV_DIGITAL|I2C_CLASS_TV_ANALOG;
#else
#ifdef I2C_CLASS_TV_ANALOG
adap->class = I2C_CLASS_TV_ANALOG;
#endif
#endif
strcpy(adap->name, "ddbridge");
adap->algo = &ddb_i2c_algo;
adap->algo_data = (void *)i2c;
adap->dev.parent = &dev->pdev->dev;
stat = i2c_add_adapter(adap);
if (stat)
break;
}
if (stat)
for (j = 0; j < i; j++) {
i2c = &dev->i2c[j];
adap = &i2c->adap;
i2c_del_adapter(adap);
}
return stat;
}