lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date:	Wed, 12 Nov 2014 18:25:00 +0200
From:	Mikko Perttunen <mikko.perttunen@...si.fi>
To:	Thierry Reding <thierry.reding@...il.com>,
	Tomeu Vizoso <tomeu.vizoso@...labora.com>
CC:	linux-tegra@...r.kernel.org,
	Javier Martinez Canillas <javier.martinez@...labora.co.uk>,
	acourbot@...dia.com, Mikko Perttunen <mperttunen@...dia.com>,
	Stephen Warren <swarren@...dotorg.org>,
	Alexandre Courbot <gnurou@...il.com>,
	Grant Likely <grant.likely@...aro.org>,
	Rob Herring <robh+dt@...nel.org>, linux-kernel@...r.kernel.org,
	devicetree@...r.kernel.org
Subject: Re: [PATCH v4 10/13] memory: tegra: Add EMC (external memory controller)
 driver

On 11/12/2014 05:45 PM, Thierry Reding wrote:
> On Wed, Nov 12, 2014 at 08:56:33AM +0100, Tomeu Vizoso wrote:
> [...]
>> diff --git a/drivers/memory/tegra/tegra124-emc.c b/drivers/memory/tegra/tegra124-emc.c
> [...]
>> +static int t124_emc_burst_regs[] = {
>
> The t124 prefix seems rather redundant in a Tegra124-specific file. Also
> these should really be unsigned.
>
>> +struct emc_timing {
>> +	unsigned long rate;
>> +
>> +	/*
>> +	 * Store EMC burst data in a union to minimize mistakes. This allows
>> +	 * us to use the same burst data lists as used by the downstream and
>> +	 * ChromeOS kernels.
>
> I don't understand what this means. Can you elaborate?

We could store the three specifically named u32's separately, but using 
a union we can easily keep the indexing for the burst registers 
identical to the indexing used in the downstream and ChromeOS kernels. 
In reality, these burst register lists are generated by NVIDIA's 
downstream tool, so allowing use of the same lists minimizes possible 
(probably very difficult to spot) mistakes when porting mach 
files/device trees from those kernels to the upstream kernel.

>
>> +	 */
>> +	union {
>> +		u32 emc_burst_data[ARRAY_SIZE(t124_emc_burst_regs)];
>> +		struct {
>> +			u32 __pad0[121];
>> +			u32 emc_xm2dqspadctrl2;
>> +			u32 __pad1[15];
>> +			u32 emc_zcal_interval;
>> +			u32 __pad2[1];
>> +			u32 emc_mrs_wait_cnt;
>> +		};
>> +	};
> [...]
>> +struct tegra_emc {
>> +	struct platform_device *pdev;
>
> I don't see this ever used other than for accessing pdev->dev, in which
> case it would be much simpler and save a lot of characters if this was
> simply:
>
> 	struct device *dev;
>
>> +	struct tegra_mc *mc;
>> +
>> +	void __iomem *emc_regs;
>
> There is no second set of registers, so the emc_ prefix is useless.
>
>> +
>> +	enum emc_dram_type dram_type;
>> +	u8 dram_num;
>
> Should be an unsized type and match what's returned by the MC accessor.
>
>> +
>> +	struct emc_timing last_timing;
>
> struct emc_timing is rather big, can this be a pointer to an entry in
> the timings array instead?

No. The timing set by the pre-kernel boot process might not correspond 
to any timing in the kernel's timing lists. Of course, we don't need 
most of the values at that point (and don't populate them either), so it 
would be possible to special case the timing change function to use some 
separate structure for the first timing change. Doing what is done now 
is simpler, though. It is also how it's done downstream (not that that 
is a good indicator of good design..)

>
>> +	struct emc_timing *timings;
>> +	unsigned int num_timings;
>> +};
>> +
>> +/* Timing change sequence functions */
>> +
>> +static void emc_ccfifo_writel(struct tegra_emc *tegra, u32 val,
>> +			      unsigned long offs)
>> +{
>> +	writel(val, tegra->emc_regs + EMC_CCFIFO_DATA);
>> +	writel(offs, tegra->emc_regs + EMC_CCFIFO_ADDR);
>> +}
>> +
>> +static void emc_seq_update_timing(struct tegra_emc *tegra)
>> +{
>> +	int i;
>
> unsigned int, please.
>
>> +
>> +	writel(1, tegra->emc_regs + EMC_TIMING_CONTROL);
>> +
>> +	for (i = 0; i < EMC_STATUS_UPDATE_TIMEOUT; ++i) {
>> +		if (!(readl(tegra->emc_regs + EMC_STATUS) &
>> +		    EMC_STATUS_TIMING_UPDATE_STALLED))
>> +			return;
>> +	}
>
> You're busy-looping 1000 times here. What are the real criteria here?
> How long is this allowed to take? Should this not be a timed loop where
> the number of iterations doesn't matter?

Logic directly taken from downstream/hw testing code. A timeout would 
sound more logical, but dunno what to use.

>
>> +
>> +	dev_err(&tegra->pdev->dev, "timing update failed\n");
>> +}
>
> Should this return an error that can be propagated?

I'm not sure if it's safe to stop during the timing change sequence.. at 
least the downstream code doesn't try either. To know for sure, you'd 
need some internal guy who really knows the sequence.

>
>> +static void emc_seq_disable_auto_cal(struct tegra_emc *tegra)
>> +{
>> +	int i;
>> +
>> +	writel(0, tegra->emc_regs + EMC_AUTO_CAL_INTERVAL);
>> +
>> +	for (i = 0; i < EMC_STATUS_UPDATE_TIMEOUT; ++i) {
>> +		if (!(readl(tegra->emc_regs + EMC_AUTO_CAL_STATUS) &
>> +		    EMC_AUTO_CAL_STATUS_ACTIVE))
>> +			return;
>> +	}
>> +
>> +	dev_err(&tegra->pdev->dev, "auto cal disable failed\n");
>> +}
>
> Same comments as for emc_seq_update_timing().
>
>> +
>> +static void emc_seq_wait_clkchange(struct tegra_emc *tegra)
>> +{
>> +	int i;
>> +
>> +	for (i = 0; i < EMC_STATUS_UPDATE_TIMEOUT; ++i) {
>> +		if (readl(tegra->emc_regs + EMC_INTSTATUS) &
>> +		    EMC_INTSTATUS_CLKCHANGE_COMPLETE)
>> +			return;
>> +	}
>> +
>> +	dev_err(&tegra->pdev->dev, "clkchange failed\n");
>> +}
>
> And again.
>
>> +
>> +static void emc_prepare_timing_change(struct tegra_emc *tegra,
>> +				      const struct emc_timing *timing)
>> +{
>> +	u32 val, val2;
>> +	bool update = false;
>> +	int pre_wait = 0;
>> +	int i;
>
> Both of these can be unsigned.
>
>> +	enum emc_dll_change dll_change;
>> +
>> +	if ((tegra->last_timing.emc_mode_1 & 0x1) == (timing->emc_mode_1 & 1))
>> +		dll_change = DLL_CHANGE_NONE;
>> +	else if (timing->emc_mode_1 & 1)
>> +		dll_change = DLL_CHANGE_ON;
>> +	else
>> +		dll_change = DLL_CHANGE_OFF;
>> +
>> +	/* Clear CLKCHANGE_COMPLETE interrupts */
>> +
>> +	writel(EMC_INTSTATUS_CLKCHANGE_COMPLETE,
>> +	       tegra->emc_regs + EMC_INTSTATUS);
>> +
>> +	/* Disable dynamic self-refresh */
>> +
>> +	val = readl(tegra->emc_regs + EMC_CFG);
>> +	if (val & EMC_CFG_PWR_MASK) {
>> +		val &= ~EMC_CFG_POWER_FEATURES_MASK;
>> +		writel(val, tegra->emc_regs + EMC_CFG);
>> +
>> +		pre_wait = 5;
>> +	}
>> +
>> +	/* Disable SEL_DPD_CTRL for clock change */
>> +
>> +	val = readl(tegra->emc_regs + EMC_SEL_DPD_CTRL);
>> +	if (val & (tegra->dram_type == DRAM_TYPE_DDR3 ?
>> +	    EMC_SEL_DPD_CTRL_DDR3_MASK : EMC_SEL_DPD_CTRL_MASK)) {
>
> This is really hard to read. Perhaps something like:
>
> 	if (tegra->dram_type == DRAM_TYPE_DDR3)
> 		mask = EMC_SEL_DPD_CTRL_DDR3_MASK;
> 	else
> 		mask = EMC_SEL_DPD_CTRL_MASK;
>
> ?
>
>> +		val &= ~(EMC_SEL_DPD_CTRL_DATA_SEL_DPD |
>> +				EMC_SEL_DPD_CTRL_ODT_SEL_DPD |
>> +				EMC_SEL_DPD_CTRL_CA_SEL_DPD |
>> +				EMC_SEL_DPD_CTRL_CLK_SEL_DPD);
>> +		if (tegra->dram_type == DRAM_TYPE_DDR3)
>> +			val &= ~EMC_SEL_DPD_CTRL_RESET_SEL_DPD;
>
> These seem to be all the fields already present in the respective masks,
> so why not just:
>
> 	val &= ~mask;
>
> ?
>
>> +		writel(val, tegra->emc_regs + EMC_SEL_DPD_CTRL);
>> +	}
>> +
>> +	/* Prepare DQ/DQS for clock change */
>> +
>> +	val = readl(tegra->emc_regs + EMC_BGBIAS_CTL0);
>> +	val2 = tegra->last_timing.emc_bgbias_ctl0;
>> +	if (!(timing->emc_bgbias_ctl0 &
>> +	      EMC_BGBIAS_CTL0_BIAS0_DSC_E_PWRD_IBIAS_RX) &&
>> +	    (val & EMC_BGBIAS_CTL0_BIAS0_DSC_E_PWRD_IBIAS_RX)) {
>> +		val2 &= ~EMC_BGBIAS_CTL0_BIAS0_DSC_E_PWRD_IBIAS_RX;
>> +		update = true;
>> +	}
>> +
>> +	if ((val & EMC_BGBIAS_CTL0_BIAS0_DSC_E_PWRD) ||
>> +	    (val & EMC_BGBIAS_CTL0_BIAS0_DSC_E_PWRD_IBIAS_VTTGEN)) {
>> +		update = true;
>> +	}
>> +
>> +	if (update) {
>> +		writel(val2, tegra->emc_regs + EMC_BGBIAS_CTL0);
>> +		if (pre_wait < 5)
>> +			pre_wait = 5;
>> +	}
>> +
>> +	update = false;
>> +	val = readl(tegra->emc_regs + EMC_XM2DQSPADCTRL2);
>> +	if (timing->emc_xm2dqspadctrl2 & EMC_XM2DQSPADCTRL2_VREF_ENABLE &&
>> +	    !(val & EMC_XM2DQSPADCTRL2_VREF_ENABLE)) {
>> +		val |= EMC_XM2DQSPADCTRL2_VREF_ENABLE;
>> +		update = true;
>> +	}
>> +
>> +	if (timing->emc_xm2dqspadctrl2 & EMC_XM2DQSPADCTRL2_RX_FT_REC_ENABLE &&
>> +	    !(val & EMC_XM2DQSPADCTRL2_RX_FT_REC_ENABLE)) {
>> +		val |= EMC_XM2DQSPADCTRL2_RX_FT_REC_ENABLE;
>> +		update = true;
>> +	}
>> +
>> +	if (update) {
>> +		writel(val, tegra->emc_regs + EMC_XM2DQSPADCTRL2);
>> +		if (pre_wait < 30)
>> +			pre_wait = 30;
>> +	}
>> +
>> +	/* Wait to settle */
>> +
>> +	if (pre_wait) {
>> +		emc_seq_update_timing(tegra);
>> +		udelay(pre_wait);
>> +	}
>> +
>> +	/* Program CTT_TERM control */
>> +
>> +	if (tegra->last_timing.emc_ctt_term_ctrl != timing->emc_ctt_term_ctrl) {
>
> There are a whole lot of very long lines caused by tegra->last_timing.
> How about introducing a temporary variable:
>
> 	struct emc_timing *last = &tegra->last_timing;
>
> or simply:
>
> 	struct emc_timing *last = tegra->last_timing;
>
> after a change I suggested earlier.
>
>> +		emc_seq_disable_auto_cal(tegra);
>> +		writel(timing->emc_ctt_term_ctrl,
>> +			tegra->emc_regs + EMC_CTT_TERM_CTRL);
>> +		emc_seq_update_timing(tegra);
>> +	}
>> +
>> +	/* Program burst shadow registers */
>> +
>> +	for (i = 0; i < ARRAY_SIZE(timing->emc_burst_data); ++i)
>> +		__raw_writel(timing->emc_burst_data[i],
>> +			     tegra->emc_regs + t124_emc_burst_regs[i]);
>> +
>> +	tegra_mc_write_emem_configuration(tegra->mc, timing->rate);
>> +
>> +	val = timing->emc_cfg & ~EMC_CFG_POWER_FEATURES_MASK;
>> +	emc_ccfifo_writel(tegra, val, EMC_CFG);
>> +
>> +	/* Program AUTO_CAL_CONFIG */
>> +
>> +	if (timing->emc_auto_cal_config2 !=
>> +	    tegra->last_timing.emc_auto_cal_config2)
>> +		emc_ccfifo_writel(tegra, timing->emc_auto_cal_config2,
>> +				  EMC_AUTO_CAL_CONFIG2);
>> +	if (timing->emc_auto_cal_config3 !=
>> +	    tegra->last_timing.emc_auto_cal_config3)
>> +		emc_ccfifo_writel(tegra, timing->emc_auto_cal_config3,
>> +				  EMC_AUTO_CAL_CONFIG3);
>> +	if (timing->emc_auto_cal_config !=
>> +	    tegra->last_timing.emc_auto_cal_config) {
>> +		val = timing->emc_auto_cal_config;
>> +		val &= EMC_AUTO_CAL_CONFIG_AUTO_CAL_START;
>> +		emc_ccfifo_writel(tegra, val, EMC_AUTO_CAL_CONFIG);
>> +	}
>> +
>> +	/* DDR3: predict MRS long wait count */
>> +
>> +	if (tegra->dram_type == DRAM_TYPE_DDR3 && dll_change == DLL_CHANGE_ON) {
>> +		u32 cnt = 32;
>> +
>> +		if (timing->emc_zcal_interval != 0 &&
>> +		    tegra->last_timing.emc_zcal_interval == 0)
>> +			cnt -= tegra->dram_num * 256;
>
> Hmm... I don't understand this: cnt == 32 at the beginning, now you're
> subtracting something that's >= 256 if dram_num > 0. Is that really
> intended?

Looking at the downstream kernel (some version of it, at least), I see 
that cnt = 512. Not sure why I don't have that.

>
>> +
>> +		val = timing->emc_mrs_wait_cnt
>> +			& EMC_MRS_WAIT_CNT_SHORT_WAIT_MASK
>> +			>> EMC_MRS_WAIT_CNT_SHORT_WAIT_SHIFT;
>
> I think >> has higher precedence than &, so you probably want
> parentheses here.

Yes.

>
>> +		if (cnt < val)
>> +			cnt = val;
>> +
>> +		val = timing->emc_mrs_wait_cnt
>> +			& ~EMC_MRS_WAIT_CNT_LONG_WAIT_MASK;
>> +		val |= (cnt << EMC_MRS_WAIT_CNT_LONG_WAIT_SHIFT)
>> +			& EMC_MRS_WAIT_CNT_LONG_WAIT_MASK;
>> +
>> +		writel(val, tegra->emc_regs + EMC_MRS_WAIT_CNT);
>> +	}
>> +
>> +	val = timing->emc_cfg_2;
>> +	val &= ~EMC_CFG_2_DIS_STP_OB_CLK_DURING_NON_WR;
>> +	emc_ccfifo_writel(tegra, val, EMC_CFG_2);
>> +
>> +	/* DDR3: Turn off DLL and enter self-refresh */
>> +
>> +	if (tegra->dram_type == DRAM_TYPE_DDR3 && dll_change == DLL_CHANGE_OFF)
>> +		emc_ccfifo_writel(tegra, timing->emc_mode_1, EMC_EMRS);
>> +
>> +	/* Disable refresh controller */
>> +
>> +	emc_ccfifo_writel(tegra, EMC_REFCTRL_DEV_SEL(tegra->dram_num),
>> +			  EMC_REFCTRL);
>> +	if (tegra->dram_type == DRAM_TYPE_DDR3)
>> +		emc_ccfifo_writel(tegra,
>> +				  EMC_DRAM_DEV_SEL(tegra->dram_num)
>> +					| EMC_SELF_REF_CMD_ENABLED,
>> +				  EMC_SELF_REF);
>> +
>> +	/* Flow control marker */
>> +
>> +	emc_ccfifo_writel(tegra, 1, EMC_STALL_THEN_EXE_AFTER_CLKCHANGE);
>> +
>> +	/* DDR3: Exit self-refresh */
>> +
>> +	if (tegra->dram_type == DRAM_TYPE_DDR3)
>> +		emc_ccfifo_writel(tegra,
>> +				  EMC_DRAM_DEV_SEL(tegra->dram_num),
>> +				  EMC_SELF_REF);
>> +	emc_ccfifo_writel(tegra,
>> +			  EMC_REFCTRL_DEV_SEL(tegra->dram_num)
>> +				| EMC_REFCTRL_ENABLE,
>> +			  EMC_REFCTRL);
>> +
>> +	/* Set DRAM mode registers */
>> +
>> +	if (tegra->dram_type == DRAM_TYPE_DDR3) {
>> +		if (timing->emc_mode_1 != tegra->last_timing.emc_mode_1)
>> +			emc_ccfifo_writel(tegra, timing->emc_mode_1, EMC_EMRS);
>> +		if (timing->emc_mode_2 != tegra->last_timing.emc_mode_2)
>> +			emc_ccfifo_writel(tegra, timing->emc_mode_2, EMC_EMRS2);
>
> These patterns repeat a lot, perhaps some helpers would be useful.
> Something like:
>
> #define emc_ccfifo_writel_if_changed(emc, old, new, field, offset)	\
> 	if ((new)->field != (old)->field)				\
> 		emc_ccfifo_writel(emc, (new)->field, offset);
>
> ...
>
> 			emc_ccfifo_writel_if_changed(tegra, last, timing,
> 						     emc_mode_1, EMC_EMRS);
>
> That's not as much of an improvement as I had hoped... so feel free to
> ignore.
>
>> +static int load_timings_from_dt(struct tegra_emc *tegra,
>> +				struct device_node *node)
>> +{
>> +	struct device_node *child;
>> +	int child_count = of_get_child_count(node);
>> +	int i = 0, err;
>
> unsigned int for i.
>
>> +
>> +	tegra->timings = devm_kzalloc(&tegra->pdev->dev,
>> +				      sizeof(struct emc_timing) * child_count,
>> +				      GFP_KERNEL);
>
> devm_kcalloc()/
>
>> +	if (!tegra->timings)
>> +		return -ENOMEM;
>> +
>> +	tegra->num_timings = child_count;
>> +
>> +	for_each_child_of_node(node, child) {
>> +		struct emc_timing *timing = tegra->timings + (i++);
>
> timing = &tegra->timings[i++];?
>
>> +
>> +		err = load_one_timing_from_dt(tegra, timing, child);
>> +		if (err)
>> +			return err;
>> +	}
>> +
>> +	sort(tegra->timings, tegra->num_timings, sizeof(struct emc_timing),
>> +	     cmp_timings, NULL);
>
> sizeof(*timing)?
>
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct of_device_id tegra_emc_of_match[] = {
>> +	{ .compatible = "nvidia,tegra124-emc" },
>> +	{}
>> +};
>> +MODULE_DEVICE_TABLE(of, tegra_emc_of_match);
>> +
>> +static struct tegra_emc *emc_instance;
>
> Can we find a way around this global variable? I suppose that somebody
> is going to call the tegra_emc_{prepare,complete}_timing_change() below,
> so perhaps the caller needs to get a reference to the EMC and pass that
> in?
>
>> +static struct emc_timing *tegra_emc_find_timing(unsigned long rate)
>> +{
>> +	int i;
>
> unsigned
>
>> +
>> +	if (!emc_instance)
>> +		return NULL;
>> +
>> +	for (i = 0; i < emc_instance->num_timings; ++i) {
>> +		if (emc_instance->timings[i].rate == rate)
>> +			break;
>> +	}
>> +
>> +	if (i == emc_instance->num_timings) {
>> +		dev_err(&emc_instance->pdev->dev, "no timing for rate %lu\n", rate);
>> +		return NULL;
>> +	}
>> +
>> +	return emc_instance->timings + i;
>
> This could be rewritten in a similar way than I suggested for the MC
> changes earlier.
>
>> +}
>> +
>> +int tegra_emc_prepare_timing_change(unsigned long rate)
>> +{
>> +	struct emc_timing *timing = tegra_emc_find_timing(rate);
>> +
>> +	if (!timing)
>> +		return -ENOENT;
>> +
>> +	emc_prepare_timing_change(emc_instance, timing);
>> +
>> +	return 0;
>> +}
>
> It seems like this really ought to return an error if
> emc_prepare_timing_change() fails.
>
>> +
>> +void tegra_emc_complete_timing_change(unsigned long rate)
>> +{
>> +	struct emc_timing *timing = tegra_emc_find_timing(rate);
>> +
>> +	if (!timing)
>> +		return;
>> +
>> +	emc_complete_timing_change(emc_instance, timing);
>> +}
>> +
>> +static int tegra_emc_probe(struct platform_device *pdev)
>> +{
>> +	struct tegra_emc *tegra;
>> +	struct device_node *node;
>> +	struct platform_device *mc_pdev;
>
> Perhaps simply "mc"?
>
>> +	struct resource *res;
>> +	u32 ram_code, node_ram_code;
>
> You only need node_ram_code in the loop, so perhaps move it there and
> make the name something less cumbersome like "value"?
>
>> +	int err;
>> +
>> +	tegra = devm_kzalloc(&pdev->dev, sizeof(*tegra), GFP_KERNEL);
>> +	if (!tegra)
>> +		return -ENOMEM;
>> +
>> +	tegra->pdev = pdev;
>> +
>> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> +	tegra->emc_regs = devm_ioremap_resource(&pdev->dev, res);
>> +	if (IS_ERR(tegra->emc_regs)) {
>> +		dev_err(&pdev->dev, "failed to map EMC regs\n");
>
> No need for this error message, devm_ioremap_resource() prints one of
> you.
>
>> +		return PTR_ERR(tegra->emc_regs);
>> +	}
>> +
>> +	node = of_parse_phandle(pdev->dev.of_node,
>> +				"nvidia,memory-controller", 0);
>> +	if (!node) {
>> +		dev_err(&pdev->dev, "could not get memory controller\n");
>> +		return -ENOENT;
>> +	}
>> +
>> +	mc_pdev = of_find_device_by_node(node);
>> +	if (!mc_pdev)
>> +		return -ENOENT;
>> +
>> +	tegra->mc = platform_get_drvdata(mc_pdev);
>> +	if (!tegra->mc)
>> +		return -ENOENT;
>
> Perhaps this ought to be -EPROBE_DEFER to handle that case?
>
>> +
>> +	of_node_put(node);
>
> I think that should go right after the call to of_find_device_by_node(),
> otherwise it'll leak in case of errors earlier.
>
>> +
>> +	ram_code = tegra_read_ram_code();
>> +
>> +	tegra->num_timings = 0;
>> +
>> +	for_each_child_of_node(pdev->dev.of_node, node) {
>> +		err = of_property_read_u32(node, "nvidia,ram-code", &node_ram_code);
>> +		if (err || (node_ram_code != ram_code))
>> +			continue;
>> +
>> +		err = load_timings_from_dt(tegra, node);
>> +		if (err)
>> +			return err;
>> +		break;
>> +	}
>> +
>> +	if (tegra->num_timings == 0)
>> +		dev_warn(&pdev->dev, "no memory timings for ram code %u registered\n", ram_code);
>
> Isn't this an error? What's the use of proceeding if this happens?

True. This is leftover from when I split the clock driver out of this 
one. The clock driver can be used readonly without timings.

>
> This is slightly more complicated than I think it should be. Perhaps
> split this into a helper function that finds a node matching the RAM
> code, then:
>
> 	node = tegra_emc_find_node_by_ram_code(pdev->dev.of_node, ram_code);
> 	if (!node)
> 		return -ENOENT;
>
> 	err = load_timings_from_dt(tegra, node);
> 	if (err)
> 		return err;
>
> Also I think you need to of_node_put() the node when you're done with
> it.
>
>> +
>> +	err = emc_init(tegra);
>> +	if (err) {
>> +		dev_err(&pdev->dev, "initialization failed: %d\n", err);
>> +		return err;
>> +	}
>> +
>> +	platform_set_drvdata(pdev, tegra);
>> +
>> +	emc_instance = tegra;
>> +
>> +	return 0;
>> +};
>> +
>> +static struct platform_driver tegra_emc_driver = {
>> +	.probe = tegra_emc_probe,
>> +	.driver = {
>> +		.name = "tegra-emc",
>> +		.of_match_table = tegra_emc_of_match,
>
> Perhaps you also want to add a ".suppress_bind_attrs = true;" here to
> avoid people from causing oopses by unbinding via sysfs.

True.

>
> Thierry
>

Thanks for reviewing, sorry I couldn't answer all.
I'll leave the rest to Tomeu ;)

Cheers,
Mikko

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ