| // SPDX-License-Identifier: GPL-2.0-or-later | |
| /* | |
| * PIKA Warp(tm) board specific routines | |
| * | |
| * Copyright (c) 2008-2009 PIKA Technologies | |
| * Sean MacLennan <smaclennan@pikatech.com> | |
| */ | |
| #include <linux/init.h> | |
| #include <linux/of_platform.h> | |
| #include <linux/kthread.h> | |
| #include <linux/i2c.h> | |
| #include <linux/interrupt.h> | |
| #include <linux/delay.h> | |
| #include <linux/of_gpio.h> | |
| #include <linux/slab.h> | |
| #include <linux/export.h> | |
| #include <asm/machdep.h> | |
| #include <asm/prom.h> | |
| #include <asm/udbg.h> | |
| #include <asm/time.h> | |
| #include <asm/uic.h> | |
| #include <asm/ppc4xx.h> | |
| #include <asm/dma.h> | |
| static const struct of_device_id warp_of_bus[] __initconst = { | |
| { .compatible = "ibm,plb4", }, | |
| { .compatible = "ibm,opb", }, | |
| { .compatible = "ibm,ebc", }, | |
| {}, | |
| }; | |
| static int __init warp_device_probe(void) | |
| { | |
| of_platform_bus_probe(NULL, warp_of_bus, NULL); | |
| return 0; | |
| } | |
| machine_device_initcall(warp, warp_device_probe); | |
| static int __init warp_probe(void) | |
| { | |
| if (!of_machine_is_compatible("pika,warp")) | |
| return 0; | |
| return 1; | |
| } | |
| define_machine(warp) { | |
| .name = "Warp", | |
| .probe = warp_probe, | |
| .progress = udbg_progress, | |
| .init_IRQ = uic_init_tree, | |
| .get_irq = uic_get_irq, | |
| .restart = ppc4xx_reset_system, | |
| .calibrate_decr = generic_calibrate_decr, | |
| }; | |
| static int __init warp_post_info(void) | |
| { | |
| struct device_node *np; | |
| void __iomem *fpga; | |
| u32 post1, post2; | |
| /* Sighhhh... POST information is in the sd area. */ | |
| np = of_find_compatible_node(NULL, NULL, "pika,fpga-sd"); | |
| if (np == NULL) | |
| return -ENOENT; | |
| fpga = of_iomap(np, 0); | |
| of_node_put(np); | |
| if (fpga == NULL) | |
| return -ENOENT; | |
| post1 = in_be32(fpga + 0x40); | |
| post2 = in_be32(fpga + 0x44); | |
| iounmap(fpga); | |
| if (post1 || post2) | |
| printk(KERN_INFO "Warp POST %08x %08x\n", post1, post2); | |
| else | |
| printk(KERN_INFO "Warp POST OK\n"); | |
| return 0; | |
| } | |
| #ifdef CONFIG_SENSORS_AD7414 | |
| static LIST_HEAD(dtm_shutdown_list); | |
| static void __iomem *dtm_fpga; | |
| static unsigned green_led, red_led; | |
| struct dtm_shutdown { | |
| struct list_head list; | |
| void (*func)(void *arg); | |
| void *arg; | |
| }; | |
| int pika_dtm_register_shutdown(void (*func)(void *arg), void *arg) | |
| { | |
| struct dtm_shutdown *shutdown; | |
| shutdown = kmalloc(sizeof(struct dtm_shutdown), GFP_KERNEL); | |
| if (shutdown == NULL) | |
| return -ENOMEM; | |
| shutdown->func = func; | |
| shutdown->arg = arg; | |
| list_add(&shutdown->list, &dtm_shutdown_list); | |
| return 0; | |
| } | |
| int pika_dtm_unregister_shutdown(void (*func)(void *arg), void *arg) | |
| { | |
| struct dtm_shutdown *shutdown; | |
| list_for_each_entry(shutdown, &dtm_shutdown_list, list) | |
| if (shutdown->func == func && shutdown->arg == arg) { | |
| list_del(&shutdown->list); | |
| kfree(shutdown); | |
| return 0; | |
| } | |
| return -EINVAL; | |
| } | |
| static irqreturn_t temp_isr(int irq, void *context) | |
| { | |
| struct dtm_shutdown *shutdown; | |
| int value = 1; | |
| local_irq_disable(); | |
| gpio_set_value(green_led, 0); | |
| /* Run through the shutdown list. */ | |
| list_for_each_entry(shutdown, &dtm_shutdown_list, list) | |
| shutdown->func(shutdown->arg); | |
| printk(KERN_EMERG "\n\nCritical Temperature Shutdown\n\n"); | |
| while (1) { | |
| if (dtm_fpga) { | |
| unsigned reset = in_be32(dtm_fpga + 0x14); | |
| out_be32(dtm_fpga + 0x14, reset); | |
| } | |
| gpio_set_value(red_led, value); | |
| value ^= 1; | |
| mdelay(500); | |
| } | |
| /* Not reached */ | |
| return IRQ_HANDLED; | |
| } | |
| static int pika_setup_leds(void) | |
| { | |
| struct device_node *np, *child; | |
| np = of_find_compatible_node(NULL, NULL, "gpio-leds"); | |
| if (!np) { | |
| printk(KERN_ERR __FILE__ ": Unable to find leds\n"); | |
| return -ENOENT; | |
| } | |
| for_each_child_of_node(np, child) | |
| if (of_node_name_eq(child, "green")) | |
| green_led = of_get_gpio(child, 0); | |
| else if (of_node_name_eq(child, "red")) | |
| red_led = of_get_gpio(child, 0); | |
| of_node_put(np); | |
| return 0; | |
| } | |
| static void pika_setup_critical_temp(struct device_node *np, | |
| struct i2c_client *client) | |
| { | |
| int irq, rc; | |
| /* Do this before enabling critical temp interrupt since we | |
| * may immediately interrupt. | |
| */ | |
| pika_setup_leds(); | |
| /* These registers are in 1 degree increments. */ | |
| i2c_smbus_write_byte_data(client, 2, 65); /* Thigh */ | |
| i2c_smbus_write_byte_data(client, 3, 0); /* Tlow */ | |
| irq = irq_of_parse_and_map(np, 0); | |
| if (!irq) { | |
| printk(KERN_ERR __FILE__ ": Unable to get ad7414 irq\n"); | |
| return; | |
| } | |
| rc = request_irq(irq, temp_isr, 0, "ad7414", NULL); | |
| if (rc) { | |
| printk(KERN_ERR __FILE__ | |
| ": Unable to request ad7414 irq %d = %d\n", irq, rc); | |
| return; | |
| } | |
| } | |
| static inline void pika_dtm_check_fan(void __iomem *fpga) | |
| { | |
| static int fan_state; | |
| u32 fan = in_be32(fpga + 0x34) & (1 << 14); | |
| if (fan_state != fan) { | |
| fan_state = fan; | |
| if (fan) | |
| printk(KERN_WARNING "Fan rotation error detected." | |
| " Please check hardware.\n"); | |
| } | |
| } | |
| static int pika_dtm_thread(void __iomem *fpga) | |
| { | |
| struct device_node *np; | |
| struct i2c_client *client; | |
| np = of_find_compatible_node(NULL, NULL, "adi,ad7414"); | |
| if (np == NULL) | |
| return -ENOENT; | |
| client = of_find_i2c_device_by_node(np); | |
| if (client == NULL) { | |
| of_node_put(np); | |
| return -ENOENT; | |
| } | |
| pika_setup_critical_temp(np, client); | |
| of_node_put(np); | |
| printk(KERN_INFO "Warp DTM thread running.\n"); | |
| while (!kthread_should_stop()) { | |
| int val; | |
| val = i2c_smbus_read_word_data(client, 0); | |
| if (val < 0) | |
| dev_dbg(&client->dev, "DTM read temp failed.\n"); | |
| else { | |
| s16 temp = swab16(val); | |
| out_be32(fpga + 0x20, temp); | |
| } | |
| pika_dtm_check_fan(fpga); | |
| set_current_state(TASK_INTERRUPTIBLE); | |
| schedule_timeout(HZ); | |
| } | |
| return 0; | |
| } | |
| static int __init pika_dtm_start(void) | |
| { | |
| struct task_struct *dtm_thread; | |
| struct device_node *np; | |
| np = of_find_compatible_node(NULL, NULL, "pika,fpga"); | |
| if (np == NULL) | |
| return -ENOENT; | |
| dtm_fpga = of_iomap(np, 0); | |
| of_node_put(np); | |
| if (dtm_fpga == NULL) | |
| return -ENOENT; | |
| /* Must get post info before thread starts. */ | |
| warp_post_info(); | |
| dtm_thread = kthread_run(pika_dtm_thread, dtm_fpga, "pika-dtm"); | |
| if (IS_ERR(dtm_thread)) { | |
| iounmap(dtm_fpga); | |
| return PTR_ERR(dtm_thread); | |
| } | |
| return 0; | |
| } | |
| machine_late_initcall(warp, pika_dtm_start); | |
| #else /* !CONFIG_SENSORS_AD7414 */ | |
| int pika_dtm_register_shutdown(void (*func)(void *arg), void *arg) | |
| { | |
| return 0; | |
| } | |
| int pika_dtm_unregister_shutdown(void (*func)(void *arg), void *arg) | |
| { | |
| return 0; | |
| } | |
| machine_late_initcall(warp, warp_post_info); | |
| #endif | |
| EXPORT_SYMBOL(pika_dtm_register_shutdown); | |
| EXPORT_SYMBOL(pika_dtm_unregister_shutdown); |