mirror of
https://github.com/torvalds/linux.git
synced 2024-12-30 14:52:05 +00:00
c0f486fde3
- Fix a regression in leds-gpio introduced by a recent commit that inadvertently changed the name of one of the properties used by the driver (Fabio Estevam). - Fix a regression in the ACPI backlight driver introduced by a recent fix that missed one special case that had to be taken into account (Aaron Lu). - Drop the level of some new kernel messages from the ACPI core introduced by a recent commit to KERN_DEBUG which they should have used from the start and drop some other unuseful KERN_ERR messages printed by ACPI (Rafael J Wysocki). - Revert an incorrect commit modifying the cpupower tool (Prarit Bhargava). - Fix two regressions introduced by recent commits in the OPP library and clean up some existing minor issues in that code (Viresh Kumar). - Continue to replace CONFIG_PM_RUNTIME with CONFIG_PM throughout the tree (or drop it where that can be done) in order to make it possible to eliminate CONFIG_PM_RUNTIME (Rafael J Wysocki, Ulf Hansson, Ludovic Desroches). There will be one more "CONFIG_PM_RUNTIME removal" batch after this one, because some new uses of it have been introduced during the current merge window, but that should be sufficient to finally get rid of it. - Make the ACPI EC driver more robust against race conditions related to GPE handler installation failures (Lv Zheng). - Prevent the ACPI device PM core code from attempting to disable GPEs that it has not enabled which confuses ACPICA and makes it report errors unnecessarily (Rafael J Wysocki). - Add a "force" command line switch to the intel_pstate driver to make it possible to override the blacklisting of some systems in that driver if needed (Ethan Zhao). - Improve intel_pstate code documentation and add a MAINTAINERS entry for it (Kristen Carlson Accardi). - Make the ACPI fan driver create cooling device interfaces witn names that reflect the IDs of the ACPI device objects they are associated with, except for "generic" ACPI fans (PNP ID "PNP0C0B"). That's necessary for user space thermal management tools to be able to connect the fans with the parts of the system they are supposed to be cooling properly. From Srinivas Pandruvada. / -----BEGIN PGP SIGNATURE----- Version: GnuPG v2.0.22 (GNU/Linux) iQIcBAABCAAGBQJUk0IDAAoJEILEb/54YlRx7fgP/3+yF/0TnEW93j2ALDAQFiLF tSv2A2vQC8vtMJjjWx0z/HqPh86gfaReEFZmUJD/Q/e2LXEnxNZJ+QMjcekPVkDM mTvcIMc2MR8vOA/oMkgxeaKregrrx7RkCfojd+NWZhVukkjl+mvBHgAnYjXRL+NZ unDWGlbHG97vq/3kGjPYhDS00nxHblw8NHFBu5HL5RxwABdWoeZJITwqxXWyuPLw nlqNWlOxmwvtSbw2VMKz0uof1nFHyQLykYsMG0ZsyayCRdWUZYkEqmE7GGpCLkLu D6yfmlpen6ccIOsEAae0eXBt50IFY9Tihk5lovx1mZmci2SNRg29BqMI105wIn0u 8b8Ej7MNHp7yMxRpB5WfU90p/y7ioJns9guFZxY0CKaRnrI2+BLt3RscMi3MPI06 Cu2/WkSSa09fhDPA+pk+VDYsmWgyVawigesNmMP5/cvYO/yYywVRjOuO1k77qQGp 4dSpFYEHfpxinejZnVZOk2V9MkvSLoSMux6wPV0xM0IE1iD0ulVpHjTJrwp80ph4 +bfUFVr/vrD1y7EKbf1PD363ZKvJhWhvQWDgETsM1vgLf21PfWO7C2kflIAsWsdQ 1ukD5nCBRlP4K73hG7bdM6kRztXhUdR0SHg85/t0KB/ExiVqtcXIzB60D0G1lENd QlKbq3O4lim1WGuhazQY =5fo2 -----END PGP SIGNATURE----- Merge tag 'pm+acpi-3.19-rc1-2' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm Pull more ACPI and power management updates from Rafael Wysocki: "These are regression fixes (leds-gpio, ACPI backlight driver, operating performance points library, ACPI device enumeration messages, cpupower tool), other bug fixes (ACPI EC driver, ACPI device PM), some cleanups in the operating performance points (OPP) framework, continuation of CONFIG_PM_RUNTIME elimination, a couple of minor intel_pstate driver changes, a new MAINTAINERS entry for it and an ACPI fan driver change needed for better support of thermal management in user space. Specifics: - Fix a regression in leds-gpio introduced by a recent commit that inadvertently changed the name of one of the properties used by the driver (Fabio Estevam). - Fix a regression in the ACPI backlight driver introduced by a recent fix that missed one special case that had to be taken into account (Aaron Lu). - Drop the level of some new kernel messages from the ACPI core introduced by a recent commit to KERN_DEBUG which they should have used from the start and drop some other unuseful KERN_ERR messages printed by ACPI (Rafael J Wysocki). - Revert an incorrect commit modifying the cpupower tool (Prarit Bhargava). - Fix two regressions introduced by recent commits in the OPP library and clean up some existing minor issues in that code (Viresh Kumar). - Continue to replace CONFIG_PM_RUNTIME with CONFIG_PM throughout the tree (or drop it where that can be done) in order to make it possible to eliminate CONFIG_PM_RUNTIME (Rafael J Wysocki, Ulf Hansson, Ludovic Desroches). There will be one more "CONFIG_PM_RUNTIME removal" batch after this one, because some new uses of it have been introduced during the current merge window, but that should be sufficient to finally get rid of it. - Make the ACPI EC driver more robust against race conditions related to GPE handler installation failures (Lv Zheng). - Prevent the ACPI device PM core code from attempting to disable GPEs that it has not enabled which confuses ACPICA and makes it report errors unnecessarily (Rafael J Wysocki). - Add a "force" command line switch to the intel_pstate driver to make it possible to override the blacklisting of some systems in that driver if needed (Ethan Zhao). - Improve intel_pstate code documentation and add a MAINTAINERS entry for it (Kristen Carlson Accardi). - Make the ACPI fan driver create cooling device interfaces witn names that reflect the IDs of the ACPI device objects they are associated with, except for "generic" ACPI fans (PNP ID "PNP0C0B"). That's necessary for user space thermal management tools to be able to connect the fans with the parts of the system they are supposed to be cooling properly. From Srinivas Pandruvada" * tag 'pm+acpi-3.19-rc1-2' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm: (32 commits) MAINTAINERS: add entry for intel_pstate ACPI / video: update the skip case for acpi_video_device_in_dod() power / PM: Eliminate CONFIG_PM_RUNTIME NFC / PM: Replace CONFIG_PM_RUNTIME with CONFIG_PM SCSI / PM: Replace CONFIG_PM_RUNTIME with CONFIG_PM ACPI / EC: Fix unexpected ec_remove_handlers() invocations Revert "tools: cpupower: fix return checks for sysfs_get_idlestate_count()" tracing / PM: Replace CONFIG_PM_RUNTIME with CONFIG_PM x86 / PM: Replace CONFIG_PM_RUNTIME in io_apic.c PM: Remove the SET_PM_RUNTIME_PM_OPS() macro mmc: atmel-mci: use SET_RUNTIME_PM_OPS() macro PM / Kconfig: Replace PM_RUNTIME with PM in dependencies ARM / PM: Replace CONFIG_PM_RUNTIME with CONFIG_PM sound / PM: Replace CONFIG_PM_RUNTIME with CONFIG_PM phy / PM: Replace CONFIG_PM_RUNTIME with CONFIG_PM video / PM: Replace CONFIG_PM_RUNTIME with CONFIG_PM tty / PM: Replace CONFIG_PM_RUNTIME with CONFIG_PM spi: Replace CONFIG_PM_RUNTIME with CONFIG_PM ACPI / PM: Do not disable wakeup GPEs that have not been enabled ACPI / utils: Drop error messages from acpi_evaluate_reference() ...
769 lines
20 KiB
C
769 lines
20 KiB
C
/*
|
|
* Copyright (c) 2008-2014, The Linux foundation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License rev 2 and
|
|
* only rev 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 the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/err.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/io.h>
|
|
#include <linux/list.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/spi/spi.h>
|
|
|
|
#define QUP_CONFIG 0x0000
|
|
#define QUP_STATE 0x0004
|
|
#define QUP_IO_M_MODES 0x0008
|
|
#define QUP_SW_RESET 0x000c
|
|
#define QUP_OPERATIONAL 0x0018
|
|
#define QUP_ERROR_FLAGS 0x001c
|
|
#define QUP_ERROR_FLAGS_EN 0x0020
|
|
#define QUP_OPERATIONAL_MASK 0x0028
|
|
#define QUP_HW_VERSION 0x0030
|
|
#define QUP_MX_OUTPUT_CNT 0x0100
|
|
#define QUP_OUTPUT_FIFO 0x0110
|
|
#define QUP_MX_WRITE_CNT 0x0150
|
|
#define QUP_MX_INPUT_CNT 0x0200
|
|
#define QUP_MX_READ_CNT 0x0208
|
|
#define QUP_INPUT_FIFO 0x0218
|
|
|
|
#define SPI_CONFIG 0x0300
|
|
#define SPI_IO_CONTROL 0x0304
|
|
#define SPI_ERROR_FLAGS 0x0308
|
|
#define SPI_ERROR_FLAGS_EN 0x030c
|
|
|
|
/* QUP_CONFIG fields */
|
|
#define QUP_CONFIG_SPI_MODE (1 << 8)
|
|
#define QUP_CONFIG_CLOCK_AUTO_GATE BIT(13)
|
|
#define QUP_CONFIG_NO_INPUT BIT(7)
|
|
#define QUP_CONFIG_NO_OUTPUT BIT(6)
|
|
#define QUP_CONFIG_N 0x001f
|
|
|
|
/* QUP_STATE fields */
|
|
#define QUP_STATE_VALID BIT(2)
|
|
#define QUP_STATE_RESET 0
|
|
#define QUP_STATE_RUN 1
|
|
#define QUP_STATE_PAUSE 3
|
|
#define QUP_STATE_MASK 3
|
|
#define QUP_STATE_CLEAR 2
|
|
|
|
#define QUP_HW_VERSION_2_1_1 0x20010001
|
|
|
|
/* QUP_IO_M_MODES fields */
|
|
#define QUP_IO_M_PACK_EN BIT(15)
|
|
#define QUP_IO_M_UNPACK_EN BIT(14)
|
|
#define QUP_IO_M_INPUT_MODE_MASK_SHIFT 12
|
|
#define QUP_IO_M_OUTPUT_MODE_MASK_SHIFT 10
|
|
#define QUP_IO_M_INPUT_MODE_MASK (3 << QUP_IO_M_INPUT_MODE_MASK_SHIFT)
|
|
#define QUP_IO_M_OUTPUT_MODE_MASK (3 << QUP_IO_M_OUTPUT_MODE_MASK_SHIFT)
|
|
|
|
#define QUP_IO_M_OUTPUT_BLOCK_SIZE(x) (((x) & (0x03 << 0)) >> 0)
|
|
#define QUP_IO_M_OUTPUT_FIFO_SIZE(x) (((x) & (0x07 << 2)) >> 2)
|
|
#define QUP_IO_M_INPUT_BLOCK_SIZE(x) (((x) & (0x03 << 5)) >> 5)
|
|
#define QUP_IO_M_INPUT_FIFO_SIZE(x) (((x) & (0x07 << 7)) >> 7)
|
|
|
|
#define QUP_IO_M_MODE_FIFO 0
|
|
#define QUP_IO_M_MODE_BLOCK 1
|
|
#define QUP_IO_M_MODE_DMOV 2
|
|
#define QUP_IO_M_MODE_BAM 3
|
|
|
|
/* QUP_OPERATIONAL fields */
|
|
#define QUP_OP_MAX_INPUT_DONE_FLAG BIT(11)
|
|
#define QUP_OP_MAX_OUTPUT_DONE_FLAG BIT(10)
|
|
#define QUP_OP_IN_SERVICE_FLAG BIT(9)
|
|
#define QUP_OP_OUT_SERVICE_FLAG BIT(8)
|
|
#define QUP_OP_IN_FIFO_FULL BIT(7)
|
|
#define QUP_OP_OUT_FIFO_FULL BIT(6)
|
|
#define QUP_OP_IN_FIFO_NOT_EMPTY BIT(5)
|
|
#define QUP_OP_OUT_FIFO_NOT_EMPTY BIT(4)
|
|
|
|
/* QUP_ERROR_FLAGS and QUP_ERROR_FLAGS_EN fields */
|
|
#define QUP_ERROR_OUTPUT_OVER_RUN BIT(5)
|
|
#define QUP_ERROR_INPUT_UNDER_RUN BIT(4)
|
|
#define QUP_ERROR_OUTPUT_UNDER_RUN BIT(3)
|
|
#define QUP_ERROR_INPUT_OVER_RUN BIT(2)
|
|
|
|
/* SPI_CONFIG fields */
|
|
#define SPI_CONFIG_HS_MODE BIT(10)
|
|
#define SPI_CONFIG_INPUT_FIRST BIT(9)
|
|
#define SPI_CONFIG_LOOPBACK BIT(8)
|
|
|
|
/* SPI_IO_CONTROL fields */
|
|
#define SPI_IO_C_FORCE_CS BIT(11)
|
|
#define SPI_IO_C_CLK_IDLE_HIGH BIT(10)
|
|
#define SPI_IO_C_MX_CS_MODE BIT(8)
|
|
#define SPI_IO_C_CS_N_POLARITY_0 BIT(4)
|
|
#define SPI_IO_C_CS_SELECT(x) (((x) & 3) << 2)
|
|
#define SPI_IO_C_CS_SELECT_MASK 0x000c
|
|
#define SPI_IO_C_TRISTATE_CS BIT(1)
|
|
#define SPI_IO_C_NO_TRI_STATE BIT(0)
|
|
|
|
/* SPI_ERROR_FLAGS and SPI_ERROR_FLAGS_EN fields */
|
|
#define SPI_ERROR_CLK_OVER_RUN BIT(1)
|
|
#define SPI_ERROR_CLK_UNDER_RUN BIT(0)
|
|
|
|
#define SPI_NUM_CHIPSELECTS 4
|
|
|
|
/* high speed mode is when bus rate is greater then 26MHz */
|
|
#define SPI_HS_MIN_RATE 26000000
|
|
#define SPI_MAX_RATE 50000000
|
|
|
|
#define SPI_DELAY_THRESHOLD 1
|
|
#define SPI_DELAY_RETRY 10
|
|
|
|
struct spi_qup {
|
|
void __iomem *base;
|
|
struct device *dev;
|
|
struct clk *cclk; /* core clock */
|
|
struct clk *iclk; /* interface clock */
|
|
int irq;
|
|
spinlock_t lock;
|
|
|
|
int in_fifo_sz;
|
|
int out_fifo_sz;
|
|
int in_blk_sz;
|
|
int out_blk_sz;
|
|
|
|
struct spi_transfer *xfer;
|
|
struct completion done;
|
|
int error;
|
|
int w_size; /* bytes per SPI word */
|
|
int tx_bytes;
|
|
int rx_bytes;
|
|
int qup_v1;
|
|
};
|
|
|
|
|
|
static inline bool spi_qup_is_valid_state(struct spi_qup *controller)
|
|
{
|
|
u32 opstate = readl_relaxed(controller->base + QUP_STATE);
|
|
|
|
return opstate & QUP_STATE_VALID;
|
|
}
|
|
|
|
static int spi_qup_set_state(struct spi_qup *controller, u32 state)
|
|
{
|
|
unsigned long loop;
|
|
u32 cur_state;
|
|
|
|
loop = 0;
|
|
while (!spi_qup_is_valid_state(controller)) {
|
|
|
|
usleep_range(SPI_DELAY_THRESHOLD, SPI_DELAY_THRESHOLD * 2);
|
|
|
|
if (++loop > SPI_DELAY_RETRY)
|
|
return -EIO;
|
|
}
|
|
|
|
if (loop)
|
|
dev_dbg(controller->dev, "invalid state for %ld,us %d\n",
|
|
loop, state);
|
|
|
|
cur_state = readl_relaxed(controller->base + QUP_STATE);
|
|
/*
|
|
* Per spec: for PAUSE_STATE to RESET_STATE, two writes
|
|
* of (b10) are required
|
|
*/
|
|
if (((cur_state & QUP_STATE_MASK) == QUP_STATE_PAUSE) &&
|
|
(state == QUP_STATE_RESET)) {
|
|
writel_relaxed(QUP_STATE_CLEAR, controller->base + QUP_STATE);
|
|
writel_relaxed(QUP_STATE_CLEAR, controller->base + QUP_STATE);
|
|
} else {
|
|
cur_state &= ~QUP_STATE_MASK;
|
|
cur_state |= state;
|
|
writel_relaxed(cur_state, controller->base + QUP_STATE);
|
|
}
|
|
|
|
loop = 0;
|
|
while (!spi_qup_is_valid_state(controller)) {
|
|
|
|
usleep_range(SPI_DELAY_THRESHOLD, SPI_DELAY_THRESHOLD * 2);
|
|
|
|
if (++loop > SPI_DELAY_RETRY)
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void spi_qup_fifo_read(struct spi_qup *controller,
|
|
struct spi_transfer *xfer)
|
|
{
|
|
u8 *rx_buf = xfer->rx_buf;
|
|
u32 word, state;
|
|
int idx, shift, w_size;
|
|
|
|
w_size = controller->w_size;
|
|
|
|
while (controller->rx_bytes < xfer->len) {
|
|
|
|
state = readl_relaxed(controller->base + QUP_OPERATIONAL);
|
|
if (0 == (state & QUP_OP_IN_FIFO_NOT_EMPTY))
|
|
break;
|
|
|
|
word = readl_relaxed(controller->base + QUP_INPUT_FIFO);
|
|
|
|
if (!rx_buf) {
|
|
controller->rx_bytes += w_size;
|
|
continue;
|
|
}
|
|
|
|
for (idx = 0; idx < w_size; idx++, controller->rx_bytes++) {
|
|
/*
|
|
* The data format depends on bytes per SPI word:
|
|
* 4 bytes: 0x12345678
|
|
* 2 bytes: 0x00001234
|
|
* 1 byte : 0x00000012
|
|
*/
|
|
shift = BITS_PER_BYTE;
|
|
shift *= (w_size - idx - 1);
|
|
rx_buf[controller->rx_bytes] = word >> shift;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void spi_qup_fifo_write(struct spi_qup *controller,
|
|
struct spi_transfer *xfer)
|
|
{
|
|
const u8 *tx_buf = xfer->tx_buf;
|
|
u32 word, state, data;
|
|
int idx, w_size;
|
|
|
|
w_size = controller->w_size;
|
|
|
|
while (controller->tx_bytes < xfer->len) {
|
|
|
|
state = readl_relaxed(controller->base + QUP_OPERATIONAL);
|
|
if (state & QUP_OP_OUT_FIFO_FULL)
|
|
break;
|
|
|
|
word = 0;
|
|
for (idx = 0; idx < w_size; idx++, controller->tx_bytes++) {
|
|
|
|
if (!tx_buf) {
|
|
controller->tx_bytes += w_size;
|
|
break;
|
|
}
|
|
|
|
data = tx_buf[controller->tx_bytes];
|
|
word |= data << (BITS_PER_BYTE * (3 - idx));
|
|
}
|
|
|
|
writel_relaxed(word, controller->base + QUP_OUTPUT_FIFO);
|
|
}
|
|
}
|
|
|
|
static irqreturn_t spi_qup_qup_irq(int irq, void *dev_id)
|
|
{
|
|
struct spi_qup *controller = dev_id;
|
|
struct spi_transfer *xfer;
|
|
u32 opflags, qup_err, spi_err;
|
|
unsigned long flags;
|
|
int error = 0;
|
|
|
|
spin_lock_irqsave(&controller->lock, flags);
|
|
xfer = controller->xfer;
|
|
controller->xfer = NULL;
|
|
spin_unlock_irqrestore(&controller->lock, flags);
|
|
|
|
qup_err = readl_relaxed(controller->base + QUP_ERROR_FLAGS);
|
|
spi_err = readl_relaxed(controller->base + SPI_ERROR_FLAGS);
|
|
opflags = readl_relaxed(controller->base + QUP_OPERATIONAL);
|
|
|
|
writel_relaxed(qup_err, controller->base + QUP_ERROR_FLAGS);
|
|
writel_relaxed(spi_err, controller->base + SPI_ERROR_FLAGS);
|
|
writel_relaxed(opflags, controller->base + QUP_OPERATIONAL);
|
|
|
|
if (!xfer) {
|
|
dev_err_ratelimited(controller->dev, "unexpected irq %08x %08x %08x\n",
|
|
qup_err, spi_err, opflags);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
if (qup_err) {
|
|
if (qup_err & QUP_ERROR_OUTPUT_OVER_RUN)
|
|
dev_warn(controller->dev, "OUTPUT_OVER_RUN\n");
|
|
if (qup_err & QUP_ERROR_INPUT_UNDER_RUN)
|
|
dev_warn(controller->dev, "INPUT_UNDER_RUN\n");
|
|
if (qup_err & QUP_ERROR_OUTPUT_UNDER_RUN)
|
|
dev_warn(controller->dev, "OUTPUT_UNDER_RUN\n");
|
|
if (qup_err & QUP_ERROR_INPUT_OVER_RUN)
|
|
dev_warn(controller->dev, "INPUT_OVER_RUN\n");
|
|
|
|
error = -EIO;
|
|
}
|
|
|
|
if (spi_err) {
|
|
if (spi_err & SPI_ERROR_CLK_OVER_RUN)
|
|
dev_warn(controller->dev, "CLK_OVER_RUN\n");
|
|
if (spi_err & SPI_ERROR_CLK_UNDER_RUN)
|
|
dev_warn(controller->dev, "CLK_UNDER_RUN\n");
|
|
|
|
error = -EIO;
|
|
}
|
|
|
|
if (opflags & QUP_OP_IN_SERVICE_FLAG)
|
|
spi_qup_fifo_read(controller, xfer);
|
|
|
|
if (opflags & QUP_OP_OUT_SERVICE_FLAG)
|
|
spi_qup_fifo_write(controller, xfer);
|
|
|
|
spin_lock_irqsave(&controller->lock, flags);
|
|
controller->error = error;
|
|
controller->xfer = xfer;
|
|
spin_unlock_irqrestore(&controller->lock, flags);
|
|
|
|
if (controller->rx_bytes == xfer->len || error)
|
|
complete(&controller->done);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
|
|
/* set clock freq ... bits per word */
|
|
static int spi_qup_io_config(struct spi_device *spi, struct spi_transfer *xfer)
|
|
{
|
|
struct spi_qup *controller = spi_master_get_devdata(spi->master);
|
|
u32 config, iomode, mode;
|
|
int ret, n_words, w_size;
|
|
|
|
if (spi->mode & SPI_LOOP && xfer->len > controller->in_fifo_sz) {
|
|
dev_err(controller->dev, "too big size for loopback %d > %d\n",
|
|
xfer->len, controller->in_fifo_sz);
|
|
return -EIO;
|
|
}
|
|
|
|
ret = clk_set_rate(controller->cclk, xfer->speed_hz);
|
|
if (ret) {
|
|
dev_err(controller->dev, "fail to set frequency %d",
|
|
xfer->speed_hz);
|
|
return -EIO;
|
|
}
|
|
|
|
if (spi_qup_set_state(controller, QUP_STATE_RESET)) {
|
|
dev_err(controller->dev, "cannot set RESET state\n");
|
|
return -EIO;
|
|
}
|
|
|
|
w_size = 4;
|
|
if (xfer->bits_per_word <= 8)
|
|
w_size = 1;
|
|
else if (xfer->bits_per_word <= 16)
|
|
w_size = 2;
|
|
|
|
n_words = xfer->len / w_size;
|
|
controller->w_size = w_size;
|
|
|
|
if (n_words <= (controller->in_fifo_sz / sizeof(u32))) {
|
|
mode = QUP_IO_M_MODE_FIFO;
|
|
writel_relaxed(n_words, controller->base + QUP_MX_READ_CNT);
|
|
writel_relaxed(n_words, controller->base + QUP_MX_WRITE_CNT);
|
|
/* must be zero for FIFO */
|
|
writel_relaxed(0, controller->base + QUP_MX_INPUT_CNT);
|
|
writel_relaxed(0, controller->base + QUP_MX_OUTPUT_CNT);
|
|
} else {
|
|
mode = QUP_IO_M_MODE_BLOCK;
|
|
writel_relaxed(n_words, controller->base + QUP_MX_INPUT_CNT);
|
|
writel_relaxed(n_words, controller->base + QUP_MX_OUTPUT_CNT);
|
|
/* must be zero for BLOCK and BAM */
|
|
writel_relaxed(0, controller->base + QUP_MX_READ_CNT);
|
|
writel_relaxed(0, controller->base + QUP_MX_WRITE_CNT);
|
|
}
|
|
|
|
iomode = readl_relaxed(controller->base + QUP_IO_M_MODES);
|
|
/* Set input and output transfer mode */
|
|
iomode &= ~(QUP_IO_M_INPUT_MODE_MASK | QUP_IO_M_OUTPUT_MODE_MASK);
|
|
iomode &= ~(QUP_IO_M_PACK_EN | QUP_IO_M_UNPACK_EN);
|
|
iomode |= (mode << QUP_IO_M_OUTPUT_MODE_MASK_SHIFT);
|
|
iomode |= (mode << QUP_IO_M_INPUT_MODE_MASK_SHIFT);
|
|
|
|
writel_relaxed(iomode, controller->base + QUP_IO_M_MODES);
|
|
|
|
config = readl_relaxed(controller->base + SPI_CONFIG);
|
|
|
|
if (spi->mode & SPI_LOOP)
|
|
config |= SPI_CONFIG_LOOPBACK;
|
|
else
|
|
config &= ~SPI_CONFIG_LOOPBACK;
|
|
|
|
if (spi->mode & SPI_CPHA)
|
|
config &= ~SPI_CONFIG_INPUT_FIRST;
|
|
else
|
|
config |= SPI_CONFIG_INPUT_FIRST;
|
|
|
|
/*
|
|
* HS_MODE improves signal stability for spi-clk high rates,
|
|
* but is invalid in loop back mode.
|
|
*/
|
|
if ((xfer->speed_hz >= SPI_HS_MIN_RATE) && !(spi->mode & SPI_LOOP))
|
|
config |= SPI_CONFIG_HS_MODE;
|
|
else
|
|
config &= ~SPI_CONFIG_HS_MODE;
|
|
|
|
writel_relaxed(config, controller->base + SPI_CONFIG);
|
|
|
|
config = readl_relaxed(controller->base + QUP_CONFIG);
|
|
config &= ~(QUP_CONFIG_NO_INPUT | QUP_CONFIG_NO_OUTPUT | QUP_CONFIG_N);
|
|
config |= xfer->bits_per_word - 1;
|
|
config |= QUP_CONFIG_SPI_MODE;
|
|
writel_relaxed(config, controller->base + QUP_CONFIG);
|
|
|
|
/* only write to OPERATIONAL_MASK when register is present */
|
|
if (!controller->qup_v1)
|
|
writel_relaxed(0, controller->base + QUP_OPERATIONAL_MASK);
|
|
return 0;
|
|
}
|
|
|
|
static int spi_qup_transfer_one(struct spi_master *master,
|
|
struct spi_device *spi,
|
|
struct spi_transfer *xfer)
|
|
{
|
|
struct spi_qup *controller = spi_master_get_devdata(master);
|
|
unsigned long timeout, flags;
|
|
int ret = -EIO;
|
|
|
|
ret = spi_qup_io_config(spi, xfer);
|
|
if (ret)
|
|
return ret;
|
|
|
|
timeout = DIV_ROUND_UP(xfer->speed_hz, MSEC_PER_SEC);
|
|
timeout = DIV_ROUND_UP(xfer->len * 8, timeout);
|
|
timeout = 100 * msecs_to_jiffies(timeout);
|
|
|
|
reinit_completion(&controller->done);
|
|
|
|
spin_lock_irqsave(&controller->lock, flags);
|
|
controller->xfer = xfer;
|
|
controller->error = 0;
|
|
controller->rx_bytes = 0;
|
|
controller->tx_bytes = 0;
|
|
spin_unlock_irqrestore(&controller->lock, flags);
|
|
|
|
if (spi_qup_set_state(controller, QUP_STATE_RUN)) {
|
|
dev_warn(controller->dev, "cannot set RUN state\n");
|
|
goto exit;
|
|
}
|
|
|
|
if (spi_qup_set_state(controller, QUP_STATE_PAUSE)) {
|
|
dev_warn(controller->dev, "cannot set PAUSE state\n");
|
|
goto exit;
|
|
}
|
|
|
|
spi_qup_fifo_write(controller, xfer);
|
|
|
|
if (spi_qup_set_state(controller, QUP_STATE_RUN)) {
|
|
dev_warn(controller->dev, "cannot set EXECUTE state\n");
|
|
goto exit;
|
|
}
|
|
|
|
if (!wait_for_completion_timeout(&controller->done, timeout))
|
|
ret = -ETIMEDOUT;
|
|
exit:
|
|
spi_qup_set_state(controller, QUP_STATE_RESET);
|
|
spin_lock_irqsave(&controller->lock, flags);
|
|
controller->xfer = NULL;
|
|
if (!ret)
|
|
ret = controller->error;
|
|
spin_unlock_irqrestore(&controller->lock, flags);
|
|
return ret;
|
|
}
|
|
|
|
static int spi_qup_probe(struct platform_device *pdev)
|
|
{
|
|
struct spi_master *master;
|
|
struct clk *iclk, *cclk;
|
|
struct spi_qup *controller;
|
|
struct resource *res;
|
|
struct device *dev;
|
|
void __iomem *base;
|
|
u32 max_freq, iomode;
|
|
int ret, irq, size;
|
|
|
|
dev = &pdev->dev;
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
base = devm_ioremap_resource(dev, res);
|
|
if (IS_ERR(base))
|
|
return PTR_ERR(base);
|
|
|
|
irq = platform_get_irq(pdev, 0);
|
|
if (irq < 0)
|
|
return irq;
|
|
|
|
cclk = devm_clk_get(dev, "core");
|
|
if (IS_ERR(cclk))
|
|
return PTR_ERR(cclk);
|
|
|
|
iclk = devm_clk_get(dev, "iface");
|
|
if (IS_ERR(iclk))
|
|
return PTR_ERR(iclk);
|
|
|
|
/* This is optional parameter */
|
|
if (of_property_read_u32(dev->of_node, "spi-max-frequency", &max_freq))
|
|
max_freq = SPI_MAX_RATE;
|
|
|
|
if (!max_freq || max_freq > SPI_MAX_RATE) {
|
|
dev_err(dev, "invalid clock frequency %d\n", max_freq);
|
|
return -ENXIO;
|
|
}
|
|
|
|
ret = clk_prepare_enable(cclk);
|
|
if (ret) {
|
|
dev_err(dev, "cannot enable core clock\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = clk_prepare_enable(iclk);
|
|
if (ret) {
|
|
clk_disable_unprepare(cclk);
|
|
dev_err(dev, "cannot enable iface clock\n");
|
|
return ret;
|
|
}
|
|
|
|
master = spi_alloc_master(dev, sizeof(struct spi_qup));
|
|
if (!master) {
|
|
clk_disable_unprepare(cclk);
|
|
clk_disable_unprepare(iclk);
|
|
dev_err(dev, "cannot allocate master\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* use num-cs unless not present or out of range */
|
|
if (of_property_read_u16(dev->of_node, "num-cs",
|
|
&master->num_chipselect) ||
|
|
(master->num_chipselect > SPI_NUM_CHIPSELECTS))
|
|
master->num_chipselect = SPI_NUM_CHIPSELECTS;
|
|
|
|
master->bus_num = pdev->id;
|
|
master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_LOOP;
|
|
master->bits_per_word_mask = SPI_BPW_RANGE_MASK(4, 32);
|
|
master->max_speed_hz = max_freq;
|
|
master->transfer_one = spi_qup_transfer_one;
|
|
master->dev.of_node = pdev->dev.of_node;
|
|
master->auto_runtime_pm = true;
|
|
|
|
platform_set_drvdata(pdev, master);
|
|
|
|
controller = spi_master_get_devdata(master);
|
|
|
|
controller->dev = dev;
|
|
controller->base = base;
|
|
controller->iclk = iclk;
|
|
controller->cclk = cclk;
|
|
controller->irq = irq;
|
|
|
|
/* set v1 flag if device is version 1 */
|
|
if (of_device_is_compatible(dev->of_node, "qcom,spi-qup-v1.1.1"))
|
|
controller->qup_v1 = 1;
|
|
|
|
spin_lock_init(&controller->lock);
|
|
init_completion(&controller->done);
|
|
|
|
iomode = readl_relaxed(base + QUP_IO_M_MODES);
|
|
|
|
size = QUP_IO_M_OUTPUT_BLOCK_SIZE(iomode);
|
|
if (size)
|
|
controller->out_blk_sz = size * 16;
|
|
else
|
|
controller->out_blk_sz = 4;
|
|
|
|
size = QUP_IO_M_INPUT_BLOCK_SIZE(iomode);
|
|
if (size)
|
|
controller->in_blk_sz = size * 16;
|
|
else
|
|
controller->in_blk_sz = 4;
|
|
|
|
size = QUP_IO_M_OUTPUT_FIFO_SIZE(iomode);
|
|
controller->out_fifo_sz = controller->out_blk_sz * (2 << size);
|
|
|
|
size = QUP_IO_M_INPUT_FIFO_SIZE(iomode);
|
|
controller->in_fifo_sz = controller->in_blk_sz * (2 << size);
|
|
|
|
dev_info(dev, "IN:block:%d, fifo:%d, OUT:block:%d, fifo:%d\n",
|
|
controller->in_blk_sz, controller->in_fifo_sz,
|
|
controller->out_blk_sz, controller->out_fifo_sz);
|
|
|
|
writel_relaxed(1, base + QUP_SW_RESET);
|
|
|
|
ret = spi_qup_set_state(controller, QUP_STATE_RESET);
|
|
if (ret) {
|
|
dev_err(dev, "cannot set RESET state\n");
|
|
goto error;
|
|
}
|
|
|
|
writel_relaxed(0, base + QUP_OPERATIONAL);
|
|
writel_relaxed(0, base + QUP_IO_M_MODES);
|
|
|
|
if (!controller->qup_v1)
|
|
writel_relaxed(0, base + QUP_OPERATIONAL_MASK);
|
|
|
|
writel_relaxed(SPI_ERROR_CLK_UNDER_RUN | SPI_ERROR_CLK_OVER_RUN,
|
|
base + SPI_ERROR_FLAGS_EN);
|
|
|
|
/* if earlier version of the QUP, disable INPUT_OVERRUN */
|
|
if (controller->qup_v1)
|
|
writel_relaxed(QUP_ERROR_OUTPUT_OVER_RUN |
|
|
QUP_ERROR_INPUT_UNDER_RUN | QUP_ERROR_OUTPUT_UNDER_RUN,
|
|
base + QUP_ERROR_FLAGS_EN);
|
|
|
|
writel_relaxed(0, base + SPI_CONFIG);
|
|
writel_relaxed(SPI_IO_C_NO_TRI_STATE, base + SPI_IO_CONTROL);
|
|
|
|
ret = devm_request_irq(dev, irq, spi_qup_qup_irq,
|
|
IRQF_TRIGGER_HIGH, pdev->name, controller);
|
|
if (ret)
|
|
goto error;
|
|
|
|
pm_runtime_set_autosuspend_delay(dev, MSEC_PER_SEC);
|
|
pm_runtime_use_autosuspend(dev);
|
|
pm_runtime_set_active(dev);
|
|
pm_runtime_enable(dev);
|
|
|
|
ret = devm_spi_register_master(dev, master);
|
|
if (ret)
|
|
goto disable_pm;
|
|
|
|
return 0;
|
|
|
|
disable_pm:
|
|
pm_runtime_disable(&pdev->dev);
|
|
error:
|
|
clk_disable_unprepare(cclk);
|
|
clk_disable_unprepare(iclk);
|
|
spi_master_put(master);
|
|
return ret;
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
static int spi_qup_pm_suspend_runtime(struct device *device)
|
|
{
|
|
struct spi_master *master = dev_get_drvdata(device);
|
|
struct spi_qup *controller = spi_master_get_devdata(master);
|
|
u32 config;
|
|
|
|
/* Enable clocks auto gaiting */
|
|
config = readl(controller->base + QUP_CONFIG);
|
|
config |= QUP_CONFIG_CLOCK_AUTO_GATE;
|
|
writel_relaxed(config, controller->base + QUP_CONFIG);
|
|
return 0;
|
|
}
|
|
|
|
static int spi_qup_pm_resume_runtime(struct device *device)
|
|
{
|
|
struct spi_master *master = dev_get_drvdata(device);
|
|
struct spi_qup *controller = spi_master_get_devdata(master);
|
|
u32 config;
|
|
|
|
/* Disable clocks auto gaiting */
|
|
config = readl_relaxed(controller->base + QUP_CONFIG);
|
|
config &= ~QUP_CONFIG_CLOCK_AUTO_GATE;
|
|
writel_relaxed(config, controller->base + QUP_CONFIG);
|
|
return 0;
|
|
}
|
|
#endif /* CONFIG_PM */
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static int spi_qup_suspend(struct device *device)
|
|
{
|
|
struct spi_master *master = dev_get_drvdata(device);
|
|
struct spi_qup *controller = spi_master_get_devdata(master);
|
|
int ret;
|
|
|
|
ret = spi_master_suspend(master);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = spi_qup_set_state(controller, QUP_STATE_RESET);
|
|
if (ret)
|
|
return ret;
|
|
|
|
clk_disable_unprepare(controller->cclk);
|
|
clk_disable_unprepare(controller->iclk);
|
|
return 0;
|
|
}
|
|
|
|
static int spi_qup_resume(struct device *device)
|
|
{
|
|
struct spi_master *master = dev_get_drvdata(device);
|
|
struct spi_qup *controller = spi_master_get_devdata(master);
|
|
int ret;
|
|
|
|
ret = clk_prepare_enable(controller->iclk);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = clk_prepare_enable(controller->cclk);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = spi_qup_set_state(controller, QUP_STATE_RESET);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return spi_master_resume(master);
|
|
}
|
|
#endif /* CONFIG_PM_SLEEP */
|
|
|
|
static int spi_qup_remove(struct platform_device *pdev)
|
|
{
|
|
struct spi_master *master = dev_get_drvdata(&pdev->dev);
|
|
struct spi_qup *controller = spi_master_get_devdata(master);
|
|
int ret;
|
|
|
|
ret = pm_runtime_get_sync(&pdev->dev);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = spi_qup_set_state(controller, QUP_STATE_RESET);
|
|
if (ret)
|
|
return ret;
|
|
|
|
clk_disable_unprepare(controller->cclk);
|
|
clk_disable_unprepare(controller->iclk);
|
|
|
|
pm_runtime_put_noidle(&pdev->dev);
|
|
pm_runtime_disable(&pdev->dev);
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id spi_qup_dt_match[] = {
|
|
{ .compatible = "qcom,spi-qup-v1.1.1", },
|
|
{ .compatible = "qcom,spi-qup-v2.1.1", },
|
|
{ .compatible = "qcom,spi-qup-v2.2.1", },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, spi_qup_dt_match);
|
|
|
|
static const struct dev_pm_ops spi_qup_dev_pm_ops = {
|
|
SET_SYSTEM_SLEEP_PM_OPS(spi_qup_suspend, spi_qup_resume)
|
|
SET_RUNTIME_PM_OPS(spi_qup_pm_suspend_runtime,
|
|
spi_qup_pm_resume_runtime,
|
|
NULL)
|
|
};
|
|
|
|
static struct platform_driver spi_qup_driver = {
|
|
.driver = {
|
|
.name = "spi_qup",
|
|
.pm = &spi_qup_dev_pm_ops,
|
|
.of_match_table = spi_qup_dt_match,
|
|
},
|
|
.probe = spi_qup_probe,
|
|
.remove = spi_qup_remove,
|
|
};
|
|
module_platform_driver(spi_qup_driver);
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_ALIAS("platform:spi_qup");
|