Browse Source

Merge branch 'acpi-lpss'

* acpi-lpss:
  ACPI / platform: create LPSS clocks if Lynxpoint devices are found during scan
  clk: x86: add support for Lynxpoint LPSS clocks
  x86: add support for Intel Low Power Subsystem
  ACPI / platform: fix comment about the platform device name
  ACPI: add support for CSRT table
Rafael J. Wysocki 12 years ago
parent
commit
17b1639b30

+ 10 - 0
arch/x86/Kconfig

@@ -454,6 +454,16 @@ config X86_MDFLD
 
 endif
 
+config X86_INTEL_LPSS
+	bool "Intel Low Power Subsystem Support"
+	depends on ACPI
+	select COMMON_CLK
+	---help---
+	  Select to build support for Intel Low Power Subsystem such as
+	  found on Intel Lynxpoint PCH. Selecting this option enables
+	  things like clock tree (common clock framework) which are needed
+	  by the LPSS peripheral drivers.
+
 config X86_RDC321X
 	bool "RDC R-321x SoC"
 	depends on X86_32

+ 1 - 0
drivers/acpi/Makefile

@@ -38,6 +38,7 @@ acpi-y				+= processor_core.o
 acpi-y				+= ec.o
 acpi-$(CONFIG_ACPI_DOCK)	+= dock.o
 acpi-y				+= pci_root.o pci_link.o pci_irq.o
+acpi-y				+= csrt.o
 acpi-y				+= acpi_platform.o
 acpi-y				+= power.o
 acpi-y				+= event.o

+ 25 - 2
drivers/acpi/acpi_platform.c

@@ -13,6 +13,7 @@
 
 #include <linux/acpi.h>
 #include <linux/device.h>
+#include <linux/err.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/platform_device.h>
@@ -21,17 +22,34 @@
 
 ACPI_MODULE_NAME("platform");
 
+static int acpi_create_platform_clks(struct acpi_device *adev)
+{
+	static struct platform_device *pdev;
+
+	/* Create Lynxpoint LPSS clocks */
+	if (!pdev && !strncmp(acpi_device_hid(adev), "INT33C", 6)) {
+		pdev = platform_device_register_simple("clk-lpt", -1, NULL, 0);
+		if (IS_ERR(pdev))
+			return PTR_ERR(pdev);
+	}
+
+	return 0;
+}
+
 /**
  * acpi_create_platform_device - Create platform device for ACPI device node
  * @adev: ACPI device node to create a platform device for.
+ * @flags: ACPI_PLATFORM_* flags that affect the creation of the platform
+ *	   devices.
  *
  * Check if the given @adev can be represented as a platform device and, if
  * that's the case, create and register a platform device, populate its common
  * resources and returns a pointer to it.  Otherwise, return %NULL.
  *
- * The platform device's name will be taken from the @adev's _HID and _UID.
+ * Name of the platform device will be the same as @adev's.
  */
-struct platform_device *acpi_create_platform_device(struct acpi_device *adev)
+struct platform_device *acpi_create_platform_device(struct acpi_device *adev,
+						    unsigned long flags)
 {
 	struct platform_device *pdev = NULL;
 	struct acpi_device *acpi_parent;
@@ -41,6 +59,11 @@ struct platform_device *acpi_create_platform_device(struct acpi_device *adev)
 	struct resource *resources;
 	int count;
 
+	if ((flags & ACPI_PLATFORM_CLK) && acpi_create_platform_clks(adev)) {
+		dev_err(&adev->dev, "failed to create clocks\n");
+		return NULL;
+	}
+
 	/* If the ACPI node already has a physical device attached, skip it. */
 	if (adev->physical_node_count)
 		return NULL;

+ 159 - 0
drivers/acpi/csrt.c

@@ -0,0 +1,159 @@
+/*
+ * Support for Core System Resources Table (CSRT)
+ *
+ * Copyright (C) 2013, Intel Corporation
+ * Authors: Mika Westerberg <mika.westerberg@linux.intel.com>
+ *	    Andy Shevchenko <andriy.shevchenko@linux.intel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#define pr_fmt(fmt) "ACPI: CSRT: " fmt
+
+#include <linux/acpi.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/sizes.h>
+
+ACPI_MODULE_NAME("CSRT");
+
+static int __init acpi_csrt_parse_shared_info(struct platform_device *pdev,
+					      const struct acpi_csrt_group *grp)
+{
+	const struct acpi_csrt_shared_info *si;
+	struct resource res[3];
+	size_t nres;
+	int ret;
+
+	memset(res, 0, sizeof(res));
+	nres = 0;
+
+	si = (const struct acpi_csrt_shared_info *)&grp[1];
+	/*
+	 * The peripherals that are listed on CSRT typically support only
+	 * 32-bit addresses so we only use the low part of MMIO base for
+	 * now.
+	 */
+	if (!si->mmio_base_high && si->mmio_base_low) {
+		/*
+		 * There is no size of the memory resource in shared_info
+		 * so we assume that it is 4k here.
+		 */
+		res[nres].start = si->mmio_base_low;
+		res[nres].end = res[0].start + SZ_4K - 1;
+		res[nres++].flags = IORESOURCE_MEM;
+	}
+
+	if (si->gsi_interrupt) {
+		int irq = acpi_register_gsi(NULL, si->gsi_interrupt,
+					    si->interrupt_mode,
+					    si->interrupt_polarity);
+		res[nres].start = irq;
+		res[nres].end = irq;
+		res[nres++].flags = IORESOURCE_IRQ;
+	}
+
+	if (si->base_request_line || si->num_handshake_signals) {
+		/*
+		 * We pass the driver a DMA resource describing the range
+		 * of request lines the device supports.
+		 */
+		res[nres].start = si->base_request_line;
+		res[nres].end = res[nres].start + si->num_handshake_signals - 1;
+		res[nres++].flags = IORESOURCE_DMA;
+	}
+
+	ret = platform_device_add_resources(pdev, res, nres);
+	if (ret) {
+		if (si->gsi_interrupt)
+			acpi_unregister_gsi(si->gsi_interrupt);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int __init
+acpi_csrt_parse_resource_group(const struct acpi_csrt_group *grp)
+{
+	struct platform_device *pdev;
+	char vendor[5], name[16];
+	int ret, i;
+
+	vendor[0] = grp->vendor_id;
+	vendor[1] = grp->vendor_id >> 8;
+	vendor[2] = grp->vendor_id >> 16;
+	vendor[3] = grp->vendor_id >> 24;
+	vendor[4] = '\0';
+
+	if (grp->shared_info_length != sizeof(struct acpi_csrt_shared_info))
+		return -ENODEV;
+
+	snprintf(name, sizeof(name), "%s%04X", vendor, grp->device_id);
+	pdev = platform_device_alloc(name, PLATFORM_DEVID_AUTO);
+	if (!pdev)
+		return -ENOMEM;
+
+	/* Add resources based on the shared info */
+	ret = acpi_csrt_parse_shared_info(pdev, grp);
+	if (ret)
+		goto fail;
+
+	ret = platform_device_add(pdev);
+	if (ret)
+		goto fail;
+
+	for (i = 0; i < pdev->num_resources; i++)
+		dev_dbg(&pdev->dev, "%pR\n", &pdev->resource[i]);
+
+	return 0;
+
+fail:
+	platform_device_put(pdev);
+	return ret;
+}
+
+/*
+ * CSRT or Core System Resources Table is a proprietary ACPI table
+ * introduced by Microsoft. This table can contain devices that are not in
+ * the system DSDT table. In particular DMA controllers might be described
+ * here.
+ *
+ * We present these devices as normal platform devices that don't have ACPI
+ * IDs or handle. The platform device name will be something like
+ * <VENDOR><DEVID>.<n>.auto for example: INTL9C06.0.auto.
+ */
+void __init acpi_csrt_init(void)
+{
+	struct acpi_csrt_group *grp, *end;
+	struct acpi_table_csrt *csrt;
+	acpi_status status;
+	int ret;
+
+	status = acpi_get_table(ACPI_SIG_CSRT, 0,
+				(struct acpi_table_header **)&csrt);
+	if (ACPI_FAILURE(status)) {
+		if (status != AE_NOT_FOUND)
+			pr_warn("failed to get the CSRT table\n");
+		return;
+	}
+
+	pr_debug("parsing CSRT table for devices\n");
+
+	grp = (struct acpi_csrt_group *)(csrt + 1);
+	end = (struct acpi_csrt_group *)((void *)csrt + csrt->header.length);
+
+	while (grp < end) {
+		ret = acpi_csrt_parse_resource_group(grp);
+		if (ret) {
+			pr_warn("error in parsing resource group: %d\n", ret);
+			return;
+		}
+
+		grp = (struct acpi_csrt_group *)((void *)grp + grp->length);
+	}
+}

+ 6 - 1
drivers/acpi/internal.h

@@ -26,6 +26,7 @@
 int init_acpi_device_notify(void);
 int acpi_scan_init(void);
 int acpi_sysfs_init(void);
+void acpi_csrt_init(void);
 
 #ifdef CONFIG_DEBUG_FS
 extern struct dentry *acpi_debugfs_dir;
@@ -117,6 +118,10 @@ static inline void suspend_nvs_restore(void) {}
   -------------------------------------------------------------------------- */
 struct platform_device;
 
-struct platform_device *acpi_create_platform_device(struct acpi_device *adev);
+/* Flags for acpi_create_platform_device */
+#define ACPI_PLATFORM_CLK	BIT(0)
+
+struct platform_device *acpi_create_platform_device(struct acpi_device *adev,
+						    unsigned long flags);
 
 #endif /* _ACPI_INTERNAL_H_ */

+ 13 - 10
drivers/acpi/scan.c

@@ -38,14 +38,14 @@ static const struct acpi_device_id acpi_platform_device_ids[] = {
 	{ "PNP0D40" },
 
 	/* Haswell LPSS devices */
-	{ "INT33C0", 0 },
-	{ "INT33C1", 0 },
-	{ "INT33C2", 0 },
-	{ "INT33C3", 0 },
-	{ "INT33C4", 0 },
-	{ "INT33C5", 0 },
-	{ "INT33C6", 0 },
-	{ "INT33C7", 0 },
+	{ "INT33C0", ACPI_PLATFORM_CLK },
+	{ "INT33C1", ACPI_PLATFORM_CLK },
+	{ "INT33C2", ACPI_PLATFORM_CLK },
+	{ "INT33C3", ACPI_PLATFORM_CLK },
+	{ "INT33C4", ACPI_PLATFORM_CLK },
+	{ "INT33C5", ACPI_PLATFORM_CLK },
+	{ "INT33C6", ACPI_PLATFORM_CLK },
+	{ "INT33C7", ACPI_PLATFORM_CLK },
 
 	{ }
 };
@@ -1578,6 +1578,7 @@ static acpi_status acpi_bus_check_add(acpi_handle handle, u32 lvl_not_used,
 static acpi_status acpi_bus_device_attach(acpi_handle handle, u32 lvl_not_used,
 					  void *not_used, void **ret_not_used)
 {
+	const struct acpi_device_id *id;
 	acpi_status status = AE_OK;
 	struct acpi_device *device;
 	unsigned long long sta_not_used;
@@ -1593,9 +1594,10 @@ static acpi_status acpi_bus_device_attach(acpi_handle handle, u32 lvl_not_used,
 	if (acpi_bus_get_device(handle, &device))
 		return AE_CTRL_DEPTH;
 
-	if (!acpi_match_device_ids(device, acpi_platform_device_ids)) {
+	id = __acpi_match_device(device, acpi_platform_device_ids);
+	if (id) {
 		/* This is a known good platform device. */
-		acpi_create_platform_device(device);
+		acpi_create_platform_device(device, id->driver_data);
 	} else if (device_attach(&device->dev) < 0) {
 		status = AE_CTRL_DEPTH;
 	}
@@ -1717,6 +1719,7 @@ int __init acpi_scan_init(void)
 	}
 
 	acpi_pci_root_init();
+	acpi_csrt_init();
 
 	/*
 	 * Enumerate devices in the ACPI namespace.

+ 1 - 0
drivers/clk/Makefile

@@ -22,6 +22,7 @@ obj-$(CONFIG_ARCH_U8500)	+= ux500/
 obj-$(CONFIG_ARCH_VT8500)	+= clk-vt8500.o
 obj-$(CONFIG_ARCH_SUNXI)	+= clk-sunxi.o
 obj-$(CONFIG_ARCH_ZYNQ)		+= clk-zynq.o
+obj-$(CONFIG_X86)		+= x86/
 
 # Chip specific
 obj-$(CONFIG_COMMON_CLK_WM831X) += clk-wm831x.o

+ 2 - 0
drivers/clk/x86/Makefile

@@ -0,0 +1,2 @@
+clk-x86-lpss-objs		:= clk-lpss.o clk-lpt.o
+obj-$(CONFIG_X86_INTEL_LPSS)	+= clk-x86-lpss.o

+ 99 - 0
drivers/clk/x86/clk-lpss.c

@@ -0,0 +1,99 @@
+/*
+ * Intel Low Power Subsystem clocks.
+ *
+ * Copyright (C) 2013, Intel Corporation
+ * Authors: Mika Westerberg <mika.westerberg@linux.intel.com>
+ *	    Heikki Krogerus <heikki.krogerus@linux.intel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/acpi.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+
+static int clk_lpss_is_mmio_resource(struct acpi_resource *res, void *data)
+{
+	struct resource r;
+	return !acpi_dev_resource_memory(res, &r);
+}
+
+static acpi_status clk_lpss_find_mmio(acpi_handle handle, u32 level,
+				      void *data, void **retval)
+{
+	struct resource_list_entry *rentry;
+	struct list_head resource_list;
+	struct acpi_device *adev;
+	const char *uid = data;
+	int ret;
+
+	if (acpi_bus_get_device(handle, &adev))
+		return AE_OK;
+
+	if (uid) {
+		if (!adev->pnp.unique_id)
+			return AE_OK;
+		if (strcmp(uid, adev->pnp.unique_id))
+			return AE_OK;
+	}
+
+	INIT_LIST_HEAD(&resource_list);
+	ret = acpi_dev_get_resources(adev, &resource_list,
+				     clk_lpss_is_mmio_resource, NULL);
+	if (ret < 0)
+		return AE_NO_MEMORY;
+
+	list_for_each_entry(rentry, &resource_list, node)
+		if (resource_type(&rentry->res) == IORESOURCE_MEM) {
+			*(struct resource *)retval = rentry->res;
+			break;
+		}
+
+	acpi_dev_free_resource_list(&resource_list);
+	return AE_OK;
+}
+
+/**
+ * clk_register_lpss_gate - register LPSS clock gate
+ * @name: name of this clock gate
+ * @parent_name: parent clock name
+ * @hid: ACPI _HID of the device
+ * @uid: ACPI _UID of the device (optional)
+ * @offset: LPSS PRV_CLOCK_PARAMS offset
+ *
+ * Creates and registers LPSS clock gate.
+ */
+struct clk *clk_register_lpss_gate(const char *name, const char *parent_name,
+				   const char *hid, const char *uid,
+				   unsigned offset)
+{
+	struct resource res = { };
+	void __iomem *mmio_base;
+	acpi_status status;
+	struct clk *clk;
+
+	/*
+	 * First try to look the device and its mmio resource from the
+	 * ACPI namespace.
+	 */
+	status = acpi_get_devices(hid, clk_lpss_find_mmio, (void *)uid,
+				  (void **)&res);
+	if (ACPI_FAILURE(status) || !res.start)
+		return ERR_PTR(-ENODEV);
+
+	mmio_base = ioremap(res.start, resource_size(&res));
+	if (!mmio_base)
+		return ERR_PTR(-ENOMEM);
+
+	clk = clk_register_gate(NULL, name, parent_name, 0, mmio_base + offset,
+				0, 0, NULL);
+	if (IS_ERR(clk))
+		iounmap(mmio_base);
+
+	return clk;
+}

+ 36 - 0
drivers/clk/x86/clk-lpss.h

@@ -0,0 +1,36 @@
+/*
+ * Intel Low Power Subsystem clock.
+ *
+ * Copyright (C) 2013, Intel Corporation
+ * Authors: Mika Westerberg <mika.westerberg@linux.intel.com>
+ *	    Heikki Krogerus <heikki.krogerus@linux.intel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __CLK_LPSS_H
+#define __CLK_LPSS_H
+
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/clk.h>
+
+#ifdef CONFIG_ACPI
+extern struct clk *clk_register_lpss_gate(const char *name,
+					  const char *parent_name,
+					  const char *hid, const char *uid,
+					  unsigned offset);
+#else
+static inline struct clk *clk_register_lpss_gate(const char *name,
+						 const char *parent_name,
+						 const char *hid,
+						 const char *uid,
+						 unsigned offset)
+{
+	return ERR_PTR(-ENODEV);
+}
+#endif
+
+#endif /* __CLK_LPSS_H */

+ 86 - 0
drivers/clk/x86/clk-lpt.c

@@ -0,0 +1,86 @@
+/*
+ * Intel Lynxpoint LPSS clocks.
+ *
+ * Copyright (C) 2013, Intel Corporation
+ * Authors: Mika Westerberg <mika.westerberg@linux.intel.com>
+ *	    Heikki Krogerus <heikki.krogerus@linux.intel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/acpi.h>
+#include <linux/clk.h>
+#include <linux/clkdev.h>
+#include <linux/clk-provider.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include "clk-lpss.h"
+
+#define PRV_CLOCK_PARAMS 0x800
+
+static int lpt_clk_probe(struct platform_device *pdev)
+{
+	struct clk *clk;
+
+	/* LPSS free running clock */
+	clk = clk_register_fixed_rate(&pdev->dev, "lpss_clk", NULL, CLK_IS_ROOT,
+				      100000000);
+	if (IS_ERR(clk))
+		return PTR_ERR(clk);
+
+	/* Shared DMA clock */
+	clk_register_clkdev(clk, "hclk", "INTL9C60.0.auto");
+
+	/* SPI clocks */
+	clk = clk_register_lpss_gate("spi0_clk", "lpss_clk", "INT33C0", NULL,
+				     PRV_CLOCK_PARAMS);
+	if (!IS_ERR(clk))
+		clk_register_clkdev(clk, NULL, "INT33C0:00");
+
+	clk = clk_register_lpss_gate("spi1_clk", "lpss_clk", "INT33C1", NULL,
+				     PRV_CLOCK_PARAMS);
+	if (!IS_ERR(clk))
+		clk_register_clkdev(clk, NULL, "INT33C1:00");
+
+	/* I2C clocks */
+	clk = clk_register_lpss_gate("i2c0_clk", "lpss_clk", "INT33C2", NULL,
+				     PRV_CLOCK_PARAMS);
+	if (!IS_ERR(clk))
+		clk_register_clkdev(clk, NULL, "INT33C2:00");
+
+	clk = clk_register_lpss_gate("i2c1_clk", "lpss_clk", "INT33C3", NULL,
+				     PRV_CLOCK_PARAMS);
+	if (!IS_ERR(clk))
+		clk_register_clkdev(clk, NULL, "INT33C3:00");
+
+	/* UART clocks */
+	clk = clk_register_lpss_gate("uart0_clk", "lpss_clk", "INT33C4", NULL,
+				     PRV_CLOCK_PARAMS);
+	if (!IS_ERR(clk))
+		clk_register_clkdev(clk, NULL, "INT33C4:00");
+
+	clk = clk_register_lpss_gate("uart1_clk", "lpss_clk", "INT33C5", NULL,
+				     PRV_CLOCK_PARAMS);
+	if (!IS_ERR(clk))
+		clk_register_clkdev(clk, NULL, "INT33C5:00");
+
+	return 0;
+}
+
+static struct platform_driver lpt_clk_driver = {
+	.driver = {
+		.name = "clk-lpt",
+		.owner = THIS_MODULE,
+	},
+	.probe = lpt_clk_probe,
+};
+
+static int __init lpt_clk_init(void)
+{
+	return platform_driver_register(&lpt_clk_driver);
+}
+arch_initcall(lpt_clk_init);