401 lines
7.8 KiB
C
401 lines
7.8 KiB
C
/*
|
|
* Copyright (C) 2008 Maarten Maathuis.
|
|
* All Rights Reserved.
|
|
*
|
|
* 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 (including the
|
|
* next paragraph) 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 COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS 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.
|
|
*
|
|
*/
|
|
|
|
/* This is largely a clone from xorg i2c functions, as i had serious trouble getting an i2c_bit_algo adaptor running. */
|
|
|
|
#include "nv50_i2c.h"
|
|
|
|
static uint32_t nv50_i2c_port(int index)
|
|
{
|
|
uint32_t port = 0;
|
|
|
|
switch (index) {
|
|
case 0:
|
|
port = NV50_PCONNECTOR_I2C_PORT_0;
|
|
break;
|
|
case 1:
|
|
port = NV50_PCONNECTOR_I2C_PORT_1;
|
|
break;
|
|
case 2:
|
|
port = NV50_PCONNECTOR_I2C_PORT_2;
|
|
break;
|
|
case 3:
|
|
port = NV50_PCONNECTOR_I2C_PORT_3;
|
|
break;
|
|
case 4:
|
|
port = NV50_PCONNECTOR_I2C_PORT_4;
|
|
break;
|
|
case 5:
|
|
port = NV50_PCONNECTOR_I2C_PORT_5;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (!port) {
|
|
DRM_ERROR("Invalid i2c port, returning 0.\n");
|
|
BUG();
|
|
}
|
|
|
|
return port;
|
|
}
|
|
|
|
static void nv50_i2c_set_bits(struct nv50_i2c_channel *chan, int clock_high, int data_high)
|
|
{
|
|
struct drm_nouveau_private *dev_priv = chan->dev->dev_private;
|
|
uint32_t port = nv50_i2c_port(chan->index);
|
|
|
|
if (!port)
|
|
return;
|
|
|
|
NV_WRITE(port, 4 | (data_high << 1) | clock_high);
|
|
}
|
|
|
|
static void nv50_i2c_get_bits(struct nv50_i2c_channel *chan, int *clock_high, int *data_high)
|
|
{
|
|
struct drm_nouveau_private *dev_priv = chan->dev->dev_private;
|
|
uint32_t port = nv50_i2c_port(chan->index);
|
|
uint32_t val;
|
|
|
|
if (!port)
|
|
return;
|
|
|
|
val = NV_READ(port);
|
|
|
|
if (val & 1)
|
|
*clock_high = 1;
|
|
else
|
|
*clock_high = 0;
|
|
|
|
if (val & 2)
|
|
*data_high = 1;
|
|
else
|
|
*data_high = 0;
|
|
}
|
|
|
|
static bool nv50_i2c_raise_clock(struct nv50_i2c_channel *chan, int data)
|
|
{
|
|
int i, clock;
|
|
|
|
nv50_i2c_set_bits(chan, 1, data);
|
|
udelay(2);
|
|
|
|
for (i = 2200; i > 0; i -= 2) {
|
|
nv50_i2c_get_bits(chan, &clock, &data);
|
|
if (clock)
|
|
return true;
|
|
udelay(2);
|
|
}
|
|
|
|
printk("a timeout occured in nv50_i2c_raise_clock\n");
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool nv50_i2c_start(struct nv50_i2c_channel *chan)
|
|
{
|
|
if (!nv50_i2c_raise_clock(chan, 1))
|
|
return false;
|
|
|
|
nv50_i2c_set_bits(chan, 1, 0);
|
|
udelay(5);
|
|
|
|
nv50_i2c_set_bits(chan, 0, 0);
|
|
udelay(5);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void nv50_i2c_stop(struct nv50_i2c_channel *chan)
|
|
{
|
|
nv50_i2c_set_bits(chan, 0, 0);
|
|
udelay(2);
|
|
|
|
nv50_i2c_set_bits(chan, 1, 0);
|
|
udelay(5);
|
|
|
|
nv50_i2c_set_bits(chan, 1, 1);
|
|
udelay(5);
|
|
}
|
|
|
|
static bool nv50_i2c_write_bit(struct nv50_i2c_channel *chan, int data)
|
|
{
|
|
bool rval;
|
|
|
|
nv50_i2c_set_bits(chan, 0, data);
|
|
udelay(2);
|
|
|
|
rval = nv50_i2c_raise_clock(chan, data);
|
|
udelay(5);
|
|
|
|
nv50_i2c_set_bits(chan, 0, data);
|
|
udelay(5);
|
|
|
|
return rval;
|
|
}
|
|
|
|
static bool nv50_i2c_read_bit(struct nv50_i2c_channel *chan, int *data)
|
|
{
|
|
bool rval;
|
|
int clock;
|
|
|
|
rval = nv50_i2c_raise_clock(chan, 1);
|
|
udelay(5);
|
|
|
|
nv50_i2c_get_bits(chan, &clock, data);
|
|
udelay(5);
|
|
|
|
nv50_i2c_set_bits(chan, 0, 1);
|
|
udelay(5);
|
|
|
|
return rval;
|
|
}
|
|
|
|
static bool nv50_i2c_write_byte(struct nv50_i2c_channel *chan, uint8_t byte)
|
|
{
|
|
bool rval;
|
|
int i, clock, data;
|
|
|
|
for (i = 7; i >= 0; i--)
|
|
if (!nv50_i2c_write_bit(chan, (byte >> i) & 1))
|
|
return false;
|
|
|
|
nv50_i2c_set_bits(chan, 0, 1);
|
|
udelay(5);
|
|
|
|
rval = nv50_i2c_raise_clock(chan, 1);
|
|
|
|
if (rval) {
|
|
for (i = 40; i > 0; i -= 2) {
|
|
udelay(2);
|
|
nv50_i2c_get_bits(chan, &clock, &data);
|
|
if (data == 0)
|
|
break;
|
|
}
|
|
|
|
if (i <= 0) {
|
|
printk("a timeout occured in nv50_i2c_write_byte\n");
|
|
rval = false;
|
|
}
|
|
}
|
|
|
|
nv50_i2c_set_bits(chan, 0, 1);
|
|
udelay(5);
|
|
|
|
return rval;
|
|
}
|
|
|
|
static bool nv50_i2c_read_byte(struct nv50_i2c_channel *chan, uint8_t *byte, bool last)
|
|
{
|
|
int i, bit;
|
|
|
|
nv50_i2c_set_bits(chan, 0, 1);
|
|
udelay(5);
|
|
|
|
*byte = 0;
|
|
|
|
for (i = 7; i >= 0; i--) {
|
|
if (nv50_i2c_read_bit(chan, &bit)) {
|
|
if (bit)
|
|
*byte |= (1 << i);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!nv50_i2c_write_bit(chan, last ? 1 : 0))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/* only 7 bits addresses. */
|
|
static bool nv50_i2c_address(struct nv50_i2c_channel *chan, uint8_t address, bool write)
|
|
{
|
|
if (nv50_i2c_start(chan)) {
|
|
uint8_t real_addr = (address << 1);
|
|
if (!write)
|
|
real_addr |= 1;
|
|
|
|
if (nv50_i2c_write_byte(chan, real_addr))
|
|
return true;
|
|
|
|
/* failure, so issue stop */
|
|
nv50_i2c_stop(chan);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool nv50_i2c_read(struct nv50_i2c_channel *chan, uint8_t address, uint8_t *buffer, uint32_t length)
|
|
{
|
|
int i, j;
|
|
bool rval, last;
|
|
|
|
/* retries */
|
|
for (i = 0; i < 4; i++) {
|
|
rval = nv50_i2c_address(chan, address, false);
|
|
if (!rval)
|
|
return false;
|
|
|
|
for (j = 0; j < length; j++) {
|
|
last = false;
|
|
if (j == (length - 1))
|
|
last = true;
|
|
rval = nv50_i2c_read_byte(chan, &buffer[j], last);
|
|
if (!rval) {
|
|
nv50_i2c_stop(chan);
|
|
break;
|
|
}
|
|
}
|
|
|
|
nv50_i2c_stop(chan);
|
|
|
|
/* done */
|
|
if (rval)
|
|
break;
|
|
}
|
|
|
|
if (!rval)
|
|
printk("nv50_i2c_read failed\n");
|
|
|
|
return rval;
|
|
}
|
|
|
|
static bool nv50_i2c_write(struct nv50_i2c_channel *chan, uint8_t address, uint8_t *buffer, uint32_t length)
|
|
{
|
|
int i, j;
|
|
bool rval;
|
|
|
|
/* retries */
|
|
for (i = 0; i < 4; i++) {
|
|
rval = nv50_i2c_address(chan, address, true);
|
|
if (!rval)
|
|
return false;
|
|
|
|
for (j = 0; j < length; j++) {
|
|
rval = nv50_i2c_write_byte(chan, buffer[j]);
|
|
if (!rval) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
nv50_i2c_stop(chan);
|
|
|
|
/* done */
|
|
if (rval)
|
|
break;
|
|
}
|
|
|
|
if (!rval)
|
|
printk("nv50_i2c_write failed\n");
|
|
|
|
return rval;
|
|
}
|
|
|
|
static int nv50_i2c_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg *msgs, int num)
|
|
{
|
|
struct nv50_i2c_channel *chan = i2c_get_adapdata(i2c_adap);
|
|
bool rval;
|
|
int i;
|
|
|
|
for (i = 0; i < num; i++) {
|
|
if (msgs[i].flags & I2C_M_RD) { /* read */
|
|
rval = nv50_i2c_read(chan, msgs[i].addr, msgs[i].buf, msgs[i].len);
|
|
} else { /* write */
|
|
rval = nv50_i2c_write(chan, msgs[i].addr, msgs[i].buf, msgs[i].len);
|
|
}
|
|
|
|
if (!rval)
|
|
break;
|
|
}
|
|
|
|
if (rval)
|
|
return i;
|
|
else
|
|
return -EINVAL;
|
|
}
|
|
|
|
static u32 nv50_i2c_functionality(struct i2c_adapter *adap)
|
|
{
|
|
return I2C_FUNC_I2C;
|
|
}
|
|
|
|
static const struct i2c_algorithm nv50_i2c_algo = {
|
|
.master_xfer = nv50_i2c_xfer,
|
|
.functionality = nv50_i2c_functionality,
|
|
};
|
|
|
|
static int nv50_i2c_register_bus(struct i2c_adapter *adap)
|
|
{
|
|
adap->algo = &nv50_i2c_algo;
|
|
|
|
adap->timeout = 40;
|
|
adap->retries = 4;
|
|
|
|
return i2c_add_adapter(adap);
|
|
}
|
|
|
|
#define I2C_HW_B_NOUVEAU 0x010030
|
|
struct nv50_i2c_channel *nv50_i2c_channel_create(struct drm_device *dev, uint32_t index)
|
|
{
|
|
struct nv50_i2c_channel *chan;
|
|
|
|
chan = kzalloc(sizeof(struct nv50_i2c_channel), GFP_KERNEL);
|
|
|
|
if (!chan)
|
|
goto out;
|
|
|
|
DRM_INFO("Creating i2c bus with index %d\n", index);
|
|
|
|
chan->dev = dev;
|
|
chan->index = index;
|
|
snprintf(chan->adapter.name, I2C_NAME_SIZE, "nv50 i2c %d", index);
|
|
chan->adapter.owner = THIS_MODULE;
|
|
chan->adapter.id = I2C_HW_B_NOUVEAU;
|
|
chan->adapter.dev.parent = &dev->pdev->dev;
|
|
|
|
i2c_set_adapdata(&chan->adapter, chan);
|
|
|
|
if (nv50_i2c_register_bus(&chan->adapter))
|
|
goto out;
|
|
|
|
return chan;
|
|
|
|
out:
|
|
kfree(chan);
|
|
return NULL;
|
|
}
|
|
|
|
void nv50_i2c_channel_destroy(struct nv50_i2c_channel *chan)
|
|
{
|
|
if (!chan)
|
|
return;
|
|
|
|
i2c_del_adapter(&chan->adapter);
|
|
kfree(chan);
|
|
}
|