* [PATCH] ARM: mach-shmobile: USB boot tool "r_usb_boot"
From: Magnus Damm @ 2011-09-27 9:22 UTC (permalink / raw)
To: linux-sh
From: Magnus Damm <damm@opensource.se>
Add a Renesas specific USB boot tool "r_usb_boot" to the directory
Documentation/arm/SH-Mobile/.
This reusable tool is designed ot runs on the Linux host machine,
connecting to the MASK ROM of the target SoC. The USB boot tool is
used to upload and start code on the SoC. The code is uploaded to
internal SRAM and executed from there.
The USB boot tool is specific to certain SoC models but independent
of board. At this point sh7372 has been tested on AP4EVB and Mackerel
boards and sh73a0 SoC support has been tested on Kota2.
A common use case is to upload a board-specific minimal kernel image
to re-program the NOR Flash or perhaps use kexec to boot a full kernel.
Signed-off-by: Magnus Damm <damm@opensource.se>
---
Documentation/arm/SH-Mobile/r_usb_boot.c | 544 ++++++++++++++++++++++++++++++
1 file changed, 544 insertions(+)
--- /dev/null
+++ work/Documentation/arm/SH-Mobile/r_usb_boot.c 2011-09-27 18:08:23.000000000 +0900
@@ -0,0 +1,544 @@
+/*
+ * USB boot tool for SH-Mobile ARM SoCs
+ *
+ * Boots a stand alone Linux kernel directly from the MASK ROM.
+ * Limited to the size of the MERAM block (1.5 MiB SRAM)
+ *
+ * Tested on sh7372 and sh73a0.
+ *
+ * Copyright (C) 2011 Renesas Solutions Corp.
+ * Copyright (C) 2011 Magnus Damm
+ *
+ * Requires libusb, use the following line to build:
+ * $ gcc `pkg-config libusb-1.0 --libs --cflags` r_usb_boot.c -o r_usb_boot
+ *
+ * This program is designed to run on a Linux host computer that is
+ * connected to the target hardware via USB. The target hardware
+ * needs to have USB boot support built into the MASK ROM and the
+ * boot mode pins (MDn) should select "USB Development mode".
+ *
+ * Usage:
+ * # r_usb_boot zImage
+ *
+ * The kernel must include System RAM setup code among other things, as
+ * an example see arch/arm/mach-shmobile/include/mach/head-ap4evb.txt
+ * Important kernel configuration parameters include:
+ *
+ * CONFIG_ZBOOT_ROM=y
+ * CONFIG_ZBOOT_ROM_TEXT=0x0
+ * CONFIG_ZBOOT_ROM_BSS=0xe5580000 (Needs to match MERAM base address)
+ * CONFIG_CMDLINE="..." (This depends on your board)
+ * CONFIG_CMDLINE_FORCE=y
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ */
+
+#include <errno.h>
+#include <signal.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "libusb.h"
+
+#define HUGECHUNK 0x40000
+#define MAX_FILESIZE (HUGECHUNK * 5) /* 1.5 MiB MERAM, 256 KiB reserved */
+
+struct device_info {
+ char *name;
+ unsigned short vid;
+ unsigned short pid;
+
+ enum { FORMAT_VRL4, FORMAT_VRL5 } format;
+ unsigned int small_chunksize;
+ unsigned int large_chunksize;
+
+} supported_devices[] = {
+ {
+ "sh7372",
+ 0x045b, 0x0055,
+ FORMAT_VRL4,
+ 64, 4096,
+ },
+ {
+ "sh73a0",
+ 0x045b, 0x0060,
+ FORMAT_VRL5,
+ 64, 16384,
+ },
+};
+
+#define TIMEOUT 5000
+#define MAX_CHUNK 16384
+
+/* ARM code that copies data in memory */
+static unsigned char mover_code_data[] = {
+ 0x02, 0x00, 0x00, 0xea, /* b after_size */
+ 0x00, 0x00, 0x00, 0x00, /* src */
+ 0x00, 0x00, 0x00, 0x00, /* dst */
+ 0x00, 0x00, 0x00, 0x00, /* size */
+
+ 0x14, 0x20, 0x1f, 0xe5, /* ldr r2, src */
+ 0x14, 0x30, 0x1f, 0xe5, /* ldr r3, dst */
+ 0x14, 0x00, 0x1f, 0xe5, /* ldr r0, len */
+
+ 0x04, 0x10, 0x92, 0xe4, /* ldr r1,[r2],#4 */
+ 0x04, 0x10, 0x83, 0xe4, /* str r1,[r3],#4 */
+ 0x04, 0x00, 0x50, 0xe2, /* subs r0,r0,#4 */
+ 0xfb, 0xff, 0xff, 0x1a, /* bne before_ldr_r1 */
+
+ 0x0e, 0xf0, 0xa0, 0xe1, /* mov pc, lr */
+};
+
+static int get_hw_version(libusb_device_handle *h)
+{
+ unsigned char get_version[] = { 0xaf, 0x00 };
+ unsigned char data[0x20];
+ int r, r2;
+
+ /* Get Version */
+ r = libusb_bulk_transfer(h, 0x02,
+ get_version, sizeof(get_version),
+ &r2, TIMEOUT);
+ if (r)
+ return -1;
+
+ /* Version Information */
+ r = libusb_bulk_transfer(h, 0x81,
+ data, sizeof(data),
+ &r2, TIMEOUT);
+
+ if (!r && r2 && (data[0] = 0xbf) && (data[1] = (r2 - 2))) {
+ data[r2] = '\0';
+ printf("Hardware Version Information %s\n", &data[2]);
+ return 0;
+ }
+
+ return -1;
+}
+
+static int status_request_response(libusb_device_handle *h)
+{
+ unsigned char data_out[3] = { 0xa1, 0x01, 0x00 };
+ unsigned char data_in[0x40];
+ int r, r2;
+
+ /* Status Request */
+ r = libusb_bulk_transfer(h, 0x02,
+ data_out, sizeof(data_out),
+ &r2, TIMEOUT);
+ if (r || (r2 != sizeof(data_out))) {
+ fprintf(stderr, "Status Request send error\n");
+ return -1;
+ }
+
+ /* Status Response */
+ r = libusb_bulk_transfer(h, 0x81,
+ data_in, sizeof(data_in),
+ &r2, TIMEOUT);
+
+ if (r || (r2 != 3) || (data_in[0] != 0xb0)) {
+ fprintf(stderr, "Status is not ok with %d %d 0x%02x 0x%02x\n",
+ r, r2, data_in[0], data_in[2]);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int setup_clocks(libusb_device_handle *h)
+{
+ unsigned char initialize[] = { 0xa0, 0x01, 0x00 };
+ unsigned char data[0x20];
+ int r, r2;
+
+ /* Initialize */
+ r = libusb_bulk_transfer(h, 0x02,
+ initialize, sizeof(initialize),
+ &r2, TIMEOUT);
+ if (r)
+ return -1;
+
+ /* Command Response */
+ r = libusb_bulk_transfer(h, 0x81,
+ data, sizeof(data),
+ &r2, TIMEOUT);
+ if (r || (r2 != 3) || (data[0] != 0xb0) || (data[2] != 0x00))
+ return -1;
+
+ /* Status Request + Status Response */
+ return status_request_response(h);
+}
+
+static int do_start(libusb_device_handle *h)
+{
+ unsigned char program_start[] = { 0xa5, 0x00 };
+ unsigned char data[0x10];
+ int r, r2;
+
+ /* Program Start */
+ r = libusb_bulk_transfer(h, 0x02,
+ program_start, sizeof(program_start),
+ &r2, TIMEOUT);
+ if (r)
+ return -1;
+
+ /* Command Response */
+ r = libusb_bulk_transfer(h, 0x81,
+ data, sizeof(data),
+ &r2, TIMEOUT);
+ if (r || (r2 != 3) || (data[0] != 0xb0) || (data[1] != 1)
+ || (data[2] != 0)) {
+ fprintf(stderr, "Unexpected Program Start Response 0x%02x\n",
+ data[2]);
+ return -1;
+ }
+ return 0;
+}
+
+static int do_download(libusb_device_handle *h,
+ int my_chunksize,
+ unsigned int base_addr,
+ unsigned char *buf, int no_bytes)
+{
+ unsigned char data[MAX_CHUNK + 7];
+ int k, r, r2;
+ unsigned int crc, allcrc;
+ int bytes_to_go;
+ int seq;
+ int chunksize;
+
+ allcrc = 0;
+ seq = 0;
+ chunksize = my_chunksize < MAX_CHUNK ? my_chunksize : MAX_CHUNK;
+
+ bytes_to_go = (no_bytes + chunksize - 1) & ~(chunksize - 1);
+
+ /* Download Start */
+ data[0] = 0xa2;
+ data[1] = 0x08;
+ data[2] = (base_addr >> 24) & 0xff;
+ data[3] = (base_addr >> 16) & 0xff;
+ data[4] = (base_addr >> 8) & 0xff;
+ data[5] = base_addr & 0xff;
+ data[6] = (bytes_to_go >> 24) & 0xff;
+ data[7] = (bytes_to_go >> 16) & 0xff;
+ data[8] = (bytes_to_go >> 8) & 0xff;
+ data[9] = bytes_to_go & 0xff;
+ r = libusb_bulk_transfer(h, 0x02,
+ data, 10,
+ &r2, TIMEOUT);
+ if (r)
+ return -1;
+
+ /* Download Response */
+ r = libusb_bulk_transfer(h, 0x81,
+ data, sizeof(data),
+ &r2, TIMEOUT);
+ if (r || (r2 != 4) || (data[0] != 0xb1))
+ return -1;
+
+ if (data[2] || data[3]) {
+ fprintf(stderr, "Download Start Response is 0x%02x 0x%02x\n",
+ data[2], data[3]);
+ return -1;
+ }
+
+ /* Status Request + Status Response */
+ status_request_response(h);
+
+ while (bytes_to_go > 0) {
+
+ k = chunksize + 4;
+
+ /* Download Data */
+ data[0] = 0xa3;
+ data[1] = (k >> 8) & 0xff;
+ data[2] = k & 0xff;
+ data[3] = (seq >> 8) & 0xff;
+ data[4] = seq & 0xff;
+
+ crc = 0;
+ for (k = 0; k < chunksize; k++) {
+ crc += buf[k];
+ allcrc += buf[k];
+ data[5 + k] = buf[k];
+ }
+ crc = ~(crc - 1);
+
+ data[chunksize + 5] = (crc >> 8) & 0xff;
+ data[chunksize + 6] = crc & 0xff;
+ r = libusb_bulk_transfer(h, 0x02,
+ data, chunksize + 7,
+ &r2, TIMEOUT);
+ if (r)
+ return -1;
+
+ /* Download Response */
+ r = libusb_bulk_transfer(h, 0x81,
+ data, sizeof(data),
+ &r2, TIMEOUT);
+ if (r || (r2 != 4) || (data[0] != 0xb1))
+ return -1;
+
+ if (data[2] || data[3]) {
+ fprintf(stderr, "Download Response 0x%02x 0x%02x\n",
+ data[2], data[3]);
+ return -1;
+ }
+
+ /* Status Request + Status Response */
+ status_request_response(h);
+
+ /* process next chunk */
+ buf += chunksize;
+ bytes_to_go -= chunksize;
+ seq++;
+ }
+
+ /* Download End Confirm */
+ data[0] = 0xa4;
+ data[1] = 0x02;
+
+ allcrc = ~(allcrc - 1);
+ data[2] = (allcrc >> 8) & 0xff;
+ data[3] = allcrc & 0xff;
+
+ r = libusb_bulk_transfer(h, 0x02,
+ data, 4,
+ &r2, TIMEOUT);
+ if (r)
+ return -1;
+
+ /* Download End Status */
+ r = libusb_bulk_transfer(h, 0x81,
+ data, sizeof(data),
+ &r2, TIMEOUT);
+ if (r || (r2 != 5) || (data[0] != 0xb2))
+ return -1;
+
+ if (data[2]) {
+ fprintf(stderr, "Download End Status is 0x%02x 0x%02x 0x%02x\n",
+ data[2], data[3], data[4]);
+ return -1;
+ }
+
+ return 0;
+}
+
+/* VRL5 header */
+static unsigned char vrl5_header_file_data[340] = {
+ 0x10, 0xf0, 0x9f, 0xe5,
+ 0x56, 0x00, 0x00, 0x00,
+ 0x20, 0x41, 0x35, 0x47,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00,
+ 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00,
+};
+
+/* VRL4 header */
+static unsigned char vrl4_header_file_data[340] = {
+ 0x00, 0x00, 0x00, 0xea,
+ 0x56, 0x00, 0x00, 0x00,
+ 0x08, 0xf0, 0x9f, 0xe5,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00,
+ 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00,
+};
+
+static int vrl_header(libusb_device_handle *h,
+ struct device_info *di,
+ unsigned int start_addr)
+{
+ unsigned char header_buf[340];
+
+ switch (di->format) {
+ case FORMAT_VRL4:
+ memcpy(header_buf, vrl4_header_file_data, sizeof(header_buf));
+ break;
+ case FORMAT_VRL5:
+ memcpy(header_buf, vrl5_header_file_data, sizeof(header_buf));
+ break;
+ default:
+ return -1;
+ }
+
+ /* Fill in start address */
+ header_buf[24] = start_addr & 0xff;
+ header_buf[25] = (start_addr >> 8) & 0xff;
+ header_buf[26] = (start_addr >> 16) & 0xff;
+ header_buf[27] = (start_addr >> 24) & 0xff;
+
+ /* Download VRL header for file at 0xe55b0000 */
+ return do_download(h, di->small_chunksize,
+ 0xe55b0000, header_buf, sizeof(header_buf));
+}
+
+static int run_mover_code(libusb_device_handle *h,
+ struct device_info *di,
+ unsigned int src_addr,
+ unsigned int dst_addr,
+ unsigned int no_bytes)
+{
+ int ret;
+
+ printf("Moving 0x%08x bytes from 0x%08x to 0x%08x\n",
+ no_bytes, src_addr, dst_addr);
+
+ mover_code_data[4] = src_addr & 0xff;
+ mover_code_data[5] = (src_addr >> 8) & 0xff;
+ mover_code_data[6] = (src_addr >> 16) & 0xff;
+ mover_code_data[7] = (src_addr >> 24) & 0xff;
+ mover_code_data[8] = dst_addr & 0xff;
+ mover_code_data[9] = (dst_addr >> 8) & 0xff;
+ mover_code_data[10] = (dst_addr >> 16) & 0xff;
+ mover_code_data[11] = (dst_addr >> 24) & 0xff;
+ mover_code_data[12] = no_bytes & 0xff;
+ mover_code_data[13] = (no_bytes >> 8) & 0xff;
+ mover_code_data[14] = (no_bytes >> 16) & 0xff;
+ mover_code_data[15] = (no_bytes >> 24) & 0xff;
+
+ /* Begin loading the headerless code */
+ do_download(h, di->small_chunksize, 0xe55b8000,
+ mover_code_data, sizeof(mover_code_data));
+
+ /* Download VRL header pointing to 0xe55b8000 */
+ vrl_header(h, di, 0xe55b8000);
+
+ /* Start the mover code */
+ ret = do_start(h);
+
+ /* Check that the code made it back:
+ * Status Request + Status Response */
+ if (ret = 0)
+ status_request_response(h);
+
+ return ret;
+}
+
+static int rolf(libusb_device_handle *h,
+ struct device_info *di,
+ char *filename)
+{
+ unsigned char file_buf[HUGECHUNK];
+ int k, ret;
+ int fd, filesize;
+
+ fd = open(filename, O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "unable to open file.\n");
+ return -1;
+ }
+
+ filesize = lseek(fd, 0, SEEK_END);
+ if (filesize = -1) {
+ fprintf(stderr, "unable to seek file.\n");
+ return -1;
+ }
+
+ if (filesize > MAX_FILESIZE) {
+ fprintf(stderr, "filesize %d exceed maximum supported %d.\n",
+ filesize, MAX_FILESIZE);
+ return -1;
+ }
+
+ if (setup_clocks(h) != 0)
+ return -1;
+
+ /* split file in HUGCHUNK size blocks - download top first */
+ for (k = ((filesize + HUGECHUNK - 1) / HUGECHUNK) - 1; k >= 0; k--) {
+
+ printf("Loading 0x%08x bytes to 0x%08x\n",
+ HUGECHUNK, 0xe55c0000);
+
+ lseek(fd, HUGECHUNK * k, SEEK_SET);
+ read(fd, file_buf, HUGECHUNK);
+
+ /* Download file data chunk */
+ do_download(h, di->large_chunksize,
+ 0xe55c0000, file_buf, HUGECHUNK);
+
+ /* need to move all file data except first block */
+ if (k) {
+ ret = run_mover_code(h, di, 0xe55c0000,
+ 0xe55c0000 + (HUGECHUNK * k),
+ HUGECHUNK);
+ if (ret != 0)
+ return ret;
+ }
+ }
+
+ /* Download VRL header pointing to 0xe55c0000 */
+ vrl_header(h, di, 0xe55c0000);
+
+ printf("Starting program at 0x%08x\n", 0xe55c0000);
+
+ /* Start the data in the file */
+ do_start(h);
+
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ struct libusb_device_handle *h;
+ int k;
+ int ret;
+
+ ret = libusb_init(NULL);
+ if (ret < 0) {
+ fprintf(stderr, "Unable to init libusb\n");
+ exit(1);
+ }
+
+ for (k = 0;
+ k < sizeof(supported_devices) / sizeof(supported_devices[0]);
+ k++) {
+
+ h = libusb_open_device_with_vid_pid(NULL,
+ supported_devices[k].vid,
+ supported_devices[k].pid);
+
+ if (h) {
+ printf("Detected %s\n",
+ supported_devices[k].name);
+ break;
+ }
+ }
+
+ if (!h) {
+ fprintf(stderr, "Unable to locate device\n");
+ goto out;
+ }
+
+ /* ttyACM is assigned by default, disconnect that driver */
+ libusb_detach_kernel_driver(h, 0);
+
+ ret = libusb_claim_interface(h, 1);
+ if (ret < 0) {
+ fprintf(stderr, "Unable to claim interface\n");
+ goto out;
+ }
+
+ if (get_hw_version(h) != 0)
+ goto out2;
+
+ if (argc > 1)
+ rolf(h, &supported_devices[k], argv[1]);
+
+ out2:
+ libusb_release_interface(h, 0);
+ out:
+ libusb_close(h);
+ libusb_exit(NULL);
+
+ return ret >= 0 ? 0 : 1;
+}
^ permalink raw reply
* [PATCH] ARM: mach-shmobile: sh7372 A4R support V2
From: Magnus Damm @ 2011-09-27 8:54 UTC (permalink / raw)
To: linux-sh
From: Magnus Damm <damm@opensource.se>
This patch is V2 of sh7372 A4R power domain support.
The sh7372 A4R hardware power domain contains the
SH CPU Core and a set of I/O devices including
multimedia accelerators and I2C controllers.
One special case about A4R is the INTCS interrupt
controller that needs to be saved and restored to
keep working as expected. Also the LCDC hardware
blocks are in a different hardware power domain
but have their IRQs routed only through INTCS. So
as long as LCDCs are active we cannot power down
INTCS because that would risk losing interrupts.
Signed-off-by: Magnus Damm <damm@opensource.se>
---
Changes since V1:
- rebased to fit on pm-test branch of linux-pm github repo
- use simple power_down_ok() governor
- let A4LC depend on A4R to support INTCS
arch/arm/mach-shmobile/board-ap4evb.c | 1
arch/arm/mach-shmobile/board-mackerel.c | 1
arch/arm/mach-shmobile/include/mach/sh7372.h | 4 ++
arch/arm/mach-shmobile/intc-sh7372.c | 52 +++++++++++++++++++++++++-
arch/arm/mach-shmobile/pm-sh7372.c | 35 ++++++++++++++++-
arch/arm/mach-shmobile/setup-sh7372.c | 8 ++++
6 files changed, 98 insertions(+), 3 deletions(-)
--- 0002/arch/arm/mach-shmobile/board-ap4evb.c
+++ work/arch/arm/mach-shmobile/board-ap4evb.c 2011-09-27 16:37:56.000000000 +0900
@@ -1412,6 +1412,7 @@ static void __init ap4evb_init(void)
sh7372_add_device_to_domain(&sh7372_a3sp, &sh_mmcif_device);
sh7372_add_device_to_domain(&sh7372_a3sp, &sdhi0_device);
sh7372_add_device_to_domain(&sh7372_a3sp, &sdhi1_device);
+ sh7372_add_device_to_domain(&sh7372_a4r, &ceu_device);
hdmi_init_pm_clock();
fsi_init_pm_clock();
--- 0002/arch/arm/mach-shmobile/board-mackerel.c
+++ work/arch/arm/mach-shmobile/board-mackerel.c 2011-09-27 16:37:56.000000000 +0900
@@ -1596,6 +1596,7 @@ static void __init mackerel_init(void)
sh7372_add_device_to_domain(&sh7372_a3sp, &sdhi1_device);
#endif
sh7372_add_device_to_domain(&sh7372_a3sp, &sdhi2_device);
+ sh7372_add_device_to_domain(&sh7372_a4r, &ceu_device);
hdmi_init_pm_clock();
sh7372_pm_init();
--- 0002/arch/arm/mach-shmobile/include/mach/sh7372.h
+++ work/arch/arm/mach-shmobile/include/mach/sh7372.h 2011-09-27 16:37:56.000000000 +0900
@@ -491,6 +491,7 @@ static inline struct sh7372_pm_domain *t
extern struct sh7372_pm_domain sh7372_a4lc;
extern struct sh7372_pm_domain sh7372_a4mp;
extern struct sh7372_pm_domain sh7372_d4;
+extern struct sh7372_pm_domain sh7372_a4r;
extern struct sh7372_pm_domain sh7372_a3rv;
extern struct sh7372_pm_domain sh7372_a3ri;
extern struct sh7372_pm_domain sh7372_a3sp;
@@ -507,4 +508,7 @@ extern void sh7372_pm_add_subdomain(stru
#define sh7372_pm_add_subdomain(pd, sd) do { } while(0)
#endif /* CONFIG_PM */
+extern void sh7372_intcs_suspend(void);
+extern void sh7372_intcs_resume(void);
+
#endif /* __ASM_SH7372_H__ */
--- 0001/arch/arm/mach-shmobile/intc-sh7372.c
+++ work/arch/arm/mach-shmobile/intc-sh7372.c 2011-09-27 16:37:56.000000000 +0900
@@ -606,9 +606,16 @@ static void intcs_demux(unsigned int irq
generic_handle_irq(intcs_evt2irq(evtcodeas));
}
+static void __iomem *intcs_ffd2;
+static void __iomem *intcs_ffd5;
+
void __init sh7372_init_irq(void)
{
- void __iomem *intevtsa = ioremap_nocache(0xffd20100, PAGE_SIZE);
+ void __iomem *intevtsa;
+
+ intcs_ffd2 = ioremap_nocache(0xffd20000, PAGE_SIZE);
+ intevtsa = intcs_ffd2 + 0x100;
+ intcs_ffd5 = ioremap_nocache(0xffd50000, PAGE_SIZE);
register_intc_controller(&intca_desc);
register_intc_controller(&intcs_desc);
@@ -617,3 +624,46 @@ void __init sh7372_init_irq(void)
irq_set_handler_data(evt2irq(0xf80), (void *)intevtsa);
irq_set_chained_handler(evt2irq(0xf80), intcs_demux);
}
+
+static unsigned short ffd2[0x200];
+static unsigned short ffd5[0x100];
+
+void sh7372_intcs_suspend(void)
+{
+ int k;
+
+ for (k = 0x00; k <= 0x30; k += 4)
+ ffd2[k] = __raw_readw(intcs_ffd2 + k);
+
+ for (k = 0x80; k <= 0xb0; k += 4)
+ ffd2[k] = __raw_readb(intcs_ffd2 + k);
+
+ for (k = 0x180; k <= 0x188; k += 4)
+ ffd2[k] = __raw_readb(intcs_ffd2 + k);
+
+ for (k = 0x00; k <= 0x3c; k += 4)
+ ffd5[k] = __raw_readw(intcs_ffd5 + k);
+
+ for (k = 0x80; k <= 0x9c; k += 4)
+ ffd5[k] = __raw_readb(intcs_ffd5 + k);
+}
+
+void sh7372_intcs_resume(void)
+{
+ int k;
+
+ for (k = 0x00; k <= 0x30; k += 4)
+ __raw_writew(ffd2[k], intcs_ffd2 + k);
+
+ for (k = 0x80; k <= 0xb0; k += 4)
+ __raw_writeb(ffd2[k], intcs_ffd2 + k);
+
+ for (k = 0x180; k <= 0x188; k += 4)
+ __raw_writeb(ffd2[k], intcs_ffd2 + k);
+
+ for (k = 0x00; k <= 0x3c; k += 4)
+ __raw_writew(ffd5[k], intcs_ffd5 + k);
+
+ for (k = 0x80; k <= 0x9c; k += 4)
+ __raw_writeb(ffd5[k], intcs_ffd5 + k);
+}
--- 0002/arch/arm/mach-shmobile/pm-sh7372.c
+++ work/arch/arm/mach-shmobile/pm-sh7372.c 2011-09-27 16:59:26.000000000 +0900
@@ -44,6 +44,7 @@
#define SPDCR 0xe6180008
#define SWUCR 0xe6180014
#define SBAR 0xe6180020
+#define WUPRMSK 0xe6180028
#define WUPSMSK 0xe618002c
#define WUPSMSK2 0xe6180048
#define PSTR 0xe6180080
@@ -132,6 +133,24 @@ static int pd_power_up(struct generic_pm
return ret;
}
+static int pd_power_up_a4r(struct generic_pm_domain *genpd)
+{
+ int ret = pd_power_up(genpd);
+
+ if (ret = 0)
+ sh7372_intcs_resume();
+
+ return ret;
+}
+
+static int pd_power_down_a4r(struct generic_pm_domain *genpd)
+{
+ sh7372_intcs_suspend();
+ __raw_writel(0x300fffff, WUPRMSK); /* avoid wakeup */
+
+ return pd_power_down(genpd);
+}
+
static bool pd_active_wakeup(struct device *dev)
{
return true;
@@ -150,6 +169,9 @@ static bool sh7372_power_down_ok(struct
if (genpd = &sh7372_a3sp.genpd)
return false;
+ if (genpd = &sh7372_a4r.genpd)
+ return false;
+
return true;
}
@@ -166,8 +188,13 @@ void sh7372_init_pm_domain(struct sh7372
genpd->start_device = pm_clk_resume;
genpd->dev_irq_safe = true;
genpd->active_wakeup = pd_active_wakeup;
- genpd->power_off = pd_power_down;
- genpd->power_on = pd_power_up;
+ if (genpd = &sh7372_a4r.genpd) {
+ genpd->power_off = pd_power_down_a4r;
+ genpd->power_on = pd_power_up_a4r;
+ } else {
+ genpd->power_off = pd_power_down;
+ genpd->power_on = pd_power_up;
+ }
genpd->power_on(&sh7372_pd->genpd);
}
@@ -199,6 +226,10 @@ struct sh7372_pm_domain sh7372_d4 = {
.bit_shift = 3,
};
+struct sh7372_pm_domain sh7372_a4r = {
+ .bit_shift = 5,
+};
+
struct sh7372_pm_domain sh7372_a3rv = {
.bit_shift = 6,
};
--- 0002/arch/arm/mach-shmobile/setup-sh7372.c
+++ work/arch/arm/mach-shmobile/setup-sh7372.c 2011-09-27 16:45:56.000000000 +0900
@@ -991,12 +991,14 @@ void __init sh7372_add_standard_devices(
sh7372_init_pm_domain(&sh7372_a4lc);
sh7372_init_pm_domain(&sh7372_a4mp);
sh7372_init_pm_domain(&sh7372_d4);
+ sh7372_init_pm_domain(&sh7372_a4r);
sh7372_init_pm_domain(&sh7372_a3rv);
sh7372_init_pm_domain(&sh7372_a3ri);
sh7372_init_pm_domain(&sh7372_a3sg);
sh7372_init_pm_domain(&sh7372_a3sp);
sh7372_pm_add_subdomain(&sh7372_a4lc, &sh7372_a3rv);
+ sh7372_pm_add_subdomain(&sh7372_a4r, &sh7372_a4lc);
platform_add_devices(sh7372_early_devices,
ARRAY_SIZE(sh7372_early_devices));
@@ -1020,6 +1022,12 @@ void __init sh7372_add_standard_devices(
sh7372_add_device_to_domain(&sh7372_a3sp, &dma2_device);
sh7372_add_device_to_domain(&sh7372_a3sp, &usb_dma0_device);
sh7372_add_device_to_domain(&sh7372_a3sp, &usb_dma1_device);
+ sh7372_add_device_to_domain(&sh7372_a4r, &iic0_device);
+ sh7372_add_device_to_domain(&sh7372_a4r, &veu0_device);
+ sh7372_add_device_to_domain(&sh7372_a4r, &veu1_device);
+ sh7372_add_device_to_domain(&sh7372_a4r, &veu2_device);
+ sh7372_add_device_to_domain(&sh7372_a4r, &veu3_device);
+ sh7372_add_device_to_domain(&sh7372_a4r, &jpu_device);
}
void __init sh7372_add_early_devices(void)
^ permalink raw reply
* [PATCH] ARM: mach-shmobile: sh7372 A3SP support V2
From: Magnus Damm @ 2011-09-27 8:47 UTC (permalink / raw)
To: linux-sh
In-Reply-To: <20110712082459.18930.51005.sendpatchset@t400s>
From: Magnus Damm <damm@opensource.se>
This patch is V2 of sh7372 A3SP power domain support.
The sh7372 A3SP hardware power domain contains a
wide range of I/O devices. The list of I/O devices
include SCIF serial ports, DMA Engine hardware,
SD and MMC controller hardware, USB controllers
and I2C master controllers.
This patch adds the A3SP low level code which
powers the hardware power domain on and off. It
also ties in platform devices to the pm domain
support code.
It is worth noting that the serial console is
hooked up to SCIFA0 on most sh7372 boards, and
the SCIFA0 port is included in the A3SP hardware
power domain. For this reason we cannot output
debug messages from the low level power control
code in the case of A3SP.
QoS support is needed in drivers before we can
enable the A3SP power control on the fly.
Signed-off-by: Magnus Damm <damm@opensource.se>
---
Changes since V1:
- rebased to fit on pm-test branch of linux-pm github repo
- added simple power_down_ok() governor
arch/arm/mach-shmobile/board-ap4evb.c | 4 ++
arch/arm/mach-shmobile/board-mackerel.c | 8 +++++
arch/arm/mach-shmobile/include/mach/sh7372.h | 1
arch/arm/mach-shmobile/pm-sh7372.c | 41 ++++++++++++++++++++++----
arch/arm/mach-shmobile/setup-sh7372.c | 14 ++++++++
5 files changed, 63 insertions(+), 5 deletions(-)
--- 0001/arch/arm/mach-shmobile/board-ap4evb.c
+++ work/arch/arm/mach-shmobile/board-ap4evb.c 2011-09-27 15:51:13.000000000 +0900
@@ -1409,6 +1409,10 @@ static void __init ap4evb_init(void)
sh7372_add_device_to_domain(&sh7372_a4lc, &lcdc_device);
sh7372_add_device_to_domain(&sh7372_a4mp, &fsi_device);
+ sh7372_add_device_to_domain(&sh7372_a3sp, &sh_mmcif_device);
+ sh7372_add_device_to_domain(&sh7372_a3sp, &sdhi0_device);
+ sh7372_add_device_to_domain(&sh7372_a3sp, &sdhi1_device);
+
hdmi_init_pm_clock();
fsi_init_pm_clock();
sh7372_pm_init();
--- 0001/arch/arm/mach-shmobile/board-mackerel.c
+++ work/arch/arm/mach-shmobile/board-mackerel.c 2011-09-27 15:51:13.000000000 +0900
@@ -1588,6 +1588,14 @@ static void __init mackerel_init(void)
sh7372_add_device_to_domain(&sh7372_a4lc, &lcdc_device);
sh7372_add_device_to_domain(&sh7372_a4lc, &hdmi_lcdc_device);
sh7372_add_device_to_domain(&sh7372_a4mp, &fsi_device);
+ sh7372_add_device_to_domain(&sh7372_a3sp, &usbhs0_device);
+ sh7372_add_device_to_domain(&sh7372_a3sp, &usbhs1_device);
+ sh7372_add_device_to_domain(&sh7372_a3sp, &sh_mmcif_device);
+ sh7372_add_device_to_domain(&sh7372_a3sp, &sdhi0_device);
+#if !defined(CONFIG_MMC_SH_MMCIF) && !defined(CONFIG_MMC_SH_MMCIF_MODULE)
+ sh7372_add_device_to_domain(&sh7372_a3sp, &sdhi1_device);
+#endif
+ sh7372_add_device_to_domain(&sh7372_a3sp, &sdhi2_device);
hdmi_init_pm_clock();
sh7372_pm_init();
--- 0001/arch/arm/mach-shmobile/include/mach/sh7372.h
+++ work/arch/arm/mach-shmobile/include/mach/sh7372.h 2011-09-27 15:51:13.000000000 +0900
@@ -493,6 +493,7 @@ extern struct sh7372_pm_domain sh7372_a4
extern struct sh7372_pm_domain sh7372_d4;
extern struct sh7372_pm_domain sh7372_a3rv;
extern struct sh7372_pm_domain sh7372_a3ri;
+extern struct sh7372_pm_domain sh7372_a3sp;
extern struct sh7372_pm_domain sh7372_a3sg;
extern void sh7372_init_pm_domain(struct sh7372_pm_domain *sh7372_pd);
--- 0001/arch/arm/mach-shmobile/pm-sh7372.c
+++ work/arch/arm/mach-shmobile/pm-sh7372.c 2011-09-27 16:34:43.000000000 +0900
@@ -92,8 +92,10 @@ static int pd_power_down(struct generic_
}
}
- pr_debug("sh7372 power domain down 0x%08x -> PSTR = 0x%08x\n",
- mask, __raw_readl(PSTR));
+ /* we cannot output debug message for A3SP */
+ if (genpd != &sh7372_a3sp.genpd)
+ pr_debug("sh7372 power domain down 0x%08x -> PSTR = 0x%08x\n",
+ mask, __raw_readl(PSTR));
return 0;
}
@@ -122,8 +124,10 @@ static int pd_power_up(struct generic_pm
ret = -EIO;
out:
- pr_debug("sh7372 power domain up 0x%08x -> PSTR = 0x%08x\n",
- mask, __raw_readl(PSTR));
+ /* we cannot output debug message for A3SP */
+ if (genpd != &sh7372_a3sp.genpd)
+ pr_debug("sh7372 power domain up 0x%08x -> PSTR = 0x%08x\n",
+ mask, __raw_readl(PSTR));
return ret;
}
@@ -133,11 +137,31 @@ static bool pd_active_wakeup(struct devi
return true;
}
+static bool sh7372_power_down_ok(struct dev_pm_domain *domain)
+{
+ struct generic_pm_domain *genpd = pd_to_genpd(domain);
+
+ /* at this point we do not allow powering down of certain
+ * power domains. this software restriction is to keep the
+ * existing low latency behavior by default. the idea is
+ * to remove this special casing for a power domain when
+ * all included drivers implement Runtime PM QoS.
+ */
+ if (genpd = &sh7372_a3sp.genpd)
+ return false;
+
+ return true;
+}
+
+struct dev_power_governor sh7372_gov = {
+ .power_down_ok = sh7372_power_down_ok,
+};
+
void sh7372_init_pm_domain(struct sh7372_pm_domain *sh7372_pd)
{
struct generic_pm_domain *genpd = &sh7372_pd->genpd;
- pm_genpd_init(genpd, NULL, false);
+ pm_genpd_init(genpd, &sh7372_gov, false);
genpd->stop_device = pm_clk_suspend;
genpd->start_device = pm_clk_resume;
genpd->dev_irq_safe = true;
@@ -183,6 +207,10 @@ struct sh7372_pm_domain sh7372_a3ri = {
.bit_shift = 8,
};
+struct sh7372_pm_domain sh7372_a3sp = {
+ .bit_shift = 11,
+};
+
struct sh7372_pm_domain sh7372_a3sg = {
.bit_shift = 13,
};
@@ -422,6 +450,9 @@ void __init sh7372_pm_init(void)
__raw_writel(0x0000a501, DBGREG9);
__raw_writel(0x00000000, DBGREG1);
+ /* do not convert A3SM, A3SP, A3SG, A4R power down into A4S */
+ __raw_writel(0, PDNSEL);
+
sh7372_suspend_init();
sh7372_cpuidle_init();
}
--- 0001/arch/arm/mach-shmobile/setup-sh7372.c
+++ work/arch/arm/mach-shmobile/setup-sh7372.c 2011-09-27 15:51:13.000000000 +0900
@@ -994,6 +994,7 @@ void __init sh7372_add_standard_devices(
sh7372_init_pm_domain(&sh7372_a3rv);
sh7372_init_pm_domain(&sh7372_a3ri);
sh7372_init_pm_domain(&sh7372_a3sg);
+ sh7372_init_pm_domain(&sh7372_a3sp);
sh7372_pm_add_subdomain(&sh7372_a4lc, &sh7372_a3rv);
@@ -1006,6 +1007,19 @@ void __init sh7372_add_standard_devices(
sh7372_add_device_to_domain(&sh7372_a3rv, &vpu_device);
sh7372_add_device_to_domain(&sh7372_a4mp, &spu0_device);
sh7372_add_device_to_domain(&sh7372_a4mp, &spu1_device);
+ sh7372_add_device_to_domain(&sh7372_a3sp, &scif0_device);
+ sh7372_add_device_to_domain(&sh7372_a3sp, &scif1_device);
+ sh7372_add_device_to_domain(&sh7372_a3sp, &scif2_device);
+ sh7372_add_device_to_domain(&sh7372_a3sp, &scif3_device);
+ sh7372_add_device_to_domain(&sh7372_a3sp, &scif4_device);
+ sh7372_add_device_to_domain(&sh7372_a3sp, &scif5_device);
+ sh7372_add_device_to_domain(&sh7372_a3sp, &scif6_device);
+ sh7372_add_device_to_domain(&sh7372_a3sp, &iic1_device);
+ sh7372_add_device_to_domain(&sh7372_a3sp, &dma0_device);
+ sh7372_add_device_to_domain(&sh7372_a3sp, &dma1_device);
+ sh7372_add_device_to_domain(&sh7372_a3sp, &dma2_device);
+ sh7372_add_device_to_domain(&sh7372_a3sp, &usb_dma0_device);
+ sh7372_add_device_to_domain(&sh7372_a3sp, &usb_dma1_device);
}
void __init sh7372_add_early_devices(void)
^ permalink raw reply
* Re: [PATCH 2/3] PM / Runtime: Don't run callbacks under lock for power.irq_safe set
From: Kevin Hilman @ 2011-09-26 23:59 UTC (permalink / raw)
To: Rafael J. Wysocki
Cc: Linux PM mailing list, LKML, Linux-sh list, Magnus Damm,
jean.pihet, Ming Lei
In-Reply-To: <201109242325.53563.rjw@sisk.pl>
"Rafael J. Wysocki" <rjw@sisk.pl> writes:
> From: Rafael J. Wysocki <rjw@sisk.pl>
>
> The rpm_suspend() and rpm_resume() routines execute subsystem or PM
> domain callbacks under power.lock if power.irq_safe is set for the
> given device. This is inconsistent with that rpm_idle() does after
> commit 02b2677 (PM / Runtime: Allow _put_sync() from
> interrupts-disabled context) and is problematic for subsystems and PM
> domains wanting to use power.lock for synchronization in their
> runtime PM callbacks.
>
> Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
The part described here looks right, and is much better for consistency.
Reviewed-by: Kevin Hilman <khilman@ti.com>
but...
[...]
> @@ -347,6 +353,15 @@ static int rpm_suspend(struct device *de
> goto out;
> }
>
> + if (dev->power.irq_safe) {
> + spin_unlock(&dev->power.lock);
> +
> + cpu_relax();
> +
> + spin_lock(&dev->power.lock);
> + goto repeat;
> + }
> +
... AFAICT, this isn't directly related to the problem described in the
changelog (or at least I didn't find it obvious), and probably deserves
a comment in the code as well.
Thanks,
Kevin
^ permalink raw reply
* Re: [BUG] on sh7372 (mackerel) with today's Linus' (github) master
From: Guennadi Liakhovetski @ 2011-09-26 17:25 UTC (permalink / raw)
To: linux-sh
In-Reply-To: <Pine.LNX.4.64.1109231012490.25166@axis700.grange>
On Sat, 24 Sep 2011, Rafael J. Wysocki wrote:
> On Saturday, September 24, 2011, Russell King - ARM Linux wrote:
> > On Sat, Sep 24, 2011 at 12:58:37AM +0200, Rafael J. Wysocki wrote:
> > > Well, since nobody seems to execute anything that may sleep under
> > > clocks_mutex, perhaps we can do something like this:
> >
> > No - a spinlock prevents preemption, a mutex does not.
> >
> > It's runtime PM which is doing stuff wrong here. You're not supposed to
> > give up the clk struct with it enabled.
>
> OK, so below is a fix confined to clock_ops.c.
Yes, works for me:
Tested-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
Thanks
Guennadi
>
> Thanks,
> Rafael
>
>
> ---
> From: Rafael J. Wysocki <rjw@sisk.pl>
> Subject: PM / Clocks: Do not acquire a mutex under a spinlock
>
> Commit b7ab83e (PM: Use spinlock instead of mutex in clock
> management functions) introduced a regression causing clocks_mutex
> to be acquired under a spinlock. This happens because
> pm_clk_suspend() and pm_clk_resume() call pm_clk_acquire() under
> pcd->lock, but pm_clk_acquire() executes clk_get() which causes
> clocks_mutex to be acquired. Similarly, __pm_clk_remove(),
> executed under pcd->lock, calls clk_put(), which also causes
> clocks_mutex to be acquired.
>
> To fix those problems make pm_clk_add() call pm_clk_acquire(), so
> that pm_clk_suspend() and pm_clk_resume() don't have to do that.
> Change pm_clk_remove() and pm_clk_destroy() to separate
> modifications of the pcd->clock_list list from the actual removal of
> PM clock entry objects done by __pm_clk_remove().
>
> Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
> ---
> drivers/base/power/clock_ops.c | 75 ++++++++++++++++++++---------------------
> 1 file changed, 38 insertions(+), 37 deletions(-)
>
> Index: linux/drivers/base/power/clock_ops.c
> =================================> --- linux.orig/drivers/base/power/clock_ops.c
> +++ linux/drivers/base/power/clock_ops.c
> @@ -42,6 +42,22 @@ static struct pm_clk_data *__to_pcd(stru
> }
>
> /**
> + * pm_clk_acquire - Acquire a device clock.
> + * @dev: Device whose clock is to be acquired.
> + * @ce: PM clock entry corresponding to the clock.
> + */
> +static void pm_clk_acquire(struct device *dev, struct pm_clock_entry *ce)
> +{
> + ce->clk = clk_get(dev, ce->con_id);
> + if (IS_ERR(ce->clk)) {
> + ce->status = PCE_STATUS_ERROR;
> + } else {
> + ce->status = PCE_STATUS_ACQUIRED;
> + dev_dbg(dev, "Clock %s managed by runtime PM.\n", ce->con_id);
> + }
> +}
> +
> +/**
> * pm_clk_add - Start using a device clock for power management.
> * @dev: Device whose clock is going to be used for power management.
> * @con_id: Connection ID of the clock.
> @@ -73,6 +89,8 @@ int pm_clk_add(struct device *dev, const
> }
> }
>
> + pm_clk_acquire(dev, ce);
> +
> spin_lock_irq(&pcd->lock);
> list_add_tail(&ce->node, &pcd->clock_list);
> spin_unlock_irq(&pcd->lock);
> @@ -82,17 +100,12 @@ int pm_clk_add(struct device *dev, const
> /**
> * __pm_clk_remove - Destroy PM clock entry.
> * @ce: PM clock entry to destroy.
> - *
> - * This routine must be called under the spinlock protecting the PM list of
> - * clocks corresponding the the @ce's device.
> */
> static void __pm_clk_remove(struct pm_clock_entry *ce)
> {
> if (!ce)
> return;
>
> - list_del(&ce->node);
> -
> if (ce->status < PCE_STATUS_ERROR) {
> if (ce->status = PCE_STATUS_ENABLED)
> clk_disable(ce->clk);
> @@ -126,18 +139,22 @@ void pm_clk_remove(struct device *dev, c
> spin_lock_irq(&pcd->lock);
>
> list_for_each_entry(ce, &pcd->clock_list, node) {
> - if (!con_id && !ce->con_id) {
> - __pm_clk_remove(ce);
> - break;
> - } else if (!con_id || !ce->con_id) {
> + if (!con_id && !ce->con_id)
> + goto remove;
> + else if (!con_id || !ce->con_id)
> continue;
> - } else if (!strcmp(con_id, ce->con_id)) {
> - __pm_clk_remove(ce);
> - break;
> - }
> + else if (!strcmp(con_id, ce->con_id))
> + goto remove;
> }
>
> spin_unlock_irq(&pcd->lock);
> + return;
> +
> + remove:
> + list_del(&ce->node);
> + spin_unlock_irq(&pcd->lock);
> +
> + __pm_clk_remove(ce);
> }
>
> /**
> @@ -175,20 +192,27 @@ void pm_clk_destroy(struct device *dev)
> {
> struct pm_clk_data *pcd = __to_pcd(dev);
> struct pm_clock_entry *ce, *c;
> + struct list_head list;
>
> if (!pcd)
> return;
>
> dev->power.subsys_data = NULL;
> + INIT_LIST_HEAD(&list);
>
> spin_lock_irq(&pcd->lock);
>
> list_for_each_entry_safe_reverse(ce, c, &pcd->clock_list, node)
> - __pm_clk_remove(ce);
> + list_move(&ce->node, &list);
>
> spin_unlock_irq(&pcd->lock);
>
> kfree(pcd);
> +
> + list_for_each_entry_safe_reverse(ce, c, &list, node) {
> + list_del(&ce->node);
> + __pm_clk_remove(ce);
> + }
> }
>
> #endif /* CONFIG_PM */
> @@ -196,23 +220,6 @@ void pm_clk_destroy(struct device *dev)
> #ifdef CONFIG_PM_RUNTIME
>
> /**
> - * pm_clk_acquire - Acquire a device clock.
> - * @dev: Device whose clock is to be acquired.
> - * @con_id: Connection ID of the clock.
> - */
> -static void pm_clk_acquire(struct device *dev,
> - struct pm_clock_entry *ce)
> -{
> - ce->clk = clk_get(dev, ce->con_id);
> - if (IS_ERR(ce->clk)) {
> - ce->status = PCE_STATUS_ERROR;
> - } else {
> - ce->status = PCE_STATUS_ACQUIRED;
> - dev_dbg(dev, "Clock %s managed by runtime PM.\n", ce->con_id);
> - }
> -}
> -
> -/**
> * pm_clk_suspend - Disable clocks in a device's PM clock list.
> * @dev: Device to disable the clocks for.
> */
> @@ -230,9 +237,6 @@ int pm_clk_suspend(struct device *dev)
> spin_lock_irqsave(&pcd->lock, flags);
>
> list_for_each_entry_reverse(ce, &pcd->clock_list, node) {
> - if (ce->status = PCE_STATUS_NONE)
> - pm_clk_acquire(dev, ce);
> -
> if (ce->status < PCE_STATUS_ERROR) {
> clk_disable(ce->clk);
> ce->status = PCE_STATUS_ACQUIRED;
> @@ -262,9 +266,6 @@ int pm_clk_resume(struct device *dev)
> spin_lock_irqsave(&pcd->lock, flags);
>
> list_for_each_entry(ce, &pcd->clock_list, node) {
> - if (ce->status = PCE_STATUS_NONE)
> - pm_clk_acquire(dev, ce);
> -
> if (ce->status < PCE_STATUS_ERROR) {
> clk_enable(ce->clk);
> ce->status = PCE_STATUS_ENABLED;
>
---
Guennadi Liakhovetski, Ph.D.
Freelance Open-Source Software Developer
http://www.open-technology.de/
^ permalink raw reply
* sh-sci blocked for sh7203
From: Federico Fuga @ 2011-09-26 11:50 UTC (permalink / raw)
To: linux-sh
Hi,
I am working on a sh7203 platform, running linux 2.6.37.
It worked just fine, except some other problem do not worth mention here.
Now I wanted to update the kernel - I once tried with 2.6.38 but faced problems while booting, and I kept my working version.
Now tried again with 3.0.x but the same problem occurred.
While using the e10a emulator I found that the boot process blocks while setting up the kernel.
It locks up here:
static void sci_set_termios(struct uart_port *port, struct ktermios *termios,
struct ktermios *old)
{
// ....
do {
status = sci_in(port, SCxSR);
} while (!(status & SCxSR_TEND(port)));
// ....
Here the sci0 registers dump :
SCSMR: 0000
SCBRR: 0008
SCSCR: 0038
SCFSR: 0060
SCFCR: 0000
SCFDR: 0000
SCSPTR: 0051
SCLSR: 0000
SCEMR: 0000
It seems that the SCSCR register disables the TX and RX, so the flags can't pass the loop.
Did anyone find similar issue? Where can I check if these registers are correctly setup?
thank you in advance
best regards
ing. Federico Fuga
^ permalink raw reply
* [PATCH 2/2] sh: add platform_device for USB function in board-sh7757lcr
From: Yoshihiro Shimoda @ 2011-09-26 8:43 UTC (permalink / raw)
To: linux-sh
Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
---
This patch depends on the following patch:
- usb: r8a66597-udc: add support for SUDMAC
arch/sh/boards/board-sh7757lcr.c | 39 ++++++++++++++++++++++++++++++++++++++
1 files changed, 39 insertions(+), 0 deletions(-)
diff --git a/arch/sh/boards/board-sh7757lcr.c b/arch/sh/boards/board-sh7757lcr.c
index 8e6f9d7..dd2c8c6 100644
--- a/arch/sh/boards/board-sh7757lcr.c
+++ b/arch/sh/boards/board-sh7757lcr.c
@@ -18,6 +18,7 @@
#include <linux/mmc/host.h>
#include <linux/mmc/sh_mmcif.h>
#include <linux/mmc/sh_mobile_sdhi.h>
+#include <linux/usb/r8a66597.h>
#include <cpu/sh7757.h>
#include <asm/sh_eth.h>
#include <asm/heartbeat.h>
@@ -317,6 +318,43 @@ static struct platform_device riic3_device = {
},
};
+static struct r8a66597_platdata usb0_data = {
+ .on_chip = 1,
+ .buswait = 5,
+ .sudmac = 1,
+};
+
+static struct resource usb0_resources[] = {
+ [0] = {
+ .start = 0xfe450000,
+ .end = 0xfe4501ff,
+ .flags = IORESOURCE_MEM,
+ },
+ [1] = {
+ .start = 50,
+ .end = 50,
+ .flags = IORESOURCE_IRQ,
+ },
+ [2] = {
+ .name = "sudmac",
+ .start = 0xfe451000,
+ .end = 0xfe4517ff,
+ .flags = IORESOURCE_MEM,
+ },
+};
+
+static struct platform_device usb0_device = {
+ .name = "r8a66597_udc",
+ .id = 0,
+ .dev = {
+ .dma_mask = NULL,
+ .coherent_dma_mask = 0xffffffff,
+ .platform_data = &usb0_data,
+ },
+ .num_resources = ARRAY_SIZE(usb0_resources),
+ .resource = usb0_resources,
+};
+
static struct platform_device *sh7757lcr_devices[] __initdata = {
&heartbeat_device,
&sh7757_eth0_device,
@@ -327,6 +365,7 @@ static struct platform_device *sh7757lcr_devices[] __initdata = {
&sdhi_device,
&riic2_device,
&riic3_device,
+ &usb0_device,
};
static struct flash_platform_data spi_flash_data = {
--
1.7.1
^ permalink raw reply related
* [PATCH 1/2] sh: add platform_device for i2c-riic driver in
From: Yoshihiro Shimoda @ 2011-09-26 8:43 UTC (permalink / raw)
To: linux-sh
This patch adds the platform_device of i2c-riic driver and
the i2c_board_info for the board.
This patch also adds the enabling the DMAC for RIIC (DMAER2).
This register is contained in the SH7757 only. the dmaengine
doesn't handle it. So, it is enabled on the board initialize.
Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
---
This patch depends on the following patches:
- i2c: i2c-riic: add support for Renesas RIIC
- i2c: i2c-riic: add dmaengine supporting
arch/sh/boards/board-sh7757lcr.c | 87 ++++++++++++++++++++++++++++++++++++++
1 files changed, 87 insertions(+), 0 deletions(-)
diff --git a/arch/sh/boards/board-sh7757lcr.c b/arch/sh/boards/board-sh7757lcr.c
index fa2a208..8e6f9d7 100644
--- a/arch/sh/boards/board-sh7757lcr.c
+++ b/arch/sh/boards/board-sh7757lcr.c
@@ -21,6 +21,9 @@
#include <cpu/sh7757.h>
#include <asm/sh_eth.h>
#include <asm/heartbeat.h>
+#include <linux/i2c.h>
+#include <linux/i2c/riic.h>
+#include <linux/i2c/at24.h>
static struct resource heartbeat_resource = {
.start = 0xffec005c, /* PUDR */
@@ -260,6 +263,60 @@ static struct platform_device sdhi_device = {
},
};
+struct riic_platform_data riic2_pdata = {
+ .clock = 400,
+ .dma_tx = {
+ .slave_id = SHDMA_SLAVE_RIIC2_TX,
+ },
+ .dma_rx = {
+ .slave_id = SHDMA_SLAVE_RIIC2_RX,
+ },
+};
+
+static struct resource riic2_resources[] = {
+ {
+ .start = 0xfe520000,
+ .end = 0xfe5200ff,
+ .flags = IORESOURCE_MEM,
+ }, {
+ .start = 160,
+ .flags = IORESOURCE_IRQ,
+ }
+};
+
+static struct platform_device riic2_device = {
+ .name = "i2c-riic",
+ .id = 2,
+ .num_resources = ARRAY_SIZE(riic2_resources),
+ .resource = riic2_resources,
+ .dev = {
+ .platform_data = &riic2_pdata,
+ },
+};
+
+/* We don't use DMA on the channel because LM75 donen't use large data. */
+struct riic_platform_data riic3_pdata = {
+ .clock = 400,
+};
+
+static struct resource riic3_resources[] = {
+ {
+ .start = 0xfe530000,
+ .end = 0xfe5300ff,
+ .flags = IORESOURCE_MEM,
+ }
+};
+
+static struct platform_device riic3_device = {
+ .name = "i2c-riic",
+ .id = 3,
+ .num_resources = ARRAY_SIZE(riic3_resources),
+ .resource = riic3_resources,
+ .dev = {
+ .platform_data = &riic3_pdata,
+ },
+};
+
static struct platform_device *sh7757lcr_devices[] __initdata = {
&heartbeat_device,
&sh7757_eth0_device,
@@ -268,6 +325,8 @@ static struct platform_device *sh7757lcr_devices[] __initdata = {
&sh7757_eth_giga1_device,
&sh_mmcif_device,
&sdhi_device,
+ &riic2_device,
+ &riic3_device,
};
static struct flash_platform_data spi_flash_data = {
@@ -285,6 +344,26 @@ static struct spi_board_info spi_board_info[] = {
},
};
+static struct at24_platform_data sh7757_at24_platdata = {
+ .byte_len = 8192,
+ .page_size = 64,
+ .flags = AT24_FLAG_ADDR16,
+};
+
+static struct i2c_board_info __initdata riic2_board_devices[] = {
+ {
+ I2C_BOARD_INFO("24c64", 0x50),
+ .platform_data = &sh7757_at24_platdata,
+ },
+};
+
+static struct i2c_board_info __initdata riic3_board_devices[] = {
+ {
+ I2C_BOARD_INFO("lm75", 0x4f),
+ },
+};
+
+#define DMAER2 0xfffa0000
static int __init sh7757lcr_devices_setup(void)
{
/* RGMII (PTA) */
@@ -517,6 +596,14 @@ static int __init sh7757lcr_devices_setup(void)
spi_register_board_info(spi_board_info,
ARRAY_SIZE(spi_board_info));
+ i2c_register_board_info(2, riic2_board_devices,
+ ARRAY_SIZE(riic2_board_devices));
+ i2c_register_board_info(3, riic3_board_devices,
+ ARRAY_SIZE(riic3_board_devices));
+
+ /* Enable RIIC's DMA */
+ __raw_writel(0x000003ff, DMAER2);
+
/* General platform */
return platform_add_devices(sh7757lcr_devices,
ARRAY_SIZE(sh7757lcr_devices));
--
1.7.1
^ permalink raw reply related
* [PATCH 2/2] usb: r8a66597-udc: add support for SUDMAC
From: Yoshihiro Shimoda @ 2011-09-26 8:43 UTC (permalink / raw)
To: linux-sh
SH7757 has a USB function with internal DMA controller (SUDMAC).
This patch supports the SUDMAC. The SUDMAC is incompatible with
general-purpose DMAC. So, it doesn't use dmaengine.
Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
---
drivers/usb/gadget/r8a66597-udc.c | 359 +++++++++++++++++++++++++++++++++++--
drivers/usb/gadget/r8a66597-udc.h | 26 +++-
include/linux/usb/r8a66597.h | 60 ++++++
3 files changed, 425 insertions(+), 20 deletions(-)
diff --git a/drivers/usb/gadget/r8a66597-udc.c b/drivers/usb/gadget/r8a66597-udc.c
index cd2cd16..4d27cb3 100644
--- a/drivers/usb/gadget/r8a66597-udc.c
+++ b/drivers/usb/gadget/r8a66597-udc.c
@@ -28,13 +28,14 @@
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/slab.h>
+#include <linux/dma-mapping.h>
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
#include "r8a66597-udc.h"
-#define DRIVER_VERSION "2009-08-18"
+#define DRIVER_VERSION "2011-09-26"
static const char udc_name[] = "r8a66597_udc";
static const char *r8a66597_ep_name[] = {
@@ -186,6 +187,51 @@ static inline void control_reg_sqclr(struct r8a66597 *r8a66597, u16 pipenum)
printk(KERN_ERR "unexpect pipe num(%d)\n", pipenum);
}
+static void control_reg_sqset(struct r8a66597 *r8a66597, u16 pipenum)
+{
+ unsigned long offset;
+
+ pipe_stop(r8a66597, pipenum);
+
+ if (pipenum = 0)
+ r8a66597_bset(r8a66597, SQSET, DCPCTR);
+ else if (pipenum < R8A66597_MAX_NUM_PIPE) {
+ offset = get_pipectr_addr(pipenum);
+ r8a66597_bset(r8a66597, SQSET, offset);
+ } else
+ printk(KERN_ERR "unexpect pipe num(%d)\n", pipenum);
+}
+
+
+static u16 control_reg_sqmon(struct r8a66597 *r8a66597, u16 pipenum)
+{
+ unsigned long offset;
+
+ if (pipenum = 0)
+ return r8a66597_read(r8a66597, DCPCTR) & SQMON;
+ else if (pipenum < R8A66597_MAX_NUM_PIPE) {
+ offset = get_pipectr_addr(pipenum);
+ return r8a66597_read(r8a66597, offset) & SQMON;
+ } else
+ printk(KERN_ERR "unexpect pipe num(%d)\n", pipenum);
+
+ return 0;
+}
+
+static u16 save_usb_toggle(struct r8a66597 *r8a66597, u16 pipenum)
+{
+ return control_reg_sqmon(r8a66597, pipenum);
+}
+
+static void restore_usb_toggle(struct r8a66597 *r8a66597, u16 pipenum,
+ u16 toggle)
+{
+ if (toggle)
+ control_reg_sqset(r8a66597, pipenum);
+ else
+ control_reg_sqclr(r8a66597, pipenum);
+}
+
static inline int get_buffer_size(struct r8a66597 *r8a66597, u16 pipenum)
{
u16 tmp;
@@ -222,18 +268,50 @@ static inline unsigned short mbw_value(struct r8a66597 *r8a66597)
return MBW_16;
}
+static void r8a66597_change_curpipe(struct r8a66597 *r8a66597, u16 pipenum,
+ u16 isel, u16 fifosel)
+{
+ u16 tmp, mask, loop;
+ int i = 0;
+
+ if (!pipenum) {
+ mask = ISEL | CURPIPE;
+ loop = isel;
+ } else {
+ mask = CURPIPE;
+ loop = pipenum;
+ }
+ r8a66597_mdfy(r8a66597, loop, mask, fifosel);
+
+ do {
+ tmp = r8a66597_read(r8a66597, fifosel);
+ if (i++ > 1000000) {
+ printk(KERN_ERR "r8a66597: register%x, loop %x "
+ "is timeout\n", fifosel, loop);
+ break;
+ }
+ ndelay(1);
+ } while ((tmp & mask) != loop);
+}
+
static inline void pipe_change(struct r8a66597 *r8a66597, u16 pipenum)
{
struct r8a66597_ep *ep = r8a66597->pipenum2ep[pipenum];
if (ep->use_dma)
- return;
+ r8a66597_bclr(r8a66597, DREQE, ep->fifosel);
r8a66597_mdfy(r8a66597, pipenum, CURPIPE, ep->fifosel);
ndelay(450);
- r8a66597_bset(r8a66597, mbw_value(r8a66597), ep->fifosel);
+ if (r8a66597_is_sudmac(r8a66597) && ep->use_dma)
+ r8a66597_bclr(r8a66597, mbw_value(r8a66597), ep->fifosel);
+ else
+ r8a66597_bset(r8a66597, mbw_value(r8a66597), ep->fifosel);
+
+ if (ep->use_dma)
+ r8a66597_bset(r8a66597, DREQE, ep->fifosel);
}
static int pipe_buffer_setting(struct r8a66597 *r8a66597,
@@ -337,9 +415,15 @@ static void r8a66597_ep_setting(struct r8a66597 *r8a66597,
ep->fifoaddr = CFIFO;
ep->fifosel = CFIFOSEL;
ep->fifoctr = CFIFOCTR;
- ep->fifotrn = 0;
ep->pipectr = get_pipectr_addr(pipenum);
+ if (is_bulk_pipe(pipenum) || is_isoc_pipe(pipenum)) {
+ ep->pipetre = get_pipetre_addr(pipenum);
+ ep->pipetrn = get_pipetrn_addr(pipenum);
+ } else {
+ ep->pipetre = 0;
+ ep->pipetrn = 0;
+ }
ep->pipenum = pipenum;
ep->ep.maxpacket = le16_to_cpu(desc->wMaxPacketSize);
r8a66597->pipenum2ep[pipenum] = ep;
@@ -495,6 +579,124 @@ static void start_ep0_write(struct r8a66597_ep *ep,
}
}
+static void disable_fifosel(struct r8a66597 *r8a66597, u16 pipenum,
+ u16 fifosel)
+{
+ u16 tmp;
+
+ tmp = r8a66597_read(r8a66597, fifosel) & CURPIPE;
+ if (tmp = pipenum)
+ r8a66597_change_curpipe(r8a66597, 0, 0, fifosel);
+}
+
+static void change_bfre_mode(struct r8a66597 *r8a66597, u16 pipenum,
+ int enable)
+{
+ struct r8a66597_ep *ep = r8a66597->pipenum2ep[pipenum];
+ u16 tmp, toggle;
+
+ /* check current BFRE bit */
+ r8a66597_write(r8a66597, pipenum, PIPESEL);
+ tmp = r8a66597_read(r8a66597, PIPECFG) & R8A66597_BFRE;
+ if ((enable && tmp) || (!enable && !tmp))
+ return;
+
+ /* change BFRE bit */
+ pipe_stop(r8a66597, pipenum);
+ disable_fifosel(r8a66597, pipenum, CFIFOSEL);
+ disable_fifosel(r8a66597, pipenum, D0FIFOSEL);
+ disable_fifosel(r8a66597, pipenum, D1FIFOSEL);
+
+ toggle = save_usb_toggle(r8a66597, pipenum);
+
+ r8a66597_write(r8a66597, pipenum, PIPESEL);
+ if (enable)
+ r8a66597_bset(r8a66597, R8A66597_BFRE, PIPECFG);
+ else
+ r8a66597_bclr(r8a66597, R8A66597_BFRE, PIPECFG);
+
+ /* initialize for internal BFRE flag */
+ r8a66597_bset(r8a66597, ACLRM, ep->pipectr);
+ r8a66597_bclr(r8a66597, ACLRM, ep->pipectr);
+
+ restore_usb_toggle(r8a66597, pipenum, toggle);
+}
+
+static int sudmac_alloc_channel(struct r8a66597 *r8a66597,
+ struct r8a66597_ep *ep,
+ struct r8a66597_request *req)
+{
+ struct r8a66597_dma *dma;
+
+ if (!r8a66597_is_sudmac(r8a66597))
+ return -ENODEV;
+
+ /* Check transfer type */
+ if (!is_bulk_pipe(ep->pipenum))
+ return -EIO;
+
+ if (r8a66597->dma.used)
+ return -EBUSY;
+
+ /* set SUDMAC parameters */
+ dma = &r8a66597->dma;
+ dma->used = 1;
+ if (ep->desc->bEndpointAddress & USB_DIR_IN) {
+ dma->dir = 1;
+ } else {
+ dma->dir = 0;
+ change_bfre_mode(r8a66597, ep->pipenum, 1);
+ }
+
+ /* set r8a66597_ep paramters */
+ ep->use_dma = 1;
+ ep->dma = dma;
+ ep->fifoaddr = D0FIFO;
+ ep->fifosel = D0FIFOSEL;
+ ep->fifoctr = D0FIFOCTR;
+
+ /* dma mapping */
+ req->req.dma = dma_map_single(ep->r8a66597->gadget.dev.parent,
+ req->req.buf, req->req.length,
+ dma->dir ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
+
+ return 0;
+}
+
+static void sudmac_free_channel(struct r8a66597 *r8a66597,
+ struct r8a66597_ep *ep,
+ struct r8a66597_request *req)
+{
+ if (!r8a66597_is_sudmac(r8a66597))
+ return;
+
+ dma_unmap_single(ep->r8a66597->gadget.dev.parent,
+ req->req.dma, req->req.length,
+ ep->dma->dir ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
+
+ r8a66597_bclr(r8a66597, DREQE, ep->fifosel);
+ r8a66597_change_curpipe(r8a66597, 0, 0, ep->fifosel);
+
+ ep->dma->used = 0;
+ ep->use_dma = 0;
+ ep->fifoaddr = CFIFO;
+ ep->fifosel = CFIFOSEL;
+ ep->fifoctr = CFIFOCTR;
+}
+
+static void sudmac_start(struct r8a66597 *r8a66597, struct r8a66597_ep *ep,
+ struct r8a66597_request *req)
+{
+ BUG_ON(req->req.length = 0);
+
+ r8a66597_sudmac_write(r8a66597, LBA_WAIT, CH0CFG);
+ r8a66597_sudmac_write(r8a66597, req->req.dma, CH0BA);
+ r8a66597_sudmac_write(r8a66597, req->req.length, CH0BBC);
+ r8a66597_sudmac_write(r8a66597, CH0ENDE, DINTCTRL);
+
+ r8a66597_sudmac_write(r8a66597, DEN, CH0DEN);
+}
+
static void start_packet_write(struct r8a66597_ep *ep,
struct r8a66597_request *req)
{
@@ -505,11 +707,29 @@ static void start_packet_write(struct r8a66597_ep *ep,
disable_irq_empty(r8a66597, ep->pipenum);
pipe_start(r8a66597, ep->pipenum);
- tmp = r8a66597_read(r8a66597, ep->fifoctr);
- if (unlikely((tmp & FRDY) = 0))
- pipe_irq_enable(r8a66597, ep->pipenum);
- else
- irq_packet_write(ep, req);
+ if (req->req.length = 0) {
+ transfer_complete(ep, req, 0);
+ } else {
+ r8a66597_write(r8a66597, ~(1 << ep->pipenum), BRDYSTS);
+ if (sudmac_alloc_channel(r8a66597, ep, req) < 0) {
+ /* PIO mode */
+ pipe_change(r8a66597, ep->pipenum);
+ disable_irq_empty(r8a66597, ep->pipenum);
+ pipe_start(r8a66597, ep->pipenum);
+ tmp = r8a66597_read(r8a66597, ep->fifoctr);
+ if (unlikely((tmp & FRDY) = 0))
+ pipe_irq_enable(r8a66597, ep->pipenum);
+ else
+ irq_packet_write(ep, req);
+ } else {
+ /* DMA mode */
+ pipe_change(r8a66597, ep->pipenum);
+ disable_irq_nrdy(r8a66597, ep->pipenum);
+ pipe_start(r8a66597, ep->pipenum);
+ enable_irq_nrdy(r8a66597, ep->pipenum);
+ sudmac_start(r8a66597, ep, req);
+ }
+ }
}
static void start_packet_read(struct r8a66597_ep *ep,
@@ -524,17 +744,26 @@ static void start_packet_read(struct r8a66597_ep *ep,
pipe_start(r8a66597, pipenum);
pipe_irq_enable(r8a66597, pipenum);
} else {
- if (ep->use_dma) {
- r8a66597_bset(r8a66597, TRCLR, ep->fifosel);
- pipe_change(r8a66597, pipenum);
- r8a66597_bset(r8a66597, TRENB, ep->fifosel);
+ pipe_stop(r8a66597, pipenum);
+ if (ep->pipetre) {
+ enable_irq_nrdy(r8a66597, pipenum);
+ r8a66597_write(r8a66597, TRCLR, ep->pipetre);
r8a66597_write(r8a66597,
- (req->req.length + ep->ep.maxpacket - 1)
- / ep->ep.maxpacket,
- ep->fifotrn);
+ DIV_ROUND_UP(req->req.length, ep->ep.maxpacket),
+ ep->pipetrn);
+ r8a66597_bset(r8a66597, TRENB, ep->pipetre);
+ }
+
+ if (sudmac_alloc_channel(r8a66597, ep, req) < 0) {
+ /* PIO mode */
+ change_bfre_mode(r8a66597, ep->pipenum, 0);
+ pipe_start(r8a66597, pipenum); /* trigger once */
+ pipe_irq_enable(r8a66597, pipenum);
+ } else {
+ pipe_change(r8a66597, pipenum);
+ sudmac_start(r8a66597, ep, req);
+ pipe_start(r8a66597, pipenum); /* trigger once */
}
- pipe_start(r8a66597, pipenum); /* trigger once */
- pipe_irq_enable(r8a66597, pipenum);
}
}
@@ -690,6 +919,9 @@ __acquires(r8a66597->lock)
if (!list_empty(&ep->queue))
restart = 1;
+ if (ep->use_dma)
+ sudmac_free_channel(ep->r8a66597, ep, req);
+
spin_unlock(&ep->r8a66597->lock);
req->req.complete(&ep->ep, &req->req);
spin_lock(&ep->r8a66597->lock);
@@ -1163,6 +1395,64 @@ __acquires(r8a66597->lock)
}
}
+static void sudmac_finish(struct r8a66597 *r8a66597, struct r8a66597_ep *ep)
+{
+ u16 pipenum;
+ struct r8a66597_request *req;
+ u32 len;
+ int i = 0;
+
+ pipenum = ep->pipenum;
+ pipe_change(r8a66597, pipenum);
+
+ while (!(r8a66597_read(r8a66597, ep->fifoctr) & FRDY)) {
+ udelay(1);
+ if (unlikely(i++ >= 10000)) { /* timeout = 10 msec */
+ printk(KERN_ERR "%s: FRDY was not set (%d)\n",
+ __func__, pipenum);
+ return;
+ }
+ }
+
+ r8a66597_bset(r8a66597, BCLR, ep->fifoctr);
+ req = get_request_from_ep(ep);
+
+ /* prepare parameters */
+ len = r8a66597_sudmac_read(r8a66597, CH0CBC);
+ req->req.actual += len;
+
+ /* clear */
+ r8a66597_sudmac_write(r8a66597, CH0STCLR, DSTSCLR);
+
+ /* check transfer finish */
+ if ((!req->req.zero && (req->req.actual = req->req.length))
+ || (len % ep->ep.maxpacket)) {
+ if (ep->dma->dir) {
+ disable_irq_ready(r8a66597, pipenum);
+ enable_irq_empty(r8a66597, pipenum);
+ } else {
+ /* Clear the interrupt flag for next transfer */
+ r8a66597_write(r8a66597, ~(1 << pipenum), BRDYSTS);
+ transfer_complete(ep, req, 0);
+ }
+ }
+}
+
+static void r8a66597_sudmac_irq(struct r8a66597 *r8a66597)
+{
+ u32 irqsts;
+ struct r8a66597_ep *ep;
+ u16 pipenum;
+
+ irqsts = r8a66597_sudmac_read(r8a66597, DINTSTS);
+ if (irqsts & CH0ENDS) {
+ r8a66597_sudmac_write(r8a66597, CH0ENDC, DINTSTSCLR);
+ pipenum = (r8a66597_read(r8a66597, D0FIFOSEL) & CURPIPE);
+ ep = r8a66597->pipenum2ep[pipenum];
+ sudmac_finish(r8a66597, ep);
+ }
+}
+
static irqreturn_t r8a66597_irq(int irq, void *_r8a66597)
{
struct r8a66597 *r8a66597 = _r8a66597;
@@ -1173,6 +1463,9 @@ static irqreturn_t r8a66597_irq(int irq, void *_r8a66597)
u16 savepipe;
u16 mask0;
+ if (r8a66597_is_sudmac(r8a66597))
+ r8a66597_sudmac_irq(r8a66597);
+
spin_lock(&r8a66597->lock);
intsts0 = r8a66597_read(r8a66597, INTSTS0);
@@ -1547,6 +1840,8 @@ static int __exit r8a66597_remove(struct platform_device *pdev)
usb_del_gadget_udc(&r8a66597->gadget);
del_timer_sync(&r8a66597->timer);
iounmap(r8a66597->reg);
+ if (r8a66597->pdata->sudmac)
+ iounmap(r8a66597->sudmac_reg);
free_irq(platform_get_irq(pdev, 0), r8a66597);
r8a66597_free_request(&r8a66597->ep[0].ep, r8a66597->ep0_req);
#ifdef CONFIG_HAVE_CLK
@@ -1563,6 +1858,26 @@ static void nop_completion(struct usb_ep *ep, struct usb_request *r)
{
}
+static int __init r8a66597_sudmac_ioremap(struct r8a66597 *r8a66597,
+ struct platform_device *pdev)
+{
+ struct resource *res;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sudmac");
+ if (!res) {
+ dev_err(&pdev->dev, "platform_get_resource error(sudmac).\n");
+ return -ENODEV;
+ }
+
+ r8a66597->sudmac_reg = ioremap(res->start, resource_size(res));
+ if (r8a66597->sudmac_reg = NULL) {
+ dev_err(&pdev->dev, "ioremap error(sudmac).\n");
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
static int __init r8a66597_probe(struct platform_device *pdev)
{
#ifdef CONFIG_HAVE_CLK
@@ -1640,6 +1955,11 @@ static int __init r8a66597_probe(struct platform_device *pdev)
clk_enable(r8a66597->clk);
}
#endif
+ if (r8a66597->pdata->sudmac) {
+ ret = r8a66597_sudmac_ioremap(r8a66597, pdev);
+ if (ret < 0)
+ goto clean_up2;
+ }
disable_controller(r8a66597); /* make sure controller is disabled */
@@ -1672,7 +1992,6 @@ static int __init r8a66597_probe(struct platform_device *pdev)
r8a66597->ep[0].fifoaddr = CFIFO;
r8a66597->ep[0].fifosel = CFIFOSEL;
r8a66597->ep[0].fifoctr = CFIFOCTR;
- r8a66597->ep[0].fifotrn = 0;
r8a66597->ep[0].pipectr = get_pipectr_addr(0);
r8a66597->pipenum2ep[0] = &r8a66597->ep[0];
r8a66597->epaddr2ep[0] = &r8a66597->ep[0];
@@ -1705,6 +2024,8 @@ clean_up2:
#endif
clean_up:
if (r8a66597) {
+ if (r8a66597->sudmac_reg)
+ iounmap(r8a66597->sudmac_reg);
if (r8a66597->ep0_req)
r8a66597_free_request(&r8a66597->ep[0].ep,
r8a66597->ep0_req);
diff --git a/drivers/usb/gadget/r8a66597-udc.h b/drivers/usb/gadget/r8a66597-udc.h
index 37d0dd5..93376f7 100644
--- a/drivers/usb/gadget/r8a66597-udc.h
+++ b/drivers/usb/gadget/r8a66597-udc.h
@@ -53,6 +53,7 @@
((pipenum >= R8A66597_BASE_PIPENUM_ISOC) && \
(pipenum < (R8A66597_BASE_PIPENUM_ISOC + R8A66597_MAX_NUM_ISOC)))
+#define r8a66597_is_sudmac(r8a66597) (r8a66597->pdata->sudmac)
struct r8a66597_pipe_info {
u16 pipe;
u16 epnum;
@@ -70,6 +71,7 @@ struct r8a66597_request {
struct r8a66597_ep {
struct usb_ep ep;
struct r8a66597 *r8a66597;
+ struct r8a66597_dma *dma;
struct list_head queue;
unsigned busy:1;
@@ -85,13 +87,20 @@ struct r8a66597_ep {
unsigned char fifoaddr;
unsigned char fifosel;
unsigned char fifoctr;
- unsigned char fifotrn;
unsigned char pipectr;
+ unsigned char pipetre;
+ unsigned char pipetrn;
+};
+
+struct r8a66597_dma {
+ unsigned used:1;
+ unsigned dir:1; /* 1 = IN(write), 0 = OUT(read) */
};
struct r8a66597 {
spinlock_t lock;
void __iomem *reg;
+ void __iomem *sudmac_reg;
#ifdef CONFIG_HAVE_CLK
struct clk *clk;
@@ -104,6 +113,7 @@ struct r8a66597 {
struct r8a66597_ep ep[R8A66597_MAX_NUM_PIPE];
struct r8a66597_ep *pipenum2ep[R8A66597_MAX_NUM_PIPE];
struct r8a66597_ep *epaddr2ep[16];
+ struct r8a66597_dma dma;
struct timer_list timer;
struct usb_request *ep0_req; /* for internal request */
@@ -260,7 +270,21 @@ static inline u16 get_xtal_from_pdata(struct r8a66597_platdata *pdata)
return clock;
}
+static inline u32 r8a66597_sudmac_read(struct r8a66597 *r8a66597,
+ unsigned long offset)
+{
+ return ioread32(r8a66597->sudmac_reg + offset);
+}
+
+static inline void r8a66597_sudmac_write(struct r8a66597 *r8a66597, u32 val,
+ unsigned long offset)
+{
+ iowrite32(val, r8a66597->sudmac_reg + offset);
+}
+
#define get_pipectr_addr(pipenum) (PIPE1CTR + (pipenum - 1) * 2)
+#define get_pipetre_addr(pipenum) (PIPE1TRE + (pipenum - 1) * 4)
+#define get_pipetrn_addr(pipenum) (PIPE1TRN + (pipenum - 1) * 4)
#define enable_irq_ready(r8a66597, pipenum) \
enable_pipe_irq(r8a66597, pipenum, BRDYENB)
diff --git a/include/linux/usb/r8a66597.h b/include/linux/usb/r8a66597.h
index b6b8660..55805f9 100644
--- a/include/linux/usb/r8a66597.h
+++ b/include/linux/usb/r8a66597.h
@@ -48,6 +48,9 @@ struct r8a66597_platdata {
/* (external controller only) set one = WR0_N shorted to WR1_N */
unsigned wr0_shorted_to_wr1:1;
+
+ /* set one = using SUDMAC */
+ unsigned sudmac:1;
};
/* Register definitions */
@@ -417,5 +420,62 @@ struct r8a66597_platdata {
#define USBSPD 0x00C0
#define RTPORT 0x0001
+/* SUDMAC registers */
+#define CH0CFG 0x00
+#define CH1CFG 0x04
+#define CH0BA 0x10
+#define CH1BA 0x14
+#define CH0BBC 0x18
+#define CH1BBC 0x1C
+#define CH0CA 0x20
+#define CH1CA 0x24
+#define CH0CBC 0x28
+#define CH1CBC 0x2C
+#define CH0DEN 0x30
+#define CH1DEN 0x34
+#define DSTSCLR 0x38
+#define DBUFCTRL 0x3C
+#define DINTCTRL 0x40
+#define DINTSTS 0x44
+#define DINTSTSCLR 0x48
+#define CH0SHCTRL 0x50
+#define CH1SHCTRL 0x54
+
+/* SUDMAC Configuration Registers */
+#define SENDBUFM 0x1000 /* b12: Transmit Buffer Mode */
+#define RCVENDM 0x0100 /* b8: Receive Data Transfer End Mode */
+#define LBA_WAIT 0x0030 /* b5-4: Local Bus Access Wait */
+
+/* DMA Enable Registers */
+#define DEN 0x0001 /* b1: DMA Transfer Enable */
+
+/* DMA Status Clear Register */
+#define CH1STCLR 0x0002 /* b2: Ch1 DMA Status Clear */
+#define CH0STCLR 0x0001 /* b1: Ch0 DMA Status Clear */
+
+/* DMA Buffer Control Register */
+#define CH1BUFW 0x0200 /* b9: Ch1 DMA Buffer Data Transfer Enable */
+#define CH0BUFW 0x0100 /* b8: Ch0 DMA Buffer Data Transfer Enable */
+#define CH1BUFS 0x0002 /* b2: Ch1 DMA Buffer Data Status */
+#define CH0BUFS 0x0001 /* b1: Ch0 DMA Buffer Data Status */
+
+/* DMA Interrupt Control Register */
+#define CH1ERRE 0x0200 /* b9: Ch1 SHwy Res Err Detect Int Enable */
+#define CH0ERRE 0x0100 /* b8: Ch0 SHwy Res Err Detect Int Enable */
+#define CH1ENDE 0x0002 /* b2: Ch1 DMA Transfer End Int Enable */
+#define CH0ENDE 0x0001 /* b1: Ch0 DMA Transfer End Int Enable */
+
+/* DMA Interrupt Status Register */
+#define CH1ERRS 0x0200 /* b9: Ch1 SHwy Res Err Detect Int Status */
+#define CH0ERRS 0x0100 /* b8: Ch0 SHwy Res Err Detect Int Status */
+#define CH1ENDS 0x0002 /* b2: Ch1 DMA Transfer End Int Status */
+#define CH0ENDS 0x0001 /* b1: Ch0 DMA Transfer End Int Status */
+
+/* DMA Interrupt Status Clear Register */
+#define CH1ERRC 0x0200 /* b9: Ch1 SHwy Res Err Detect Int Stat Clear */
+#define CH0ERRC 0x0100 /* b8: Ch0 SHwy Res Err Detect Int Stat Clear */
+#define CH1ENDC 0x0002 /* b2: Ch1 DMA Transfer End Int Stat Clear */
+#define CH0ENDC 0x0001 /* b1: Ch0 DMA Transfer End Int Stat Clear */
+
#endif /* __LINUX_USB_R8A66597_H */
--
1.7.1
^ permalink raw reply related
* [PATCH 2/2 v3] i2c: i2c-riic: add dmaengine supporting
From: Yoshihiro Shimoda @ 2011-09-26 8:42 UTC (permalink / raw)
To: ben-linux-elnMNo+KYs3YtjvyW6yDsg
Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA, SH-Linux
We can use the dmaengine for the driver, if the dma_tx and/or
dma_rx in riic_platform_data is set. If we use it, we have to set
the irq number for EEI in the resource.
Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
---
about v3
- adjust for the latest kernel (fix compile error)
drivers/i2c/busses/i2c-riic.c | 290 +++++++++++++++++++++++++++++++++++------
include/linux/i2c/riic.h | 4 +
2 files changed, 251 insertions(+), 43 deletions(-)
diff --git a/drivers/i2c/busses/i2c-riic.c b/drivers/i2c/busses/i2c-riic.c
index 1e6639c..93e114c 100644
--- a/drivers/i2c/busses/i2c-riic.c
+++ b/drivers/i2c/busses/i2c-riic.c
@@ -31,6 +31,9 @@
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/clk.h>
+#include <linux/dmaengine.h>
+#include <linux/scatterlist.h>
+#include <linux/dma-mapping.h>
#define RIIC_ICCR1 0x00
#define RIIC_ICCR2 0x01
@@ -166,6 +169,14 @@ struct riic_data {
void __iomem *reg;
struct i2c_adapter adap;
struct i2c_msg *msg;
+ int index;
+ unsigned char icsr2;
+
+ /* for DMAENGINE */
+ struct dma_chan *chan_tx;
+ struct dma_chan *chan_rx;
+ int dma_callbacked;
+ wait_queue_head_t wait;
};
#define DRIVER_VERSION "2011-09-21"
@@ -250,7 +261,8 @@ static int riic_init_setting(struct riic_data *pd, int clock)
return ret;
riic_set_bit(pd, ICCR1_ICE, RIIC_ICCR1); /* Enable RIIC */
- riic_set_bit(pd, ICMR3_RDRFS | ICMR3_WAIT | ICMR3_ACKWP, RIIC_ICMR3);
+ riic_set_bit(pd, ICMR3_WAIT | ICMR3_ACKWP, RIIC_ICMR3);
+ riic_set_bit(pd, ICIER_TIE | ICIER_RIE, RIIC_ICIER);
return 0;
}
@@ -312,10 +324,79 @@ static int riic_send_slave_address(struct riic_data *pd, int read)
return 0;
}
+static void riic_dma_complete(void *arg)
+{
+ struct riic_data *pd = arg;
+
+ pd->dma_callbacked = 1;
+ wake_up(&pd->wait);
+}
+
+static int riic_master_transmit_pio(struct riic_data *pd)
+{
+ int index;
+ int ret = 0;
+
+ index = 0;
+ do {
+ ret = riic_wait_for_icsr2(pd, ICSR2_TDRE);
+ if (ret < 0)
+ return ret;
+
+ riic_write(pd, pd->msg->buf[index], RIIC_ICDRT);
+ index++;
+ } while (pd->msg->len > index);
+
+ return ret;
+}
+
+static int riic_master_transmit_dma(struct riic_data *pd)
+{
+ struct scatterlist sg;
+ unsigned char *buf = pd->msg->buf;
+ struct dma_async_tx_descriptor *desc;
+ int ret;
+
+ sg_init_table(&sg, 1);
+ sg_set_buf(&sg, buf, pd->msg->len);
+ sg_dma_len(&sg) = pd->msg->len;
+ dma_map_sg(pd->chan_tx->device->dev, &sg, 1, DMA_TO_DEVICE);
+
+ desc = pd->chan_tx->device->device_prep_slave_sg(pd->chan_tx,
+ &sg, 1, DMA_TO_DEVICE,
+ DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+ if (!desc)
+ return -EIO;
+
+ desc->callback = riic_dma_complete;
+ desc->callback_param = pd;
+ pd->dma_callbacked = 0;
+ ret = riic_wait_for_icsr2(pd, ICSR2_TDRE);
+ if (ret < 0)
+ return ret;
+ dmaengine_submit(desc);
+ dma_async_issue_pending(pd->chan_tx);
+
+ pd->icsr2 = riic_read(pd, RIIC_ICSR2);
+ riic_set_bit(pd, ICIER_NAKIE, RIIC_ICIER);
+ ret = wait_event_timeout(pd->wait, pd->dma_callbacked ||
+ pd->icsr2 & ICSR2_NACKF, HZ);
+ riic_clear_bit(pd, ICIER_NAKIE, RIIC_ICIER);
+ if (pd->icsr2 & ICSR2_NACKF) {
+ dmaengine_terminate_all(pd->chan_tx);
+ return -EIO;
+ }
+ if (!ret && !pd->dma_callbacked) {
+ dmaengine_terminate_all(pd->chan_tx);
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
static int riic_master_transmit(struct riic_data *pd, int stop)
{
int ret = 0;
- int index;
riic_set_bit(pd, ICCR2_ST, RIIC_ICCR2);
ret = riic_wait_for_icsr2(pd, ICSR2_START);
@@ -328,15 +409,12 @@ static int riic_master_transmit(struct riic_data *pd, int stop)
goto force_exit;
/* transmit data */
- index = 0;
- do {
- ret = riic_wait_for_icsr2(pd, ICSR2_TDRE);
- if (ret < 0)
- goto force_exit;
-
- riic_write(pd, pd->msg->buf[index], RIIC_ICDRT);
- index++;
- } while (pd->msg->len > index);
+ if (pd->chan_tx && pd->msg->len > 1)
+ ret = riic_master_transmit_dma(pd);
+ else
+ ret = riic_master_transmit_pio(pd);
+ if (ret < 0)
+ goto force_exit;
ret = riic_wait_for_icsr2(pd, ICSR2_TEND);
if (ret < 0)
@@ -361,12 +439,72 @@ static void riic_set_receive_ack(struct riic_data *pd, int ack)
riic_set_bit(pd, ICMR3_ACKBT, RIIC_ICMR3);
}
+static int riic_master_receive_pio(struct riic_data *pd)
+{
+ int ret;
+
+ pd->index = 0;
+
+ while ((pd->msg->len - 1) > pd->index) {
+ ret = riic_wait_for_icsr2(pd, ICSR2_RDRF);
+ if (ret < 0)
+ return ret;
+
+ if ((pd->index + 1) >= (pd->msg->len - 1))
+ break;
+
+ pd->msg->buf[pd->index] = riic_read(pd, RIIC_ICDRR);
+ pd->index++;
+ }
+
+ return 0;
+}
+
+static int riic_master_receive_dma(struct riic_data *pd)
+{
+ struct scatterlist sg;
+ unsigned char *buf = pd->msg->buf;
+ struct dma_async_tx_descriptor *desc;
+ int ret;
+ int len = pd->msg->len - 2;
+
+ pd->index = 0;
+
+ sg_init_table(&sg, 1);
+ sg_set_buf(&sg, buf, len);
+ sg_dma_len(&sg) = len;
+ dma_map_sg(pd->chan_rx->device->dev, &sg, 1, DMA_FROM_DEVICE);
+
+ desc = pd->chan_rx->device->device_prep_slave_sg(pd->chan_rx,
+ &sg, 1, DMA_FROM_DEVICE,
+ DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+ if (!desc)
+ return -EIO;
+
+ desc->callback = riic_dma_complete;
+ desc->callback_param = pd;
+ pd->dma_callbacked = 0;
+ ret = riic_wait_for_icsr2(pd, ICSR2_RDRF);
+ if (ret < 0)
+ return ret;
+ dmaengine_submit(desc);
+ dma_async_issue_pending(pd->chan_rx);
+
+ ret = wait_event_timeout(pd->wait, pd->dma_callbacked, HZ);
+ if (!ret && !pd->dma_callbacked) {
+ dmaengine_terminate_all(pd->chan_rx);
+ return -ETIMEDOUT;
+ }
+
+ pd->index = len;
+ return 0;
+}
+
static int riic_master_receive(struct riic_data *pd, int restart)
{
- int dummy_read = 1;
int ret = 0;
- int index;
+ riic_set_receive_ack(pd, 1);
if (restart)
riic_set_bit(pd, ICCR2_RS, RIIC_ICCR2);
else
@@ -394,40 +532,26 @@ static int riic_master_receive(struct riic_data *pd, int restart)
goto force_exit;
}
- /* receive data */
- index = 0;
- while ((pd->msg->len - 1) > index) {
- ret = riic_wait_for_icsr2(pd, ICSR2_RDRF);
- if (ret < 0)
- return ret;
-
- if ((index + 1) >= (pd->msg->len - 1))
- break;
+ /* step 4 */
+ riic_read(pd, RIIC_ICDRR); /* dummy read */
- if (dummy_read) {
- riic_read(pd, RIIC_ICDRR);
- dummy_read = 0;
- } else {
- pd->msg->buf[index] = riic_read(pd, RIIC_ICDRR);
- index++;
- riic_set_receive_ack(pd, 1);
- }
- }
+ /* receive data */
+ if (pd->chan_rx && pd->msg->len > 2)
+ ret = riic_master_receive_dma(pd);
+ else
+ ret = riic_master_receive_pio(pd);
+ if (ret < 0)
+ return ret;
/* step 6 */
ret = riic_wait_for_icsr2(pd, ICSR2_RDRF);
if (ret < 0)
return ret;
+ riic_set_receive_ack(pd, 0);
/* step 7 */
- if (dummy_read) {
- riic_read(pd, RIIC_ICDRR);
- dummy_read = 0;
- } else {
- pd->msg->buf[index] = riic_read(pd, RIIC_ICDRR);
- index++;
- }
- riic_set_receive_ack(pd, 1);
+ pd->msg->buf[pd->index] = riic_read(pd, RIIC_ICDRR);
+ pd->index++;
ret = riic_wait_for_icsr2(pd, ICSR2_RDRF);
if (ret < 0)
@@ -436,11 +560,11 @@ static int riic_master_receive(struct riic_data *pd, int restart)
riic_clear_bit(pd, ICSR2_STOP, RIIC_ICSR2);
riic_set_bit(pd, ICCR2_SP, RIIC_ICCR2);
- pd->msg->buf[index] = riic_read(pd, RIIC_ICDRR);
- index++;
- riic_set_receive_ack(pd, 0);
+ pd->msg->buf[pd->index] = riic_read(pd, RIIC_ICDRR);
+ pd->index++;
force_exit:
+ /* step 8 */
ret = riic_wait_for_icsr2(pd, ICSR2_STOP);
if (ret < 0)
return ret;
@@ -484,14 +608,74 @@ static struct i2c_algorithm riic_algorithm = {
.master_xfer = riic_xfer,
};
+static irqreturn_t riic_irq(int irq, void *data)
+{
+ struct riic_data *pd = data;
+ irqreturn_t ret = IRQ_NONE;
+
+ pd->icsr2 = riic_read(pd, RIIC_ICSR2);
+
+ if (pd->icsr2 & ICSR2_NACKF) {
+ ret = IRQ_HANDLED;
+ riic_clear_bit(pd, ICIER_NAKIE, RIIC_ICIER);
+ wake_up(&pd->wait);
+ }
+
+ return ret;
+}
+
+static bool riic_filter(struct dma_chan *chan, void *filter_param)
+{
+ chan->private = filter_param;
+
+ return true;
+}
+
+static void riic_request_dma(struct riic_data *pd,
+ struct riic_platform_data *riic_data)
+{
+ dma_cap_mask_t mask;
+
+ if (riic_data->dma_tx.slave_id) {
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_SLAVE, mask);
+ pd->chan_tx = dma_request_channel(mask, riic_filter,
+ &riic_data->dma_tx);
+ if (!pd->chan_tx)
+ dev_warn(pd->dev, "dma_request_channel for tx failed."
+ " Then, use PIO.\n");
+ }
+ if (riic_data->dma_rx.slave_id) {
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_SLAVE, mask);
+ pd->chan_rx = dma_request_channel(mask, riic_filter,
+ &riic_data->dma_rx);
+ if (!pd->chan_rx)
+ dev_warn(pd->dev, "dma_request_channel for rx failed."
+ " Then, use PIO.\n");
+ }
+}
+
+static void riic_release_dma(struct riic_data *pd)
+{
+ if (pd->chan_tx)
+ dma_release_channel(pd->chan_tx);
+ if (pd->chan_rx)
+ dma_release_channel(pd->chan_rx);
+}
+
static int __devexit riic_remove(struct platform_device *pdev)
{
struct riic_data *pd = platform_get_drvdata(pdev);
+ int irq = platform_get_irq(pdev, 0);
if (!pd)
return 0;
i2c_del_adapter(&pd->adap);
+ if (irq >= 0)
+ free_irq(irq, pd);
+ riic_release_dma(pd);
iounmap(pd->reg);
clk_disable(pd->clk);
clk_put(pd->clk);
@@ -509,6 +693,7 @@ static int __devinit riic_probe(struct platform_device *pdev)
void __iomem *reg = NULL;
int ret = 0;
char clk_name[16];
+ int irq;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
@@ -516,6 +701,7 @@ static int __devinit riic_probe(struct platform_device *pdev)
dev_err(&pdev->dev, "platform_get_resource failed.\n");
goto clean_up;
}
+ irq = platform_get_irq(pdev, 0);
if (!pdev->dev.platform_data) {
ret = -ENOENT;
@@ -562,27 +748,45 @@ static int __devinit riic_probe(struct platform_device *pdev)
}
clk_enable(pd->clk);
+ riic_request_dma(pd, riic_data);
+ init_waitqueue_head(&pd->wait);
ret = riic_init_setting(pd, riic_data->clock);
if (ret < 0) {
dev_err(&pdev->dev, "riic_init_setting failed.\n");
goto clean_up;
}
+ if (irq >= 0) {
+ /* interruption of EEI for DMA */
+ ret = request_irq(irq, riic_irq, 0, dev_name(&pdev->dev), pd);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "request_irq error\n");
+ goto clean_up;
+ }
+ } else if (pd->chan_tx || pd->chan_rx) {
+ dev_err(&pdev->dev, "Interrupt resource needed.\n");
+ goto clean_up;
+ }
+
ret = i2c_add_numbered_adapter(adap);
if (ret < 0) {
dev_err(&pdev->dev, "i2c_add_numbered_adapter failed.\n");
- goto clean_up;
+ goto clean_up2;
}
dev_info(&pdev->dev, "version %s\n", DRIVER_VERSION);
return ret;
+clean_up2:
+ if (irq >= 0)
+ free_irq(irq, pd);
clean_up:
if (pd) {
if (!IS_ERR(pd->clk)) {
clk_disable(pd->clk);
clk_put(pd->clk);
}
+ riic_release_dma(pd);
}
if (reg)
iounmap(reg);
diff --git a/include/linux/i2c/riic.h b/include/linux/i2c/riic.h
index 5839381..f97b65c 100644
--- a/include/linux/i2c/riic.h
+++ b/include/linux/i2c/riic.h
@@ -21,8 +21,12 @@
#ifndef _RIIC_H_
#define _RIIC_H_
+#include <linux/sh_dma.h>
+
struct riic_platform_data {
int clock; /* i2c clock (kHZ) */
+ struct sh_dmae_slave dma_tx;
+ struct sh_dmae_slave dma_rx;
};
#endif
--
1.7.1
^ permalink raw reply related
* [PATCH 1/2 v3] i2c: i2c-riic: add support for Renesas RIIC
From: Yoshihiro Shimoda @ 2011-09-26 8:41 UTC (permalink / raw)
To: ben-linux; +Cc: linux-i2c, SH-Linux
This driver supports the RIIC module. The SH7757 has it.
The driver doesn't use any IRQ handler. This is because the module
has errata in an interrupt. So, we cannot use the interrupt for
transfer.
Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
---
about v3
- add clk framework
- adjust for the latest kernel (fix compile error)
drivers/i2c/busses/Kconfig | 9 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-riic.c | 620 +++++++++++++++++++++++++++++++++++++++++
include/linux/i2c/riic.h | 29 ++
4 files changed, 659 insertions(+), 0 deletions(-)
create mode 100644 drivers/i2c/busses/i2c-riic.c
create mode 100644 include/linux/i2c/riic.h
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 646068e..e057344 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -687,6 +687,15 @@ config I2C_EG20T
ML7213/ML7223 is companion chip for Intel Atom E6xx series.
ML7213/ML7223 is completely compatible for Intel EG20T PCH.
+config I2C_RIIC
+ tristate "Renesas RIIC controller"
+ depends on SUPERH
+ help
+ This driver supports the RIIC module of the Renesas SH7757.
+
+ This driver can also be built as a module. If so, the module
+ will be called i2c-riic.
+
comment "External I2C/SMBus adapter drivers"
config I2C_DIOLAN_U2C
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index e6cf294..93ca34f 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -55,6 +55,7 @@ obj-$(CONFIG_I2C_PNX) += i2c-pnx.o
obj-$(CONFIG_I2C_PUV3) += i2c-puv3.o
obj-$(CONFIG_I2C_PXA) += i2c-pxa.o
obj-$(CONFIG_I2C_PXA_PCI) += i2c-pxa-pci.o
+obj-$(CONFIG_I2C_RIIC) += i2c-riic.o
obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o
obj-$(CONFIG_I2C_S6000) += i2c-s6000.o
obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o
diff --git a/drivers/i2c/busses/i2c-riic.c b/drivers/i2c/busses/i2c-riic.c
new file mode 100644
index 0000000..1e6639c
--- /dev/null
+++ b/drivers/i2c/busses/i2c-riic.c
@@ -0,0 +1,620 @@
+/*
+ * RIIC bus driver
+ *
+ * Copyright (C) 2011 Renesas Solutions Corp.
+ *
+ * Based on i2c-sh_mobile.c
+ * Portion Copyright (C) 2008 Magnus Damm
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/i2c.h>
+#include <linux/i2c/riic.h>
+#include <linux/io.h>
+#include <linux/timer.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+
+#define RIIC_ICCR1 0x00
+#define RIIC_ICCR2 0x01
+#define RIIC_ICMR1 0x02
+#define RIIC_ICMR2 0x03
+#define RIIC_ICMR3 0x04
+#define RIIC_ICFER 0x05
+#define RIIC_ICSER 0x06
+#define RIIC_ICIER 0x07
+#define RIIC_ICSR1 0x08
+#define RIIC_ICSR2 0x09
+#define RIIC_SARL0 0x0a
+#define RIIC_SARU0 0x0b
+#define RIIC_SARL1 0x0c
+#define RIIC_SARU1 0x0d
+#define RIIC_SARL2 0x0e
+#define RIIC_SARU2 0x0f
+#define RIIC_ICBRL 0x10
+#define RIIC_ICBRH 0x11
+#define RIIC_ICDRT 0x12
+#define RIIC_ICDRR 0x13
+
+/* ICCR1 */
+#define ICCR1_ICE 0x80
+#define ICCR1_IICRST 0x40
+#define ICCR1_CLO 0x20
+#define ICCR1_SOWP 0x10
+#define ICCR1_SCLO 0x08
+#define ICCR1_SDAO 0x04
+#define ICCR1_SCLI 0x02
+#define ICCR1_SDAI 0x01
+
+/* ICCR2 */
+#define ICCR2_BBSY 0x80
+#define ICCR2_MST 0x40
+#define ICCR2_TRS 0x20
+#define ICCR2_SP 0x08
+#define ICCR2_RS 0x04
+#define ICCR2_ST 0x02
+
+/* ICMR1 */
+#define ICMR1_MTWP 0x80
+#define ICMR1_CKS_MASK 0x70
+#define ICMR1_BCWP 0x08
+#define ICMR1_BC_MASK 0x07
+
+#define ICMR1_CKS(_x) ((_x << 4) & ICMR1_CKS_MASK)
+#define ICMR1_BC(_x) ((_x) & ICMR1_BC_MASK)
+
+/* ICMR2 */
+#define ICMR2_DLCS 0x80
+#define ICMR2_SDDL_MASK 0x70
+#define ICMR2_TMOH 0x04
+#define ICMR2_TMOL 0x02
+#define ICMR2_TMOS 0x01
+
+/* ICMR3 */
+#define ICMR3_SMBS 0x80
+#define ICMR3_WAIT 0x40
+#define ICMR3_RDRFS 0x20
+#define ICMR3_ACKWP 0x10
+#define ICMR3_ACKBT 0x08
+#define ICMR3_ACKBR 0x04
+#define ICMR3_NF_MASK 0x03
+
+/* ICFER */
+#define ICFER_FMPE 0x80
+#define ICFER_SCLE 0x40
+#define ICFER_NFE 0x20
+#define ICFER_NACKE 0x10
+#define ICFER_SALE 0x08
+#define ICFER_NALE 0x04
+#define ICFER_MALE 0x02
+#define ICFER_TMOE 0x01
+
+/* ICSER */
+#define ICSER_HOAE 0x80
+#define ICSER_DIDE 0x20
+#define ICSER_GCAE 0x08
+#define ICSER_SAR2E 0x04
+#define ICSER_SAR1E 0x02
+#define ICSER_SAR0E 0x01
+
+/* ICIER */
+#define ICIER_TIE 0x80
+#define ICIER_TEIE 0x40
+#define ICIER_RIE 0x20
+#define ICIER_NAKIE 0x10
+#define ICIER_SPIE 0x08
+#define ICIER_STIE 0x04
+#define ICIER_ALIE 0x02
+#define ICIER_TMOIE 0x01
+
+/* ICSR1 */
+#define ICSR1_HOA 0x80
+#define ICSR1_DID 0x20
+#define ICSR1_GCA 0x08
+#define ICSR1_AAS2 0x04
+#define ICSR1_AAS1 0x02
+#define ICSR1_AAS0 0x01
+
+/* ICSR2 */
+#define ICSR2_TDRE 0x80
+#define ICSR2_TEND 0x40
+#define ICSR2_RDRF 0x20
+#define ICSR2_NACKF 0x10
+#define ICSR2_STOP 0x08
+#define ICSR2_START 0x04
+#define ICSR2_AL 0x02
+#define ICSR2_TMOF 0x01
+
+/* SARLn */
+#define SARL_SVA_MASK 0xfe /* SVA[7:1] */
+#define SARL_SVA 0x01
+
+/* SARUn */
+#define SARU_SVA_MASK 0x06 /* SVA[9:8] */
+#define SARU_FS 0x01
+
+/* ICBRH */
+#define ICBRH_RESERVED 0xe0 /* The write value shoud always be 1 */
+#define ICBRH_BRH_MASK 0x1f
+
+/* ICBRL */
+#define ICBRL_RESERVED 0xe0 /* The write value shoud always be 1 */
+#define ICBRL_BRL_MASK 0x1f
+
+#define RIIC_TIMEOUT 10000 /* 100msec (unit = 10usec) */
+
+struct riic_data {
+ struct device *dev;
+ struct clk *clk;
+ void __iomem *reg;
+ struct i2c_adapter adap;
+ struct i2c_msg *msg;
+};
+
+#define DRIVER_VERSION "2011-09-21"
+
+static unsigned char riic_read(struct riic_data *pd, unsigned long addr)
+{
+ return ioread8(pd->reg + addr);
+}
+
+static void riic_write(struct riic_data *pd, unsigned char data,
+ unsigned long addr)
+{
+ iowrite8(data, pd->reg + addr);
+}
+
+static void riic_set_bit(struct riic_data *pd, unsigned char val,
+ unsigned long offset)
+{
+ unsigned char tmp;
+
+ tmp = riic_read(pd, offset) | val;
+ riic_write(pd, tmp, offset);
+}
+
+static void riic_clear_bit(struct riic_data *pd, unsigned char val,
+ unsigned long offset)
+{
+ unsigned char tmp;
+
+ tmp = riic_read(pd, offset) & ~val;
+ riic_write(pd, tmp, offset);
+}
+
+static int riic_set_clock(struct riic_data *pd, int clock)
+{
+ switch (clock) {
+ case 100:
+ riic_clear_bit(pd, ICFER_FMPE, RIIC_ICFER);
+ riic_clear_bit(pd, ICMR1_CKS_MASK, RIIC_ICMR1);
+ riic_set_bit(pd, ICMR1_CKS(3), RIIC_ICMR1);
+ riic_write(pd, ICBRH_RESERVED | 23, RIIC_ICBRH);
+ riic_write(pd, ICBRL_RESERVED | 23, RIIC_ICBRL);
+ break;
+ case 400:
+ riic_clear_bit(pd, ICFER_FMPE, RIIC_ICFER);
+ riic_clear_bit(pd, ICMR1_CKS_MASK, RIIC_ICMR1);
+ riic_set_bit(pd, ICMR1_CKS(1), RIIC_ICMR1);
+ riic_write(pd, ICBRH_RESERVED | 20, RIIC_ICBRH);
+ riic_write(pd, ICBRL_RESERVED | 19, RIIC_ICBRL);
+ break;
+ case 1000:
+ riic_set_bit(pd, ICFER_FMPE, RIIC_ICFER);
+ riic_clear_bit(pd, ICMR1_CKS_MASK, RIIC_ICMR1);
+ riic_set_bit(pd, ICMR1_CKS(0), RIIC_ICMR1);
+ riic_write(pd, ICBRH_RESERVED | 14, RIIC_ICBRH);
+ riic_write(pd, ICBRL_RESERVED | 14, RIIC_ICBRL);
+ break;
+
+ default:
+ dev_err(pd->dev, "unsupported clock (%dkHz)\n", clock);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int riic_init_setting(struct riic_data *pd, int clock)
+{
+ int ret;
+
+ riic_clear_bit(pd, ICCR1_ICE, RIIC_ICCR1);
+ riic_set_bit(pd, ICCR1_IICRST, RIIC_ICCR1);
+ riic_clear_bit(pd, ICCR1_IICRST, RIIC_ICCR1);
+
+ riic_write(pd, 0, RIIC_SARL0);
+ riic_write(pd, 0, RIIC_SARU0);
+ riic_write(pd, ICSER_SAR0E, RIIC_ICSER);
+
+ riic_write(pd, ICMR1_BC(7), RIIC_ICMR1);
+ ret = riic_set_clock(pd, clock);
+ if (ret < 0)
+ return ret;
+
+ riic_set_bit(pd, ICCR1_ICE, RIIC_ICCR1); /* Enable RIIC */
+ riic_set_bit(pd, ICMR3_RDRFS | ICMR3_WAIT | ICMR3_ACKWP, RIIC_ICMR3);
+
+ return 0;
+}
+
+static int riic_check_busy(struct riic_data *pd)
+{
+ if (riic_read(pd, RIIC_ICCR2) & ICCR2_BBSY) {
+ dev_err(pd->dev, "i2c bus is busy.\n");
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+static int riic_wait_for_icsr2(struct riic_data *pd, unsigned short bit)
+{
+ unsigned char icsr2;
+ int timeout = RIIC_TIMEOUT;
+
+ while (timeout-- > 0) {
+ icsr2 = riic_read(pd, RIIC_ICSR2);
+ if (icsr2 & ICSR2_NACKF)
+ return -EIO;
+ if (icsr2 & bit)
+ return 0;
+ udelay(10);
+ }
+
+ dev_err(pd->dev, "%s: bit = %x icsr2 = %x, iccr2 = %x\n", __func__,
+ bit, riic_read(pd, RIIC_ICSR2), riic_read(pd, RIIC_ICCR2));
+
+ return -ETIMEDOUT;
+}
+
+static int riic_send_slave_address(struct riic_data *pd, int read)
+{
+ unsigned char sa_rw[2];
+ int ret;
+
+ if (pd->msg->flags & I2C_M_TEN) {
+ sa_rw[0] = ((((pd->msg->addr & 0x300) >> 8) | 0x78) << 1);
+ sa_rw[0] |= read;
+ sa_rw[1] = pd->msg->addr & 0xff;
+ ret = riic_wait_for_icsr2(pd, ICSR2_TDRE);
+ if (ret < 0)
+ return ret;
+ riic_write(pd, sa_rw[0], RIIC_ICDRT);
+ ret = riic_wait_for_icsr2(pd, ICSR2_TDRE);
+ if (ret < 0)
+ return ret;
+ riic_write(pd, sa_rw[1], RIIC_ICDRT);
+ } else {
+ sa_rw[0] = (pd->msg->addr << 1) | read;
+ ret = riic_wait_for_icsr2(pd, ICSR2_TDRE);
+ if (ret < 0)
+ return ret;
+ riic_write(pd, sa_rw[0], RIIC_ICDRT);
+ }
+ return 0;
+}
+
+static int riic_master_transmit(struct riic_data *pd, int stop)
+{
+ int ret = 0;
+ int index;
+
+ riic_set_bit(pd, ICCR2_ST, RIIC_ICCR2);
+ ret = riic_wait_for_icsr2(pd, ICSR2_START);
+ if (ret < 0)
+ goto force_exit;
+ riic_clear_bit(pd, ICSR2_START, RIIC_ICSR2);
+
+ ret = riic_send_slave_address(pd, 0);
+ if (ret < 0)
+ goto force_exit;
+
+ /* transmit data */
+ index = 0;
+ do {
+ ret = riic_wait_for_icsr2(pd, ICSR2_TDRE);
+ if (ret < 0)
+ goto force_exit;
+
+ riic_write(pd, pd->msg->buf[index], RIIC_ICDRT);
+ index++;
+ } while (pd->msg->len > index);
+
+ ret = riic_wait_for_icsr2(pd, ICSR2_TEND);
+ if (ret < 0)
+ goto force_exit;
+
+force_exit:
+ if (stop) {
+ riic_clear_bit(pd, ICSR2_STOP | ICSR2_NACKF, RIIC_ICSR2);
+ riic_set_bit(pd, ICCR2_SP, RIIC_ICCR2);
+ riic_wait_for_icsr2(pd, ICSR2_STOP);
+ }
+ riic_clear_bit(pd, ICSR2_STOP | ICSR2_NACKF, RIIC_ICSR2);
+
+ return ret;
+}
+
+static void riic_set_receive_ack(struct riic_data *pd, int ack)
+{
+ if (ack)
+ riic_clear_bit(pd, ICMR3_ACKBT, RIIC_ICMR3);
+ else
+ riic_set_bit(pd, ICMR3_ACKBT, RIIC_ICMR3);
+}
+
+static int riic_master_receive(struct riic_data *pd, int restart)
+{
+ int dummy_read = 1;
+ int ret = 0;
+ int index;
+
+ if (restart)
+ riic_set_bit(pd, ICCR2_RS, RIIC_ICCR2);
+ else
+ riic_set_bit(pd, ICCR2_ST, RIIC_ICCR2);
+
+ ret = riic_wait_for_icsr2(pd, ICSR2_START);
+ if (ret < 0)
+ return ret;
+ riic_clear_bit(pd, ICSR2_START, RIIC_ICSR2);
+
+ /* send slave address */
+ ret = riic_send_slave_address(pd, 1);
+ if (ret < 0)
+ return ret;
+
+ ret = riic_wait_for_icsr2(pd, ICSR2_RDRF);
+ if (ret < 0)
+ return ret;
+
+ if (riic_read(pd, RIIC_ICSR2) & ICSR2_NACKF) {
+ /* received NACK */
+ riic_clear_bit(pd, ICSR2_STOP, RIIC_ICSR2);
+ riic_set_bit(pd, ICCR2_SP, RIIC_ICCR2);
+ riic_read(pd, RIIC_ICDRR); /* dummy read */
+ goto force_exit;
+ }
+
+ /* receive data */
+ index = 0;
+ while ((pd->msg->len - 1) > index) {
+ ret = riic_wait_for_icsr2(pd, ICSR2_RDRF);
+ if (ret < 0)
+ return ret;
+
+ if ((index + 1) >= (pd->msg->len - 1))
+ break;
+
+ if (dummy_read) {
+ riic_read(pd, RIIC_ICDRR);
+ dummy_read = 0;
+ } else {
+ pd->msg->buf[index] = riic_read(pd, RIIC_ICDRR);
+ index++;
+ riic_set_receive_ack(pd, 1);
+ }
+ }
+
+ /* step 6 */
+ ret = riic_wait_for_icsr2(pd, ICSR2_RDRF);
+ if (ret < 0)
+ return ret;
+
+ /* step 7 */
+ if (dummy_read) {
+ riic_read(pd, RIIC_ICDRR);
+ dummy_read = 0;
+ } else {
+ pd->msg->buf[index] = riic_read(pd, RIIC_ICDRR);
+ index++;
+ }
+ riic_set_receive_ack(pd, 1);
+
+ ret = riic_wait_for_icsr2(pd, ICSR2_RDRF);
+ if (ret < 0)
+ return ret;
+
+ riic_clear_bit(pd, ICSR2_STOP, RIIC_ICSR2);
+ riic_set_bit(pd, ICCR2_SP, RIIC_ICCR2);
+
+ pd->msg->buf[index] = riic_read(pd, RIIC_ICDRR);
+ index++;
+ riic_set_receive_ack(pd, 0);
+
+force_exit:
+ ret = riic_wait_for_icsr2(pd, ICSR2_STOP);
+ if (ret < 0)
+ return ret;
+
+ riic_clear_bit(pd, ICSR2_STOP | ICSR2_NACKF, RIIC_ICSR2);
+
+ return ret;
+}
+
+static int riic_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs,
+ int num)
+{
+ struct riic_data *pd = i2c_get_adapdata(adapter);
+ int i, ret = 0;
+ int restart = num > 1 ? 1 : 0;
+ int stop;
+
+ if (riic_check_busy(pd))
+ return -EBUSY;
+
+ for (i = 0; (i < num) && !ret; i++) {
+ pd->msg = &msgs[i];
+ stop = (i = num - 1);
+
+ if (pd->msg->flags & I2C_M_RD)
+ ret = riic_master_receive(pd, restart);
+ else
+ ret = riic_master_transmit(pd, stop);
+ }
+
+ return !ret ? num : ret;
+}
+
+static u32 riic_func(struct i2c_adapter *adapter)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_10BIT_ADDR | I2C_FUNC_SMBUS_EMUL;
+}
+
+static struct i2c_algorithm riic_algorithm = {
+ .functionality = riic_func,
+ .master_xfer = riic_xfer,
+};
+
+static int __devexit riic_remove(struct platform_device *pdev)
+{
+ struct riic_data *pd = platform_get_drvdata(pdev);
+
+ if (!pd)
+ return 0;
+
+ i2c_del_adapter(&pd->adap);
+ iounmap(pd->reg);
+ clk_disable(pd->clk);
+ clk_put(pd->clk);
+ kfree(pd);
+
+ return 0;
+}
+
+static int __devinit riic_probe(struct platform_device *pdev)
+{
+ struct resource *res = NULL;
+ struct riic_data *pd = NULL;
+ struct riic_platform_data *riic_data = NULL;
+ struct i2c_adapter *adap;
+ void __iomem *reg = NULL;
+ int ret = 0;
+ char clk_name[16];
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ ret = -ENOENT;
+ dev_err(&pdev->dev, "platform_get_resource failed.\n");
+ goto clean_up;
+ }
+
+ if (!pdev->dev.platform_data) {
+ ret = -ENOENT;
+ dev_err(&pdev->dev, "no platform data\n");
+ goto clean_up;
+ }
+ riic_data = pdev->dev.platform_data;
+
+ reg = ioremap(res->start, resource_size(res));
+ if (reg = NULL) {
+ ret = -ENXIO;
+ dev_err(&pdev->dev, "ioremap failed.\n");
+ goto clean_up;
+ }
+
+ pd = kzalloc(sizeof(struct riic_data), GFP_KERNEL);
+ if (pd = NULL) {
+ ret = -ENOMEM;
+ dev_err(&pdev->dev, "kzalloc failed.\n");
+ goto clean_up;
+ }
+
+ pd->dev = &pdev->dev;
+ pd->reg = reg;
+ platform_set_drvdata(pdev, pd);
+
+ adap = &pd->adap;
+ i2c_set_adapdata(adap, pd);
+
+ adap->owner = THIS_MODULE;
+ adap->algo = &riic_algorithm;
+ adap->dev.parent = &pdev->dev;
+ adap->retries = 5;
+ adap->nr = pdev->id;
+
+ strlcpy(adap->name, dev_name(&pdev->dev), sizeof(adap->name));
+
+ snprintf(clk_name, sizeof(clk_name), "riic%d", pdev->id);
+ pd->clk = clk_get(&pdev->dev, clk_name);
+ if (IS_ERR(pd->clk)) {
+ dev_err(&pdev->dev, "clk_get failed.\n");
+ ret = PTR_ERR(pd->clk);
+ goto clean_up;
+ }
+ clk_enable(pd->clk);
+
+ ret = riic_init_setting(pd, riic_data->clock);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "riic_init_setting failed.\n");
+ goto clean_up;
+ }
+
+ ret = i2c_add_numbered_adapter(adap);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "i2c_add_numbered_adapter failed.\n");
+ goto clean_up;
+ }
+
+ dev_info(&pdev->dev, "version %s\n", DRIVER_VERSION);
+ return ret;
+
+clean_up:
+ if (pd) {
+ if (!IS_ERR(pd->clk)) {
+ clk_disable(pd->clk);
+ clk_put(pd->clk);
+ }
+ }
+ if (reg)
+ iounmap(reg);
+ kfree(pd);
+ platform_set_drvdata(pdev, NULL);
+
+ return ret;
+}
+
+static struct platform_driver riic_driver = {
+ .probe = riic_probe,
+ .remove = __devexit_p(riic_remove),
+ .driver = {
+ .name = "i2c-riic",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init riic_init(void)
+{
+ return platform_driver_register(&riic_driver);
+}
+module_init(riic_init);
+
+static void __exit riic_cleanup(void)
+{
+ platform_driver_unregister(&riic_driver);
+}
+module_exit(riic_cleanup);
+
+MODULE_DESCRIPTION("Renesas RIIC Driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Yoshihiro Shimoda");
+MODULE_ALIAS("platform:i2c-riic");
+
diff --git a/include/linux/i2c/riic.h b/include/linux/i2c/riic.h
new file mode 100644
index 0000000..5839381
--- /dev/null
+++ b/include/linux/i2c/riic.h
@@ -0,0 +1,29 @@
+/*
+ * RIIC bus driver
+ *
+ * Copyright (C) 2011 Renesas Solutions Corp.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef _RIIC_H_
+#define _RIIC_H_
+
+struct riic_platform_data {
+ int clock; /* i2c clock (kHZ) */
+};
+
+#endif
+
--
1.7.1
^ permalink raw reply related
* [PATCH] sh: add parameters for EHCI and RIIC in clock-sh7757.c
From: Yoshihiro Shimoda @ 2011-09-26 8:41 UTC (permalink / raw)
To: linux-sh
Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
---
arch/sh/kernel/cpu/sh4a/clock-sh7757.c | 10 +++++++++-
1 files changed, 9 insertions(+), 1 deletions(-)
diff --git a/arch/sh/kernel/cpu/sh4a/clock-sh7757.c b/arch/sh/kernel/cpu/sh4a/clock-sh7757.c
index 3b097b0..19222da 100644
--- a/arch/sh/kernel/cpu/sh4a/clock-sh7757.c
+++ b/arch/sh/kernel/cpu/sh4a/clock-sh7757.c
@@ -113,7 +113,14 @@ static struct clk_lookup lookups[] = {
/* MSTP32 clocks */
CLKDEV_CON_ID("sdhi0", &mstp_clks[MSTP004]),
- CLKDEV_CON_ID("riic", &mstp_clks[MSTP000]),
+ CLKDEV_CON_ID("riic0", &mstp_clks[MSTP000]),
+ CLKDEV_CON_ID("riic1", &mstp_clks[MSTP000]),
+ CLKDEV_CON_ID("riic2", &mstp_clks[MSTP000]),
+ CLKDEV_CON_ID("riic3", &mstp_clks[MSTP000]),
+ CLKDEV_CON_ID("riic4", &mstp_clks[MSTP000]),
+ CLKDEV_CON_ID("riic5", &mstp_clks[MSTP000]),
+ CLKDEV_CON_ID("riic6", &mstp_clks[MSTP000]),
+ CLKDEV_CON_ID("riic7", &mstp_clks[MSTP000]),
CLKDEV_ICK_ID("tmu_fck", "sh_tmu.0", &mstp_clks[MSTP113]),
CLKDEV_ICK_ID("tmu_fck", "sh_tmu.1", &mstp_clks[MSTP114]),
@@ -121,6 +128,7 @@ static struct clk_lookup lookups[] = {
CLKDEV_ICK_ID("sci_fck", "sh-sci.1", &mstp_clks[MSTP111]),
CLKDEV_ICK_ID("sci_fck", "sh-sci.0", &mstp_clks[MSTP110]),
+ CLKDEV_CON_ID("usb_fck", &mstp_clks[MSTP103]),
CLKDEV_CON_ID("usb0", &mstp_clks[MSTP102]),
CLKDEV_CON_ID("mmc0", &mstp_clks[MSTP220]),
};
--
1.7.1
^ permalink raw reply related
* Re: [BUG] on sh7372 (mackerel) with today's Linus' (github) master
From: Russell King - ARM Linux @ 2011-09-26 8:39 UTC (permalink / raw)
To: linux-sh
In-Reply-To: <Pine.LNX.4.64.1109231012490.25166@axis700.grange>
On Sat, Sep 24, 2011 at 03:07:07PM +0200, Rafael J. Wysocki wrote:
> On Saturday, September 24, 2011, Russell King - ARM Linux wrote:
> > On Sat, Sep 24, 2011 at 12:58:37AM +0200, Rafael J. Wysocki wrote:
> > > Well, since nobody seems to execute anything that may sleep under
> > > clocks_mutex, perhaps we can do something like this:
> >
> > No - a spinlock prevents preemption, a mutex does not.
> >
> > It's runtime PM which is doing stuff wrong here. You're not supposed to
> > give up the clk struct with it enabled.
>
> OK, so below is a fix confined to clock_ops.c.
Looks fine from a read-through the patch, thanks.
Acked-by: Russell King <rmk+kernel@arm.linux.org.uk>
^ permalink raw reply
* Re: [PATCH 00/03] ARM: mach-shmobile: sh7372 CPU sleep patches
From: Rafael J. Wysocki @ 2011-09-25 21:29 UTC (permalink / raw)
To: linux-sh
In-Reply-To: <20110630094653.10442.68817.sendpatchset@t400s>
On Friday, September 23, 2011, Magnus Damm wrote:
> ARM: mach-shmobile: sh7372 CPU sleep patches
>
> Subject: [PATCH 01/03] ARM: mach-shmobile: sh7372 generic suspend/resume support
> Subject: [PATCH 02/03] ARM: mach-shmobile: sh7372 A3SM support
> Subject: [PATCH 03/03] ARM: mach-shmobile: sh7372 sleep warning fixes
>
> These patches update the sh7372 sleep code to make use
> of the generic ARM suspend/resume code and to add A3SM
> sleep mode support for Suspend-to-RAM. The first two
> patches have been posted earlier, and the last contain
> a few warning fixes.
>
> Fits the pm-test branch of the linux-pm git repository.
>
> Signed-off-by: Magnus Damm <damm@opensource.se>
All patches applied to linux-pm/pm-domains (and merged into pm-test
and linux-next).
Thanks,
Rafael
> ---
>
> arch/arm/mach-shmobile/include/mach/common.h | 6
> arch/arm/mach-shmobile/pm-sh7372.c | 252 ++++++++++++++++++++--
> arch/arm/mach-shmobile/sleep-sh7372.S | 287 +++++---------------------
> 3 files changed, 286 insertions(+), 259 deletions(-)
>
>
^ permalink raw reply
* Re: [PATCH] dma: shdma: transfer based runtime PM
From: Guennadi Liakhovetski @ 2011-09-25 14:19 UTC (permalink / raw)
To: Vinod Koul; +Cc: linux-kernel, linux-sh, Dan Williams, Paul Mundt
In-Reply-To: <1315235596.26251.201.camel@vkoul-udesk3>
Hi Vinod
On Mon, 5 Sep 2011, Vinod Koul wrote:
[snip]
> This patch fails to apply for me, can you please rebase it to me tree
> and resend, I will apply it later this week
Sorry, it didn't apply, because I apparently forgot to send out this
patch:
http://marc.info/?l=linux-kernel&m\x131695995404173&w=2
which I've also cc'ed to you a couple of minutes ago. With that patch
applied first, the patch in question
http://marc.info/?l=linux-kernel&m\x131386369726846&w=2
still applies cleanly on top of the "next" branch of your slave-dma tree.
Thanks
Guennadi
---
Guennadi Liakhovetski, Ph.D.
Freelance Open-Source Software Developer
http://www.open-technology.de/
^ permalink raw reply
* [PATCH] dmaengine: shdma: protect against the IRQ handler
From: Guennadi Liakhovetski @ 2011-09-25 14:12 UTC (permalink / raw)
To: linux-sh; +Cc: Dan Williams, Vinod Koul, linux-kernel
The IRQ handler of the shdma driver accesses common hardware registers,
that are also accessed from other contexts. Therefore access to them
has to be performed with interrupts disabled, not only with disabled
bottom halves.
Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
---
drivers/dma/shdma.c | 55 ++++++++++++++++++++++++++-------------------------
1 files changed, 28 insertions(+), 27 deletions(-)
diff --git a/drivers/dma/shdma.c b/drivers/dma/shdma.c
index 7f49235..e7bb747 100644
--- a/drivers/dma/shdma.c
+++ b/drivers/dma/shdma.c
@@ -265,8 +265,9 @@ static dma_cookie_t sh_dmae_tx_submit(struct dma_async_tx_descriptor *tx)
struct sh_dmae_chan *sh_chan = to_sh_chan(tx->chan);
dma_async_tx_callback callback = tx->callback;
dma_cookie_t cookie;
+ unsigned long flags;
- spin_lock_bh(&sh_chan->desc_lock);
+ spin_lock_irqsave(&sh_chan->desc_lock, flags);
cookie = sh_chan->common.cookie;
cookie++;
@@ -302,7 +303,7 @@ static dma_cookie_t sh_dmae_tx_submit(struct dma_async_tx_descriptor *tx)
tx->cookie, &last->async_tx, sh_chan->id,
desc->hw.sar, desc->hw.tcr, desc->hw.dar);
- spin_unlock_bh(&sh_chan->desc_lock);
+ spin_unlock_irqrestore(&sh_chan->desc_lock, flags);
return cookie;
}
@@ -374,24 +375,18 @@ static int sh_dmae_alloc_chan_resources(struct dma_chan *chan)
dmae_init(sh_chan);
}
- spin_lock_bh(&sh_chan->desc_lock);
while (sh_chan->descs_allocated < NR_DESCS_PER_CHANNEL) {
- spin_unlock_bh(&sh_chan->desc_lock);
desc = kzalloc(sizeof(struct sh_desc), GFP_KERNEL);
- if (!desc) {
- spin_lock_bh(&sh_chan->desc_lock);
+ if (!desc)
break;
- }
dma_async_tx_descriptor_init(&desc->async_tx,
&sh_chan->common);
desc->async_tx.tx_submit = sh_dmae_tx_submit;
desc->mark = DESC_IDLE;
- spin_lock_bh(&sh_chan->desc_lock);
list_add(&desc->node, &sh_chan->ld_free);
sh_chan->descs_allocated++;
}
- spin_unlock_bh(&sh_chan->desc_lock);
if (!sh_chan->descs_allocated) {
ret = -ENOMEM;
@@ -405,6 +400,7 @@ edescalloc:
clear_bit(param->slave_id, sh_dmae_slave_used);
etestused:
efindslave:
+ chan->private = NULL;
pm_runtime_put(sh_chan->dev);
return ret;
}
@@ -437,12 +433,12 @@ static void sh_dmae_free_chan_resources(struct dma_chan *chan)
chan->private = NULL;
}
- spin_lock_bh(&sh_chan->desc_lock);
+ spin_lock_irq(&sh_chan->desc_lock);
list_splice_init(&sh_chan->ld_free, &list);
sh_chan->descs_allocated = 0;
- spin_unlock_bh(&sh_chan->desc_lock);
+ spin_unlock_irq(&sh_chan->desc_lock);
if (descs > 0)
pm_runtime_put(sh_chan->dev);
@@ -534,6 +530,7 @@ static struct dma_async_tx_descriptor *sh_dmae_prep_sg(struct sh_dmae_chan *sh_c
struct sh_desc *first = NULL, *new = NULL /* compiler... */;
LIST_HEAD(tx_list);
int chunks = 0;
+ unsigned long irq_flags;
int i;
if (!sg_len)
@@ -544,7 +541,7 @@ static struct dma_async_tx_descriptor *sh_dmae_prep_sg(struct sh_dmae_chan *sh_c
(SH_DMA_TCR_MAX + 1);
/* Have to lock the whole loop to protect against concurrent release */
- spin_lock_bh(&sh_chan->desc_lock);
+ spin_lock_irqsave(&sh_chan->desc_lock, irq_flags);
/*
* Chaining:
@@ -590,7 +587,7 @@ static struct dma_async_tx_descriptor *sh_dmae_prep_sg(struct sh_dmae_chan *sh_c
/* Put them back on the free list, so, they don't get lost */
list_splice_tail(&tx_list, &sh_chan->ld_free);
- spin_unlock_bh(&sh_chan->desc_lock);
+ spin_unlock_irqrestore(&sh_chan->desc_lock, irq_flags);
return &first->async_tx;
@@ -599,7 +596,7 @@ err_get_desc:
new->mark = DESC_IDLE;
list_splice(&tx_list, &sh_chan->ld_free);
- spin_unlock_bh(&sh_chan->desc_lock);
+ spin_unlock_irqrestore(&sh_chan->desc_lock, irq_flags);
return NULL;
}
@@ -661,6 +658,7 @@ static int sh_dmae_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd,
unsigned long arg)
{
struct sh_dmae_chan *sh_chan = to_sh_chan(chan);
+ unsigned long flags;
/* Only supports DMA_TERMINATE_ALL */
if (cmd != DMA_TERMINATE_ALL)
@@ -669,7 +667,7 @@ static int sh_dmae_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd,
if (!chan)
return -EINVAL;
- spin_lock_bh(&sh_chan->desc_lock);
+ spin_lock_irqsave(&sh_chan->desc_lock, flags);
dmae_halt(sh_chan);
if (!list_empty(&sh_chan->ld_queue)) {
@@ -680,7 +678,7 @@ static int sh_dmae_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd,
sh_chan->xmit_shift;
}
- spin_unlock_bh(&sh_chan->desc_lock);
+ spin_unlock_irqrestore(&sh_chan->desc_lock, flags);
sh_dmae_chan_ld_cleanup(sh_chan, true);
@@ -695,8 +693,9 @@ static dma_async_tx_callback __ld_cleanup(struct sh_dmae_chan *sh_chan, bool all
dma_cookie_t cookie = 0;
dma_async_tx_callback callback = NULL;
void *param = NULL;
+ unsigned long flags;
- spin_lock_bh(&sh_chan->desc_lock);
+ spin_lock_irqsave(&sh_chan->desc_lock, flags);
list_for_each_entry_safe(desc, _desc, &sh_chan->ld_queue, node) {
struct dma_async_tx_descriptor *tx = &desc->async_tx;
@@ -773,7 +772,7 @@ static dma_async_tx_callback __ld_cleanup(struct sh_dmae_chan *sh_chan, bool all
*/
sh_chan->completed_cookie = sh_chan->common.cookie;
- spin_unlock_bh(&sh_chan->desc_lock);
+ spin_unlock_irqrestore(&sh_chan->desc_lock, flags);
if (callback)
callback(param);
@@ -796,10 +795,12 @@ static void sh_chan_xfer_ld_queue(struct sh_dmae_chan *sh_chan)
{
struct sh_desc *desc;
- spin_lock_bh(&sh_chan->desc_lock);
+ spin_lock_irq(&sh_chan->desc_lock);
/* DMA work check */
- if (dmae_is_busy(sh_chan))
- goto sh_chan_xfer_ld_queue_end;
+ if (dmae_is_busy(sh_chan)) {
+ spin_unlock_irq(&sh_chan->desc_lock);
+ return;
+ }
/* Find the first not transferred descriptor */
list_for_each_entry(desc, &sh_chan->ld_queue, node)
@@ -813,8 +814,7 @@ static void sh_chan_xfer_ld_queue(struct sh_dmae_chan *sh_chan)
break;
}
-sh_chan_xfer_ld_queue_end:
- spin_unlock_bh(&sh_chan->desc_lock);
+ spin_unlock_irq(&sh_chan->desc_lock);
}
static void sh_dmae_memcpy_issue_pending(struct dma_chan *chan)
@@ -831,6 +831,7 @@ static enum dma_status sh_dmae_tx_status(struct dma_chan *chan,
dma_cookie_t last_used;
dma_cookie_t last_complete;
enum dma_status status;
+ unsigned long flags;
sh_dmae_chan_ld_cleanup(sh_chan, false);
@@ -841,7 +842,7 @@ static enum dma_status sh_dmae_tx_status(struct dma_chan *chan,
BUG_ON(last_complete < 0);
dma_set_tx_state(txstate, last_complete, last_used, 0);
- spin_lock_bh(&sh_chan->desc_lock);
+ spin_lock_irqsave(&sh_chan->desc_lock, flags);
status = dma_async_is_complete(cookie, last_complete, last_used);
@@ -859,7 +860,7 @@ static enum dma_status sh_dmae_tx_status(struct dma_chan *chan,
}
}
- spin_unlock_bh(&sh_chan->desc_lock);
+ spin_unlock_irqrestore(&sh_chan->desc_lock, flags);
return status;
}
@@ -952,7 +953,7 @@ static void dmae_do_tasklet(unsigned long data)
u32 sar_buf = sh_dmae_readl(sh_chan, SAR);
u32 dar_buf = sh_dmae_readl(sh_chan, DAR);
- spin_lock(&sh_chan->desc_lock);
+ spin_lock_irq(&sh_chan->desc_lock);
list_for_each_entry(desc, &sh_chan->ld_queue, node) {
if (desc->mark = DESC_SUBMITTED &&
((desc->direction = DMA_FROM_DEVICE &&
@@ -965,7 +966,7 @@ static void dmae_do_tasklet(unsigned long data)
break;
}
}
- spin_unlock(&sh_chan->desc_lock);
+ spin_unlock_irq(&sh_chan->desc_lock);
/* Next desc */
sh_chan_xfer_ld_queue(sh_chan);
--
1.7.2.5
^ permalink raw reply related
* Re: [PATCH 2/3] PM / Runtime: Don't run callbacks under lock for
From: Ming Lei @ 2011-09-25 8:24 UTC (permalink / raw)
To: Rafael J. Wysocki
Cc: Linux PM mailing list, LKML, Linux-sh list, Magnus Damm,
Kevin Hilman, jean.pihet
In-Reply-To: <201109242325.53563.rjw@sisk.pl>
On Sun, Sep 25, 2011 at 5:25 AM, Rafael J. Wysocki <rjw@sisk.pl> wrote:
> From: Rafael J. Wysocki <rjw@sisk.pl>
>
> The rpm_suspend() and rpm_resume() routines execute subsystem or PM
> domain callbacks under power.lock if power.irq_safe is set for the
> given device. This is inconsistent with that rpm_idle() does after
> commit 02b2677 (PM / Runtime: Allow _put_sync() from
> interrupts-disabled context) and is problematic for subsystems and PM
> domains wanting to use power.lock for synchronization in their
> runtime PM callbacks.
>
> Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
Reviewed-by: Ming Lei <tom.leiming@gmail.com>
> ---
> drivers/base/power/runtime.c | 68 +++++++++++++++++++++++++++++--------------
> 1 file changed, 46 insertions(+), 22 deletions(-)
>
> Index: linux/drivers/base/power/runtime.c
> =================================> --- linux.orig/drivers/base/power/runtime.c
> +++ linux/drivers/base/power/runtime.c
> @@ -155,6 +155,31 @@ static int rpm_check_suspend_allowed(str
> }
>
> /**
> + * __rpm_callback - Run a given runtime PM callback for a given device.
> + * @cb: Runtime PM callback to run.
> + * @dev: Device to run the callback for.
> + */
> +static int __rpm_callback(int (*cb)(struct device *), struct device *dev)
> + __releases(&dev->power.lock) __acquires(&dev->power.lock)
> +{
> + int retval;
> +
> + if (dev->power.irq_safe)
> + spin_unlock(&dev->power.lock);
> + else
> + spin_unlock_irq(&dev->power.lock);
> +
> + retval = cb(dev);
> +
> + if (dev->power.irq_safe)
> + spin_lock(&dev->power.lock);
> + else
> + spin_lock_irq(&dev->power.lock);
> +
> + return retval;
> +}
> +
> +/**
> * rpm_idle - Notify device bus type if the device can be suspended.
> * @dev: Device to notify the bus type about.
> * @rpmflags: Flag bits.
> @@ -225,19 +250,8 @@ static int rpm_idle(struct device *dev,
> else
> callback = NULL;
>
> - if (callback) {
> - if (dev->power.irq_safe)
> - spin_unlock(&dev->power.lock);
> - else
> - spin_unlock_irq(&dev->power.lock);
> -
> - callback(dev);
> -
> - if (dev->power.irq_safe)
> - spin_lock(&dev->power.lock);
> - else
> - spin_lock_irq(&dev->power.lock);
> - }
> + if (callback)
> + __rpm_callback(callback, dev);
>
> dev->power.idle_notification = false;
> wake_up_all(&dev->power.wait_queue);
> @@ -252,22 +266,14 @@ static int rpm_idle(struct device *dev,
> * @dev: Device to run the callback for.
> */
> static int rpm_callback(int (*cb)(struct device *), struct device *dev)
> - __releases(&dev->power.lock) __acquires(&dev->power.lock)
> {
> int retval;
>
> if (!cb)
> return -ENOSYS;
>
> - if (dev->power.irq_safe) {
> - retval = cb(dev);
> - } else {
> - spin_unlock_irq(&dev->power.lock);
> -
> - retval = cb(dev);
> + retval = __rpm_callback(cb, dev);
>
> - spin_lock_irq(&dev->power.lock);
> - }
> dev->power.runtime_error = retval;
> return retval != -EACCES ? retval : -EIO;
> }
> @@ -347,6 +353,15 @@ static int rpm_suspend(struct device *de
> goto out;
> }
>
> + if (dev->power.irq_safe) {
> + spin_unlock(&dev->power.lock);
> +
> + cpu_relax();
> +
> + spin_lock(&dev->power.lock);
> + goto repeat;
> + }
> +
> /* Wait for the other suspend running in parallel with us. */
> for (;;) {
> prepare_to_wait(&dev->power.wait_queue, &wait,
> @@ -496,6 +511,15 @@ static int rpm_resume(struct device *dev
> goto out;
> }
>
> + if (dev->power.irq_safe) {
> + spin_unlock(&dev->power.lock);
> +
> + cpu_relax();
> +
> + spin_lock(&dev->power.lock);
> + goto repeat;
> + }
> +
> /* Wait for the operation carried out in parallel with us. */
> for (;;) {
> prepare_to_wait(&dev->power.wait_queue, &wait,
>
>
thanks,
--
Ming Lei
^ permalink raw reply
* [PATCH 3/3] PM / QoS: Add function dev_pm_qos_read_value() (v2)
From: Rafael J. Wysocki @ 2011-09-24 21:26 UTC (permalink / raw)
To: Linux PM mailing list
Cc: LKML, Linux-sh list, Magnus Damm, Kevin Hilman, jean.pihet,
Ming Lei
In-Reply-To: <201109242323.03474.rjw@sisk.pl>
From: Rafael J. Wysocki <rjw@sisk.pl>
To read the current PM QoS value for a given device we need to
make sure that the device's power.constraints object won't be
removed while we're doing that. For this reason, put the
operation under dev->power.lock and acquire the lock
around the initialization and removal of power.constraints.
Moreover, since we're using the value of power.constraints to
determine whether or not the object is present, the
power.constraints_state field isn't necessary any more and may be
removed. However, dev_pm_qos_add_request() needs to check if the
device is being removed from the system before allocating a new
PM QoS constraints object for it, so it has to use device_pm_lock()
and the device PM QoS initialization and destruction should be done
under device_pm_lock() as well.
Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
drivers/base/power/main.c | 4 -
drivers/base/power/qos.c | 168 ++++++++++++++++++++++++++--------------------
include/linux/pm.h | 8 --
include/linux/pm_qos.h | 3
4 files changed, 103 insertions(+), 80 deletions(-)
Index: linux/drivers/base/power/qos.c
=================================--- linux.orig/drivers/base/power/qos.c
+++ linux/drivers/base/power/qos.c
@@ -30,15 +30,6 @@
* . To minimize the data usage by the per-device constraints, the data struct
* is only allocated at the first call to dev_pm_qos_add_request.
* . The data is later free'd when the device is removed from the system.
- * . The constraints_state variable from dev_pm_info tracks the data struct
- * allocation state:
- * DEV_PM_QOS_NO_DEVICE: No device present or device removed, no data
- * allocated,
- * DEV_PM_QOS_DEVICE_PRESENT: Device present, data not allocated and will be
- * allocated at the first call to dev_pm_qos_add_request,
- * DEV_PM_QOS_ALLOCATED: Device present, data allocated. The per-device
- * PM QoS constraints framework is operational and constraints can be
- * added, updated or removed using the dev_pm_qos_* API.
* . A global mutex protects the constraints users from the data being
* allocated and free'd.
*/
@@ -51,8 +42,30 @@
static DEFINE_MUTEX(dev_pm_qos_mtx);
+
static BLOCKING_NOTIFIER_HEAD(dev_pm_notifiers);
+/**
+ * dev_pm_qos_read_value - Get PM QoS constraint for a given device.
+ * @dev: Device to get the PM QoS constraint value for.
+ */
+s32 dev_pm_qos_read_value(struct device *dev)
+{
+ struct pm_qos_constraints *c;
+ unsigned long flags;
+ s32 ret = 0;
+
+ spin_lock_irqsave(&dev->power.lock, flags);
+
+ c = dev->power.constraints;
+ if (c)
+ ret = pm_qos_read_value(c);
+
+ spin_unlock_irqrestore(&dev->power.lock, flags);
+
+ return ret;
+}
+
/*
* apply_constraint
* @req: constraint request to apply
@@ -105,27 +118,37 @@ static int dev_pm_qos_constraints_alloca
}
BLOCKING_INIT_NOTIFIER_HEAD(n);
+ plist_head_init(&c->list);
+ c->target_value = PM_QOS_DEV_LAT_DEFAULT_VALUE;
+ c->default_value = PM_QOS_DEV_LAT_DEFAULT_VALUE;
+ c->type = PM_QOS_MIN;
+ c->notifiers = n;
+
+ spin_lock_irq(&dev->power.lock);
dev->power.constraints = c;
- plist_head_init(&dev->power.constraints->list);
- dev->power.constraints->target_value = PM_QOS_DEV_LAT_DEFAULT_VALUE;
- dev->power.constraints->default_value = PM_QOS_DEV_LAT_DEFAULT_VALUE;
- dev->power.constraints->type = PM_QOS_MIN;
- dev->power.constraints->notifiers = n;
- dev->power.constraints_state = DEV_PM_QOS_ALLOCATED;
+ spin_unlock_irq(&dev->power.lock);
return 0;
}
+static void __dev_pm_qos_constraints_init(struct device *dev)
+{
+ spin_lock_irq(&dev->power.lock);
+ dev->power.constraints = NULL;
+ spin_unlock_irq(&dev->power.lock);
+}
+
/**
- * dev_pm_qos_constraints_init
+ * dev_pm_qos_constraints_init - Initalize device's PM QoS constraints pointer.
* @dev: target device
*
- * Called from the device PM subsystem at device insertion
+ * Called from the device PM subsystem at device insertion under
+ * device_pm_lock().
*/
void dev_pm_qos_constraints_init(struct device *dev)
{
mutex_lock(&dev_pm_qos_mtx);
- dev->power.constraints_state = DEV_PM_QOS_DEVICE_PRESENT;
+ dev->power.constraints = NULL;
mutex_unlock(&dev_pm_qos_mtx);
}
@@ -133,34 +156,35 @@ void dev_pm_qos_constraints_init(struct
* dev_pm_qos_constraints_destroy
* @dev: target device
*
- * Called from the device PM subsystem at device removal
+ * Called from the device PM subsystem at device removal under device_pm_lock().
*/
void dev_pm_qos_constraints_destroy(struct device *dev)
{
struct dev_pm_qos_request *req, *tmp;
+ struct pm_qos_constraints *c;
mutex_lock(&dev_pm_qos_mtx);
- if (dev->power.constraints_state = DEV_PM_QOS_ALLOCATED) {
- /* Flush the constraints list for the device */
- plist_for_each_entry_safe(req, tmp,
- &dev->power.constraints->list,
- node) {
- /*
- * Update constraints list and call the notification
- * callbacks if needed
- */
- apply_constraint(req, PM_QOS_REMOVE_REQ,
- PM_QOS_DEFAULT_VALUE);
- memset(req, 0, sizeof(*req));
- }
+ c = dev->power.constraints;
+ if (!c)
+ goto out;
- kfree(dev->power.constraints->notifiers);
- kfree(dev->power.constraints);
- dev->power.constraints = NULL;
+ /* Flush the constraints list for the device */
+ plist_for_each_entry_safe(req, tmp, &c->list, node) {
+ /*
+ * Update constraints list and call the notification
+ * callbacks if needed
+ */
+ apply_constraint(req, PM_QOS_REMOVE_REQ, PM_QOS_DEFAULT_VALUE);
+ memset(req, 0, sizeof(*req));
}
- dev->power.constraints_state = DEV_PM_QOS_NO_DEVICE;
+ __dev_pm_qos_constraints_init(dev);
+
+ kfree(c->notifiers);
+ kfree(c);
+
+ out:
mutex_unlock(&dev_pm_qos_mtx);
}
@@ -178,8 +202,9 @@ void dev_pm_qos_constraints_destroy(stru
*
* Returns 1 if the aggregated constraint value has changed,
* 0 if the aggregated constraint value has not changed,
- * -EINVAL in case of wrong parameters, -ENODEV if the device has been
- * removed from the system
+ * -EINVAL in case of wrong parameters, -ENOMEM if there's not enough memory
+ * to allocate for data structures, -ENODEV if the device has just been removed
+ * from the system.
*/
int dev_pm_qos_add_request(struct device *dev, struct dev_pm_qos_request *req,
s32 value)
@@ -195,28 +220,37 @@ int dev_pm_qos_add_request(struct device
return -EINVAL;
}
- mutex_lock(&dev_pm_qos_mtx);
req->dev = dev;
- /* Return if the device has been removed */
- if (req->dev->power.constraints_state = DEV_PM_QOS_NO_DEVICE) {
- ret = -ENODEV;
- goto out;
- }
+ device_pm_lock();
+ mutex_lock(&dev_pm_qos_mtx);
- /*
- * Allocate the constraints data on the first call to add_request,
- * i.e. only if the data is not already allocated and if the device has
- * not been removed
- */
- if (dev->power.constraints_state = DEV_PM_QOS_DEVICE_PRESENT)
- ret = dev_pm_qos_constraints_allocate(dev);
+ if (dev->power.constraints) {
+ device_pm_unlock();
+ } else {
+ if (list_empty(&dev->power.entry)) {
+ /* The device has been removed from the system. */
+ device_pm_unlock();
+ req->dev = NULL;
+ ret = -ENODEV;
+ goto out;
+ } else {
+ device_pm_unlock();
+ /*
+ * Allocate the constraints data on the first call to
+ * add_request, i.e. only if the data is not already
+ * allocated and if the device has not been removed.
+ */
+ ret = dev_pm_qos_constraints_allocate(dev);
+ }
+ }
if (!ret)
ret = apply_constraint(req, PM_QOS_ADD_REQ, value);
-out:
+ out:
mutex_unlock(&dev_pm_qos_mtx);
+
return ret;
}
EXPORT_SYMBOL_GPL(dev_pm_qos_add_request);
@@ -252,7 +286,7 @@ int dev_pm_qos_update_request(struct dev
mutex_lock(&dev_pm_qos_mtx);
- if (req->dev->power.constraints_state = DEV_PM_QOS_ALLOCATED) {
+ if (req->dev->power.constraints) {
if (new_value != req->node.prio)
ret = apply_constraint(req, PM_QOS_UPDATE_REQ,
new_value);
@@ -293,7 +327,7 @@ int dev_pm_qos_remove_request(struct dev
mutex_lock(&dev_pm_qos_mtx);
- if (req->dev->power.constraints_state = DEV_PM_QOS_ALLOCATED) {
+ if (req->dev->power.constraints) {
ret = apply_constraint(req, PM_QOS_REMOVE_REQ,
PM_QOS_DEFAULT_VALUE);
memset(req, 0, sizeof(*req));
@@ -323,15 +357,12 @@ int dev_pm_qos_add_notifier(struct devic
mutex_lock(&dev_pm_qos_mtx);
- /* Silently return if the device has been removed */
- if (dev->power.constraints_state != DEV_PM_QOS_ALLOCATED)
- goto out;
-
- retval = blocking_notifier_chain_register(
- dev->power.constraints->notifiers,
- notifier);
+ /* Silently return if the constraints object is not present. */
+ if (dev->power.constraints)
+ retval = blocking_notifier_chain_register(
+ dev->power.constraints->notifiers,
+ notifier);
-out:
mutex_unlock(&dev_pm_qos_mtx);
return retval;
}
@@ -354,15 +385,12 @@ int dev_pm_qos_remove_notifier(struct de
mutex_lock(&dev_pm_qos_mtx);
- /* Silently return if the device has been removed */
- if (dev->power.constraints_state != DEV_PM_QOS_ALLOCATED)
- goto out;
-
- retval = blocking_notifier_chain_unregister(
- dev->power.constraints->notifiers,
- notifier);
+ /* Silently return if the constraints object is not present. */
+ if (dev->power.constraints)
+ retval = blocking_notifier_chain_unregister(
+ dev->power.constraints->notifiers,
+ notifier);
-out:
mutex_unlock(&dev_pm_qos_mtx);
return retval;
}
Index: linux/include/linux/pm_qos.h
=================================--- linux.orig/include/linux/pm_qos.h
+++ linux/include/linux/pm_qos.h
@@ -77,6 +77,7 @@ int pm_qos_remove_notifier(int pm_qos_cl
int pm_qos_request_active(struct pm_qos_request *req);
s32 pm_qos_read_value(struct pm_qos_constraints *c);
+s32 dev_pm_qos_read_value(struct device *dev);
int dev_pm_qos_add_request(struct device *dev, struct dev_pm_qos_request *req,
s32 value);
int dev_pm_qos_update_request(struct dev_pm_qos_request *req, s32 new_value);
@@ -117,6 +118,8 @@ static inline int pm_qos_request_active(
static inline s32 pm_qos_read_value(struct pm_qos_constraints *c)
{ return 0; }
+static inline s32 dev_pm_qos_read_value(struct device *dev)
+ { return 0; }
static inline int dev_pm_qos_add_request(struct device *dev,
struct dev_pm_qos_request *req,
s32 value)
Index: linux/drivers/base/power/main.c
=================================--- linux.orig/drivers/base/power/main.c
+++ linux/drivers/base/power/main.c
@@ -98,8 +98,8 @@ void device_pm_add(struct device *dev)
dev_warn(dev, "parent %s should not be sleeping\n",
dev_name(dev->parent));
list_add_tail(&dev->power.entry, &dpm_list);
- mutex_unlock(&dpm_list_mtx);
dev_pm_qos_constraints_init(dev);
+ mutex_unlock(&dpm_list_mtx);
}
/**
@@ -110,9 +110,9 @@ void device_pm_remove(struct device *dev
{
pr_debug("PM: Removing info for %s:%s\n",
dev->bus ? dev->bus->name : "No Bus", dev_name(dev));
- dev_pm_qos_constraints_destroy(dev);
complete_all(&dev->power.completion);
mutex_lock(&dpm_list_mtx);
+ dev_pm_qos_constraints_destroy(dev);
list_del_init(&dev->power.entry);
mutex_unlock(&dpm_list_mtx);
device_wakeup_disable(dev);
Index: linux/include/linux/pm.h
=================================--- linux.orig/include/linux/pm.h
+++ linux/include/linux/pm.h
@@ -421,13 +421,6 @@ enum rpm_request {
RPM_REQ_RESUME,
};
-/* Per-device PM QoS constraints data struct state */
-enum dev_pm_qos_state {
- DEV_PM_QOS_NO_DEVICE, /* No device present */
- DEV_PM_QOS_DEVICE_PRESENT, /* Device present, data not allocated */
- DEV_PM_QOS_ALLOCATED, /* Device present, data allocated */
-};
-
struct wakeup_source;
struct pm_domain_data {
@@ -489,7 +482,6 @@ struct dev_pm_info {
#endif
struct pm_subsys_data *subsys_data; /* Owned by the subsystem. */
struct pm_qos_constraints *constraints;
- enum dev_pm_qos_state constraints_state;
};
extern void update_pm_runtime_accounting(struct device *dev);
^ permalink raw reply
* [PATCH 2/3] PM / Runtime: Don't run callbacks under lock for power.irq_safe set
From: Rafael J. Wysocki @ 2011-09-24 21:25 UTC (permalink / raw)
To: Linux PM mailing list
Cc: LKML, Linux-sh list, Magnus Damm, Kevin Hilman, jean.pihet,
Ming Lei
In-Reply-To: <201109242323.03474.rjw@sisk.pl>
From: Rafael J. Wysocki <rjw@sisk.pl>
The rpm_suspend() and rpm_resume() routines execute subsystem or PM
domain callbacks under power.lock if power.irq_safe is set for the
given device. This is inconsistent with that rpm_idle() does after
commit 02b2677 (PM / Runtime: Allow _put_sync() from
interrupts-disabled context) and is problematic for subsystems and PM
domains wanting to use power.lock for synchronization in their
runtime PM callbacks.
Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
drivers/base/power/runtime.c | 68 +++++++++++++++++++++++++++++--------------
1 file changed, 46 insertions(+), 22 deletions(-)
Index: linux/drivers/base/power/runtime.c
=================================--- linux.orig/drivers/base/power/runtime.c
+++ linux/drivers/base/power/runtime.c
@@ -155,6 +155,31 @@ static int rpm_check_suspend_allowed(str
}
/**
+ * __rpm_callback - Run a given runtime PM callback for a given device.
+ * @cb: Runtime PM callback to run.
+ * @dev: Device to run the callback for.
+ */
+static int __rpm_callback(int (*cb)(struct device *), struct device *dev)
+ __releases(&dev->power.lock) __acquires(&dev->power.lock)
+{
+ int retval;
+
+ if (dev->power.irq_safe)
+ spin_unlock(&dev->power.lock);
+ else
+ spin_unlock_irq(&dev->power.lock);
+
+ retval = cb(dev);
+
+ if (dev->power.irq_safe)
+ spin_lock(&dev->power.lock);
+ else
+ spin_lock_irq(&dev->power.lock);
+
+ return retval;
+}
+
+/**
* rpm_idle - Notify device bus type if the device can be suspended.
* @dev: Device to notify the bus type about.
* @rpmflags: Flag bits.
@@ -225,19 +250,8 @@ static int rpm_idle(struct device *dev,
else
callback = NULL;
- if (callback) {
- if (dev->power.irq_safe)
- spin_unlock(&dev->power.lock);
- else
- spin_unlock_irq(&dev->power.lock);
-
- callback(dev);
-
- if (dev->power.irq_safe)
- spin_lock(&dev->power.lock);
- else
- spin_lock_irq(&dev->power.lock);
- }
+ if (callback)
+ __rpm_callback(callback, dev);
dev->power.idle_notification = false;
wake_up_all(&dev->power.wait_queue);
@@ -252,22 +266,14 @@ static int rpm_idle(struct device *dev,
* @dev: Device to run the callback for.
*/
static int rpm_callback(int (*cb)(struct device *), struct device *dev)
- __releases(&dev->power.lock) __acquires(&dev->power.lock)
{
int retval;
if (!cb)
return -ENOSYS;
- if (dev->power.irq_safe) {
- retval = cb(dev);
- } else {
- spin_unlock_irq(&dev->power.lock);
-
- retval = cb(dev);
+ retval = __rpm_callback(cb, dev);
- spin_lock_irq(&dev->power.lock);
- }
dev->power.runtime_error = retval;
return retval != -EACCES ? retval : -EIO;
}
@@ -347,6 +353,15 @@ static int rpm_suspend(struct device *de
goto out;
}
+ if (dev->power.irq_safe) {
+ spin_unlock(&dev->power.lock);
+
+ cpu_relax();
+
+ spin_lock(&dev->power.lock);
+ goto repeat;
+ }
+
/* Wait for the other suspend running in parallel with us. */
for (;;) {
prepare_to_wait(&dev->power.wait_queue, &wait,
@@ -496,6 +511,15 @@ static int rpm_resume(struct device *dev
goto out;
}
+ if (dev->power.irq_safe) {
+ spin_unlock(&dev->power.lock);
+
+ cpu_relax();
+
+ spin_lock(&dev->power.lock);
+ goto repeat;
+ }
+
/* Wait for the operation carried out in parallel with us. */
for (;;) {
prepare_to_wait(&dev->power.wait_queue, &wait,
^ permalink raw reply
* [PATCH 1/3] PM / Domains: Split device PM domain data into base and need_restore
From: Rafael J. Wysocki @ 2011-09-24 21:24 UTC (permalink / raw)
To: Linux PM mailing list
Cc: LKML, Linux-sh list, Magnus Damm, Kevin Hilman, jean.pihet,
Ming Lei
In-Reply-To: <201109242323.03474.rjw@sisk.pl>
From: Rafael J. Wysocki <rjw@sisk.pl>
The struct pm_domain_data data type is defined in such a way that
adding new fields specific to the generic PM domains code will
require include/linux/pm.h to be modified. As a result, data types
used only by the generic PM domains code will be defined in two
headers, although they all should be defined in pm_domain.h and
pm.h will need to include more headers, which won't be very nice.
For this reason change the definition of struct pm_subsys_data
so that its domain_data member is a pointer, which will allow
struct pm_domain_data to be subclassed by various PM domains
implementations. Remove the need_restore member from
struct pm_domain_data and make the generic PM domains code
subclass it by adding the need_restore member to the new data type.
Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
drivers/base/power/domain.c | 28 +++++++++++++++++++---------
include/linux/pm.h | 3 +--
include/linux/pm_domain.h | 10 ++++++++++
3 files changed, 30 insertions(+), 11 deletions(-)
Index: linux/include/linux/pm.h
=================================--- linux.orig/include/linux/pm.h
+++ linux/include/linux/pm.h
@@ -433,7 +433,6 @@ struct wakeup_source;
struct pm_domain_data {
struct list_head list_node;
struct device *dev;
- bool need_restore;
};
struct pm_subsys_data {
@@ -443,7 +442,7 @@ struct pm_subsys_data {
struct list_head clock_list;
#endif
#ifdef CONFIG_PM_GENERIC_DOMAINS
- struct pm_domain_data domain_data;
+ struct pm_domain_data *domain_data;
#endif
};
Index: linux/include/linux/pm_domain.h
=================================--- linux.orig/include/linux/pm_domain.h
+++ linux/include/linux/pm_domain.h
@@ -62,6 +62,16 @@ struct gpd_link {
struct list_head slave_node;
};
+struct generic_pm_domain_data {
+ struct pm_domain_data base;
+ bool need_restore;
+};
+
+static inline struct generic_pm_domain_data *to_gpd_data(struct pm_domain_data *pdd)
+{
+ return container_of(pdd, struct generic_pm_domain_data, base);
+}
+
#ifdef CONFIG_PM_GENERIC_DOMAINS
extern int pm_genpd_add_device(struct generic_pm_domain *genpd,
struct device *dev);
Index: linux/drivers/base/power/domain.c
=================================--- linux.orig/drivers/base/power/domain.c
+++ linux/drivers/base/power/domain.c
@@ -188,11 +188,12 @@ static int __pm_genpd_save_device(struct
struct generic_pm_domain *genpd)
__releases(&genpd->lock) __acquires(&genpd->lock)
{
+ struct generic_pm_domain_data *gpd_data = to_gpd_data(pdd);
struct device *dev = pdd->dev;
struct device_driver *drv = dev->driver;
int ret = 0;
- if (pdd->need_restore)
+ if (gpd_data->need_restore)
return 0;
mutex_unlock(&genpd->lock);
@@ -210,7 +211,7 @@ static int __pm_genpd_save_device(struct
mutex_lock(&genpd->lock);
if (!ret)
- pdd->need_restore = true;
+ gpd_data->need_restore = true;
return ret;
}
@@ -224,10 +225,11 @@ static void __pm_genpd_restore_device(st
struct generic_pm_domain *genpd)
__releases(&genpd->lock) __acquires(&genpd->lock)
{
+ struct generic_pm_domain_data *gpd_data = to_gpd_data(pdd);
struct device *dev = pdd->dev;
struct device_driver *drv = dev->driver;
- if (!pdd->need_restore)
+ if (!gpd_data->need_restore)
return;
mutex_unlock(&genpd->lock);
@@ -244,7 +246,7 @@ static void __pm_genpd_restore_device(st
mutex_lock(&genpd->lock);
- pdd->need_restore = false;
+ gpd_data->need_restore = false;
}
/**
@@ -493,7 +495,7 @@ static int pm_genpd_runtime_resume(struc
mutex_lock(&genpd->lock);
}
finish_wait(&genpd->status_wait_queue, &wait);
- __pm_genpd_restore_device(&dev->power.subsys_data->domain_data, genpd);
+ __pm_genpd_restore_device(dev->power.subsys_data->domain_data, genpd);
genpd->resume_count--;
genpd_set_active(genpd);
wake_up_all(&genpd->status_wait_queue);
@@ -1080,6 +1082,7 @@ static void pm_genpd_complete(struct dev
*/
int pm_genpd_add_device(struct generic_pm_domain *genpd, struct device *dev)
{
+ struct generic_pm_domain_data *gpd_data;
struct pm_domain_data *pdd;
int ret = 0;
@@ -1106,14 +1109,20 @@ int pm_genpd_add_device(struct generic_p
goto out;
}
+ gpd_data = kzalloc(sizeof(*gpd_data), GFP_KERNEL);
+ if (!gpd_data) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
genpd->device_count++;
dev->pm_domain = &genpd->domain;
dev_pm_get_subsys_data(dev);
- pdd = &dev->power.subsys_data->domain_data;
- pdd->dev = dev;
- pdd->need_restore = false;
- list_add_tail(&pdd->list_node, &genpd->dev_list);
+ dev->power.subsys_data->domain_data = &gpd_data->base;
+ gpd_data->base.dev = dev;
+ gpd_data->need_restore = false;
+ list_add_tail(&gpd_data->base.list_node, &genpd->dev_list);
out:
genpd_release_lock(genpd);
@@ -1152,6 +1161,7 @@ int pm_genpd_remove_device(struct generi
pdd->dev = NULL;
dev_pm_put_subsys_data(dev);
dev->pm_domain = NULL;
+ kfree(to_gpd_data(pdd));
genpd->device_count--;
^ permalink raw reply
* [PATCH 0/3] PM: Runtime PM and device PM QoS refinements
From: Rafael J. Wysocki @ 2011-09-24 21:23 UTC (permalink / raw)
To: Linux PM mailing list
Cc: LKML, Linux-sh list, Magnus Damm, Kevin Hilman, jean.pihet,
Ming Lei
In-Reply-To: <201108310017.03103.rjw@sisk.pl>
Hi,
On Wednesday, August 31, 2011, Rafael J. Wysocki wrote:
> Hi,
>
> This patchset illustrates how device PM QoS may be used along with
> PM domains in my view.
>
> Actually, it consists of two parts. Namely, patches [1-3/5] seem to be
> suitable for 3.2, unless somebody hates them, but patches [4-5/5] are
> total RFC. They haven't been tested, only compiled, so the use of them
> is not encouraged (they may kill your dog or make your cat go wild, or
> do something equally nasty, so beware). Their purpose is to illustrate
> an idea that I'd like to discuss at the PM miniconference during the
> LPC.
>
> [1/5] - Split device PM domain data into a "base" and additional fields
> (one need_restore field at the moment, but the subsequent patches
> add more fields).
>
> [2/5] - Make runtime PM always release power.lock if power.irq_safe is set.
>
> [3/5] - Add function for reading device PM QoS values safely.
>
> [4/5] - Add governor function for stopping devices.
>
> [5/5] - Add generic PM domain power off governor function.
I have updated patches [1-3] after receiving some feedback and the result
follows. These patches are regarded as 3.2 material, if no one has
objections.
[1/3] - Split device PM domain data into a "base" and additional fields
(one need_restore field at the moment).
[2/3] - Make runtime PM always release power.lock if power.irq_safe is set.
[3/3] - Add function for reading device PM QoS values safely.
Thanks,
Rafael
^ permalink raw reply
* Re: [BUG] on sh7372 (mackerel) with today's Linus' (github) master
From: Rafael J. Wysocki @ 2011-09-24 13:07 UTC (permalink / raw)
To: linux-sh
In-Reply-To: <Pine.LNX.4.64.1109231012490.25166@axis700.grange>
On Saturday, September 24, 2011, Russell King - ARM Linux wrote:
> On Sat, Sep 24, 2011 at 12:58:37AM +0200, Rafael J. Wysocki wrote:
> > Well, since nobody seems to execute anything that may sleep under
> > clocks_mutex, perhaps we can do something like this:
>
> No - a spinlock prevents preemption, a mutex does not.
>
> It's runtime PM which is doing stuff wrong here. You're not supposed to
> give up the clk struct with it enabled.
OK, so below is a fix confined to clock_ops.c.
Thanks,
Rafael
---
From: Rafael J. Wysocki <rjw@sisk.pl>
Subject: PM / Clocks: Do not acquire a mutex under a spinlock
Commit b7ab83e (PM: Use spinlock instead of mutex in clock
management functions) introduced a regression causing clocks_mutex
to be acquired under a spinlock. This happens because
pm_clk_suspend() and pm_clk_resume() call pm_clk_acquire() under
pcd->lock, but pm_clk_acquire() executes clk_get() which causes
clocks_mutex to be acquired. Similarly, __pm_clk_remove(),
executed under pcd->lock, calls clk_put(), which also causes
clocks_mutex to be acquired.
To fix those problems make pm_clk_add() call pm_clk_acquire(), so
that pm_clk_suspend() and pm_clk_resume() don't have to do that.
Change pm_clk_remove() and pm_clk_destroy() to separate
modifications of the pcd->clock_list list from the actual removal of
PM clock entry objects done by __pm_clk_remove().
Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
drivers/base/power/clock_ops.c | 75 ++++++++++++++++++++---------------------
1 file changed, 38 insertions(+), 37 deletions(-)
Index: linux/drivers/base/power/clock_ops.c
=================================--- linux.orig/drivers/base/power/clock_ops.c
+++ linux/drivers/base/power/clock_ops.c
@@ -42,6 +42,22 @@ static struct pm_clk_data *__to_pcd(stru
}
/**
+ * pm_clk_acquire - Acquire a device clock.
+ * @dev: Device whose clock is to be acquired.
+ * @ce: PM clock entry corresponding to the clock.
+ */
+static void pm_clk_acquire(struct device *dev, struct pm_clock_entry *ce)
+{
+ ce->clk = clk_get(dev, ce->con_id);
+ if (IS_ERR(ce->clk)) {
+ ce->status = PCE_STATUS_ERROR;
+ } else {
+ ce->status = PCE_STATUS_ACQUIRED;
+ dev_dbg(dev, "Clock %s managed by runtime PM.\n", ce->con_id);
+ }
+}
+
+/**
* pm_clk_add - Start using a device clock for power management.
* @dev: Device whose clock is going to be used for power management.
* @con_id: Connection ID of the clock.
@@ -73,6 +89,8 @@ int pm_clk_add(struct device *dev, const
}
}
+ pm_clk_acquire(dev, ce);
+
spin_lock_irq(&pcd->lock);
list_add_tail(&ce->node, &pcd->clock_list);
spin_unlock_irq(&pcd->lock);
@@ -82,17 +100,12 @@ int pm_clk_add(struct device *dev, const
/**
* __pm_clk_remove - Destroy PM clock entry.
* @ce: PM clock entry to destroy.
- *
- * This routine must be called under the spinlock protecting the PM list of
- * clocks corresponding the the @ce's device.
*/
static void __pm_clk_remove(struct pm_clock_entry *ce)
{
if (!ce)
return;
- list_del(&ce->node);
-
if (ce->status < PCE_STATUS_ERROR) {
if (ce->status = PCE_STATUS_ENABLED)
clk_disable(ce->clk);
@@ -126,18 +139,22 @@ void pm_clk_remove(struct device *dev, c
spin_lock_irq(&pcd->lock);
list_for_each_entry(ce, &pcd->clock_list, node) {
- if (!con_id && !ce->con_id) {
- __pm_clk_remove(ce);
- break;
- } else if (!con_id || !ce->con_id) {
+ if (!con_id && !ce->con_id)
+ goto remove;
+ else if (!con_id || !ce->con_id)
continue;
- } else if (!strcmp(con_id, ce->con_id)) {
- __pm_clk_remove(ce);
- break;
- }
+ else if (!strcmp(con_id, ce->con_id))
+ goto remove;
}
spin_unlock_irq(&pcd->lock);
+ return;
+
+ remove:
+ list_del(&ce->node);
+ spin_unlock_irq(&pcd->lock);
+
+ __pm_clk_remove(ce);
}
/**
@@ -175,20 +192,27 @@ void pm_clk_destroy(struct device *dev)
{
struct pm_clk_data *pcd = __to_pcd(dev);
struct pm_clock_entry *ce, *c;
+ struct list_head list;
if (!pcd)
return;
dev->power.subsys_data = NULL;
+ INIT_LIST_HEAD(&list);
spin_lock_irq(&pcd->lock);
list_for_each_entry_safe_reverse(ce, c, &pcd->clock_list, node)
- __pm_clk_remove(ce);
+ list_move(&ce->node, &list);
spin_unlock_irq(&pcd->lock);
kfree(pcd);
+
+ list_for_each_entry_safe_reverse(ce, c, &list, node) {
+ list_del(&ce->node);
+ __pm_clk_remove(ce);
+ }
}
#endif /* CONFIG_PM */
@@ -196,23 +220,6 @@ void pm_clk_destroy(struct device *dev)
#ifdef CONFIG_PM_RUNTIME
/**
- * pm_clk_acquire - Acquire a device clock.
- * @dev: Device whose clock is to be acquired.
- * @con_id: Connection ID of the clock.
- */
-static void pm_clk_acquire(struct device *dev,
- struct pm_clock_entry *ce)
-{
- ce->clk = clk_get(dev, ce->con_id);
- if (IS_ERR(ce->clk)) {
- ce->status = PCE_STATUS_ERROR;
- } else {
- ce->status = PCE_STATUS_ACQUIRED;
- dev_dbg(dev, "Clock %s managed by runtime PM.\n", ce->con_id);
- }
-}
-
-/**
* pm_clk_suspend - Disable clocks in a device's PM clock list.
* @dev: Device to disable the clocks for.
*/
@@ -230,9 +237,6 @@ int pm_clk_suspend(struct device *dev)
spin_lock_irqsave(&pcd->lock, flags);
list_for_each_entry_reverse(ce, &pcd->clock_list, node) {
- if (ce->status = PCE_STATUS_NONE)
- pm_clk_acquire(dev, ce);
-
if (ce->status < PCE_STATUS_ERROR) {
clk_disable(ce->clk);
ce->status = PCE_STATUS_ACQUIRED;
@@ -262,9 +266,6 @@ int pm_clk_resume(struct device *dev)
spin_lock_irqsave(&pcd->lock, flags);
list_for_each_entry(ce, &pcd->clock_list, node) {
- if (ce->status = PCE_STATUS_NONE)
- pm_clk_acquire(dev, ce);
-
if (ce->status < PCE_STATUS_ERROR) {
clk_enable(ce->clk);
ce->status = PCE_STATUS_ENABLED;
^ permalink raw reply
* Re: [BUG] on sh7372 (mackerel) with today's Linus' (github) master
From: Russell King - ARM Linux @ 2011-09-24 8:09 UTC (permalink / raw)
To: linux-sh
In-Reply-To: <Pine.LNX.4.64.1109231012490.25166@axis700.grange>
On Sat, Sep 24, 2011 at 12:58:37AM +0200, Rafael J. Wysocki wrote:
> Well, since nobody seems to execute anything that may sleep under
> clocks_mutex, perhaps we can do something like this:
No - a spinlock prevents preemption, a mutex does not.
It's runtime PM which is doing stuff wrong here. You're not supposed to
give up the clk struct with it enabled.
^ permalink raw reply
* Re: [BUG] on sh7372 (mackerel) with today's Linus' (github) master
From: Rafael J. Wysocki @ 2011-09-23 22:58 UTC (permalink / raw)
To: linux-sh
In-Reply-To: <Pine.LNX.4.64.1109231012490.25166@axis700.grange>
On Saturday, September 24, 2011, Rafael J. Wysocki wrote:
> Hi,
>
> On Friday, September 23, 2011, Guennadi Liakhovetski wrote:
> > Hi
> >
> > Not a very pleasant message from an -rc7 kernel:
>
> Well, I wonder why I can't reproduce this.
>
> > [ 9.906250] BUG: sleeping function called from invalid context at /home/lyakh/software/project/24/src/linux-2.6/kernel/mutex.c:271
> > [ 9.906250] in_atomic(): 1, irqs_disabled(): 128, pid: 167, name: modprobe
> > [ 9.906250] 3 locks held by modprobe/167:
> > [ 9.906250] #0: (&__lockdep_no_validate__){......}, at: [<c0176114>] __driver_attach+0x48/0x94
> > [ 9.906250] #1: (&__lockdep_no_validate__){......}, at: [<c0176124>] __driver_attach+0x58/0x94
> > [ 9.906250] #2: (&(&pcd->lock)->rlock){......}, at: [<c017f0d4>] pm_clk_resume+0x28/0x88
> > [ 9.906250] Backtrace:
> > [ 9.906250] [<c0012434>] (dump_backtrace+0x0/0x110) from [<c024f8f0>] (dump_stack+0x18/0x1c)
> > [ 9.906250] r6:00000000 r5:cf0dde20 r4:cf798000 r3:60000093
> > [ 9.906250] [<c024f8d8>] (dump_stack+0x0/0x1c) from [<c001da88>] (__might_sleep+0x100/0x120)
> > [ 9.906250] [<c001d988>] (__might_sleep+0x0/0x120) from [<c025192c>] (mutex_lock_nested+0x2c/0x2fc)
> > [ 9.906250] r4:c0356808
> > [ 9.906250] [<c0251900>] (mutex_lock_nested+0x0/0x2fc) from [<c01a5a44>] (clk_get_sys+0x2c/0xf4)
> > [ 9.906250] r8:00000000 r7:00000000 r6:00000000 r5:cf0dde20 r4:c0356808
> > [ 9.906250] [<c01a5a18>] (clk_get_sys+0x0/0xf4) from [<c01a5b34>] (clk_get+0x28/0x2c)
> > [ 9.906250] [<c01a5b0c>] (clk_get+0x0/0x2c) from [<c017f094>] (pm_clk_acquire+0x18/0x30)
> > [ 9.906250] [<c017f07c>] (pm_clk_acquire+0x0/0x30) from [<c017f0f8>] (pm_clk_resume+0x4c/0x88)
> > [ 9.906250] r4:cf0dcf40 r3:00000000
> > [ 9.906250] [<c017f0ac>] (pm_clk_resume+0x0/0x88) from [<c017c6b8>] (rpm_callback+0x4c/0x6c)
> > [ 9.906250] r8:cf798000 r7:c0333be0 r6:c0333be0 r5:c0333b50 r4:c017f0ac
> > [ 9.906250] r3:00000001
> > [ 9.906250] [<c017c66c>] (rpm_callback+0x0/0x6c) from [<c017d3f0>] (rpm_resume+0x300/0x3d4)
> > [ 9.906250] r6:00000000 r5:c03520c8 r4:c0333b50 r3:c0331fb8
> > [ 9.906250] [<c017d0f0>] (rpm_resume+0x0/0x3d4) from [<c017d750>] (__pm_runtime_resume+0x50/0x68)
> > [ 9.906250] [<c017d700>] (__pm_runtime_resume+0x0/0x68) from [<bf016834>] (sh_mobile_i2c_xfer+0x3c/0x37c [i2c_sh_mobile])
> > [ 9.906250] r7:00000002 r6:00000009 r5:cf799cc0 r4:cf64f800
> > [ 9.906250] [<bf0167f8>] (sh_mobile_i2c_xfer+0x0/0x37c [i2c_sh_mobile]) from [<bf0016ec>] (i2c_transfer+0x98/0xe4 [i2c_core])
> > [ 9.906250] [<bf001654>] (i2c_transfer+0x0/0xe4 [i2c_core]) from [<bf001bb0>] (i2c_smbus_xfer+0x3cc/0x504 [i2c_core])
> > [ 9.906250] [<bf0017e4>] (i2c_smbus_xfer+0x0/0x504 [i2c_core]) from [<bf001fdc>] (i2c_smbus_read_byte_data+0x3c/0x4c [i2c_core])
> > [ 9.906250] [<bf001fa0>] (i2c_smbus_read_byte_data+0x0/0x4c [i2c_core]) from [<bf0910d8>] (tca6416_read_reg+0x48/0x8c [tca6416_keypad])
> > [ 9.906250] [<bf091090>] (tca6416_read_reg+0x0/0x8c [tca6416_keypad]) from [<bf091500>] (tca6416_keypad_probe+0x1dc/0x3a0 [tca6416_keypad])
> > [ 9.906250] r7:00000030 r6:c033d204 r5:cf3dd800 r4:cf1ff0a0
> > [ 9.906250] [<bf091324>] (tca6416_keypad_probe+0x0/0x3a0 [tca6416_keypad]) from [<bf0005cc>] (i2c_device_probe+0x7c/0xa4 [i2c_core])
> > [ 9.906250] [<bf000550>] (i2c_device_probe+0x0/0xa4 [i2c_core]) from [<c0176010>] (driver_probe_device+0xd0/0x18c)
> > [ 9.906250] r6:bf091878 r5:cf64f054 r4:cf64f020 r3:bf000550
> > [ 9.906250] [<c0175f40>] (driver_probe_device+0x0/0x18c) from [<c017613c>] (__driver_attach+0x70/0x94)
> > [ 9.906250] r7:00000000 r6:bf091878 r5:cf64f054 r4:cf64f020
> > [ 9.906250] [<c01760cc>] (__driver_attach+0x0/0x94) from [<c01757a4>] (bus_for_each_dev+0x54/0x84)
> > [ 9.906250] r6:00000000 r5:c01760cc r4:bf091878 r3:00000000
> > [ 9.906250] [<c0175750>] (bus_for_each_dev+0x0/0x84) from [<c0175e34>] (driver_attach+0x20/0x28)
> > [ 9.906250] r6:bf002df8 r5:cf3ef440 r4:bf091878
> > [ 9.906250] [<c0175e14>] (driver_attach+0x0/0x28) from [<c017506c>] (bus_add_driver+0xa8/0x228)
> > [ 9.906250] [<c0174fc4>] (bus_add_driver+0x0/0x228) from [<c01767c8>] (driver_register+0xb0/0x140)
> > [ 9.906250] [<c0176718>] (driver_register+0x0/0x140) from [<bf0009a4>] (i2c_register_driver+0x48/0xb4 [i2c_core])
> > [ 9.906250] r8:00000001 r7:00000000 r6:00000001 r5:bf093000 r4:bf091850
> > [ 9.906250] r3:bf002df8
> > [ 9.906250] [<bf00095c>] (i2c_register_driver+0x0/0xb4 [i2c_core]) from [<bf093018>] (tca6416_keypad_init+0x18/0x24 [tca6416_keypad])
> > [ 9.906250] r5:bf093000 r4:bf0918c4
> > [ 9.906250] [<bf093000>] (tca6416_keypad_init+0x0/0x24 [tca6416_keypad]) from [<c0009374>] (do_one_initcall+0xa0/0x170)
> > [ 9.906250] [<c00092d4>] (do_one_initcall+0x0/0x170) from [<c005583c>] (sys_init_module+0xd24/0xf48)
> > [ 9.906250] r9:0000001b r8:00000001 r7:cf5793a0 r6:00000001 r5:bf09190c
> > [ 9.906250] r4:bf0918c4
> > [ 9.906250] [<c0054b18>] (sys_init_module+0x0/0xf48) from [<c000efc0>] (ret_fast_syscall+0x0/0x30)
> > [ 9.960937] input: tca6408-keys as /devices/platform/i2c-sh_mobile.0/i2c-0/0-0020/input/input0
>
> There is a bug in pm_clk_suspend() (and analogously in pm_clk_resume())
> that calls pm_clk_acquire() under a spinlock, but pm_clk_acquire()
> calls clk_get().
>
> I'm not sure how to fix this at the moment. We may need to revert commits
> b7ab83e and 5a50a01, although I would hate that, because it would require us
> to fix sh-sci runtime PM in a different way.
>
> I'll try to figure out something over the weekend.
Well, since nobody seems to execute anything that may sleep under
clocks_mutex, perhaps we can do something like this:
---
drivers/clk/clkdev.c | 25 ++++++++++++++++---------
1 file changed, 16 insertions(+), 9 deletions(-)
Index: linux/drivers/clk/clkdev.c
=================================--- linux.orig/drivers/clk/clkdev.c
+++ linux/drivers/clk/clkdev.c
@@ -21,7 +21,7 @@
#include <linux/clkdev.h>
static LIST_HEAD(clocks);
-static DEFINE_MUTEX(clocks_mutex);
+static DEFINE_SPINLOCK(clocks_lock);
/*
* Find the correct struct clk for the device and connection ID.
@@ -64,12 +64,13 @@ static struct clk_lookup *clk_find(const
struct clk *clk_get_sys(const char *dev_id, const char *con_id)
{
struct clk_lookup *cl;
+ unsigned long flags;
- mutex_lock(&clocks_mutex);
+ spin_lock_irqsave(&clocks_lock, flags);
cl = clk_find(dev_id, con_id);
if (cl && !__clk_get(cl->clk))
cl = NULL;
- mutex_unlock(&clocks_mutex);
+ spin_unlock_irqrestore(&clocks_lock, flags);
return cl ? cl->clk : ERR_PTR(-ENOENT);
}
@@ -91,20 +92,24 @@ EXPORT_SYMBOL(clk_put);
void clkdev_add(struct clk_lookup *cl)
{
- mutex_lock(&clocks_mutex);
+ unsigned long flags;
+
+ spin_lock_irqsave(&clocks_lock, flags);
list_add_tail(&cl->node, &clocks);
- mutex_unlock(&clocks_mutex);
+ spin_unlock_irqrestore(&clocks_lock, flags);
}
EXPORT_SYMBOL(clkdev_add);
void __init clkdev_add_table(struct clk_lookup *cl, size_t num)
{
- mutex_lock(&clocks_mutex);
+ unsigned long flags;
+
+ spin_lock_irqsave(&clocks_lock, flags);
while (num--) {
list_add_tail(&cl->node, &clocks);
cl++;
}
- mutex_unlock(&clocks_mutex);
+ spin_unlock_irqrestore(&clocks_lock, flags);
}
#define MAX_DEV_ID 20
@@ -167,9 +172,11 @@ EXPORT_SYMBOL(clk_add_alias);
*/
void clkdev_drop(struct clk_lookup *cl)
{
- mutex_lock(&clocks_mutex);
+ unsigned long flags;
+
+ spin_lock_irqsave(&clocks_lock, flags);
list_del(&cl->node);
- mutex_unlock(&clocks_mutex);
+ spin_unlock_irqrestore(&clocks_lock, flags);
kfree(cl);
}
EXPORT_SYMBOL(clkdev_drop);
^ permalink raw reply
* Re: [BUG] on sh7372 (mackerel) with today's Linus' (github) master
From: Rafael J. Wysocki @ 2011-09-23 22:21 UTC (permalink / raw)
To: linux-sh
In-Reply-To: <Pine.LNX.4.64.1109231012490.25166@axis700.grange>
Hi,
On Friday, September 23, 2011, Guennadi Liakhovetski wrote:
> Hi
>
> Not a very pleasant message from an -rc7 kernel:
Well, I wonder why I can't reproduce this.
> [ 9.906250] BUG: sleeping function called from invalid context at /home/lyakh/software/project/24/src/linux-2.6/kernel/mutex.c:271
> [ 9.906250] in_atomic(): 1, irqs_disabled(): 128, pid: 167, name: modprobe
> [ 9.906250] 3 locks held by modprobe/167:
> [ 9.906250] #0: (&__lockdep_no_validate__){......}, at: [<c0176114>] __driver_attach+0x48/0x94
> [ 9.906250] #1: (&__lockdep_no_validate__){......}, at: [<c0176124>] __driver_attach+0x58/0x94
> [ 9.906250] #2: (&(&pcd->lock)->rlock){......}, at: [<c017f0d4>] pm_clk_resume+0x28/0x88
> [ 9.906250] Backtrace:
> [ 9.906250] [<c0012434>] (dump_backtrace+0x0/0x110) from [<c024f8f0>] (dump_stack+0x18/0x1c)
> [ 9.906250] r6:00000000 r5:cf0dde20 r4:cf798000 r3:60000093
> [ 9.906250] [<c024f8d8>] (dump_stack+0x0/0x1c) from [<c001da88>] (__might_sleep+0x100/0x120)
> [ 9.906250] [<c001d988>] (__might_sleep+0x0/0x120) from [<c025192c>] (mutex_lock_nested+0x2c/0x2fc)
> [ 9.906250] r4:c0356808
> [ 9.906250] [<c0251900>] (mutex_lock_nested+0x0/0x2fc) from [<c01a5a44>] (clk_get_sys+0x2c/0xf4)
> [ 9.906250] r8:00000000 r7:00000000 r6:00000000 r5:cf0dde20 r4:c0356808
> [ 9.906250] [<c01a5a18>] (clk_get_sys+0x0/0xf4) from [<c01a5b34>] (clk_get+0x28/0x2c)
> [ 9.906250] [<c01a5b0c>] (clk_get+0x0/0x2c) from [<c017f094>] (pm_clk_acquire+0x18/0x30)
> [ 9.906250] [<c017f07c>] (pm_clk_acquire+0x0/0x30) from [<c017f0f8>] (pm_clk_resume+0x4c/0x88)
> [ 9.906250] r4:cf0dcf40 r3:00000000
> [ 9.906250] [<c017f0ac>] (pm_clk_resume+0x0/0x88) from [<c017c6b8>] (rpm_callback+0x4c/0x6c)
> [ 9.906250] r8:cf798000 r7:c0333be0 r6:c0333be0 r5:c0333b50 r4:c017f0ac
> [ 9.906250] r3:00000001
> [ 9.906250] [<c017c66c>] (rpm_callback+0x0/0x6c) from [<c017d3f0>] (rpm_resume+0x300/0x3d4)
> [ 9.906250] r6:00000000 r5:c03520c8 r4:c0333b50 r3:c0331fb8
> [ 9.906250] [<c017d0f0>] (rpm_resume+0x0/0x3d4) from [<c017d750>] (__pm_runtime_resume+0x50/0x68)
> [ 9.906250] [<c017d700>] (__pm_runtime_resume+0x0/0x68) from [<bf016834>] (sh_mobile_i2c_xfer+0x3c/0x37c [i2c_sh_mobile])
> [ 9.906250] r7:00000002 r6:00000009 r5:cf799cc0 r4:cf64f800
> [ 9.906250] [<bf0167f8>] (sh_mobile_i2c_xfer+0x0/0x37c [i2c_sh_mobile]) from [<bf0016ec>] (i2c_transfer+0x98/0xe4 [i2c_core])
> [ 9.906250] [<bf001654>] (i2c_transfer+0x0/0xe4 [i2c_core]) from [<bf001bb0>] (i2c_smbus_xfer+0x3cc/0x504 [i2c_core])
> [ 9.906250] [<bf0017e4>] (i2c_smbus_xfer+0x0/0x504 [i2c_core]) from [<bf001fdc>] (i2c_smbus_read_byte_data+0x3c/0x4c [i2c_core])
> [ 9.906250] [<bf001fa0>] (i2c_smbus_read_byte_data+0x0/0x4c [i2c_core]) from [<bf0910d8>] (tca6416_read_reg+0x48/0x8c [tca6416_keypad])
> [ 9.906250] [<bf091090>] (tca6416_read_reg+0x0/0x8c [tca6416_keypad]) from [<bf091500>] (tca6416_keypad_probe+0x1dc/0x3a0 [tca6416_keypad])
> [ 9.906250] r7:00000030 r6:c033d204 r5:cf3dd800 r4:cf1ff0a0
> [ 9.906250] [<bf091324>] (tca6416_keypad_probe+0x0/0x3a0 [tca6416_keypad]) from [<bf0005cc>] (i2c_device_probe+0x7c/0xa4 [i2c_core])
> [ 9.906250] [<bf000550>] (i2c_device_probe+0x0/0xa4 [i2c_core]) from [<c0176010>] (driver_probe_device+0xd0/0x18c)
> [ 9.906250] r6:bf091878 r5:cf64f054 r4:cf64f020 r3:bf000550
> [ 9.906250] [<c0175f40>] (driver_probe_device+0x0/0x18c) from [<c017613c>] (__driver_attach+0x70/0x94)
> [ 9.906250] r7:00000000 r6:bf091878 r5:cf64f054 r4:cf64f020
> [ 9.906250] [<c01760cc>] (__driver_attach+0x0/0x94) from [<c01757a4>] (bus_for_each_dev+0x54/0x84)
> [ 9.906250] r6:00000000 r5:c01760cc r4:bf091878 r3:00000000
> [ 9.906250] [<c0175750>] (bus_for_each_dev+0x0/0x84) from [<c0175e34>] (driver_attach+0x20/0x28)
> [ 9.906250] r6:bf002df8 r5:cf3ef440 r4:bf091878
> [ 9.906250] [<c0175e14>] (driver_attach+0x0/0x28) from [<c017506c>] (bus_add_driver+0xa8/0x228)
> [ 9.906250] [<c0174fc4>] (bus_add_driver+0x0/0x228) from [<c01767c8>] (driver_register+0xb0/0x140)
> [ 9.906250] [<c0176718>] (driver_register+0x0/0x140) from [<bf0009a4>] (i2c_register_driver+0x48/0xb4 [i2c_core])
> [ 9.906250] r8:00000001 r7:00000000 r6:00000001 r5:bf093000 r4:bf091850
> [ 9.906250] r3:bf002df8
> [ 9.906250] [<bf00095c>] (i2c_register_driver+0x0/0xb4 [i2c_core]) from [<bf093018>] (tca6416_keypad_init+0x18/0x24 [tca6416_keypad])
> [ 9.906250] r5:bf093000 r4:bf0918c4
> [ 9.906250] [<bf093000>] (tca6416_keypad_init+0x0/0x24 [tca6416_keypad]) from [<c0009374>] (do_one_initcall+0xa0/0x170)
> [ 9.906250] [<c00092d4>] (do_one_initcall+0x0/0x170) from [<c005583c>] (sys_init_module+0xd24/0xf48)
> [ 9.906250] r9:0000001b r8:00000001 r7:cf5793a0 r6:00000001 r5:bf09190c
> [ 9.906250] r4:bf0918c4
> [ 9.906250] [<c0054b18>] (sys_init_module+0x0/0xf48) from [<c000efc0>] (ret_fast_syscall+0x0/0x30)
> [ 9.960937] input: tca6408-keys as /devices/platform/i2c-sh_mobile.0/i2c-0/0-0020/input/input0
There is a bug in pm_clk_suspend() (and analogously in pm_clk_resume())
that calls pm_clk_acquire() under a spinlock, but pm_clk_acquire()
calls clk_get().
I'm not sure how to fix this at the moment. We may need to revert commits
b7ab83e and 5a50a01, although I would hate that, because it would require us
to fix sh-sci runtime PM in a different way.
I'll try to figure out something over the weekend.
Thanks,
Rafael
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox