i2c: tegra: Move clk_prepare/clk_set_rate to probe
Currently the i2c-tegra bus driver prepares, enables and set_rates its clocks separately for each transfer. This causes locking problems when doing I2C transfers from clock notifiers; see http://lists.infradead.org/pipermail/linux-arm-kernel/2014-July/268653.html This patch moves clk_prepare/unprepare and clk_set_rate calls to the probe function, leaving only clk_enable/disable to be done on each transfer. This solves the locking issue. Signed-off-by: Mikko Perttunen <mperttunen@nvidia.com> Reviewed-by: Stephen Warren <swarren@nvidia.com> Signed-off-by: Wolfram Sang <wsa@the-dreams.de>
This commit is contained in:
		
					parent
					
						
							
								9e82bf0141
							
						
					
				
			
			
				commit
				
					
						c9a9ef4170
					
				
			
		
					 1 changed files with 45 additions and 12 deletions
				
			
		| 
						 | 
					@ -380,34 +380,33 @@ static inline int tegra_i2c_clock_enable(struct tegra_i2c_dev *i2c_dev)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	int ret;
 | 
						int ret;
 | 
				
			||||||
	if (!i2c_dev->hw->has_single_clk_source) {
 | 
						if (!i2c_dev->hw->has_single_clk_source) {
 | 
				
			||||||
		ret = clk_prepare_enable(i2c_dev->fast_clk);
 | 
							ret = clk_enable(i2c_dev->fast_clk);
 | 
				
			||||||
		if (ret < 0) {
 | 
							if (ret < 0) {
 | 
				
			||||||
			dev_err(i2c_dev->dev,
 | 
								dev_err(i2c_dev->dev,
 | 
				
			||||||
				"Enabling fast clk failed, err %d\n", ret);
 | 
									"Enabling fast clk failed, err %d\n", ret);
 | 
				
			||||||
			return ret;
 | 
								return ret;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	ret = clk_prepare_enable(i2c_dev->div_clk);
 | 
						ret = clk_enable(i2c_dev->div_clk);
 | 
				
			||||||
	if (ret < 0) {
 | 
						if (ret < 0) {
 | 
				
			||||||
		dev_err(i2c_dev->dev,
 | 
							dev_err(i2c_dev->dev,
 | 
				
			||||||
			"Enabling div clk failed, err %d\n", ret);
 | 
								"Enabling div clk failed, err %d\n", ret);
 | 
				
			||||||
		clk_disable_unprepare(i2c_dev->fast_clk);
 | 
							clk_disable(i2c_dev->fast_clk);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return ret;
 | 
						return ret;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static inline void tegra_i2c_clock_disable(struct tegra_i2c_dev *i2c_dev)
 | 
					static inline void tegra_i2c_clock_disable(struct tegra_i2c_dev *i2c_dev)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	clk_disable_unprepare(i2c_dev->div_clk);
 | 
						clk_disable(i2c_dev->div_clk);
 | 
				
			||||||
	if (!i2c_dev->hw->has_single_clk_source)
 | 
						if (!i2c_dev->hw->has_single_clk_source)
 | 
				
			||||||
		clk_disable_unprepare(i2c_dev->fast_clk);
 | 
							clk_disable(i2c_dev->fast_clk);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev)
 | 
					static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	u32 val;
 | 
						u32 val;
 | 
				
			||||||
	int err = 0;
 | 
						int err = 0;
 | 
				
			||||||
	int clk_multiplier = I2C_CLK_MULTIPLIER_STD_FAST_MODE;
 | 
					 | 
				
			||||||
	u32 clk_divisor;
 | 
						u32 clk_divisor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err = tegra_i2c_clock_enable(i2c_dev);
 | 
						err = tegra_i2c_clock_enable(i2c_dev);
 | 
				
			||||||
| 
						 | 
					@ -428,9 +427,6 @@ static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev)
 | 
				
			||||||
	i2c_writel(i2c_dev, val, I2C_CNFG);
 | 
						i2c_writel(i2c_dev, val, I2C_CNFG);
 | 
				
			||||||
	i2c_writel(i2c_dev, 0, I2C_INT_MASK);
 | 
						i2c_writel(i2c_dev, 0, I2C_INT_MASK);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	clk_multiplier *= (i2c_dev->hw->clk_divisor_std_fast_mode + 1);
 | 
					 | 
				
			||||||
	clk_set_rate(i2c_dev->div_clk, i2c_dev->bus_clk_rate * clk_multiplier);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/* Make sure clock divisor programmed correctly */
 | 
						/* Make sure clock divisor programmed correctly */
 | 
				
			||||||
	clk_divisor = i2c_dev->hw->clk_divisor_hs_mode;
 | 
						clk_divisor = i2c_dev->hw->clk_divisor_hs_mode;
 | 
				
			||||||
	clk_divisor |= i2c_dev->hw->clk_divisor_std_fast_mode <<
 | 
						clk_divisor |= i2c_dev->hw->clk_divisor_std_fast_mode <<
 | 
				
			||||||
| 
						 | 
					@ -712,6 +708,7 @@ static int tegra_i2c_probe(struct platform_device *pdev)
 | 
				
			||||||
	void __iomem *base;
 | 
						void __iomem *base;
 | 
				
			||||||
	int irq;
 | 
						int irq;
 | 
				
			||||||
	int ret = 0;
 | 
						int ret = 0;
 | 
				
			||||||
 | 
						int clk_multiplier = I2C_CLK_MULTIPLIER_STD_FAST_MODE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 | 
						res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 | 
				
			||||||
	base = devm_ioremap_resource(&pdev->dev, res);
 | 
						base = devm_ioremap_resource(&pdev->dev, res);
 | 
				
			||||||
| 
						 | 
					@ -777,17 +774,39 @@ static int tegra_i2c_probe(struct platform_device *pdev)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	platform_set_drvdata(pdev, i2c_dev);
 | 
						platform_set_drvdata(pdev, i2c_dev);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!i2c_dev->hw->has_single_clk_source) {
 | 
				
			||||||
 | 
							ret = clk_prepare(i2c_dev->fast_clk);
 | 
				
			||||||
 | 
							if (ret < 0) {
 | 
				
			||||||
 | 
								dev_err(i2c_dev->dev, "Clock prepare failed %d\n", ret);
 | 
				
			||||||
 | 
								return ret;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						clk_multiplier *= (i2c_dev->hw->clk_divisor_std_fast_mode + 1);
 | 
				
			||||||
 | 
						ret = clk_set_rate(i2c_dev->div_clk,
 | 
				
			||||||
 | 
								   i2c_dev->bus_clk_rate * clk_multiplier);
 | 
				
			||||||
 | 
						if (ret) {
 | 
				
			||||||
 | 
							dev_err(i2c_dev->dev, "Clock rate change failed %d\n", ret);
 | 
				
			||||||
 | 
							goto unprepare_fast_clk;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ret = clk_prepare(i2c_dev->div_clk);
 | 
				
			||||||
 | 
						if (ret < 0) {
 | 
				
			||||||
 | 
							dev_err(i2c_dev->dev, "Clock prepare failed %d\n", ret);
 | 
				
			||||||
 | 
							goto unprepare_fast_clk;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ret = tegra_i2c_init(i2c_dev);
 | 
						ret = tegra_i2c_init(i2c_dev);
 | 
				
			||||||
	if (ret) {
 | 
						if (ret) {
 | 
				
			||||||
		dev_err(&pdev->dev, "Failed to initialize i2c controller");
 | 
							dev_err(&pdev->dev, "Failed to initialize i2c controller");
 | 
				
			||||||
		return ret;
 | 
							goto unprepare_div_clk;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ret = devm_request_irq(&pdev->dev, i2c_dev->irq,
 | 
						ret = devm_request_irq(&pdev->dev, i2c_dev->irq,
 | 
				
			||||||
			tegra_i2c_isr, 0, dev_name(&pdev->dev), i2c_dev);
 | 
								tegra_i2c_isr, 0, dev_name(&pdev->dev), i2c_dev);
 | 
				
			||||||
	if (ret) {
 | 
						if (ret) {
 | 
				
			||||||
		dev_err(&pdev->dev, "Failed to request irq %i\n", i2c_dev->irq);
 | 
							dev_err(&pdev->dev, "Failed to request irq %i\n", i2c_dev->irq);
 | 
				
			||||||
		return ret;
 | 
							goto unprepare_div_clk;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	i2c_set_adapdata(&i2c_dev->adapter, i2c_dev);
 | 
						i2c_set_adapdata(&i2c_dev->adapter, i2c_dev);
 | 
				
			||||||
| 
						 | 
					@ -803,16 +822,30 @@ static int tegra_i2c_probe(struct platform_device *pdev)
 | 
				
			||||||
	ret = i2c_add_numbered_adapter(&i2c_dev->adapter);
 | 
						ret = i2c_add_numbered_adapter(&i2c_dev->adapter);
 | 
				
			||||||
	if (ret) {
 | 
						if (ret) {
 | 
				
			||||||
		dev_err(&pdev->dev, "Failed to add I2C adapter\n");
 | 
							dev_err(&pdev->dev, "Failed to add I2C adapter\n");
 | 
				
			||||||
		return ret;
 | 
							goto unprepare_div_clk;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					unprepare_div_clk:
 | 
				
			||||||
 | 
						clk_unprepare(i2c_dev->div_clk);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					unprepare_fast_clk:
 | 
				
			||||||
 | 
						if (!i2c_dev->hw->has_single_clk_source)
 | 
				
			||||||
 | 
							clk_unprepare(i2c_dev->fast_clk);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ret;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int tegra_i2c_remove(struct platform_device *pdev)
 | 
					static int tegra_i2c_remove(struct platform_device *pdev)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct tegra_i2c_dev *i2c_dev = platform_get_drvdata(pdev);
 | 
						struct tegra_i2c_dev *i2c_dev = platform_get_drvdata(pdev);
 | 
				
			||||||
	i2c_del_adapter(&i2c_dev->adapter);
 | 
						i2c_del_adapter(&i2c_dev->adapter);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						clk_unprepare(i2c_dev->div_clk);
 | 
				
			||||||
 | 
						if (!i2c_dev->hw->has_single_clk_source)
 | 
				
			||||||
 | 
							clk_unprepare(i2c_dev->fast_clk);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue