diff options
Diffstat (limited to 'arch/powerpc/platforms/512x/mpc512x_shared.c')
| -rw-r--r-- | arch/powerpc/platforms/512x/mpc512x_shared.c | 326 | 
1 files changed, 186 insertions, 140 deletions
diff --git a/arch/powerpc/platforms/512x/mpc512x_shared.c b/arch/powerpc/platforms/512x/mpc512x_shared.c index e41ebbdb3e1..adb95f03d4d 100644 --- a/arch/powerpc/platforms/512x/mpc512x_shared.c +++ b/arch/powerpc/platforms/512x/mpc512x_shared.c @@ -12,6 +12,7 @@   * (at your option) any later version.   */ +#include <linux/clk.h>  #include <linux/kernel.h>  #include <linux/io.h>  #include <linux/irq.h> @@ -35,8 +36,10 @@ static struct mpc512x_reset_module __iomem *reset_module_base;  static void __init mpc512x_restart_init(void)  {  	struct device_node *np; +	const char *reset_compat; -	np = of_find_compatible_node(NULL, NULL, "fsl,mpc5121-reset"); +	reset_compat = mpc512x_select_reset_compat(); +	np = of_find_compatible_node(NULL, NULL, reset_compat);  	if (!np)  		return; @@ -66,147 +69,130 @@ struct fsl_diu_shared_fb {  	bool		in_use;  }; -unsigned int mpc512x_get_pixel_format(unsigned int bits_per_pixel, -				      int monitor_port) +/* receives a pixel clock spec in pico seconds, adjusts the DIU clock rate */ +static void mpc512x_set_pixel_clock(unsigned int pixclock)  { -	switch (bits_per_pixel) { -	case 32: -		return 0x88883316; -	case 24: -		return 0x88082219; -	case 16: -		return 0x65053118; -	} -	return 0x00000400; -} - -void mpc512x_set_gamma_table(int monitor_port, char *gamma_table_base) -{ -} - -void mpc512x_set_monitor_port(int monitor_port) -{ -} - -#define DIU_DIV_MASK	0x000000ff -void mpc512x_set_pixel_clock(unsigned int pixclock) -{ -	unsigned long bestval, bestfreq, speed, busfreq; -	unsigned long minpixclock, maxpixclock, pixval; -	struct mpc512x_ccm __iomem *ccm;  	struct device_node *np; -	u32 temp; -	long err; -	int i; +	struct clk *clk_diu; +	unsigned long epsilon, minpixclock, maxpixclock; +	unsigned long offset, want, got, delta; -	np = of_find_compatible_node(NULL, NULL, "fsl,mpc5121-clock"); +	/* lookup and enable the DIU clock */ +	np = of_find_compatible_node(NULL, NULL, "fsl,mpc5121-diu");  	if (!np) { -		pr_err("Can't find clock control module.\n"); +		pr_err("Could not find DIU device tree node.\n");  		return;  	} - -	ccm = of_iomap(np, 0); +	clk_diu = of_clk_get(np, 0); +	if (IS_ERR(clk_diu)) { +		/* backwards compat with device trees that lack clock specs */ +		clk_diu = clk_get_sys(np->name, "ipg"); +	}  	of_node_put(np); -	if (!ccm) { -		pr_err("Can't map clock control module reg.\n"); +	if (IS_ERR(clk_diu)) { +		pr_err("Could not lookup DIU clock.\n");  		return;  	} - -	np = of_find_node_by_type(NULL, "cpu"); -	if (np) { -		const unsigned int *prop = -			of_get_property(np, "bus-frequency", NULL); - -		of_node_put(np); -		if (prop) { -			busfreq = *prop; -		} else { -			pr_err("Can't get bus-frequency property\n"); -			return; -		} -	} else { -		pr_err("Can't find 'cpu' node.\n"); +	if (clk_prepare_enable(clk_diu)) { +		pr_err("Could not enable DIU clock.\n");  		return;  	} -	/* Pixel Clock configuration */ -	pr_debug("DIU: Bus Frequency = %lu\n", busfreq); -	speed = busfreq * 4; /* DIU_DIV ratio is 4 * CSB_CLK / DIU_CLK */ - -	/* Calculate the pixel clock with the smallest error */ -	/* calculate the following in steps to avoid overflow */ -	pr_debug("DIU pixclock in ps - %d\n", pixclock); -	temp = (1000000000 / pixclock) * 1000; -	pixclock = temp; -	pr_debug("DIU pixclock freq - %u\n", pixclock); - -	temp = temp / 20; /* pixclock * 0.05 */ -	pr_debug("deviation = %d\n", temp); -	minpixclock = pixclock - temp; -	maxpixclock = pixclock + temp; -	pr_debug("DIU minpixclock - %lu\n", minpixclock); -	pr_debug("DIU maxpixclock - %lu\n", maxpixclock); -	pixval = speed/pixclock; -	pr_debug("DIU pixval = %lu\n", pixval); - -	err = LONG_MAX; -	bestval = pixval; -	pr_debug("DIU bestval = %lu\n", bestval); - -	bestfreq = 0; -	for (i = -1; i <= 1; i++) { -		temp = speed / (pixval+i); -		pr_debug("DIU test pixval i=%d, pixval=%lu, temp freq. = %u\n", -			i, pixval, temp); -		if ((temp < minpixclock) || (temp > maxpixclock)) -			pr_debug("DIU exceeds monitor range (%lu to %lu)\n", -				minpixclock, maxpixclock); -		else if (abs(temp - pixclock) < err) { -			pr_debug("Entered the else if block %d\n", i); -			err = abs(temp - pixclock); -			bestval = pixval + i; -			bestfreq = temp; -		} -	} +	/* +	 * convert the picoseconds spec into the desired clock rate, +	 * determine the acceptable clock range for the monitor (+/- 5%), +	 * do the calculation in steps to avoid integer overflow +	 */ +	pr_debug("DIU pixclock in ps - %u\n", pixclock); +	pixclock = (1000000000 / pixclock) * 1000; +	pr_debug("DIU pixclock freq  - %u\n", pixclock); +	epsilon = pixclock / 20; /* pixclock * 0.05 */ +	pr_debug("DIU deviation      - %lu\n", epsilon); +	minpixclock = pixclock - epsilon; +	maxpixclock = pixclock + epsilon; +	pr_debug("DIU minpixclock    - %lu\n", minpixclock); +	pr_debug("DIU maxpixclock    - %lu\n", maxpixclock); -	pr_debug("DIU chose = %lx\n", bestval); -	pr_debug("DIU error = %ld\n NomPixClk ", err); -	pr_debug("DIU: Best Freq = %lx\n", bestfreq); -	/* Modify DIU_DIV in CCM SCFR1 */ -	temp = in_be32(&ccm->scfr1); -	pr_debug("DIU: Current value of SCFR1: 0x%08x\n", temp); -	temp &= ~DIU_DIV_MASK; -	temp |= (bestval & DIU_DIV_MASK); -	out_be32(&ccm->scfr1, temp); -	pr_debug("DIU: Modified value of SCFR1: 0x%08x\n", temp); -	iounmap(ccm); -} +	/* +	 * check whether the DIU supports the desired pixel clock +	 * +	 * - simply request the desired clock and see what the +	 *   platform's clock driver will make of it, assuming that it +	 *   will setup the best approximation of the requested value +	 * - try other candidate frequencies in the order of decreasing +	 *   preference (i.e. with increasing distance from the desired +	 *   pixel clock, and checking the lower frequency before the +	 *   higher frequency to not overload the hardware) until the +	 *   first match is found -- any potential subsequent match +	 *   would only be as good as the former match or typically +	 *   would be less preferrable +	 * +	 * the offset increment of pixelclock divided by 64 is an +	 * arbitrary choice -- it's simple to calculate, in the typical +	 * case we expect the first check to succeed already, in the +	 * worst case seven frequencies get tested (the exact center and +	 * three more values each to the left and to the right) before +	 * the 5% tolerance window is exceeded, resulting in fast enough +	 * execution yet high enough probability of finding a suitable +	 * value, while the error rate will be in the order of single +	 * percents +	 */ +	for (offset = 0; offset <= epsilon; offset += pixclock / 64) { +		want = pixclock - offset; +		pr_debug("DIU checking clock - %lu\n", want); +		clk_set_rate(clk_diu, want); +		got = clk_get_rate(clk_diu); +		delta = abs(pixclock - got); +		if (delta < epsilon) +			break; +		if (!offset) +			continue; +		want = pixclock + offset; +		pr_debug("DIU checking clock - %lu\n", want); +		clk_set_rate(clk_diu, want); +		got = clk_get_rate(clk_diu); +		delta = abs(pixclock - got); +		if (delta < epsilon) +			break; +	} +	if (offset <= epsilon) { +		pr_debug("DIU clock accepted - %lu\n", want); +		pr_debug("DIU pixclock want %u, got %lu, delta %lu, eps %lu\n", +			 pixclock, got, delta, epsilon); +		return; +	} +	pr_warn("DIU pixclock auto search unsuccessful\n"); -ssize_t mpc512x_show_monitor_port(int monitor_port, char *buf) -{ -	return sprintf(buf, "0 - 5121 LCD\n"); +	/* +	 * what is the most appropriate action to take when the search +	 * for an available pixel clock which is acceptable to the +	 * monitor has failed?  disable the DIU (clock) or just provide +	 * a "best effort"?  we go with the latter +	 */ +	pr_warn("DIU pixclock best effort fallback (backend's choice)\n"); +	clk_set_rate(clk_diu, pixclock); +	got = clk_get_rate(clk_diu); +	delta = abs(pixclock - got); +	pr_debug("DIU pixclock want %u, got %lu, delta %lu, eps %lu\n", +		 pixclock, got, delta, epsilon);  } -int mpc512x_set_sysfs_monitor_port(int val) +static enum fsl_diu_monitor_port +mpc512x_valid_monitor_port(enum fsl_diu_monitor_port port)  { -	return 0; +	return FSL_DIU_PORT_DVI;  }  static struct fsl_diu_shared_fb __attribute__ ((__aligned__(8))) diu_shared_fb; -#if defined(CONFIG_FB_FSL_DIU) || \ -    defined(CONFIG_FB_FSL_DIU_MODULE)  static inline void mpc512x_free_bootmem(struct page *page)  { -	__ClearPageReserved(page);  	BUG_ON(PageTail(page));  	BUG_ON(atomic_read(&page->_count) > 1); -	atomic_set(&page->_count, 1); -	__free_page(page); -	totalram_pages++; +	free_reserved_page(page);  } -void mpc512x_release_bootmem(void) +static void mpc512x_release_bootmem(void)  {  	unsigned long addr = diu_shared_fb.fb_phys & PAGE_MASK;  	unsigned long size = diu_shared_fb.fb_len; @@ -223,7 +209,6 @@ void mpc512x_release_bootmem(void)  	}  	diu_ops.release_bootmem	= NULL;  } -#endif  /*   * Check if DIU was pre-initialized. If so, perform steps @@ -233,7 +218,7 @@ void mpc512x_release_bootmem(void)   * address range will be reserved in setup_arch() after bootmem   * allocator is up.   */ -void __init mpc512x_init_diu(void) +static void __init mpc512x_init_diu(void)  {  	struct device_node *np;  	struct diu __iomem *diu_reg; @@ -256,7 +241,7 @@ void __init mpc512x_init_diu(void)  	}  	mode = in_be32(&diu_reg->diu_mode); -	if (mode != MFB_MODE1) { +	if (mode == MFB_MODE0) {  		pr_info("%s: DIU OFF\n", __func__);  		goto out;  	} @@ -302,7 +287,7 @@ out:  	iounmap(diu_reg);  } -void __init mpc512x_setup_diu(void) +static void __init mpc512x_setup_diu(void)  {  	int ret; @@ -326,16 +311,9 @@ void __init mpc512x_setup_diu(void)  		}  	} -#if defined(CONFIG_FB_FSL_DIU) || \ -    defined(CONFIG_FB_FSL_DIU_MODULE) -	diu_ops.get_pixel_format	= mpc512x_get_pixel_format; -	diu_ops.set_gamma_table		= mpc512x_set_gamma_table; -	diu_ops.set_monitor_port	= mpc512x_set_monitor_port;  	diu_ops.set_pixel_clock		= mpc512x_set_pixel_clock; -	diu_ops.show_monitor_port	= mpc512x_show_monitor_port; -	diu_ops.set_sysfs_monitor_port	= mpc512x_set_sysfs_monitor_port; +	diu_ops.valid_monitor_port	= mpc512x_valid_monitor_port;  	diu_ops.release_bootmem		= mpc512x_release_bootmem; -#endif  }  void __init mpc512x_init_IRQ(void) @@ -362,26 +340,45 @@ void __init mpc512x_init_IRQ(void)  static struct of_device_id __initdata of_bus_ids[] = {  	{ .compatible = "fsl,mpc5121-immr", },  	{ .compatible = "fsl,mpc5121-localbus", }, +	{ .compatible = "fsl,mpc5121-mbx", }, +	{ .compatible = "fsl,mpc5121-nfc", }, +	{ .compatible = "fsl,mpc5121-sram", }, +	{ .compatible = "fsl,mpc5121-pci", }, +	{ .compatible = "gpio-leds", },  	{},  }; -void __init mpc512x_declare_of_platform_devices(void) +static void __init mpc512x_declare_of_platform_devices(void)  { -	struct device_node *np; -  	if (of_platform_bus_probe(NULL, of_bus_ids, NULL))  		printk(KERN_ERR __FILE__ ": "  			"Error while probing of_platform bus\n"); - -	np = of_find_compatible_node(NULL, NULL, "fsl,mpc5121-nfc"); -	if (np) { -		of_platform_device_create(np, NULL, NULL); -		of_node_put(np); -	}  }  #define DEFAULT_FIFO_SIZE 16 +const char *mpc512x_select_psc_compat(void) +{ +	if (of_machine_is_compatible("fsl,mpc5121")) +		return "fsl,mpc5121-psc"; + +	if (of_machine_is_compatible("fsl,mpc5125")) +		return "fsl,mpc5125-psc"; + +	return NULL; +} + +const char *mpc512x_select_reset_compat(void) +{ +	if (of_machine_is_compatible("fsl,mpc5121")) +		return "fsl,mpc5121-reset"; + +	if (of_machine_is_compatible("fsl,mpc5125")) +		return "fsl,mpc5125-reset"; + +	return NULL; +} +  static unsigned int __init get_fifo_size(struct device_node *np,  					 char *prop_name)  { @@ -401,15 +398,22 @@ static unsigned int __init get_fifo_size(struct device_node *np,  		    ((u32)(_base) + sizeof(struct mpc52xx_psc)))  /* Init PSC FIFO space for TX and RX slices */ -void __init mpc512x_psc_fifo_init(void) +static void __init mpc512x_psc_fifo_init(void)  {  	struct device_node *np;  	void __iomem *psc;  	unsigned int tx_fifo_size;  	unsigned int rx_fifo_size; +	const char *psc_compat;  	int fifobase = 0; /* current fifo address in 32 bit words */ -	for_each_compatible_node(np, NULL, "fsl,mpc5121-psc") { +	psc_compat = mpc512x_select_psc_compat(); +	if (!psc_compat) { +		pr_err("%s: no compatible devices found\n", __func__); +		return; +	} + +	for_each_compatible_node(np, NULL, psc_compat) {  		tx_fifo_size = get_fifo_size(np, "fsl,tx-fifo-size");  		rx_fifo_size = get_fifo_size(np, "fsl,rx-fifo-size"); @@ -456,10 +460,52 @@ void __init mpc512x_psc_fifo_init(void)  	}  } +void __init mpc512x_init_early(void) +{ +	mpc512x_restart_init(); +	if (IS_ENABLED(CONFIG_FB_FSL_DIU)) +		mpc512x_init_diu(); +} +  void __init mpc512x_init(void)  { -	mpc512x_declare_of_platform_devices();  	mpc5121_clk_init(); -	mpc512x_restart_init(); +	mpc512x_declare_of_platform_devices();  	mpc512x_psc_fifo_init();  } + +void __init mpc512x_setup_arch(void) +{ +	if (IS_ENABLED(CONFIG_FB_FSL_DIU)) +		mpc512x_setup_diu(); +} + +/** + * mpc512x_cs_config - Setup chip select configuration + * @cs: chip select number + * @val: chip select configuration value + * + * Perform chip select configuration for devices on LocalPlus Bus. + * Intended to dynamically reconfigure the chip select parameters + * for configurable devices on the bus. + */ +int mpc512x_cs_config(unsigned int cs, u32 val) +{ +	static struct mpc512x_lpc __iomem *lpc; +	struct device_node *np; + +	if (cs > 7) +		return -EINVAL; + +	if (!lpc) { +		np = of_find_compatible_node(NULL, NULL, "fsl,mpc5121-lpc"); +		lpc = of_iomap(np, 0); +		of_node_put(np); +		if (!lpc) +			return -ENOMEM; +	} + +	out_be32(&lpc->cs_cfg[cs], val); +	return 0; +} +EXPORT_SYMBOL(mpc512x_cs_config);  | 
