Staging / IIO driver fixes for 4.6-rc3
Here are some IIO driver fixes, along with two staging driver fixes for 4.6-rc3. One staging driver patch reverts the deletion of a driver that happened in 4.6-rc1. We thought that laptop.org was dead, but it's still alive and kicking, and has users that were mad we broke their hardware by deleting a driver for their machines. So that driver is added back and everyone is happy again. All of these have been in linux-next for a while with no reported issues. Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> -----BEGIN PGP SIGNATURE----- Version: GnuPG v2 iEYEABECAAYFAlcJNwYACgkQMUfUDdst+ynczgCgnrRAIAi5n9h0Y39CzY8OLH0Y 2qMAnj5gSZGdP6iS6nPsJTX7cENNch3V =Z855 -----END PGP SIGNATURE----- Merge tag 'staging-4.6-rc3' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging Pull staging and IIO driver fixes from Greg KH: "Here are some IIO driver fixes, along with two staging driver fixes for 4.6-rc3. One staging driver patch reverts the deletion of a driver that happened in 4.6-rc1. We thought that laptop.org was dead, but it's still alive and kicking, and has users that were mad we broke their hardware by deleting a driver for their machines. So that driver is added back and everyone is happy again. All of these have been in linux-next for a while with no reported issues" * tag 'staging-4.6-rc3' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging: Revert "Staging: olpc_dcon: Remove obsolete driver" staging/rdma/hfi1: select CRC32 iio: gyro: bmg160: fix buffer read values iio: gyro: bmg160: fix endianness when reading axes iio: accel: bmc150: fix endianness when reading axes iio: st_magn: always define ST_MAGN_TRIGGER_SET_STATE iio: fix config watermark initial value iio: health: max30100: correct FIFO check condition iio: imu: Fix inv_mpu6050 dependencies iio: adc: Fix build error of missing devm_ioremap_resource on UM iio: light: apds9960: correct FIFO check condition iio: adc: max1363: correct reference voltage iio: adc: max1363: add missing adc to max1363_id
This commit is contained in:
commit
c6e6e58cc8
|
@ -10595,6 +10595,14 @@ L: linux-tegra@vger.kernel.org
|
|||
S: Maintained
|
||||
F: drivers/staging/nvec/
|
||||
|
||||
STAGING - OLPC SECONDARY DISPLAY CONTROLLER (DCON)
|
||||
M: Jens Frederich <jfrederich@gmail.com>
|
||||
M: Daniel Drake <dsd@laptop.org>
|
||||
M: Jon Nettleton <jon.nettleton@gmail.com>
|
||||
W: http://wiki.laptop.org/go/DCON
|
||||
S: Maintained
|
||||
F: drivers/staging/olpc_dcon/
|
||||
|
||||
STAGING - REALTEK RTL8712U DRIVERS
|
||||
M: Larry Finger <Larry.Finger@lwfinger.net>
|
||||
M: Florian Schilhabel <florian.c.schilhabel@googlemail.com>.
|
||||
|
|
|
@ -547,7 +547,7 @@ static int bmc150_accel_get_axis(struct bmc150_accel_data *data,
|
|||
{
|
||||
int ret;
|
||||
int axis = chan->scan_index;
|
||||
unsigned int raw_val;
|
||||
__le16 raw_val;
|
||||
|
||||
mutex_lock(&data->mutex);
|
||||
ret = bmc150_accel_set_power_state(data, true);
|
||||
|
@ -557,14 +557,14 @@ static int bmc150_accel_get_axis(struct bmc150_accel_data *data,
|
|||
}
|
||||
|
||||
ret = regmap_bulk_read(data->regmap, BMC150_ACCEL_AXIS_TO_REG(axis),
|
||||
&raw_val, 2);
|
||||
&raw_val, sizeof(raw_val));
|
||||
if (ret < 0) {
|
||||
dev_err(data->dev, "Error reading axis %d\n", axis);
|
||||
bmc150_accel_set_power_state(data, false);
|
||||
mutex_unlock(&data->mutex);
|
||||
return ret;
|
||||
}
|
||||
*val = sign_extend32(raw_val >> chan->scan_type.shift,
|
||||
*val = sign_extend32(le16_to_cpu(raw_val) >> chan->scan_type.shift,
|
||||
chan->scan_type.realbits - 1);
|
||||
ret = bmc150_accel_set_power_state(data, false);
|
||||
mutex_unlock(&data->mutex);
|
||||
|
@ -988,6 +988,7 @@ static const struct iio_event_spec bmc150_accel_event = {
|
|||
.realbits = (bits), \
|
||||
.storagebits = 16, \
|
||||
.shift = 16 - (bits), \
|
||||
.endianness = IIO_LE, \
|
||||
}, \
|
||||
.event_spec = &bmc150_accel_event, \
|
||||
.num_event_specs = 1 \
|
||||
|
|
|
@ -134,6 +134,7 @@ config AT91_ADC
|
|||
config AT91_SAMA5D2_ADC
|
||||
tristate "Atmel AT91 SAMA5D2 ADC"
|
||||
depends on ARCH_AT91 || COMPILE_TEST
|
||||
depends on HAS_IOMEM
|
||||
help
|
||||
Say yes here to build support for Atmel SAMA5D2 ADC which is
|
||||
available on SAMA5D2 SoC family.
|
||||
|
|
|
@ -1386,7 +1386,7 @@ static const struct max1363_chip_info max1363_chip_info_tbl[] = {
|
|||
},
|
||||
[max11644] = {
|
||||
.bits = 12,
|
||||
.int_vref_mv = 2048,
|
||||
.int_vref_mv = 4096,
|
||||
.mode_list = max11644_mode_list,
|
||||
.num_modes = ARRAY_SIZE(max11644_mode_list),
|
||||
.default_mode = s0to1,
|
||||
|
@ -1396,7 +1396,7 @@ static const struct max1363_chip_info max1363_chip_info_tbl[] = {
|
|||
},
|
||||
[max11645] = {
|
||||
.bits = 12,
|
||||
.int_vref_mv = 4096,
|
||||
.int_vref_mv = 2048,
|
||||
.mode_list = max11644_mode_list,
|
||||
.num_modes = ARRAY_SIZE(max11644_mode_list),
|
||||
.default_mode = s0to1,
|
||||
|
@ -1406,7 +1406,7 @@ static const struct max1363_chip_info max1363_chip_info_tbl[] = {
|
|||
},
|
||||
[max11646] = {
|
||||
.bits = 10,
|
||||
.int_vref_mv = 2048,
|
||||
.int_vref_mv = 4096,
|
||||
.mode_list = max11644_mode_list,
|
||||
.num_modes = ARRAY_SIZE(max11644_mode_list),
|
||||
.default_mode = s0to1,
|
||||
|
@ -1416,7 +1416,7 @@ static const struct max1363_chip_info max1363_chip_info_tbl[] = {
|
|||
},
|
||||
[max11647] = {
|
||||
.bits = 10,
|
||||
.int_vref_mv = 4096,
|
||||
.int_vref_mv = 2048,
|
||||
.mode_list = max11644_mode_list,
|
||||
.num_modes = ARRAY_SIZE(max11644_mode_list),
|
||||
.default_mode = s0to1,
|
||||
|
@ -1680,6 +1680,10 @@ static const struct i2c_device_id max1363_id[] = {
|
|||
{ "max11615", max11615 },
|
||||
{ "max11616", max11616 },
|
||||
{ "max11617", max11617 },
|
||||
{ "max11644", max11644 },
|
||||
{ "max11645", max11645 },
|
||||
{ "max11646", max11646 },
|
||||
{ "max11647", max11647 },
|
||||
{}
|
||||
};
|
||||
|
||||
|
|
|
@ -452,7 +452,7 @@ static int bmg160_get_temp(struct bmg160_data *data, int *val)
|
|||
static int bmg160_get_axis(struct bmg160_data *data, int axis, int *val)
|
||||
{
|
||||
int ret;
|
||||
unsigned int raw_val;
|
||||
__le16 raw_val;
|
||||
|
||||
mutex_lock(&data->mutex);
|
||||
ret = bmg160_set_power_state(data, true);
|
||||
|
@ -462,7 +462,7 @@ static int bmg160_get_axis(struct bmg160_data *data, int axis, int *val)
|
|||
}
|
||||
|
||||
ret = regmap_bulk_read(data->regmap, BMG160_AXIS_TO_REG(axis), &raw_val,
|
||||
2);
|
||||
sizeof(raw_val));
|
||||
if (ret < 0) {
|
||||
dev_err(data->dev, "Error reading axis %d\n", axis);
|
||||
bmg160_set_power_state(data, false);
|
||||
|
@ -470,7 +470,7 @@ static int bmg160_get_axis(struct bmg160_data *data, int axis, int *val)
|
|||
return ret;
|
||||
}
|
||||
|
||||
*val = sign_extend32(raw_val, 15);
|
||||
*val = sign_extend32(le16_to_cpu(raw_val), 15);
|
||||
ret = bmg160_set_power_state(data, false);
|
||||
mutex_unlock(&data->mutex);
|
||||
if (ret < 0)
|
||||
|
@ -733,6 +733,7 @@ static const struct iio_event_spec bmg160_event = {
|
|||
.sign = 's', \
|
||||
.realbits = 16, \
|
||||
.storagebits = 16, \
|
||||
.endianness = IIO_LE, \
|
||||
}, \
|
||||
.event_spec = &bmg160_event, \
|
||||
.num_event_specs = 1 \
|
||||
|
@ -780,7 +781,7 @@ static irqreturn_t bmg160_trigger_handler(int irq, void *p)
|
|||
mutex_unlock(&data->mutex);
|
||||
goto err;
|
||||
}
|
||||
data->buffer[i++] = ret;
|
||||
data->buffer[i++] = val;
|
||||
}
|
||||
mutex_unlock(&data->mutex);
|
||||
|
||||
|
|
|
@ -238,12 +238,13 @@ static irqreturn_t max30100_interrupt_handler(int irq, void *private)
|
|||
|
||||
mutex_lock(&data->lock);
|
||||
|
||||
while (cnt-- || (cnt = max30100_fifo_count(data) > 0)) {
|
||||
while (cnt || (cnt = max30100_fifo_count(data) > 0)) {
|
||||
ret = max30100_read_measurement(data);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
iio_push_to_buffers(data->indio_dev, data->buffer);
|
||||
cnt--;
|
||||
}
|
||||
|
||||
mutex_unlock(&data->lock);
|
||||
|
|
|
@ -9,9 +9,8 @@ config INV_MPU6050_IIO
|
|||
|
||||
config INV_MPU6050_I2C
|
||||
tristate "Invensense MPU6050 devices (I2C)"
|
||||
depends on I2C
|
||||
depends on I2C_MUX
|
||||
select INV_MPU6050_IIO
|
||||
select I2C_MUX
|
||||
select REGMAP_I2C
|
||||
help
|
||||
This driver supports the Invensense MPU6050 devices.
|
||||
|
|
|
@ -653,6 +653,7 @@ static int iio_verify_update(struct iio_dev *indio_dev,
|
|||
unsigned int modes;
|
||||
|
||||
memset(config, 0, sizeof(*config));
|
||||
config->watermark = ~0;
|
||||
|
||||
/*
|
||||
* If there is just one buffer and we are removing it there is nothing
|
||||
|
|
|
@ -769,7 +769,7 @@ static void apds9960_read_gesture_fifo(struct apds9960_data *data)
|
|||
mutex_lock(&data->lock);
|
||||
data->gesture_mode_running = 1;
|
||||
|
||||
while (cnt-- || (cnt = apds9660_fifo_is_empty(data) > 0)) {
|
||||
while (cnt || (cnt = apds9660_fifo_is_empty(data) > 0)) {
|
||||
ret = regmap_bulk_read(data->regmap, APDS9960_REG_GFIFO_BASE,
|
||||
&data->buffer, 4);
|
||||
|
||||
|
@ -777,6 +777,7 @@ static void apds9960_read_gesture_fifo(struct apds9960_data *data)
|
|||
goto err_read;
|
||||
|
||||
iio_push_to_buffers(data->indio_dev, data->buffer);
|
||||
cnt--;
|
||||
}
|
||||
|
||||
err_read:
|
||||
|
|
|
@ -44,6 +44,7 @@ static inline int st_magn_allocate_ring(struct iio_dev *indio_dev)
|
|||
static inline void st_magn_deallocate_ring(struct iio_dev *indio_dev)
|
||||
{
|
||||
}
|
||||
#define ST_MAGN_TRIGGER_SET_STATE NULL
|
||||
#endif /* CONFIG_IIO_BUFFER */
|
||||
|
||||
#endif /* ST_MAGN_H */
|
||||
|
|
|
@ -30,6 +30,8 @@ source "drivers/staging/wlan-ng/Kconfig"
|
|||
|
||||
source "drivers/staging/comedi/Kconfig"
|
||||
|
||||
source "drivers/staging/olpc_dcon/Kconfig"
|
||||
|
||||
source "drivers/staging/rtl8192u/Kconfig"
|
||||
|
||||
source "drivers/staging/rtl8192e/Kconfig"
|
||||
|
|
|
@ -4,6 +4,7 @@ obj-y += media/
|
|||
obj-$(CONFIG_SLICOSS) += slicoss/
|
||||
obj-$(CONFIG_PRISM2_USB) += wlan-ng/
|
||||
obj-$(CONFIG_COMEDI) += comedi/
|
||||
obj-$(CONFIG_FB_OLPC_DCON) += olpc_dcon/
|
||||
obj-$(CONFIG_RTL8192U) += rtl8192u/
|
||||
obj-$(CONFIG_RTL8192E) += rtl8192e/
|
||||
obj-$(CONFIG_R8712U) += rtl8712/
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
config FB_OLPC_DCON
|
||||
tristate "One Laptop Per Child Display CONtroller support"
|
||||
depends on OLPC && FB
|
||||
depends on I2C
|
||||
depends on (GPIO_CS5535 || GPIO_CS5535=n)
|
||||
select BACKLIGHT_CLASS_DEVICE
|
||||
---help---
|
||||
In order to support very low power operation, the XO laptop uses a
|
||||
secondary Display CONtroller, or DCON. This secondary controller
|
||||
is present in the video pipeline between the primary display
|
||||
controller (integrate into the processor or chipset) and the LCD
|
||||
panel. It allows the main processor/display controller to be
|
||||
completely powered off while still retaining an image on the display.
|
||||
This controller is only available on OLPC platforms. Unless you have
|
||||
one of these platforms, you will want to say 'N'.
|
||||
|
||||
config FB_OLPC_DCON_1
|
||||
bool "OLPC XO-1 DCON support"
|
||||
depends on FB_OLPC_DCON && GPIO_CS5535
|
||||
default y
|
||||
---help---
|
||||
Enable support for the DCON in XO-1 model laptops. The kernel
|
||||
communicates with the DCON using model-specific code. If you
|
||||
have an XO-1 (or if you're unsure what model you have), you should
|
||||
say 'Y'.
|
||||
|
||||
config FB_OLPC_DCON_1_5
|
||||
bool "OLPC XO-1.5 DCON support"
|
||||
depends on FB_OLPC_DCON && ACPI
|
||||
default y
|
||||
---help---
|
||||
Enable support for the DCON in XO-1.5 model laptops. The kernel
|
||||
communicates with the DCON using model-specific code. If you
|
||||
have an XO-1.5 (or if you're unsure what model you have), you
|
||||
should say 'Y'.
|
|
@ -0,0 +1,6 @@
|
|||
olpc-dcon-objs += olpc_dcon.o
|
||||
olpc-dcon-$(CONFIG_FB_OLPC_DCON_1) += olpc_dcon_xo_1.o
|
||||
olpc-dcon-$(CONFIG_FB_OLPC_DCON_1_5) += olpc_dcon_xo_1_5.o
|
||||
obj-$(CONFIG_FB_OLPC_DCON) += olpc-dcon.o
|
||||
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
TODO:
|
||||
- see if vx855 gpio API can be made similar enough to cs5535 so we can
|
||||
share more code
|
||||
- allow simultaneous XO-1 and XO-1.5 support
|
||||
|
||||
Please send patches to Greg Kroah-Hartman <greg@kroah.com> and
|
||||
copy:
|
||||
Daniel Drake <dsd@laptop.org>
|
||||
Jens Frederich <jfrederich@gmail.com>
|
|
@ -0,0 +1,813 @@
|
|||
/*
|
||||
* Mainly by David Woodhouse, somewhat modified by Jordan Crouse
|
||||
*
|
||||
* Copyright © 2006-2007 Red Hat, Inc.
|
||||
* Copyright © 2006-2007 Advanced Micro Devices, Inc.
|
||||
* Copyright © 2009 VIA Technology, Inc.
|
||||
* Copyright (c) 2010-2011 Andres Salomon <dilinger@queued.net>
|
||||
*
|
||||
* This program is free software. You can redistribute it and/or
|
||||
* modify it under the terms of version 2 of the GNU General Public
|
||||
* License as published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/fb.h>
|
||||
#include <linux/console.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/backlight.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/reboot.h>
|
||||
#include <linux/olpc-ec.h>
|
||||
#include <asm/tsc.h>
|
||||
#include <asm/olpc.h>
|
||||
|
||||
#include "olpc_dcon.h"
|
||||
|
||||
/* Module definitions */
|
||||
|
||||
static ushort resumeline = 898;
|
||||
module_param(resumeline, ushort, 0444);
|
||||
|
||||
static struct dcon_platform_data *pdata;
|
||||
|
||||
/* I2C structures */
|
||||
|
||||
/* Platform devices */
|
||||
static struct platform_device *dcon_device;
|
||||
|
||||
static unsigned short normal_i2c[] = { 0x0d, I2C_CLIENT_END };
|
||||
|
||||
static s32 dcon_write(struct dcon_priv *dcon, u8 reg, u16 val)
|
||||
{
|
||||
return i2c_smbus_write_word_data(dcon->client, reg, val);
|
||||
}
|
||||
|
||||
static s32 dcon_read(struct dcon_priv *dcon, u8 reg)
|
||||
{
|
||||
return i2c_smbus_read_word_data(dcon->client, reg);
|
||||
}
|
||||
|
||||
/* ===== API functions - these are called by a variety of users ==== */
|
||||
|
||||
static int dcon_hw_init(struct dcon_priv *dcon, int is_init)
|
||||
{
|
||||
u16 ver;
|
||||
int rc = 0;
|
||||
|
||||
ver = dcon_read(dcon, DCON_REG_ID);
|
||||
if ((ver >> 8) != 0xDC) {
|
||||
pr_err("DCON ID not 0xDCxx: 0x%04x instead.\n", ver);
|
||||
rc = -ENXIO;
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (is_init) {
|
||||
pr_info("Discovered DCON version %x\n", ver & 0xFF);
|
||||
rc = pdata->init(dcon);
|
||||
if (rc != 0) {
|
||||
pr_err("Unable to init.\n");
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
if (ver < 0xdc02) {
|
||||
dev_err(&dcon->client->dev,
|
||||
"DCON v1 is unsupported, giving up..\n");
|
||||
rc = -ENODEV;
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* SDRAM setup/hold time */
|
||||
dcon_write(dcon, 0x3a, 0xc040);
|
||||
dcon_write(dcon, DCON_REG_MEM_OPT_A, 0x0000); /* clear option bits */
|
||||
dcon_write(dcon, DCON_REG_MEM_OPT_A,
|
||||
MEM_DLL_CLOCK_DELAY | MEM_POWER_DOWN);
|
||||
dcon_write(dcon, DCON_REG_MEM_OPT_B, MEM_SOFT_RESET);
|
||||
|
||||
/* Colour swizzle, AA, no passthrough, backlight */
|
||||
if (is_init) {
|
||||
dcon->disp_mode = MODE_PASSTHRU | MODE_BL_ENABLE |
|
||||
MODE_CSWIZZLE | MODE_COL_AA;
|
||||
}
|
||||
dcon_write(dcon, DCON_REG_MODE, dcon->disp_mode);
|
||||
|
||||
/* Set the scanline to interrupt on during resume */
|
||||
dcon_write(dcon, DCON_REG_SCAN_INT, resumeline);
|
||||
|
||||
err:
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* The smbus doesn't always come back due to what is believed to be
|
||||
* hardware (power rail) bugs. For older models where this is known to
|
||||
* occur, our solution is to attempt to wait for the bus to stabilize;
|
||||
* if it doesn't happen, cut power to the dcon, repower it, and wait
|
||||
* for the bus to stabilize. Rinse, repeat until we have a working
|
||||
* smbus. For newer models, we simply BUG(); we want to know if this
|
||||
* still happens despite the power fixes that have been made!
|
||||
*/
|
||||
static int dcon_bus_stabilize(struct dcon_priv *dcon, int is_powered_down)
|
||||
{
|
||||
unsigned long timeout;
|
||||
u8 pm;
|
||||
int x;
|
||||
|
||||
power_up:
|
||||
if (is_powered_down) {
|
||||
pm = 1;
|
||||
x = olpc_ec_cmd(EC_DCON_POWER_MODE, &pm, 1, NULL, 0);
|
||||
if (x) {
|
||||
pr_warn("unable to force dcon to power up: %d!\n", x);
|
||||
return x;
|
||||
}
|
||||
usleep_range(10000, 11000); /* we'll be conservative */
|
||||
}
|
||||
|
||||
pdata->bus_stabilize_wiggle();
|
||||
|
||||
for (x = -1, timeout = 50; timeout && x < 0; timeout--) {
|
||||
usleep_range(1000, 1100);
|
||||
x = dcon_read(dcon, DCON_REG_ID);
|
||||
}
|
||||
if (x < 0) {
|
||||
pr_err("unable to stabilize dcon's smbus, reasserting power and praying.\n");
|
||||
BUG_ON(olpc_board_at_least(olpc_board(0xc2)));
|
||||
pm = 0;
|
||||
olpc_ec_cmd(EC_DCON_POWER_MODE, &pm, 1, NULL, 0);
|
||||
msleep(100);
|
||||
is_powered_down = 1;
|
||||
goto power_up; /* argh, stupid hardware.. */
|
||||
}
|
||||
|
||||
if (is_powered_down)
|
||||
return dcon_hw_init(dcon, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dcon_set_backlight(struct dcon_priv *dcon, u8 level)
|
||||
{
|
||||
dcon->bl_val = level;
|
||||
dcon_write(dcon, DCON_REG_BRIGHT, dcon->bl_val);
|
||||
|
||||
/* Purposely turn off the backlight when we go to level 0 */
|
||||
if (dcon->bl_val == 0) {
|
||||
dcon->disp_mode &= ~MODE_BL_ENABLE;
|
||||
dcon_write(dcon, DCON_REG_MODE, dcon->disp_mode);
|
||||
} else if (!(dcon->disp_mode & MODE_BL_ENABLE)) {
|
||||
dcon->disp_mode |= MODE_BL_ENABLE;
|
||||
dcon_write(dcon, DCON_REG_MODE, dcon->disp_mode);
|
||||
}
|
||||
}
|
||||
|
||||
/* Set the output type to either color or mono */
|
||||
static int dcon_set_mono_mode(struct dcon_priv *dcon, bool enable_mono)
|
||||
{
|
||||
if (dcon->mono == enable_mono)
|
||||
return 0;
|
||||
|
||||
dcon->mono = enable_mono;
|
||||
|
||||
if (enable_mono) {
|
||||
dcon->disp_mode &= ~(MODE_CSWIZZLE | MODE_COL_AA);
|
||||
dcon->disp_mode |= MODE_MONO_LUMA;
|
||||
} else {
|
||||
dcon->disp_mode &= ~(MODE_MONO_LUMA);
|
||||
dcon->disp_mode |= MODE_CSWIZZLE | MODE_COL_AA;
|
||||
}
|
||||
|
||||
dcon_write(dcon, DCON_REG_MODE, dcon->disp_mode);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* For now, this will be really stupid - we need to address how
|
||||
* DCONLOAD works in a sleep and account for it accordingly
|
||||
*/
|
||||
|
||||
static void dcon_sleep(struct dcon_priv *dcon, bool sleep)
|
||||
{
|
||||
int x;
|
||||
|
||||
/* Turn off the backlight and put the DCON to sleep */
|
||||
|
||||
if (dcon->asleep == sleep)
|
||||
return;
|
||||
|
||||
if (!olpc_board_at_least(olpc_board(0xc2)))
|
||||
return;
|
||||
|
||||
if (sleep) {
|
||||
u8 pm = 0;
|
||||
|
||||
x = olpc_ec_cmd(EC_DCON_POWER_MODE, &pm, 1, NULL, 0);
|
||||
if (x)
|
||||
pr_warn("unable to force dcon to power down: %d!\n", x);
|
||||
else
|
||||
dcon->asleep = sleep;
|
||||
} else {
|
||||
/* Only re-enable the backlight if the backlight value is set */
|
||||
if (dcon->bl_val != 0)
|
||||
dcon->disp_mode |= MODE_BL_ENABLE;
|
||||
x = dcon_bus_stabilize(dcon, 1);
|
||||
if (x)
|
||||
pr_warn("unable to reinit dcon hardware: %d!\n", x);
|
||||
else
|
||||
dcon->asleep = sleep;
|
||||
|
||||
/* Restore backlight */
|
||||
dcon_set_backlight(dcon, dcon->bl_val);
|
||||
}
|
||||
|
||||
/* We should turn off some stuff in the framebuffer - but what? */
|
||||
}
|
||||
|
||||
/* the DCON seems to get confused if we change DCONLOAD too
|
||||
* frequently -- i.e., approximately faster than frame time.
|
||||
* normally we don't change it this fast, so in general we won't
|
||||
* delay here.
|
||||
*/
|
||||
static void dcon_load_holdoff(struct dcon_priv *dcon)
|
||||
{
|
||||
ktime_t delta_t, now;
|
||||
|
||||
while (1) {
|
||||
now = ktime_get();
|
||||
delta_t = ktime_sub(now, dcon->load_time);
|
||||
if (ktime_to_ns(delta_t) > NSEC_PER_MSEC * 20)
|
||||
break;
|
||||
mdelay(4);
|
||||
}
|
||||
}
|
||||
|
||||
static bool dcon_blank_fb(struct dcon_priv *dcon, bool blank)
|
||||
{
|
||||
int err;
|
||||
|
||||
console_lock();
|
||||
if (!lock_fb_info(dcon->fbinfo)) {
|
||||
console_unlock();
|
||||
dev_err(&dcon->client->dev, "unable to lock framebuffer\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
dcon->ignore_fb_events = true;
|
||||
err = fb_blank(dcon->fbinfo,
|
||||
blank ? FB_BLANK_POWERDOWN : FB_BLANK_UNBLANK);
|
||||
dcon->ignore_fb_events = false;
|
||||
unlock_fb_info(dcon->fbinfo);
|
||||
console_unlock();
|
||||
|
||||
if (err) {
|
||||
dev_err(&dcon->client->dev, "couldn't %sblank framebuffer\n",
|
||||
blank ? "" : "un");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Set the source of the display (CPU or DCON) */
|
||||
static void dcon_source_switch(struct work_struct *work)
|
||||
{
|
||||
struct dcon_priv *dcon = container_of(work, struct dcon_priv,
|
||||
switch_source);
|
||||
int source = dcon->pending_src;
|
||||
|
||||
if (dcon->curr_src == source)
|
||||
return;
|
||||
|
||||
dcon_load_holdoff(dcon);
|
||||
|
||||
dcon->switched = false;
|
||||
|
||||
switch (source) {
|
||||
case DCON_SOURCE_CPU:
|
||||
pr_info("dcon_source_switch to CPU\n");
|
||||
/* Enable the scanline interrupt bit */
|
||||
if (dcon_write(dcon, DCON_REG_MODE,
|
||||
dcon->disp_mode | MODE_SCAN_INT))
|
||||
pr_err("couldn't enable scanline interrupt!\n");
|
||||
else
|
||||
/* Wait up to one second for the scanline interrupt */
|
||||
wait_event_timeout(dcon->waitq, dcon->switched, HZ);
|
||||
|
||||
if (!dcon->switched)
|
||||
pr_err("Timeout entering CPU mode; expect a screen glitch.\n");
|
||||
|
||||
/* Turn off the scanline interrupt */
|
||||
if (dcon_write(dcon, DCON_REG_MODE, dcon->disp_mode))
|
||||
pr_err("couldn't disable scanline interrupt!\n");
|
||||
|
||||
/*
|
||||
* Ideally we'd like to disable interrupts here so that the
|
||||
* fb unblanking and DCON turn on happen at a known time value;
|
||||
* however, we can't do that right now with fb_blank
|
||||
* messing with semaphores.
|
||||
*
|
||||
* For now, we just hope..
|
||||
*/
|
||||
if (!dcon_blank_fb(dcon, false)) {
|
||||
pr_err("Failed to enter CPU mode\n");
|
||||
dcon->pending_src = DCON_SOURCE_DCON;
|
||||
return;
|
||||
}
|
||||
|
||||
/* And turn off the DCON */
|
||||
pdata->set_dconload(1);
|
||||
dcon->load_time = ktime_get();
|
||||
|
||||
pr_info("The CPU has control\n");
|
||||
break;
|
||||
case DCON_SOURCE_DCON:
|
||||
{
|
||||
ktime_t delta_t;
|
||||
|
||||
pr_info("dcon_source_switch to DCON\n");
|
||||
|
||||
/* Clear DCONLOAD - this implies that the DCON is in control */
|
||||
pdata->set_dconload(0);
|
||||
dcon->load_time = ktime_get();
|
||||
|
||||
wait_event_timeout(dcon->waitq, dcon->switched, HZ/2);
|
||||
|
||||
if (!dcon->switched) {
|
||||
pr_err("Timeout entering DCON mode; expect a screen glitch.\n");
|
||||
} else {
|
||||
/* sometimes the DCON doesn't follow its own rules,
|
||||
* and doesn't wait for two vsync pulses before
|
||||
* ack'ing the frame load with an IRQ. the result
|
||||
* is that the display shows the *previously*
|
||||
* loaded frame. we can detect this by looking at
|
||||
* the time between asserting DCONLOAD and the IRQ --
|
||||
* if it's less than 20msec, then the DCON couldn't
|
||||
* have seen two VSYNC pulses. in that case we
|
||||
* deassert and reassert, and hope for the best.
|
||||
* see http://dev.laptop.org/ticket/9664
|
||||
*/
|
||||
delta_t = ktime_sub(dcon->irq_time, dcon->load_time);
|
||||
if (dcon->switched && ktime_to_ns(delta_t)
|
||||
< NSEC_PER_MSEC * 20) {
|
||||
pr_err("missed loading, retrying\n");
|
||||
pdata->set_dconload(1);
|
||||
mdelay(41);
|
||||
pdata->set_dconload(0);
|
||||
dcon->load_time = ktime_get();
|
||||
mdelay(41);
|
||||
}
|
||||
}
|
||||
|
||||
dcon_blank_fb(dcon, true);
|
||||
pr_info("The DCON has control\n");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
BUG();
|
||||
}
|
||||
|
||||
dcon->curr_src = source;
|
||||
}
|
||||
|
||||
static void dcon_set_source(struct dcon_priv *dcon, int arg)
|
||||
{
|
||||
if (dcon->pending_src == arg)
|
||||
return;
|
||||
|
||||
dcon->pending_src = arg;
|
||||
|
||||
if (dcon->curr_src != arg)
|
||||
schedule_work(&dcon->switch_source);
|
||||
}
|
||||
|
||||
static void dcon_set_source_sync(struct dcon_priv *dcon, int arg)
|
||||
{
|
||||
dcon_set_source(dcon, arg);
|
||||
flush_scheduled_work();
|
||||
}
|
||||
|
||||
static ssize_t dcon_mode_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct dcon_priv *dcon = dev_get_drvdata(dev);
|
||||
|
||||
return sprintf(buf, "%4.4X\n", dcon->disp_mode);
|
||||
}
|
||||
|
||||
static ssize_t dcon_sleep_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct dcon_priv *dcon = dev_get_drvdata(dev);
|
||||
|
||||
return sprintf(buf, "%d\n", dcon->asleep);
|
||||
}
|
||||
|
||||
static ssize_t dcon_freeze_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct dcon_priv *dcon = dev_get_drvdata(dev);
|
||||
|
||||
return sprintf(buf, "%d\n", dcon->curr_src == DCON_SOURCE_DCON ? 1 : 0);
|
||||
}
|
||||
|
||||
static ssize_t dcon_mono_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct dcon_priv *dcon = dev_get_drvdata(dev);
|
||||
|
||||
return sprintf(buf, "%d\n", dcon->mono);
|
||||
}
|
||||
|
||||
static ssize_t dcon_resumeline_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
return sprintf(buf, "%d\n", resumeline);
|
||||
}
|
||||
|
||||
static ssize_t dcon_mono_store(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
unsigned long enable_mono;
|
||||
int rc;
|
||||
|
||||
rc = kstrtoul(buf, 10, &enable_mono);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
dcon_set_mono_mode(dev_get_drvdata(dev), enable_mono ? true : false);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t dcon_freeze_store(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
struct dcon_priv *dcon = dev_get_drvdata(dev);
|
||||
unsigned long output;
|
||||
int ret;
|
||||
|
||||
ret = kstrtoul(buf, 10, &output);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
pr_info("dcon_freeze_store: %lu\n", output);
|
||||
|
||||
switch (output) {
|
||||
case 0:
|
||||
dcon_set_source(dcon, DCON_SOURCE_CPU);
|
||||
break;
|
||||
case 1:
|
||||
dcon_set_source_sync(dcon, DCON_SOURCE_DCON);
|
||||
break;
|
||||
case 2: /* normally unused */
|
||||
dcon_set_source(dcon, DCON_SOURCE_DCON);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t dcon_resumeline_store(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
unsigned short rl;
|
||||
int rc;
|
||||
|
||||
rc = kstrtou16(buf, 10, &rl);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
resumeline = rl;
|
||||
dcon_write(dev_get_drvdata(dev), DCON_REG_SCAN_INT, resumeline);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t dcon_sleep_store(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
unsigned long output;
|
||||
int ret;
|
||||
|
||||
ret = kstrtoul(buf, 10, &output);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
dcon_sleep(dev_get_drvdata(dev), output ? true : false);
|
||||
return count;
|
||||
}
|
||||
|
||||
static struct device_attribute dcon_device_files[] = {
|
||||
__ATTR(mode, 0444, dcon_mode_show, NULL),
|
||||
__ATTR(sleep, 0644, dcon_sleep_show, dcon_sleep_store),
|
||||
__ATTR(freeze, 0644, dcon_freeze_show, dcon_freeze_store),
|
||||
__ATTR(monochrome, 0644, dcon_mono_show, dcon_mono_store),
|
||||
__ATTR(resumeline, 0644, dcon_resumeline_show, dcon_resumeline_store),
|
||||
};
|
||||
|
||||
static int dcon_bl_update(struct backlight_device *dev)
|
||||
{
|
||||
struct dcon_priv *dcon = bl_get_data(dev);
|
||||
u8 level = dev->props.brightness & 0x0F;
|
||||
|
||||
if (dev->props.power != FB_BLANK_UNBLANK)
|
||||
level = 0;
|
||||
|
||||
if (level != dcon->bl_val)
|
||||
dcon_set_backlight(dcon, level);
|
||||
|
||||
/* power down the DCON when the screen is blanked */
|
||||
if (!dcon->ignore_fb_events)
|
||||
dcon_sleep(dcon, !!(dev->props.state & BL_CORE_FBBLANK));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dcon_bl_get(struct backlight_device *dev)
|
||||
{
|
||||
struct dcon_priv *dcon = bl_get_data(dev);
|
||||
|
||||
return dcon->bl_val;
|
||||
}
|
||||
|
||||
static const struct backlight_ops dcon_bl_ops = {
|
||||
.update_status = dcon_bl_update,
|
||||
.get_brightness = dcon_bl_get,
|
||||
};
|
||||
|
||||
static struct backlight_properties dcon_bl_props = {
|
||||
.max_brightness = 15,
|
||||
.type = BACKLIGHT_RAW,
|
||||
.power = FB_BLANK_UNBLANK,
|
||||
};
|
||||
|
||||
static int dcon_reboot_notify(struct notifier_block *nb,
|
||||
unsigned long foo, void *bar)
|
||||
{
|
||||
struct dcon_priv *dcon = container_of(nb, struct dcon_priv, reboot_nb);
|
||||
|
||||
if (!dcon || !dcon->client)
|
||||
return NOTIFY_DONE;
|
||||
|
||||
/* Turn off the DCON. Entirely. */
|
||||
dcon_write(dcon, DCON_REG_MODE, 0x39);
|
||||
dcon_write(dcon, DCON_REG_MODE, 0x32);
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static int unfreeze_on_panic(struct notifier_block *nb,
|
||||
unsigned long e, void *p)
|
||||
{
|
||||
pdata->set_dconload(1);
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static struct notifier_block dcon_panic_nb = {
|
||||
.notifier_call = unfreeze_on_panic,
|
||||
};
|
||||
|
||||
static int dcon_detect(struct i2c_client *client, struct i2c_board_info *info)
|
||||
{
|
||||
strlcpy(info->type, "olpc_dcon", I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dcon_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
||||
{
|
||||
struct dcon_priv *dcon;
|
||||
int rc, i, j;
|
||||
|
||||
if (!pdata)
|
||||
return -ENXIO;
|
||||
|
||||
dcon = kzalloc(sizeof(*dcon), GFP_KERNEL);
|
||||
if (!dcon)
|
||||
return -ENOMEM;
|
||||
|
||||
dcon->client = client;
|
||||
init_waitqueue_head(&dcon->waitq);
|
||||
INIT_WORK(&dcon->switch_source, dcon_source_switch);
|
||||
dcon->reboot_nb.notifier_call = dcon_reboot_notify;
|
||||
dcon->reboot_nb.priority = -1;
|
||||
|
||||
i2c_set_clientdata(client, dcon);
|
||||
|
||||
if (num_registered_fb < 1) {
|
||||
dev_err(&client->dev, "DCON driver requires a registered fb\n");
|
||||
rc = -EIO;
|
||||
goto einit;
|
||||
}
|
||||
dcon->fbinfo = registered_fb[0];
|
||||
|
||||
rc = dcon_hw_init(dcon, 1);
|
||||
if (rc)
|
||||
goto einit;
|
||||
|
||||
/* Add the DCON device */
|
||||
|
||||
dcon_device = platform_device_alloc("dcon", -1);
|
||||
|
||||
if (!dcon_device) {
|
||||
pr_err("Unable to create the DCON device\n");
|
||||
rc = -ENOMEM;
|
||||
goto eirq;
|
||||
}
|
||||
rc = platform_device_add(dcon_device);
|
||||
platform_set_drvdata(dcon_device, dcon);
|
||||
|
||||
if (rc) {
|
||||
pr_err("Unable to add the DCON device\n");
|
||||
goto edev;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(dcon_device_files); i++) {
|
||||
rc = device_create_file(&dcon_device->dev,
|
||||
&dcon_device_files[i]);
|
||||
if (rc) {
|
||||
dev_err(&dcon_device->dev, "Cannot create sysfs file\n");
|
||||
goto ecreate;
|
||||
}
|
||||
}
|
||||
|
||||
dcon->bl_val = dcon_read(dcon, DCON_REG_BRIGHT) & 0x0F;
|
||||
|
||||
/* Add the backlight device for the DCON */
|
||||
dcon_bl_props.brightness = dcon->bl_val;
|
||||
dcon->bl_dev = backlight_device_register("dcon-bl", &dcon_device->dev,
|
||||
dcon, &dcon_bl_ops, &dcon_bl_props);
|
||||
if (IS_ERR(dcon->bl_dev)) {
|
||||
dev_err(&client->dev, "cannot register backlight dev (%ld)\n",
|
||||
PTR_ERR(dcon->bl_dev));
|
||||
dcon->bl_dev = NULL;
|
||||
}
|
||||
|
||||
register_reboot_notifier(&dcon->reboot_nb);
|
||||
atomic_notifier_chain_register(&panic_notifier_list, &dcon_panic_nb);
|
||||
|
||||
return 0;
|
||||
|
||||
ecreate:
|
||||
for (j = 0; j < i; j++)
|
||||
device_remove_file(&dcon_device->dev, &dcon_device_files[j]);
|
||||
edev:
|
||||
platform_device_unregister(dcon_device);
|
||||
dcon_device = NULL;
|
||||
eirq:
|
||||
free_irq(DCON_IRQ, dcon);
|
||||
einit:
|
||||
kfree(dcon);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int dcon_remove(struct i2c_client *client)
|
||||
{
|
||||
struct dcon_priv *dcon = i2c_get_clientdata(client);
|
||||
|
||||
unregister_reboot_notifier(&dcon->reboot_nb);
|
||||
atomic_notifier_chain_unregister(&panic_notifier_list, &dcon_panic_nb);
|
||||
|
||||
free_irq(DCON_IRQ, dcon);
|
||||
|
||||
backlight_device_unregister(dcon->bl_dev);
|
||||
|
||||
if (dcon_device)
|
||||
platform_device_unregister(dcon_device);
|
||||
cancel_work_sync(&dcon->switch_source);
|
||||
|
||||
kfree(dcon);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int dcon_suspend(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct dcon_priv *dcon = i2c_get_clientdata(client);
|
||||
|
||||
if (!dcon->asleep) {
|
||||
/* Set up the DCON to have the source */
|
||||
dcon_set_source_sync(dcon, DCON_SOURCE_DCON);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dcon_resume(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
struct dcon_priv *dcon = i2c_get_clientdata(client);
|
||||
|
||||
if (!dcon->asleep) {
|
||||
dcon_bus_stabilize(dcon, 0);
|
||||
dcon_set_source(dcon, DCON_SOURCE_CPU);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#define dcon_suspend NULL
|
||||
#define dcon_resume NULL
|
||||
|
||||
#endif /* CONFIG_PM */
|
||||
|
||||
irqreturn_t dcon_interrupt(int irq, void *id)
|
||||
{
|
||||
struct dcon_priv *dcon = id;
|
||||
u8 status;
|
||||
|
||||
if (pdata->read_status(&status))
|
||||
return IRQ_NONE;
|
||||
|
||||
switch (status & 3) {
|
||||
case 3:
|
||||
pr_debug("DCONLOAD_MISSED interrupt\n");
|
||||
break;
|
||||
|
||||
case 2: /* switch to DCON mode */
|
||||
case 1: /* switch to CPU mode */
|
||||
dcon->switched = true;
|
||||
dcon->irq_time = ktime_get();
|
||||
wake_up(&dcon->waitq);
|
||||
break;
|
||||
|
||||
case 0:
|
||||
/* workaround resume case: the DCON (on 1.5) doesn't
|
||||
* ever assert status 0x01 when switching to CPU mode
|
||||
* during resume. this is because DCONLOAD is de-asserted
|
||||
* _immediately_ upon exiting S3, so the actual release
|
||||
* of the DCON happened long before this point.
|
||||
* see http://dev.laptop.org/ticket/9869
|
||||
*/
|
||||
if (dcon->curr_src != dcon->pending_src && !dcon->switched) {
|
||||
dcon->switched = true;
|
||||
dcon->irq_time = ktime_get();
|
||||
wake_up(&dcon->waitq);
|
||||
pr_debug("switching w/ status 0/0\n");
|
||||
} else {
|
||||
pr_debug("scanline interrupt w/CPU\n");
|
||||
}
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops dcon_pm_ops = {
|
||||
.suspend = dcon_suspend,
|
||||
.resume = dcon_resume,
|
||||
};
|
||||
|
||||
static const struct i2c_device_id dcon_idtable[] = {
|
||||
{ "olpc_dcon", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, dcon_idtable);
|
||||
|
||||
static struct i2c_driver dcon_driver = {
|
||||
.driver = {
|
||||
.name = "olpc_dcon",
|
||||
.pm = &dcon_pm_ops,
|
||||
},
|
||||
.class = I2C_CLASS_DDC | I2C_CLASS_HWMON,
|
||||
.id_table = dcon_idtable,
|
||||
.probe = dcon_probe,
|
||||
.remove = dcon_remove,
|
||||
.detect = dcon_detect,
|
||||
.address_list = normal_i2c,
|
||||
};
|
||||
|
||||
static int __init olpc_dcon_init(void)
|
||||
{
|
||||
#ifdef CONFIG_FB_OLPC_DCON_1_5
|
||||
/* XO-1.5 */
|
||||
if (olpc_board_at_least(olpc_board(0xd0)))
|
||||
pdata = &dcon_pdata_xo_1_5;
|
||||
#endif
|
||||
#ifdef CONFIG_FB_OLPC_DCON_1
|
||||
if (!pdata)
|
||||
pdata = &dcon_pdata_xo_1;
|
||||
#endif
|
||||
|
||||
return i2c_add_driver(&dcon_driver);
|
||||
}
|
||||
|
||||
static void __exit olpc_dcon_exit(void)
|
||||
{
|
||||
i2c_del_driver(&dcon_driver);
|
||||
}
|
||||
|
||||
module_init(olpc_dcon_init);
|
||||
module_exit(olpc_dcon_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,111 @@
|
|||
#ifndef OLPC_DCON_H_
|
||||
#define OLPC_DCON_H_
|
||||
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
/* DCON registers */
|
||||
|
||||
#define DCON_REG_ID 0
|
||||
#define DCON_REG_MODE 1
|
||||
|
||||
#define MODE_PASSTHRU (1<<0)
|
||||
#define MODE_SLEEP (1<<1)
|
||||
#define MODE_SLEEP_AUTO (1<<2)
|
||||
#define MODE_BL_ENABLE (1<<3)
|
||||
#define MODE_BLANK (1<<4)
|
||||
#define MODE_CSWIZZLE (1<<5)
|
||||
#define MODE_COL_AA (1<<6)
|
||||
#define MODE_MONO_LUMA (1<<7)
|
||||
#define MODE_SCAN_INT (1<<8)
|
||||
#define MODE_CLOCKDIV (1<<9)
|
||||
#define MODE_DEBUG (1<<14)
|
||||
#define MODE_SELFTEST (1<<15)
|
||||
|
||||
#define DCON_REG_HRES 0x2
|
||||
#define DCON_REG_HTOTAL 0x3
|
||||
#define DCON_REG_HSYNC_WIDTH 0x4
|
||||
#define DCON_REG_VRES 0x5
|
||||
#define DCON_REG_VTOTAL 0x6
|
||||
#define DCON_REG_VSYNC_WIDTH 0x7
|
||||
#define DCON_REG_TIMEOUT 0x8
|
||||
#define DCON_REG_SCAN_INT 0x9
|
||||
#define DCON_REG_BRIGHT 0xa
|
||||
#define DCON_REG_MEM_OPT_A 0x41
|
||||
#define DCON_REG_MEM_OPT_B 0x42
|
||||
|
||||
/* Load Delay Locked Loop (DLL) settings for clock delay */
|
||||
#define MEM_DLL_CLOCK_DELAY (1<<0)
|
||||
/* Memory controller power down function */
|
||||
#define MEM_POWER_DOWN (1<<8)
|
||||
/* Memory controller software reset */
|
||||
#define MEM_SOFT_RESET (1<<0)
|
||||
|
||||
/* Status values */
|
||||
|
||||
#define DCONSTAT_SCANINT 0
|
||||
#define DCONSTAT_SCANINT_DCON 1
|
||||
#define DCONSTAT_DISPLAYLOAD 2
|
||||
#define DCONSTAT_MISSED 3
|
||||
|
||||
/* Source values */
|
||||
|
||||
#define DCON_SOURCE_DCON 0
|
||||
#define DCON_SOURCE_CPU 1
|
||||
|
||||
/* Interrupt */
|
||||
#define DCON_IRQ 6
|
||||
|
||||
struct dcon_priv {
|
||||
struct i2c_client *client;
|
||||
struct fb_info *fbinfo;
|
||||
struct backlight_device *bl_dev;
|
||||
|
||||
wait_queue_head_t waitq;
|
||||
struct work_struct switch_source;
|
||||
struct notifier_block reboot_nb;
|
||||
|
||||
/* Shadow register for the DCON_REG_MODE register */
|
||||
u8 disp_mode;
|
||||
|
||||
/* The current backlight value - this saves us some smbus traffic */
|
||||
u8 bl_val;
|
||||
|
||||
/* Current source, initialized at probe time */
|
||||
int curr_src;
|
||||
|
||||
/* Desired source */
|
||||
int pending_src;
|
||||
|
||||
/* Variables used during switches */
|
||||
bool switched;
|
||||
ktime_t irq_time;
|
||||
ktime_t load_time;
|
||||
|
||||
/* Current output type; true == mono, false == color */
|
||||
bool mono;
|
||||
bool asleep;
|
||||
/* This get set while controlling fb blank state from the driver */
|
||||
bool ignore_fb_events;
|
||||
};
|
||||
|
||||
struct dcon_platform_data {
|
||||
int (*init)(struct dcon_priv *);
|
||||
void (*bus_stabilize_wiggle)(void);
|
||||
void (*set_dconload)(int);
|
||||
int (*read_status)(u8 *);
|
||||
};
|
||||
|
||||
#include <linux/interrupt.h>
|
||||
|
||||
irqreturn_t dcon_interrupt(int irq, void *id);
|
||||
|
||||
#ifdef CONFIG_FB_OLPC_DCON_1
|
||||
extern struct dcon_platform_data dcon_pdata_xo_1;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_FB_OLPC_DCON_1_5
|
||||
extern struct dcon_platform_data dcon_pdata_xo_1_5;
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,205 @@
|
|||
/*
|
||||
* Mainly by David Woodhouse, somewhat modified by Jordan Crouse
|
||||
*
|
||||
* Copyright © 2006-2007 Red Hat, Inc.
|
||||
* Copyright © 2006-2007 Advanced Micro Devices, Inc.
|
||||
* Copyright © 2009 VIA Technology, Inc.
|
||||
* Copyright (c) 2010 Andres Salomon <dilinger@queued.net>
|
||||
*
|
||||
* This program is free software. You can redistribute it and/or
|
||||
* modify it under the terms of version 2 of the GNU General Public
|
||||
* License as published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/cs5535.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/delay.h>
|
||||
#include <asm/olpc.h>
|
||||
|
||||
#include "olpc_dcon.h"
|
||||
|
||||
static int dcon_init_xo_1(struct dcon_priv *dcon)
|
||||
{
|
||||
unsigned char lob;
|
||||
|
||||
if (gpio_request(OLPC_GPIO_DCON_STAT0, "OLPC-DCON")) {
|
||||
pr_err("failed to request STAT0 GPIO\n");
|
||||
return -EIO;
|
||||
}
|
||||
if (gpio_request(OLPC_GPIO_DCON_STAT1, "OLPC-DCON")) {
|
||||
pr_err("failed to request STAT1 GPIO\n");
|
||||
goto err_gp_stat1;
|
||||
}
|
||||
if (gpio_request(OLPC_GPIO_DCON_IRQ, "OLPC-DCON")) {
|
||||
pr_err("failed to request IRQ GPIO\n");
|
||||
goto err_gp_irq;
|
||||
}
|
||||
if (gpio_request(OLPC_GPIO_DCON_LOAD, "OLPC-DCON")) {
|
||||
pr_err("failed to request LOAD GPIO\n");
|
||||
goto err_gp_load;
|
||||
}
|
||||
if (gpio_request(OLPC_GPIO_DCON_BLANK, "OLPC-DCON")) {
|
||||
pr_err("failed to request BLANK GPIO\n");
|
||||
goto err_gp_blank;
|
||||
}
|
||||
|
||||
/* Turn off the event enable for GPIO7 just to be safe */
|
||||
cs5535_gpio_clear(OLPC_GPIO_DCON_IRQ, GPIO_EVENTS_ENABLE);
|
||||
|
||||
/*
|
||||
* Determine the current state by reading the GPIO bit; earlier
|
||||
* stages of the boot process have established the state.
|
||||
*
|
||||
* Note that we read GPIO_OUTPUT_VAL rather than GPIO_READ_BACK here;
|
||||
* this is because OFW will disable input for the pin and set a value..
|
||||
* READ_BACK will only contain a valid value if input is enabled and
|
||||
* then a value is set. So, future readings of the pin can use
|
||||
* READ_BACK, but the first one cannot. Awesome, huh?
|
||||
*/
|
||||
dcon->curr_src = cs5535_gpio_isset(OLPC_GPIO_DCON_LOAD, GPIO_OUTPUT_VAL)
|
||||
? DCON_SOURCE_CPU
|
||||
: DCON_SOURCE_DCON;
|
||||
dcon->pending_src = dcon->curr_src;
|
||||
|
||||
/* Set the directions for the GPIO pins */
|
||||
gpio_direction_input(OLPC_GPIO_DCON_STAT0);
|
||||
gpio_direction_input(OLPC_GPIO_DCON_STAT1);
|
||||
gpio_direction_input(OLPC_GPIO_DCON_IRQ);
|
||||
gpio_direction_input(OLPC_GPIO_DCON_BLANK);
|
||||
gpio_direction_output(OLPC_GPIO_DCON_LOAD,
|
||||
dcon->curr_src == DCON_SOURCE_CPU);
|
||||
|
||||
/* Set up the interrupt mappings */
|
||||
|
||||
/* Set the IRQ to pair 2 */
|
||||
cs5535_gpio_setup_event(OLPC_GPIO_DCON_IRQ, 2, 0);
|
||||
|
||||
/* Enable group 2 to trigger the DCON interrupt */
|
||||
cs5535_gpio_set_irq(2, DCON_IRQ);
|
||||
|
||||
/* Select edge level for interrupt (in PIC) */
|
||||
lob = inb(0x4d0);
|
||||
lob &= ~(1 << DCON_IRQ);
|
||||
outb(lob, 0x4d0);
|
||||
|
||||
/* Register the interrupt handler */
|
||||
if (request_irq(DCON_IRQ, &dcon_interrupt, 0, "DCON", dcon)) {
|
||||
pr_err("failed to request DCON's irq\n");
|
||||
goto err_req_irq;
|
||||
}
|
||||
|
||||
/* Clear INV_EN for GPIO7 (DCONIRQ) */
|
||||
cs5535_gpio_clear(OLPC_GPIO_DCON_IRQ, GPIO_INPUT_INVERT);
|
||||
|
||||
/* Enable filter for GPIO12 (DCONBLANK) */
|
||||
cs5535_gpio_set(OLPC_GPIO_DCON_BLANK, GPIO_INPUT_FILTER);
|
||||
|
||||
/* Disable filter for GPIO7 */
|
||||
cs5535_gpio_clear(OLPC_GPIO_DCON_IRQ, GPIO_INPUT_FILTER);
|
||||
|
||||
/* Disable event counter for GPIO7 (DCONIRQ) and GPIO12 (DCONBLANK) */
|
||||
cs5535_gpio_clear(OLPC_GPIO_DCON_IRQ, GPIO_INPUT_EVENT_COUNT);
|
||||
cs5535_gpio_clear(OLPC_GPIO_DCON_BLANK, GPIO_INPUT_EVENT_COUNT);
|
||||
|
||||
/* Add GPIO12 to the Filter Event Pair #7 */
|
||||
cs5535_gpio_set(OLPC_GPIO_DCON_BLANK, GPIO_FE7_SEL);
|
||||
|
||||
/* Turn off negative Edge Enable for GPIO12 */
|
||||
cs5535_gpio_clear(OLPC_GPIO_DCON_BLANK, GPIO_NEGATIVE_EDGE_EN);
|
||||
|
||||
/* Enable negative Edge Enable for GPIO7 */
|
||||
cs5535_gpio_set(OLPC_GPIO_DCON_IRQ, GPIO_NEGATIVE_EDGE_EN);
|
||||
|
||||
/* Zero the filter amount for Filter Event Pair #7 */
|
||||
cs5535_gpio_set(0, GPIO_FLTR7_AMOUNT);
|
||||
|
||||
/* Clear the negative edge status for GPIO7 and GPIO12 */
|
||||
cs5535_gpio_set(OLPC_GPIO_DCON_IRQ, GPIO_NEGATIVE_EDGE_STS);
|
||||
cs5535_gpio_set(OLPC_GPIO_DCON_BLANK, GPIO_NEGATIVE_EDGE_STS);
|
||||
|
||||
/* FIXME: Clear the positive status as well, just to be sure */
|
||||
cs5535_gpio_set(OLPC_GPIO_DCON_IRQ, GPIO_POSITIVE_EDGE_STS);
|
||||
cs5535_gpio_set(OLPC_GPIO_DCON_BLANK, GPIO_POSITIVE_EDGE_STS);
|
||||
|
||||
/* Enable events for GPIO7 (DCONIRQ) and GPIO12 (DCONBLANK) */
|
||||
cs5535_gpio_set(OLPC_GPIO_DCON_IRQ, GPIO_EVENTS_ENABLE);
|
||||
cs5535_gpio_set(OLPC_GPIO_DCON_BLANK, GPIO_EVENTS_ENABLE);
|
||||
|
||||
return 0;
|
||||
|
||||
err_req_irq:
|
||||
gpio_free(OLPC_GPIO_DCON_BLANK);
|
||||
err_gp_blank:
|
||||
gpio_free(OLPC_GPIO_DCON_LOAD);
|
||||
err_gp_load:
|
||||
gpio_free(OLPC_GPIO_DCON_IRQ);
|
||||
err_gp_irq:
|
||||
gpio_free(OLPC_GPIO_DCON_STAT1);
|
||||
err_gp_stat1:
|
||||
gpio_free(OLPC_GPIO_DCON_STAT0);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static void dcon_wiggle_xo_1(void)
|
||||
{
|
||||
int x;
|
||||
|
||||
/*
|
||||
* According to HiMax, when powering the DCON up we should hold
|
||||
* SMB_DATA high for 8 SMB_CLK cycles. This will force the DCON
|
||||
* state machine to reset to a (sane) initial state. Mitch Bradley
|
||||
* did some testing and discovered that holding for 16 SMB_CLK cycles
|
||||
* worked a lot more reliably, so that's what we do here.
|
||||
*
|
||||
* According to the cs5536 spec, to set GPIO14 to SMB_CLK we must
|
||||
* simultaneously set AUX1 IN/OUT to GPIO14; ditto for SMB_DATA and
|
||||
* GPIO15.
|
||||
*/
|
||||
cs5535_gpio_set(OLPC_GPIO_SMB_CLK, GPIO_OUTPUT_VAL);
|
||||
cs5535_gpio_set(OLPC_GPIO_SMB_DATA, GPIO_OUTPUT_VAL);
|
||||
cs5535_gpio_set(OLPC_GPIO_SMB_CLK, GPIO_OUTPUT_ENABLE);
|
||||
cs5535_gpio_set(OLPC_GPIO_SMB_DATA, GPIO_OUTPUT_ENABLE);
|
||||
cs5535_gpio_clear(OLPC_GPIO_SMB_CLK, GPIO_OUTPUT_AUX1);
|
||||
cs5535_gpio_clear(OLPC_GPIO_SMB_DATA, GPIO_OUTPUT_AUX1);
|
||||
cs5535_gpio_clear(OLPC_GPIO_SMB_CLK, GPIO_OUTPUT_AUX2);
|
||||
cs5535_gpio_clear(OLPC_GPIO_SMB_DATA, GPIO_OUTPUT_AUX2);
|
||||
cs5535_gpio_clear(OLPC_GPIO_SMB_CLK, GPIO_INPUT_AUX1);
|
||||
cs5535_gpio_clear(OLPC_GPIO_SMB_DATA, GPIO_INPUT_AUX1);
|
||||
|
||||
for (x = 0; x < 16; x++) {
|
||||
udelay(5);
|
||||
cs5535_gpio_clear(OLPC_GPIO_SMB_CLK, GPIO_OUTPUT_VAL);
|
||||
udelay(5);
|
||||
cs5535_gpio_set(OLPC_GPIO_SMB_CLK, GPIO_OUTPUT_VAL);
|
||||
}
|
||||
udelay(5);
|
||||
cs5535_gpio_set(OLPC_GPIO_SMB_CLK, GPIO_OUTPUT_AUX1);
|
||||
cs5535_gpio_set(OLPC_GPIO_SMB_DATA, GPIO_OUTPUT_AUX1);
|
||||
cs5535_gpio_set(OLPC_GPIO_SMB_CLK, GPIO_INPUT_AUX1);
|
||||
cs5535_gpio_set(OLPC_GPIO_SMB_DATA, GPIO_INPUT_AUX1);
|
||||
}
|
||||
|
||||
static void dcon_set_dconload_1(int val)
|
||||
{
|
||||
gpio_set_value(OLPC_GPIO_DCON_LOAD, val);
|
||||
}
|
||||
|
||||
static int dcon_read_status_xo_1(u8 *status)
|
||||
{
|
||||
*status = gpio_get_value(OLPC_GPIO_DCON_STAT0);
|
||||
*status |= gpio_get_value(OLPC_GPIO_DCON_STAT1) << 1;
|
||||
|
||||
/* Clear the negative edge status for GPIO7 */
|
||||
cs5535_gpio_set(OLPC_GPIO_DCON_IRQ, GPIO_NEGATIVE_EDGE_STS);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct dcon_platform_data dcon_pdata_xo_1 = {
|
||||
.init = dcon_init_xo_1,
|
||||
.bus_stabilize_wiggle = dcon_wiggle_xo_1,
|
||||
.set_dconload = dcon_set_dconload_1,
|
||||
.read_status = dcon_read_status_xo_1,
|
||||
};
|
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* Copyright (c) 2009,2010 One Laptop per Child
|
||||
*
|
||||
* This program is free software. You can redistribute it and/or
|
||||
* modify it under the terms of version 2 of the GNU General Public
|
||||
* License as published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <asm/olpc.h>
|
||||
|
||||
/* TODO: this eventually belongs in linux/vx855.h */
|
||||
#define NR_VX855_GPI 14
|
||||
#define NR_VX855_GPO 13
|
||||
#define NR_VX855_GPIO 15
|
||||
|
||||
#define VX855_GPI(n) (n)
|
||||
#define VX855_GPO(n) (NR_VX855_GPI + (n))
|
||||
#define VX855_GPIO(n) (NR_VX855_GPI + NR_VX855_GPO + (n))
|
||||
|
||||
#include "olpc_dcon.h"
|
||||
|
||||
/* Hardware setup on the XO 1.5:
|
||||
* DCONLOAD connects to VX855_GPIO1 (not SMBCK2)
|
||||
* DCONBLANK connects to VX855_GPIO8 (not SSPICLK) unused in driver
|
||||
* DCONSTAT0 connects to VX855_GPI10 (not SSPISDI)
|
||||
* DCONSTAT1 connects to VX855_GPI11 (not nSSPISS)
|
||||
* DCONIRQ connects to VX855_GPIO12
|
||||
* DCONSMBDATA connects to VX855 graphics CRTSPD
|
||||
* DCONSMBCLK connects to VX855 graphics CRTSPCLK
|
||||
*/
|
||||
|
||||
#define VX855_GENL_PURPOSE_OUTPUT 0x44c /* PMIO_Rx4c-4f */
|
||||
#define VX855_GPI_STATUS_CHG 0x450 /* PMIO_Rx50 */
|
||||
#define VX855_GPI_SCI_SMI 0x452 /* PMIO_Rx52 */
|
||||
#define BIT_GPIO12 0x40
|
||||
|
||||
#define PREFIX "OLPC DCON:"
|
||||
|
||||
static void dcon_clear_irq(void)
|
||||
{
|
||||
/* irq status will appear in PMIO_Rx50[6] (RW1C) on gpio12 */
|
||||
outb(BIT_GPIO12, VX855_GPI_STATUS_CHG);
|
||||
}
|
||||
|
||||
static int dcon_was_irq(void)
|
||||
{
|
||||
u_int8_t tmp;
|
||||
|
||||
/* irq status will appear in PMIO_Rx50[6] on gpio12 */
|
||||
tmp = inb(VX855_GPI_STATUS_CHG);
|
||||
return !!(tmp & BIT_GPIO12);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dcon_init_xo_1_5(struct dcon_priv *dcon)
|
||||
{
|
||||
unsigned int irq;
|
||||
|
||||
dcon_clear_irq();
|
||||
|
||||
/* set PMIO_Rx52[6] to enable SCI/SMI on gpio12 */
|
||||
outb(inb(VX855_GPI_SCI_SMI)|BIT_GPIO12, VX855_GPI_SCI_SMI);
|
||||
|
||||
/* Determine the current state of DCONLOAD, likely set by firmware */
|
||||
/* GPIO1 */
|
||||
dcon->curr_src = (inl(VX855_GENL_PURPOSE_OUTPUT) & 0x1000) ?
|
||||
DCON_SOURCE_CPU : DCON_SOURCE_DCON;
|
||||
dcon->pending_src = dcon->curr_src;
|
||||
|
||||
/* we're sharing the IRQ with ACPI */
|
||||
irq = acpi_gbl_FADT.sci_interrupt;
|
||||
if (request_irq(irq, &dcon_interrupt, IRQF_SHARED, "DCON", dcon)) {
|
||||
pr_err("DCON (IRQ%d) allocation failed\n", irq);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void set_i2c_line(int sda, int scl)
|
||||
{
|
||||
unsigned char tmp;
|
||||
unsigned int port = 0x26;
|
||||
|
||||
/* FIXME: This directly accesses the CRT GPIO controller !!! */
|
||||
outb(port, 0x3c4);
|
||||
tmp = inb(0x3c5);
|
||||
|
||||
if (scl)
|
||||
tmp |= 0x20;
|
||||
else
|
||||
tmp &= ~0x20;
|
||||
|
||||
if (sda)
|
||||
tmp |= 0x10;
|
||||
else
|
||||
tmp &= ~0x10;
|
||||
|
||||
tmp |= 0x01;
|
||||
|
||||
outb(port, 0x3c4);
|
||||
outb(tmp, 0x3c5);
|
||||
}
|
||||
|
||||
|
||||
static void dcon_wiggle_xo_1_5(void)
|
||||
{
|
||||
int x;
|
||||
|
||||
/*
|
||||
* According to HiMax, when powering the DCON up we should hold
|
||||
* SMB_DATA high for 8 SMB_CLK cycles. This will force the DCON
|
||||
* state machine to reset to a (sane) initial state. Mitch Bradley
|
||||
* did some testing and discovered that holding for 16 SMB_CLK cycles
|
||||
* worked a lot more reliably, so that's what we do here.
|
||||
*/
|
||||
set_i2c_line(1, 1);
|
||||
|
||||
for (x = 0; x < 16; x++) {
|
||||
udelay(5);
|
||||
set_i2c_line(1, 0);
|
||||
udelay(5);
|
||||
set_i2c_line(1, 1);
|
||||
}
|
||||
udelay(5);
|
||||
|
||||
/* set PMIO_Rx52[6] to enable SCI/SMI on gpio12 */
|
||||
outb(inb(VX855_GPI_SCI_SMI)|BIT_GPIO12, VX855_GPI_SCI_SMI);
|
||||
}
|
||||
|
||||
static void dcon_set_dconload_xo_1_5(int val)
|
||||
{
|
||||
gpio_set_value(VX855_GPIO(1), val);
|
||||
}
|
||||
|
||||
static int dcon_read_status_xo_1_5(u8 *status)
|
||||
{
|
||||
if (!dcon_was_irq())
|
||||
return -1;
|
||||
|
||||
/* i believe this is the same as "inb(0x44b) & 3" */
|
||||
*status = gpio_get_value(VX855_GPI(10));
|
||||
*status |= gpio_get_value(VX855_GPI(11)) << 1;
|
||||
|
||||
dcon_clear_irq();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct dcon_platform_data dcon_pdata_xo_1_5 = {
|
||||
.init = dcon_init_xo_1_5,
|
||||
.bus_stabilize_wiggle = dcon_wiggle_xo_1_5,
|
||||
.set_dconload = dcon_set_dconload_xo_1_5,
|
||||
.read_status = dcon_read_status_xo_1_5,
|
||||
};
|
|
@ -2,6 +2,7 @@ config INFINIBAND_HFI1
|
|||
tristate "Intel OPA Gen1 support"
|
||||
depends on X86_64 && INFINIBAND_RDMAVT
|
||||
select MMU_NOTIFIER
|
||||
select CRC32
|
||||
default m
|
||||
---help---
|
||||
This is a low-level driver for Intel OPA Gen1 adapter.
|
||||
|
|
Loading…
Reference in New Issue