Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Laurentiu Palcu | 3625 | 96.93% | 2 | 22.22% |
Yang Yingliang | 70 | 1.87% | 1 | 11.11% |
Andy Shevchenko | 15 | 0.40% | 1 | 11.11% |
Herve Codina via Alsa-devel | 15 | 0.40% | 1 | 11.11% |
Crestez Dan Leonard | 10 | 0.27% | 1 | 11.11% |
Uwe Kleine-König | 2 | 0.05% | 1 | 11.11% |
Thomas Gleixner | 2 | 0.05% | 1 | 11.11% |
Mark Brown | 1 | 0.03% | 1 | 11.11% |
Total | 3740 | 9 |
// SPDX-License-Identifier: GPL-2.0-only /* * Driver for the Diolan DLN-2 USB-SPI adapter * * Copyright (c) 2014 Intel Corporation */ #include <linux/kernel.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/property.h> #include <linux/mfd/dln2.h> #include <linux/spi/spi.h> #include <linux/pm_runtime.h> #include <asm/unaligned.h> #define DLN2_SPI_MODULE_ID 0x02 #define DLN2_SPI_CMD(cmd) DLN2_CMD(cmd, DLN2_SPI_MODULE_ID) /* SPI commands */ #define DLN2_SPI_GET_PORT_COUNT DLN2_SPI_CMD(0x00) #define DLN2_SPI_ENABLE DLN2_SPI_CMD(0x11) #define DLN2_SPI_DISABLE DLN2_SPI_CMD(0x12) #define DLN2_SPI_IS_ENABLED DLN2_SPI_CMD(0x13) #define DLN2_SPI_SET_MODE DLN2_SPI_CMD(0x14) #define DLN2_SPI_GET_MODE DLN2_SPI_CMD(0x15) #define DLN2_SPI_SET_FRAME_SIZE DLN2_SPI_CMD(0x16) #define DLN2_SPI_GET_FRAME_SIZE DLN2_SPI_CMD(0x17) #define DLN2_SPI_SET_FREQUENCY DLN2_SPI_CMD(0x18) #define DLN2_SPI_GET_FREQUENCY DLN2_SPI_CMD(0x19) #define DLN2_SPI_READ_WRITE DLN2_SPI_CMD(0x1A) #define DLN2_SPI_READ DLN2_SPI_CMD(0x1B) #define DLN2_SPI_WRITE DLN2_SPI_CMD(0x1C) #define DLN2_SPI_SET_DELAY_BETWEEN_SS DLN2_SPI_CMD(0x20) #define DLN2_SPI_GET_DELAY_BETWEEN_SS DLN2_SPI_CMD(0x21) #define DLN2_SPI_SET_DELAY_AFTER_SS DLN2_SPI_CMD(0x22) #define DLN2_SPI_GET_DELAY_AFTER_SS DLN2_SPI_CMD(0x23) #define DLN2_SPI_SET_DELAY_BETWEEN_FRAMES DLN2_SPI_CMD(0x24) #define DLN2_SPI_GET_DELAY_BETWEEN_FRAMES DLN2_SPI_CMD(0x25) #define DLN2_SPI_SET_SS DLN2_SPI_CMD(0x26) #define DLN2_SPI_GET_SS DLN2_SPI_CMD(0x27) #define DLN2_SPI_RELEASE_SS DLN2_SPI_CMD(0x28) #define DLN2_SPI_SS_VARIABLE_ENABLE DLN2_SPI_CMD(0x2B) #define DLN2_SPI_SS_VARIABLE_DISABLE DLN2_SPI_CMD(0x2C) #define DLN2_SPI_SS_VARIABLE_IS_ENABLED DLN2_SPI_CMD(0x2D) #define DLN2_SPI_SS_AAT_ENABLE DLN2_SPI_CMD(0x2E) #define DLN2_SPI_SS_AAT_DISABLE DLN2_SPI_CMD(0x2F) #define DLN2_SPI_SS_AAT_IS_ENABLED DLN2_SPI_CMD(0x30) #define DLN2_SPI_SS_BETWEEN_FRAMES_ENABLE DLN2_SPI_CMD(0x31) #define DLN2_SPI_SS_BETWEEN_FRAMES_DISABLE DLN2_SPI_CMD(0x32) #define DLN2_SPI_SS_BETWEEN_FRAMES_IS_ENABLED DLN2_SPI_CMD(0x33) #define DLN2_SPI_SET_CPHA DLN2_SPI_CMD(0x34) #define DLN2_SPI_GET_CPHA DLN2_SPI_CMD(0x35) #define DLN2_SPI_SET_CPOL DLN2_SPI_CMD(0x36) #define DLN2_SPI_GET_CPOL DLN2_SPI_CMD(0x37) #define DLN2_SPI_SS_MULTI_ENABLE DLN2_SPI_CMD(0x38) #define DLN2_SPI_SS_MULTI_DISABLE DLN2_SPI_CMD(0x39) #define DLN2_SPI_SS_MULTI_IS_ENABLED DLN2_SPI_CMD(0x3A) #define DLN2_SPI_GET_SUPPORTED_MODES DLN2_SPI_CMD(0x40) #define DLN2_SPI_GET_SUPPORTED_CPHA_VALUES DLN2_SPI_CMD(0x41) #define DLN2_SPI_GET_SUPPORTED_CPOL_VALUES DLN2_SPI_CMD(0x42) #define DLN2_SPI_GET_SUPPORTED_FRAME_SIZES DLN2_SPI_CMD(0x43) #define DLN2_SPI_GET_SS_COUNT DLN2_SPI_CMD(0x44) #define DLN2_SPI_GET_MIN_FREQUENCY DLN2_SPI_CMD(0x45) #define DLN2_SPI_GET_MAX_FREQUENCY DLN2_SPI_CMD(0x46) #define DLN2_SPI_GET_MIN_DELAY_BETWEEN_SS DLN2_SPI_CMD(0x47) #define DLN2_SPI_GET_MAX_DELAY_BETWEEN_SS DLN2_SPI_CMD(0x48) #define DLN2_SPI_GET_MIN_DELAY_AFTER_SS DLN2_SPI_CMD(0x49) #define DLN2_SPI_GET_MAX_DELAY_AFTER_SS DLN2_SPI_CMD(0x4A) #define DLN2_SPI_GET_MIN_DELAY_BETWEEN_FRAMES DLN2_SPI_CMD(0x4B) #define DLN2_SPI_GET_MAX_DELAY_BETWEEN_FRAMES DLN2_SPI_CMD(0x4C) #define DLN2_SPI_MAX_XFER_SIZE 256 #define DLN2_SPI_BUF_SIZE (DLN2_SPI_MAX_XFER_SIZE + 16) #define DLN2_SPI_ATTR_LEAVE_SS_LOW BIT(0) #define DLN2_TRANSFERS_WAIT_COMPLETE 1 #define DLN2_TRANSFERS_CANCEL 0 #define DLN2_RPM_AUTOSUSPEND_TIMEOUT 2000 struct dln2_spi { struct platform_device *pdev; struct spi_controller *host; u8 port; /* * This buffer will be used mainly for read/write operations. Since * they're quite large, we cannot use the stack. Protection is not * needed because all SPI communication is serialized by the SPI core. */ void *buf; u8 bpw; u32 speed; u16 mode; u8 cs; }; /* * Enable/Disable SPI module. The disable command will wait for transfers to * complete first. */ static int dln2_spi_enable(struct dln2_spi *dln2, bool enable) { u16 cmd; struct { u8 port; u8 wait_for_completion; } tx; unsigned len = sizeof(tx); tx.port = dln2->port; if (enable) { cmd = DLN2_SPI_ENABLE; len -= sizeof(tx.wait_for_completion); } else { tx.wait_for_completion = DLN2_TRANSFERS_WAIT_COMPLETE; cmd = DLN2_SPI_DISABLE; } return dln2_transfer_tx(dln2->pdev, cmd, &tx, len); } /* * Select/unselect multiple CS lines. The selected lines will be automatically * toggled LOW/HIGH by the board firmware during transfers, provided they're * enabled first. * * Ex: cs_mask = 0x03 -> CS0 & CS1 will be selected and the next WR/RD operation * will toggle the lines LOW/HIGH automatically. */ static int dln2_spi_cs_set(struct dln2_spi *dln2, u8 cs_mask) { struct { u8 port; u8 cs; } tx; tx.port = dln2->port; /* * According to Diolan docs, "a slave device can be selected by changing * the corresponding bit value to 0". The rest must be set to 1. Hence * the bitwise NOT in front. */ tx.cs = ~cs_mask; return dln2_transfer_tx(dln2->pdev, DLN2_SPI_SET_SS, &tx, sizeof(tx)); } /* * Select one CS line. The other lines will be un-selected. */ static int dln2_spi_cs_set_one(struct dln2_spi *dln2, u8 cs) { return dln2_spi_cs_set(dln2, BIT(cs)); } /* * Enable/disable CS lines for usage. The module has to be disabled first. */ static int dln2_spi_cs_enable(struct dln2_spi *dln2, u8 cs_mask, bool enable) { struct { u8 port; u8 cs; } tx; u16 cmd; tx.port = dln2->port; tx.cs = cs_mask; cmd = enable ? DLN2_SPI_SS_MULTI_ENABLE : DLN2_SPI_SS_MULTI_DISABLE; return dln2_transfer_tx(dln2->pdev, cmd, &tx, sizeof(tx)); } static int dln2_spi_cs_enable_all(struct dln2_spi *dln2, bool enable) { u8 cs_mask = GENMASK(dln2->host->num_chipselect - 1, 0); return dln2_spi_cs_enable(dln2, cs_mask, enable); } static int dln2_spi_get_cs_num(struct dln2_spi *dln2, u16 *cs_num) { int ret; struct { u8 port; } tx; struct { __le16 cs_count; } rx; unsigned rx_len = sizeof(rx); tx.port = dln2->port; ret = dln2_transfer(dln2->pdev, DLN2_SPI_GET_SS_COUNT, &tx, sizeof(tx), &rx, &rx_len); if (ret < 0) return ret; if (rx_len < sizeof(rx)) return -EPROTO; *cs_num = le16_to_cpu(rx.cs_count); dev_dbg(&dln2->pdev->dev, "cs_num = %d\n", *cs_num); return 0; } static int dln2_spi_get_speed(struct dln2_spi *dln2, u16 cmd, u32 *freq) { int ret; struct { u8 port; } tx; struct { __le32 speed; } rx; unsigned rx_len = sizeof(rx); tx.port = dln2->port; ret = dln2_transfer(dln2->pdev, cmd, &tx, sizeof(tx), &rx, &rx_len); if (ret < 0) return ret; if (rx_len < sizeof(rx)) return -EPROTO; *freq = le32_to_cpu(rx.speed); return 0; } /* * Get bus min/max frequencies. */ static int dln2_spi_get_speed_range(struct dln2_spi *dln2, u32 *fmin, u32 *fmax) { int ret; ret = dln2_spi_get_speed(dln2, DLN2_SPI_GET_MIN_FREQUENCY, fmin); if (ret < 0) return ret; ret = dln2_spi_get_speed(dln2, DLN2_SPI_GET_MAX_FREQUENCY, fmax); if (ret < 0) return ret; dev_dbg(&dln2->pdev->dev, "freq_min = %d, freq_max = %d\n", *fmin, *fmax); return 0; } /* * Set the bus speed. The module will automatically round down to the closest * available frequency and returns it. The module has to be disabled first. */ static int dln2_spi_set_speed(struct dln2_spi *dln2, u32 speed) { int ret; struct { u8 port; __le32 speed; } __packed tx; struct { __le32 speed; } rx; int rx_len = sizeof(rx); tx.port = dln2->port; tx.speed = cpu_to_le32(speed); ret = dln2_transfer(dln2->pdev, DLN2_SPI_SET_FREQUENCY, &tx, sizeof(tx), &rx, &rx_len); if (ret < 0) return ret; if (rx_len < sizeof(rx)) return -EPROTO; return 0; } /* * Change CPOL & CPHA. The module has to be disabled first. */ static int dln2_spi_set_mode(struct dln2_spi *dln2, u8 mode) { struct { u8 port; u8 mode; } tx; tx.port = dln2->port; tx.mode = mode; return dln2_transfer_tx(dln2->pdev, DLN2_SPI_SET_MODE, &tx, sizeof(tx)); } /* * Change frame size. The module has to be disabled first. */ static int dln2_spi_set_bpw(struct dln2_spi *dln2, u8 bpw) { struct { u8 port; u8 bpw; } tx; tx.port = dln2->port; tx.bpw = bpw; return dln2_transfer_tx(dln2->pdev, DLN2_SPI_SET_FRAME_SIZE, &tx, sizeof(tx)); } static int dln2_spi_get_supported_frame_sizes(struct dln2_spi *dln2, u32 *bpw_mask) { int ret; struct { u8 port; } tx; struct { u8 count; u8 frame_sizes[36]; } *rx = dln2->buf; unsigned rx_len = sizeof(*rx); int i; tx.port = dln2->port; ret = dln2_transfer(dln2->pdev, DLN2_SPI_GET_SUPPORTED_FRAME_SIZES, &tx, sizeof(tx), rx, &rx_len); if (ret < 0) return ret; if (rx_len < sizeof(*rx)) return -EPROTO; if (rx->count > ARRAY_SIZE(rx->frame_sizes)) return -EPROTO; *bpw_mask = 0; for (i = 0; i < rx->count; i++) *bpw_mask |= BIT(rx->frame_sizes[i] - 1); dev_dbg(&dln2->pdev->dev, "bpw_mask = 0x%X\n", *bpw_mask); return 0; } /* * Copy the data to DLN2 buffer and change the byte order to LE, requested by * DLN2 module. SPI core makes sure that the data length is a multiple of word * size. */ static int dln2_spi_copy_to_buf(u8 *dln2_buf, const u8 *src, u16 len, u8 bpw) { #ifdef __LITTLE_ENDIAN memcpy(dln2_buf, src, len); #else if (bpw <= 8) { memcpy(dln2_buf, src, len); } else if (bpw <= 16) { __le16 *d = (__le16 *)dln2_buf; u16 *s = (u16 *)src; len = len / 2; while (len--) *d++ = cpu_to_le16p(s++); } else { __le32 *d = (__le32 *)dln2_buf; u32 *s = (u32 *)src; len = len / 4; while (len--) *d++ = cpu_to_le32p(s++); } #endif return 0; } /* * Copy the data from DLN2 buffer and convert to CPU byte order since the DLN2 * buffer is LE ordered. SPI core makes sure that the data length is a multiple * of word size. The RX dln2_buf is 2 byte aligned so, for BE, we have to make * sure we avoid unaligned accesses for 32 bit case. */ static int dln2_spi_copy_from_buf(u8 *dest, const u8 *dln2_buf, u16 len, u8 bpw) { #ifdef __LITTLE_ENDIAN memcpy(dest, dln2_buf, len); #else if (bpw <= 8) { memcpy(dest, dln2_buf, len); } else if (bpw <= 16) { u16 *d = (u16 *)dest; __le16 *s = (__le16 *)dln2_buf; len = len / 2; while (len--) *d++ = le16_to_cpup(s++); } else { u32 *d = (u32 *)dest; __le32 *s = (__le32 *)dln2_buf; len = len / 4; while (len--) *d++ = get_unaligned_le32(s++); } #endif return 0; } /* * Perform one write operation. */ static int dln2_spi_write_one(struct dln2_spi *dln2, const u8 *data, u16 data_len, u8 attr) { struct { u8 port; __le16 size; u8 attr; u8 buf[DLN2_SPI_MAX_XFER_SIZE]; } __packed *tx = dln2->buf; unsigned tx_len; BUILD_BUG_ON(sizeof(*tx) > DLN2_SPI_BUF_SIZE); if (data_len > DLN2_SPI_MAX_XFER_SIZE) return -EINVAL; tx->port = dln2->port; tx->size = cpu_to_le16(data_len); tx->attr = attr; dln2_spi_copy_to_buf(tx->buf, data, data_len, dln2->bpw); tx_len = sizeof(*tx) + data_len - DLN2_SPI_MAX_XFER_SIZE; return dln2_transfer_tx(dln2->pdev, DLN2_SPI_WRITE, tx, tx_len); } /* * Perform one read operation. */ static int dln2_spi_read_one(struct dln2_spi *dln2, u8 *data, u16 data_len, u8 attr) { int ret; struct { u8 port; __le16 size; u8 attr; } __packed tx; struct { __le16 size; u8 buf[DLN2_SPI_MAX_XFER_SIZE]; } __packed *rx = dln2->buf; unsigned rx_len = sizeof(*rx); BUILD_BUG_ON(sizeof(*rx) > DLN2_SPI_BUF_SIZE); if (data_len > DLN2_SPI_MAX_XFER_SIZE) return -EINVAL; tx.port = dln2->port; tx.size = cpu_to_le16(data_len); tx.attr = attr; ret = dln2_transfer(dln2->pdev, DLN2_SPI_READ, &tx, sizeof(tx), rx, &rx_len); if (ret < 0) return ret; if (rx_len < sizeof(rx->size) + data_len) return -EPROTO; if (le16_to_cpu(rx->size) != data_len) return -EPROTO; dln2_spi_copy_from_buf(data, rx->buf, data_len, dln2->bpw); return 0; } /* * Perform one write & read operation. */ static int dln2_spi_read_write_one(struct dln2_spi *dln2, const u8 *tx_data, u8 *rx_data, u16 data_len, u8 attr) { int ret; struct { u8 port; __le16 size; u8 attr; u8 buf[DLN2_SPI_MAX_XFER_SIZE]; } __packed *tx; struct { __le16 size; u8 buf[DLN2_SPI_MAX_XFER_SIZE]; } __packed *rx; unsigned tx_len, rx_len; BUILD_BUG_ON(sizeof(*tx) > DLN2_SPI_BUF_SIZE || sizeof(*rx) > DLN2_SPI_BUF_SIZE); if (data_len > DLN2_SPI_MAX_XFER_SIZE) return -EINVAL; /* * Since this is a pseudo full-duplex communication, we're perfectly * safe to use the same buffer for both tx and rx. When DLN2 sends the * response back, with the rx data, we don't need the tx buffer anymore. */ tx = dln2->buf; rx = dln2->buf; tx->port = dln2->port; tx->size = cpu_to_le16(data_len); tx->attr = attr; dln2_spi_copy_to_buf(tx->buf, tx_data, data_len, dln2->bpw); tx_len = sizeof(*tx) + data_len - DLN2_SPI_MAX_XFER_SIZE; rx_len = sizeof(*rx); ret = dln2_transfer(dln2->pdev, DLN2_SPI_READ_WRITE, tx, tx_len, rx, &rx_len); if (ret < 0) return ret; if (rx_len < sizeof(rx->size) + data_len) return -EPROTO; if (le16_to_cpu(rx->size) != data_len) return -EPROTO; dln2_spi_copy_from_buf(rx_data, rx->buf, data_len, dln2->bpw); return 0; } /* * Read/Write wrapper. It will automatically split an operation into multiple * single ones due to device buffer constraints. */ static int dln2_spi_rdwr(struct dln2_spi *dln2, const u8 *tx_data, u8 *rx_data, u16 data_len, u8 attr) { int ret; u16 len; u8 temp_attr; u16 remaining = data_len; u16 offset; do { if (remaining > DLN2_SPI_MAX_XFER_SIZE) { len = DLN2_SPI_MAX_XFER_SIZE; temp_attr = DLN2_SPI_ATTR_LEAVE_SS_LOW; } else { len = remaining; temp_attr = attr; } offset = data_len - remaining; if (tx_data && rx_data) { ret = dln2_spi_read_write_one(dln2, tx_data + offset, rx_data + offset, len, temp_attr); } else if (tx_data) { ret = dln2_spi_write_one(dln2, tx_data + offset, len, temp_attr); } else if (rx_data) { ret = dln2_spi_read_one(dln2, rx_data + offset, len, temp_attr); } else { return -EINVAL; } if (ret < 0) return ret; remaining -= len; } while (remaining); return 0; } static int dln2_spi_prepare_message(struct spi_controller *host, struct spi_message *message) { int ret; struct dln2_spi *dln2 = spi_controller_get_devdata(host); struct spi_device *spi = message->spi; if (dln2->cs != spi_get_chipselect(spi, 0)) { ret = dln2_spi_cs_set_one(dln2, spi_get_chipselect(spi, 0)); if (ret < 0) return ret; dln2->cs = spi_get_chipselect(spi, 0); } return 0; } static int dln2_spi_transfer_setup(struct dln2_spi *dln2, u32 speed, u8 bpw, u8 mode) { int ret; bool bus_setup_change; bus_setup_change = dln2->speed != speed || dln2->mode != mode || dln2->bpw != bpw; if (!bus_setup_change) return 0; ret = dln2_spi_enable(dln2, false); if (ret < 0) return ret; if (dln2->speed != speed) { ret = dln2_spi_set_speed(dln2, speed); if (ret < 0) return ret; dln2->speed = speed; } if (dln2->mode != mode) { ret = dln2_spi_set_mode(dln2, mode & 0x3); if (ret < 0) return ret; dln2->mode = mode; } if (dln2->bpw != bpw) { ret = dln2_spi_set_bpw(dln2, bpw); if (ret < 0) return ret; dln2->bpw = bpw; } return dln2_spi_enable(dln2, true); } static int dln2_spi_transfer_one(struct spi_controller *host, struct spi_device *spi, struct spi_transfer *xfer) { struct dln2_spi *dln2 = spi_controller_get_devdata(host); int status; u8 attr = 0; status = dln2_spi_transfer_setup(dln2, xfer->speed_hz, xfer->bits_per_word, spi->mode); if (status < 0) { dev_err(&dln2->pdev->dev, "Cannot setup transfer\n"); return status; } if (!xfer->cs_change && !spi_transfer_is_last(host, xfer)) attr = DLN2_SPI_ATTR_LEAVE_SS_LOW; status = dln2_spi_rdwr(dln2, xfer->tx_buf, xfer->rx_buf, xfer->len, attr); if (status < 0) dev_err(&dln2->pdev->dev, "write/read failed!\n"); return status; } static int dln2_spi_probe(struct platform_device *pdev) { struct spi_controller *host; struct dln2_spi *dln2; struct dln2_platform_data *pdata = dev_get_platdata(&pdev->dev); struct device *dev = &pdev->dev; int ret; host = spi_alloc_host(&pdev->dev, sizeof(*dln2)); if (!host) return -ENOMEM; device_set_node(&host->dev, dev_fwnode(dev)); platform_set_drvdata(pdev, host); dln2 = spi_controller_get_devdata(host); dln2->buf = devm_kmalloc(&pdev->dev, DLN2_SPI_BUF_SIZE, GFP_KERNEL); if (!dln2->buf) { ret = -ENOMEM; goto exit_free_host; } dln2->host = host; dln2->pdev = pdev; dln2->port = pdata->port; /* cs/mode can never be 0xff, so the first transfer will set them */ dln2->cs = 0xff; dln2->mode = 0xff; /* disable SPI module before continuing with the setup */ ret = dln2_spi_enable(dln2, false); if (ret < 0) { dev_err(&pdev->dev, "Failed to disable SPI module\n"); goto exit_free_host; } ret = dln2_spi_get_cs_num(dln2, &host->num_chipselect); if (ret < 0) { dev_err(&pdev->dev, "Failed to get number of CS pins\n"); goto exit_free_host; } ret = dln2_spi_get_speed_range(dln2, &host->min_speed_hz, &host->max_speed_hz); if (ret < 0) { dev_err(&pdev->dev, "Failed to read bus min/max freqs\n"); goto exit_free_host; } ret = dln2_spi_get_supported_frame_sizes(dln2, &host->bits_per_word_mask); if (ret < 0) { dev_err(&pdev->dev, "Failed to read supported frame sizes\n"); goto exit_free_host; } ret = dln2_spi_cs_enable_all(dln2, true); if (ret < 0) { dev_err(&pdev->dev, "Failed to enable CS pins\n"); goto exit_free_host; } host->bus_num = -1; host->mode_bits = SPI_CPOL | SPI_CPHA; host->prepare_message = dln2_spi_prepare_message; host->transfer_one = dln2_spi_transfer_one; host->auto_runtime_pm = true; /* enable SPI module, we're good to go */ ret = dln2_spi_enable(dln2, true); if (ret < 0) { dev_err(&pdev->dev, "Failed to enable SPI module\n"); goto exit_free_host; } pm_runtime_set_autosuspend_delay(&pdev->dev, DLN2_RPM_AUTOSUSPEND_TIMEOUT); pm_runtime_use_autosuspend(&pdev->dev); pm_runtime_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev); ret = devm_spi_register_controller(&pdev->dev, host); if (ret < 0) { dev_err(&pdev->dev, "Failed to register host\n"); goto exit_register; } return ret; exit_register: pm_runtime_disable(&pdev->dev); pm_runtime_set_suspended(&pdev->dev); if (dln2_spi_enable(dln2, false) < 0) dev_err(&pdev->dev, "Failed to disable SPI module\n"); exit_free_host: spi_controller_put(host); return ret; } static void dln2_spi_remove(struct platform_device *pdev) { struct spi_controller *host = platform_get_drvdata(pdev); struct dln2_spi *dln2 = spi_controller_get_devdata(host); pm_runtime_disable(&pdev->dev); if (dln2_spi_enable(dln2, false) < 0) dev_err(&pdev->dev, "Failed to disable SPI module\n"); } #ifdef CONFIG_PM_SLEEP static int dln2_spi_suspend(struct device *dev) { int ret; struct spi_controller *host = dev_get_drvdata(dev); struct dln2_spi *dln2 = spi_controller_get_devdata(host); ret = spi_controller_suspend(host); if (ret < 0) return ret; if (!pm_runtime_suspended(dev)) { ret = dln2_spi_enable(dln2, false); if (ret < 0) return ret; } /* * USB power may be cut off during sleep. Resetting the following * parameters will force the board to be set up before first transfer. */ dln2->cs = 0xff; dln2->speed = 0; dln2->bpw = 0; dln2->mode = 0xff; return 0; } static int dln2_spi_resume(struct device *dev) { int ret; struct spi_controller *host = dev_get_drvdata(dev); struct dln2_spi *dln2 = spi_controller_get_devdata(host); if (!pm_runtime_suspended(dev)) { ret = dln2_spi_cs_enable_all(dln2, true); if (ret < 0) return ret; ret = dln2_spi_enable(dln2, true); if (ret < 0) return ret; } return spi_controller_resume(host); } #endif /* CONFIG_PM_SLEEP */ #ifdef CONFIG_PM static int dln2_spi_runtime_suspend(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); struct dln2_spi *dln2 = spi_controller_get_devdata(host); return dln2_spi_enable(dln2, false); } static int dln2_spi_runtime_resume(struct device *dev) { struct spi_controller *host = dev_get_drvdata(dev); struct dln2_spi *dln2 = spi_controller_get_devdata(host); return dln2_spi_enable(dln2, true); } #endif /* CONFIG_PM */ static const struct dev_pm_ops dln2_spi_pm = { SET_SYSTEM_SLEEP_PM_OPS(dln2_spi_suspend, dln2_spi_resume) SET_RUNTIME_PM_OPS(dln2_spi_runtime_suspend, dln2_spi_runtime_resume, NULL) }; static struct platform_driver spi_dln2_driver = { .driver = { .name = "dln2-spi", .pm = &dln2_spi_pm, }, .probe = dln2_spi_probe, .remove_new = dln2_spi_remove, }; module_platform_driver(spi_dln2_driver); MODULE_DESCRIPTION("Driver for the Diolan DLN2 SPI host interface"); MODULE_AUTHOR("Laurentiu Palcu <laurentiu.palcu@intel.com>"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:dln2-spi");
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1