| // SPDX-License-Identifier: (GPL-2.0+ OR MIT) |
| /* |
| * Copyright (c) 2019 Amlogic, Inc. All rights reserved. |
| */ |
| |
| #define pr_fmt(fmt) "jtag: " fmt |
| |
| #include <linux/init.h> |
| #include <linux/module.h> |
| #include <linux/platform_device.h> |
| #include <linux/of_device.h> |
| #include <linux/of_gpio.h> |
| #include <linux/of_address.h> |
| #include <linux/of_platform.h> |
| #include <linux/arm-smccc.h> |
| |
| #include <linux/amlogic/jtag.h> |
| #include <linux/arm-smccc.h> |
| #include <linux/interrupt.h> |
| #include <linux/ctype.h> |
| #ifdef CONFIG_MACH_MESON8B |
| #include <linux/amlogic/meson-secure.h> |
| #endif |
| |
| #include "meson_jtag.h" |
| |
| #define AML_JTAG_NAME "jtag" |
| #define JTAG_IRQ_CMD 0xFFF |
| |
| /* store the jtag select globaly */ |
| static int global_select = AMLOGIC_JTAG_DISABLE; |
| |
| /* whether the jtag select is setup by the boot param |
| * jtag select is setup by the boot prior to device tree. |
| */ |
| static bool jtag_select_setup; |
| |
| /* store the params that are setup by the boot param */ |
| static int jtag_select = AMLOGIC_JTAG_DISABLE; |
| |
| /* cluster default is 0 */ |
| static int jtag_cluster; |
| |
| static struct jtag_id_desc jtag_id_data[] = { |
| {JTAG_SELECT_ID(AP, JTAG_A, 0), "ap", "jtag_a", "apao"}, |
| {JTAG_SELECT_ID(AP, JTAG_B, 0), "ap", "jtag_b", "apee"}, |
| {JTAG_SELECT_ID(AP, SWD_A, 0), "ap", "swd_a", "swd_apao"}, |
| }; |
| |
| bool is_jtag_disable(void) |
| { |
| if (global_select == AMLOGIC_JTAG_DISABLE) |
| return true; |
| else |
| return false; |
| } |
| EXPORT_SYMBOL(is_jtag_disable); |
| |
| bool is_jtag_a(void) |
| { |
| if (global_select == JTAG_SELECT_ID(AP, JTAG_A, 0)) |
| return true; |
| else |
| return false; |
| } |
| EXPORT_SYMBOL(is_jtag_a); |
| |
| bool is_jtag_b(void) |
| { |
| if (global_select == JTAG_SELECT_ID(AP, JTAG_B, 0)) |
| return true; |
| else |
| return false; |
| } |
| EXPORT_SYMBOL(is_jtag_b); |
| |
| static int jtag_id_find(const char *core_type, const char *jtag_type) |
| { |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(jtag_id_data); i++) { |
| if (!jtag_id_data[i].core_type || |
| !jtag_id_data[i].jtag_type) |
| continue; |
| if ((strcmp(core_type, jtag_id_data[i].core_type) == 0) && |
| (strcmp(jtag_type, jtag_id_data[i].jtag_type) == 0)) { |
| return jtag_id_data[i].id; |
| } |
| } |
| |
| return AMLOGIC_JTAG_DISABLE; |
| } |
| |
| static int jtag_id_find_by_alias(const char *name) |
| { |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(jtag_id_data); i++) { |
| if (!jtag_id_data[i].alias) |
| continue; |
| if (strcmp(name, jtag_id_data[i].alias) == 0) |
| return jtag_id_data[i].id; |
| } |
| |
| return AMLOGIC_JTAG_DISABLE; |
| } |
| |
| static struct jtag_id_desc *select_to_name(int select) |
| { |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(jtag_id_data); i++) { |
| if (select == jtag_id_data[i].id) |
| return &jtag_id_data[i]; |
| } |
| |
| return NULL; |
| } |
| |
| static void aml_jtag_option_parse(const char *s, |
| int *jtag_select, int *jtag_cluster) |
| { |
| char *argv; |
| int argc = 0; |
| void *parameter[MAX_PARAM_NUM]; |
| int ret; |
| unsigned long cluster = 0; |
| int jtag_id = AMLOGIC_JTAG_DISABLE; |
| |
| if (strcmp(s, "disable") == 0) { |
| *jtag_select = jtag_id; |
| *jtag_cluster = (int)cluster; |
| return; |
| } |
| |
| do { |
| argv = strsep((char **)&s, ","); |
| if (!argv) |
| break; |
| pr_debug("param[%d] = %s\n", argc, argv); |
| parameter[argc] = argv; |
| argc++; |
| } while (argc < MAX_PARAM_NUM); |
| |
| switch (argc) { |
| case 1: |
| jtag_id = jtag_id_find_by_alias(parameter[0]); |
| break; |
| case 2: |
| if (isdigit(*(char *)parameter[1])) { |
| jtag_id = jtag_id_find_by_alias(parameter[0]); |
| ret = kstrtoul(parameter[1], 10, |
| (unsigned long *)&cluster); |
| if (ret) |
| cluster = 0; |
| } else { |
| jtag_id = jtag_id_find(parameter[0], parameter[1]); |
| } |
| break; |
| case 3: |
| jtag_id = jtag_id_find(parameter[0], parameter[1]); |
| ret = kstrtoul(parameter[2], 10, (unsigned long *)&cluster); |
| if (ret) |
| cluster = 0; |
| break; |
| default: |
| pr_info("invalid argument count!\n"); |
| *jtag_select = jtag_id; |
| *jtag_cluster = (int)cluster; |
| return; |
| } |
| if (jtag_id == AMLOGIC_JTAG_DISABLE) |
| pr_info("parameter not support, disabled jtag.\n"); |
| *jtag_select = jtag_id; |
| *jtag_cluster = (int)cluster; |
| } |
| |
| static int setup_jtag(char *p) |
| { |
| if (!p) |
| return -EINVAL; |
| |
| jtag_select_setup = true; |
| aml_jtag_option_parse(p, &jtag_select, &jtag_cluster); |
| |
| return 0; |
| } |
| |
| /* |
| * jtag=[ap,jtag_a|ap,jtag_b|ap,swd_a] |
| * jtag=[ap,jtag_a|ap,jtag_b|ap,swd_a]{,[0|1]} |
| * jtag=[apao|apee|swd_apao] |
| * jtag=[apao|apee|swd_apao]{,[0|1]} |
| * |
| * [jtag_a|jtag_b]: jtag_type |
| * [0|1]: cluster index |
| */ |
| static char *jtag_mode = ""; |
| |
| static int set_jtag_mode(const char *val, const struct kernel_param *kp) |
| { |
| param_set_charp(val, kp); |
| |
| return setup_jtag(jtag_mode); |
| } |
| |
| static const struct kernel_param_ops setup_jtag_ops = { |
| .set = set_jtag_mode, |
| .get = param_get_charp, |
| }; |
| |
| module_param_cb(jtag, &setup_jtag_ops, &jtag_mode, 0644); |
| MODULE_PARM_DESC(jtag, "jtag mode"); |
| |
| |
| #ifdef CONFIG_MACH_MESON8B |
| |
| static int aml_jtag_select_tee(struct platform_device *pdev) |
| { |
| struct aml_jtag_dev *jdev = platform_get_drvdata(pdev); |
| u32 select = jdev->select; |
| struct jtag_id_desc *tmp = NULL; |
| |
| pr_info("set state %u\n", select); |
| set_cpus_allowed_ptr(current, cpumask_of(0)); |
| tmp = select_to_name(jdev->select); |
| if (tmp) |
| pr_info("meson8b select %s,%s, alias:%s\n", |
| tmp->core_type, tmp->jtag_type, tmp->alias); |
| else |
| pr_info("meson8b select disable\n"); |
| switch (select) { |
| case AMLOGIC_JTAG_DISABLE: |
| meson_secure_jtag_disable(); |
| break; |
| case JTAG_SELECT_ID(AP, JTAG_A, 0): |
| meson_secure_jtag_apao(); |
| break; |
| case JTAG_SELECT_ID(AP, JTAG_B, 0): |
| meson_secure_jtag_apao(); |
| break; |
| |
| default: |
| writel_relaxed(0x0, jdev->base); |
| break; |
| } |
| set_cpus_allowed_ptr(current, cpu_all_mask); |
| |
| return 0; |
| } |
| |
| static int aml_jtag_select_ree(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| struct device_node *np = dev->of_node; |
| struct aml_jtag_dev *jdev = platform_get_drvdata(pdev); |
| unsigned int sel = jdev->select; |
| u32 val; |
| struct jtag_id_desc *tmp = NULL; |
| |
| jdev->base = of_iomap(np, 0); |
| if (!jdev->base) { |
| pr_err("failed to iomap regs"); |
| return -ENODEV; |
| } |
| tmp = select_to_name(jdev->select); |
| if (tmp) |
| pr_info("meson8b select %s,%s, alias:%s\n", |
| tmp->core_type, tmp->jtag_type, tmp->alias); |
| else |
| pr_info("meson8b select disable\n"); |
| switch (sel) { |
| case AMLOGIC_JTAG_DISABLE: |
| writel_relaxed(0x0, jdev->base); |
| break; |
| case JTAG_SELECT_ID(AP, JTAG_A, 0): |
| val = readl_relaxed(jdev->base); |
| val &= ~0x3FF; |
| val |= (2 << 0) | (1 << 8); |
| writel_relaxed(val, jdev->base); |
| break; |
| case JTAG_SELECT_ID(AP, JTAG_B, 0): |
| val = readl_relaxed(jdev->base); |
| val &= ~0x3FF; |
| val |= (2 << 4) | (2 << 8); |
| writel_relaxed(val, jdev->base); |
| break; |
| default: |
| writel_relaxed(0x0, jdev->base); |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static int aml_jtag_select(struct platform_device *pdev) |
| { |
| if (meson_secure_enabled()) |
| aml_jtag_select_tee(pdev); |
| else |
| aml_jtag_select_ree(pdev); |
| |
| return 0; |
| } |
| |
| #else |
| |
| static unsigned long __invoke_psci_fn_smc(unsigned long function_id, |
| unsigned long arg0, |
| unsigned long arg1, |
| unsigned long arg2) |
| { |
| struct arm_smccc_res res; |
| |
| arm_smccc_smc(function_id, arg0, arg1, arg2, 0, 0, 0, 0, &res); |
| return res.a0; |
| } |
| |
| /* |
| * setup jtag on/off, and setup jtag-a/jtag-b |
| * |
| * @state: must be JTAG_STATE_ON/JTAG_STATE_OFF |
| * @select: mest be JTAG_DISABLE/JTAG_A/JTAG_B |
| */ |
| void aml_set_jtag_state(unsigned int state, unsigned int select) |
| { |
| u64 command; |
| |
| if (state == AMLOGIC_JTAG_STATE_ON) |
| command = AMLOGIC_JTAG_ON; |
| else |
| command = AMLOGIC_JTAG_OFF; |
| asm __volatile__("" : : : "memory"); |
| |
| __invoke_psci_fn_smc(command, select, 0, 0); |
| } |
| |
| static int aml_jtag_select(struct platform_device *pdev) |
| { |
| struct aml_jtag_dev *jdev = platform_get_drvdata(pdev); |
| unsigned int select; |
| unsigned int state = AMLOGIC_JTAG_STATE_OFF; |
| struct jtag_id_desc *tmp = NULL; |
| |
| if (jdev->select != AMLOGIC_JTAG_DISABLE) |
| state = AMLOGIC_JTAG_STATE_ON; |
| |
| if (jdev->cluster != 0) |
| jdev->select = CLUSTER_TYPE_UPDATE(jdev->select, |
| jdev->cluster); |
| select = jdev->select; |
| |
| tmp = select_to_name(jdev->select); |
| if (tmp) |
| pr_info("select %s,%s, alias:%s\n", |
| tmp->core_type, tmp->jtag_type, tmp->alias); |
| else |
| pr_info("select disable\n"); |
| |
| set_cpus_allowed_ptr(current, cpumask_of(0)); |
| if (select == AMLOGIC_JTAG_DISABLE && |
| jdev->old_select != AMLOGIC_JTAG_DISABLE) |
| select = jdev->old_select; |
| aml_set_jtag_state(state, select); |
| set_cpus_allowed_ptr(current, cpu_all_mask); |
| |
| return 0; |
| } |
| |
| static void jtag_send_irq_to_bl31(void) |
| { |
| asm __volatile__("" : : : "memory"); |
| __invoke_psci_fn_smc(AMLOGIC_JTAG_ON, JTAG_IRQ_CMD, 0, 0); |
| } |
| |
| static irqreturn_t jtag_irq_handler(int irq, void *data) |
| { |
| jtag_send_irq_to_bl31(); |
| return IRQ_HANDLED; |
| } |
| |
| #endif |
| |
| static int aml_jtag_setup(struct aml_jtag_dev *jdev) |
| { |
| unsigned int old_select = jdev->old_select; |
| unsigned int select = jdev->select; |
| struct pinctrl_state *s; |
| int ret; |
| |
| if (old_select == select) |
| return 0; |
| if (!jdev->jtag_pinctrl) { |
| jdev->jtag_pinctrl = devm_pinctrl_get(&jdev->pdev->dev); |
| if (IS_ERR_OR_NULL(jdev->jtag_pinctrl)) { |
| dev_err(&jdev->pdev->dev, "could not get pinctrl handle\n"); |
| return -EINVAL; |
| } |
| } |
| /* set pinmux */ |
| switch (select) { |
| case JTAG_SELECT_ID(AP, JTAG_A, 0): |
| s = pinctrl_lookup_state(jdev->jtag_pinctrl, "jtag_a_pins"); |
| if (IS_ERR_OR_NULL(s)) { |
| dev_err(&jdev->pdev->dev, |
| "could not get jtag_a_pins state\n"); |
| return -EINVAL; |
| } |
| ret = pinctrl_select_state(jdev->jtag_pinctrl, s); |
| if (ret) { |
| dev_err(&jdev->pdev->dev, "failed to set pinctrl\n"); |
| return -EINVAL; |
| } |
| break; |
| case JTAG_SELECT_ID(AP, JTAG_B, 0): |
| s = pinctrl_lookup_state(jdev->jtag_pinctrl, "jtag_b_pins"); |
| if (IS_ERR_OR_NULL(s)) { |
| dev_err(&jdev->pdev->dev, |
| "could not get jtag_b_pins state\n"); |
| return -EINVAL; |
| } |
| ret = pinctrl_select_state(jdev->jtag_pinctrl, s); |
| if (ret) { |
| dev_err(&jdev->pdev->dev, "failed to set pinctrl\n"); |
| return -EINVAL; |
| } |
| break; |
| case JTAG_SELECT_ID(AP, SWD_A, 0): |
| s = pinctrl_lookup_state(jdev->jtag_pinctrl, |
| "swd_a_pins"); |
| if (IS_ERR_OR_NULL(s)) { |
| dev_err(&jdev->pdev->dev, |
| "could not get swd_a_pins state\n"); |
| return -EINVAL; |
| } |
| ret = pinctrl_select_state(jdev->jtag_pinctrl, s); |
| if (ret) { |
| dev_err(&jdev->pdev->dev, "failed to set pinctrl\n"); |
| return -EINVAL; |
| } |
| break; |
| default: |
| if (old_select != AMLOGIC_JTAG_DISABLE) { |
| devm_pinctrl_put(jdev->jtag_pinctrl); |
| jdev->jtag_pinctrl = NULL; |
| } |
| break; |
| } |
| |
| ret = of_property_read_bool(jdev->pdev->dev.of_node, |
| "amlogic,support-jtag-trace"); |
| if (ret) { |
| s = pinctrl_lookup_state(jdev->jtag_pinctrl, "jtag_trace_pins"); |
| if (IS_ERR_OR_NULL(s)) { |
| dev_err(&jdev->pdev->dev, |
| "could not get jtag_trace_pins state\n"); |
| return -EINVAL; |
| } |
| ret = pinctrl_select_state(jdev->jtag_pinctrl, s); |
| if (ret) { |
| dev_err(&jdev->pdev->dev, "failed to set pinctrl\n"); |
| return -EINVAL; |
| } |
| } |
| |
| /* save to global */ |
| global_select = jdev->select; |
| aml_jtag_select(jdev->pdev); |
| jdev->old_select = select; |
| |
| return 0; |
| } |
| |
| static ssize_t select_show(struct class *cls, |
| struct class_attribute *attr, char *buf) |
| { |
| unsigned int len = 0; |
| struct jtag_id_desc *tmp = NULL; |
| |
| tmp = select_to_name(global_select); |
| if (tmp) |
| len += sprintf(buf + len, |
| "current select: %s,%s, alias:%s\n\n", |
| tmp->core_type, tmp->jtag_type, tmp->alias); |
| else |
| len += sprintf(buf + len, "current select: disable\n\n"); |
| len += sprintf(buf + len, "usage:\n"); |
| len += sprintf(buf + len, |
| " echo <core_type>,<jtag_type>{,[0|1]} > select\n"); |
| len += sprintf(buf + len, " core_type:ap\n"); |
| len += sprintf(buf + len, " jtag_type:jtag_a|jtag_b|swd_a\n"); |
| |
| return len; |
| } |
| |
| static ssize_t select_store(struct class *cls, |
| struct class_attribute *attr, |
| const char *buffer, size_t count) |
| { |
| struct aml_jtag_dev *jdev; |
| int ret; |
| char tmp[MAX_PARAM_LENGTH] = {0}; |
| |
| jdev = container_of(cls, struct aml_jtag_dev, cls); |
| count = min_t(size_t, MAX_PARAM_LENGTH, count); |
| strncpy(tmp, buffer, count - 1); |
| aml_jtag_option_parse(tmp, &jtag_select, &jtag_cluster); |
| jdev->select = jtag_select; |
| jdev->cluster = jtag_cluster; |
| ret = aml_jtag_setup(jdev); |
| if (ret < 0) |
| return ret; |
| |
| return count; |
| } |
| |
| static CLASS_ATTR_RW(select); |
| |
| static struct attribute *aml_jtag_attrs[] = { |
| &class_attr_select.attr, |
| NULL |
| }; |
| |
| ATTRIBUTE_GROUPS(aml_jtag); |
| |
| static int aml_jtag_dt_parse(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| struct device_node *np = dev->of_node; |
| struct aml_jtag_dev *jdev = platform_get_drvdata(pdev); |
| const char *tmp; |
| int ret; |
| |
| /* otherwise set select with dt */ |
| ret = of_property_read_string(np, "select", &tmp); |
| if (ret < 0) { |
| pr_err("select not configured\n"); |
| return -EINVAL; |
| } |
| aml_jtag_option_parse(tmp, &jdev->select, &jdev->cluster); |
| |
| return 0; |
| } |
| |
| static int aml_jtag_probe(struct platform_device *pdev) |
| { |
| struct aml_jtag_dev *jdev; |
| int ret; |
| int irq; |
| |
| /* kzalloc device */ |
| jdev = devm_kzalloc(&pdev->dev, sizeof(*jdev), GFP_KERNEL); |
| if (!jdev) |
| return -ENOMEM; |
| |
| /* set driver data */ |
| jdev->pdev = pdev; |
| platform_set_drvdata(pdev, jdev); |
| |
| ret = aml_jtag_dt_parse(pdev); |
| if (ret) |
| return -EINVAL; |
| #ifndef CONFIG_MACH_MESON8B |
| if (of_property_read_bool(pdev->dev.of_node, "interrupts")) { |
| irq = platform_get_irq(pdev, 0); |
| ret = devm_request_irq(&pdev->dev, |
| irq, jtag_irq_handler, |
| IRQ_TYPE_EDGE_RISING, "jtag", NULL); |
| if (ret) { |
| dev_err(&pdev->dev, "Requesting jtag irq failed!\n"); |
| return -EINVAL; |
| } |
| } |
| #endif |
| jdev->old_select = AMLOGIC_JTAG_DISABLE; |
| /* if jtag= param is setup, use select with jtag= param */ |
| if (jtag_select_setup) { |
| jdev->select = jtag_select; |
| jdev->cluster = jtag_cluster; |
| } |
| |
| /* create class attributes */ |
| jdev->cls.name = AML_JTAG_NAME; |
| jdev->cls.owner = THIS_MODULE; |
| jdev->cls.class_groups = aml_jtag_groups; |
| ret = class_register(&jdev->cls); |
| if (ret) { |
| pr_err("couldn't register sysfs class\n"); |
| return ret; |
| } |
| |
| /* setup jtag */ |
| ret = aml_jtag_setup(jdev); |
| if (ret < 0) { |
| class_unregister(&jdev->cls); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int __exit aml_jtag_remove(struct platform_device *pdev) |
| { |
| struct aml_jtag_dev *jdev = platform_get_drvdata(pdev); |
| |
| class_unregister(&jdev->cls); |
| platform_set_drvdata(pdev, NULL); |
| |
| return 0; |
| } |
| |
| static const struct of_device_id aml_jtag_dt_match[] = { |
| { |
| .compatible = "amlogic, jtag", |
| }, |
| {}, |
| }; |
| |
| static struct platform_driver aml_jtag_driver = { |
| .driver = { |
| .name = AML_JTAG_NAME, |
| .owner = THIS_MODULE, |
| .of_match_table = aml_jtag_dt_match, |
| }, |
| .probe = aml_jtag_probe, |
| .remove = __exit_p(aml_jtag_remove), |
| }; |
| |
| static int __init aml_jtag_init(void) |
| { |
| return platform_driver_register(&aml_jtag_driver); |
| } |
| /* Jtag will be setuped before device_initcall that most driver used. |
| * But jtag should be after pinmux. |
| * That means we must use some initcall between arch_initcall |
| * and device_initcall. |
| */ |
| fs_initcall(aml_jtag_init); |
| |
| static void __exit aml_jtag_exit(void) |
| { |
| platform_driver_unregister(&aml_jtag_driver); |
| } |
| module_exit(aml_jtag_exit); |
| |
| MODULE_DESCRIPTION("Meson JTAG Driver"); |
| MODULE_LICENSE("GPL"); |
| MODULE_AUTHOR("Amlogic, Inc."); |