| /* | 
 |  * This file is subject to the terms and conditions of the GNU General Public | 
 |  * License.  See the file "COPYING" in the main directory of this archive | 
 |  * for more details. | 
 |  * | 
 |  * Copyright (C) 2003 Atheros Communications, Inc.,  All Rights Reserved. | 
 |  * Copyright (C) 2006 FON Technology, SL. | 
 |  * Copyright (C) 2006 Imre Kaloz <kaloz@openwrt.org> | 
 |  * Copyright (C) 2006-2009 Felix Fietkau <nbd@openwrt.org> | 
 |  */ | 
 |  | 
 | #include <linux/init.h> | 
 | #include <linux/interrupt.h> | 
 | #include <asm/irq_cpu.h> | 
 | #include <asm/reboot.h> | 
 | #include <asm/bootinfo.h> | 
 | #include <asm/time.h> | 
 |  | 
 | #include <ath25_platform.h> | 
 | #include "devices.h" | 
 | #include "ar5312.h" | 
 | #include "ar2315.h" | 
 |  | 
 | void (*ath25_irq_dispatch)(void); | 
 |  | 
 | static inline bool check_radio_magic(const void __iomem *addr) | 
 | { | 
 | 	addr += 0x7a; /* offset for flash magic */ | 
 | 	return (__raw_readb(addr) == 0x5a) && (__raw_readb(addr + 1) == 0xa5); | 
 | } | 
 |  | 
 | static inline bool check_notempty(const void __iomem *addr) | 
 | { | 
 | 	return __raw_readl(addr) != 0xffffffff; | 
 | } | 
 |  | 
 | static inline bool check_board_data(const void __iomem *addr, bool broken) | 
 | { | 
 | 	/* config magic found */ | 
 | 	if (__raw_readl(addr) == ATH25_BD_MAGIC) | 
 | 		return true; | 
 |  | 
 | 	if (!broken) | 
 | 		return false; | 
 |  | 
 | 	/* broken board data detected, use radio data to find the | 
 | 	 * offset, user will fix this */ | 
 |  | 
 | 	if (check_radio_magic(addr + 0x1000)) | 
 | 		return true; | 
 | 	if (check_radio_magic(addr + 0xf8)) | 
 | 		return true; | 
 |  | 
 | 	return false; | 
 | } | 
 |  | 
 | static const void __iomem * __init find_board_config(const void __iomem *limit, | 
 | 						     const bool broken) | 
 | { | 
 | 	const void __iomem *addr; | 
 | 	const void __iomem *begin = limit - 0x1000; | 
 | 	const void __iomem *end = limit - 0x30000; | 
 |  | 
 | 	for (addr = begin; addr >= end; addr -= 0x1000) | 
 | 		if (check_board_data(addr, broken)) | 
 | 			return addr; | 
 |  | 
 | 	return NULL; | 
 | } | 
 |  | 
 | static const void __iomem * __init find_radio_config(const void __iomem *limit, | 
 | 						     const void __iomem *bcfg) | 
 | { | 
 | 	const void __iomem *rcfg, *begin, *end; | 
 |  | 
 | 	/* | 
 | 	 * Now find the start of Radio Configuration data, using heuristics: | 
 | 	 * Search forward from Board Configuration data by 0x1000 bytes | 
 | 	 * at a time until we find non-0xffffffff. | 
 | 	 */ | 
 | 	begin = bcfg + 0x1000; | 
 | 	end = limit; | 
 | 	for (rcfg = begin; rcfg < end; rcfg += 0x1000) | 
 | 		if (check_notempty(rcfg) && check_radio_magic(rcfg)) | 
 | 			return rcfg; | 
 |  | 
 | 	/* AR2316 relocates radio config to new location */ | 
 | 	begin = bcfg + 0xf8; | 
 | 	end = limit - 0x1000 + 0xf8; | 
 | 	for (rcfg = begin; rcfg < end; rcfg += 0x1000) | 
 | 		if (check_notempty(rcfg) && check_radio_magic(rcfg)) | 
 | 			return rcfg; | 
 |  | 
 | 	return NULL; | 
 | } | 
 |  | 
 | /* | 
 |  * NB: Search region size could be larger than the actual flash size, | 
 |  * but this shouldn't be a problem here, because the flash | 
 |  * will simply be mapped multiple times. | 
 |  */ | 
 | int __init ath25_find_config(phys_addr_t base, unsigned long size) | 
 | { | 
 | 	const void __iomem *flash_base, *flash_limit; | 
 | 	struct ath25_boarddata *config; | 
 | 	unsigned int rcfg_size; | 
 | 	int broken_boarddata = 0; | 
 | 	const void __iomem *bcfg, *rcfg; | 
 | 	u8 *board_data; | 
 | 	u8 *radio_data; | 
 | 	u8 *mac_addr; | 
 | 	u32 offset; | 
 |  | 
 | 	flash_base = ioremap_nocache(base, size); | 
 | 	flash_limit = flash_base + size; | 
 |  | 
 | 	ath25_board.config = NULL; | 
 | 	ath25_board.radio = NULL; | 
 |  | 
 | 	/* Copy the board and radio data to RAM, because accessing the mapped | 
 | 	 * memory of the flash directly after booting is not safe */ | 
 |  | 
 | 	/* Try to find valid board and radio data */ | 
 | 	bcfg = find_board_config(flash_limit, false); | 
 |  | 
 | 	/* If that fails, try to at least find valid radio data */ | 
 | 	if (!bcfg) { | 
 | 		bcfg = find_board_config(flash_limit, true); | 
 | 		broken_boarddata = 1; | 
 | 	} | 
 |  | 
 | 	if (!bcfg) { | 
 | 		pr_warn("WARNING: No board configuration data found!\n"); | 
 | 		goto error; | 
 | 	} | 
 |  | 
 | 	board_data = kzalloc(BOARD_CONFIG_BUFSZ, GFP_KERNEL); | 
 | 	if (!board_data) | 
 | 		goto error; | 
 | 	ath25_board.config = (struct ath25_boarddata *)board_data; | 
 | 	memcpy_fromio(board_data, bcfg, 0x100); | 
 | 	if (broken_boarddata) { | 
 | 		pr_warn("WARNING: broken board data detected\n"); | 
 | 		config = ath25_board.config; | 
 | 		if (is_zero_ether_addr(config->enet0_mac)) { | 
 | 			pr_info("Fixing up empty mac addresses\n"); | 
 | 			config->reset_config_gpio = 0xffff; | 
 | 			config->sys_led_gpio = 0xffff; | 
 | 			random_ether_addr(config->wlan0_mac); | 
 | 			config->wlan0_mac[0] &= ~0x06; | 
 | 			random_ether_addr(config->enet0_mac); | 
 | 			random_ether_addr(config->enet1_mac); | 
 | 		} | 
 | 	} | 
 |  | 
 | 	/* Radio config starts 0x100 bytes after board config, regardless | 
 | 	 * of what the physical layout on the flash chip looks like */ | 
 |  | 
 | 	rcfg = find_radio_config(flash_limit, bcfg); | 
 | 	if (!rcfg) { | 
 | 		pr_warn("WARNING: Could not find Radio Configuration data\n"); | 
 | 		goto error; | 
 | 	} | 
 |  | 
 | 	radio_data = board_data + 0x100 + ((rcfg - bcfg) & 0xfff); | 
 | 	ath25_board.radio = radio_data; | 
 | 	offset = radio_data - board_data; | 
 | 	pr_info("Radio config found at offset 0x%x (0x%x)\n", rcfg - bcfg, | 
 | 		offset); | 
 | 	rcfg_size = BOARD_CONFIG_BUFSZ - offset; | 
 | 	memcpy_fromio(radio_data, rcfg, rcfg_size); | 
 |  | 
 | 	mac_addr = &radio_data[0x1d * 2]; | 
 | 	if (is_broadcast_ether_addr(mac_addr)) { | 
 | 		pr_info("Radio MAC is blank; using board-data\n"); | 
 | 		ether_addr_copy(mac_addr, ath25_board.config->wlan0_mac); | 
 | 	} | 
 |  | 
 | 	iounmap(flash_base); | 
 |  | 
 | 	return 0; | 
 |  | 
 | error: | 
 | 	iounmap(flash_base); | 
 | 	return -ENODEV; | 
 | } | 
 |  | 
 | static void ath25_halt(void) | 
 | { | 
 | 	local_irq_disable(); | 
 | 	unreachable(); | 
 | } | 
 |  | 
 | void __init plat_mem_setup(void) | 
 | { | 
 | 	_machine_halt = ath25_halt; | 
 | 	pm_power_off = ath25_halt; | 
 |  | 
 | 	if (is_ar5312()) | 
 | 		ar5312_plat_mem_setup(); | 
 | 	else | 
 | 		ar2315_plat_mem_setup(); | 
 |  | 
 | 	/* Disable data watchpoints */ | 
 | 	write_c0_watchlo0(0); | 
 | } | 
 |  | 
 | asmlinkage void plat_irq_dispatch(void) | 
 | { | 
 | 	ath25_irq_dispatch(); | 
 | } | 
 |  | 
 | void __init plat_time_init(void) | 
 | { | 
 | 	if (is_ar5312()) | 
 | 		ar5312_plat_time_init(); | 
 | 	else | 
 | 		ar2315_plat_time_init(); | 
 | } | 
 |  | 
 | unsigned int get_c0_compare_int(void) | 
 | { | 
 | 	return CP0_LEGACY_COMPARE_IRQ; | 
 | } | 
 |  | 
 | void __init arch_init_irq(void) | 
 | { | 
 | 	clear_c0_status(ST0_IM); | 
 | 	mips_cpu_irq_init(); | 
 |  | 
 | 	/* Initialize interrupt controllers */ | 
 | 	if (is_ar5312()) | 
 | 		ar5312_arch_init_irq(); | 
 | 	else | 
 | 		ar2315_arch_init_irq(); | 
 | } |