From b68eef0b610e8f2759eb8c93305fbdc0a6b19a24 Mon Sep 17 00:00:00 2001 From: Sean Young Date: Wed, 23 Aug 2017 11:43:56 +0100 Subject: [PATCH 1/2] z8f0811 dumper. Returns correct stuff most of the time (not always) Signed-off-by: Sean Young --- drivers/char/Kconfig | 15 +++ drivers/char/Makefile | 2 + drivers/char/zdumper-clock.c | 60 ++++++++++ drivers/char/zdumper.c | 259 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 336 insertions(+) create mode 100644 drivers/char/zdumper-clock.c create mode 100644 drivers/char/zdumper.c diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index ccd239ab879f..189f07158c34 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -587,5 +587,20 @@ config TILE_SROM source "drivers/char/xillybus/Kconfig" +config ZDUMPER + tristate "Memory Dumper for Z8F0811 device" + default y + ---help--- + An Z8F0811 must be connected to gpio ports. Creates + a /dev/zdumper chardev. Dump using dd: + + dd if=/dev/zdumper of=z8f0811.bin bs=8192 count=1 + +config ZDUMPER_CLOCK + tristate "Clock for Dumper for Z8F0811 device" + default y + ---help--- + An 32KHz clock is need. A PWM is used. + endmenu diff --git a/drivers/char/Makefile b/drivers/char/Makefile index 53e33720818c..1512a7ff7633 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -59,3 +59,5 @@ js-rtc-y = rtc.o obj-$(CONFIG_TILE_SROM) += tile-srom.o obj-$(CONFIG_XILLYBUS) += xillybus/ obj-$(CONFIG_POWERNV_OP_PANEL) += powernv-op-panel.o +obj-$(CONFIG_ZDUMPER) += zdumper.o +obj-$(CONFIG_ZDUMPER_CLOCK) += zdumper-clock.o diff --git a/drivers/char/zdumper-clock.c b/drivers/char/zdumper-clock.c new file mode 100644 index 000000000000..28076de85220 --- /dev/null +++ b/drivers/char/zdumper-clock.c @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2017 Sean Young + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "zdumper-clock" + +static const struct of_device_id zdumper_clock_of_match[] = { + { .compatible = "zdumper-clock", }, + { }, +}; +MODULE_DEVICE_TABLE(of, zdumper_clock_of_match); + +static int zdumper_clock_probe(struct platform_device *pdev) +{ + struct pwm_device *pwm; + int rc = 0, duty, period; + + pwm = devm_pwm_get(&pdev->dev, NULL); + if (IS_ERR(pwm)) + return PTR_ERR(pwm); + + period = DIV_ROUND_CLOSEST(NSEC_PER_SEC, 32000); + duty = DIV_ROUND_CLOSEST(50 * period, 100); + + pwm_config(pwm, duty, period); + + pwm_enable(pwm); + + return rc; +} + +static struct platform_driver zdumper_clock_driver = { + .probe = zdumper_clock_probe, + .driver = { + .name = DRIVER_NAME, + .of_match_table = of_match_ptr(zdumper_clock_of_match), + }, +}; +module_platform_driver(zdumper_clock_driver); + +MODULE_DESCRIPTION("Z8F0811 Clock driver"); +MODULE_AUTHOR("Sean Young "); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/zdumper.c b/drivers/char/zdumper.c new file mode 100644 index 000000000000..96a80dd88615 --- /dev/null +++ b/drivers/char/zdumper.c @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2017 Sean Young + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "zdumper" + +struct zdumper { + struct gpio_desc *debug, *reset; + struct gpio_desc *porta[8]; + struct gpio_desc *portbc[3]; + struct gpio_desc *clock; + /* we need a spinlock to hold the cpu while transmitting */ + spinlock_t lock; +}; + +static struct zdumper *zdumper; + +static const struct of_device_id zdumper_of_match[] = { + { .compatible = DRIVER_NAME, }, + { }, +}; +MODULE_DEVICE_TABLE(of, zdumper_of_match); + +static void gpio_debug_byte(struct gpio_desc *gpio, u8 val) +{ + int i; + + gpiod_set_value(gpio, 1); + mdelay(10); + + for (i=1; i<255; i <<= 1) { + if (val & i) + gpiod_set_value(gpio, 0); + else + gpiod_set_value(gpio, 1); + mdelay(10); + } + + gpiod_set_value(gpio, 0); + mdelay(10); +} + +static void wait_falling_edge(struct zdumper *z) +{ + // wait for high + while (!gpiod_get_value(z->clock)); + // wait for low + while (gpiod_get_value(z->clock)); +} + +static void wait_rising_edge(struct zdumper *z) +{ + // wait for low + while (gpiod_get_value(z->clock)); + // wait for high + while (!gpiod_get_value(z->clock)); +} + +static void write_bypass_reg(struct zdumper *z, u8 reg, u8 val) +{ + // ensure we're in output mode + int array[3] = { reg & 1, (reg & 2) != 0, (reg & 4) != 0 }; + int i; + + wait_rising_edge(z); + for (i=0; i<8; i++) { + gpiod_direction_output(z->porta[i], (val & (1 << i)) != 0); + } + + gpiod_set_array_value(3, z->portbc, array); + wait_rising_edge(z); +} + +static int read_bypass_reg(struct zdumper *z) +{ + // ensure we're in output mode + int array[3] = { 1, 0, 1 }; + int res[8]; + int i, val; + + wait_rising_edge(z); + gpiod_set_array_value(3, z->portbc, array); + for (i=0; i<8; i++) + gpiod_direction_input(z->porta[i]); + + wait_rising_edge(z); + wait_rising_edge(z); + for (i=0; i<8; i++) + res[i] = gpiod_get_value(z->porta[i]); + + val = 0; + for (i=0; i<8; i++) { + if (res[i]) + val |= 1 << i; + } + + return val; +} + +static ssize_t zdumper_read(struct file *file, char __user *buf, + size_t n, loff_t *ppos) +{ + struct gpio_desc *reset = zdumper->reset; + unsigned long flags; + u8 *memory; + int i, ret; + + if (n > 0x10000) + return -EINVAL; + + memory = kzalloc(n, GFP_KERNEL); + if (!memory) + return -ENOMEM; + + spin_lock_irqsave(&zdumper->lock, flags); + + gpiod_direction_output(zdumper->debug, 1); + gpiod_direction_input(zdumper->clock); + + // Reset the device for good measure + gpiod_set_value(reset, 1); + mdelay(50); + gpiod_set_value(reset, 0); + gpiod_set_value(zdumper->debug, 0); + + mdelay(100); + gpiod_set_value(zdumper->debug, 1); + mdelay(500); + gpiod_set_value(zdumper->debug, 0); + mdelay(500); + + gpio_debug_byte(zdumper->debug, 0x80); + gpio_debug_byte(zdumper->debug, 0xf0); + gpio_debug_byte(zdumper->debug, 0x04); + + // flash controller bypassed + write_bypass_reg(zdumper, 0x04, 0x80); + write_bypass_reg(zdumper, 0x03, 0xf0); + + for (i=0; i> 8); + write_bypass_reg(zdumper, 1, i); + memory[i] = read_bypass_reg(zdumper); + } + write_bypass_reg(zdumper, 0x07, 0xff); + + // done! + +#if 0 + gpiod_direction_input(zdumper->debug); + + edge = ktime_get(); + + for (;;) { + ktime_t now = ktime_get(); + int val = gpiod_get_value(zdumper->debug); + + if (val != last) + printk(KERN_WARNING "OCD DEBUG PIN: %d %llu\n", + val, now - edge); + + last = val; + if (now - edge > MS_TO_NS(500)) + break; + } + +#endif + spin_unlock_irqrestore(&zdumper->lock, flags); + + ret = copy_to_user(buf, memory, n); + kfree(memory); + if (ret) + return ret; + + return n; +} + +static const struct file_operations zdumper_fops = { + .owner = THIS_MODULE, + .read = zdumper_read, + .llseek = no_llseek, +}; + +static struct miscdevice zdumper_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = DRIVER_NAME, + .fops = &zdumper_fops, +}; + +static int zdumper_probe(struct platform_device *pdev) +{ + int rc, i; + + zdumper = devm_kmalloc(&pdev->dev, sizeof(*zdumper), GFP_KERNEL); + if (!zdumper) + return -ENOMEM; + + zdumper->debug = devm_gpiod_get(&pdev->dev, "debug", GPIOD_OUT_LOW); + if (IS_ERR(zdumper->debug)) { + if (PTR_ERR(zdumper->debug ) != -EPROBE_DEFER) + dev_err(&pdev->dev, "Failed to get gpio (%ld)\n", + PTR_ERR(zdumper->debug)); + return PTR_ERR(zdumper->debug); + } + + zdumper->reset = devm_gpiod_get(&pdev->dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(zdumper->reset)) { + if (PTR_ERR(zdumper->reset ) != -EPROBE_DEFER) + dev_err(&pdev->dev, "Failed to get gpio (%ld)\n", + PTR_ERR(zdumper->reset)); + return PTR_ERR(zdumper->reset); + } + + zdumper->clock = devm_gpiod_get(&pdev->dev, "clock", GPIOD_OUT_LOW); + for (i=0; i<8; i++) + zdumper->porta[i] = devm_gpiod_get_index(&pdev->dev, "porta", i, GPIOD_OUT_LOW); + for (i=0; i<3; i++) + zdumper->portbc[i] = devm_gpiod_get_index(&pdev->dev, "portbc", i, GPIOD_OUT_LOW); + + spin_lock_init(&zdumper->lock); + rc = misc_register(&zdumper_misc); + if (rc) + dev_err(&pdev->dev, "failed to register zdumper misc device\n"); + + return rc; +} + +static struct platform_driver zdumper_driver = { + .probe = zdumper_probe, + .driver = { + .name = DRIVER_NAME, + .of_match_table = of_match_ptr(zdumper_of_match), + }, +}; +module_platform_driver(zdumper_driver); + +MODULE_DESCRIPTION("Zilog Z8F0811 Dumper"); +MODULE_AUTHOR("Sean Young "); +MODULE_LICENSE("GPL"); -- 2.13.5