mirror of
				git://git.openwrt.org/openwrt/openwrt.git
				synced 2025-10-31 05:54:26 -04:00 
			
		
		
		
	Targets were build tested and patches are refreshed. Signed-off-by: Luka Perkov <luka@openwrt.org> SVN-Revision: 42463
		
			
				
	
	
		
			873 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
			
		
		
	
	
			873 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
| From 3b5db9d024f173c30ef4060c31bb8e9fbd194cc1 Mon Sep 17 00:00:00 2001
 | |
| From: Hans de Goede <hdegoede@redhat.com>
 | |
| Date: Mon, 2 Dec 2013 16:13:32 +0100
 | |
| Subject: [PATCH] libahci: Allow drivers to override start_engine
 | |
| 
 | |
| Allwinner A10 and A20 ARM SoCs have an AHCI sata controller which needs a
 | |
| special register to be poked before starting the DMA engine.
 | |
| 
 | |
| This register gets reset on an ahci_stop_engine call, so there is no other
 | |
| place then ahci_start_engine where this poking can be done.
 | |
| 
 | |
| This commit allows drivers to override ahci_start_engine behavior for use by
 | |
| the Allwinner AHCI driver (and potentially other drivers in the future).
 | |
| 
 | |
| Signed-off-by: Hans de Goede <hdegoede@redhat.com>
 | |
| ---
 | |
|  drivers/ata/ahci.c          |  6 ++++--
 | |
|  drivers/ata/ahci.h          |  6 ++++++
 | |
|  drivers/ata/libahci.c       | 26 +++++++++++++++++++-------
 | |
|  drivers/ata/sata_highbank.c |  3 ++-
 | |
|  4 files changed, 31 insertions(+), 10 deletions(-)
 | |
| 
 | |
| --- a/drivers/ata/ahci.c
 | |
| +++ b/drivers/ata/ahci.c
 | |
| @@ -583,6 +583,7 @@ static int ahci_vt8251_hardreset(struct
 | |
|  				 unsigned long deadline)
 | |
|  {
 | |
|  	struct ata_port *ap = link->ap;
 | |
| +	struct ahci_host_priv *hpriv = ap->host->private_data;
 | |
|  	bool online;
 | |
|  	int rc;
 | |
|  
 | |
| @@ -593,7 +594,7 @@ static int ahci_vt8251_hardreset(struct
 | |
|  	rc = sata_link_hardreset(link, sata_ehc_deb_timing(&link->eh_context),
 | |
|  				 deadline, &online, NULL);
 | |
|  
 | |
| -	ahci_start_engine(ap);
 | |
| +	hpriv->start_engine(ap);
 | |
|  
 | |
|  	DPRINTK("EXIT, rc=%d, class=%u\n", rc, *class);
 | |
|  
 | |
| @@ -608,6 +609,7 @@ static int ahci_p5wdh_hardreset(struct a
 | |
|  {
 | |
|  	struct ata_port *ap = link->ap;
 | |
|  	struct ahci_port_priv *pp = ap->private_data;
 | |
| +	struct ahci_host_priv *hpriv = ap->host->private_data;
 | |
|  	u8 *d2h_fis = pp->rx_fis + RX_FIS_D2H_REG;
 | |
|  	struct ata_taskfile tf;
 | |
|  	bool online;
 | |
| @@ -623,7 +625,7 @@ static int ahci_p5wdh_hardreset(struct a
 | |
|  	rc = sata_link_hardreset(link, sata_ehc_deb_timing(&link->eh_context),
 | |
|  				 deadline, &online, NULL);
 | |
|  
 | |
| -	ahci_start_engine(ap);
 | |
| +	hpriv->start_engine(ap);
 | |
|  
 | |
|  	/* The pseudo configuration device on SIMG4726 attached to
 | |
|  	 * ASUS P5W-DH Deluxe doesn't send signature FIS after
 | |
| --- a/drivers/ata/ahci.h
 | |
| +++ b/drivers/ata/ahci.h
 | |
| @@ -37,6 +37,7 @@
 | |
|  
 | |
|  #include <linux/clk.h>
 | |
|  #include <linux/libata.h>
 | |
| +#include <linux/regulator/consumer.h>
 | |
|  
 | |
|  /* Enclosure Management Control */
 | |
|  #define EM_CTRL_MSG_TYPE              0x000f0000
 | |
| @@ -51,6 +52,7 @@
 | |
|  
 | |
|  enum {
 | |
|  	AHCI_MAX_PORTS		= 32,
 | |
| +	AHCI_MAX_CLKS		= 3,
 | |
|  	AHCI_MAX_SG		= 168, /* hardware max is 64K */
 | |
|  	AHCI_DMA_BOUNDARY	= 0xffffffff,
 | |
|  	AHCI_MAX_CMDS		= 32,
 | |
| @@ -322,8 +324,15 @@ struct ahci_host_priv {
 | |
|  	u32 			em_loc; /* enclosure management location */
 | |
|  	u32			em_buf_sz;	/* EM buffer size in byte */
 | |
|  	u32			em_msg_type;	/* EM message type */
 | |
| -	struct clk		*clk;		/* Only for platforms supporting clk */
 | |
| +	struct clk		*clks[AHCI_MAX_CLKS]; /* Optional */
 | |
| +	struct regulator	*target_pwr;	/* Optional */
 | |
|  	void			*plat_data;	/* Other platform data */
 | |
| +	/*
 | |
| +	 * Optional ahci_start_engine override, if not set this gets set to the
 | |
| +	 * default ahci_start_engine during ahci_save_initial_config, this can
 | |
| +	 * be overridden anytime before the host is activated.
 | |
| +	 */
 | |
| +	void			(*start_engine)(struct ata_port *ap);
 | |
|  };
 | |
|  
 | |
|  extern int ahci_ignore_sss;
 | |
| --- a/drivers/ata/libahci.c
 | |
| +++ b/drivers/ata/libahci.c
 | |
| @@ -394,6 +394,9 @@ static ssize_t ahci_show_em_supported(st
 | |
|   *
 | |
|   *	If inconsistent, config values are fixed up by this function.
 | |
|   *
 | |
| + *	If it is not set already this function sets hpriv->start_engine to
 | |
| + *	ahci_start_engine.
 | |
| + *
 | |
|   *	LOCKING:
 | |
|   *	None.
 | |
|   */
 | |
| @@ -500,6 +503,9 @@ void ahci_save_initial_config(struct dev
 | |
|  	hpriv->cap = cap;
 | |
|  	hpriv->cap2 = cap2;
 | |
|  	hpriv->port_map = port_map;
 | |
| +
 | |
| +	if (!hpriv->start_engine)
 | |
| +		hpriv->start_engine = ahci_start_engine;
 | |
|  }
 | |
|  EXPORT_SYMBOL_GPL(ahci_save_initial_config);
 | |
|  
 | |
| @@ -766,7 +772,7 @@ static void ahci_start_port(struct ata_p
 | |
|  
 | |
|  	/* enable DMA */
 | |
|  	if (!(hpriv->flags & AHCI_HFLAG_DELAY_ENGINE))
 | |
| -		ahci_start_engine(ap);
 | |
| +		hpriv->start_engine(ap);
 | |
|  
 | |
|  	/* turn on LEDs */
 | |
|  	if (ap->flags & ATA_FLAG_EM) {
 | |
| @@ -1234,7 +1240,7 @@ int ahci_kick_engine(struct ata_port *ap
 | |
|  
 | |
|  	/* restart engine */
 | |
|   out_restart:
 | |
| -	ahci_start_engine(ap);
 | |
| +	hpriv->start_engine(ap);
 | |
|  	return rc;
 | |
|  }
 | |
|  EXPORT_SYMBOL_GPL(ahci_kick_engine);
 | |
| @@ -1426,6 +1432,7 @@ static int ahci_hardreset(struct ata_lin
 | |
|  	const unsigned long *timing = sata_ehc_deb_timing(&link->eh_context);
 | |
|  	struct ata_port *ap = link->ap;
 | |
|  	struct ahci_port_priv *pp = ap->private_data;
 | |
| +	struct ahci_host_priv *hpriv = ap->host->private_data;
 | |
|  	u8 *d2h_fis = pp->rx_fis + RX_FIS_D2H_REG;
 | |
|  	struct ata_taskfile tf;
 | |
|  	bool online;
 | |
| @@ -1443,7 +1450,7 @@ static int ahci_hardreset(struct ata_lin
 | |
|  	rc = sata_link_hardreset(link, timing, deadline, &online,
 | |
|  				 ahci_check_ready);
 | |
|  
 | |
| -	ahci_start_engine(ap);
 | |
| +	hpriv->start_engine(ap);
 | |
|  
 | |
|  	if (online)
 | |
|  		*class = ahci_dev_classify(ap);
 | |
| @@ -2007,10 +2014,12 @@ static void ahci_thaw(struct ata_port *a
 | |
|  
 | |
|  void ahci_error_handler(struct ata_port *ap)
 | |
|  {
 | |
| +	struct ahci_host_priv *hpriv = ap->host->private_data;
 | |
| +
 | |
|  	if (!(ap->pflags & ATA_PFLAG_FROZEN)) {
 | |
|  		/* restart engine */
 | |
|  		ahci_stop_engine(ap);
 | |
| -		ahci_start_engine(ap);
 | |
| +		hpriv->start_engine(ap);
 | |
|  	}
 | |
|  
 | |
|  	sata_pmp_error_handler(ap);
 | |
| @@ -2031,6 +2040,7 @@ static void ahci_post_internal_cmd(struc
 | |
|  
 | |
|  static void ahci_set_aggressive_devslp(struct ata_port *ap, bool sleep)
 | |
|  {
 | |
| +	struct ahci_host_priv *hpriv = ap->host->private_data;
 | |
|  	void __iomem *port_mmio = ahci_port_base(ap);
 | |
|  	struct ata_device *dev = ap->link.device;
 | |
|  	u32 devslp, dm, dito, mdat, deto;
 | |
| @@ -2094,7 +2104,7 @@ static void ahci_set_aggressive_devslp(s
 | |
|  		   PORT_DEVSLP_ADSE);
 | |
|  	writel(devslp, port_mmio + PORT_DEVSLP);
 | |
|  
 | |
| -	ahci_start_engine(ap);
 | |
| +	hpriv->start_engine(ap);
 | |
|  
 | |
|  	/* enable device sleep feature for the drive */
 | |
|  	err_mask = ata_dev_set_feature(dev,
 | |
| @@ -2106,6 +2116,7 @@ static void ahci_set_aggressive_devslp(s
 | |
|  
 | |
|  static void ahci_enable_fbs(struct ata_port *ap)
 | |
|  {
 | |
| +	struct ahci_host_priv *hpriv = ap->host->private_data;
 | |
|  	struct ahci_port_priv *pp = ap->private_data;
 | |
|  	void __iomem *port_mmio = ahci_port_base(ap);
 | |
|  	u32 fbs;
 | |
| @@ -2134,11 +2145,12 @@ static void ahci_enable_fbs(struct ata_p
 | |
|  	} else
 | |
|  		dev_err(ap->host->dev, "Failed to enable FBS\n");
 | |
|  
 | |
| -	ahci_start_engine(ap);
 | |
| +	hpriv->start_engine(ap);
 | |
|  }
 | |
|  
 | |
|  static void ahci_disable_fbs(struct ata_port *ap)
 | |
|  {
 | |
| +	struct ahci_host_priv *hpriv = ap->host->private_data;
 | |
|  	struct ahci_port_priv *pp = ap->private_data;
 | |
|  	void __iomem *port_mmio = ahci_port_base(ap);
 | |
|  	u32 fbs;
 | |
| @@ -2166,7 +2178,7 @@ static void ahci_disable_fbs(struct ata_
 | |
|  		pp->fbs_enabled = false;
 | |
|  	}
 | |
|  
 | |
| -	ahci_start_engine(ap);
 | |
| +	hpriv->start_engine(ap);
 | |
|  }
 | |
|  
 | |
|  static void ahci_pmp_attach(struct ata_port *ap)
 | |
| --- a/drivers/ata/sata_highbank.c
 | |
| +++ b/drivers/ata/sata_highbank.c
 | |
| @@ -403,6 +403,7 @@ static int ahci_highbank_hardreset(struc
 | |
|  	static const unsigned long timing[] = { 5, 100, 500};
 | |
|  	struct ata_port *ap = link->ap;
 | |
|  	struct ahci_port_priv *pp = ap->private_data;
 | |
| +	struct ahci_host_priv *hpriv = ap->host->private_data;
 | |
|  	u8 *d2h_fis = pp->rx_fis + RX_FIS_D2H_REG;
 | |
|  	struct ata_taskfile tf;
 | |
|  	bool online;
 | |
| @@ -431,7 +432,7 @@ static int ahci_highbank_hardreset(struc
 | |
|  			break;
 | |
|  	} while (!online && retry--);
 | |
|  
 | |
| -	ahci_start_engine(ap);
 | |
| +	hpriv->start_engine(ap);
 | |
|  
 | |
|  	if (online)
 | |
|  		*class = ahci_dev_classify(ap);
 | |
| --- a/Documentation/devicetree/bindings/ata/ahci-platform.txt
 | |
| +++ b/Documentation/devicetree/bindings/ata/ahci-platform.txt
 | |
| @@ -10,6 +10,8 @@ Required properties:
 | |
|  
 | |
|  Optional properties:
 | |
|  - dma-coherent      : Present if dma operations are coherent
 | |
| +- clocks            : a list of phandle + clock specifier pairs
 | |
| +- target-supply     : regulator for SATA target power
 | |
|  
 | |
|  Example:
 | |
|          sata@ffe08000 {
 | |
| --- a/drivers/ata/ahci_platform.c
 | |
| +++ b/drivers/ata/ahci_platform.c
 | |
| @@ -87,78 +87,252 @@ static struct scsi_host_template ahci_pl
 | |
|  	AHCI_SHT("ahci_platform"),
 | |
|  };
 | |
|  
 | |
| -static int ahci_probe(struct platform_device *pdev)
 | |
| +/**
 | |
| + *	ahci_platform_enable_clks - Enable platform clocks
 | |
| + *	@hpriv: host private area to store config values
 | |
| + *
 | |
| + *	This function enables all the clks found in hpriv->clks, starting
 | |
| + *	at index 0. If any clk fails to enable it disables all the clks
 | |
| + *	already enabled in reverse order, and then returns an error.
 | |
| + *
 | |
| + *	LOCKING:
 | |
| + *	None.
 | |
| + *
 | |
| + *	RETURNS:
 | |
| + *	0 on success otherwise a negative error code
 | |
| + */
 | |
| +int ahci_platform_enable_clks(struct ahci_host_priv *hpriv)
 | |
|  {
 | |
| -	struct device *dev = &pdev->dev;
 | |
| -	struct ahci_platform_data *pdata = dev_get_platdata(dev);
 | |
| -	const struct platform_device_id *id = platform_get_device_id(pdev);
 | |
| -	struct ata_port_info pi = ahci_port_info[id ? id->driver_data : 0];
 | |
| -	const struct ata_port_info *ppi[] = { &pi, NULL };
 | |
| -	struct ahci_host_priv *hpriv;
 | |
| -	struct ata_host *host;
 | |
| -	struct resource *mem;
 | |
| -	int irq;
 | |
| -	int n_ports;
 | |
| -	int i;
 | |
| -	int rc;
 | |
| +	int c, rc;
 | |
|  
 | |
| -	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 | |
| -	if (!mem) {
 | |
| -		dev_err(dev, "no mmio space\n");
 | |
| -		return -EINVAL;
 | |
| +	for (c = 0; c < AHCI_MAX_CLKS && hpriv->clks[c]; c++) {
 | |
| +		rc = clk_prepare_enable(hpriv->clks[c]);
 | |
| +		if (rc)
 | |
| +			goto disable_unprepare_clk;
 | |
|  	}
 | |
| +	return 0;
 | |
|  
 | |
| -	irq = platform_get_irq(pdev, 0);
 | |
| -	if (irq <= 0) {
 | |
| -		dev_err(dev, "no irq\n");
 | |
| -		return -EINVAL;
 | |
| -	}
 | |
| +disable_unprepare_clk:
 | |
| +	while (--c >= 0)
 | |
| +		clk_disable_unprepare(hpriv->clks[c]);
 | |
| +	return rc;
 | |
| +}
 | |
| +EXPORT_SYMBOL_GPL(ahci_platform_enable_clks);
 | |
|  
 | |
| -	if (pdata && pdata->ata_port_info)
 | |
| -		pi = *pdata->ata_port_info;
 | |
| +/**
 | |
| + *	ahci_platform_disable_clks - Disable platform clocks
 | |
| + *	@hpriv: host private area to store config values
 | |
| + *
 | |
| + *	This function disables all the clks found in hpriv->clks, in reverse
 | |
| + *	order of ahci_platform_enable_clks (starting at the end of the array).
 | |
| + *
 | |
| + *	LOCKING:
 | |
| + *	None.
 | |
| + */
 | |
| +void ahci_platform_disable_clks(struct ahci_host_priv *hpriv)
 | |
| +{
 | |
| +	int c;
 | |
|  
 | |
| -	hpriv = devm_kzalloc(dev, sizeof(*hpriv), GFP_KERNEL);
 | |
| -	if (!hpriv) {
 | |
| -		dev_err(dev, "can't alloc ahci_host_priv\n");
 | |
| -		return -ENOMEM;
 | |
| +	for (c = AHCI_MAX_CLKS - 1; c >= 0; c--)
 | |
| +		if (hpriv->clks[c])
 | |
| +			clk_disable_unprepare(hpriv->clks[c]);
 | |
| +}
 | |
| +EXPORT_SYMBOL_GPL(ahci_platform_disable_clks);
 | |
| +
 | |
| +/**
 | |
| + *	ahci_platform_enable_resources - Enable platform resources
 | |
| + *	@hpriv: host private area to store config values
 | |
| + *
 | |
| + *	This function enables all ahci_platform managed resources in
 | |
| + *	the following order:
 | |
| + *	1) Regulator
 | |
| + *	2) Clocks (through ahci_platform_enable_clks)
 | |
| + *
 | |
| + *	If resource enabling fails at any point the previous enabled
 | |
| + *	resources are disabled in reverse order.
 | |
| + *
 | |
| + *	LOCKING:
 | |
| + *	None.
 | |
| + *
 | |
| + *	RETURNS:
 | |
| + *	0 on success otherwise a negative error code
 | |
| + */
 | |
| +int ahci_platform_enable_resources(struct ahci_host_priv *hpriv)
 | |
| +{
 | |
| +	int rc;
 | |
| +
 | |
| +	if (hpriv->target_pwr) {
 | |
| +		rc = regulator_enable(hpriv->target_pwr);
 | |
| +		if (rc)
 | |
| +			return rc;
 | |
|  	}
 | |
|  
 | |
| -	hpriv->flags |= (unsigned long)pi.private_data;
 | |
| +	rc = ahci_platform_enable_clks(hpriv);
 | |
| +	if (rc)
 | |
| +		goto disable_regulator;
 | |
| +
 | |
| +	return 0;
 | |
| +
 | |
| +disable_regulator:
 | |
| +	if (hpriv->target_pwr)
 | |
| +		regulator_disable(hpriv->target_pwr);
 | |
| +	return rc;
 | |
| +}
 | |
| +EXPORT_SYMBOL_GPL(ahci_platform_enable_resources);
 | |
| +
 | |
| +/**
 | |
| + *	ahci_platform_disable_resources - Disable platform resources
 | |
| + *	@hpriv: host private area to store config values
 | |
| + *
 | |
| + *	This function disables all ahci_platform managed resources in
 | |
| + *	the following order:
 | |
| + *	1) Clocks (through ahci_platform_disable_clks)
 | |
| + *	2) Regulator
 | |
| + *
 | |
| + *	LOCKING:
 | |
| + *	None.
 | |
| + */
 | |
| +void ahci_platform_disable_resources(struct ahci_host_priv *hpriv)
 | |
| +{
 | |
| +	ahci_platform_disable_clks(hpriv);
 | |
|  
 | |
| -	hpriv->mmio = devm_ioremap(dev, mem->start, resource_size(mem));
 | |
| +	if (hpriv->target_pwr)
 | |
| +		regulator_disable(hpriv->target_pwr);
 | |
| +}
 | |
| +EXPORT_SYMBOL_GPL(ahci_platform_disable_resources);
 | |
| +
 | |
| +static void ahci_platform_put_resources(struct device *dev, void *res)
 | |
| +{
 | |
| +	struct ahci_host_priv *hpriv = res;
 | |
| +	int c;
 | |
| +
 | |
| +	for (c = 0; c < AHCI_MAX_CLKS && hpriv->clks[c]; c++)
 | |
| +		clk_put(hpriv->clks[c]);
 | |
| +}
 | |
| +
 | |
| +/**
 | |
| + *	ahci_platform_get_resources - Get platform resources
 | |
| + *	@pdev: platform device to get resources for
 | |
| + *
 | |
| + *	This function allocates an ahci_host_priv struct, and gets the
 | |
| + *	following resources, storing a reference to them inside the returned
 | |
| + *	struct:
 | |
| + *
 | |
| + *	1) mmio registers (IORESOURCE_MEM 0, mandatory)
 | |
| + *	2) regulator for controlling the targets power (optional)
 | |
| + *	3) 0 - AHCI_MAX_CLKS clocks, as specified in the devs devicetree node,
 | |
| + *	   or for non devicetree enabled platforms a single clock
 | |
| + *
 | |
| + *	LOCKING:
 | |
| + *	None.
 | |
| + *
 | |
| + *	RETURNS:
 | |
| + *	The allocated ahci_host_priv on success, otherwise an ERR_PTR value
 | |
| + */
 | |
| +struct ahci_host_priv *ahci_platform_get_resources(
 | |
| +	struct platform_device *pdev)
 | |
| +{
 | |
| +	struct device *dev = &pdev->dev;
 | |
| +	struct ahci_host_priv *hpriv;
 | |
| +	struct clk *clk;
 | |
| +	int i, rc = -ENOMEM;
 | |
| +
 | |
| +	if (!devres_open_group(dev, NULL, GFP_KERNEL))
 | |
| +		return ERR_PTR(-ENOMEM);
 | |
| +
 | |
| +	hpriv = devres_alloc(ahci_platform_put_resources, sizeof(*hpriv),
 | |
| +			     GFP_KERNEL);
 | |
| +	if (!hpriv)
 | |
| +		goto err_out;
 | |
| +
 | |
| +	devres_add(dev, hpriv);
 | |
| +
 | |
| +	hpriv->mmio = devm_ioremap_resource(dev,
 | |
| +			      platform_get_resource(pdev, IORESOURCE_MEM, 0));
 | |
|  	if (!hpriv->mmio) {
 | |
| -		dev_err(dev, "can't map %pR\n", mem);
 | |
| -		return -ENOMEM;
 | |
| +		dev_err(dev, "no mmio space\n");
 | |
| +		goto err_out;
 | |
|  	}
 | |
|  
 | |
| -	hpriv->clk = clk_get(dev, NULL);
 | |
| -	if (IS_ERR(hpriv->clk)) {
 | |
| -		dev_err(dev, "can't get clock\n");
 | |
| -	} else {
 | |
| -		rc = clk_prepare_enable(hpriv->clk);
 | |
| -		if (rc) {
 | |
| -			dev_err(dev, "clock prepare enable failed");
 | |
| -			goto free_clk;
 | |
| +	hpriv->target_pwr = devm_regulator_get_optional(dev, "target");
 | |
| +	if (IS_ERR(hpriv->target_pwr)) {
 | |
| +		rc = PTR_ERR(hpriv->target_pwr);
 | |
| +		if (rc == -EPROBE_DEFER)
 | |
| +			goto err_out;
 | |
| +		hpriv->target_pwr = NULL;
 | |
| +	}
 | |
| +
 | |
| +	for (i = 0; i < AHCI_MAX_CLKS; i++) {
 | |
| +		/*
 | |
| +		 * For now we must use clk_get(dev, NULL) for the first clock,
 | |
| +		 * because some platforms (da850, spear13xx) are not yet
 | |
| +		 * converted to use devicetree for clocks.  For new platforms
 | |
| +		 * this is equivalent to of_clk_get(dev->of_node, 0).
 | |
| +		 */
 | |
| +		if (i == 0)
 | |
| +			clk = clk_get(dev, NULL);
 | |
| +		else
 | |
| +			clk = of_clk_get(dev->of_node, i);
 | |
| +
 | |
| +		if (IS_ERR(clk)) {
 | |
| +			rc = PTR_ERR(clk);
 | |
| +			if (rc == -EPROBE_DEFER)
 | |
| +				goto err_out;
 | |
| +			break;
 | |
|  		}
 | |
| +		hpriv->clks[i] = clk;
 | |
|  	}
 | |
|  
 | |
| -	/*
 | |
| -	 * Some platforms might need to prepare for mmio region access,
 | |
| -	 * which could be done in the following init call. So, the mmio
 | |
| -	 * region shouldn't be accessed before init (if provided) has
 | |
| -	 * returned successfully.
 | |
| -	 */
 | |
| -	if (pdata && pdata->init) {
 | |
| -		rc = pdata->init(dev, hpriv->mmio);
 | |
| -		if (rc)
 | |
| -			goto disable_unprepare_clk;
 | |
| -	}
 | |
| +	devres_remove_group(dev, NULL);
 | |
| +	return hpriv;
 | |
| +
 | |
| +err_out:
 | |
| +	devres_release_group(dev, NULL);
 | |
| +	return ERR_PTR(rc);
 | |
| +}
 | |
| +EXPORT_SYMBOL_GPL(ahci_platform_get_resources);
 | |
| +
 | |
| +/**
 | |
| + *	ahci_platform_init_host - Bring up an ahci-platform host
 | |
| + *	@pdev: platform device pointer for the host
 | |
| + *	@hpriv: ahci-host private data for the host
 | |
| + *	@pi_template: template for the ata_port_info to use
 | |
| + *	@force_port_map: param passed to ahci_save_initial_config
 | |
| + *	@mask_port_map: param passed to ahci_save_initial_config
 | |
| + *
 | |
| + *	This function does all the usual steps needed to bring up an
 | |
| + *	ahci-platform host, note any necessary resources (ie clks, phy, etc.)
 | |
| + *	must be initialized / enabled before calling this.
 | |
| + *
 | |
| + *	LOCKING:
 | |
| + *	None.
 | |
| + *
 | |
| + *	RETURNS:
 | |
| + *	0 on success otherwise a negative error code
 | |
| + */
 | |
| +int ahci_platform_init_host(struct platform_device *pdev,
 | |
| +			    struct ahci_host_priv *hpriv,
 | |
| +			    const struct ata_port_info *pi_template,
 | |
| +			    unsigned int force_port_map,
 | |
| +			    unsigned int mask_port_map)
 | |
| +{
 | |
| +	struct device *dev = &pdev->dev;
 | |
| +	struct ata_port_info pi = *pi_template;
 | |
| +	const struct ata_port_info *ppi[] = { &pi, NULL };
 | |
| +	struct ata_host *host;
 | |
| +	int i, irq, n_ports, rc;
 | |
|  
 | |
| -	ahci_save_initial_config(dev, hpriv,
 | |
| -		pdata ? pdata->force_port_map : 0,
 | |
| -		pdata ? pdata->mask_port_map  : 0);
 | |
| +	irq = platform_get_irq(pdev, 0);
 | |
| +	if (irq <= 0) {
 | |
| +		dev_err(dev, "no irq\n");
 | |
| +		return -EINVAL;
 | |
| +	}
 | |
|  
 | |
|  	/* prepare host */
 | |
| +	hpriv->flags |= (unsigned long)pi.private_data;
 | |
| +
 | |
| +	ahci_save_initial_config(dev, hpriv, force_port_map, mask_port_map);
 | |
| +
 | |
|  	if (hpriv->cap & HOST_CAP_NCQ)
 | |
|  		pi.flags |= ATA_FLAG_NCQ;
 | |
|  
 | |
| @@ -175,10 +349,8 @@ static int ahci_probe(struct platform_de
 | |
|  	n_ports = max(ahci_nr_ports(hpriv->cap), fls(hpriv->port_map));
 | |
|  
 | |
|  	host = ata_host_alloc_pinfo(dev, ppi, n_ports);
 | |
| -	if (!host) {
 | |
| -		rc = -ENOMEM;
 | |
| -		goto pdata_exit;
 | |
| -	}
 | |
| +	if (!host)
 | |
| +		return -ENOMEM;
 | |
|  
 | |
|  	host->private_data = hpriv;
 | |
|  
 | |
| @@ -193,7 +365,8 @@ static int ahci_probe(struct platform_de
 | |
|  	for (i = 0; i < host->n_ports; i++) {
 | |
|  		struct ata_port *ap = host->ports[i];
 | |
|  
 | |
| -		ata_port_desc(ap, "mmio %pR", mem);
 | |
| +		ata_port_desc(ap, "mmio %pR",
 | |
| +			      platform_get_resource(pdev, IORESOURCE_MEM, 0));
 | |
|  		ata_port_desc(ap, "port 0x%x", 0x100 + ap->port_no * 0x80);
 | |
|  
 | |
|  		/* set enclosure management message type */
 | |
| @@ -207,13 +380,53 @@ static int ahci_probe(struct platform_de
 | |
|  
 | |
|  	rc = ahci_reset_controller(host);
 | |
|  	if (rc)
 | |
| -		goto pdata_exit;
 | |
| +		return rc;
 | |
|  
 | |
|  	ahci_init_controller(host);
 | |
|  	ahci_print_info(host, "platform");
 | |
|  
 | |
| -	rc = ata_host_activate(host, irq, ahci_interrupt, IRQF_SHARED,
 | |
| -			       &ahci_platform_sht);
 | |
| +	return ata_host_activate(host, irq, ahci_interrupt, IRQF_SHARED,
 | |
| +				 &ahci_platform_sht);
 | |
| +}
 | |
| +EXPORT_SYMBOL_GPL(ahci_platform_init_host);
 | |
| +
 | |
| +static int ahci_probe(struct platform_device *pdev)
 | |
| +{
 | |
| +	struct device *dev = &pdev->dev;
 | |
| +	struct ahci_platform_data *pdata = dev_get_platdata(dev);
 | |
| +	const struct platform_device_id *id = platform_get_device_id(pdev);
 | |
| +	const struct ata_port_info *pi_template;
 | |
| +	struct ahci_host_priv *hpriv;
 | |
| +	int rc;
 | |
| +
 | |
| +	hpriv = ahci_platform_get_resources(pdev);
 | |
| +	if (IS_ERR(hpriv))
 | |
| +		return PTR_ERR(hpriv);
 | |
| +
 | |
| +	rc = ahci_platform_enable_resources(hpriv);
 | |
| +	if (rc)
 | |
| +		return rc;
 | |
| +
 | |
| +	/*
 | |
| +	 * Some platforms might need to prepare for mmio region access,
 | |
| +	 * which could be done in the following init call. So, the mmio
 | |
| +	 * region shouldn't be accessed before init (if provided) has
 | |
| +	 * returned successfully.
 | |
| +	 */
 | |
| +	if (pdata && pdata->init) {
 | |
| +		rc = pdata->init(dev, hpriv->mmio);
 | |
| +		if (rc)
 | |
| +			goto disable_resources;
 | |
| +	}
 | |
| +
 | |
| +	if (pdata && pdata->ata_port_info)
 | |
| +		pi_template = pdata->ata_port_info;
 | |
| +	else
 | |
| +		pi_template = &ahci_port_info[id ? id->driver_data : 0];
 | |
| +
 | |
| +	rc = ahci_platform_init_host(pdev, hpriv, pi_template,
 | |
| +				     pdata ? pdata->force_port_map : 0,
 | |
| +				     pdata ? pdata->mask_port_map  : 0);
 | |
|  	if (rc)
 | |
|  		goto pdata_exit;
 | |
|  
 | |
| @@ -221,12 +434,8 @@ static int ahci_probe(struct platform_de
 | |
|  pdata_exit:
 | |
|  	if (pdata && pdata->exit)
 | |
|  		pdata->exit(dev);
 | |
| -disable_unprepare_clk:
 | |
| -	if (!IS_ERR(hpriv->clk))
 | |
| -		clk_disable_unprepare(hpriv->clk);
 | |
| -free_clk:
 | |
| -	if (!IS_ERR(hpriv->clk))
 | |
| -		clk_put(hpriv->clk);
 | |
| +disable_resources:
 | |
| +	ahci_platform_disable_resources(hpriv);
 | |
|  	return rc;
 | |
|  }
 | |
|  
 | |
| @@ -239,21 +448,30 @@ static void ahci_host_stop(struct ata_ho
 | |
|  	if (pdata && pdata->exit)
 | |
|  		pdata->exit(dev);
 | |
|  
 | |
| -	if (!IS_ERR(hpriv->clk)) {
 | |
| -		clk_disable_unprepare(hpriv->clk);
 | |
| -		clk_put(hpriv->clk);
 | |
| -	}
 | |
| +	ahci_platform_disable_resources(hpriv);
 | |
|  }
 | |
|  
 | |
|  #ifdef CONFIG_PM_SLEEP
 | |
| -static int ahci_suspend(struct device *dev)
 | |
| +/**
 | |
| + *	ahci_platform_suspend_host - Suspend an ahci-platform host
 | |
| + *	@dev: device pointer for the host
 | |
| + *
 | |
| + *	This function does all the usual steps needed to suspend an
 | |
| + *	ahci-platform host, note any necessary resources (ie clks, phy, etc.)
 | |
| + *	must be disabled after calling this.
 | |
| + *
 | |
| + *	LOCKING:
 | |
| + *	None.
 | |
| + *
 | |
| + *	RETURNS:
 | |
| + *	0 on success otherwise a negative error code
 | |
| + */
 | |
| +int ahci_platform_suspend_host(struct device *dev)
 | |
|  {
 | |
| -	struct ahci_platform_data *pdata = dev_get_platdata(dev);
 | |
|  	struct ata_host *host = dev_get_drvdata(dev);
 | |
|  	struct ahci_host_priv *hpriv = host->private_data;
 | |
|  	void __iomem *mmio = hpriv->mmio;
 | |
|  	u32 ctl;
 | |
| -	int rc;
 | |
|  
 | |
|  	if (hpriv->flags & AHCI_HFLAG_NO_SUSPEND) {
 | |
|  		dev_err(dev, "firmware update required for suspend/resume\n");
 | |
| @@ -270,61 +488,122 @@ static int ahci_suspend(struct device *d
 | |
|  	writel(ctl, mmio + HOST_CTL);
 | |
|  	readl(mmio + HOST_CTL); /* flush */
 | |
|  
 | |
| -	rc = ata_host_suspend(host, PMSG_SUSPEND);
 | |
| +	return ata_host_suspend(host, PMSG_SUSPEND);
 | |
| +}
 | |
| +EXPORT_SYMBOL_GPL(ahci_platform_suspend_host);
 | |
| +
 | |
| +/**
 | |
| + *	ahci_platform_resume_host - Resume an ahci-platform host
 | |
| + *	@dev: device pointer for the host
 | |
| + *
 | |
| + *	This function does all the usual steps needed to resume an
 | |
| + *	ahci-platform host, note any necessary resources (ie clks, phy, etc.)
 | |
| + *	must be initialized / enabled before calling this.
 | |
| + *
 | |
| + *	LOCKING:
 | |
| + *	None.
 | |
| + *
 | |
| + *	RETURNS:
 | |
| + *	0 on success otherwise a negative error code
 | |
| + */
 | |
| +int ahci_platform_resume_host(struct device *dev)
 | |
| +{
 | |
| +	struct ata_host *host = dev_get_drvdata(dev);
 | |
| +	int rc;
 | |
| +
 | |
| +	if (dev->power.power_state.event == PM_EVENT_SUSPEND) {
 | |
| +		rc = ahci_reset_controller(host);
 | |
| +		if (rc)
 | |
| +			return rc;
 | |
| +
 | |
| +		ahci_init_controller(host);
 | |
| +	}
 | |
| +
 | |
| +	ata_host_resume(host);
 | |
| +
 | |
| +	return 0;
 | |
| +}
 | |
| +EXPORT_SYMBOL_GPL(ahci_platform_resume_host);
 | |
| +
 | |
| +/**
 | |
| + *	ahci_platform_suspend - Suspend an ahci-platform device
 | |
| + *	@dev: the platform device to suspend
 | |
| + *
 | |
| + *	This function suspends the host associated with the device, followed
 | |
| + *	by disabling all the resources of the device.
 | |
| + *
 | |
| + *	LOCKING:
 | |
| + *	None.
 | |
| + *
 | |
| + *	RETURNS:
 | |
| + *	0 on success otherwise a negative error code
 | |
| + */
 | |
| +int ahci_platform_suspend(struct device *dev)
 | |
| +{
 | |
| +	struct ahci_platform_data *pdata = dev_get_platdata(dev);
 | |
| +	struct ata_host *host = dev_get_drvdata(dev);
 | |
| +	struct ahci_host_priv *hpriv = host->private_data;
 | |
| +	int rc;
 | |
| +
 | |
| +	rc = ahci_platform_suspend_host(dev);
 | |
|  	if (rc)
 | |
|  		return rc;
 | |
|  
 | |
|  	if (pdata && pdata->suspend)
 | |
|  		return pdata->suspend(dev);
 | |
|  
 | |
| -	if (!IS_ERR(hpriv->clk))
 | |
| -		clk_disable_unprepare(hpriv->clk);
 | |
| +	ahci_platform_disable_resources(hpriv);
 | |
|  
 | |
|  	return 0;
 | |
|  }
 | |
| +EXPORT_SYMBOL_GPL(ahci_platform_suspend);
 | |
|  
 | |
| -static int ahci_resume(struct device *dev)
 | |
| +/**
 | |
| + *	ahci_platform_resume - Resume an ahci-platform device
 | |
| + *	@dev: the platform device to resume
 | |
| + *
 | |
| + *	This function enables all the resources of the device followed by
 | |
| + *	resuming the host associated with the device.
 | |
| + *
 | |
| + *	LOCKING:
 | |
| + *	None.
 | |
| + *
 | |
| + *	RETURNS:
 | |
| + *	0 on success otherwise a negative error code
 | |
| + */
 | |
| +int ahci_platform_resume(struct device *dev)
 | |
|  {
 | |
|  	struct ahci_platform_data *pdata = dev_get_platdata(dev);
 | |
|  	struct ata_host *host = dev_get_drvdata(dev);
 | |
|  	struct ahci_host_priv *hpriv = host->private_data;
 | |
|  	int rc;
 | |
|  
 | |
| -	if (!IS_ERR(hpriv->clk)) {
 | |
| -		rc = clk_prepare_enable(hpriv->clk);
 | |
| -		if (rc) {
 | |
| -			dev_err(dev, "clock prepare enable failed");
 | |
| -			return rc;
 | |
| -		}
 | |
| -	}
 | |
| +	rc = ahci_platform_enable_resources(hpriv);
 | |
| +	if (rc)
 | |
| +		return rc;
 | |
|  
 | |
|  	if (pdata && pdata->resume) {
 | |
|  		rc = pdata->resume(dev);
 | |
|  		if (rc)
 | |
| -			goto disable_unprepare_clk;
 | |
| -	}
 | |
| -
 | |
| -	if (dev->power.power_state.event == PM_EVENT_SUSPEND) {
 | |
| -		rc = ahci_reset_controller(host);
 | |
| -		if (rc)
 | |
| -			goto disable_unprepare_clk;
 | |
| -
 | |
| -		ahci_init_controller(host);
 | |
| +			goto disable_resources;
 | |
|  	}
 | |
|  
 | |
| -	ata_host_resume(host);
 | |
| +	rc = ahci_platform_resume_host(dev);
 | |
| +	if (rc)
 | |
| +		goto disable_resources;
 | |
|  
 | |
|  	return 0;
 | |
|  
 | |
| -disable_unprepare_clk:
 | |
| -	if (!IS_ERR(hpriv->clk))
 | |
| -		clk_disable_unprepare(hpriv->clk);
 | |
| +disable_resources:
 | |
| +	ahci_platform_disable_resources(hpriv);
 | |
|  
 | |
|  	return rc;
 | |
|  }
 | |
| +EXPORT_SYMBOL_GPL(ahci_platform_resume);
 | |
|  #endif
 | |
|  
 | |
| -static SIMPLE_DEV_PM_OPS(ahci_pm_ops, ahci_suspend, ahci_resume);
 | |
| +static SIMPLE_DEV_PM_OPS(ahci_pm_ops, ahci_platform_suspend,
 | |
| +			 ahci_platform_resume);
 | |
|  
 | |
|  static const struct of_device_id ahci_of_match[] = {
 | |
|  	{ .compatible = "snps,spear-ahci", },
 | |
| --- a/include/linux/ahci_platform.h
 | |
| +++ b/include/linux/ahci_platform.h
 | |
| @@ -19,7 +19,15 @@
 | |
|  
 | |
|  struct device;
 | |
|  struct ata_port_info;
 | |
| +struct ahci_host_priv;
 | |
| +struct platform_device;
 | |
|  
 | |
| +/*
 | |
| + * Note ahci_platform_data is deprecated, it is only kept around for use
 | |
| + * by the old da850 and spear13xx ahci code.
 | |
| + * New drivers should instead declare their own platform_driver struct, and
 | |
| + * use ahci_platform* functions in their own probe, suspend and resume methods.
 | |
| + */
 | |
|  struct ahci_platform_data {
 | |
|  	int (*init)(struct device *dev, void __iomem *addr);
 | |
|  	void (*exit)(struct device *dev);
 | |
| @@ -30,4 +38,21 @@ struct ahci_platform_data {
 | |
|  	unsigned int mask_port_map;
 | |
|  };
 | |
|  
 | |
| +int ahci_platform_enable_clks(struct ahci_host_priv *hpriv);
 | |
| +void ahci_platform_disable_clks(struct ahci_host_priv *hpriv);
 | |
| +int ahci_platform_enable_resources(struct ahci_host_priv *hpriv);
 | |
| +void ahci_platform_disable_resources(struct ahci_host_priv *hpriv);
 | |
| +struct ahci_host_priv *ahci_platform_get_resources(
 | |
| +	struct platform_device *pdev);
 | |
| +int ahci_platform_init_host(struct platform_device *pdev,
 | |
| +			    struct ahci_host_priv *hpriv,
 | |
| +			    const struct ata_port_info *pi_template,
 | |
| +			    unsigned int force_port_map,
 | |
| +			    unsigned int mask_port_map);
 | |
| +
 | |
| +int ahci_platform_suspend_host(struct device *dev);
 | |
| +int ahci_platform_resume_host(struct device *dev);
 | |
| +int ahci_platform_suspend(struct device *dev);
 | |
| +int ahci_platform_resume(struct device *dev);
 | |
| +
 | |
|  #endif /* _AHCI_PLATFORM_H */
 |