162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * PowerPC 476FPE board specific routines 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright © 2013 Tony Breeds IBM Corporation 662306a36Sopenharmony_ci * Copyright © 2013 Alistair Popple IBM Corporation 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * Based on earlier code: 962306a36Sopenharmony_ci * Matt Porter <mporter@kernel.crashing.org> 1062306a36Sopenharmony_ci * Copyright 2002-2005 MontaVista Software Inc. 1162306a36Sopenharmony_ci * 1262306a36Sopenharmony_ci * Eugene Surovegin <eugene.surovegin@zultys.com> or <ebs@ebshome.net> 1362306a36Sopenharmony_ci * Copyright (c) 2003-2005 Zultys Technologies 1462306a36Sopenharmony_ci * 1562306a36Sopenharmony_ci * Rewritten and ported to the merged powerpc tree: 1662306a36Sopenharmony_ci * Copyright 2007 David Gibson <dwg@au1.ibm.com>, IBM Corporation. 1762306a36Sopenharmony_ci * Copyright © 2011 David Kliekamp IBM Corporation 1862306a36Sopenharmony_ci */ 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#include <linux/init.h> 2162306a36Sopenharmony_ci#include <linux/of.h> 2262306a36Sopenharmony_ci#include <linux/of_address.h> 2362306a36Sopenharmony_ci#include <linux/of_platform.h> 2462306a36Sopenharmony_ci#include <linux/rtc.h> 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci#include <asm/machdep.h> 2762306a36Sopenharmony_ci#include <asm/udbg.h> 2862306a36Sopenharmony_ci#include <asm/time.h> 2962306a36Sopenharmony_ci#include <asm/uic.h> 3062306a36Sopenharmony_ci#include <asm/ppc4xx.h> 3162306a36Sopenharmony_ci#include <asm/mpic.h> 3262306a36Sopenharmony_ci#include <asm/mmu.h> 3362306a36Sopenharmony_ci#include <asm/swiotlb.h> 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci#include <linux/pci.h> 3662306a36Sopenharmony_ci#include <linux/i2c.h> 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_cistatic const struct of_device_id ppc47x_of_bus[] __initconst = { 3962306a36Sopenharmony_ci { .compatible = "ibm,plb4", }, 4062306a36Sopenharmony_ci { .compatible = "ibm,plb6", }, 4162306a36Sopenharmony_ci { .compatible = "ibm,opb", }, 4262306a36Sopenharmony_ci { .compatible = "ibm,ebc", }, 4362306a36Sopenharmony_ci {}, 4462306a36Sopenharmony_ci}; 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci/* The EEPROM is missing and the default values are bogus. This forces USB in 4762306a36Sopenharmony_ci * to EHCI mode */ 4862306a36Sopenharmony_cistatic void quirk_ppc_currituck_usb_fixup(struct pci_dev *dev) 4962306a36Sopenharmony_ci{ 5062306a36Sopenharmony_ci if (of_machine_is_compatible("ibm,currituck")) { 5162306a36Sopenharmony_ci pci_write_config_dword(dev, 0xe0, 0x0114231f); 5262306a36Sopenharmony_ci pci_write_config_dword(dev, 0xe4, 0x00006c40); 5362306a36Sopenharmony_ci } 5462306a36Sopenharmony_ci} 5562306a36Sopenharmony_ciDECLARE_PCI_FIXUP_HEADER(0x1033, 0x0035, quirk_ppc_currituck_usb_fixup); 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci/* Akebono has an AVR microcontroller attached to the I2C bus 5862306a36Sopenharmony_ci * which is used to power off/reset the system. */ 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci/* AVR I2C Commands */ 6162306a36Sopenharmony_ci#define AVR_PWRCTL_CMD (0x26) 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci/* Flags for the power control I2C commands */ 6462306a36Sopenharmony_ci#define AVR_PWRCTL_PWROFF (0x01) 6562306a36Sopenharmony_ci#define AVR_PWRCTL_RESET (0x02) 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_cistatic struct i2c_client *avr_i2c_client; 6862306a36Sopenharmony_cistatic void __noreturn avr_halt_system(int pwrctl_flags) 6962306a36Sopenharmony_ci{ 7062306a36Sopenharmony_ci /* Request the AVR to reset the system */ 7162306a36Sopenharmony_ci i2c_smbus_write_byte_data(avr_i2c_client, 7262306a36Sopenharmony_ci AVR_PWRCTL_CMD, pwrctl_flags); 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci /* Wait for system to be reset */ 7562306a36Sopenharmony_ci while (1) 7662306a36Sopenharmony_ci ; 7762306a36Sopenharmony_ci} 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_cistatic void avr_power_off_system(void) 8062306a36Sopenharmony_ci{ 8162306a36Sopenharmony_ci avr_halt_system(AVR_PWRCTL_PWROFF); 8262306a36Sopenharmony_ci} 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_cistatic void __noreturn avr_reset_system(char *cmd) 8562306a36Sopenharmony_ci{ 8662306a36Sopenharmony_ci avr_halt_system(AVR_PWRCTL_RESET); 8762306a36Sopenharmony_ci} 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_cistatic int avr_probe(struct i2c_client *client) 9062306a36Sopenharmony_ci{ 9162306a36Sopenharmony_ci avr_i2c_client = client; 9262306a36Sopenharmony_ci ppc_md.restart = avr_reset_system; 9362306a36Sopenharmony_ci pm_power_off = avr_power_off_system; 9462306a36Sopenharmony_ci return 0; 9562306a36Sopenharmony_ci} 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_cistatic const struct i2c_device_id avr_id[] = { 9862306a36Sopenharmony_ci { "akebono-avr", 0 }, 9962306a36Sopenharmony_ci { } 10062306a36Sopenharmony_ci}; 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_cistatic struct i2c_driver avr_driver = { 10362306a36Sopenharmony_ci .driver = { 10462306a36Sopenharmony_ci .name = "akebono-avr", 10562306a36Sopenharmony_ci }, 10662306a36Sopenharmony_ci .probe = avr_probe, 10762306a36Sopenharmony_ci .id_table = avr_id, 10862306a36Sopenharmony_ci}; 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_cistatic int __init ppc47x_device_probe(void) 11162306a36Sopenharmony_ci{ 11262306a36Sopenharmony_ci i2c_add_driver(&avr_driver); 11362306a36Sopenharmony_ci of_platform_bus_probe(NULL, ppc47x_of_bus, NULL); 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci return 0; 11662306a36Sopenharmony_ci} 11762306a36Sopenharmony_cimachine_device_initcall(ppc47x_akebono, ppc47x_device_probe); 11862306a36Sopenharmony_cimachine_device_initcall(ppc47x_currituck, ppc47x_device_probe); 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_cistatic void __init ppc47x_init_irq(void) 12162306a36Sopenharmony_ci{ 12262306a36Sopenharmony_ci struct device_node *np; 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci /* Find top level interrupt controller */ 12562306a36Sopenharmony_ci for_each_node_with_property(np, "interrupt-controller") { 12662306a36Sopenharmony_ci if (!of_property_present(np, "interrupts")) 12762306a36Sopenharmony_ci break; 12862306a36Sopenharmony_ci } 12962306a36Sopenharmony_ci if (np == NULL) 13062306a36Sopenharmony_ci panic("Can't find top level interrupt controller"); 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci /* Check type and do appropriate initialization */ 13362306a36Sopenharmony_ci if (of_device_is_compatible(np, "chrp,open-pic")) { 13462306a36Sopenharmony_ci /* The MPIC driver will get everything it needs from the 13562306a36Sopenharmony_ci * device-tree, just pass 0 to all arguments 13662306a36Sopenharmony_ci */ 13762306a36Sopenharmony_ci struct mpic *mpic = 13862306a36Sopenharmony_ci mpic_alloc(np, 0, MPIC_NO_RESET, 0, 0, " MPIC "); 13962306a36Sopenharmony_ci BUG_ON(mpic == NULL); 14062306a36Sopenharmony_ci mpic_init(mpic); 14162306a36Sopenharmony_ci ppc_md.get_irq = mpic_get_irq; 14262306a36Sopenharmony_ci } else 14362306a36Sopenharmony_ci panic("Unrecognized top level interrupt controller"); 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci of_node_put(np); 14662306a36Sopenharmony_ci} 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci#ifdef CONFIG_SMP 14962306a36Sopenharmony_cistatic void smp_ppc47x_setup_cpu(int cpu) 15062306a36Sopenharmony_ci{ 15162306a36Sopenharmony_ci mpic_setup_this_cpu(); 15262306a36Sopenharmony_ci} 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_cistatic int smp_ppc47x_kick_cpu(int cpu) 15562306a36Sopenharmony_ci{ 15662306a36Sopenharmony_ci struct device_node *cpunode = of_get_cpu_node(cpu, NULL); 15762306a36Sopenharmony_ci const u64 *spin_table_addr_prop; 15862306a36Sopenharmony_ci u32 *spin_table; 15962306a36Sopenharmony_ci extern void start_secondary_47x(void); 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci BUG_ON(cpunode == NULL); 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci /* Assume spin table. We could test for the enable-method in 16462306a36Sopenharmony_ci * the device-tree but currently there's little point as it's 16562306a36Sopenharmony_ci * our only supported method 16662306a36Sopenharmony_ci */ 16762306a36Sopenharmony_ci spin_table_addr_prop = 16862306a36Sopenharmony_ci of_get_property(cpunode, "cpu-release-addr", NULL); 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci if (spin_table_addr_prop == NULL) { 17162306a36Sopenharmony_ci pr_err("CPU%d: Can't start, missing cpu-release-addr !\n", 17262306a36Sopenharmony_ci cpu); 17362306a36Sopenharmony_ci return 1; 17462306a36Sopenharmony_ci } 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci /* Assume it's mapped as part of the linear mapping. This is a bit 17762306a36Sopenharmony_ci * fishy but will work fine for now 17862306a36Sopenharmony_ci * 17962306a36Sopenharmony_ci * XXX: Is there any reason to assume differently? 18062306a36Sopenharmony_ci */ 18162306a36Sopenharmony_ci spin_table = (u32 *)__va(*spin_table_addr_prop); 18262306a36Sopenharmony_ci pr_debug("CPU%d: Spin table mapped at %p\n", cpu, spin_table); 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci spin_table[3] = cpu; 18562306a36Sopenharmony_ci smp_wmb(); 18662306a36Sopenharmony_ci spin_table[1] = __pa(start_secondary_47x); 18762306a36Sopenharmony_ci mb(); 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci return 0; 19062306a36Sopenharmony_ci} 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_cistatic struct smp_ops_t ppc47x_smp_ops = { 19362306a36Sopenharmony_ci .probe = smp_mpic_probe, 19462306a36Sopenharmony_ci .message_pass = smp_mpic_message_pass, 19562306a36Sopenharmony_ci .setup_cpu = smp_ppc47x_setup_cpu, 19662306a36Sopenharmony_ci .kick_cpu = smp_ppc47x_kick_cpu, 19762306a36Sopenharmony_ci .give_timebase = smp_generic_give_timebase, 19862306a36Sopenharmony_ci .take_timebase = smp_generic_take_timebase, 19962306a36Sopenharmony_ci}; 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_cistatic void __init ppc47x_smp_init(void) 20262306a36Sopenharmony_ci{ 20362306a36Sopenharmony_ci if (mmu_has_feature(MMU_FTR_TYPE_47x)) 20462306a36Sopenharmony_ci smp_ops = &ppc47x_smp_ops; 20562306a36Sopenharmony_ci} 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci#else /* CONFIG_SMP */ 20862306a36Sopenharmony_cistatic void __init ppc47x_smp_init(void) { } 20962306a36Sopenharmony_ci#endif /* CONFIG_SMP */ 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_cistatic void __init ppc47x_setup_arch(void) 21262306a36Sopenharmony_ci{ 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci /* No need to check the DMA config as we /know/ our windows are all of 21562306a36Sopenharmony_ci * RAM. Lets hope that doesn't change */ 21662306a36Sopenharmony_ci swiotlb_detect_4g(); 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci ppc47x_smp_init(); 21962306a36Sopenharmony_ci} 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_cistatic int board_rev = -1; 22262306a36Sopenharmony_cistatic int __init ppc47x_get_board_rev(void) 22362306a36Sopenharmony_ci{ 22462306a36Sopenharmony_ci int reg; 22562306a36Sopenharmony_ci u8 __iomem *fpga; 22662306a36Sopenharmony_ci struct device_node *np = NULL; 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci if (of_machine_is_compatible("ibm,currituck")) { 22962306a36Sopenharmony_ci np = of_find_compatible_node(NULL, NULL, "ibm,currituck-fpga"); 23062306a36Sopenharmony_ci reg = 0; 23162306a36Sopenharmony_ci } else if (of_machine_is_compatible("ibm,akebono")) { 23262306a36Sopenharmony_ci np = of_find_compatible_node(NULL, NULL, "ibm,akebono-fpga"); 23362306a36Sopenharmony_ci reg = 2; 23462306a36Sopenharmony_ci } 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci if (!np) 23762306a36Sopenharmony_ci goto fail; 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci fpga = of_iomap(np, 0); 24062306a36Sopenharmony_ci of_node_put(np); 24162306a36Sopenharmony_ci if (!fpga) 24262306a36Sopenharmony_ci goto fail; 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci board_rev = ioread8(fpga + reg) & 0x03; 24562306a36Sopenharmony_ci pr_info("%s: Found board revision %d\n", __func__, board_rev); 24662306a36Sopenharmony_ci iounmap(fpga); 24762306a36Sopenharmony_ci return 0; 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_cifail: 25062306a36Sopenharmony_ci pr_info("%s: Unable to find board revision\n", __func__); 25162306a36Sopenharmony_ci return 0; 25262306a36Sopenharmony_ci} 25362306a36Sopenharmony_cimachine_arch_initcall(ppc47x_akebono, ppc47x_get_board_rev); 25462306a36Sopenharmony_cimachine_arch_initcall(ppc47x_currituck, ppc47x_get_board_rev); 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci/* Use USB controller should have been hardware swizzled but it wasn't :( */ 25762306a36Sopenharmony_cistatic void ppc47x_pci_irq_fixup(struct pci_dev *dev) 25862306a36Sopenharmony_ci{ 25962306a36Sopenharmony_ci if (dev->vendor == 0x1033 && (dev->device == 0x0035 || 26062306a36Sopenharmony_ci dev->device == 0x00e0)) { 26162306a36Sopenharmony_ci if (board_rev == 0) { 26262306a36Sopenharmony_ci dev->irq = irq_create_mapping(NULL, 47); 26362306a36Sopenharmony_ci pr_info("%s: Mapping irq %d\n", __func__, dev->irq); 26462306a36Sopenharmony_ci } else if (board_rev == 2) { 26562306a36Sopenharmony_ci dev->irq = irq_create_mapping(NULL, 49); 26662306a36Sopenharmony_ci pr_info("%s: Mapping irq %d\n", __func__, dev->irq); 26762306a36Sopenharmony_ci } else { 26862306a36Sopenharmony_ci pr_alert("%s: Unknown board revision\n", __func__); 26962306a36Sopenharmony_ci } 27062306a36Sopenharmony_ci } 27162306a36Sopenharmony_ci} 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_cidefine_machine(ppc47x_akebono) { 27462306a36Sopenharmony_ci .name = "PowerPC 47x (akebono)", 27562306a36Sopenharmony_ci .compatible = "ibm,akebono", 27662306a36Sopenharmony_ci .progress = udbg_progress, 27762306a36Sopenharmony_ci .init_IRQ = ppc47x_init_irq, 27862306a36Sopenharmony_ci .setup_arch = ppc47x_setup_arch, 27962306a36Sopenharmony_ci .restart = ppc4xx_reset_system, 28062306a36Sopenharmony_ci}; 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_cidefine_machine(ppc47x_currituck) { 28362306a36Sopenharmony_ci .name = "PowerPC 47x (currituck)", 28462306a36Sopenharmony_ci .compatible = "ibm,currituck", 28562306a36Sopenharmony_ci .progress = udbg_progress, 28662306a36Sopenharmony_ci .init_IRQ = ppc47x_init_irq, 28762306a36Sopenharmony_ci .pci_irq_fixup = ppc47x_pci_irq_fixup, 28862306a36Sopenharmony_ci .setup_arch = ppc47x_setup_arch, 28962306a36Sopenharmony_ci .restart = ppc4xx_reset_system, 29062306a36Sopenharmony_ci}; 291