|
@@ -33,6 +33,13 @@ ACPI_MODULE_NAME("acpi_lpss");
|
|
|
#define LPSS_GENERAL_UART_RTS_OVRD BIT(3)
|
|
|
#define LPSS_SW_LTR 0x10
|
|
|
#define LPSS_AUTO_LTR 0x14
|
|
|
+#define LPSS_LTR_SNOOP_REQ BIT(15)
|
|
|
+#define LPSS_LTR_SNOOP_MASK 0x0000FFFF
|
|
|
+#define LPSS_LTR_SNOOP_LAT_1US 0x800
|
|
|
+#define LPSS_LTR_SNOOP_LAT_32US 0xC00
|
|
|
+#define LPSS_LTR_SNOOP_LAT_SHIFT 5
|
|
|
+#define LPSS_LTR_SNOOP_LAT_CUTOFF 3000
|
|
|
+#define LPSS_LTR_MAX_VAL 0x3FF
|
|
|
#define LPSS_TX_INT 0x20
|
|
|
#define LPSS_TX_INT_MASK BIT(1)
|
|
|
|
|
@@ -315,6 +322,17 @@ static int acpi_lpss_create_device(struct acpi_device *adev,
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
+static u32 __lpss_reg_read(struct lpss_private_data *pdata, unsigned int reg)
|
|
|
+{
|
|
|
+ return readl(pdata->mmio_base + pdata->dev_desc->prv_offset + reg);
|
|
|
+}
|
|
|
+
|
|
|
+static void __lpss_reg_write(u32 val, struct lpss_private_data *pdata,
|
|
|
+ unsigned int reg)
|
|
|
+{
|
|
|
+ writel(val, pdata->mmio_base + pdata->dev_desc->prv_offset + reg);
|
|
|
+}
|
|
|
+
|
|
|
static int lpss_reg_read(struct device *dev, unsigned int reg, u32 *val)
|
|
|
{
|
|
|
struct acpi_device *adev;
|
|
@@ -336,7 +354,7 @@ static int lpss_reg_read(struct device *dev, unsigned int reg, u32 *val)
|
|
|
ret = -ENODEV;
|
|
|
goto out;
|
|
|
}
|
|
|
- *val = readl(pdata->mmio_base + pdata->dev_desc->prv_offset + reg);
|
|
|
+ *val = __lpss_reg_read(pdata, reg);
|
|
|
|
|
|
out:
|
|
|
spin_unlock_irqrestore(&dev->power.lock, flags);
|
|
@@ -389,6 +407,37 @@ static struct attribute_group lpss_attr_group = {
|
|
|
.name = "lpss_ltr",
|
|
|
};
|
|
|
|
|
|
+static void acpi_lpss_set_ltr(struct device *dev, s32 val)
|
|
|
+{
|
|
|
+ struct lpss_private_data *pdata = acpi_driver_data(ACPI_COMPANION(dev));
|
|
|
+ u32 ltr_mode, ltr_val;
|
|
|
+
|
|
|
+ ltr_mode = __lpss_reg_read(pdata, LPSS_GENERAL);
|
|
|
+ if (val < 0) {
|
|
|
+ if (ltr_mode & LPSS_GENERAL_LTR_MODE_SW) {
|
|
|
+ ltr_mode &= ~LPSS_GENERAL_LTR_MODE_SW;
|
|
|
+ __lpss_reg_write(ltr_mode, pdata, LPSS_GENERAL);
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ ltr_val = __lpss_reg_read(pdata, LPSS_SW_LTR) & ~LPSS_LTR_SNOOP_MASK;
|
|
|
+ if (val >= LPSS_LTR_SNOOP_LAT_CUTOFF) {
|
|
|
+ ltr_val |= LPSS_LTR_SNOOP_LAT_32US;
|
|
|
+ val = LPSS_LTR_MAX_VAL;
|
|
|
+ } else if (val > LPSS_LTR_MAX_VAL) {
|
|
|
+ ltr_val |= LPSS_LTR_SNOOP_LAT_32US | LPSS_LTR_SNOOP_REQ;
|
|
|
+ val >>= LPSS_LTR_SNOOP_LAT_SHIFT;
|
|
|
+ } else {
|
|
|
+ ltr_val |= LPSS_LTR_SNOOP_LAT_1US | LPSS_LTR_SNOOP_REQ;
|
|
|
+ }
|
|
|
+ ltr_val |= val;
|
|
|
+ __lpss_reg_write(ltr_val, pdata, LPSS_SW_LTR);
|
|
|
+ if (!(ltr_mode & LPSS_GENERAL_LTR_MODE_SW)) {
|
|
|
+ ltr_mode |= LPSS_GENERAL_LTR_MODE_SW;
|
|
|
+ __lpss_reg_write(ltr_mode, pdata, LPSS_GENERAL);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
static int acpi_lpss_platform_notify(struct notifier_block *nb,
|
|
|
unsigned long action, void *data)
|
|
|
{
|
|
@@ -426,9 +475,29 @@ static struct notifier_block acpi_lpss_nb = {
|
|
|
.notifier_call = acpi_lpss_platform_notify,
|
|
|
};
|
|
|
|
|
|
+static void acpi_lpss_bind(struct device *dev)
|
|
|
+{
|
|
|
+ struct lpss_private_data *pdata = acpi_driver_data(ACPI_COMPANION(dev));
|
|
|
+
|
|
|
+ if (!pdata || !pdata->mmio_base || !pdata->dev_desc->ltr_required)
|
|
|
+ return;
|
|
|
+
|
|
|
+ if (pdata->mmio_size >= pdata->dev_desc->prv_offset + LPSS_LTR_SIZE)
|
|
|
+ dev->power.set_latency_tolerance = acpi_lpss_set_ltr;
|
|
|
+ else
|
|
|
+ dev_err(dev, "MMIO size insufficient to access LTR\n");
|
|
|
+}
|
|
|
+
|
|
|
+static void acpi_lpss_unbind(struct device *dev)
|
|
|
+{
|
|
|
+ dev->power.set_latency_tolerance = NULL;
|
|
|
+}
|
|
|
+
|
|
|
static struct acpi_scan_handler lpss_handler = {
|
|
|
.ids = acpi_lpss_device_ids,
|
|
|
.attach = acpi_lpss_create_device,
|
|
|
+ .bind = acpi_lpss_bind,
|
|
|
+ .unbind = acpi_lpss_unbind,
|
|
|
};
|
|
|
|
|
|
void __init acpi_lpss_init(void)
|