forked from torvalds/linux
Permalink
Show file tree
Hide file tree
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
misc: gehc-achc: new driver
General Electric Healthcare's PPD has a secondary processor from NXP's Kinetis K20 series. That device has two SPI chip selects: The main interface's behaviour depends on the loaded firmware and is currently unused. The secondary interface can be used to update the firmware using EzPort protocol. This is implemented by this driver using the kernel's firmware API. It's not done during probe time, since the device has non-volatile memory and flashing lasts almost 3 minutes. Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
- Loading branch information
1 parent
af060d2
commit f355764ca45490e52adcd1c4e9886c42fdd66b58
Showing
7 changed files
with
640 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| What: /sys/bus/spi/<dev>/update_firmware | ||
| Date: Jun 2021 | ||
| Contact: sebastian.reichel@collabora.com | ||
| Description: Write 1 to this file to update the ACHC microcontroller | ||
| firmware via the EzPort interface. For this the kernel | ||
| will load "achc.bin" via the firmware API (so usually | ||
| from /lib/firmware). The write will block until the FW | ||
| has either been flashed successfully or an error occured. | ||
|
|
||
| What: /sys/bus/spi/<dev>/reset | ||
| Date: Jun 2021 | ||
| Contact: sebastian.reichel@collabora.com | ||
| Description: Write 1 to this file to reset the microcontroller via the | ||
| reset GPIO. The write will block until the reset completes. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| // SPDX-License-Identifier: GPL-2.0-only | ||
| /* | ||
| * datasheet: https://www.nxp.com/docs/en/data-sheet/K20P144M120SF3.pdf | ||
| * | ||
| * Copyright (C) 2018-2021 Collabora | ||
| * Copyright (C) 2018-2021 GE Healthcare | ||
| */ | ||
|
|
||
| #include <linux/kernel.h> | ||
| #include <linux/module.h> | ||
| #include <linux/spi/spi.h> | ||
| #include <linux/of.h> | ||
| #include "nxp-ezport.h" | ||
|
|
||
| #define ACHC_MAX_FREQ_HZ 300000 | ||
|
|
||
| struct achc_data { | ||
| struct spi_device *main; | ||
| struct spi_device *ezport; | ||
| struct gpio_desc *reset; | ||
|
|
||
| struct mutex device_lock; /* avoid concurrent device access */ | ||
| }; | ||
|
|
||
| static ssize_t update_firmware_store(struct device *dev, struct device_attribute *attr, | ||
| const char *buf, size_t count) | ||
| { | ||
| struct achc_data *achc = dev_get_drvdata(dev); | ||
| int ret; | ||
|
|
||
| if (count != 1 || buf[0] != '1') | ||
| return -EINVAL; | ||
|
|
||
| mutex_lock(&achc->device_lock); | ||
| ret = ezport_flash(achc->ezport, achc->reset, "achc.bin"); | ||
| mutex_unlock(&achc->device_lock); | ||
|
|
||
| if (ret < 0) | ||
| return ret; | ||
|
|
||
| return count; | ||
| } | ||
| static DEVICE_ATTR_WO(update_firmware); | ||
|
|
||
| static ssize_t reset_store(struct device *dev, struct device_attribute *attr, | ||
| const char *buf, size_t count) | ||
| { | ||
| struct achc_data *achc = dev_get_drvdata(dev); | ||
|
|
||
| if (count != 1 || buf[0] != '1') | ||
| return -EINVAL; | ||
|
|
||
| mutex_lock(&achc->device_lock); | ||
| ezport_reset(achc->reset); | ||
| mutex_unlock(&achc->device_lock); | ||
|
|
||
| return count; | ||
| } | ||
| static DEVICE_ATTR_WO(reset); | ||
|
|
||
| static struct attribute *gehc_achc_attrs[] = { | ||
| &dev_attr_update_firmware.attr, | ||
| &dev_attr_reset.attr, | ||
| NULL, | ||
| }; | ||
| ATTRIBUTE_GROUPS(gehc_achc); | ||
|
|
||
| static void unregister_ezport(void *data) | ||
| { | ||
| struct spi_device *ezport = data; | ||
|
|
||
| spi_unregister_device(ezport); | ||
| } | ||
|
|
||
| static int gehc_achc_probe(struct spi_device *spi) | ||
| { | ||
| struct achc_data *achc; | ||
| int ezport_reg, ret; | ||
|
|
||
| spi->max_speed_hz = ACHC_MAX_FREQ_HZ; | ||
| spi->bits_per_word = 8; | ||
| spi->mode = SPI_MODE_0; | ||
|
|
||
| achc = devm_kzalloc(&spi->dev, sizeof(*achc), GFP_KERNEL); | ||
| if (!achc) | ||
| return -ENOMEM; | ||
| spi_set_drvdata(spi, achc); | ||
| achc->main = spi; | ||
|
|
||
| mutex_init(&achc->device_lock); | ||
|
|
||
| ret = of_property_read_u32_index(spi->dev.of_node, "reg", 1, &ezport_reg); | ||
| if (ret) | ||
| return dev_err_probe(&spi->dev, ret, "missing second reg entry!\n"); | ||
|
|
||
| achc->ezport = spi_new_ancillary_device(spi, ezport_reg); | ||
| if (IS_ERR(achc->ezport)) | ||
| return PTR_ERR(achc->ezport); | ||
|
|
||
| ret = devm_add_action_or_reset(&spi->dev, unregister_ezport, achc->ezport); | ||
| if (ret) | ||
| return ret; | ||
|
|
||
| achc->reset = devm_gpiod_get(&spi->dev, "reset", GPIOD_OUT_LOW); | ||
| if (IS_ERR(achc->reset)) | ||
| return dev_err_probe(&spi->dev, PTR_ERR(achc->reset), "Could not get reset gpio\n"); | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
| static const struct spi_device_id gehc_achc_id[] = { | ||
| { "ge,achc", 0 }, | ||
| { } | ||
| }; | ||
| MODULE_DEVICE_TABLE(spi, gehc_achc_id); | ||
|
|
||
| static const struct of_device_id gehc_achc_of_match[] = { | ||
| { .compatible = "ge,achc" }, | ||
| { /* sentinel */ } | ||
| }; | ||
| MODULE_DEVICE_TABLE(of, gehc_achc_of_match); | ||
|
|
||
| static struct spi_driver gehc_achc_spi_driver = { | ||
| .driver = { | ||
| .name = "gehc-achc", | ||
| .of_match_table = gehc_achc_of_match, | ||
| .dev_groups = gehc_achc_groups, | ||
| }, | ||
| .probe = gehc_achc_probe, | ||
| .id_table = gehc_achc_id, | ||
| }; | ||
| module_spi_driver(gehc_achc_spi_driver); | ||
|
|
||
| MODULE_DESCRIPTION("GEHC ACHC driver"); | ||
| MODULE_AUTHOR("Sebastian Reichel <sebastian.reichel@collabora.com>"); | ||
| MODULE_LICENSE("GPL"); |
Oops, something went wrong.