8fbeca388a
There is no functional change. The goal is to ease the future update to Linux 3.8's i915 driver. MFC after: 2 months
769 lines
20 KiB
C
769 lines
20 KiB
C
/*
|
|
* Copyright (c) 2006 Dave Airlie <airlied@linux.ie>
|
|
* Copyright © 2006-2008,2010 Intel Corporation
|
|
* Jesse Barnes <jesse.barnes@intel.com>
|
|
*
|
|
* 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 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.
|
|
*
|
|
* Authors:
|
|
* Eric Anholt <eric@anholt.net>
|
|
* Chris Wilson <chris@chris-wilson.co.uk>
|
|
*
|
|
* Copyright (c) 2011 The FreeBSD Foundation
|
|
* All rights reserved.
|
|
*
|
|
* This software was developed by Konstantin Belousov under sponsorship from
|
|
* the FreeBSD Foundation.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <dev/drm2/drmP.h>
|
|
#include <dev/drm2/drm.h>
|
|
#include <dev/drm2/i915/i915_drm.h>
|
|
#include <dev/drm2/i915/i915_drv.h>
|
|
#include <dev/drm2/i915/intel_drv.h>
|
|
#include <dev/iicbus/iic.h>
|
|
#include <dev/iicbus/iiconf.h>
|
|
#include <dev/iicbus/iicbus.h>
|
|
#include "iicbus_if.h"
|
|
#include "iicbb_if.h"
|
|
|
|
static void intel_teardown_gmbus_m(struct drm_device *dev, int m);
|
|
|
|
struct gmbus_port {
|
|
const char *name;
|
|
int reg;
|
|
};
|
|
|
|
static const struct gmbus_port gmbus_ports[] = {
|
|
{ "ssc", GPIOB },
|
|
{ "vga", GPIOA },
|
|
{ "panel", GPIOC },
|
|
{ "dpc", GPIOD },
|
|
{ "dpb", GPIOE },
|
|
{ "dpd", GPIOF },
|
|
};
|
|
|
|
/* Intel GPIO access functions */
|
|
|
|
#define I2C_RISEFALL_TIME 10
|
|
|
|
struct intel_iic_softc {
|
|
struct drm_device *drm_dev;
|
|
device_t iic_dev;
|
|
bool force_bit_dev;
|
|
char name[32];
|
|
uint32_t reg;
|
|
uint32_t reg0;
|
|
};
|
|
|
|
void
|
|
intel_iic_reset(struct drm_device *dev)
|
|
{
|
|
struct drm_i915_private *dev_priv = dev->dev_private;
|
|
I915_WRITE(dev_priv->gpio_mmio_base + GMBUS0, 0);
|
|
}
|
|
|
|
static int
|
|
intel_iicbus_reset(device_t idev, u_char speed, u_char addr, u_char *oldaddr)
|
|
{
|
|
struct intel_iic_softc *sc;
|
|
struct drm_device *dev;
|
|
|
|
sc = device_get_softc(idev);
|
|
dev = sc->drm_dev;
|
|
|
|
intel_iic_reset(dev);
|
|
return (0);
|
|
}
|
|
|
|
static void intel_i2c_quirk_set(struct drm_i915_private *dev_priv, bool enable)
|
|
{
|
|
u32 val;
|
|
|
|
/* When using bit bashing for I2C, this bit needs to be set to 1 */
|
|
if (!IS_PINEVIEW(dev_priv->dev))
|
|
return;
|
|
|
|
val = I915_READ(DSPCLK_GATE_D);
|
|
if (enable)
|
|
val |= DPCUNIT_CLOCK_GATE_DISABLE;
|
|
else
|
|
val &= ~DPCUNIT_CLOCK_GATE_DISABLE;
|
|
I915_WRITE(DSPCLK_GATE_D, val);
|
|
}
|
|
|
|
static u32 get_reserved(device_t idev)
|
|
{
|
|
struct intel_iic_softc *sc = device_get_softc(idev);
|
|
struct drm_device *dev = sc->drm_dev;
|
|
struct drm_i915_private *dev_priv = dev->dev_private;
|
|
u32 reserved = 0;
|
|
|
|
if (!IS_I830(dev) && !IS_845G(dev))
|
|
reserved = I915_READ_NOTRACE(sc->reg) &
|
|
(GPIO_DATA_PULLUP_DISABLE |
|
|
GPIO_CLOCK_PULLUP_DISABLE);
|
|
|
|
return reserved;
|
|
}
|
|
|
|
static int get_clock(device_t adapter)
|
|
{
|
|
struct intel_iic_softc *sc = device_get_softc(adapter);
|
|
struct drm_i915_private *dev_priv = sc->drm_dev->dev_private;
|
|
u32 reserved = get_reserved(adapter);
|
|
I915_WRITE_NOTRACE(sc->reg, reserved | GPIO_CLOCK_DIR_MASK);
|
|
I915_WRITE_NOTRACE(sc->reg, reserved);
|
|
return ((I915_READ_NOTRACE(sc->reg) & GPIO_CLOCK_VAL_IN) != 0);
|
|
}
|
|
|
|
static int get_data(device_t adapter)
|
|
{
|
|
struct intel_iic_softc *sc = device_get_softc(adapter);
|
|
struct drm_i915_private *dev_priv = sc->drm_dev->dev_private;
|
|
u32 reserved = get_reserved(adapter);
|
|
I915_WRITE_NOTRACE(sc->reg, reserved | GPIO_DATA_DIR_MASK);
|
|
I915_WRITE_NOTRACE(sc->reg, reserved);
|
|
return ((I915_READ_NOTRACE(sc->reg) & GPIO_DATA_VAL_IN) != 0);
|
|
}
|
|
|
|
static void set_clock(device_t adapter, int state_high)
|
|
{
|
|
struct intel_iic_softc *sc = device_get_softc(adapter);
|
|
struct drm_i915_private *dev_priv = sc->drm_dev->dev_private;
|
|
u32 reserved = get_reserved(adapter);
|
|
u32 clock_bits;
|
|
|
|
if (state_high)
|
|
clock_bits = GPIO_CLOCK_DIR_IN | GPIO_CLOCK_DIR_MASK;
|
|
else
|
|
clock_bits = GPIO_CLOCK_DIR_OUT | GPIO_CLOCK_DIR_MASK |
|
|
GPIO_CLOCK_VAL_MASK;
|
|
|
|
I915_WRITE_NOTRACE(sc->reg, reserved | clock_bits);
|
|
POSTING_READ(sc->reg);
|
|
}
|
|
|
|
static void set_data(device_t adapter, int state_high)
|
|
{
|
|
struct intel_iic_softc *sc = device_get_softc(adapter);
|
|
struct drm_i915_private *dev_priv = sc->drm_dev->dev_private;
|
|
u32 reserved = get_reserved(adapter);
|
|
u32 data_bits;
|
|
|
|
if (state_high)
|
|
data_bits = GPIO_DATA_DIR_IN | GPIO_DATA_DIR_MASK;
|
|
else
|
|
data_bits = GPIO_DATA_DIR_OUT | GPIO_DATA_DIR_MASK |
|
|
GPIO_DATA_VAL_MASK;
|
|
|
|
I915_WRITE_NOTRACE(sc->reg, reserved | data_bits);
|
|
POSTING_READ(sc->reg);
|
|
}
|
|
|
|
static int
|
|
intel_gpio_pre_xfer(device_t adapter)
|
|
{
|
|
struct intel_iic_softc *sc = device_get_softc(adapter);
|
|
struct drm_i915_private *dev_priv = sc->drm_dev->dev_private;
|
|
|
|
intel_iic_reset(sc->drm_dev);
|
|
intel_i2c_quirk_set(dev_priv, true);
|
|
IICBB_SETSDA(adapter, 1);
|
|
IICBB_SETSCL(adapter, 1);
|
|
DELAY(I2C_RISEFALL_TIME);
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
intel_gpio_post_xfer(device_t adapter)
|
|
{
|
|
struct intel_iic_softc *sc = device_get_softc(adapter);
|
|
struct drm_i915_private *dev_priv = sc->drm_dev->dev_private;
|
|
|
|
IICBB_SETSDA(adapter, 1);
|
|
IICBB_SETSCL(adapter, 1);
|
|
intel_i2c_quirk_set(dev_priv, false);
|
|
}
|
|
|
|
static int
|
|
gmbus_xfer_read(struct drm_i915_private *dev_priv, struct iic_msg *msg,
|
|
u32 gmbus1_index)
|
|
{
|
|
int reg_offset = dev_priv->gpio_mmio_base;
|
|
u16 len = msg->len;
|
|
u8 *buf = msg->buf;
|
|
|
|
I915_WRITE(GMBUS1 + reg_offset,
|
|
gmbus1_index |
|
|
GMBUS_CYCLE_WAIT |
|
|
(len << GMBUS_BYTE_COUNT_SHIFT) |
|
|
(msg->slave << (GMBUS_SLAVE_ADDR_SHIFT - 1)) |
|
|
GMBUS_SLAVE_READ | GMBUS_SW_RDY);
|
|
while (len) {
|
|
int ret;
|
|
u32 val, loop = 0;
|
|
u32 gmbus2;
|
|
|
|
ret = _intel_wait_for(sc->drm_dev,
|
|
((gmbus2 = I915_READ(GMBUS2 + reg_offset)) &
|
|
(GMBUS_SATOER | GMBUS_HW_RDY)),
|
|
50, 1, "915gbr");
|
|
if (ret)
|
|
return -ETIMEDOUT;
|
|
if (gmbus2 & GMBUS_SATOER)
|
|
return -ENXIO;
|
|
|
|
val = I915_READ(GMBUS3 + reg_offset);
|
|
do {
|
|
*buf++ = val & 0xff;
|
|
val >>= 8;
|
|
} while (--len && ++loop < 4);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
gmbus_xfer_write(struct drm_i915_private *dev_priv, struct iic_msg *msg)
|
|
{
|
|
int reg_offset = dev_priv->gpio_mmio_base;
|
|
u16 len = msg->len;
|
|
u8 *buf = msg->buf;
|
|
u32 val, loop;
|
|
|
|
val = loop = 0;
|
|
while (len && loop < 4) {
|
|
val |= *buf++ << (8 * loop++);
|
|
len -= 1;
|
|
}
|
|
|
|
I915_WRITE(GMBUS3 + reg_offset, val);
|
|
I915_WRITE(GMBUS1 + reg_offset,
|
|
GMBUS_CYCLE_WAIT |
|
|
(msg->len << GMBUS_BYTE_COUNT_SHIFT) |
|
|
(msg->slave << (GMBUS_SLAVE_ADDR_SHIFT - 1)) |
|
|
GMBUS_SLAVE_WRITE | GMBUS_SW_RDY);
|
|
while (len) {
|
|
int ret;
|
|
u32 gmbus2;
|
|
|
|
val = loop = 0;
|
|
do {
|
|
val |= *buf++ << (8 * loop);
|
|
} while (--len && ++loop < 4);
|
|
|
|
I915_WRITE(GMBUS3 + reg_offset, val);
|
|
|
|
ret = _intel_wait_for(sc->drm_dev,
|
|
((gmbus2 = I915_READ(GMBUS2 + reg_offset)) &
|
|
(GMBUS_SATOER | GMBUS_HW_RDY)),
|
|
50, 1, "915gbw");
|
|
if (ret)
|
|
return -ETIMEDOUT;
|
|
if (gmbus2 & GMBUS_SATOER)
|
|
return -ENXIO;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* The gmbus controller can combine a 1 or 2 byte write with a read that
|
|
* immediately follows it by using an "INDEX" cycle.
|
|
*/
|
|
static bool
|
|
gmbus_is_index_read(struct iic_msg *msgs, int i, int num)
|
|
{
|
|
return (i + 1 < num &&
|
|
!(msgs[i].flags & IIC_M_RD) && msgs[i].len <= 2 &&
|
|
(msgs[i + 1].flags & IIC_M_RD));
|
|
}
|
|
|
|
static int
|
|
gmbus_xfer_index_read(struct drm_i915_private *dev_priv, struct iic_msg *msgs)
|
|
{
|
|
int reg_offset = dev_priv->gpio_mmio_base;
|
|
u32 gmbus1_index = 0;
|
|
u32 gmbus5 = 0;
|
|
int ret;
|
|
|
|
if (msgs[0].len == 2)
|
|
gmbus5 = GMBUS_2BYTE_INDEX_EN |
|
|
msgs[0].buf[1] | (msgs[0].buf[0] << 8);
|
|
if (msgs[0].len == 1)
|
|
gmbus1_index = GMBUS_CYCLE_INDEX |
|
|
(msgs[0].buf[0] << GMBUS_SLAVE_INDEX_SHIFT);
|
|
|
|
/* GMBUS5 holds 16-bit index */
|
|
if (gmbus5)
|
|
I915_WRITE(GMBUS5 + reg_offset, gmbus5);
|
|
|
|
ret = gmbus_xfer_read(dev_priv, &msgs[1], gmbus1_index);
|
|
|
|
/* Clear GMBUS5 after each index transfer */
|
|
if (gmbus5)
|
|
I915_WRITE(GMBUS5 + reg_offset, 0);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
gmbus_xfer(device_t adapter,
|
|
struct iic_msg *msgs,
|
|
uint32_t num)
|
|
{
|
|
struct intel_iic_softc *sc = device_get_softc(adapter);
|
|
struct drm_i915_private *dev_priv = sc->drm_dev->dev_private;
|
|
int error, i, ret, reg_offset, unit;
|
|
|
|
error = 0;
|
|
unit = device_get_unit(adapter);
|
|
|
|
sx_xlock(&dev_priv->gmbus_sx);
|
|
if (sc->force_bit_dev) {
|
|
error = -IICBUS_TRANSFER(dev_priv->bbbus[unit], msgs, num);
|
|
goto out;
|
|
}
|
|
|
|
reg_offset = dev_priv->gpio_mmio_base;
|
|
|
|
I915_WRITE(GMBUS0 + reg_offset, sc->reg0);
|
|
|
|
for (i = 0; i < num; i++) {
|
|
u32 gmbus2;
|
|
|
|
if (gmbus_is_index_read(msgs, i, num)) {
|
|
error = gmbus_xfer_index_read(dev_priv, &msgs[i]);
|
|
i += 1; /* set i to the index of the read xfer */
|
|
} else if (msgs[i].flags & IIC_M_RD) {
|
|
error = gmbus_xfer_read(dev_priv, &msgs[i], 0);
|
|
} else {
|
|
error = gmbus_xfer_write(dev_priv, &msgs[i]);
|
|
}
|
|
|
|
if (error == -ETIMEDOUT)
|
|
goto timeout;
|
|
if (error == -ENXIO)
|
|
goto clear_err;
|
|
|
|
ret = _intel_wait_for(sc->drm_dev,
|
|
((gmbus2 = I915_READ(GMBUS2 + reg_offset)) &
|
|
(GMBUS_SATOER | GMBUS_HW_WAIT_PHASE)),
|
|
50, 1, "915gbh");
|
|
if (ret)
|
|
goto timeout;
|
|
if (gmbus2 & GMBUS_SATOER)
|
|
goto clear_err;
|
|
}
|
|
|
|
/* Generate a STOP condition on the bus. Note that gmbus can't generata
|
|
* a STOP on the very first cycle. To simplify the code we
|
|
* unconditionally generate the STOP condition with an additional gmbus
|
|
* cycle. */
|
|
I915_WRITE(GMBUS1 + reg_offset, GMBUS_CYCLE_STOP | GMBUS_SW_RDY);
|
|
|
|
/* Mark the GMBUS interface as disabled after waiting for idle.
|
|
* We will re-enable it at the start of the next xfer,
|
|
* till then let it sleep.
|
|
*/
|
|
if (_intel_wait_for(dev,
|
|
(I915_READ(GMBUS2 + reg_offset) & GMBUS_ACTIVE) == 0,
|
|
10, 1, "915gbu")) {
|
|
DRM_DEBUG_KMS("GMBUS [%s] timed out waiting for idle\n",
|
|
sc->name);
|
|
error = -ETIMEDOUT;
|
|
}
|
|
I915_WRITE(GMBUS0 + reg_offset, 0);
|
|
goto out;
|
|
|
|
clear_err:
|
|
/*
|
|
* Wait for bus to IDLE before clearing NAK.
|
|
* If we clear the NAK while bus is still active, then it will stay
|
|
* active and the next transaction may fail.
|
|
*/
|
|
if (_intel_wait_for(dev,
|
|
(I915_READ(GMBUS2 + reg_offset) & GMBUS_ACTIVE) == 0,
|
|
10, 1, "915gbu"))
|
|
DRM_DEBUG_KMS("GMBUS [%s] timed out after NAK\n", sc->name);
|
|
|
|
/* Toggle the Software Clear Interrupt bit. This has the effect
|
|
* of resetting the GMBUS controller and so clearing the
|
|
* BUS_ERROR raised by the slave's NAK.
|
|
*/
|
|
I915_WRITE(GMBUS1 + reg_offset, GMBUS_SW_CLR_INT);
|
|
I915_WRITE(GMBUS1 + reg_offset, 0);
|
|
I915_WRITE(GMBUS0 + reg_offset, 0);
|
|
|
|
DRM_DEBUG_KMS("GMBUS [%s] NAK for addr: %04x %c(%d)\n",
|
|
sc->name, msgs[i].slave,
|
|
(msgs[i].flags & IIC_M_RD) ? 'r' : 'w', msgs[i].len);
|
|
|
|
/*
|
|
* If no ACK is received during the address phase of a transaction,
|
|
* the adapter must report -ENXIO.
|
|
* It is not clear what to return if no ACK is received at other times.
|
|
* So, we always return -ENXIO in all NAK cases, to ensure we send
|
|
* it at least during the one case that is specified.
|
|
*/
|
|
error = -ENXIO;
|
|
goto out;
|
|
|
|
timeout:
|
|
DRM_INFO("GMBUS [%s] timed out, falling back to bit banging on pin %d\n",
|
|
sc->name, sc->reg0 & 0xff);
|
|
I915_WRITE(GMBUS0 + reg_offset, 0);
|
|
|
|
/* Hardware may not support GMBUS over these pins? Try GPIO bitbanging instead. */
|
|
sc->force_bit_dev = true;
|
|
error = -IICBUS_TRANSFER(adapter, msgs, num);
|
|
|
|
out:
|
|
sx_xunlock(&dev_priv->gmbus_sx);
|
|
return -error;
|
|
}
|
|
|
|
static int
|
|
intel_gmbus_probe(device_t dev)
|
|
{
|
|
|
|
return (BUS_PROBE_SPECIFIC);
|
|
}
|
|
|
|
static int
|
|
intel_gmbus_attach(device_t idev)
|
|
{
|
|
struct drm_i915_private *dev_priv;
|
|
struct intel_iic_softc *sc;
|
|
int pin, port;
|
|
|
|
sc = device_get_softc(idev);
|
|
sc->drm_dev = device_get_softc(device_get_parent(idev));
|
|
dev_priv = sc->drm_dev->dev_private;
|
|
pin = device_get_unit(idev);
|
|
port = pin + 1;
|
|
|
|
snprintf(sc->name, sizeof(sc->name), "gmbus %s",
|
|
intel_gmbus_is_port_valid(port) ? gmbus_ports[pin].name :
|
|
"reserved");
|
|
device_set_desc(idev, sc->name);
|
|
|
|
/* By default use a conservative clock rate */
|
|
sc->reg0 = port | GMBUS_RATE_100KHZ;
|
|
|
|
/* gmbus seems to be broken on i830 */
|
|
if (IS_I830(sc->drm_dev))
|
|
sc->force_bit_dev = true;
|
|
#if 0
|
|
if (IS_GEN2(sc->drm_dev)) {
|
|
sc->force_bit_dev = true;
|
|
}
|
|
#endif
|
|
|
|
/* add bus interface device */
|
|
sc->iic_dev = device_add_child(idev, "iicbus", -1);
|
|
if (sc->iic_dev == NULL)
|
|
return (ENXIO);
|
|
device_quiet(sc->iic_dev);
|
|
bus_generic_attach(idev);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
intel_gmbus_detach(device_t idev)
|
|
{
|
|
struct intel_iic_softc *sc;
|
|
struct drm_i915_private *dev_priv;
|
|
device_t child;
|
|
int u;
|
|
|
|
sc = device_get_softc(idev);
|
|
u = device_get_unit(idev);
|
|
dev_priv = sc->drm_dev->dev_private;
|
|
|
|
child = sc->iic_dev;
|
|
bus_generic_detach(idev);
|
|
if (child != NULL)
|
|
device_delete_child(idev, child);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
intel_iicbb_probe(device_t dev)
|
|
{
|
|
|
|
return (BUS_PROBE_DEFAULT);
|
|
}
|
|
|
|
static int
|
|
intel_iicbb_attach(device_t idev)
|
|
{
|
|
struct intel_iic_softc *sc;
|
|
struct drm_i915_private *dev_priv;
|
|
int pin, port;
|
|
|
|
sc = device_get_softc(idev);
|
|
sc->drm_dev = device_get_softc(device_get_parent(idev));
|
|
dev_priv = sc->drm_dev->dev_private;
|
|
pin = device_get_unit(idev);
|
|
port = pin + 1;
|
|
|
|
snprintf(sc->name, sizeof(sc->name), "i915 iicbb %s",
|
|
intel_gmbus_is_port_valid(port) ? gmbus_ports[pin].name :
|
|
"reserved");
|
|
device_set_desc(idev, sc->name);
|
|
|
|
if (!intel_gmbus_is_port_valid(port))
|
|
pin = 1 ; /* GPIOA, VGA */
|
|
sc->reg0 = pin | GMBUS_RATE_100KHZ;
|
|
sc->reg = dev_priv->gpio_mmio_base + gmbus_ports[pin].reg;
|
|
|
|
/* add generic bit-banging code */
|
|
sc->iic_dev = device_add_child(idev, "iicbb", -1);
|
|
if (sc->iic_dev == NULL)
|
|
return (ENXIO);
|
|
device_quiet(sc->iic_dev);
|
|
bus_generic_attach(idev);
|
|
iicbus_set_nostop(idev, true);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
intel_iicbb_detach(device_t idev)
|
|
{
|
|
struct intel_iic_softc *sc;
|
|
device_t child;
|
|
|
|
sc = device_get_softc(idev);
|
|
child = sc->iic_dev;
|
|
bus_generic_detach(idev);
|
|
if (child)
|
|
device_delete_child(idev, child);
|
|
return (0);
|
|
}
|
|
|
|
static device_method_t intel_gmbus_methods[] = {
|
|
DEVMETHOD(device_probe, intel_gmbus_probe),
|
|
DEVMETHOD(device_attach, intel_gmbus_attach),
|
|
DEVMETHOD(device_detach, intel_gmbus_detach),
|
|
DEVMETHOD(iicbus_reset, intel_iicbus_reset),
|
|
DEVMETHOD(iicbus_transfer, gmbus_xfer),
|
|
DEVMETHOD_END
|
|
};
|
|
static driver_t intel_gmbus_driver = {
|
|
"intel_gmbus",
|
|
intel_gmbus_methods,
|
|
sizeof(struct intel_iic_softc)
|
|
};
|
|
static devclass_t intel_gmbus_devclass;
|
|
DRIVER_MODULE_ORDERED(intel_gmbus, drmn, intel_gmbus_driver,
|
|
intel_gmbus_devclass, 0, 0, SI_ORDER_FIRST);
|
|
DRIVER_MODULE(iicbus, intel_gmbus, iicbus_driver, iicbus_devclass, 0, 0);
|
|
|
|
static device_method_t intel_iicbb_methods[] = {
|
|
DEVMETHOD(device_probe, intel_iicbb_probe),
|
|
DEVMETHOD(device_attach, intel_iicbb_attach),
|
|
DEVMETHOD(device_detach, intel_iicbb_detach),
|
|
|
|
DEVMETHOD(bus_add_child, bus_generic_add_child),
|
|
DEVMETHOD(bus_print_child, bus_generic_print_child),
|
|
|
|
DEVMETHOD(iicbb_callback, iicbus_null_callback),
|
|
DEVMETHOD(iicbb_reset, intel_iicbus_reset),
|
|
DEVMETHOD(iicbb_setsda, set_data),
|
|
DEVMETHOD(iicbb_setscl, set_clock),
|
|
DEVMETHOD(iicbb_getsda, get_data),
|
|
DEVMETHOD(iicbb_getscl, get_clock),
|
|
DEVMETHOD(iicbb_pre_xfer, intel_gpio_pre_xfer),
|
|
DEVMETHOD(iicbb_post_xfer, intel_gpio_post_xfer),
|
|
DEVMETHOD_END
|
|
};
|
|
static driver_t intel_iicbb_driver = {
|
|
"intel_iicbb",
|
|
intel_iicbb_methods,
|
|
sizeof(struct intel_iic_softc)
|
|
};
|
|
static devclass_t intel_iicbb_devclass;
|
|
DRIVER_MODULE_ORDERED(intel_iicbb, drmn, intel_iicbb_driver,
|
|
intel_iicbb_devclass, 0, 0, SI_ORDER_FIRST);
|
|
DRIVER_MODULE(iicbb, intel_iicbb, iicbb_driver, iicbb_devclass, 0, 0);
|
|
|
|
int intel_setup_gmbus(struct drm_device *dev)
|
|
{
|
|
struct drm_i915_private *dev_priv = dev->dev_private;
|
|
device_t iic_dev;
|
|
int ret, i;
|
|
|
|
if (HAS_PCH_SPLIT(dev))
|
|
dev_priv->gpio_mmio_base = PCH_GPIOA - GPIOA;
|
|
else
|
|
dev_priv->gpio_mmio_base = 0;
|
|
|
|
sx_init(&dev_priv->gmbus_sx, "gmbus");
|
|
|
|
/*
|
|
* The Giant there is recursed, most likely. Normally, the
|
|
* intel_setup_gmbus() is called from the attach method of the
|
|
* driver.
|
|
*/
|
|
mtx_lock(&Giant);
|
|
for (i = 0; i <= GMBUS_NUM_PORTS; i++) {
|
|
/*
|
|
* Initialized bbbus_bridge before gmbus_bridge, since
|
|
* gmbus may decide to force quirk transfer in the
|
|
* attachment code.
|
|
*/
|
|
dev_priv->bbbus_bridge[i] = device_add_child(dev->dev,
|
|
"intel_iicbb", i);
|
|
if (dev_priv->bbbus_bridge[i] == NULL) {
|
|
DRM_ERROR("bbbus bridge %d creation failed\n", i);
|
|
ret = -ENXIO;
|
|
goto err;
|
|
}
|
|
device_quiet(dev_priv->bbbus_bridge[i]);
|
|
ret = -device_probe_and_attach(dev_priv->bbbus_bridge[i]);
|
|
if (ret != 0) {
|
|
DRM_ERROR("bbbus bridge %d attach failed, %d\n", i,
|
|
ret);
|
|
goto err;
|
|
}
|
|
|
|
iic_dev = device_find_child(dev_priv->bbbus_bridge[i], "iicbb",
|
|
-1);
|
|
if (iic_dev == NULL) {
|
|
DRM_ERROR("bbbus bridge doesn't have iicbb child\n");
|
|
goto err;
|
|
}
|
|
iic_dev = device_find_child(iic_dev, "iicbus", -1);
|
|
if (iic_dev == NULL) {
|
|
DRM_ERROR(
|
|
"bbbus bridge doesn't have iicbus grandchild\n");
|
|
goto err;
|
|
}
|
|
|
|
dev_priv->bbbus[i] = iic_dev;
|
|
|
|
dev_priv->gmbus_bridge[i] = device_add_child(dev->dev,
|
|
"intel_gmbus", i);
|
|
if (dev_priv->gmbus_bridge[i] == NULL) {
|
|
DRM_ERROR("gmbus bridge %d creation failed\n", i);
|
|
ret = -ENXIO;
|
|
goto err;
|
|
}
|
|
device_quiet(dev_priv->gmbus_bridge[i]);
|
|
ret = -device_probe_and_attach(dev_priv->gmbus_bridge[i]);
|
|
if (ret != 0) {
|
|
DRM_ERROR("gmbus bridge %d attach failed, %d\n", i,
|
|
ret);
|
|
ret = -ENXIO;
|
|
goto err;
|
|
}
|
|
|
|
iic_dev = device_find_child(dev_priv->gmbus_bridge[i],
|
|
"iicbus", -1);
|
|
if (iic_dev == NULL) {
|
|
DRM_ERROR("gmbus bridge doesn't have iicbus child\n");
|
|
goto err;
|
|
}
|
|
dev_priv->gmbus[i] = iic_dev;
|
|
|
|
intel_iic_reset(dev);
|
|
}
|
|
mtx_unlock(&Giant);
|
|
|
|
return 0;
|
|
|
|
err:
|
|
intel_teardown_gmbus_m(dev, i);
|
|
mtx_unlock(&Giant);
|
|
return ret;
|
|
}
|
|
|
|
device_t intel_gmbus_get_adapter(struct drm_i915_private *dev_priv,
|
|
unsigned port)
|
|
{
|
|
|
|
if (!intel_gmbus_is_port_valid(port))
|
|
DRM_ERROR("GMBUS get adapter %d: invalid port\n", port);
|
|
return (intel_gmbus_is_port_valid(port) ? dev_priv->gmbus[port - 1] :
|
|
NULL);
|
|
}
|
|
|
|
void intel_gmbus_set_speed(device_t adapter, int speed)
|
|
{
|
|
struct intel_iic_softc *sc;
|
|
|
|
sc = device_get_softc(device_get_parent(adapter));
|
|
|
|
sc->reg0 = (sc->reg0 & ~(0x3 << 8)) | speed;
|
|
}
|
|
|
|
void intel_gmbus_force_bit(device_t adapter, bool force_bit)
|
|
{
|
|
struct intel_iic_softc *sc;
|
|
|
|
sc = device_get_softc(device_get_parent(adapter));
|
|
sc->force_bit_dev = force_bit;
|
|
}
|
|
|
|
static void
|
|
intel_teardown_gmbus_m(struct drm_device *dev, int m)
|
|
{
|
|
struct drm_i915_private *dev_priv;
|
|
|
|
dev_priv = dev->dev_private;
|
|
|
|
sx_destroy(&dev_priv->gmbus_sx);
|
|
}
|
|
|
|
void intel_teardown_gmbus(struct drm_device *dev)
|
|
{
|
|
|
|
mtx_lock(&Giant);
|
|
intel_teardown_gmbus_m(dev, GMBUS_NUM_PORTS);
|
|
mtx_unlock(&Giant);
|
|
}
|