forked from torvalds/linux
-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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 exposed to userspace (as before). 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
eec55df
commit dac014d
Showing
7 changed files
with
671 additions
and
3 deletions.
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
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,160 @@ | ||
// SPDX-License-Identifier: GPL-2.0+ | ||
/* | ||
* 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 <linux/platform_data/nxp-ezport.h> | ||
|
||
#define ACHC_MAX_FREQ 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) | ||
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) | ||
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, | ||
}; | ||
|
||
static const struct attribute_group gehc_achc_attr_group = { | ||
.attrs = gehc_achc_attrs, | ||
}; | ||
|
||
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; | ||
spi->bits_per_word = 8; | ||
spi->mode = SPI_MODE_0; | ||
|
||
achc = devm_kzalloc(&spi->dev, sizeof(*achc), GFP_KERNEL); | ||
if (!achc) | ||
return -ENOMEM; | ||
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"); | ||
|
||
/* | ||
* The sysfs properties are bound to the dummy device, since the main device already | ||
* uses drvdata assigned by the spidev driver. | ||
*/ | ||
spi_set_drvdata(achc->ezport, achc); | ||
ret = devm_device_add_group(&achc->ezport->dev, &gehc_achc_attr_group); | ||
if (ret) | ||
return ret; | ||
|
||
/* | ||
* Anything before this must use device managed resources to ensure resources being | ||
* free'd in reverse allocation order. | ||
*/ | ||
ret = spidev_probe(spi); | ||
if (ret) | ||
return ret; | ||
|
||
return 0; | ||
} | ||
|
||
static int gehc_achc_remove(struct spi_device *spi) | ||
{ | ||
return spidev_remove(spi); | ||
} | ||
|
||
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, | ||
}, | ||
.probe = gehc_achc_probe, | ||
.remove = gehc_achc_remove, | ||
.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.