e27f871969
works with new generations of GPUs (IronLake, SandyBridge and supposedly IvyBridge). The driver is not connected to the build yet. Sponsored by: The FreeBSD Foundation MFC after: 1 week
717 lines
18 KiB
C
717 lines
18 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 int intel_iic_quirk_xfer(device_t idev, struct iic_msg *msgs, int nmsgs);
|
|
static void intel_teardown_gmbus_m(struct drm_device *dev, int m);
|
|
|
|
/* 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;
|
|
};
|
|
|
|
static void
|
|
intel_iic_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
|
|
intel_iic_get_reserved(device_t idev)
|
|
{
|
|
struct intel_iic_softc *sc;
|
|
struct drm_device *dev;
|
|
struct drm_i915_private *dev_priv;
|
|
u32 reserved;
|
|
|
|
sc = device_get_softc(idev);
|
|
dev = sc->drm_dev;
|
|
dev_priv = dev->dev_private;
|
|
|
|
if (!IS_I830(dev) && !IS_845G(dev)) {
|
|
reserved = I915_READ_NOTRACE(sc->reg) &
|
|
(GPIO_DATA_PULLUP_DISABLE | GPIO_CLOCK_PULLUP_DISABLE);
|
|
} else {
|
|
reserved = 0;
|
|
}
|
|
|
|
return (reserved);
|
|
}
|
|
|
|
void
|
|
intel_iic_reset(struct drm_device *dev)
|
|
{
|
|
struct drm_i915_private *dev_priv;
|
|
|
|
dev_priv = dev->dev_private;
|
|
if (HAS_PCH_SPLIT(dev))
|
|
I915_WRITE(PCH_GMBUS0, 0);
|
|
else
|
|
I915_WRITE(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_iicbb_setsda(device_t idev, int val)
|
|
{
|
|
struct intel_iic_softc *sc;
|
|
struct drm_i915_private *dev_priv;
|
|
u32 reserved;
|
|
u32 data_bits;
|
|
|
|
sc = device_get_softc(idev);
|
|
dev_priv = sc->drm_dev->dev_private;
|
|
|
|
reserved = intel_iic_get_reserved(idev);
|
|
if (val)
|
|
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 void
|
|
intel_iicbb_setscl(device_t idev, int val)
|
|
{
|
|
struct intel_iic_softc *sc;
|
|
struct drm_i915_private *dev_priv;
|
|
u32 clock_bits, reserved;
|
|
|
|
sc = device_get_softc(idev);
|
|
dev_priv = sc->drm_dev->dev_private;
|
|
|
|
reserved = intel_iic_get_reserved(idev);
|
|
if (val)
|
|
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 int
|
|
intel_iicbb_getsda(device_t idev)
|
|
{
|
|
struct intel_iic_softc *sc;
|
|
struct drm_i915_private *dev_priv;
|
|
u32 reserved;
|
|
|
|
sc = device_get_softc(idev);
|
|
dev_priv = sc->drm_dev->dev_private;
|
|
|
|
reserved = intel_iic_get_reserved(idev);
|
|
|
|
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 int
|
|
intel_iicbb_getscl(device_t idev)
|
|
{
|
|
struct intel_iic_softc *sc;
|
|
struct drm_i915_private *dev_priv;
|
|
u32 reserved;
|
|
|
|
sc = device_get_softc(idev);
|
|
dev_priv = sc->drm_dev->dev_private;
|
|
|
|
reserved = intel_iic_get_reserved(idev);
|
|
|
|
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
|
|
intel_gmbus_transfer(device_t idev, struct iic_msg *msgs, uint32_t nmsgs)
|
|
{
|
|
struct intel_iic_softc *sc;
|
|
struct drm_i915_private *dev_priv;
|
|
u8 *buf;
|
|
int error, i, reg_offset, unit;
|
|
u32 val, loop;
|
|
u16 len;
|
|
|
|
sc = device_get_softc(idev);
|
|
dev_priv = sc->drm_dev->dev_private;
|
|
unit = device_get_unit(idev);
|
|
|
|
sx_xlock(&dev_priv->gmbus_sx);
|
|
if (sc->force_bit_dev) {
|
|
error = intel_iic_quirk_xfer(dev_priv->bbbus[unit], msgs, nmsgs);
|
|
goto out;
|
|
}
|
|
|
|
reg_offset = HAS_PCH_SPLIT(dev_priv->dev) ? PCH_GMBUS0 - GMBUS0 : 0;
|
|
|
|
I915_WRITE(GMBUS0 + reg_offset, sc->reg0);
|
|
|
|
for (i = 0; i < nmsgs; i++) {
|
|
len = msgs[i].len;
|
|
buf = msgs[i].buf;
|
|
|
|
if ((msgs[i].flags & IIC_M_RD) != 0) {
|
|
I915_WRITE(GMBUS1 + reg_offset, GMBUS_CYCLE_WAIT |
|
|
(i + 1 == nmsgs ? GMBUS_CYCLE_STOP : 0) |
|
|
(len << GMBUS_BYTE_COUNT_SHIFT) |
|
|
(msgs[i].slave << GMBUS_SLAVE_ADDR_SHIFT) |
|
|
GMBUS_SLAVE_READ | GMBUS_SW_RDY);
|
|
POSTING_READ(GMBUS2 + reg_offset);
|
|
do {
|
|
loop = 0;
|
|
|
|
if (_intel_wait_for(sc->drm_dev,
|
|
(I915_READ(GMBUS2 + reg_offset) &
|
|
(GMBUS_SATOER | GMBUS_HW_RDY)) != 0,
|
|
50, 1, "915gbr"))
|
|
goto timeout;
|
|
if ((I915_READ(GMBUS2 + reg_offset) &
|
|
GMBUS_SATOER) != 0)
|
|
goto clear_err;
|
|
|
|
val = I915_READ(GMBUS3 + reg_offset);
|
|
do {
|
|
*buf++ = val & 0xff;
|
|
val >>= 8;
|
|
} while (--len != 0 && ++loop < 4);
|
|
} while (len != 0);
|
|
} else {
|
|
val = loop = 0;
|
|
do {
|
|
val |= *buf++ << (8 * loop);
|
|
} while (--len != 0 && ++loop < 4);
|
|
|
|
I915_WRITE(GMBUS3 + reg_offset, val);
|
|
I915_WRITE(GMBUS1 + reg_offset, GMBUS_CYCLE_WAIT |
|
|
(i + 1 == nmsgs ? GMBUS_CYCLE_STOP : 0) |
|
|
(msgs[i].len << GMBUS_BYTE_COUNT_SHIFT) |
|
|
(msgs[i].slave << GMBUS_SLAVE_ADDR_SHIFT) |
|
|
GMBUS_SLAVE_WRITE | GMBUS_SW_RDY);
|
|
POSTING_READ(GMBUS2+reg_offset);
|
|
|
|
while (len != 0) {
|
|
if (_intel_wait_for(sc->drm_dev,
|
|
(I915_READ(GMBUS2 + reg_offset) &
|
|
(GMBUS_SATOER | GMBUS_HW_RDY)) != 0,
|
|
50, 1, "915gbw"))
|
|
goto timeout;
|
|
if (I915_READ(GMBUS2 + reg_offset) & GMBUS_SATOER)
|
|
goto clear_err;
|
|
|
|
val = loop = 0;
|
|
do {
|
|
val |= *buf++ << (8 * loop);
|
|
} while (--len != 0 && ++loop < 4);
|
|
|
|
I915_WRITE(GMBUS3 + reg_offset, val);
|
|
POSTING_READ(GMBUS2 + reg_offset);
|
|
}
|
|
}
|
|
|
|
if (i + 1 < nmsgs && _intel_wait_for(sc->drm_dev,
|
|
(I915_READ(GMBUS2 + reg_offset) & (GMBUS_SATOER |
|
|
GMBUS_HW_WAIT_PHASE)) != 0,
|
|
50, 1, "915gbh"))
|
|
goto timeout;
|
|
if ((I915_READ(GMBUS2 + reg_offset) & GMBUS_SATOER) != 0)
|
|
goto clear_err;
|
|
}
|
|
|
|
error = 0;
|
|
done:
|
|
/* 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_INFO("GMBUS timed out waiting for idle\n");
|
|
I915_WRITE(GMBUS0 + reg_offset, 0);
|
|
out:
|
|
sx_xunlock(&dev_priv->gmbus_sx);
|
|
return (error);
|
|
|
|
clear_err:
|
|
/* 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);
|
|
error = EIO;
|
|
goto done;
|
|
|
|
timeout:
|
|
DRM_INFO("GMBUS timed out, falling back to bit banging on pin %d [%s]\n",
|
|
sc->reg0 & 0xff, sc->name);
|
|
I915_WRITE(GMBUS0 + reg_offset, 0);
|
|
|
|
/*
|
|
* Hardware may not support GMBUS over these pins?
|
|
* Try GPIO bitbanging instead.
|
|
*/
|
|
sc->force_bit_dev = true;
|
|
|
|
error = intel_iic_quirk_xfer(dev_priv->bbbus[unit], msgs, nmsgs);
|
|
goto out;
|
|
}
|
|
|
|
void
|
|
intel_gmbus_set_speed(device_t idev, int speed)
|
|
{
|
|
struct intel_iic_softc *sc;
|
|
|
|
sc = device_get_softc(device_get_parent(idev));
|
|
|
|
sc->reg0 = (sc->reg0 & ~(0x3 << 8)) | speed;
|
|
}
|
|
|
|
void
|
|
intel_gmbus_force_bit(device_t idev, bool force_bit)
|
|
{
|
|
struct intel_iic_softc *sc;
|
|
|
|
sc = device_get_softc(device_get_parent(idev));
|
|
sc->force_bit_dev = force_bit;
|
|
}
|
|
|
|
static int
|
|
intel_iic_quirk_xfer(device_t idev, struct iic_msg *msgs, int nmsgs)
|
|
{
|
|
device_t bridge_dev;
|
|
struct intel_iic_softc *sc;
|
|
struct drm_i915_private *dev_priv;
|
|
int ret;
|
|
int i;
|
|
|
|
bridge_dev = device_get_parent(device_get_parent(idev));
|
|
sc = device_get_softc(bridge_dev);
|
|
dev_priv = sc->drm_dev->dev_private;
|
|
|
|
intel_iic_reset(sc->drm_dev);
|
|
intel_iic_quirk_set(dev_priv, true);
|
|
IICBB_SETSDA(bridge_dev, 1);
|
|
IICBB_SETSCL(bridge_dev, 1);
|
|
DELAY(I2C_RISEFALL_TIME);
|
|
|
|
/* convert slave addresses to format expected by iicbb */
|
|
for (i = 0; i < nmsgs; i++) {
|
|
msgs[i].slave <<= 1;
|
|
/* force use of repeated start instead of default stop+start */
|
|
if (i != (nmsgs - 1))
|
|
msgs[i].flags |= IIC_M_NOSTOP;
|
|
}
|
|
ret = iicbus_transfer(idev, msgs, nmsgs);
|
|
/* restore the addresses */
|
|
for (i = 0; i < nmsgs; i++)
|
|
msgs[i].slave >>= 1;
|
|
IICBB_SETSDA(bridge_dev, 1);
|
|
IICBB_SETSCL(bridge_dev, 1);
|
|
intel_iic_quirk_set(dev_priv, false);
|
|
|
|
return (ret);
|
|
}
|
|
|
|
static const char *gpio_names[GMBUS_NUM_PORTS] = {
|
|
"disabled",
|
|
"ssc",
|
|
"vga",
|
|
"panel",
|
|
"dpc",
|
|
"dpb",
|
|
"reserved",
|
|
"dpd",
|
|
};
|
|
|
|
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;
|
|
|
|
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);
|
|
|
|
snprintf(sc->name, sizeof(sc->name), "gmbus bus %s", gpio_names[pin]);
|
|
device_set_desc(idev, sc->name);
|
|
|
|
/* By default use a conservative clock rate */
|
|
sc->reg0 = pin | GMBUS_RATE_100KHZ;
|
|
|
|
/* XXX force bit banging until GMBUS is fully debugged */
|
|
if (IS_GEN2(sc->drm_dev)) {
|
|
sc->force_bit_dev = true;
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
static const int map_pin_to_reg[] = {
|
|
0,
|
|
GPIOB,
|
|
GPIOA,
|
|
GPIOC,
|
|
GPIOD,
|
|
GPIOE,
|
|
0,
|
|
GPIOF
|
|
};
|
|
|
|
struct intel_iic_softc *sc;
|
|
struct drm_i915_private *dev_priv;
|
|
int pin;
|
|
|
|
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);
|
|
|
|
snprintf(sc->name, sizeof(sc->name), "i915 iicbb %s", gpio_names[pin]);
|
|
device_set_desc(idev, sc->name);
|
|
|
|
sc->reg0 = pin | GMBUS_RATE_100KHZ;
|
|
sc->reg = map_pin_to_reg[pin];
|
|
if (HAS_PCH_SPLIT(dev_priv->dev))
|
|
sc->reg += PCH_GPIOA - GPIOA;
|
|
|
|
/* 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);
|
|
|
|
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, intel_gmbus_transfer),
|
|
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, intel_iicbb_setsda),
|
|
DEVMETHOD(iicbb_setscl, intel_iicbb_setscl),
|
|
DEVMETHOD(iicbb_getsda, intel_iicbb_getsda),
|
|
DEVMETHOD(iicbb_getscl, intel_iicbb_getscl),
|
|
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;
|
|
device_t iic_dev;
|
|
int i, ret;
|
|
|
|
dev_priv = dev->dev_private;
|
|
sx_init(&dev_priv->gmbus_sx, "gmbus");
|
|
dev_priv->gmbus_bridge = malloc(sizeof(device_t) * GMBUS_NUM_PORTS,
|
|
DRM_MEM_DRIVER, M_WAITOK | M_ZERO);
|
|
dev_priv->bbbus_bridge = malloc(sizeof(device_t) * GMBUS_NUM_PORTS,
|
|
DRM_MEM_DRIVER, M_WAITOK | M_ZERO);
|
|
dev_priv->gmbus = malloc(sizeof(device_t) * GMBUS_NUM_PORTS,
|
|
DRM_MEM_DRIVER, M_WAITOK | M_ZERO);
|
|
dev_priv->bbbus = malloc(sizeof(device_t) * GMBUS_NUM_PORTS,
|
|
DRM_MEM_DRIVER, M_WAITOK | M_ZERO);
|
|
|
|
/*
|
|
* 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->device,
|
|
"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->device,
|
|
"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);
|
|
}
|
|
|
|
static void
|
|
intel_teardown_gmbus_m(struct drm_device *dev, int m)
|
|
{
|
|
struct drm_i915_private *dev_priv;
|
|
|
|
dev_priv = dev->dev_private;
|
|
|
|
free(dev_priv->gmbus, DRM_MEM_DRIVER);
|
|
dev_priv->gmbus = NULL;
|
|
free(dev_priv->bbbus, DRM_MEM_DRIVER);
|
|
dev_priv->bbbus = NULL;
|
|
free(dev_priv->gmbus_bridge, DRM_MEM_DRIVER);
|
|
dev_priv->gmbus_bridge = NULL;
|
|
free(dev_priv->bbbus_bridge, DRM_MEM_DRIVER);
|
|
dev_priv->bbbus_bridge = NULL;
|
|
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);
|
|
}
|