static int sun8i_a33_mbus_probe()

in sun8i-a33-mbus.c [331:459]


static int sun8i_a33_mbus_probe(struct platform_device *pdev)
{
	const struct sun8i_a33_mbus_variant *variant;
	struct device *dev = &pdev->dev;
	struct sun8i_a33_mbus *priv;
	unsigned long base_freq;
	unsigned int max_state;
	const char *err;
	int i, ret;

	variant = device_get_match_data(dev);
	if (!variant)
		return -EINVAL;

	max_state = variant->max_dram_divider - variant->min_dram_divider + 1;

	priv = devm_kzalloc(dev, struct_size(priv, freq_table, max_state), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	platform_set_drvdata(pdev, priv);

	priv->variant = variant;

	priv->reg_dram = devm_platform_ioremap_resource_byname(pdev, "dram");
	if (IS_ERR(priv->reg_dram))
		return PTR_ERR(priv->reg_dram);

	priv->reg_mbus = devm_platform_ioremap_resource_byname(pdev, "mbus");
	if (IS_ERR(priv->reg_mbus))
		return PTR_ERR(priv->reg_mbus);

	priv->clk_bus = devm_clk_get(dev, "bus");
	if (IS_ERR(priv->clk_bus))
		return dev_err_probe(dev, PTR_ERR(priv->clk_bus),
				     "failed to get bus clock\n");

	priv->clk_dram = devm_clk_get(dev, "dram");
	if (IS_ERR(priv->clk_dram))
		return dev_err_probe(dev, PTR_ERR(priv->clk_dram),
				     "failed to get dram clock\n");

	priv->clk_mbus = devm_clk_get(dev, "mbus");
	if (IS_ERR(priv->clk_mbus))
		return dev_err_probe(dev, PTR_ERR(priv->clk_mbus),
				     "failed to get mbus clock\n");

	ret = clk_prepare_enable(priv->clk_bus);
	if (ret)
		return dev_err_probe(dev, ret,
				     "failed to enable bus clock\n");

	/* Lock the DRAM clock rate to keep priv->nominal_bw in sync. */
	ret = clk_rate_exclusive_get(priv->clk_dram);
	if (ret) {
		err = "failed to lock dram clock rate\n";
		goto err_disable_bus;
	}

	/* Lock the MBUS clock rate to keep MBUS_TMR_PERIOD in sync. */
	ret = clk_rate_exclusive_get(priv->clk_mbus);
	if (ret) {
		err = "failed to lock mbus clock rate\n";
		goto err_unlock_dram;
	}

	priv->gov_data.upthreshold	= 10;
	priv->gov_data.downdifferential	=  5;

	priv->profile.initial_freq	= clk_get_rate(priv->clk_dram);
	priv->profile.polling_ms	= 1000;
	priv->profile.target		= sun8i_a33_mbus_set_dram_target;
	priv->profile.get_dev_status	= sun8i_a33_mbus_get_dram_status;
	priv->profile.freq_table	= priv->freq_table;
	priv->profile.max_state		= max_state;

	ret = devm_pm_opp_set_clkname(dev, "dram");
	if (ret) {
		err = "failed to add OPP table\n";
		goto err_unlock_mbus;
	}

	base_freq = clk_get_rate(clk_get_parent(priv->clk_dram));
	for (i = 0; i < max_state; ++i) {
		unsigned int div = variant->max_dram_divider - i;

		priv->freq_table[i] = base_freq / div;

		ret = dev_pm_opp_add(dev, priv->freq_table[i], 0);
		if (ret) {
			err = "failed to add OPPs\n";
			goto err_remove_opps;
		}
	}

	ret = sun8i_a33_mbus_hw_init(dev, priv, priv->profile.initial_freq);
	if (ret) {
		err = "failed to init hardware\n";
		goto err_remove_opps;
	}

	priv->devfreq_dram = devfreq_add_device(dev, &priv->profile,
						DEVFREQ_GOV_SIMPLE_ONDEMAND,
						&priv->gov_data);
	if (IS_ERR(priv->devfreq_dram)) {
		ret = PTR_ERR(priv->devfreq_dram);
		err = "failed to add devfreq device\n";
		goto err_remove_opps;
	}

	/*
	 * This must be set manually after registering the devfreq device,
	 * because there is no way to select a dynamic OPP as the suspend OPP.
	 */
	priv->devfreq_dram->suspend_freq = priv->freq_table[0];

	return 0;

err_remove_opps:
	dev_pm_opp_remove_all_dynamic(dev);
err_unlock_mbus:
	clk_rate_exclusive_put(priv->clk_mbus);
err_unlock_dram:
	clk_rate_exclusive_put(priv->clk_dram);
err_disable_bus:
	clk_disable_unprepare(priv->clk_bus);

	return dev_err_probe(dev, ret, err);
}