| /* |
| * Copyright (C) 2018 MediaTek Inc. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| * |
| * 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 http://www.gnu.org/licenses/gpl-2.0.html for more details. |
| */ |
| |
| #include <linux/completion.h> |
| #include <linux/module.h> |
| //nclude <linux/sched/clock.h> |
| #include <linux/spi/spi.h> |
| |
| #include <linux/random.h> |
| |
| #include <linux/clk.h> |
| |
| static bool spis_auto_test_flag; |
| static struct pinctrl *pinctrl_spi;//zhengzhou modify 20201110 start |
| static struct pinctrl_state *default_spi, *spi_gpio;//zhengzhou modify 20201110 start |
| static void spi_slave_txbuf_malloc(struct spi_transfer *trans) |
| { |
| int i; //random; |
| |
| trans->tx_buf = kzalloc(trans->len, GFP_KERNEL); |
| |
| //random = get_random_int() && 0xff; |
| for (i = 0; i < trans->len; i++) |
| *((char *)trans->tx_buf + i) = i + 1;//random + i; |
| } |
| |
| static void spi_slave_rxbuf_malloc(struct spi_transfer *trans) |
| { |
| trans->rx_buf = kzalloc(trans->len, GFP_KERNEL); |
| memset(trans->rx_buf, 0, trans->len); |
| } |
| |
| static void spi_slave_txbuf_free(struct spi_transfer *trans) |
| { |
| kfree(trans->tx_buf); |
| } |
| |
| static void spi_slave_rxbuf_free(struct spi_transfer *trans) |
| { |
| kfree(trans->rx_buf); |
| } |
| |
| static void spi_slave_dump_packet(char *name, u8 *ptr, int len) |
| { |
| int i; |
| |
| pr_info("%s: ", name); |
| for (i = 0; i < len; i++) |
| pr_info(" %02x", ptr[i]); |
| |
| pr_info("\n"); |
| } |
| |
| int spis_loopback_check(struct spi_transfer *trans) |
| { |
| int i, err = 0; |
| pr_info("spis_loopback_check\n"); |
| |
| for (i = 0; i < trans->len; i++) { |
| if (*((u8 *) trans->tx_buf + i) != *((u8 *) trans->rx_buf + i)) |
| err++; |
| } |
| |
| if (err) { |
| pr_info("spis_len:%d, err %d\n", trans->len, err); |
| spi_slave_dump_packet("spis tx", |
| (char *)trans->tx_buf, trans->len); |
| spi_slave_dump_packet("spis rx", trans->rx_buf, trans->len); |
| pr_info("spis test fail."); |
| spis_auto_test_flag = false; |
| return -1; |
| } |
| |
| pr_info("spis_len:%d, err %d\n", trans->len, err); |
| pr_info("spis test pass."); |
| spis_auto_test_flag = true; |
| |
| return 0; |
| } |
| |
| static int spi_slave_txrx_transfer(struct spi_device *spi, int len) |
| { |
| int ret; |
| struct spi_transfer trans; |
| struct spi_message msg; |
| |
| memset(&trans, 0, sizeof(trans)); |
| trans.len = len; |
| |
| spi_slave_txbuf_malloc(&trans); |
| spi_slave_rxbuf_malloc(&trans); |
| |
| spi_message_init(&msg); |
| spi_message_add_tail(&trans, &msg); |
| |
| ret = spi_sync(spi, &msg); |
| if (ret < 0) |
| pr_info("Message transfer err,line(%d):%d\n", __LINE__, ret); |
| |
| spis_loopback_check(&trans); |
| |
| spi_slave_txbuf_free(&trans); |
| spi_slave_rxbuf_free(&trans); |
| |
| return ret; |
| } |
| |
| static int spi_slave_tx_transfer(struct spi_device *spi, int len) |
| { |
| int ret; |
| struct spi_transfer trans; |
| struct spi_message msg; |
| |
| memset(&trans, 0, sizeof(trans)); |
| trans.len = len; |
| |
| spi_slave_txbuf_malloc(&trans); |
| |
| spi_message_init(&msg); |
| spi_message_add_tail(&trans, &msg); |
| |
| ret = spi_sync(spi, &msg); |
| if (ret < 0) |
| pr_info("Message transfer err,line(%d):%d\n", __LINE__, ret); |
| |
| spi_slave_dump_packet("spis tx", (char *)trans.tx_buf, len); |
| |
| spi_slave_txbuf_free(&trans); |
| |
| return ret; |
| } |
| |
| static int spi_slave_rx_transfer(struct spi_device *spi, int len) |
| { |
| int ret; |
| struct spi_transfer trans; |
| struct spi_message msg; |
| |
| memset(&trans, 0, sizeof(trans)); |
| trans.len = len; |
| |
| spi_slave_rxbuf_malloc(&trans); |
| |
| spi_message_init(&msg); |
| spi_message_add_tail(&trans, &msg); |
| |
| ret = spi_sync(spi, &msg); |
| if (ret < 0) |
| pr_info("Message transfer err,line(%d):%d\n", __LINE__, ret); |
| |
| spi_slave_dump_packet("spis rx", trans.rx_buf, len); |
| |
| spi_slave_rxbuf_free(&trans); |
| |
| return ret; |
| } |
| |
| static ssize_t spis_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| char *bp = buf; |
| |
| if (spis_auto_test_flag) |
| bp += sprintf(bp, "spis talk with spim pass\n"); |
| else |
| bp += sprintf(bp, "spis talk with spim fail\n"); |
| |
| *bp++ = '\n'; |
| |
| return bp - buf; |
| } |
| |
| static ssize_t spis_store(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| int len;//ret = 0; |
| // int addr, old, new, reg_val; |
| struct spi_device *spi = container_of(dev, struct spi_device, dev); |
| // struct mtk_spi_slave *mdata = spi_get_drvdata(spi); |
| |
| if (!strncmp(buf, "txrx", 4)) { |
| buf += 5; |
| if (!strncmp(buf, "len=", 4)) |
| if (sscanf(buf+4, "%d", &len) > 0) |
| spi_slave_txrx_transfer(spi, len); |
| } else if (!strncmp(buf, "onlytx", 6)) { |
| buf += 7; |
| if (!strncmp(buf, "len=", 4)) |
| if (sscanf(buf+4, "%d", &len) > 0) |
| spi_slave_tx_transfer(spi, len); |
| } else if (!strncmp(buf, "onlyrx", 6)) { |
| buf += 7; |
| if (!strncmp(buf, "len=", 4)) |
| if (sscanf(buf+4, "%d", &len) > 0) |
| spi_slave_rx_transfer(spi, len); |
| } |
| //else if (!strncmp(buf, "abort", 5)) { |
| // spi_slave_abort(spi); |
| //} |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR(spi, 0644, spis_show, spis_store); |
| |
| static struct device_attribute *spi_attribute[] = { |
| &dev_attr_spi, |
| }; |
| static void spi_create_attribute(struct device *dev) |
| { |
| int size, idx; |
| |
| size = ARRAY_SIZE(spi_attribute); |
| for (idx = 0; idx < size; idx++) |
| device_create_file(dev, spi_attribute[idx]); |
| } |
| //zhengzhou modify 20201110 start |
| static int spi_get_pinctrl_info(struct spi_device *spi) |
| { |
| int ret; |
| |
| pinctrl_spi = devm_pinctrl_get(&spi->dev); |
| if (IS_ERR(pinctrl_spi)) { |
| ret = PTR_ERR(pinctrl_spi); |
| pr_notice("Cannot find pinctrl_spi!\n"); |
| return ret; |
| } |
| |
| default_spi = pinctrl_lookup_state(pinctrl_spi, "default"); |
| if (IS_ERR(default_spi)) { |
| ret = PTR_ERR(default_spi); |
| pr_notice("Cannot find pinctrl default %d!\n", ret); |
| } |
| |
| //spi_gpio = pinctrl_lookup_state(pinctrl_spi, "spi_gpio_mode"); |
| //if (IS_ERR(spi_gpio)) { |
| // ret = PTR_ERR(spi_gpio); |
| // pr_notice("Cannot find pinctrl spi_gpio_mode %d!\n", ret); |
| //} |
| |
| return 0; |
| } |
| //zhengzhou modify 20201110 end |
| static int spi_slave_mt27xx_test_probe(struct spi_device *spi) |
| { |
| spi_get_pinctrl_info(spi);//zhengzhou modify 20201110 |
| pinctrl_select_state(pinctrl_spi, default_spi);//zhengzhou modify 20201110 |
| spi_create_attribute(&spi->dev); |
| return 0; |
| } |
| |
| static int spi_slave_mt27xx_test_remove(struct spi_device *spi) |
| { |
| //spi_slave_abort(spi); |
| return 0; |
| } |
| //zhengzhou modify 20201110 start |
| static const struct of_device_id spisdev_dt_ids[] = { |
| { .compatible = "mediatek,spi-slave-mt27xx-test" }, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(of, spisdev_dt_ids); |
| //zhengzhou modify 20201110 start end |
| static struct spi_driver spi_slave_mt27xx_test_driver = { |
| .driver = { |
| .name = "spi-slave-mt27xx-test", |
| .of_match_table = spisdev_dt_ids,//zhengzhou modify 20201110 |
| }, |
| .probe = spi_slave_mt27xx_test_probe, |
| .remove = spi_slave_mt27xx_test_remove, |
| }; |
| module_spi_driver(spi_slave_mt27xx_test_driver); |
| |
| MODULE_AUTHOR("Mediatek"); |
| MODULE_LICENSE("GPL v2"); |