From 78db441d2ea0c804bc43a2bf3f894c4f7a6c7788 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Thu, 13 Apr 2017 16:36:50 -0400 Subject: USB: f_mass_storage: improve async notification handling This patch makes several adjustments to the way f_mass_storage.c handles its internal state and asynchronous notifications (AKA exceptions): A number of states weren't being used for anything. They are removed. The FSG_STATE_IDLE state was renamed to FSG_STATE_NORMAL, because it now applies whenever the gadget is operating normally, not just when the gadget is idle. The FSG_STATE_RESET state was renamed to FSG_STATE_PROTOCOL_RESET, indicating that it represents a Bulk-Only Transport protocol reset and not a general USB reset. When a signal arrives, it's silly for the signal handler to send itself another signal! Now it takes care of everything inline. Along with an assortment of other minor changes in the same category. Tested-by: Thinh Nguyen Signed-off-by: Alan Stern Signed-off-by: Felipe Balbi --- drivers/usb/gadget/function/f_mass_storage.c | 68 +++++++++------------------- drivers/usb/gadget/function/storage_common.h | 11 +---- 2 files changed, 24 insertions(+), 55 deletions(-) diff --git a/drivers/usb/gadget/function/f_mass_storage.c b/drivers/usb/gadget/function/f_mass_storage.c index 4c8aacc232c0..a0890a058f09 100644 --- a/drivers/usb/gadget/function/f_mass_storage.c +++ b/drivers/usb/gadget/function/f_mass_storage.c @@ -355,7 +355,7 @@ typedef void (*fsg_routine_t)(struct fsg_dev *); static int exception_in_progress(struct fsg_common *common) { - return common->state > FSG_STATE_IDLE; + return common->state > FSG_STATE_NORMAL; } /* Make bulk-out requests be divisible by the maxpacket size */ @@ -528,7 +528,7 @@ static int fsg_setup(struct usb_function *f, * and reinitialize our state. */ DBG(fsg, "bulk reset request\n"); - raise_exception(fsg->common, FSG_STATE_RESET); + raise_exception(fsg->common, FSG_STATE_PROTOCOL_RESET); return USB_GADGET_DELAYED_STATUS; case US_BULK_GET_MAX_LUN: @@ -1625,7 +1625,7 @@ static int finish_reply(struct fsg_common *common) return rc; } -static int send_status(struct fsg_common *common) +static void send_status(struct fsg_common *common) { struct fsg_lun *curlun = common->curlun; struct fsg_buffhd *bh; @@ -1639,7 +1639,7 @@ static int send_status(struct fsg_common *common) while (bh->state != BUF_STATE_EMPTY) { rc = sleep_thread(common, true); if (rc) - return rc; + return; } if (curlun) { @@ -1674,10 +1674,10 @@ static int send_status(struct fsg_common *common) bh->inreq->zero = 0; if (!start_in_transfer(common, bh)) /* Don't know what to do if common->fsg is NULL */ - return -EIO; + return; common->next_buffhd_to_fill = bh->next; - return 0; + return; } @@ -2362,9 +2362,11 @@ static void handle_exception(struct fsg_common *common) if (!sig) break; if (sig != SIGUSR1) { + spin_lock_irq(&common->lock); if (common->state < FSG_STATE_EXIT) DBG(common, "Main thread exiting on signal\n"); - raise_exception(common, FSG_STATE_EXIT); + common->state = FSG_STATE_EXIT; + spin_unlock_irq(&common->lock); } } @@ -2413,10 +2415,9 @@ static void handle_exception(struct fsg_common *common) common->next_buffhd_to_drain = &common->buffhds[0]; exception_req_tag = common->exception_req_tag; old_state = common->state; + common->state = FSG_STATE_NORMAL; - if (old_state == FSG_STATE_ABORT_BULK_OUT) - common->state = FSG_STATE_STATUS_PHASE; - else { + if (old_state != FSG_STATE_ABORT_BULK_OUT) { for (i = 0; i < ARRAY_SIZE(common->luns); ++i) { curlun = common->luns[i]; if (!curlun) @@ -2427,21 +2428,19 @@ static void handle_exception(struct fsg_common *common) curlun->sense_data_info = 0; curlun->info_valid = 0; } - common->state = FSG_STATE_IDLE; } spin_unlock_irq(&common->lock); /* Carry out any extra actions required for the exception */ switch (old_state) { + case FSG_STATE_NORMAL: + break; + case FSG_STATE_ABORT_BULK_OUT: send_status(common); - spin_lock_irq(&common->lock); - if (common->state == FSG_STATE_STATUS_PHASE) - common->state = FSG_STATE_IDLE; - spin_unlock_irq(&common->lock); break; - case FSG_STATE_RESET: + case FSG_STATE_PROTOCOL_RESET: /* * In case we were forced against our will to halt a * bulk endpoint, clear the halt now. (The SuperH UDC @@ -2474,19 +2473,13 @@ static void handle_exception(struct fsg_common *common) break; case FSG_STATE_EXIT: - case FSG_STATE_TERMINATED: do_set_interface(common, NULL); /* Free resources */ spin_lock_irq(&common->lock); common->state = FSG_STATE_TERMINATED; /* Stop the thread */ spin_unlock_irq(&common->lock); break; - case FSG_STATE_INTERFACE_CHANGE: - case FSG_STATE_DISCONNECT: - case FSG_STATE_COMMAND_PHASE: - case FSG_STATE_DATA_PHASE: - case FSG_STATE_STATUS_PHASE: - case FSG_STATE_IDLE: + case FSG_STATE_TERMINATED: break; } } @@ -2529,29 +2522,13 @@ static int fsg_main_thread(void *common_) continue; } - if (get_next_command(common)) + if (get_next_command(common) || exception_in_progress(common)) continue; - - spin_lock_irq(&common->lock); - if (!exception_in_progress(common)) - common->state = FSG_STATE_DATA_PHASE; - spin_unlock_irq(&common->lock); - - if (do_scsi_command(common) || finish_reply(common)) + if (do_scsi_command(common) || exception_in_progress(common)) continue; - - spin_lock_irq(&common->lock); - if (!exception_in_progress(common)) - common->state = FSG_STATE_STATUS_PHASE; - spin_unlock_irq(&common->lock); - - if (send_status(common)) + if (finish_reply(common) || exception_in_progress(common)) continue; - - spin_lock_irq(&common->lock); - if (!exception_in_progress(common)) - common->state = FSG_STATE_IDLE; - spin_unlock_irq(&common->lock); + send_status(common); } spin_lock_irq(&common->lock); @@ -2972,7 +2949,6 @@ static void fsg_common_release(struct kref *ref) if (common->state != FSG_STATE_TERMINATED) { raise_exception(common, FSG_STATE_EXIT); wait_for_completion(&common->thread_notifier); - common->thread_task = NULL; } for (i = 0; i < ARRAY_SIZE(common->luns); ++i) { @@ -3021,11 +2997,11 @@ static int fsg_bind(struct usb_configuration *c, struct usb_function *f) } if (!common->thread_task) { - common->state = FSG_STATE_IDLE; + common->state = FSG_STATE_NORMAL; common->thread_task = kthread_create(fsg_main_thread, common, "file-storage"); if (IS_ERR(common->thread_task)) { - int ret = PTR_ERR(common->thread_task); + ret = PTR_ERR(common->thread_task); common->thread_task = NULL; common->state = FSG_STATE_TERMINATED; return ret; diff --git a/drivers/usb/gadget/function/storage_common.h b/drivers/usb/gadget/function/storage_common.h index e69848994cb4..e6095dfbf1d5 100644 --- a/drivers/usb/gadget/function/storage_common.h +++ b/drivers/usb/gadget/function/storage_common.h @@ -157,17 +157,10 @@ struct fsg_buffhd { }; enum fsg_state { - /* This one isn't used anywhere */ - FSG_STATE_COMMAND_PHASE = -10, - FSG_STATE_DATA_PHASE, - FSG_STATE_STATUS_PHASE, - - FSG_STATE_IDLE = 0, + FSG_STATE_NORMAL, FSG_STATE_ABORT_BULK_OUT, - FSG_STATE_RESET, - FSG_STATE_INTERFACE_CHANGE, + FSG_STATE_PROTOCOL_RESET, FSG_STATE_CONFIG_CHANGE, - FSG_STATE_DISCONNECT, FSG_STATE_EXIT, FSG_STATE_TERMINATED }; -- cgit v1.2.3 From 225785aec726f3edd5077be8f084b0b70ca197a8 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Thu, 13 Apr 2017 16:37:01 -0400 Subject: USB: f_mass_storage: improve memory barriers and synchronization This patch reworks the way f_mass_storage.c handles memory barriers and synchronization: The driver now uses a wait_queue instead of doing its own task-state manipulations (even though only one task will ever use the wait_queue). The thread_wakeup_needed variable is removed. It was only a source of trouble; although it was what the driver tested to see whether it should wake up, what we really wanted to see was whether a USB transfer had completed. All the explicit memory barriers scattered throughout the driver are replaced by a few calls to smp_load_acquire() and smp_store_release(). The inreq_busy and outreq_busy fields are removed. In their place, the driver keeps track of the current I/O direction by splitting BUF_STATE_BUSY into two states: BUF_STATE_SENDING and BUF_STATE_RECEIVING. The buffer states are no longer protected by a lock. Mutual exclusion isn't needed; the state is changed only by the driver's main thread when it owns the buffer, and only by the request completion routine when the gadget core owns the buffer. The do_write() and throw_away_data() routines were reorganized to make efficient use of the new sleeping mechanism. This resulted in the removal of one indentation level in those routines, making the patch appear to be more more complicated than it really is. In a few places, the driver allowed itself to be frozen although it really shouldn't have (in the middle of executing a SCSI command). Those places have been fixed. The logic in the exception handler for aborting transfers and waiting for them to stop has been simplified. Tested-by: Thinh Nguyen Signed-off-by: Alan Stern Signed-off-by: Felipe Balbi --- drivers/usb/gadget/function/f_mass_storage.c | 355 ++++++++++++--------------- drivers/usb/gadget/function/storage_common.h | 7 +- 2 files changed, 156 insertions(+), 206 deletions(-) diff --git a/drivers/usb/gadget/function/f_mass_storage.c b/drivers/usb/gadget/function/f_mass_storage.c index a0890a058f09..e80b9c123a9d 100644 --- a/drivers/usb/gadget/function/f_mass_storage.c +++ b/drivers/usb/gadget/function/f_mass_storage.c @@ -260,12 +260,13 @@ struct fsg_common { struct usb_gadget *gadget; struct usb_composite_dev *cdev; struct fsg_dev *fsg, *new_fsg; + wait_queue_head_t io_wait; wait_queue_head_t fsg_wait; /* filesem protects: backing files in use */ struct rw_semaphore filesem; - /* lock protects: state, all the req_busy's */ + /* lock protects: state and thread_task */ spinlock_t lock; struct usb_ep *ep0; /* Copy of gadget->ep0 */ @@ -303,7 +304,6 @@ struct fsg_common { unsigned int running:1; unsigned int sysfs:1; - int thread_wakeup_needed; struct completion thread_notifier; struct task_struct *thread_task; @@ -393,16 +393,6 @@ static int fsg_set_halt(struct fsg_dev *fsg, struct usb_ep *ep) /* These routines may be called in process context or in_irq */ -/* Caller must hold fsg->lock */ -static void wakeup_thread(struct fsg_common *common) -{ - smp_wmb(); /* ensure the write of bh->state is complete */ - /* Tell the main thread that something has happened */ - common->thread_wakeup_needed = 1; - if (common->thread_task) - wake_up_process(common->thread_task); -} - static void raise_exception(struct fsg_common *common, enum fsg_state new_state) { unsigned long flags; @@ -456,13 +446,9 @@ static void bulk_in_complete(struct usb_ep *ep, struct usb_request *req) if (req->status == -ECONNRESET) /* Request was cancelled */ usb_ep_fifo_flush(ep); - /* Hold the lock while we update the request and buffer states */ - smp_wmb(); - spin_lock(&common->lock); - bh->inreq_busy = 0; - bh->state = BUF_STATE_EMPTY; - wakeup_thread(common); - spin_unlock(&common->lock); + /* Synchronize with the smp_load_acquire() in sleep_thread() */ + smp_store_release(&bh->state, BUF_STATE_EMPTY); + wake_up(&common->io_wait); } static void bulk_out_complete(struct usb_ep *ep, struct usb_request *req) @@ -477,13 +463,9 @@ static void bulk_out_complete(struct usb_ep *ep, struct usb_request *req) if (req->status == -ECONNRESET) /* Request was cancelled */ usb_ep_fifo_flush(ep); - /* Hold the lock while we update the request and buffer states */ - smp_wmb(); - spin_lock(&common->lock); - bh->outreq_busy = 0; - bh->state = BUF_STATE_FULL; - wakeup_thread(common); - spin_unlock(&common->lock); + /* Synchronize with the smp_load_acquire() in sleep_thread() */ + smp_store_release(&bh->state, BUF_STATE_FULL); + wake_up(&common->io_wait); } static int _fsg_common_get_max_lun(struct fsg_common *common) @@ -559,43 +541,39 @@ static int fsg_setup(struct usb_function *f, /* All the following routines run in process context */ /* Use this for bulk or interrupt transfers, not ep0 */ -static void start_transfer(struct fsg_dev *fsg, struct usb_ep *ep, - struct usb_request *req, int *pbusy, - enum fsg_buffer_state *state) +static int start_transfer(struct fsg_dev *fsg, struct usb_ep *ep, + struct usb_request *req) { int rc; if (ep == fsg->bulk_in) dump_msg(fsg, "bulk-in", req->buf, req->length); - spin_lock_irq(&fsg->common->lock); - *pbusy = 1; - *state = BUF_STATE_BUSY; - spin_unlock_irq(&fsg->common->lock); - rc = usb_ep_queue(ep, req, GFP_KERNEL); - if (rc == 0) - return; /* All good, we're done */ - - *pbusy = 0; - *state = BUF_STATE_EMPTY; + if (rc) { - /* We can't do much more than wait for a reset */ + /* We can't do much more than wait for a reset */ + req->status = rc; - /* - * Note: currently the net2280 driver fails zero-length - * submissions if DMA is enabled. - */ - if (rc != -ESHUTDOWN && !(rc == -EOPNOTSUPP && req->length == 0)) - WARNING(fsg, "error in submission: %s --> %d\n", ep->name, rc); + /* + * Note: currently the net2280 driver fails zero-length + * submissions if DMA is enabled. + */ + if (rc != -ESHUTDOWN && + !(rc == -EOPNOTSUPP && req->length == 0)) + WARNING(fsg, "error in submission: %s --> %d\n", + ep->name, rc); + } + return rc; } static bool start_in_transfer(struct fsg_common *common, struct fsg_buffhd *bh) { if (!fsg_is_set(common)) return false; - start_transfer(common->fsg, common->fsg->bulk_in, - bh->inreq, &bh->inreq_busy, &bh->state); + bh->state = BUF_STATE_SENDING; + if (start_transfer(common->fsg, common->fsg->bulk_in, bh->inreq)) + bh->state = BUF_STATE_EMPTY; return true; } @@ -603,32 +581,31 @@ static bool start_out_transfer(struct fsg_common *common, struct fsg_buffhd *bh) { if (!fsg_is_set(common)) return false; - start_transfer(common->fsg, common->fsg->bulk_out, - bh->outreq, &bh->outreq_busy, &bh->state); + bh->state = BUF_STATE_RECEIVING; + if (start_transfer(common->fsg, common->fsg->bulk_out, bh->outreq)) + bh->state = BUF_STATE_FULL; return true; } -static int sleep_thread(struct fsg_common *common, bool can_freeze) +static int sleep_thread(struct fsg_common *common, bool can_freeze, + struct fsg_buffhd *bh) { - int rc = 0; + int rc; - /* Wait until a signal arrives or we are woken up */ - for (;;) { - if (can_freeze) - try_to_freeze(); - set_current_state(TASK_INTERRUPTIBLE); - if (signal_pending(current)) { - rc = -EINTR; - break; - } - if (common->thread_wakeup_needed) - break; - schedule(); - } - __set_current_state(TASK_RUNNING); - common->thread_wakeup_needed = 0; - smp_rmb(); /* ensure the latest bh->state is visible */ - return rc; + /* Wait until a signal arrives or bh is no longer busy */ + if (can_freeze) + /* + * synchronize with the smp_store_release(&bh->state) in + * bulk_in_complete() or bulk_out_complete() + */ + rc = wait_event_freezable(common->io_wait, + bh && smp_load_acquire(&bh->state) >= + BUF_STATE_EMPTY); + else + rc = wait_event_interruptible(common->io_wait, + bh && smp_load_acquire(&bh->state) >= + BUF_STATE_EMPTY); + return rc ? -EINTR : 0; } @@ -688,11 +665,9 @@ static int do_read(struct fsg_common *common) /* Wait for the next buffer to become available */ bh = common->next_buffhd_to_fill; - while (bh->state != BUF_STATE_EMPTY) { - rc = sleep_thread(common, false); - if (rc) - return rc; - } + rc = sleep_thread(common, false, bh); + if (rc) + return rc; /* * If we were asked to read past the end of file, @@ -869,84 +844,80 @@ static int do_write(struct fsg_common *common) bh = common->next_buffhd_to_drain; if (bh->state == BUF_STATE_EMPTY && !get_some_more) break; /* We stopped early */ - if (bh->state == BUF_STATE_FULL) { - smp_rmb(); - common->next_buffhd_to_drain = bh->next; - bh->state = BUF_STATE_EMPTY; - - /* Did something go wrong with the transfer? */ - if (bh->outreq->status != 0) { - curlun->sense_data = SS_COMMUNICATION_FAILURE; - curlun->sense_data_info = + + /* Wait for the data to be received */ + rc = sleep_thread(common, false, bh); + if (rc) + return rc; + + common->next_buffhd_to_drain = bh->next; + bh->state = BUF_STATE_EMPTY; + + /* Did something go wrong with the transfer? */ + if (bh->outreq->status != 0) { + curlun->sense_data = SS_COMMUNICATION_FAILURE; + curlun->sense_data_info = file_offset >> curlun->blkbits; - curlun->info_valid = 1; - break; - } + curlun->info_valid = 1; + break; + } - amount = bh->outreq->actual; - if (curlun->file_length - file_offset < amount) { - LERROR(curlun, - "write %u @ %llu beyond end %llu\n", + amount = bh->outreq->actual; + if (curlun->file_length - file_offset < amount) { + LERROR(curlun, "write %u @ %llu beyond end %llu\n", amount, (unsigned long long)file_offset, (unsigned long long)curlun->file_length); - amount = curlun->file_length - file_offset; - } + amount = curlun->file_length - file_offset; + } - /* Don't accept excess data. The spec doesn't say - * what to do in this case. We'll ignore the error. - */ - amount = min(amount, bh->bulk_out_intended_length); - - /* Don't write a partial block */ - amount = round_down(amount, curlun->blksize); - if (amount == 0) - goto empty_write; - - /* Perform the write */ - file_offset_tmp = file_offset; - nwritten = vfs_write(curlun->filp, - (char __user *)bh->buf, - amount, &file_offset_tmp); - VLDBG(curlun, "file write %u @ %llu -> %d\n", amount, - (unsigned long long)file_offset, (int)nwritten); - if (signal_pending(current)) - return -EINTR; /* Interrupted! */ - - if (nwritten < 0) { - LDBG(curlun, "error in file write: %d\n", - (int)nwritten); - nwritten = 0; - } else if (nwritten < amount) { - LDBG(curlun, "partial file write: %d/%u\n", - (int)nwritten, amount); - nwritten = round_down(nwritten, curlun->blksize); - } - file_offset += nwritten; - amount_left_to_write -= nwritten; - common->residue -= nwritten; + /* + * Don't accept excess data. The spec doesn't say + * what to do in this case. We'll ignore the error. + */ + amount = min(amount, bh->bulk_out_intended_length); - /* If an error occurred, report it and its position */ - if (nwritten < amount) { - curlun->sense_data = SS_WRITE_ERROR; - curlun->sense_data_info = + /* Don't write a partial block */ + amount = round_down(amount, curlun->blksize); + if (amount == 0) + goto empty_write; + + /* Perform the write */ + file_offset_tmp = file_offset; + nwritten = vfs_write(curlun->filp, (char __user *)bh->buf, + amount, &file_offset_tmp); + VLDBG(curlun, "file write %u @ %llu -> %d\n", amount, + (unsigned long long)file_offset, (int)nwritten); + if (signal_pending(current)) + return -EINTR; /* Interrupted! */ + + if (nwritten < 0) { + LDBG(curlun, "error in file write: %d\n", + (int) nwritten); + nwritten = 0; + } else if (nwritten < amount) { + LDBG(curlun, "partial file write: %d/%u\n", + (int) nwritten, amount); + nwritten = round_down(nwritten, curlun->blksize); + } + file_offset += nwritten; + amount_left_to_write -= nwritten; + common->residue -= nwritten; + + /* If an error occurred, report it and its position */ + if (nwritten < amount) { + curlun->sense_data = SS_WRITE_ERROR; + curlun->sense_data_info = file_offset >> curlun->blkbits; - curlun->info_valid = 1; - break; - } + curlun->info_valid = 1; + break; + } empty_write: - /* Did the host decide to stop early? */ - if (bh->outreq->actual < bh->bulk_out_intended_length) { - common->short_packet_received = 1; - break; - } - continue; + /* Did the host decide to stop early? */ + if (bh->outreq->actual < bh->bulk_out_intended_length) { + common->short_packet_received = 1; + break; } - - /* Wait for something to happen */ - rc = sleep_thread(common, false); - if (rc) - return rc; } return -EIO; /* No default reply */ @@ -1471,7 +1442,7 @@ static int wedge_bulk_in_endpoint(struct fsg_dev *fsg) static int throw_away_data(struct fsg_common *common) { - struct fsg_buffhd *bh; + struct fsg_buffhd *bh, *bh2; u32 amount; int rc; @@ -1479,26 +1450,10 @@ static int throw_away_data(struct fsg_common *common) bh->state != BUF_STATE_EMPTY || common->usb_amount_left > 0; bh = common->next_buffhd_to_drain) { - /* Throw away the data in a filled buffer */ - if (bh->state == BUF_STATE_FULL) { - smp_rmb(); - bh->state = BUF_STATE_EMPTY; - common->next_buffhd_to_drain = bh->next; - - /* A short packet or an error ends everything */ - if (bh->outreq->actual < bh->bulk_out_intended_length || - bh->outreq->status != 0) { - raise_exception(common, - FSG_STATE_ABORT_BULK_OUT); - return -EINTR; - } - continue; - } - /* Try to submit another request if we need one */ - bh = common->next_buffhd_to_fill; - if (bh->state == BUF_STATE_EMPTY - && common->usb_amount_left > 0) { + bh2 = common->next_buffhd_to_fill; + if (bh2->state == BUF_STATE_EMPTY && + common->usb_amount_left > 0) { amount = min(common->usb_amount_left, FSG_BUFLEN); /* @@ -1506,19 +1461,30 @@ static int throw_away_data(struct fsg_common *common) * equal to the buffer size, which is divisible by * the bulk-out maxpacket size. */ - set_bulk_out_req_length(common, bh, amount); - if (!start_out_transfer(common, bh)) + set_bulk_out_req_length(common, bh2, amount); + if (!start_out_transfer(common, bh2)) /* Dunno what to do if common->fsg is NULL */ return -EIO; - common->next_buffhd_to_fill = bh->next; + common->next_buffhd_to_fill = bh2->next; common->usb_amount_left -= amount; continue; } - /* Otherwise wait for something to happen */ - rc = sleep_thread(common, true); + /* Wait for the data to be received */ + rc = sleep_thread(common, false, bh); if (rc) return rc; + + /* Throw away the data in a filled buffer */ + bh->state = BUF_STATE_EMPTY; + common->next_buffhd_to_drain = bh->next; + + /* A short packet or an error ends everything */ + if (bh->outreq->actual < bh->bulk_out_intended_length || + bh->outreq->status != 0) { + raise_exception(common, FSG_STATE_ABORT_BULK_OUT); + return -EINTR; + } } return 0; } @@ -1636,11 +1602,9 @@ static void send_status(struct fsg_common *common) /* Wait for the next buffer to become available */ bh = common->next_buffhd_to_fill; - while (bh->state != BUF_STATE_EMPTY) { - rc = sleep_thread(common, true); - if (rc) - return; - } + rc = sleep_thread(common, false, bh); + if (rc) + return; if (curlun) { sd = curlun->sense_data; @@ -1839,11 +1803,10 @@ static int do_scsi_command(struct fsg_common *common) /* Wait for the next buffer to become available for data or status */ bh = common->next_buffhd_to_fill; common->next_buffhd_to_drain = bh; - while (bh->state != BUF_STATE_EMPTY) { - rc = sleep_thread(common, true); - if (rc) - return rc; - } + rc = sleep_thread(common, false, bh); + if (rc) + return rc; + common->phase_error = 0; common->short_packet_received = 0; @@ -2186,11 +2149,9 @@ static int get_next_command(struct fsg_common *common) /* Wait for the next buffer to become available */ bh = common->next_buffhd_to_fill; - while (bh->state != BUF_STATE_EMPTY) { - rc = sleep_thread(common, true); - if (rc) - return rc; - } + rc = sleep_thread(common, true, bh); + if (rc) + return rc; /* Queue a request to read a Bulk-only CBW */ set_bulk_out_req_length(common, bh, US_BULK_CB_WRAP_LEN); @@ -2205,12 +2166,10 @@ static int get_next_command(struct fsg_common *common) */ /* Wait for the CBW to arrive */ - while (bh->state != BUF_STATE_FULL) { - rc = sleep_thread(common, true); - if (rc) - return rc; - } - smp_rmb(); + rc = sleep_thread(common, true, bh); + if (rc) + return rc; + rc = fsg_is_set(common) ? received_cbw(common->fsg, bh) : -EIO; bh->state = BUF_STATE_EMPTY; @@ -2374,23 +2333,14 @@ static void handle_exception(struct fsg_common *common) if (likely(common->fsg)) { for (i = 0; i < common->fsg_num_buffers; ++i) { bh = &common->buffhds[i]; - if (bh->inreq_busy) + if (bh->state == BUF_STATE_SENDING) usb_ep_dequeue(common->fsg->bulk_in, bh->inreq); - if (bh->outreq_busy) + if (bh->state == BUF_STATE_RECEIVING) usb_ep_dequeue(common->fsg->bulk_out, bh->outreq); - } - /* Wait until everything is idle */ - for (;;) { - int num_active = 0; - for (i = 0; i < common->fsg_num_buffers; ++i) { - bh = &common->buffhds[i]; - num_active += bh->inreq_busy + bh->outreq_busy; - } - if (num_active == 0) - break; - if (sleep_thread(common, true)) + /* Wait for a transfer to become idle */ + if (sleep_thread(common, false, bh)) return; } @@ -2518,7 +2468,7 @@ static int fsg_main_thread(void *common_) } if (!common->running) { - sleep_thread(common, true); + sleep_thread(common, true, NULL); continue; } @@ -2648,6 +2598,7 @@ static struct fsg_common *fsg_common_setup(struct fsg_common *common) spin_lock_init(&common->lock); kref_init(&common->ref); init_completion(&common->thread_notifier); + init_waitqueue_head(&common->io_wait); init_waitqueue_head(&common->fsg_wait); common->state = FSG_STATE_TERMINATED; memset(common->luns, 0, sizeof(common->luns)); diff --git a/drivers/usb/gadget/function/storage_common.h b/drivers/usb/gadget/function/storage_common.h index e6095dfbf1d5..e0814a960132 100644 --- a/drivers/usb/gadget/function/storage_common.h +++ b/drivers/usb/gadget/function/storage_common.h @@ -133,9 +133,10 @@ static inline bool fsg_lun_is_open(struct fsg_lun *curlun) #define FSG_MAX_LUNS 16 enum fsg_buffer_state { + BUF_STATE_SENDING = -2, + BUF_STATE_RECEIVING, BUF_STATE_EMPTY = 0, - BUF_STATE_FULL, - BUF_STATE_BUSY + BUF_STATE_FULL }; struct fsg_buffhd { @@ -151,9 +152,7 @@ struct fsg_buffhd { unsigned int bulk_out_intended_length; struct usb_request *inreq; - int inreq_busy; struct usb_request *outreq; - int outreq_busy; }; enum fsg_state { -- cgit v1.2.3 From 202adafe5a6e68fea30982c57cf51489936487ef Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Wed, 17 May 2017 13:19:06 +0300 Subject: usb: dwc3: gadget: don't WARN about lack of TRBs We don't need a big fat warning with stack dump at all. Running out of TRBs is a normal condition and we will have more TRBs available as soon as some transfers complete. Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/gadget.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 6f6f0b3be3ad..7113a9f53ca9 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -953,7 +953,6 @@ static struct dwc3_trb *dwc3_ep_prev_trb(struct dwc3_ep *dep, u8 index) static u32 dwc3_calc_trbs_left(struct dwc3_ep *dep) { struct dwc3_trb *tmp; - struct dwc3 *dwc = dep->dwc; u8 trbs_left; /* @@ -965,8 +964,7 @@ static u32 dwc3_calc_trbs_left(struct dwc3_ep *dep) */ if (dep->trb_enqueue == dep->trb_dequeue) { tmp = dwc3_ep_prev_trb(dep, dep->trb_enqueue); - if (dev_WARN_ONCE(dwc->dev, tmp->ctrl & DWC3_TRB_CTRL_HWO, - "%s No TRBS left\n", dep->name)) + if (tmp->ctrl & DWC3_TRB_CTRL_HWO) return 0; return DWC3_TRB_NUM - 1; -- cgit v1.2.3 From 64b9533ec14bdfb8e77bbe1ae4f4842043d22aeb Mon Sep 17 00:00:00 2001 From: Geliang Tang Date: Mon, 8 May 2017 23:14:39 +0800 Subject: usb: cdc-wdm: use memdup_user Use memdup_user() helper instead of open-coding to simplify the code. Signed-off-by: Geliang Tang Acked-by: Oliver Neukum Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/cdc-wdm.c | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c index 08669fee6d7f..8f972247b1c1 100644 --- a/drivers/usb/class/cdc-wdm.c +++ b/drivers/usb/class/cdc-wdm.c @@ -361,17 +361,9 @@ static ssize_t wdm_write if (we < 0) return usb_translate_errors(we); - buf = kmalloc(count, GFP_KERNEL); - if (!buf) { - rv = -ENOMEM; - goto outnl; - } - - r = copy_from_user(buf, buffer, count); - if (r > 0) { - rv = -EFAULT; - goto out_free_mem; - } + buf = memdup_user(buffer, count); + if (IS_ERR(buf)) + return PTR_ERR(buf); /* concurrent writes and disconnect */ r = mutex_lock_interruptible(&desc->wlock); @@ -441,8 +433,7 @@ static ssize_t wdm_write usb_autopm_put_interface(desc->intf); mutex_unlock(&desc->wlock); -outnl: - return rv < 0 ? rv : count; + return count; out_free_mem_pm: usb_autopm_put_interface(desc->intf); -- cgit v1.2.3 From 4568136620c6482bcd4a6b0cd6ae8e5679615d7e Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Tue, 25 Apr 2017 17:56:11 -0700 Subject: usb: core: Check URB setup_packet and transfer_buffer sanity Update usb_hcd_map_urb_for_dma() to check for an URB's setup_packet and transfer_buffer sanity. We first check that urb->setup_packet is neither coming from vmalloc space nor is an on stack buffer, and if that's the case, produce a warning and return an error. For urb->transfer_buffer there is an existing is_vmalloc_addr() check so we just supplement that with an object_is_on_stack() check, produce a warning if that is the case and also return an error. Signed-off-by: Florian Fainelli Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hcd.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 49550790a3cb..26d710eec7da 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -1523,6 +1524,14 @@ int usb_hcd_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb, if (hcd->self.uses_pio_for_control) return ret; if (IS_ENABLED(CONFIG_HAS_DMA) && hcd->self.uses_dma) { + if (is_vmalloc_addr(urb->setup_packet)) { + WARN_ONCE(1, "setup packet is not dma capable\n"); + return -EAGAIN; + } else if (object_is_on_stack(urb->setup_packet)) { + WARN_ONCE(1, "setup packet is on stack\n"); + return -EAGAIN; + } + urb->setup_dma = dma_map_single( hcd->self.sysdev, urb->setup_packet, @@ -1587,6 +1596,9 @@ int usb_hcd_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb, } else if (is_vmalloc_addr(urb->transfer_buffer)) { WARN_ONCE(1, "transfer buffer not dma capable\n"); ret = -EAGAIN; + } else if (object_is_on_stack(urb->transfer_buffer)) { + WARN_ONCE(1, "transfer buffer is on stack\n"); + ret = -EAGAIN; } else { urb->transfer_dma = dma_map_single( hcd->self.sysdev, -- cgit v1.2.3 From 614536dac7f33c64a58fab4767a325d52572a7d1 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Tue, 25 Apr 2017 17:56:12 -0700 Subject: usb: udc: core: Error if req->buf is either from vmalloc or stack Check that req->buf is a valid DMA capable address, produce a warning and return an error if it's either coming from vmalloc space or is an on stack buffer. Signed-off-by: Florian Fainelli Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/core.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c index efce68e9a8e0..a03aec574f64 100644 --- a/drivers/usb/gadget/udc/core.c +++ b/drivers/usb/gadget/udc/core.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -798,6 +799,14 @@ int usb_gadget_map_request_by_dev(struct device *dev, req->num_mapped_sgs = mapped; } else { + if (is_vmalloc_addr(req->buf)) { + dev_err(dev, "buffer is not dma capable\n"); + return -EFAULT; + } else if (object_is_on_stack(req->buf)) { + dev_err(dev, "buffer is on stack\n"); + return -EFAULT; + } + req->dma = dma_map_single(dev, req->buf, req->length, is_in ? DMA_TO_DEVICE : DMA_FROM_DEVICE); -- cgit v1.2.3 From a86c309e71dc4f43c68483f7e328b1d4f9fef618 Mon Sep 17 00:00:00 2001 From: Mats Karrman Date: Tue, 25 Apr 2017 23:49:47 +0200 Subject: usb: typec: Don't prevent using constant typec_mode_desc initializers In some situations, e.g. when registering alternate modes for local typec ports, it may be handy to use constant mode descriptors. Allow this by changing the mode descriptor arguments of typec_port_register_altmode() et.al. to using const pointers. Signed-off-by: Mats Karrman Reviewed-by: Guenter Roeck Acked-by: Heikki Krogerus Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/typec.c | 11 ++++++----- include/linux/usb/typec.h | 6 +++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/drivers/usb/typec/typec.c b/drivers/usb/typec/typec.c index 89e540bb7ff3..db5ee730ad24 100644 --- a/drivers/usb/typec/typec.c +++ b/drivers/usb/typec/typec.c @@ -291,7 +291,7 @@ typec_altmode_roles_show(struct device *dev, struct device_attribute *attr, } static void typec_init_modes(struct typec_altmode *alt, - struct typec_mode_desc *desc, bool is_port) + const struct typec_mode_desc *desc, bool is_port) { int i; @@ -378,7 +378,8 @@ static const struct device_type typec_altmode_dev_type = { }; static struct typec_altmode * -typec_register_altmode(struct device *parent, struct typec_altmode_desc *desc) +typec_register_altmode(struct device *parent, + const struct typec_altmode_desc *desc) { struct typec_altmode *alt; int ret; @@ -495,7 +496,7 @@ EXPORT_SYMBOL_GPL(typec_partner_set_identity); */ struct typec_altmode * typec_partner_register_altmode(struct typec_partner *partner, - struct typec_altmode_desc *desc) + const struct typec_altmode_desc *desc) { return typec_register_altmode(&partner->dev, desc); } @@ -590,7 +591,7 @@ static const struct device_type typec_plug_dev_type = { */ struct typec_altmode * typec_plug_register_altmode(struct typec_plug *plug, - struct typec_altmode_desc *desc) + const struct typec_altmode_desc *desc) { return typec_register_altmode(&plug->dev, desc); } @@ -1159,7 +1160,7 @@ EXPORT_SYMBOL_GPL(typec_set_pwr_opmode); */ struct typec_altmode * typec_port_register_altmode(struct typec_port *port, - struct typec_altmode_desc *desc) + const struct typec_altmode_desc *desc) { return typec_register_altmode(&port->dev, desc); } diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h index ec78204964ab..d1d2ebcf36ec 100644 --- a/include/linux/usb/typec.h +++ b/include/linux/usb/typec.h @@ -117,13 +117,13 @@ struct typec_altmode_desc { struct typec_altmode *typec_partner_register_altmode(struct typec_partner *partner, - struct typec_altmode_desc *desc); + const struct typec_altmode_desc *desc); struct typec_altmode *typec_plug_register_altmode(struct typec_plug *plug, - struct typec_altmode_desc *desc); + const struct typec_altmode_desc *desc); struct typec_altmode *typec_port_register_altmode(struct typec_port *port, - struct typec_altmode_desc *desc); + const struct typec_altmode_desc *desc); void typec_unregister_altmode(struct typec_altmode *altmode); struct typec_port *typec_altmode2port(struct typec_altmode *alt); -- cgit v1.2.3 From 2aa3add0cc8f4b937062f33332297486c95ae966 Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Tue, 2 May 2017 11:09:11 -0500 Subject: usb: host: remove unnecessary null check Remove unnecessary null check. udev->tt cannot ever be NULL when this section of code runs. Addresses-Coverity-ID: 100828 Signed-off-by: Gustavo A. R. Silva Acked-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-sched.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/host/ehci-sched.c b/drivers/usb/host/ehci-sched.c index 980a6b3b2da2..6bc6304672bc 100644 --- a/drivers/usb/host/ehci-sched.c +++ b/drivers/usb/host/ehci-sched.c @@ -1105,7 +1105,7 @@ iso_stream_init( addr |= epnum << 8; addr |= dev->devnum; stream->ps.usecs = HS_USECS_ISO(maxp); - think_time = dev->tt ? dev->tt->think_time : 0; + think_time = dev->tt->think_time; stream->ps.tt_usecs = NS_TO_US(think_time + usb_calc_bus_time( dev->speed, is_input, 1, maxp)); hs_transfers = max(1u, (maxp + 187) / 188); -- cgit v1.2.3 From ca2ef0d5cd0568d4ef60da6ff6bd7f69f317407a Mon Sep 17 00:00:00 2001 From: Geliang Tang Date: Sat, 13 May 2017 11:16:00 +0800 Subject: USB: iowarrior: use memdup_user Use memdup_user() helper instead of open-coding to simplify the code. Signed-off-by: Geliang Tang Reviewed-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/iowarrior.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/drivers/usb/misc/iowarrior.c b/drivers/usb/misc/iowarrior.c index 77569531b78a..816afadc4707 100644 --- a/drivers/usb/misc/iowarrior.c +++ b/drivers/usb/misc/iowarrior.c @@ -368,14 +368,9 @@ static ssize_t iowarrior_write(struct file *file, case USB_DEVICE_ID_CODEMERCS_IOWPV2: case USB_DEVICE_ID_CODEMERCS_IOW40: /* IOW24 and IOW40 use a synchronous call */ - buf = kmalloc(count, GFP_KERNEL); - if (!buf) { - retval = -ENOMEM; - goto exit; - } - if (copy_from_user(buf, user_buffer, count)) { - retval = -EFAULT; - kfree(buf); + buf = memdup_user(user_buffer, count); + if (IS_ERR(buf)) { + retval = PTR_ERR(buf); goto exit; } retval = usb_set_report(dev->interface, 2, 0, buf, count); -- cgit v1.2.3 From cdb55b39fab82b5d48c9a7aa0348268f07b993ed Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Wed, 17 May 2017 13:21:14 +0300 Subject: usb: dwc3: gadget: lazily map requests for DMA Some functions might want to have very, very long request queues. We can't make any assumptions about how many requests we *are* able to map, so instead of mapping requests early, let's map them late. This way, functions can queue as many requests as they'd like but we won't take DMA resources until they are needed. Also, we can now stop processing requests when we run out of DMA resources but still keep requests in the queue for late processing. Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/gadget.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 7113a9f53ca9..750364eb11e1 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -1099,6 +1099,17 @@ static void dwc3_prepare_trbs(struct dwc3_ep *dep) } list_for_each_entry_safe(req, n, &dep->pending_list, list) { + struct dwc3 *dwc = dep->dwc; + int ret; + + ret = usb_gadget_map_request_by_dev(dwc->sysdev, &req->request, + dep->direction); + if (ret) + return; + + req->sg = req->request.sg; + req->num_pending_sgs = req->request.num_mapped_sgs; + if (req->num_pending_sgs > 0) dwc3_prepare_one_trb_sg(dep, req); else @@ -1205,7 +1216,7 @@ static void dwc3_gadget_start_isoc(struct dwc3 *dwc, static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) { struct dwc3 *dwc = dep->dwc; - int ret; + int ret = 0; if (!dep->endpoint.desc) { dev_err(dwc->dev, "%s: can't queue to disabled endpoint\n", @@ -1229,14 +1240,6 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) trace_dwc3_ep_queue(req); - ret = usb_gadget_map_request_by_dev(dwc->sysdev, &req->request, - dep->direction); - if (ret) - return ret; - - req->sg = req->request.sg; - req->num_pending_sgs = req->request.num_mapped_sgs; - list_add_tail(&req->list, &dep->pending_list); /* -- cgit v1.2.3 From 0db56e43359c47ff184ceaf8b04b664d997bff88 Mon Sep 17 00:00:00 2001 From: Sekhar Nori Date: Wed, 17 May 2017 13:45:17 +0530 Subject: usb: gadget: f_uac2: calculate wMaxPacketSize before endpoint match Calculate wMaxPacketSize before endpoint matching the descriptor is found. This allows audio gadget to be used with controllers which have a shortage or unavailability of endpoints that can handle max packet size of 1023 (FS) or 1024 (HS). With this audio gadget can be used on TI's OMAP-L138 SoC which has a MUSB HS controller with endpoints having max packet size much less than 1023 or 1024. See mode_2_cfg in drivers/usb/musb/musb_core.c Signed-off-by: Sekhar Nori Signed-off-by: Felipe Balbi --- drivers/usb/gadget/function/f_uac2.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c index f6a0d3a1311b..5a7ba058d947 100644 --- a/drivers/usb/gadget/function/f_uac2.c +++ b/drivers/usb/gadget/function/f_uac2.c @@ -1065,6 +1065,12 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) agdev->as_in_intf = ret; agdev->as_in_alt = 0; + /* Calculate wMaxPacketSize according to audio bandwidth */ + set_ep_max_packet_size(uac2_opts, &fs_epin_desc, 1000, true); + set_ep_max_packet_size(uac2_opts, &fs_epout_desc, 1000, false); + set_ep_max_packet_size(uac2_opts, &hs_epin_desc, 8000, true); + set_ep_max_packet_size(uac2_opts, &hs_epout_desc, 8000, false); + agdev->out_ep = usb_ep_autoconfig(gadget, &fs_epout_desc); if (!agdev->out_ep) { dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); @@ -1080,12 +1086,6 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) uac2->p_prm.uac2 = uac2; uac2->c_prm.uac2 = uac2; - /* Calculate wMaxPacketSize according to audio bandwidth */ - set_ep_max_packet_size(uac2_opts, &fs_epin_desc, 1000, true); - set_ep_max_packet_size(uac2_opts, &fs_epout_desc, 1000, false); - set_ep_max_packet_size(uac2_opts, &hs_epin_desc, 8000, true); - set_ep_max_packet_size(uac2_opts, &hs_epout_desc, 8000, false); - hs_epout_desc.bEndpointAddress = fs_epout_desc.bEndpointAddress; hs_epin_desc.bEndpointAddress = fs_epin_desc.bEndpointAddress; -- cgit v1.2.3 From 05853ad68e66ab3dc7ed510005203672d7db292f Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Tue, 16 May 2017 15:11:58 -0300 Subject: usb: fix the comment with regards to DocBook The USB gadget documentation is not at DocBook anymore. The main file was converted to ReST, and stored at Documentation/driver-api/usb/gadget.rst, but there are still several plain text files related to gadget under Documentation/usb. So, be generic and just mention documentation without specifying where it is. Signed-off-by: Mauro Carvalho Chehab Signed-off-by: Felipe Balbi --- drivers/usb/gadget/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index c164d6b788c3..b3c879b75a39 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -41,7 +41,7 @@ menuconfig USB_GADGET don't have this kind of hardware (except maybe inside Linux PDAs). For more information, see and - the kernel DocBook documentation for this API. + the kernel documentation for this API. if USB_GADGET -- cgit v1.2.3 From 7d21114dc6a2d53babef43a84a8d8db2905d283d Mon Sep 17 00:00:00 2001 From: Baolin Wang Date: Fri, 5 May 2017 14:12:24 +0800 Subject: usb: phy: Introduce one extcon device into usb phy Usually usb phy need register one extcon device to get the connection notifications. It will remove some duplicate code if the extcon device is registered using common code instead of each phy driver having its own related extcon APIs. So we add one pointer of extcon device into usb phy structure, and some other helper functions to register extcon. Signed-off-by: Baolin Wang Signed-off-by: Felipe Balbi --- drivers/usb/phy/Kconfig | 6 +++--- drivers/usb/phy/phy.c | 57 +++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/usb/phy.h | 7 ++++++ 3 files changed, 67 insertions(+), 3 deletions(-) diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig index 3006f569c068..aff702c0eb9f 100644 --- a/drivers/usb/phy/Kconfig +++ b/drivers/usb/phy/Kconfig @@ -4,6 +4,7 @@ menu "USB Physical Layer drivers" config USB_PHY + select EXTCON def_bool n # @@ -109,7 +110,7 @@ config OMAP_OTG config TAHVO_USB tristate "Tahvo USB transceiver driver" - depends on MFD_RETU && EXTCON + depends on MFD_RETU depends on USB_GADGET || !USB_GADGET # if USB_GADGET=m, this can't be 'y' select USB_PHY help @@ -141,7 +142,6 @@ config USB_MSM_OTG depends on (USB || USB_GADGET) && (ARCH_QCOM || COMPILE_TEST) depends on USB_GADGET || !USB_GADGET # if USB_GADGET=m, this can't be 'y' depends on RESET_CONTROLLER - depends on EXTCON select USB_PHY help Enable this to support the USB OTG transceiver on Qualcomm chips. It @@ -155,7 +155,7 @@ config USB_MSM_OTG config USB_QCOM_8X16_PHY tristate "Qualcomm APQ8016/MSM8916 on-chip USB PHY controller support" depends on ARCH_QCOM || COMPILE_TEST - depends on RESET_CONTROLLER && EXTCON + depends on RESET_CONTROLLER select USB_PHY select USB_ULPI_VIEWPORT help diff --git a/drivers/usb/phy/phy.c b/drivers/usb/phy/phy.c index 98f75d2842b7..032f5afaad4b 100644 --- a/drivers/usb/phy/phy.c +++ b/drivers/usb/phy/phy.c @@ -100,6 +100,54 @@ static int devm_usb_phy_match(struct device *dev, void *res, void *match_data) return *phy == match_data; } +static int usb_add_extcon(struct usb_phy *x) +{ + int ret; + + if (of_property_read_bool(x->dev->of_node, "extcon")) { + x->edev = extcon_get_edev_by_phandle(x->dev, 0); + if (IS_ERR(x->edev)) + return PTR_ERR(x->edev); + + x->id_edev = extcon_get_edev_by_phandle(x->dev, 1); + if (IS_ERR(x->id_edev)) { + x->id_edev = NULL; + dev_info(x->dev, "No separate ID extcon device\n"); + } + + if (x->vbus_nb.notifier_call) { + ret = devm_extcon_register_notifier(x->dev, x->edev, + EXTCON_USB, + &x->vbus_nb); + if (ret < 0) { + dev_err(x->dev, + "register VBUS notifier failed\n"); + return ret; + } + } + + if (x->id_nb.notifier_call) { + struct extcon_dev *id_ext; + + if (x->id_edev) + id_ext = x->id_edev; + else + id_ext = x->edev; + + ret = devm_extcon_register_notifier(x->dev, id_ext, + EXTCON_USB_HOST, + &x->id_nb); + if (ret < 0) { + dev_err(x->dev, + "register ID notifier failed\n"); + return ret; + } + } + } + + return 0; +} + /** * devm_usb_get_phy - find the USB PHY * @dev - device that requests this phy @@ -388,6 +436,10 @@ int usb_add_phy(struct usb_phy *x, enum usb_phy_type type) return -EINVAL; } + ret = usb_add_extcon(x); + if (ret) + return ret; + ATOMIC_INIT_NOTIFIER_HEAD(&x->notifier); spin_lock_irqsave(&phy_lock, flags); @@ -422,12 +474,17 @@ int usb_add_phy_dev(struct usb_phy *x) { struct usb_phy_bind *phy_bind; unsigned long flags; + int ret; if (!x->dev) { dev_err(x->dev, "no device provided for PHY\n"); return -EINVAL; } + ret = usb_add_extcon(x); + if (ret) + return ret; + ATOMIC_INIT_NOTIFIER_HEAD(&x->notifier); spin_lock_irqsave(&phy_lock, flags); diff --git a/include/linux/usb/phy.h b/include/linux/usb/phy.h index 31a8068c42a5..299245105610 100644 --- a/include/linux/usb/phy.h +++ b/include/linux/usb/phy.h @@ -9,6 +9,7 @@ #ifndef __LINUX_USB_PHY_H #define __LINUX_USB_PHY_H +#include #include #include @@ -85,6 +86,12 @@ struct usb_phy { struct usb_phy_io_ops *io_ops; void __iomem *io_priv; + /* to support extcon device */ + struct extcon_dev *edev; + struct extcon_dev *id_edev; + struct notifier_block vbus_nb; + struct notifier_block id_nb; + /* for notification of usb_phy_events */ struct atomic_notifier_head notifier; -- cgit v1.2.3 From 78a467d8ff97e680dcd644e35e9025d472cfa65b Mon Sep 17 00:00:00 2001 From: Baolin Wang Date: Fri, 5 May 2017 14:12:25 +0800 Subject: usb: phy: phy-qcom-8x16-usb: Remove redundant extcon register/unregister Since usb phy core has added common code to register or unregister extcon device, then phy-qcom-8x16-usb driver does not need its own code to register/unregister extcon device, then remove them. Signed-off-by: Baolin Wang Signed-off-by: Felipe Balbi --- drivers/usb/phy/phy-qcom-8x16-usb.c | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/drivers/usb/phy/phy-qcom-8x16-usb.c b/drivers/usb/phy/phy-qcom-8x16-usb.c index fdf686398772..b6a83a5cbad3 100644 --- a/drivers/usb/phy/phy-qcom-8x16-usb.c +++ b/drivers/usb/phy/phy-qcom-8x16-usb.c @@ -69,9 +69,6 @@ struct phy_8x16 { struct reset_control *phy_reset; - struct extcon_dev *vbus_edev; - struct notifier_block vbus_notify; - struct gpio_desc *switch_gpio; struct notifier_block reboot_notify; }; @@ -131,7 +128,8 @@ static int phy_8x16_vbus_off(struct phy_8x16 *qphy) static int phy_8x16_vbus_notify(struct notifier_block *nb, unsigned long event, void *ptr) { - struct phy_8x16 *qphy = container_of(nb, struct phy_8x16, vbus_notify); + struct usb_phy *usb_phy = container_of(nb, struct usb_phy, vbus_nb); + struct phy_8x16 *qphy = container_of(usb_phy, struct phy_8x16, phy); if (event) phy_8x16_vbus_on(qphy); @@ -187,7 +185,7 @@ static int phy_8x16_init(struct usb_phy *phy) val = ULPI_PWR_OTG_COMP_DISABLE; usb_phy_io_write(phy, val, ULPI_SET(ULPI_PWR_CLK_MNG_REG)); - state = extcon_get_state(qphy->vbus_edev, EXTCON_USB); + state = extcon_get_state(qphy->phy.edev, EXTCON_USB); if (state) phy_8x16_vbus_on(qphy); else @@ -289,15 +287,13 @@ static int phy_8x16_probe(struct platform_device *pdev) phy->io_priv = qphy->regs + HSPHY_ULPI_VIEWPORT; phy->io_ops = &ulpi_viewport_access_ops; phy->type = USB_PHY_TYPE_USB2; + phy->vbus_nb.notifier_call = phy_8x16_vbus_notify; + phy->id_nb.notifier_call = NULL; ret = phy_8x16_read_devicetree(qphy); if (ret < 0) return ret; - qphy->vbus_edev = extcon_get_edev_by_phandle(phy->dev, 0); - if (IS_ERR(qphy->vbus_edev)) - return PTR_ERR(qphy->vbus_edev); - ret = clk_set_rate(qphy->core_clk, INT_MAX); if (ret < 0) dev_dbg(phy->dev, "Can't boost core clock\n"); @@ -315,12 +311,6 @@ static int phy_8x16_probe(struct platform_device *pdev) if (WARN_ON(ret)) goto off_clks; - qphy->vbus_notify.notifier_call = phy_8x16_vbus_notify; - ret = devm_extcon_register_notifier(&pdev->dev, qphy->vbus_edev, - EXTCON_USB, &qphy->vbus_notify); - if (ret < 0) - goto off_power; - ret = usb_add_phy_dev(&qphy->phy); if (ret) goto off_power; -- cgit v1.2.3 From d94e64cb24790b1df52524173330020ff950921d Mon Sep 17 00:00:00 2001 From: Baolin Wang Date: Fri, 5 May 2017 14:12:26 +0800 Subject: usb: phy: phy-msm-usb: Remove redundant extcon register/unregister Since usb phy core has added common code to register or unregister extcon device, then phy-msm-usb driver does not need its own code to register/unregister extcon device, then remove them. Signed-off-by: Baolin Wang Signed-off-by: Felipe Balbi --- drivers/usb/phy/phy-msm-usb.c | 85 +++++++++---------------------------------- 1 file changed, 18 insertions(+), 67 deletions(-) diff --git a/drivers/usb/phy/phy-msm-usb.c b/drivers/usb/phy/phy-msm-usb.c index 93d9aaad2994..8fb86a5f458e 100644 --- a/drivers/usb/phy/phy-msm-usb.c +++ b/drivers/usb/phy/phy-msm-usb.c @@ -145,17 +145,6 @@ struct msm_otg_platform_data { void (*setup_gpio)(enum usb_otg_state state); }; -/** - * struct msm_usb_cable - structure for exteternal connector cable - * state tracking - * @nb: hold event notification callback - * @conn: used for notification registration - */ -struct msm_usb_cable { - struct notifier_block nb; - struct extcon_dev *extcon; -}; - /** * struct msm_otg: OTG driver data. Shared by HCD and DCD. * @otg: USB OTG Transceiver structure. @@ -215,9 +204,6 @@ struct msm_otg { bool manual_pullup; - struct msm_usb_cable vbus; - struct msm_usb_cable id; - struct gpio_desc *switch_gpio; struct notifier_block reboot; }; @@ -1612,8 +1598,8 @@ MODULE_DEVICE_TABLE(of, msm_otg_dt_match); static int msm_otg_vbus_notifier(struct notifier_block *nb, unsigned long event, void *ptr) { - struct msm_usb_cable *vbus = container_of(nb, struct msm_usb_cable, nb); - struct msm_otg *motg = container_of(vbus, struct msm_otg, vbus); + struct usb_phy *usb_phy = container_of(nb, struct usb_phy, vbus_nb); + struct msm_otg *motg = container_of(usb_phy, struct msm_otg, phy); if (event) set_bit(B_SESS_VLD, &motg->inputs); @@ -1636,8 +1622,8 @@ static int msm_otg_vbus_notifier(struct notifier_block *nb, unsigned long event, static int msm_otg_id_notifier(struct notifier_block *nb, unsigned long event, void *ptr) { - struct msm_usb_cable *id = container_of(nb, struct msm_usb_cable, nb); - struct msm_otg *motg = container_of(id, struct msm_otg, id); + struct usb_phy *usb_phy = container_of(nb, struct usb_phy, id_nb); + struct msm_otg *motg = container_of(usb_phy, struct msm_otg, phy); if (event) clear_bit(ID, &motg->inputs); @@ -1652,7 +1638,6 @@ static int msm_otg_id_notifier(struct notifier_block *nb, unsigned long event, static int msm_otg_read_dt(struct platform_device *pdev, struct msm_otg *motg) { struct msm_otg_platform_data *pdata; - struct extcon_dev *ext_id, *ext_vbus; struct device_node *node = pdev->dev.of_node; struct property *prop; int len, ret, words; @@ -1708,54 +1693,6 @@ static int msm_otg_read_dt(struct platform_device *pdev, struct msm_otg *motg) if (IS_ERR(motg->switch_gpio)) return PTR_ERR(motg->switch_gpio); - ext_id = ERR_PTR(-ENODEV); - ext_vbus = ERR_PTR(-ENODEV); - if (of_property_read_bool(node, "extcon")) { - - /* Each one of them is not mandatory */ - ext_vbus = extcon_get_edev_by_phandle(&pdev->dev, 0); - if (IS_ERR(ext_vbus) && PTR_ERR(ext_vbus) != -ENODEV) - return PTR_ERR(ext_vbus); - - ext_id = extcon_get_edev_by_phandle(&pdev->dev, 1); - if (IS_ERR(ext_id) && PTR_ERR(ext_id) != -ENODEV) - return PTR_ERR(ext_id); - } - - if (!IS_ERR(ext_vbus)) { - motg->vbus.extcon = ext_vbus; - motg->vbus.nb.notifier_call = msm_otg_vbus_notifier; - ret = devm_extcon_register_notifier(&pdev->dev, ext_vbus, - EXTCON_USB, &motg->vbus.nb); - if (ret < 0) { - dev_err(&pdev->dev, "register VBUS notifier failed\n"); - return ret; - } - - ret = extcon_get_state(ext_vbus, EXTCON_USB); - if (ret) - set_bit(B_SESS_VLD, &motg->inputs); - else - clear_bit(B_SESS_VLD, &motg->inputs); - } - - if (!IS_ERR(ext_id)) { - motg->id.extcon = ext_id; - motg->id.nb.notifier_call = msm_otg_id_notifier; - ret = devm_extcon_register_notifier(&pdev->dev, ext_id, - EXTCON_USB_HOST, &motg->id.nb); - if (ret < 0) { - dev_err(&pdev->dev, "register ID notifier failed\n"); - return ret; - } - - ret = extcon_get_state(ext_id, EXTCON_USB_HOST); - if (ret) - clear_bit(ID, &motg->inputs); - else - set_bit(ID, &motg->inputs); - } - prop = of_find_property(node, "qcom,phy-init-sequence", &len); if (!prop || !len) return 0; @@ -1932,6 +1869,8 @@ static int msm_otg_probe(struct platform_device *pdev) phy->init = msm_phy_init; phy->notify_disconnect = msm_phy_notify_disconnect; phy->type = USB_PHY_TYPE_USB2; + phy->vbus_nb.notifier_call = msm_otg_vbus_notifier; + phy->id_nb.notifier_call = msm_otg_id_notifier; phy->io_ops = &msm_otg_io_ops; @@ -1947,6 +1886,18 @@ static int msm_otg_probe(struct platform_device *pdev) goto disable_ldo; } + ret = extcon_get_state(phy->edev, EXTCON_USB); + if (ret) + set_bit(B_SESS_VLD, &motg->inputs); + else + clear_bit(B_SESS_VLD, &motg->inputs); + + ret = extcon_get_state(phy->id_edev, EXTCON_USB_HOST); + if (ret) + clear_bit(ID, &motg->inputs); + else + set_bit(ID, &motg->inputs); + platform_set_drvdata(pdev, motg); device_init_wakeup(&pdev->dev, 1); -- cgit v1.2.3 From 53e720f332d546dee5a98a55f6e532355727bc4d Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Tue, 2 May 2017 11:37:22 -0500 Subject: usb: gadget: udc: add null check before pointer dereference Add null check before dereferencing dev->regs pointer inside net2280_led_shutdown() function. Addresses-Coverity-ID: 101783 Acked-by: Alan Stern Signed-off-by: Gustavo A. R. Silva Signed-off-by: Felipe Balbi --- drivers/usb/gadget/udc/net2280.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/usb/gadget/udc/net2280.c b/drivers/usb/gadget/udc/net2280.c index 6cf07857eaca..29c3d11a0e1d 100644 --- a/drivers/usb/gadget/udc/net2280.c +++ b/drivers/usb/gadget/udc/net2280.c @@ -3573,7 +3573,6 @@ static void net2280_remove(struct pci_dev *pdev) BUG_ON(dev->driver); /* then clean up the resources we allocated during probe() */ - net2280_led_shutdown(dev); if (dev->requests) { int i; for (i = 1; i < 5; i++) { @@ -3588,8 +3587,10 @@ static void net2280_remove(struct pci_dev *pdev) free_irq(pdev->irq, dev); if (dev->quirks & PLX_PCIE) pci_disable_msi(pdev); - if (dev->regs) + if (dev->regs) { + net2280_led_shutdown(dev); iounmap(dev->regs); + } if (dev->region) release_mem_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0)); -- cgit v1.2.3 From 65db7a0c9816370da5303500c7ba3754fa430720 Mon Sep 17 00:00:00 2001 From: William Wu Date: Wed, 19 Apr 2017 20:11:38 +0800 Subject: usb: dwc3: add disable u2mac linestate check quirk This patch adds a quirk to disable USB 2.0 MAC linestate check during HS transmit. Refer the dwc3 databook, we can use it for some special platforms if the linestate not reflect the expected line state(J) during transmission. When use this quirk, the controller implements a fixed 40-bit TxEndDelay after the packet is given on UTMI and ignores the linestate during the transmit of a token (during token-to-token and token-to-data IPGAP). On some rockchip platforms (e.g. rk3399), it requires to disable the u2mac linestate check to decrease the SSPLIT token to SETUP token inter-packet delay from 566ns to 466ns, and fix the issue that FS/LS devices not recognized if inserted through USB 3.0 HUB. Acked-by: Rob Herring Reviewed-by: Guenter Roeck Signed-off-by: William Wu Signed-off-by: Felipe Balbi --- Documentation/devicetree/bindings/usb/dwc3.txt | 2 ++ drivers/usb/dwc3/core.c | 20 ++++++++++++++------ drivers/usb/dwc3/core.h | 4 ++++ 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/Documentation/devicetree/bindings/usb/dwc3.txt b/Documentation/devicetree/bindings/usb/dwc3.txt index f658f394c2d3..52fb41046b34 100644 --- a/Documentation/devicetree/bindings/usb/dwc3.txt +++ b/Documentation/devicetree/bindings/usb/dwc3.txt @@ -45,6 +45,8 @@ Optional properties: a free-running PHY clock. - snps,dis-del-phy-power-chg-quirk: when set core will change PHY power from P0 to P1/P2/P3 without delay. + - snps,dis-tx-ipgap-linecheck-quirk: when set, disable u2mac linestate check + during HS transmit. - snps,is-utmi-l1-suspend: true when DWC3 asserts output signal utmi_l1_suspend_n, false when asserts utmi_sleep_n - snps,hird-threshold: HIRD threshold diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 455d89a1cd6d..9d5a67cc2645 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -796,13 +796,19 @@ static int dwc3_core_init(struct dwc3 *dwc) dwc3_writel(dwc->regs, DWC3_GUCTL2, reg); } - /* - * Enable hardware control of sending remote wakeup in HS when - * the device is in the L1 state. - */ - if (dwc->revision >= DWC3_REVISION_290A) { + if (dwc->revision >= DWC3_REVISION_250A) { reg = dwc3_readl(dwc->regs, DWC3_GUCTL1); - reg |= DWC3_GUCTL1_DEV_L1_EXIT_BY_HW; + + /* + * Enable hardware control of sending remote wakeup + * in HS when the device is in the L1 state. + */ + if (dwc->revision >= DWC3_REVISION_290A) + reg |= DWC3_GUCTL1_DEV_L1_EXIT_BY_HW; + + if (dwc->dis_tx_ipgap_linecheck_quirk) + reg |= DWC3_GUCTL1_TX_IPGAP_LINECHECK_DIS; + dwc3_writel(dwc->regs, DWC3_GUCTL1, reg); } @@ -1023,6 +1029,8 @@ static void dwc3_get_properties(struct dwc3 *dwc) "snps,dis-u2-freeclk-exists-quirk"); dwc->dis_del_phy_power_chg_quirk = device_property_read_bool(dev, "snps,dis-del-phy-power-chg-quirk"); + dwc->dis_tx_ipgap_linecheck_quirk = device_property_read_bool(dev, + "snps,dis-tx-ipgap-linecheck-quirk"); dwc->tx_de_emphasis_quirk = device_property_read_bool(dev, "snps,tx_de_emphasis_quirk"); diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 981c77f5628e..6f6294dbea9d 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -204,6 +204,7 @@ #define DWC3_GCTL_DSBLCLKGTNG BIT(0) /* Global User Control 1 Register */ +#define DWC3_GUCTL1_TX_IPGAP_LINECHECK_DIS BIT(28) #define DWC3_GUCTL1_DEV_L1_EXIT_BY_HW BIT(24) /* Global USB2 PHY Configuration Register */ @@ -850,6 +851,8 @@ struct dwc3_scratchpad_array { * provide a free-running PHY clock. * @dis_del_phy_power_chg_quirk: set if we disable delay phy power * change quirk. + * @dis_tx_ipgap_linecheck_quirk: set if we disable u2mac linestate + * check during HS transmit. * @tx_de_emphasis_quirk: set if we enable Tx de-emphasis quirk * @tx_de_emphasis: Tx de-emphasis value * 0 - -6dB de-emphasis @@ -1004,6 +1007,7 @@ struct dwc3 { unsigned dis_rxdet_inp3_quirk:1; unsigned dis_u2_freeclk_exists_quirk:1; unsigned dis_del_phy_power_chg_quirk:1; + unsigned dis_tx_ipgap_linecheck_quirk:1; unsigned tx_de_emphasis_quirk:1; unsigned tx_de_emphasis:2; -- cgit v1.2.3 From 0df6d8db355a527c605725ffacce4bd119533a7c Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 27 Apr 2017 12:11:18 +0300 Subject: usb: gadget: udc-xilinx: clean up a variable name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "ep->udc->lock" and "udc->lock" are the same thing. It confuses Smatch if we don't use the same name consistently. Reviewed-by: Sören Brinkmann Signed-off-by: Dan Carpenter Signed-off-by: Felipe Balbi --- drivers/usb/gadget/udc/udc-xilinx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/gadget/udc/udc-xilinx.c b/drivers/usb/gadget/udc/udc-xilinx.c index 588e2531b8b8..de207a90571e 100644 --- a/drivers/usb/gadget/udc/udc-xilinx.c +++ b/drivers/usb/gadget/udc/udc-xilinx.c @@ -1151,7 +1151,7 @@ static int xudc_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) break; } if (&req->usb_req != _req) { - spin_unlock_irqrestore(&ep->udc->lock, flags); + spin_unlock_irqrestore(&udc->lock, flags); return -EINVAL; } xudc_done(ep, req, -ECONNRESET); -- cgit v1.2.3 From c4a0bbbdb7f6e3c37fa6deb3ef28c5ed99da6175 Mon Sep 17 00:00:00 2001 From: Jisheng Zhang Date: Wed, 26 Apr 2017 16:59:34 +0800 Subject: usb: chipidea: properly handle host or gadget initialization failure If ci_hdrc_host_init() or ci_hdrc_gadget_init() returns error and the error != -ENXIO, as Peter pointed out, "it stands for initialization for host or gadget has failed", so we'd better return failure rather continue. And before destroying the otg, i.e ci_hdrc_otg_destroy(ci), we should also check ci->roles[CI_ROLE_GADGET]. Signed-off-by: Jisheng Zhang Signed-off-by: Peter Chen --- drivers/usb/chipidea/core.c | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 9e217b1361ea..4cad8a9b6f17 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -818,7 +818,7 @@ static inline void ci_role_destroy(struct ci_hdrc *ci) { ci_hdrc_gadget_destroy(ci); ci_hdrc_host_destroy(ci); - if (ci->is_otg) + if (ci->is_otg && ci->roles[CI_ROLE_GADGET]) ci_hdrc_otg_destroy(ci); } @@ -977,27 +977,35 @@ static int ci_hdrc_probe(struct platform_device *pdev) /* initialize role(s) before the interrupt is requested */ if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) { ret = ci_hdrc_host_init(ci); - if (ret) - dev_info(dev, "doesn't support host\n"); + if (ret) { + if (ret == -ENXIO) + dev_info(dev, "doesn't support host\n"); + else + goto deinit_phy; + } } if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL) { ret = ci_hdrc_gadget_init(ci); - if (ret) - dev_info(dev, "doesn't support gadget\n"); + if (ret) { + if (ret == -ENXIO) + dev_info(dev, "doesn't support gadget\n"); + else + goto deinit_host; + } } if (!ci->roles[CI_ROLE_HOST] && !ci->roles[CI_ROLE_GADGET]) { dev_err(dev, "no supported roles\n"); ret = -ENODEV; - goto deinit_phy; + goto deinit_gadget; } if (ci->is_otg && ci->roles[CI_ROLE_GADGET]) { ret = ci_hdrc_otg_init(ci); if (ret) { dev_err(dev, "init otg fails, ret = %d\n", ret); - goto stop; + goto deinit_gadget; } } @@ -1067,7 +1075,12 @@ static int ci_hdrc_probe(struct platform_device *pdev) remove_debug: dbg_remove_files(ci); stop: - ci_role_destroy(ci); + if (ci->is_otg && ci->roles[CI_ROLE_GADGET]) + ci_hdrc_otg_destroy(ci); +deinit_gadget: + ci_hdrc_gadget_destroy(ci); +deinit_host: + ci_hdrc_host_destroy(ci); deinit_phy: ci_usb_phy_exit(ci); ulpi_exit: -- cgit v1.2.3 From 4b309f1c4972c8f09e03ac64fc63510dbf5591a4 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Tue, 16 May 2017 11:47:42 -0400 Subject: USB: ene_usb6250: fix first command execution In the ene_usb6250 sub-driver for usb-storage, the ene_transport() routine is supposed to initialize the driver before executing the current command, if the initialization has not already been performed. However, a bug in the routine causes it to skip the command after doing the initialization. Also, the routine does not return an appropriate error code if either the initialization or the command fails. As a result of the first bug, the first command (a SCSI INQUIRY) is not carried out. The results can be seen in the system log, in the form of a warning message and empty or garbage INQUIRY data: Apr 18 22:40:08 notebook2 kernel: scsi host6: scsi scan: INQUIRY result too short (5), using 36 Apr 18 22:40:08 notebook2 kernel: scsi 6:0:0:0: Direct-Access PQ: 0 ANSI: 0 This patch fixes both errors. Signed-off-by: Alan Stern Reported-and-tested-by: Andreas Hartmann Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/ene_ub6250.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/usb/storage/ene_ub6250.c b/drivers/usb/storage/ene_ub6250.c index 369f3c24815a..80cd67841074 100644 --- a/drivers/usb/storage/ene_ub6250.c +++ b/drivers/usb/storage/ene_ub6250.c @@ -2280,21 +2280,22 @@ static int ms_scsi_irp(struct us_data *us, struct scsi_cmnd *srb) static int ene_transport(struct scsi_cmnd *srb, struct us_data *us) { - int result = 0; + int result = USB_STOR_XFER_GOOD; struct ene_ub6250_info *info = (struct ene_ub6250_info *)(us->extra); /*US_DEBUG(usb_stor_show_command(us, srb)); */ scsi_set_resid(srb, 0); - if (unlikely(!(info->SD_Status.Ready || info->MS_Status.Ready))) { + if (unlikely(!(info->SD_Status.Ready || info->MS_Status.Ready))) result = ene_init(us); - } else { + if (result == USB_STOR_XFER_GOOD) { + result = USB_STOR_TRANSPORT_ERROR; if (info->SD_Status.Ready) result = sd_scsi_irp(us, srb); if (info->MS_Status.Ready) result = ms_scsi_irp(us, srb); } - return 0; + return result; } static struct scsi_host_template ene_ub6250_host_template; -- cgit v1.2.3 From aa18c4b6e0e39bfb00af48734ec24bc189ac9909 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Tue, 16 May 2017 11:47:52 -0400 Subject: USB: ene_usb6250: fix SCSI residue overwriting In the ene_usb6250 sub-driver for usb-storage, the SCSI residue is not reported correctly. The residue is initialized to 0, but this value is overwritten whenever the driver sends firmware to the card reader before performing the current command. As a result, a valid READ or WRITE operation appears to have failed, causing the SCSI core to retry the command multiple times and eventually fail. This patch fixes the problem by resetting the SCSI residue to 0 after sending firmware to the device. Signed-off-by: Alan Stern Reported-and-tested-by: Andreas Hartmann Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/ene_ub6250.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/storage/ene_ub6250.c b/drivers/usb/storage/ene_ub6250.c index 80cd67841074..107018447551 100644 --- a/drivers/usb/storage/ene_ub6250.c +++ b/drivers/usb/storage/ene_ub6250.c @@ -1929,6 +1929,8 @@ static int ene_load_bincode(struct us_data *us, unsigned char flag) bcb->CDB[0] = 0xEF; result = ene_send_scsi_cmd(us, FDIR_WRITE, buf, 0); + if (us->srb != NULL) + scsi_set_resid(us->srb, 0); info->BIN_FLAG = flag; kfree(buf); -- cgit v1.2.3 From ce553bd103c161df2676ff201746bff8ca512715 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Tue, 16 May 2017 11:48:01 -0400 Subject: USB: ene_usb6250: implement REQUEST SENSE In the ene_usb6250 sub-driver for usb-storage, there is no support for the REQUEST SENSE command. This command is issued whenever a failure occurs, and without it the driver has no way to tell the SCSI core what the reason for the failure was. This patch adds a do_scsi_request_sense() routine to the driver. The new routine reports the error code stored by the previous command. Signed-off-by: Alan Stern Reported-and-tested-by: Andreas Hartmann Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/ene_ub6250.c | 41 ++++++++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/drivers/usb/storage/ene_ub6250.c b/drivers/usb/storage/ene_ub6250.c index 107018447551..44aca29ad6cc 100644 --- a/drivers/usb/storage/ene_ub6250.c +++ b/drivers/usb/storage/ene_ub6250.c @@ -95,12 +95,12 @@ static struct us_unusual_dev ene_ub6250_unusual_dev_list[] = { #define REG_HW_TRAP1 0xFF89 /* SRB Status */ -#define SS_SUCCESS 0x00 /* No Sense */ -#define SS_NOT_READY 0x02 -#define SS_MEDIUM_ERR 0x03 -#define SS_HW_ERR 0x04 -#define SS_ILLEGAL_REQUEST 0x05 -#define SS_UNIT_ATTENTION 0x06 +#define SS_SUCCESS 0x000000 /* No Sense */ +#define SS_NOT_READY 0x023A00 /* Medium not present */ +#define SS_MEDIUM_ERR 0x031100 /* Unrecovered read error */ +#define SS_HW_ERR 0x040800 /* Communication failure */ +#define SS_ILLEGAL_REQUEST 0x052000 /* Invalid command */ +#define SS_UNIT_ATTENTION 0x062900 /* Reset occurred */ /* ENE Load FW Pattern */ #define SD_INIT1_PATTERN 1 @@ -577,6 +577,22 @@ static int ene_send_scsi_cmd(struct us_data *us, u8 fDir, void *buf, int use_sg) return USB_STOR_TRANSPORT_GOOD; } +static int do_scsi_request_sense(struct us_data *us, struct scsi_cmnd *srb) +{ + struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; + unsigned char buf[18]; + + memset(buf, 0, 18); + buf[0] = 0x70; /* Current error */ + buf[2] = info->SrbStatus >> 16; /* Sense key */ + buf[7] = 10; /* Additional length */ + buf[12] = info->SrbStatus >> 8; /* ASC */ + buf[13] = info->SrbStatus; /* ASCQ */ + + usb_stor_set_xfer_buf(buf, sizeof(buf), srb); + return USB_STOR_TRANSPORT_GOOD; +} + static int sd_scsi_test_unit_ready(struct us_data *us, struct scsi_cmnd *srb) { struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; @@ -2212,11 +2228,13 @@ static int sd_scsi_irp(struct us_data *us, struct scsi_cmnd *srb) int result; struct ene_ub6250_info *info = (struct ene_ub6250_info *)us->extra; - info->SrbStatus = SS_SUCCESS; switch (srb->cmnd[0]) { case TEST_UNIT_READY: result = sd_scsi_test_unit_ready(us, srb); break; /* 0x00 */ + case REQUEST_SENSE: + result = do_scsi_request_sense(us, srb); + break; /* 0x03 */ case INQUIRY: result = sd_scsi_inquiry(us, srb); break; /* 0x12 */ @@ -2242,6 +2260,8 @@ static int sd_scsi_irp(struct us_data *us, struct scsi_cmnd *srb) result = USB_STOR_TRANSPORT_FAILED; break; } + if (result == USB_STOR_TRANSPORT_GOOD) + info->SrbStatus = SS_SUCCESS; return result; } @@ -2252,11 +2272,14 @@ static int ms_scsi_irp(struct us_data *us, struct scsi_cmnd *srb) { int result; struct ene_ub6250_info *info = (struct ene_ub6250_info *)us->extra; - info->SrbStatus = SS_SUCCESS; + switch (srb->cmnd[0]) { case TEST_UNIT_READY: result = ms_scsi_test_unit_ready(us, srb); break; /* 0x00 */ + case REQUEST_SENSE: + result = do_scsi_request_sense(us, srb); + break; /* 0x03 */ case INQUIRY: result = ms_scsi_inquiry(us, srb); break; /* 0x12 */ @@ -2277,6 +2300,8 @@ static int ms_scsi_irp(struct us_data *us, struct scsi_cmnd *srb) result = USB_STOR_TRANSPORT_FAILED; break; } + if (result == USB_STOR_TRANSPORT_GOOD) + info->SrbStatus = SS_SUCCESS; return result; } -- cgit v1.2.3 From f8efdabd14532c47e5420dc593c2a13028e42140 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Tue, 16 May 2017 11:48:10 -0400 Subject: USB: ene_usb6250: remove subroutine duplication In the ene_usb6250 sub-driver for usb-storage, the sd_scsi_inquiry() and ms_scsi_inquiry() subroutines (one meant for use with SD memory cards and the other for use with MS memory cards) are exact duplicates. This patch removes the duplication by creating a single do_scsi_inquiry() command and using it instead of the other two. Signed-off-by: Alan Stern Tested-by: Andreas Hartmann Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/ene_ub6250.c | 41 ++++++++++++++-------------------------- 1 file changed, 14 insertions(+), 27 deletions(-) diff --git a/drivers/usb/storage/ene_ub6250.c b/drivers/usb/storage/ene_ub6250.c index 44aca29ad6cc..a5ccdefed31e 100644 --- a/drivers/usb/storage/ene_ub6250.c +++ b/drivers/usb/storage/ene_ub6250.c @@ -593,6 +593,18 @@ static int do_scsi_request_sense(struct us_data *us, struct scsi_cmnd *srb) return USB_STOR_TRANSPORT_GOOD; } +static int do_scsi_inquiry(struct us_data *us, struct scsi_cmnd *srb) +{ + unsigned char data_ptr[36] = { + 0x00, 0x80, 0x02, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x55, + 0x53, 0x42, 0x32, 0x2E, 0x30, 0x20, 0x20, 0x43, 0x61, + 0x72, 0x64, 0x52, 0x65, 0x61, 0x64, 0x65, 0x72, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x31, 0x30, 0x30 }; + + usb_stor_set_xfer_buf(data_ptr, 36, srb); + return USB_STOR_TRANSPORT_GOOD; +} + static int sd_scsi_test_unit_ready(struct us_data *us, struct scsi_cmnd *srb) { struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; @@ -607,18 +619,6 @@ static int sd_scsi_test_unit_ready(struct us_data *us, struct scsi_cmnd *srb) return USB_STOR_TRANSPORT_GOOD; } -static int sd_scsi_inquiry(struct us_data *us, struct scsi_cmnd *srb) -{ - unsigned char data_ptr[36] = { - 0x00, 0x80, 0x02, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x55, - 0x53, 0x42, 0x32, 0x2E, 0x30, 0x20, 0x20, 0x43, 0x61, - 0x72, 0x64, 0x52, 0x65, 0x61, 0x64, 0x65, 0x72, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x31, 0x30, 0x30 }; - - usb_stor_set_xfer_buf(data_ptr, 36, srb); - return USB_STOR_TRANSPORT_GOOD; -} - static int sd_scsi_mode_sense(struct us_data *us, struct scsi_cmnd *srb) { struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; @@ -1462,19 +1462,6 @@ static int ms_scsi_test_unit_ready(struct us_data *us, struct scsi_cmnd *srb) return USB_STOR_TRANSPORT_GOOD; } -static int ms_scsi_inquiry(struct us_data *us, struct scsi_cmnd *srb) -{ - /* pr_info("MS_SCSI_Inquiry\n"); */ - unsigned char data_ptr[36] = { - 0x00, 0x80, 0x02, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x55, - 0x53, 0x42, 0x32, 0x2E, 0x30, 0x20, 0x20, 0x43, 0x61, - 0x72, 0x64, 0x52, 0x65, 0x61, 0x64, 0x65, 0x72, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x31, 0x30, 0x30}; - - usb_stor_set_xfer_buf(data_ptr, 36, srb); - return USB_STOR_TRANSPORT_GOOD; -} - static int ms_scsi_mode_sense(struct us_data *us, struct scsi_cmnd *srb) { struct ene_ub6250_info *info = (struct ene_ub6250_info *) us->extra; @@ -2236,7 +2223,7 @@ static int sd_scsi_irp(struct us_data *us, struct scsi_cmnd *srb) result = do_scsi_request_sense(us, srb); break; /* 0x03 */ case INQUIRY: - result = sd_scsi_inquiry(us, srb); + result = do_scsi_inquiry(us, srb); break; /* 0x12 */ case MODE_SENSE: result = sd_scsi_mode_sense(us, srb); @@ -2281,7 +2268,7 @@ static int ms_scsi_irp(struct us_data *us, struct scsi_cmnd *srb) result = do_scsi_request_sense(us, srb); break; /* 0x03 */ case INQUIRY: - result = ms_scsi_inquiry(us, srb); + result = do_scsi_inquiry(us, srb); break; /* 0x12 */ case MODE_SENSE: result = ms_scsi_mode_sense(us, srb); -- cgit v1.2.3 From 5fcf93795e6b72368cd98cd541b6d4bbe8804320 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Tue, 16 May 2017 11:48:20 -0400 Subject: USB: ene_usb6250: turn off the Removable flag In the ene_usb6250 sub-driver for usb-storage, the INQUIRY data returned by the driver indicates that the device has removable media. While this is technically correct (memory cards can be removed from the reader), it is not useful because the device automatically disconnects itself from the USB bus when no media is present. In addition, the driver does not support the PREVENT-ALLOW MEDIUM REMOVAL and START STOP UNIT commands, and this can cause user-interface frameworks to get confused when the user asks for the card to be removed or ejected. This patch fixes the problem by changing the INQUIRY data to specify non-removable media; in practice this value works much better. Signed-off-by: Alan Stern Reported-and-tested-by: Andreas Hartmann Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/ene_ub6250.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/storage/ene_ub6250.c b/drivers/usb/storage/ene_ub6250.c index a5ccdefed31e..22b850a1ce06 100644 --- a/drivers/usb/storage/ene_ub6250.c +++ b/drivers/usb/storage/ene_ub6250.c @@ -596,7 +596,7 @@ static int do_scsi_request_sense(struct us_data *us, struct scsi_cmnd *srb) static int do_scsi_inquiry(struct us_data *us, struct scsi_cmnd *srb) { unsigned char data_ptr[36] = { - 0x00, 0x80, 0x02, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x55, + 0x00, 0x00, 0x02, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x55, 0x53, 0x42, 0x32, 0x2E, 0x30, 0x20, 0x20, 0x43, 0x61, 0x72, 0x64, 0x52, 0x65, 0x61, 0x64, 0x65, 0x72, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x31, 0x30, 0x30 }; -- cgit v1.2.3 From 3f5071a8b07b697993a16bbe9cbc8c876ba60048 Mon Sep 17 00:00:00 2001 From: Markus Elfring Date: Sun, 23 Apr 2017 20:54:11 +0200 Subject: extcon: Use devm_kcalloc() in extcon_dev_register() A multiplication for the size determination of a memory allocation indicated that an array data structure should be processed. Thus use the corresponding function "devm_kcalloc". Signed-off-by: Markus Elfring Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/extcon/extcon.c b/drivers/extcon/extcon.c index f422a78ba342..acb847bc1619 100644 --- a/drivers/extcon/extcon.c +++ b/drivers/extcon/extcon.c @@ -1252,9 +1252,8 @@ int extcon_dev_register(struct extcon_dev *edev) } spin_lock_init(&edev->lock); - - edev->nh = devm_kzalloc(&edev->dev, - sizeof(*edev->nh) * edev->max_supported, GFP_KERNEL); + edev->nh = devm_kcalloc(&edev->dev, edev->max_supported, + sizeof(*edev->nh), GFP_KERNEL); if (!edev->nh) { ret = -ENOMEM; goto err_dev; -- cgit v1.2.3 From 826a47e978303989a12d318b3539feefb127bbe6 Mon Sep 17 00:00:00 2001 From: Markus Elfring Date: Sun, 23 Apr 2017 22:15:20 +0200 Subject: extcon: Fix a typo in three comment lines Adjust three words in this description for a function. Signed-off-by: Markus Elfring Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/extcon/extcon.c b/drivers/extcon/extcon.c index acb847bc1619..8eccf7b14937 100644 --- a/drivers/extcon/extcon.c +++ b/drivers/extcon/extcon.c @@ -964,12 +964,12 @@ EXPORT_SYMBOL_GPL(extcon_unregister_notifier); /** * extcon_register_notifier_all() - Register a notifier block for all connectors - * @edev: the extcon device that has the external connecotr. + * @edev: the extcon device that has the external connector. * @nb: a notifier block to be registered. * - * This fucntion registers a notifier block in order to receive the state + * This function registers a notifier block in order to receive the state * change of all supported external connectors from extcon device. - * And The second parameter given to the callback of nb (val) is + * And the second parameter given to the callback of nb (val) is * the current state and third parameter is the edev pointer. * * Returns 0 if success or error number if fail -- cgit v1.2.3 From cf5459a9e2f3033bf16773ef5fc32ad0243a513b Mon Sep 17 00:00:00 2001 From: Markus Elfring Date: Sun, 23 Apr 2017 22:44:19 +0200 Subject: extcon: arizona: Use devm_kcalloc() in arizona_extcon_get_micd_configs() * A multiplication for the size determination of a memory allocation indicated that an array data structure should be processed. Thus use the corresponding function "devm_kcalloc". * Replace the specification of a data structure by a pointer dereference to make the corresponding size determination a bit safer according to the Linux coding style convention. Signed-off-by: Markus Elfring Reviewed-by: Charles Keepax Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-arizona.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index e2d78cd7030d..f84da4a17724 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -1271,9 +1271,7 @@ static int arizona_extcon_get_micd_configs(struct device *dev, goto out; nconfs /= entries_per_config; - - micd_configs = devm_kzalloc(dev, - nconfs * sizeof(struct arizona_micd_range), + micd_configs = devm_kcalloc(dev, nconfs, sizeof(*micd_configs), GFP_KERNEL); if (!micd_configs) { ret = -ENOMEM; -- cgit v1.2.3 From a781a7d646ada7a69da4d2a804fe3405f2606b17 Mon Sep 17 00:00:00 2001 From: Peter Robinson Date: Sat, 20 May 2017 17:45:07 +0100 Subject: extcon: qcom-spmi-misc: add dependency on ARCH_QCOM Depend on the architecture the device actuall is in, also add dep on the compile test to ensure continued coverage. Signed-off-by: Peter Robinson Signed-off-by: Chanwoo Choi --- drivers/extcon/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index 32f2dc8e4702..6d50071f07d5 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -115,6 +115,7 @@ config EXTCON_PALMAS config EXTCON_QCOM_SPMI_MISC tristate "Qualcomm USB extcon support" + depends on ARCH_QCOM || COMPILE_TEST help Say Y here to enable SPMI PMIC based USB cable detection support on Qualcomm PMICs such as PM8941. -- cgit v1.2.3 From 4642d34a439f80e16af0d56ed6258a33abae257a Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Tue, 23 May 2017 10:44:05 +1000 Subject: usb/uhci: Add support for Aspeed BMC SoCs The Aspeed 2400/2500 families have a variant of UHCI which requires some quirks to the driver to work: - The register offsets are different. We add a remapping helper. - All accesses have to be done via 32-bit loads and stores. We force all accessors to use readl/writel. This is of no consequence for reads as we never read "in the middle" of a register. For writes it also works fine as the registers only actually implement the bits we try to write (16-bit for the registers accessed with writew and 8-bit for the register accessed with writeb), so always using a 32-bit write will have no negative effect. We never do partial writes. - The resume detect interrupt is broken - The number of ports is (optionally) provided via the device-tree Signed-off-by: Benjamin Herrenschmidt Acked-by: Alan Stern -- v2. Remove the bulk of the #ifdef's drivers/usb/host/Kconfig | 6 ++++- drivers/usb/host/uhci-hcd.c | 17 +++++++++++--- drivers/usb/host/uhci-hcd.h | 51 ++++++++++++++++++++++++++++++++++++++++ drivers/usb/host/uhci-platform.c | 22 ++++++++++++++++- 4 files changed, 91 insertions(+), 5 deletions(-) Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/Kconfig | 6 ++++- drivers/usb/host/uhci-hcd.c | 17 +++++++++++--- drivers/usb/host/uhci-hcd.h | 51 ++++++++++++++++++++++++++++++++++++++++ drivers/usb/host/uhci-platform.c | 22 ++++++++++++++++- 4 files changed, 91 insertions(+), 5 deletions(-) diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index ababb91d654a..70d32a0cacab 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -627,7 +627,11 @@ config USB_UHCI_SUPPORT_NON_PCI_HC config USB_UHCI_PLATFORM bool - default y if ARCH_VT8500 + default y if (ARCH_VT8500 || ARCH_ASPEED) + +config USB_UHCI_ASPEED + bool + default y if ARCH_ASPEED config USB_UHCI_BIG_ENDIAN_MMIO bool diff --git a/drivers/usb/host/uhci-hcd.c b/drivers/usb/host/uhci-hcd.c index 94b150196d4f..c3267a78c94e 100644 --- a/drivers/usb/host/uhci-hcd.c +++ b/drivers/usb/host/uhci-hcd.c @@ -265,9 +265,13 @@ static void configure_hc(struct uhci_hcd *uhci) static int resume_detect_interrupts_are_broken(struct uhci_hcd *uhci) { - /* If we have to ignore overcurrent events then almost by definition - * we can't depend on resume-detect interrupts. */ - if (ignore_oc) + /* + * If we have to ignore overcurrent events then almost by definition + * we can't depend on resume-detect interrupts. + * + * Those interrupts also don't seem to work on ASpeed SoCs. + */ + if (ignore_oc || uhci_is_aspeed(uhci)) return 1; return uhci->resume_detect_interrupts_are_broken ? @@ -384,6 +388,13 @@ static void start_rh(struct uhci_hcd *uhci) { uhci->is_stopped = 0; + /* + * Clear stale status bits on Aspeed as we get a stale HCH + * which causes problems later on + */ + if (uhci_is_aspeed(uhci)) + uhci_writew(uhci, uhci_readw(uhci, USBSTS), USBSTS); + /* Mark it configured and running with a 64-byte max packet. * All interrupts are enabled, even though RESUME won't do anything. */ diff --git a/drivers/usb/host/uhci-hcd.h b/drivers/usb/host/uhci-hcd.h index 7fa318a3091d..91b22b2ea3aa 100644 --- a/drivers/usb/host/uhci-hcd.h +++ b/drivers/usb/host/uhci-hcd.h @@ -48,6 +48,8 @@ /* USB port status and control registers */ #define USBPORTSC1 16 #define USBPORTSC2 18 +#define USBPORTSC3 20 +#define USBPORTSC4 22 #define USBPORTSC_CCS 0x0001 /* Current Connect Status * ("device present") */ #define USBPORTSC_CSC 0x0002 /* Connect Status Change */ @@ -427,6 +429,7 @@ struct uhci_hcd { unsigned int wait_for_hp:1; /* Wait for HP port reset */ unsigned int big_endian_mmio:1; /* Big endian registers */ unsigned int big_endian_desc:1; /* Big endian descriptors */ + unsigned int is_aspeed:1; /* Aspeed impl. workarounds */ /* Support for port suspend/resume/reset */ unsigned long port_c_suspend; /* Bit-arrays of ports */ @@ -490,6 +493,12 @@ struct urb_priv { #define PCI_VENDOR_ID_GENESYS 0x17a0 #define PCI_DEVICE_ID_GL880S_UHCI 0x8083 +/* Aspeed SoC needs some quirks */ +static inline bool uhci_is_aspeed(const struct uhci_hcd *uhci) +{ + return IS_ENABLED(CONFIG_USB_UHCI_ASPEED) && uhci->is_aspeed; +} + /* * Functions used to access controller registers. The UCHI spec says that host * controller I/O registers are mapped into PCI I/O space. For non-PCI hosts @@ -545,10 +554,42 @@ static inline void uhci_writeb(const struct uhci_hcd *uhci, u8 val, int reg) #define uhci_big_endian_mmio(u) 0 #endif +static inline int uhci_aspeed_reg(unsigned int reg) +{ + switch (reg) { + case USBCMD: + return 00; + case USBSTS: + return 0x04; + case USBINTR: + return 0x08; + case USBFRNUM: + return 0x80; + case USBFLBASEADD: + return 0x0c; + case USBSOF: + return 0x84; + case USBPORTSC1: + return 0x88; + case USBPORTSC2: + return 0x8c; + case USBPORTSC3: + return 0x90; + case USBPORTSC4: + return 0x94; + default: + pr_warn("UHCI: Unsupported register 0x%02x on Aspeed\n", reg); + /* Return an unimplemented register */ + return 0x10; + } +} + static inline u32 uhci_readl(const struct uhci_hcd *uhci, int reg) { if (uhci_has_pci_registers(uhci)) return inl(uhci->io_addr + reg); + else if (uhci_is_aspeed(uhci)) + return readl(uhci->regs + uhci_aspeed_reg(reg)); #ifdef CONFIG_USB_UHCI_BIG_ENDIAN_MMIO else if (uhci_big_endian_mmio(uhci)) return readl_be(uhci->regs + reg); @@ -561,6 +602,8 @@ static inline void uhci_writel(const struct uhci_hcd *uhci, u32 val, int reg) { if (uhci_has_pci_registers(uhci)) outl(val, uhci->io_addr + reg); + else if (uhci_is_aspeed(uhci)) + writel(val, uhci->regs + uhci_aspeed_reg(reg)); #ifdef CONFIG_USB_UHCI_BIG_ENDIAN_MMIO else if (uhci_big_endian_mmio(uhci)) writel_be(val, uhci->regs + reg); @@ -573,6 +616,8 @@ static inline u16 uhci_readw(const struct uhci_hcd *uhci, int reg) { if (uhci_has_pci_registers(uhci)) return inw(uhci->io_addr + reg); + else if (uhci_is_aspeed(uhci)) + return readl(uhci->regs + uhci_aspeed_reg(reg)); #ifdef CONFIG_USB_UHCI_BIG_ENDIAN_MMIO else if (uhci_big_endian_mmio(uhci)) return readw_be(uhci->regs + reg); @@ -585,6 +630,8 @@ static inline void uhci_writew(const struct uhci_hcd *uhci, u16 val, int reg) { if (uhci_has_pci_registers(uhci)) outw(val, uhci->io_addr + reg); + else if (uhci_is_aspeed(uhci)) + writel(val, uhci->regs + uhci_aspeed_reg(reg)); #ifdef CONFIG_USB_UHCI_BIG_ENDIAN_MMIO else if (uhci_big_endian_mmio(uhci)) writew_be(val, uhci->regs + reg); @@ -597,6 +644,8 @@ static inline u8 uhci_readb(const struct uhci_hcd *uhci, int reg) { if (uhci_has_pci_registers(uhci)) return inb(uhci->io_addr + reg); + else if (uhci_is_aspeed(uhci)) + return readl(uhci->regs + uhci_aspeed_reg(reg)); #ifdef CONFIG_USB_UHCI_BIG_ENDIAN_MMIO else if (uhci_big_endian_mmio(uhci)) return readb_be(uhci->regs + reg); @@ -609,6 +658,8 @@ static inline void uhci_writeb(const struct uhci_hcd *uhci, u8 val, int reg) { if (uhci_has_pci_registers(uhci)) outb(val, uhci->io_addr + reg); + else if (uhci_is_aspeed(uhci)) + writel(val, uhci->regs + uhci_aspeed_reg(reg)); #ifdef CONFIG_USB_UHCI_BIG_ENDIAN_MMIO else if (uhci_big_endian_mmio(uhci)) writeb_be(val, uhci->regs + reg); diff --git a/drivers/usb/host/uhci-platform.c b/drivers/usb/host/uhci-platform.c index 32a6f3d8deec..1b4e086c33a0 100644 --- a/drivers/usb/host/uhci-platform.c +++ b/drivers/usb/host/uhci-platform.c @@ -15,7 +15,9 @@ static int uhci_platform_init(struct usb_hcd *hcd) { struct uhci_hcd *uhci = hcd_to_uhci(hcd); - uhci->rh_numports = uhci_count_ports(hcd); + /* Probe number of ports if not already provided by DT */ + if (!uhci->rh_numports) + uhci->rh_numports = uhci_count_ports(hcd); /* Set up pointers to to generic functions */ uhci->reset_hc = uhci_generic_reset_hc; @@ -63,6 +65,7 @@ static const struct hc_driver uhci_platform_hc_driver = { static int uhci_hcd_platform_probe(struct platform_device *pdev) { + struct device_node *np = pdev->dev.of_node; struct usb_hcd *hcd; struct uhci_hcd *uhci; struct resource *res; @@ -98,6 +101,23 @@ static int uhci_hcd_platform_probe(struct platform_device *pdev) uhci->regs = hcd->regs; + /* Grab some things from the device-tree */ + if (np) { + u32 num_ports; + + if (of_property_read_u32(np, "#ports", &num_ports) == 0) { + uhci->rh_numports = num_ports; + dev_info(&pdev->dev, + "Detected %d ports from device-tree\n", + num_ports); + } + if (of_device_is_compatible(np, "aspeed,ast2400-uhci") || + of_device_is_compatible(np, "aspeed,ast2500-uhci")) { + uhci->is_aspeed = 1; + dev_info(&pdev->dev, + "Enabled Aspeed implementation workarounds\n"); + } + } ret = usb_add_hcd(hcd, pdev->resource[1].start, IRQF_SHARED); if (ret) goto err_rmr; -- cgit v1.2.3 From 9b4632ef3ff2097495e3724e7c4c1a307cbcfce4 Mon Sep 17 00:00:00 2001 From: Masahiro Yamada Date: Sun, 21 May 2017 02:05:31 +0900 Subject: usb: mtu3: cleanup with list_first_entry_or_null() The combo of list_empty() and list_first_entry() can be replaced with list_first_entry_or_null(). Signed-off-by: Masahiro Yamada Signed-off-by: Greg Kroah-Hartman --- drivers/usb/mtu3/mtu3.h | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/usb/mtu3/mtu3.h b/drivers/usb/mtu3/mtu3.h index aa6fd6a51221..7b6dc23d77e9 100644 --- a/drivers/usb/mtu3/mtu3.h +++ b/drivers/usb/mtu3/mtu3.h @@ -356,12 +356,8 @@ static inline struct mtu3_ep *to_mtu3_ep(struct usb_ep *ep) static inline struct mtu3_request *next_request(struct mtu3_ep *mep) { - struct list_head *queue = &mep->req_list; - - if (list_empty(queue)) - return NULL; - - return list_first_entry(queue, struct mtu3_request, list); + return list_first_entry_or_null(&mep->req_list, struct mtu3_request, + list); } static inline void mtu3_writel(void __iomem *base, u32 offset, u32 data) -- cgit v1.2.3 From 7f7c3cde613d15bbd6179dc56a96c2de5d0b51c8 Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Fri, 19 May 2017 03:22:41 -0500 Subject: uwb: i1480: add missing goto Add missing goto. Addresses-Coverity-ID: 1226913 Signed-off-by: Gustavo A. R. Silva Signed-off-by: Greg Kroah-Hartman --- drivers/uwb/i1480/dfu/phy.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/uwb/i1480/dfu/phy.c b/drivers/uwb/i1480/dfu/phy.c index 3b1a87de8e63..1ac8526bb689 100644 --- a/drivers/uwb/i1480/dfu/phy.c +++ b/drivers/uwb/i1480/dfu/phy.c @@ -126,6 +126,7 @@ int i1480_mpi_read(struct i1480 *i1480, u8 *data, u16 srcaddr, size_t size) dev_err(i1480->dev, "MPI-READ: command execution failed: %d\n", reply->bResultCode); result = -EIO; + goto out; } for (cnt = 0; cnt < size; cnt++) { if (reply->data[cnt].page != (srcaddr + cnt) >> 8) -- cgit v1.2.3 From 4cd4475effd8fb6fd021fc7c881b4aa74480fa9e Mon Sep 17 00:00:00 2001 From: Maksim Salau Date: Mon, 24 Apr 2017 12:59:53 +0300 Subject: USB: serial: upd78f0730: make constants static Some local constants don't change from call to call and are good candidates to become static. This will prevent copying of these constants to stack during runtime. Signed-off-by: Maksim Salau Signed-off-by: Johan Hovold --- drivers/usb/serial/upd78f0730.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/usb/serial/upd78f0730.c b/drivers/usb/serial/upd78f0730.c index a028dd2310c9..6819a3486e5d 100644 --- a/drivers/usb/serial/upd78f0730.c +++ b/drivers/usb/serial/upd78f0730.c @@ -288,7 +288,7 @@ static void upd78f0730_dtr_rts(struct usb_serial_port *port, int on) static speed_t upd78f0730_get_baud_rate(struct tty_struct *tty) { const speed_t baud_rate = tty_get_baud_rate(tty); - const speed_t supported[] = { + static const speed_t supported[] = { 0, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 153600 }; int i; @@ -384,7 +384,7 @@ static void upd78f0730_set_termios(struct tty_struct *tty, static int upd78f0730_open(struct tty_struct *tty, struct usb_serial_port *port) { - struct upd78f0730_open_close request = { + static const struct upd78f0730_open_close request = { .opcode = UPD78F0730_CMD_OPEN_CLOSE, .state = UPD78F0730_PORT_OPEN }; @@ -402,7 +402,7 @@ static int upd78f0730_open(struct tty_struct *tty, struct usb_serial_port *port) static void upd78f0730_close(struct usb_serial_port *port) { - struct upd78f0730_open_close request = { + static const struct upd78f0730_open_close request = { .opcode = UPD78F0730_CMD_OPEN_CLOSE, .state = UPD78F0730_PORT_CLOSE }; -- cgit v1.2.3 From 706a3b69955816bd0f3591be738db64cba74b674 Mon Sep 17 00:00:00 2001 From: Vivek Gautam Date: Thu, 11 May 2017 12:17:40 +0530 Subject: phy: qcom-usb: Remove unused ulpi phy header Ulpi phy header is not used for anything. Remove the same from qcom-hs and qcom-hsic phy drivers. Signed-off-by: Vivek Gautam Suggested-by: Stephen Boyd Cc: Kishon Vijay Abraham I Cc: linux-arm-kernel@lists.infradead.org Cc: linux-arm-msm@vger.kernel.org Cc: linux-kernel@vger.kernel.org Cc: linux-usb@vger.kernel.org Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/phy-qcom-usb-hs.c | 3 +-- drivers/phy/phy-qcom-usb-hsic.c | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/phy/phy-qcom-usb-hs.c b/drivers/phy/phy-qcom-usb-hs.c index 94dfbfd739c3..4b20abc3ae2f 100644 --- a/drivers/phy/phy-qcom-usb-hs.c +++ b/drivers/phy/phy-qcom-usb-hs.c @@ -11,12 +11,11 @@ #include #include #include +#include #include #include #include -#include "ulpi_phy.h" - #define ULPI_PWR_CLK_MNG_REG 0x88 # define ULPI_PWR_OTG_COMP_DISABLE BIT(0) diff --git a/drivers/phy/phy-qcom-usb-hsic.c b/drivers/phy/phy-qcom-usb-hsic.c index 47690f9945b9..c110563a73cb 100644 --- a/drivers/phy/phy-qcom-usb-hsic.c +++ b/drivers/phy/phy-qcom-usb-hsic.c @@ -8,13 +8,12 @@ #include #include #include +#include #include #include #include #include -#include "ulpi_phy.h" - #define ULPI_HSIC_CFG 0x30 #define ULPI_HSIC_IO_CAL 0x33 -- cgit v1.2.3 From 858edde001e14f070d0fff347fb56c6c79e15312 Mon Sep 17 00:00:00 2001 From: Vivek Gautam Date: Thu, 11 May 2017 12:17:41 +0530 Subject: phy: Move ULPI phy header out of drivers to include path Although ULPI phy is currently being used by tusb1210, there can be other consumers too in future. So move this to the includes path for phy. Signed-off-by: Vivek Gautam Cc: Stephen Boyd Cc: Heikki Krogerus Cc: Kishon Vijay Abraham I Cc: linux-arm-kernel@lists.infradead.org Cc: linux-kernel@vger.kernel.org Cc: linux-omap@vger.kernel.org Cc: linux-usb@vger.kernel.org Acked-by: Heikki Krogerus Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/phy-tusb1210.c | 3 +-- drivers/phy/ulpi_phy.h | 31 ------------------------------- include/linux/phy/ulpi_phy.h | 31 +++++++++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 33 deletions(-) delete mode 100644 drivers/phy/ulpi_phy.h create mode 100644 include/linux/phy/ulpi_phy.h diff --git a/drivers/phy/phy-tusb1210.c b/drivers/phy/phy-tusb1210.c index 4f6d5e71507d..bb3fb031c478 100644 --- a/drivers/phy/phy-tusb1210.c +++ b/drivers/phy/phy-tusb1210.c @@ -12,8 +12,7 @@ #include #include #include - -#include "ulpi_phy.h" +#include #define TUSB1210_VENDOR_SPECIFIC2 0x80 #define TUSB1210_VENDOR_SPECIFIC2_IHSTX_SHIFT 0 diff --git a/drivers/phy/ulpi_phy.h b/drivers/phy/ulpi_phy.h deleted file mode 100644 index f2ebe490a4bc..000000000000 --- a/drivers/phy/ulpi_phy.h +++ /dev/null @@ -1,31 +0,0 @@ -#include - -/** - * Helper that registers PHY for a ULPI device and adds a lookup for binding it - * and it's controller, which is always the parent. - */ -static inline struct phy -*ulpi_phy_create(struct ulpi *ulpi, const struct phy_ops *ops) -{ - struct phy *phy; - int ret; - - phy = phy_create(&ulpi->dev, NULL, ops); - if (IS_ERR(phy)) - return phy; - - ret = phy_create_lookup(phy, "usb2-phy", dev_name(ulpi->dev.parent)); - if (ret) { - phy_destroy(phy); - return ERR_PTR(ret); - } - - return phy; -} - -/* Remove a PHY that was created with ulpi_phy_create() and it's lookup. */ -static inline void ulpi_phy_destroy(struct ulpi *ulpi, struct phy *phy) -{ - phy_remove_lookup(phy, "usb2-phy", dev_name(ulpi->dev.parent)); - phy_destroy(phy); -} diff --git a/include/linux/phy/ulpi_phy.h b/include/linux/phy/ulpi_phy.h new file mode 100644 index 000000000000..f2ebe490a4bc --- /dev/null +++ b/include/linux/phy/ulpi_phy.h @@ -0,0 +1,31 @@ +#include + +/** + * Helper that registers PHY for a ULPI device and adds a lookup for binding it + * and it's controller, which is always the parent. + */ +static inline struct phy +*ulpi_phy_create(struct ulpi *ulpi, const struct phy_ops *ops) +{ + struct phy *phy; + int ret; + + phy = phy_create(&ulpi->dev, NULL, ops); + if (IS_ERR(phy)) + return phy; + + ret = phy_create_lookup(phy, "usb2-phy", dev_name(ulpi->dev.parent)); + if (ret) { + phy_destroy(phy); + return ERR_PTR(ret); + } + + return phy; +} + +/* Remove a PHY that was created with ulpi_phy_create() and it's lookup. */ +static inline void ulpi_phy_destroy(struct ulpi *ulpi, struct phy *phy) +{ + phy_remove_lookup(phy, "usb2-phy", dev_name(ulpi->dev.parent)); + phy_destroy(phy); +} -- cgit v1.2.3 From 0b56e9a7e8358e59b21d8a425e463072bfae523c Mon Sep 17 00:00:00 2001 From: Vivek Gautam Date: Thu, 11 May 2017 12:17:42 +0530 Subject: phy: Group vendor specific phy drivers Adding vendor specific directories in phy to group phy drivers under their respective vendor umbrella. Also updated the MAINTAINERS file to reflect the correct directory structure for phy drivers. Signed-off-by: Vivek Gautam Acked-by: Heiko Stuebner Acked-by: Viresh Kumar Acked-by: Krzysztof Kozlowski Acked-by: Yoshihiro Shimoda Reviewed-by: Jaehoon Chung Cc: Kishon Vijay Abraham I Cc: David S. Miller Cc: Geert Uytterhoeven Cc: Yoshihiro Shimoda Cc: Guenter Roeck Cc: Heiko Stuebner Cc: Viresh Kumar Cc: Maxime Ripard Cc: Chen-Yu Tsai Cc: Sylwester Nawrocki Cc: Krzysztof Kozlowski Cc: Jaehoon Chung Cc: Stephen Boyd Cc: Martin Blumenstingl Cc: linux-arm-kernel@lists.infradead.org Cc: linux-arm-msm@vger.kernel.org Cc: linux-kernel@vger.kernel.org Cc: linux-omap@vger.kernel.org Cc: linux-renesas-soc@vger.kernel.org Cc: linux-rockchip@lists.infradead.org Cc: linux-samsung-soc@vger.kernel.org Cc: linux-usb@vger.kernel.org Signed-off-by: Kishon Vijay Abraham I --- MAINTAINERS | 18 +- drivers/phy/Kconfig | 491 +--------- drivers/phy/Makefile | 70 +- drivers/phy/allwinner/Kconfig | 31 + drivers/phy/allwinner/Makefile | 2 + drivers/phy/allwinner/phy-sun4i-usb.c | 891 +++++++++++++++++ drivers/phy/allwinner/phy-sun9i-usb.c | 202 ++++ drivers/phy/amlogic/Kconfig | 14 + drivers/phy/amlogic/Makefile | 1 + drivers/phy/amlogic/phy-meson8b-usb2.c | 286 ++++++ drivers/phy/broadcom/Kconfig | 55 ++ drivers/phy/broadcom/Makefile | 6 + drivers/phy/broadcom/phy-bcm-cygnus-pcie.c | 221 +++++ drivers/phy/broadcom/phy-bcm-kona-usb2.c | 155 +++ drivers/phy/broadcom/phy-bcm-ns-usb2.c | 137 +++ drivers/phy/broadcom/phy-bcm-ns-usb3.c | 303 ++++++ drivers/phy/broadcom/phy-bcm-ns2-pcie.c | 101 ++ drivers/phy/broadcom/phy-brcm-sata.c | 493 ++++++++++ drivers/phy/hisilicon/Kconfig | 20 + drivers/phy/hisilicon/Makefile | 2 + drivers/phy/hisilicon/phy-hi6220-usb.c | 168 ++++ drivers/phy/hisilicon/phy-hix5hd2-sata.c | 191 ++++ drivers/phy/marvell/Kconfig | 50 + drivers/phy/marvell/Makefile | 6 + drivers/phy/marvell/phy-armada375-usb2.c | 158 +++ drivers/phy/marvell/phy-berlin-sata.c | 299 ++++++ drivers/phy/marvell/phy-berlin-usb.c | 214 ++++ drivers/phy/marvell/phy-mvebu-sata.c | 138 +++ drivers/phy/marvell/phy-pxa-28nm-hsic.c | 220 +++++ drivers/phy/marvell/phy-pxa-28nm-usb2.c | 355 +++++++ drivers/phy/phy-armada375-usb2.c | 158 --- drivers/phy/phy-bcm-cygnus-pcie.c | 221 ----- drivers/phy/phy-bcm-kona-usb2.c | 155 --- drivers/phy/phy-bcm-ns-usb2.c | 137 --- drivers/phy/phy-bcm-ns-usb3.c | 303 ------ drivers/phy/phy-bcm-ns2-pcie.c | 101 -- drivers/phy/phy-berlin-sata.c | 299 ------ drivers/phy/phy-berlin-usb.c | 214 ---- drivers/phy/phy-brcm-sata.c | 493 ---------- drivers/phy/phy-da8xx-usb.c | 251 ----- drivers/phy/phy-dm816x-usb.c | 290 ------ drivers/phy/phy-exynos-dp-video.c | 122 --- drivers/phy/phy-exynos-mipi-video.c | 377 -------- drivers/phy/phy-exynos-pcie.c | 281 ------ drivers/phy/phy-exynos4210-usb2.c | 260 ----- drivers/phy/phy-exynos4x12-usb2.c | 378 -------- drivers/phy/phy-exynos5-usbdrd.c | 782 --------------- drivers/phy/phy-exynos5250-sata.c | 250 ----- drivers/phy/phy-exynos5250-usb2.c | 401 -------- drivers/phy/phy-hi6220-usb.c | 168 ---- drivers/phy/phy-hix5hd2-sata.c | 191 ---- drivers/phy/phy-meson8b-usb2.c | 286 ------ drivers/phy/phy-miphy28lp.c | 1286 ------------------------- drivers/phy/phy-mvebu-sata.c | 138 --- drivers/phy/phy-omap-control.c | 360 ------- drivers/phy/phy-omap-usb2.c | 439 --------- drivers/phy/phy-pxa-28nm-hsic.c | 220 ----- drivers/phy/phy-pxa-28nm-usb2.c | 355 ------- drivers/phy/phy-qcom-apq8064-sata.c | 287 ------ drivers/phy/phy-qcom-ipq806x-sata.c | 209 ---- drivers/phy/phy-qcom-qmp.c | 1153 ---------------------- drivers/phy/phy-qcom-qusb2.c | 493 ---------- drivers/phy/phy-qcom-ufs-i.h | 155 --- drivers/phy/phy-qcom-ufs-qmp-14nm.c | 178 ---- drivers/phy/phy-qcom-ufs-qmp-14nm.h | 177 ---- drivers/phy/phy-qcom-ufs-qmp-20nm.c | 232 ----- drivers/phy/phy-qcom-ufs-qmp-20nm.h | 235 ----- drivers/phy/phy-qcom-ufs.c | 691 ------------- drivers/phy/phy-qcom-usb-hs.c | 295 ------ drivers/phy/phy-qcom-usb-hsic.c | 159 --- drivers/phy/phy-rcar-gen2.c | 337 ------- drivers/phy/phy-rcar-gen3-usb2.c | 507 ---------- drivers/phy/phy-rockchip-dp.c | 154 --- drivers/phy/phy-rockchip-emmc.c | 383 -------- drivers/phy/phy-rockchip-inno-usb2.c | 1284 ------------------------ drivers/phy/phy-rockchip-pcie.c | 346 ------- drivers/phy/phy-rockchip-typec.c | 1023 -------------------- drivers/phy/phy-rockchip-usb.c | 540 ----------- drivers/phy/phy-s5pv210-usb2.c | 187 ---- drivers/phy/phy-samsung-usb2.c | 267 ----- drivers/phy/phy-samsung-usb2.h | 73 -- drivers/phy/phy-spear1310-miphy.c | 261 ----- drivers/phy/phy-spear1340-miphy.c | 294 ------ drivers/phy/phy-stih407-usb.c | 180 ---- drivers/phy/phy-sun4i-usb.c | 891 ----------------- drivers/phy/phy-sun9i-usb.c | 202 ---- drivers/phy/phy-ti-pipe3.c | 697 -------------- drivers/phy/phy-tusb1210.c | 146 --- drivers/phy/phy-twl4030-usb.c | 839 ---------------- drivers/phy/qualcomm/Kconfig | 58 ++ drivers/phy/qualcomm/Makefile | 9 + drivers/phy/qualcomm/phy-qcom-apq8064-sata.c | 287 ++++++ drivers/phy/qualcomm/phy-qcom-ipq806x-sata.c | 209 ++++ drivers/phy/qualcomm/phy-qcom-qmp.c | 1153 ++++++++++++++++++++++ drivers/phy/qualcomm/phy-qcom-qusb2.c | 493 ++++++++++ drivers/phy/qualcomm/phy-qcom-ufs-i.h | 155 +++ drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.c | 178 ++++ drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.h | 177 ++++ drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.c | 232 +++++ drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.h | 235 +++++ drivers/phy/qualcomm/phy-qcom-ufs.c | 691 +++++++++++++ drivers/phy/qualcomm/phy-qcom-usb-hs.c | 295 ++++++ drivers/phy/qualcomm/phy-qcom-usb-hsic.c | 159 +++ drivers/phy/renesas/Kconfig | 17 + drivers/phy/renesas/Makefile | 2 + drivers/phy/renesas/phy-rcar-gen2.c | 337 +++++++ drivers/phy/renesas/phy-rcar-gen3-usb2.c | 507 ++++++++++ drivers/phy/rockchip/Kconfig | 51 + drivers/phy/rockchip/Makefile | 6 + drivers/phy/rockchip/phy-rockchip-dp.c | 154 +++ drivers/phy/rockchip/phy-rockchip-emmc.c | 383 ++++++++ drivers/phy/rockchip/phy-rockchip-inno-usb2.c | 1284 ++++++++++++++++++++++++ drivers/phy/rockchip/phy-rockchip-pcie.c | 346 +++++++ drivers/phy/rockchip/phy-rockchip-typec.c | 1023 ++++++++++++++++++++ drivers/phy/rockchip/phy-rockchip-usb.c | 540 +++++++++++ drivers/phy/samsung/Kconfig | 95 ++ drivers/phy/samsung/Makefile | 11 + drivers/phy/samsung/phy-exynos-dp-video.c | 122 +++ drivers/phy/samsung/phy-exynos-mipi-video.c | 377 ++++++++ drivers/phy/samsung/phy-exynos-pcie.c | 281 ++++++ drivers/phy/samsung/phy-exynos4210-usb2.c | 260 +++++ drivers/phy/samsung/phy-exynos4x12-usb2.c | 378 ++++++++ drivers/phy/samsung/phy-exynos5-usbdrd.c | 782 +++++++++++++++ drivers/phy/samsung/phy-exynos5250-sata.c | 250 +++++ drivers/phy/samsung/phy-exynos5250-usb2.c | 401 ++++++++ drivers/phy/samsung/phy-s5pv210-usb2.c | 187 ++++ drivers/phy/samsung/phy-samsung-usb2.c | 267 +++++ drivers/phy/samsung/phy-samsung-usb2.h | 73 ++ drivers/phy/st/Kconfig | 33 + drivers/phy/st/Makefile | 4 + drivers/phy/st/phy-miphy28lp.c | 1286 +++++++++++++++++++++++++ drivers/phy/st/phy-spear1310-miphy.c | 261 +++++ drivers/phy/st/phy-spear1340-miphy.c | 294 ++++++ drivers/phy/st/phy-stih407-usb.c | 180 ++++ drivers/phy/ti/Kconfig | 78 ++ drivers/phy/ti/Makefile | 7 + drivers/phy/ti/phy-da8xx-usb.c | 251 +++++ drivers/phy/ti/phy-dm816x-usb.c | 290 ++++++ drivers/phy/ti/phy-omap-control.c | 360 +++++++ drivers/phy/ti/phy-omap-usb2.c | 439 +++++++++ drivers/phy/ti/phy-ti-pipe3.c | 697 ++++++++++++++ drivers/phy/ti/phy-tusb1210.c | 146 +++ drivers/phy/ti/phy-twl4030-usb.c | 839 ++++++++++++++++ 143 files changed, 22382 insertions(+), 22337 deletions(-) create mode 100644 drivers/phy/allwinner/Kconfig create mode 100644 drivers/phy/allwinner/Makefile create mode 100644 drivers/phy/allwinner/phy-sun4i-usb.c create mode 100644 drivers/phy/allwinner/phy-sun9i-usb.c create mode 100644 drivers/phy/amlogic/Kconfig create mode 100644 drivers/phy/amlogic/Makefile create mode 100644 drivers/phy/amlogic/phy-meson8b-usb2.c create mode 100644 drivers/phy/broadcom/Kconfig create mode 100644 drivers/phy/broadcom/Makefile create mode 100644 drivers/phy/broadcom/phy-bcm-cygnus-pcie.c create mode 100644 drivers/phy/broadcom/phy-bcm-kona-usb2.c create mode 100644 drivers/phy/broadcom/phy-bcm-ns-usb2.c create mode 100644 drivers/phy/broadcom/phy-bcm-ns-usb3.c create mode 100644 drivers/phy/broadcom/phy-bcm-ns2-pcie.c create mode 100644 drivers/phy/broadcom/phy-brcm-sata.c create mode 100644 drivers/phy/hisilicon/Kconfig create mode 100644 drivers/phy/hisilicon/Makefile create mode 100644 drivers/phy/hisilicon/phy-hi6220-usb.c create mode 100644 drivers/phy/hisilicon/phy-hix5hd2-sata.c create mode 100644 drivers/phy/marvell/Kconfig create mode 100644 drivers/phy/marvell/Makefile create mode 100644 drivers/phy/marvell/phy-armada375-usb2.c create mode 100644 drivers/phy/marvell/phy-berlin-sata.c create mode 100644 drivers/phy/marvell/phy-berlin-usb.c create mode 100644 drivers/phy/marvell/phy-mvebu-sata.c create mode 100644 drivers/phy/marvell/phy-pxa-28nm-hsic.c create mode 100644 drivers/phy/marvell/phy-pxa-28nm-usb2.c delete mode 100644 drivers/phy/phy-armada375-usb2.c delete mode 100644 drivers/phy/phy-bcm-cygnus-pcie.c delete mode 100644 drivers/phy/phy-bcm-kona-usb2.c delete mode 100644 drivers/phy/phy-bcm-ns-usb2.c delete mode 100644 drivers/phy/phy-bcm-ns-usb3.c delete mode 100644 drivers/phy/phy-bcm-ns2-pcie.c delete mode 100644 drivers/phy/phy-berlin-sata.c delete mode 100644 drivers/phy/phy-berlin-usb.c delete mode 100644 drivers/phy/phy-brcm-sata.c delete mode 100644 drivers/phy/phy-da8xx-usb.c delete mode 100644 drivers/phy/phy-dm816x-usb.c delete mode 100644 drivers/phy/phy-exynos-dp-video.c delete mode 100644 drivers/phy/phy-exynos-mipi-video.c delete mode 100644 drivers/phy/phy-exynos-pcie.c delete mode 100644 drivers/phy/phy-exynos4210-usb2.c delete mode 100644 drivers/phy/phy-exynos4x12-usb2.c delete mode 100644 drivers/phy/phy-exynos5-usbdrd.c delete mode 100644 drivers/phy/phy-exynos5250-sata.c delete mode 100644 drivers/phy/phy-exynos5250-usb2.c delete mode 100644 drivers/phy/phy-hi6220-usb.c delete mode 100644 drivers/phy/phy-hix5hd2-sata.c delete mode 100644 drivers/phy/phy-meson8b-usb2.c delete mode 100644 drivers/phy/phy-miphy28lp.c delete mode 100644 drivers/phy/phy-mvebu-sata.c delete mode 100644 drivers/phy/phy-omap-control.c delete mode 100644 drivers/phy/phy-omap-usb2.c delete mode 100644 drivers/phy/phy-pxa-28nm-hsic.c delete mode 100644 drivers/phy/phy-pxa-28nm-usb2.c delete mode 100644 drivers/phy/phy-qcom-apq8064-sata.c delete mode 100644 drivers/phy/phy-qcom-ipq806x-sata.c delete mode 100644 drivers/phy/phy-qcom-qmp.c delete mode 100644 drivers/phy/phy-qcom-qusb2.c delete mode 100644 drivers/phy/phy-qcom-ufs-i.h delete mode 100644 drivers/phy/phy-qcom-ufs-qmp-14nm.c delete mode 100644 drivers/phy/phy-qcom-ufs-qmp-14nm.h delete mode 100644 drivers/phy/phy-qcom-ufs-qmp-20nm.c delete mode 100644 drivers/phy/phy-qcom-ufs-qmp-20nm.h delete mode 100644 drivers/phy/phy-qcom-ufs.c delete mode 100644 drivers/phy/phy-qcom-usb-hs.c delete mode 100644 drivers/phy/phy-qcom-usb-hsic.c delete mode 100644 drivers/phy/phy-rcar-gen2.c delete mode 100644 drivers/phy/phy-rcar-gen3-usb2.c delete mode 100644 drivers/phy/phy-rockchip-dp.c delete mode 100644 drivers/phy/phy-rockchip-emmc.c delete mode 100644 drivers/phy/phy-rockchip-inno-usb2.c delete mode 100644 drivers/phy/phy-rockchip-pcie.c delete mode 100644 drivers/phy/phy-rockchip-typec.c delete mode 100644 drivers/phy/phy-rockchip-usb.c delete mode 100644 drivers/phy/phy-s5pv210-usb2.c delete mode 100644 drivers/phy/phy-samsung-usb2.c delete mode 100644 drivers/phy/phy-samsung-usb2.h delete mode 100644 drivers/phy/phy-spear1310-miphy.c delete mode 100644 drivers/phy/phy-spear1340-miphy.c delete mode 100644 drivers/phy/phy-stih407-usb.c delete mode 100644 drivers/phy/phy-sun4i-usb.c delete mode 100644 drivers/phy/phy-sun9i-usb.c delete mode 100644 drivers/phy/phy-ti-pipe3.c delete mode 100644 drivers/phy/phy-tusb1210.c delete mode 100644 drivers/phy/phy-twl4030-usb.c create mode 100644 drivers/phy/qualcomm/Kconfig create mode 100644 drivers/phy/qualcomm/Makefile create mode 100644 drivers/phy/qualcomm/phy-qcom-apq8064-sata.c create mode 100644 drivers/phy/qualcomm/phy-qcom-ipq806x-sata.c create mode 100644 drivers/phy/qualcomm/phy-qcom-qmp.c create mode 100644 drivers/phy/qualcomm/phy-qcom-qusb2.c create mode 100644 drivers/phy/qualcomm/phy-qcom-ufs-i.h create mode 100644 drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.c create mode 100644 drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.h create mode 100644 drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.c create mode 100644 drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.h create mode 100644 drivers/phy/qualcomm/phy-qcom-ufs.c create mode 100644 drivers/phy/qualcomm/phy-qcom-usb-hs.c create mode 100644 drivers/phy/qualcomm/phy-qcom-usb-hsic.c create mode 100644 drivers/phy/renesas/Kconfig create mode 100644 drivers/phy/renesas/Makefile create mode 100644 drivers/phy/renesas/phy-rcar-gen2.c create mode 100644 drivers/phy/renesas/phy-rcar-gen3-usb2.c create mode 100644 drivers/phy/rockchip/Kconfig create mode 100644 drivers/phy/rockchip/Makefile create mode 100644 drivers/phy/rockchip/phy-rockchip-dp.c create mode 100644 drivers/phy/rockchip/phy-rockchip-emmc.c create mode 100644 drivers/phy/rockchip/phy-rockchip-inno-usb2.c create mode 100644 drivers/phy/rockchip/phy-rockchip-pcie.c create mode 100644 drivers/phy/rockchip/phy-rockchip-typec.c create mode 100644 drivers/phy/rockchip/phy-rockchip-usb.c create mode 100644 drivers/phy/samsung/Kconfig create mode 100644 drivers/phy/samsung/Makefile create mode 100644 drivers/phy/samsung/phy-exynos-dp-video.c create mode 100644 drivers/phy/samsung/phy-exynos-mipi-video.c create mode 100644 drivers/phy/samsung/phy-exynos-pcie.c create mode 100644 drivers/phy/samsung/phy-exynos4210-usb2.c create mode 100644 drivers/phy/samsung/phy-exynos4x12-usb2.c create mode 100644 drivers/phy/samsung/phy-exynos5-usbdrd.c create mode 100644 drivers/phy/samsung/phy-exynos5250-sata.c create mode 100644 drivers/phy/samsung/phy-exynos5250-usb2.c create mode 100644 drivers/phy/samsung/phy-s5pv210-usb2.c create mode 100644 drivers/phy/samsung/phy-samsung-usb2.c create mode 100644 drivers/phy/samsung/phy-samsung-usb2.h create mode 100644 drivers/phy/st/Kconfig create mode 100644 drivers/phy/st/Makefile create mode 100644 drivers/phy/st/phy-miphy28lp.c create mode 100644 drivers/phy/st/phy-spear1310-miphy.c create mode 100644 drivers/phy/st/phy-spear1340-miphy.c create mode 100644 drivers/phy/st/phy-stih407-usb.c create mode 100644 drivers/phy/ti/Kconfig create mode 100644 drivers/phy/ti/Makefile create mode 100644 drivers/phy/ti/phy-da8xx-usb.c create mode 100644 drivers/phy/ti/phy-dm816x-usb.c create mode 100644 drivers/phy/ti/phy-omap-control.c create mode 100644 drivers/phy/ti/phy-omap-usb2.c create mode 100644 drivers/phy/ti/phy-ti-pipe3.c create mode 100644 drivers/phy/ti/phy-tusb1210.c create mode 100644 drivers/phy/ti/phy-twl4030-usb.c diff --git a/MAINTAINERS b/MAINTAINERS index f7d568b8f133..a47d3da4d35d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1844,8 +1844,8 @@ F: drivers/i2c/busses/i2c-st.c F: drivers/media/rc/st_rc.c F: drivers/media/platform/sti/c8sectpfe/ F: drivers/mmc/host/sdhci-st.c -F: drivers/phy/phy-miphy28lp.c -F: drivers/phy/phy-stih407-usb.c +F: drivers/phy/st/phy-miphy28lp.c +F: drivers/phy/st/phy-stih407-usb.c F: drivers/pinctrl/pinctrl-st.c F: drivers/remoteproc/st_remoteproc.c F: drivers/remoteproc/st_slim_rproc.c @@ -10833,7 +10833,7 @@ RENESAS USB2 PHY DRIVER M: Yoshihiro Shimoda L: linux-renesas-soc@vger.kernel.org S: Maintained -F: drivers/phy/phy-rcar-gen3-usb2.c +F: drivers/phy/renesas/phy-rcar-gen3-usb2.c RESET CONTROLLER FRAMEWORK M: Philipp Zabel @@ -11235,12 +11235,12 @@ L: linux-kernel@vger.kernel.org S: Supported F: Documentation/devicetree/bindings/phy/samsung-phy.txt F: Documentation/phy/samsung-usb2.txt -F: drivers/phy/phy-exynos4210-usb2.c -F: drivers/phy/phy-exynos4x12-usb2.c -F: drivers/phy/phy-exynos5250-usb2.c -F: drivers/phy/phy-s5pv210-usb2.c -F: drivers/phy/phy-samsung-usb2.c -F: drivers/phy/phy-samsung-usb2.h +F: drivers/phy/samsung/phy-exynos4210-usb2.c +F: drivers/phy/samsung/phy-exynos4x12-usb2.c +F: drivers/phy/samsung/phy-exynos5250-usb2.c +F: drivers/phy/samsung/phy-s5pv210-usb2.c +F: drivers/phy/samsung/phy-samsung-usb2.c +F: drivers/phy/samsung/phy-samsung-usb2.h SERIAL DRIVERS M: Greg Kroah-Hartman diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index afaf7b643eeb..01009b2a7d74 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -15,73 +15,6 @@ config GENERIC_PHY phy users can obtain reference to the PHY. All the users of this framework should select this config. -config PHY_BCM_NS_USB2 - tristate "Broadcom Northstar USB 2.0 PHY Driver" - depends on ARCH_BCM_IPROC || COMPILE_TEST - depends on HAS_IOMEM && OF - select GENERIC_PHY - help - Enable this to support Broadcom USB 2.0 PHY connected to the USB - controller on Northstar family. - -config PHY_BCM_NS_USB3 - tristate "Broadcom Northstar USB 3.0 PHY Driver" - depends on ARCH_BCM_IPROC || COMPILE_TEST - depends on HAS_IOMEM && OF - select GENERIC_PHY - help - Enable this to support Broadcom USB 3.0 PHY connected to the USB - controller on Northstar family. - -config PHY_BERLIN_USB - tristate "Marvell Berlin USB PHY Driver" - depends on ARCH_BERLIN && RESET_CONTROLLER && HAS_IOMEM && OF - select GENERIC_PHY - help - Enable this to support the USB PHY on Marvell Berlin SoCs. - -config PHY_BERLIN_SATA - tristate "Marvell Berlin SATA PHY driver" - depends on ARCH_BERLIN && HAS_IOMEM && OF - select GENERIC_PHY - help - Enable this to support the SATA PHY on Marvell Berlin SoCs. - -config ARMADA375_USBCLUSTER_PHY - def_bool y - depends on MACH_ARMADA_375 || COMPILE_TEST - depends on OF && HAS_IOMEM - select GENERIC_PHY - -config PHY_DA8XX_USB - tristate "TI DA8xx USB PHY Driver" - depends on ARCH_DAVINCI_DA8XX - select GENERIC_PHY - select MFD_SYSCON - help - Enable this to support the USB PHY on DA8xx SoCs. - - This driver controls both the USB 1.1 PHY and the USB 2.0 PHY. - -config PHY_DM816X_USB - tristate "TI dm816x USB PHY driver" - depends on ARCH_OMAP2PLUS - depends on USB_SUPPORT - select GENERIC_PHY - select USB_PHY - help - Enable this for dm816x USB to work. - -config PHY_EXYNOS_MIPI_VIDEO - tristate "S5P/EXYNOS SoC series MIPI CSI-2/DSI PHY driver" - depends on HAS_IOMEM - depends on ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST - select GENERIC_PHY - default y if ARCH_S5PV210 || ARCH_EXYNOS - help - Support for MIPI CSI-2 and MIPI DSI DPHY found on Samsung S5P - and EXYNOS SoCs. - config PHY_LPC18XX_USB_OTG tristate "NXP LPC18xx/43xx SoC USB OTG PHY driver" depends on OF && (ARCH_LPC18XX || COMPILE_TEST) @@ -93,146 +26,6 @@ config PHY_LPC18XX_USB_OTG This driver is need for USB0 support on LPC18xx/43xx and takes care of enabling and clock setup. -config PHY_PXA_28NM_HSIC - tristate "Marvell USB HSIC 28nm PHY Driver" - depends on HAS_IOMEM - select GENERIC_PHY - help - Enable this to support Marvell USB HSIC PHY driver for Marvell - SoC. This driver will do the PHY initialization and shutdown. - The PHY driver will be used by Marvell ehci driver. - - To compile this driver as a module, choose M here. - -config PHY_PXA_28NM_USB2 - tristate "Marvell USB 2.0 28nm PHY Driver" - depends on HAS_IOMEM - select GENERIC_PHY - help - Enable this to support Marvell USB 2.0 PHY driver for Marvell - SoC. This driver will do the PHY initialization and shutdown. - The PHY driver will be used by Marvell udc/ehci/otg driver. - - To compile this driver as a module, choose M here. - -config PHY_MVEBU_SATA - def_bool y - depends on ARCH_DOVE || MACH_DOVE || MACH_KIRKWOOD - depends on OF - select GENERIC_PHY - -config PHY_MIPHY28LP - tristate "STMicroelectronics MIPHY28LP PHY driver for STiH407" - depends on ARCH_STI - select GENERIC_PHY - help - Enable this to support the miphy transceiver (for SATA/PCIE/USB3) - that is part of STMicroelectronics STiH407 SoC. - -config PHY_RCAR_GEN2 - tristate "Renesas R-Car generation 2 USB PHY driver" - depends on ARCH_RENESAS - depends on GENERIC_PHY - help - Support for USB PHY found on Renesas R-Car generation 2 SoCs. - -config PHY_RCAR_GEN3_USB2 - tristate "Renesas R-Car generation 3 USB 2.0 PHY driver" - depends on ARCH_RENESAS - depends on EXTCON - select GENERIC_PHY - help - Support for USB 2.0 PHY found on Renesas R-Car generation 3 SoCs. - -config OMAP_CONTROL_PHY - tristate "OMAP CONTROL PHY Driver" - depends on ARCH_OMAP2PLUS || COMPILE_TEST - help - Enable this to add support for the PHY part present in the control - module. This driver has API to power on the USB2 PHY and to write to - the mailbox. The mailbox is present only in omap4 and the register to - power on the USB2 PHY is present in OMAP4 and OMAP5. OMAP5 has an - additional register to power on USB3 PHY/SATA PHY/PCIE PHY - (PIPE3 PHY). - -config OMAP_USB2 - tristate "OMAP USB2 PHY Driver" - depends on ARCH_OMAP2PLUS - depends on USB_SUPPORT - select GENERIC_PHY - select USB_PHY - select OMAP_CONTROL_PHY - depends on OMAP_OCP2SCP - help - Enable this to support the transceiver that is part of SOC. This - driver takes care of all the PHY functionality apart from comparator. - The USB OTG controller communicates with the comparator using this - driver. - -config TI_PIPE3 - tristate "TI PIPE3 PHY Driver" - depends on ARCH_OMAP2PLUS || COMPILE_TEST - select GENERIC_PHY - select OMAP_CONTROL_PHY - depends on OMAP_OCP2SCP - help - Enable this to support the PIPE3 PHY that is part of TI SOCs. This - driver takes care of all the PHY functionality apart from comparator. - This driver interacts with the "OMAP Control PHY Driver" to power - on/off the PHY. - -config TWL4030_USB - tristate "TWL4030 USB Transceiver Driver" - depends on TWL4030_CORE && REGULATOR_TWL4030 && USB_MUSB_OMAP2PLUS - depends on USB_SUPPORT - depends on USB_GADGET || !USB_GADGET # if USB_GADGET=m, this can't 'y' - select GENERIC_PHY - select USB_PHY - help - Enable this to support the USB OTG transceiver on TWL4030 - family chips (including the TWL5030 and TPS659x0 devices). - This transceiver supports high and full speed devices plus, - in host mode, low speed. - -config PHY_EXYNOS_DP_VIDEO - tristate "EXYNOS SoC series Display Port PHY driver" - depends on OF - depends on ARCH_EXYNOS || COMPILE_TEST - default ARCH_EXYNOS - select GENERIC_PHY - help - Support for Display Port PHY found on Samsung EXYNOS SoCs. - -config BCM_KONA_USB2_PHY - tristate "Broadcom Kona USB2 PHY Driver" - depends on HAS_IOMEM - select GENERIC_PHY - help - Enable this to support the Broadcom Kona USB 2.0 PHY. - -config PHY_EXYNOS5250_SATA - tristate "Exynos5250 Sata SerDes/PHY driver" - depends on SOC_EXYNOS5250 - depends on HAS_IOMEM - depends on OF - select GENERIC_PHY - select I2C - select I2C_S3C2410 - select MFD_SYSCON - help - Enable this to support SATA SerDes/Phy found on Samsung's - Exynos5250 based SoCs.This SerDes/Phy supports SATA 1.5 Gb/s, - SATA 3.0 Gb/s, SATA 6.0 Gb/s speeds. It supports one SATA host - port to accept one SATA device. - -config PHY_HIX5HD2_SATA - tristate "HIX5HD2 SATA PHY Driver" - depends on ARCH_HIX5HD2 && OF && HAS_IOMEM - select GENERIC_PHY - select MFD_SYSCON - help - Support for SATA PHY on Hisilicon hix5hd2 Soc. - config PHY_MT65XX_USB3 tristate "Mediatek USB3.0 PHY Driver" depends on ARCH_MEDIATEK && OF @@ -241,104 +34,6 @@ config PHY_MT65XX_USB3 Say 'Y' here to add support for Mediatek USB3.0 PHY driver, it supports multiple usb2.0 and usb3.0 ports. -config PHY_HI6220_USB - tristate "hi6220 USB PHY support" - depends on (ARCH_HISI && ARM64) || COMPILE_TEST - select GENERIC_PHY - select MFD_SYSCON - help - Enable this to support the HISILICON HI6220 USB PHY. - - To compile this driver as a module, choose M here. - -config PHY_SUN4I_USB - tristate "Allwinner sunxi SoC USB PHY driver" - depends on ARCH_SUNXI && HAS_IOMEM && OF - depends on RESET_CONTROLLER - depends on EXTCON - depends on POWER_SUPPLY - depends on USB_SUPPORT - select GENERIC_PHY - select USB_COMMON - help - Enable this to support the transceiver that is part of Allwinner - sunxi SoCs. - - This driver controls the entire USB PHY block, both the USB OTG - parts, as well as the 2 regular USB 2 host PHYs. - -config PHY_SUN9I_USB - tristate "Allwinner sun9i SoC USB PHY driver" - depends on ARCH_SUNXI && HAS_IOMEM && OF - depends on RESET_CONTROLLER - depends on USB_SUPPORT - select USB_COMMON - select GENERIC_PHY - help - Enable this to support the transceiver that is part of Allwinner - sun9i SoCs. - - This driver controls each individual USB 2 host PHY. - -config PHY_SAMSUNG_USB2 - tristate "Samsung USB 2.0 PHY driver" - depends on HAS_IOMEM - depends on USB_EHCI_EXYNOS || USB_OHCI_EXYNOS || USB_DWC2 - select GENERIC_PHY - select MFD_SYSCON - default ARCH_EXYNOS - help - Enable this to support the Samsung USB 2.0 PHY driver for Samsung - SoCs. This driver provides the interface for USB 2.0 PHY. Support - for particular PHYs will be enabled based on the SoC type in addition - to this driver. - -config PHY_S5PV210_USB2 - bool "Support for S5PV210" - depends on PHY_SAMSUNG_USB2 - depends on ARCH_S5PV210 - help - Enable USB PHY support for S5PV210. This option requires that Samsung - USB 2.0 PHY driver is enabled and means that support for this - particular SoC is compiled in the driver. In case of S5PV210 two phys - are available - device and host. - -config PHY_EXYNOS4210_USB2 - bool - depends on PHY_SAMSUNG_USB2 - default CPU_EXYNOS4210 - -config PHY_EXYNOS4X12_USB2 - bool - depends on PHY_SAMSUNG_USB2 - default SOC_EXYNOS3250 || SOC_EXYNOS4212 || SOC_EXYNOS4412 - -config PHY_EXYNOS5250_USB2 - bool - depends on PHY_SAMSUNG_USB2 - default SOC_EXYNOS5250 || SOC_EXYNOS5420 - -config PHY_EXYNOS5_USBDRD - tristate "Exynos5 SoC series USB DRD PHY driver" - depends on ARCH_EXYNOS && OF - depends on HAS_IOMEM - depends on USB_DWC3_EXYNOS - select GENERIC_PHY - select MFD_SYSCON - default y - help - Enable USB DRD PHY support for Exynos 5 SoC series. - This driver provides PHY interface for USB 3.0 DRD controller - present on Exynos5 SoC series. - -config PHY_EXYNOS_PCIE - bool "Exynos PCIe PHY driver" - depends on OF && (ARCH_EXYNOS || COMPILE_TEST) - select GENERIC_PHY - help - Enable PCIe PHY support for Exynos SoC series. - This driver provides PHY interface for Exynos PCIe controller. - config PHY_PISTACHIO_USB tristate "IMG Pistachio USB2.0 PHY driver" depends on MACH_PISTACHIO @@ -346,83 +41,6 @@ config PHY_PISTACHIO_USB help Enable this to support the USB2.0 PHY on the IMG Pistachio SoC. -config PHY_QCOM_APQ8064_SATA - tristate "Qualcomm APQ8064 SATA SerDes/PHY driver" - depends on ARCH_QCOM - depends on HAS_IOMEM - depends on OF - select GENERIC_PHY - -config PHY_QCOM_IPQ806X_SATA - tristate "Qualcomm IPQ806x SATA SerDes/PHY driver" - depends on ARCH_QCOM - depends on HAS_IOMEM - depends on OF - select GENERIC_PHY - -config PHY_ROCKCHIP_USB - tristate "Rockchip USB2 PHY Driver" - depends on ARCH_ROCKCHIP && OF - select GENERIC_PHY - help - Enable this to support the Rockchip USB 2.0 PHY. - -config PHY_ROCKCHIP_INNO_USB2 - tristate "Rockchip INNO USB2PHY Driver" - depends on (ARCH_ROCKCHIP || COMPILE_TEST) && OF - depends on COMMON_CLK - depends on EXTCON - depends on USB_SUPPORT - select GENERIC_PHY - select USB_COMMON - help - Support for Rockchip USB2.0 PHY with Innosilicon IP block. - -config PHY_ROCKCHIP_EMMC - tristate "Rockchip EMMC PHY Driver" - depends on ARCH_ROCKCHIP && OF - select GENERIC_PHY - help - Enable this to support the Rockchip EMMC PHY. - -config PHY_ROCKCHIP_DP - tristate "Rockchip Display Port PHY Driver" - depends on ARCH_ROCKCHIP && OF - select GENERIC_PHY - help - Enable this to support the Rockchip Display Port PHY. - -config PHY_ROCKCHIP_PCIE - tristate "Rockchip PCIe PHY Driver" - depends on (ARCH_ROCKCHIP && OF) || COMPILE_TEST - select GENERIC_PHY - select MFD_SYSCON - help - Enable this to support the Rockchip PCIe PHY. - -config PHY_ROCKCHIP_TYPEC - tristate "Rockchip TYPEC PHY Driver" - depends on OF && (ARCH_ROCKCHIP || COMPILE_TEST) - select EXTCON - select GENERIC_PHY - select RESET_CONTROLLER - help - Enable this to support the Rockchip USB TYPEC PHY. - -config PHY_ST_SPEAR1310_MIPHY - tristate "ST SPEAR1310-MIPHY driver" - select GENERIC_PHY - depends on MACH_SPEAR1310 || COMPILE_TEST - help - Support for ST SPEAr1310 MIPHY which can be used for PCIe and SATA. - -config PHY_ST_SPEAR1340_MIPHY - tristate "ST SPEAR1340-MIPHY driver" - select GENERIC_PHY - depends on MACH_SPEAR1340 || COMPILE_TEST - help - Support for ST SPEAr1340 MIPHY which can be used for PCIe and SATA. - config PHY_XGENE tristate "APM X-Gene 15Gbps PHY support" depends on HAS_IOMEM && OF && (ARM64 || COMPILE_TEST) @@ -430,104 +48,17 @@ config PHY_XGENE help This option enables support for APM X-Gene SoC multi-purpose PHY. -config PHY_STIH407_USB - tristate "STMicroelectronics USB2 picoPHY driver for STiH407 family" - depends on RESET_CONTROLLER - depends on ARCH_STI || COMPILE_TEST - select GENERIC_PHY - help - Enable this support to enable the picoPHY device used by USB2 - and USB3 controllers on STMicroelectronics STiH407 SoC families. - -config PHY_QCOM_QMP - tristate "Qualcomm QMP PHY Driver" - depends on OF && COMMON_CLK && (ARCH_QCOM || COMPILE_TEST) - select GENERIC_PHY - help - Enable this to support the QMP PHY transceiver that is used - with controllers such as PCIe, UFS, and USB on Qualcomm chips. - -config PHY_QCOM_QUSB2 - tristate "Qualcomm QUSB2 PHY Driver" - depends on OF && (ARCH_QCOM || COMPILE_TEST) - depends on NVMEM || !NVMEM - select GENERIC_PHY - help - Enable this to support the HighSpeed QUSB2 PHY transceiver for USB - controllers on Qualcomm chips. This driver supports the high-speed - PHY which is usually paired with either the ChipIdea or Synopsys DWC3 - USB IPs on MSM SOCs. - -config PHY_QCOM_UFS - tristate "Qualcomm UFS PHY driver" - depends on OF && ARCH_QCOM - select GENERIC_PHY - help - Support for UFS PHY on QCOM chipsets. - -config PHY_QCOM_USB_HS - tristate "Qualcomm USB HS PHY module" - depends on USB_ULPI_BUS - depends on EXTCON || !EXTCON # if EXTCON=m, this cannot be built-in - select GENERIC_PHY - help - Support for the USB high-speed ULPI compliant phy on Qualcomm - chipsets. - -config PHY_QCOM_USB_HSIC - tristate "Qualcomm USB HSIC ULPI PHY module" - depends on USB_ULPI_BUS - select GENERIC_PHY - help - Support for the USB HSIC ULPI compliant PHY on QCOM chipsets. - -config PHY_TUSB1210 - tristate "TI TUSB1210 ULPI PHY module" - depends on USB_ULPI_BUS - select GENERIC_PHY - help - Support for TI TUSB1210 USB ULPI PHY. - -config PHY_BRCM_SATA - tristate "Broadcom SATA PHY driver" - depends on ARCH_BRCMSTB || ARCH_BCM_IPROC || BMIPS_GENERIC || COMPILE_TEST - depends on OF - select GENERIC_PHY - default ARCH_BCM_IPROC - help - Enable this to support the Broadcom SATA PHY. - If unsure, say N. - -config PHY_CYGNUS_PCIE - tristate "Broadcom Cygnus PCIe PHY driver" - depends on OF && (ARCH_BCM_CYGNUS || COMPILE_TEST) - select GENERIC_PHY - default ARCH_BCM_CYGNUS - help - Enable this to support the Broadcom Cygnus PCIe PHY. - If unsure, say N. - +source "drivers/phy/allwinner/Kconfig" +source "drivers/phy/amlogic/Kconfig" +source "drivers/phy/broadcom/Kconfig" +source "drivers/phy/hisilicon/Kconfig" +source "drivers/phy/marvell/Kconfig" +source "drivers/phy/qualcomm/Kconfig" +source "drivers/phy/renesas/Kconfig" +source "drivers/phy/rockchip/Kconfig" +source "drivers/phy/samsung/Kconfig" +source "drivers/phy/st/Kconfig" source "drivers/phy/tegra/Kconfig" - -config PHY_NS2_PCIE - tristate "Broadcom Northstar2 PCIe PHY driver" - depends on OF && MDIO_BUS_MUX_BCM_IPROC - select GENERIC_PHY - default ARCH_BCM_IPROC - help - Enable this to support the Broadcom Northstar2 PCIe PHY. - If unsure, say N. - -config PHY_MESON8B_USB2 - tristate "Meson8b and GXBB USB2 PHY driver" - default ARCH_MESON - depends on OF && (ARCH_MESON || COMPILE_TEST) - depends on USB_SUPPORT - select USB_COMMON - select GENERIC_PHY - help - Enable this to support the Meson USB2 PHYs found in Meson8b - and GXBB SoCs. - If unsure, say N. +source "drivers/phy/ti/Kconfig" endmenu diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index f8047b4639fa..c1bd1fa3c853 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -3,64 +3,20 @@ # obj-$(CONFIG_GENERIC_PHY) += phy-core.o -obj-$(CONFIG_PHY_BCM_NS_USB2) += phy-bcm-ns-usb2.o -obj-$(CONFIG_PHY_BCM_NS_USB3) += phy-bcm-ns-usb3.o -obj-$(CONFIG_PHY_BERLIN_USB) += phy-berlin-usb.o -obj-$(CONFIG_PHY_BERLIN_SATA) += phy-berlin-sata.o -obj-$(CONFIG_PHY_DA8XX_USB) += phy-da8xx-usb.o -obj-$(CONFIG_PHY_DM816X_USB) += phy-dm816x-usb.o -obj-$(CONFIG_ARMADA375_USBCLUSTER_PHY) += phy-armada375-usb2.o -obj-$(CONFIG_BCM_KONA_USB2_PHY) += phy-bcm-kona-usb2.o -obj-$(CONFIG_PHY_EXYNOS_DP_VIDEO) += phy-exynos-dp-video.o -obj-$(CONFIG_PHY_EXYNOS_MIPI_VIDEO) += phy-exynos-mipi-video.o obj-$(CONFIG_PHY_LPC18XX_USB_OTG) += phy-lpc18xx-usb-otg.o -obj-$(CONFIG_PHY_PXA_28NM_USB2) += phy-pxa-28nm-usb2.o -obj-$(CONFIG_PHY_PXA_28NM_HSIC) += phy-pxa-28nm-hsic.o -obj-$(CONFIG_PHY_MVEBU_SATA) += phy-mvebu-sata.o -obj-$(CONFIG_PHY_MIPHY28LP) += phy-miphy28lp.o -obj-$(CONFIG_PHY_RCAR_GEN2) += phy-rcar-gen2.o -obj-$(CONFIG_PHY_RCAR_GEN3_USB2) += phy-rcar-gen3-usb2.o -obj-$(CONFIG_OMAP_CONTROL_PHY) += phy-omap-control.o -obj-$(CONFIG_OMAP_USB2) += phy-omap-usb2.o -obj-$(CONFIG_TI_PIPE3) += phy-ti-pipe3.o -obj-$(CONFIG_TWL4030_USB) += phy-twl4030-usb.o -obj-$(CONFIG_PHY_EXYNOS5250_SATA) += phy-exynos5250-sata.o -obj-$(CONFIG_PHY_HIX5HD2_SATA) += phy-hix5hd2-sata.o -obj-$(CONFIG_PHY_HI6220_USB) += phy-hi6220-usb.o obj-$(CONFIG_PHY_MT65XX_USB3) += phy-mt65xx-usb3.o -obj-$(CONFIG_PHY_SUN4I_USB) += phy-sun4i-usb.o -obj-$(CONFIG_PHY_SUN9I_USB) += phy-sun9i-usb.o -obj-$(CONFIG_PHY_SAMSUNG_USB2) += phy-exynos-usb2.o -phy-exynos-usb2-y += phy-samsung-usb2.o -phy-exynos-usb2-$(CONFIG_PHY_EXYNOS4210_USB2) += phy-exynos4210-usb2.o -phy-exynos-usb2-$(CONFIG_PHY_EXYNOS4X12_USB2) += phy-exynos4x12-usb2.o -phy-exynos-usb2-$(CONFIG_PHY_EXYNOS5250_USB2) += phy-exynos5250-usb2.o -phy-exynos-usb2-$(CONFIG_PHY_S5PV210_USB2) += phy-s5pv210-usb2.o -obj-$(CONFIG_PHY_EXYNOS5_USBDRD) += phy-exynos5-usbdrd.o -obj-$(CONFIG_PHY_EXYNOS_PCIE) += phy-exynos-pcie.o -obj-$(CONFIG_PHY_QCOM_APQ8064_SATA) += phy-qcom-apq8064-sata.o -obj-$(CONFIG_PHY_ROCKCHIP_USB) += phy-rockchip-usb.o -obj-$(CONFIG_PHY_ROCKCHIP_INNO_USB2) += phy-rockchip-inno-usb2.o -obj-$(CONFIG_PHY_ROCKCHIP_EMMC) += phy-rockchip-emmc.o -obj-$(CONFIG_PHY_ROCKCHIP_PCIE) += phy-rockchip-pcie.o -obj-$(CONFIG_PHY_ROCKCHIP_DP) += phy-rockchip-dp.o -obj-$(CONFIG_PHY_ROCKCHIP_TYPEC) += phy-rockchip-typec.o -obj-$(CONFIG_PHY_QCOM_IPQ806X_SATA) += phy-qcom-ipq806x-sata.o -obj-$(CONFIG_PHY_ST_SPEAR1310_MIPHY) += phy-spear1310-miphy.o -obj-$(CONFIG_PHY_ST_SPEAR1340_MIPHY) += phy-spear1340-miphy.o obj-$(CONFIG_PHY_XGENE) += phy-xgene.o -obj-$(CONFIG_PHY_STIH407_USB) += phy-stih407-usb.o -obj-$(CONFIG_PHY_QCOM_QMP) += phy-qcom-qmp.o -obj-$(CONFIG_PHY_QCOM_QUSB2) += phy-qcom-qusb2.o -obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs.o -obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs-qmp-20nm.o -obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs-qmp-14nm.o -obj-$(CONFIG_PHY_QCOM_USB_HS) += phy-qcom-usb-hs.o -obj-$(CONFIG_PHY_QCOM_USB_HSIC) += phy-qcom-usb-hsic.o -obj-$(CONFIG_PHY_TUSB1210) += phy-tusb1210.o -obj-$(CONFIG_PHY_BRCM_SATA) += phy-brcm-sata.o obj-$(CONFIG_PHY_PISTACHIO_USB) += phy-pistachio-usb.o -obj-$(CONFIG_PHY_CYGNUS_PCIE) += phy-bcm-cygnus-pcie.o -obj-$(CONFIG_ARCH_TEGRA) += tegra/ -obj-$(CONFIG_PHY_NS2_PCIE) += phy-bcm-ns2-pcie.o -obj-$(CONFIG_PHY_MESON8B_USB2) += phy-meson8b-usb2.o + +obj-$(CONFIG_ARCH_SUNXI) += allwinner/ +obj-$(CONFIG_ARCH_MESON) += amlogic/ +obj-$(CONFIG_ARCH_RENESAS) += renesas/ +obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/ +obj-$(CONFIG_ARCH_TEGRA) += tegra/ +obj-y += broadcom/ \ + hisilicon/ \ + marvell/ \ + qualcomm/ \ + samsung/ \ + st/ \ + ti/ diff --git a/drivers/phy/allwinner/Kconfig b/drivers/phy/allwinner/Kconfig new file mode 100644 index 000000000000..cdc1e745ba47 --- /dev/null +++ b/drivers/phy/allwinner/Kconfig @@ -0,0 +1,31 @@ +# +# Phy drivers for Allwinner platforms +# +config PHY_SUN4I_USB + tristate "Allwinner sunxi SoC USB PHY driver" + depends on ARCH_SUNXI && HAS_IOMEM && OF + depends on RESET_CONTROLLER + depends on EXTCON + depends on POWER_SUPPLY + depends on USB_SUPPORT + select GENERIC_PHY + select USB_COMMON + help + Enable this to support the transceiver that is part of Allwinner + sunxi SoCs. + + This driver controls the entire USB PHY block, both the USB OTG + parts, as well as the 2 regular USB 2 host PHYs. + +config PHY_SUN9I_USB + tristate "Allwinner sun9i SoC USB PHY driver" + depends on ARCH_SUNXI && HAS_IOMEM && OF + depends on RESET_CONTROLLER + depends on USB_SUPPORT + select USB_COMMON + select GENERIC_PHY + help + Enable this to support the transceiver that is part of Allwinner + sun9i SoCs. + + This driver controls each individual USB 2 host PHY. diff --git a/drivers/phy/allwinner/Makefile b/drivers/phy/allwinner/Makefile new file mode 100644 index 000000000000..8605529c01a1 --- /dev/null +++ b/drivers/phy/allwinner/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_PHY_SUN4I_USB) += phy-sun4i-usb.o +obj-$(CONFIG_PHY_SUN9I_USB) += phy-sun9i-usb.o diff --git a/drivers/phy/allwinner/phy-sun4i-usb.c b/drivers/phy/allwinner/phy-sun4i-usb.c new file mode 100644 index 000000000000..bbf06cfe5898 --- /dev/null +++ b/drivers/phy/allwinner/phy-sun4i-usb.c @@ -0,0 +1,891 @@ +/* + * Allwinner sun4i USB phy driver + * + * Copyright (C) 2014-2015 Hans de Goede + * + * Based on code from + * Allwinner Technology Co., Ltd. + * + * Modelled after: Samsung S5P/EXYNOS SoC series MIPI CSIS/DSIM DPHY driver + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Author: Sylwester Nawrocki + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define REG_ISCR 0x00 +#define REG_PHYCTL_A10 0x04 +#define REG_PHYBIST 0x08 +#define REG_PHYTUNE 0x0c +#define REG_PHYCTL_A33 0x10 +#define REG_PHY_OTGCTL 0x20 + +#define REG_PMU_UNK1 0x10 + +#define PHYCTL_DATA BIT(7) + +#define OTGCTL_ROUTE_MUSB BIT(0) + +#define SUNXI_AHB_ICHR8_EN BIT(10) +#define SUNXI_AHB_INCR4_BURST_EN BIT(9) +#define SUNXI_AHB_INCRX_ALIGN_EN BIT(8) +#define SUNXI_ULPI_BYPASS_EN BIT(0) + +/* ISCR, Interface Status and Control bits */ +#define ISCR_ID_PULLUP_EN (1 << 17) +#define ISCR_DPDM_PULLUP_EN (1 << 16) +/* sunxi has the phy id/vbus pins not connected, so we use the force bits */ +#define ISCR_FORCE_ID_MASK (3 << 14) +#define ISCR_FORCE_ID_LOW (2 << 14) +#define ISCR_FORCE_ID_HIGH (3 << 14) +#define ISCR_FORCE_VBUS_MASK (3 << 12) +#define ISCR_FORCE_VBUS_LOW (2 << 12) +#define ISCR_FORCE_VBUS_HIGH (3 << 12) + +/* Common Control Bits for Both PHYs */ +#define PHY_PLL_BW 0x03 +#define PHY_RES45_CAL_EN 0x0c + +/* Private Control Bits for Each PHY */ +#define PHY_TX_AMPLITUDE_TUNE 0x20 +#define PHY_TX_SLEWRATE_TUNE 0x22 +#define PHY_VBUSVALID_TH_SEL 0x25 +#define PHY_PULLUP_RES_SEL 0x27 +#define PHY_OTG_FUNC_EN 0x28 +#define PHY_VBUS_DET_EN 0x29 +#define PHY_DISCON_TH_SEL 0x2a +#define PHY_SQUELCH_DETECT 0x3c + +#define MAX_PHYS 4 + +/* + * Note do not raise the debounce time, we must report Vusb high within 100ms + * otherwise we get Vbus errors + */ +#define DEBOUNCE_TIME msecs_to_jiffies(50) +#define POLL_TIME msecs_to_jiffies(250) + +enum sun4i_usb_phy_type { + sun4i_a10_phy, + sun6i_a31_phy, + sun8i_a33_phy, + sun8i_h3_phy, + sun8i_v3s_phy, + sun50i_a64_phy, +}; + +struct sun4i_usb_phy_cfg { + int num_phys; + enum sun4i_usb_phy_type type; + u32 disc_thresh; + u8 phyctl_offset; + bool dedicated_clocks; + bool enable_pmu_unk1; + bool phy0_dual_route; +}; + +struct sun4i_usb_phy_data { + void __iomem *base; + const struct sun4i_usb_phy_cfg *cfg; + enum usb_dr_mode dr_mode; + spinlock_t reg_lock; /* guard access to phyctl reg */ + struct sun4i_usb_phy { + struct phy *phy; + void __iomem *pmu; + struct regulator *vbus; + struct reset_control *reset; + struct clk *clk; + bool regulator_on; + int index; + } phys[MAX_PHYS]; + /* phy0 / otg related variables */ + struct extcon_dev *extcon; + bool phy0_init; + struct gpio_desc *id_det_gpio; + struct gpio_desc *vbus_det_gpio; + struct power_supply *vbus_power_supply; + struct notifier_block vbus_power_nb; + bool vbus_power_nb_registered; + bool force_session_end; + int id_det_irq; + int vbus_det_irq; + int id_det; + int vbus_det; + struct delayed_work detect; +}; + +#define to_sun4i_usb_phy_data(phy) \ + container_of((phy), struct sun4i_usb_phy_data, phys[(phy)->index]) + +static void sun4i_usb_phy0_update_iscr(struct phy *_phy, u32 clr, u32 set) +{ + struct sun4i_usb_phy *phy = phy_get_drvdata(_phy); + struct sun4i_usb_phy_data *data = to_sun4i_usb_phy_data(phy); + u32 iscr; + + iscr = readl(data->base + REG_ISCR); + iscr &= ~clr; + iscr |= set; + writel(iscr, data->base + REG_ISCR); +} + +static void sun4i_usb_phy0_set_id_detect(struct phy *phy, u32 val) +{ + if (val) + val = ISCR_FORCE_ID_HIGH; + else + val = ISCR_FORCE_ID_LOW; + + sun4i_usb_phy0_update_iscr(phy, ISCR_FORCE_ID_MASK, val); +} + +static void sun4i_usb_phy0_set_vbus_detect(struct phy *phy, u32 val) +{ + if (val) + val = ISCR_FORCE_VBUS_HIGH; + else + val = ISCR_FORCE_VBUS_LOW; + + sun4i_usb_phy0_update_iscr(phy, ISCR_FORCE_VBUS_MASK, val); +} + +static void sun4i_usb_phy_write(struct sun4i_usb_phy *phy, u32 addr, u32 data, + int len) +{ + struct sun4i_usb_phy_data *phy_data = to_sun4i_usb_phy_data(phy); + u32 temp, usbc_bit = BIT(phy->index * 2); + void __iomem *phyctl = phy_data->base + phy_data->cfg->phyctl_offset; + unsigned long flags; + int i; + + spin_lock_irqsave(&phy_data->reg_lock, flags); + + if (phy_data->cfg->phyctl_offset == REG_PHYCTL_A33) { + /* SoCs newer than A33 need us to set phyctl to 0 explicitly */ + writel(0, phyctl); + } + + for (i = 0; i < len; i++) { + temp = readl(phyctl); + + /* clear the address portion */ + temp &= ~(0xff << 8); + + /* set the address */ + temp |= ((addr + i) << 8); + writel(temp, phyctl); + + /* set the data bit and clear usbc bit*/ + temp = readb(phyctl); + if (data & 0x1) + temp |= PHYCTL_DATA; + else + temp &= ~PHYCTL_DATA; + temp &= ~usbc_bit; + writeb(temp, phyctl); + + /* pulse usbc_bit */ + temp = readb(phyctl); + temp |= usbc_bit; + writeb(temp, phyctl); + + temp = readb(phyctl); + temp &= ~usbc_bit; + writeb(temp, phyctl); + + data >>= 1; + } + + spin_unlock_irqrestore(&phy_data->reg_lock, flags); +} + +static void sun4i_usb_phy_passby(struct sun4i_usb_phy *phy, int enable) +{ + u32 bits, reg_value; + + if (!phy->pmu) + return; + + bits = SUNXI_AHB_ICHR8_EN | SUNXI_AHB_INCR4_BURST_EN | + SUNXI_AHB_INCRX_ALIGN_EN | SUNXI_ULPI_BYPASS_EN; + + reg_value = readl(phy->pmu); + + if (enable) + reg_value |= bits; + else + reg_value &= ~bits; + + writel(reg_value, phy->pmu); +} + +static int sun4i_usb_phy_init(struct phy *_phy) +{ + struct sun4i_usb_phy *phy = phy_get_drvdata(_phy); + struct sun4i_usb_phy_data *data = to_sun4i_usb_phy_data(phy); + int ret; + u32 val; + + ret = clk_prepare_enable(phy->clk); + if (ret) + return ret; + + ret = reset_control_deassert(phy->reset); + if (ret) { + clk_disable_unprepare(phy->clk); + return ret; + } + + if (phy->pmu && data->cfg->enable_pmu_unk1) { + val = readl(phy->pmu + REG_PMU_UNK1); + writel(val & ~2, phy->pmu + REG_PMU_UNK1); + } + + /* Enable USB 45 Ohm resistor calibration */ + if (phy->index == 0) + sun4i_usb_phy_write(phy, PHY_RES45_CAL_EN, 0x01, 1); + + /* Adjust PHY's magnitude and rate */ + sun4i_usb_phy_write(phy, PHY_TX_AMPLITUDE_TUNE, 0x14, 5); + + /* Disconnect threshold adjustment */ + sun4i_usb_phy_write(phy, PHY_DISCON_TH_SEL, + data->cfg->disc_thresh, 2); + + sun4i_usb_phy_passby(phy, 1); + + if (phy->index == 0) { + data->phy0_init = true; + + /* Enable pull-ups */ + sun4i_usb_phy0_update_iscr(_phy, 0, ISCR_DPDM_PULLUP_EN); + sun4i_usb_phy0_update_iscr(_phy, 0, ISCR_ID_PULLUP_EN); + + /* Force ISCR and cable state updates */ + data->id_det = -1; + data->vbus_det = -1; + queue_delayed_work(system_wq, &data->detect, 0); + } + + return 0; +} + +static int sun4i_usb_phy_exit(struct phy *_phy) +{ + struct sun4i_usb_phy *phy = phy_get_drvdata(_phy); + struct sun4i_usb_phy_data *data = to_sun4i_usb_phy_data(phy); + + if (phy->index == 0) { + /* Disable pull-ups */ + sun4i_usb_phy0_update_iscr(_phy, ISCR_DPDM_PULLUP_EN, 0); + sun4i_usb_phy0_update_iscr(_phy, ISCR_ID_PULLUP_EN, 0); + data->phy0_init = false; + } + + sun4i_usb_phy_passby(phy, 0); + reset_control_assert(phy->reset); + clk_disable_unprepare(phy->clk); + + return 0; +} + +static int sun4i_usb_phy0_get_id_det(struct sun4i_usb_phy_data *data) +{ + switch (data->dr_mode) { + case USB_DR_MODE_OTG: + if (data->id_det_gpio) + return gpiod_get_value_cansleep(data->id_det_gpio); + else + return 1; /* Fallback to peripheral mode */ + case USB_DR_MODE_HOST: + return 0; + case USB_DR_MODE_PERIPHERAL: + default: + return 1; + } +} + +static int sun4i_usb_phy0_get_vbus_det(struct sun4i_usb_phy_data *data) +{ + if (data->vbus_det_gpio) + return gpiod_get_value_cansleep(data->vbus_det_gpio); + + if (data->vbus_power_supply) { + union power_supply_propval val; + int r; + + r = power_supply_get_property(data->vbus_power_supply, + POWER_SUPPLY_PROP_PRESENT, &val); + if (r == 0) + return val.intval; + } + + /* Fallback: report vbus as high */ + return 1; +} + +static bool sun4i_usb_phy0_have_vbus_det(struct sun4i_usb_phy_data *data) +{ + return data->vbus_det_gpio || data->vbus_power_supply; +} + +static bool sun4i_usb_phy0_poll(struct sun4i_usb_phy_data *data) +{ + if ((data->id_det_gpio && data->id_det_irq <= 0) || + (data->vbus_det_gpio && data->vbus_det_irq <= 0)) + return true; + + /* + * The A31 companion pmic (axp221) does not generate vbus change + * interrupts when the board is driving vbus, so we must poll + * when using the pmic for vbus-det _and_ we're driving vbus. + */ + if (data->cfg->type == sun6i_a31_phy && + data->vbus_power_supply && data->phys[0].regulator_on) + return true; + + return false; +} + +static int sun4i_usb_phy_power_on(struct phy *_phy) +{ + struct sun4i_usb_phy *phy = phy_get_drvdata(_phy); + struct sun4i_usb_phy_data *data = to_sun4i_usb_phy_data(phy); + int ret; + + if (!phy->vbus || phy->regulator_on) + return 0; + + /* For phy0 only turn on Vbus if we don't have an ext. Vbus */ + if (phy->index == 0 && sun4i_usb_phy0_have_vbus_det(data) && + data->vbus_det) { + dev_warn(&_phy->dev, "External vbus detected, not enabling our own vbus\n"); + return 0; + } + + ret = regulator_enable(phy->vbus); + if (ret) + return ret; + + phy->regulator_on = true; + + /* We must report Vbus high within OTG_TIME_A_WAIT_VRISE msec. */ + if (phy->index == 0 && sun4i_usb_phy0_poll(data)) + mod_delayed_work(system_wq, &data->detect, DEBOUNCE_TIME); + + return 0; +} + +static int sun4i_usb_phy_power_off(struct phy *_phy) +{ + struct sun4i_usb_phy *phy = phy_get_drvdata(_phy); + struct sun4i_usb_phy_data *data = to_sun4i_usb_phy_data(phy); + + if (!phy->vbus || !phy->regulator_on) + return 0; + + regulator_disable(phy->vbus); + phy->regulator_on = false; + + /* + * phy0 vbus typically slowly discharges, sometimes this causes the + * Vbus gpio to not trigger an edge irq on Vbus off, so force a rescan. + */ + if (phy->index == 0 && !sun4i_usb_phy0_poll(data)) + mod_delayed_work(system_wq, &data->detect, POLL_TIME); + + return 0; +} + +static int sun4i_usb_phy_set_mode(struct phy *_phy, enum phy_mode mode) +{ + struct sun4i_usb_phy *phy = phy_get_drvdata(_phy); + struct sun4i_usb_phy_data *data = to_sun4i_usb_phy_data(phy); + int new_mode; + + if (phy->index != 0) + return -EINVAL; + + switch (mode) { + case PHY_MODE_USB_HOST: + new_mode = USB_DR_MODE_HOST; + break; + case PHY_MODE_USB_DEVICE: + new_mode = USB_DR_MODE_PERIPHERAL; + break; + case PHY_MODE_USB_OTG: + new_mode = USB_DR_MODE_OTG; + break; + default: + return -EINVAL; + } + + if (new_mode != data->dr_mode) { + dev_info(&_phy->dev, "Changing dr_mode to %d\n", new_mode); + data->dr_mode = new_mode; + } + + data->id_det = -1; /* Force reprocessing of id */ + data->force_session_end = true; + queue_delayed_work(system_wq, &data->detect, 0); + + return 0; +} + +void sun4i_usb_phy_set_squelch_detect(struct phy *_phy, bool enabled) +{ + struct sun4i_usb_phy *phy = phy_get_drvdata(_phy); + + sun4i_usb_phy_write(phy, PHY_SQUELCH_DETECT, enabled ? 0 : 2, 2); +} +EXPORT_SYMBOL_GPL(sun4i_usb_phy_set_squelch_detect); + +static const struct phy_ops sun4i_usb_phy_ops = { + .init = sun4i_usb_phy_init, + .exit = sun4i_usb_phy_exit, + .power_on = sun4i_usb_phy_power_on, + .power_off = sun4i_usb_phy_power_off, + .set_mode = sun4i_usb_phy_set_mode, + .owner = THIS_MODULE, +}; + +static void sun4i_usb_phy0_reroute(struct sun4i_usb_phy_data *data, int id_det) +{ + u32 regval; + + regval = readl(data->base + REG_PHY_OTGCTL); + if (id_det == 0) { + /* Host mode. Route phy0 to EHCI/OHCI */ + regval &= ~OTGCTL_ROUTE_MUSB; + } else { + /* Peripheral mode. Route phy0 to MUSB */ + regval |= OTGCTL_ROUTE_MUSB; + } + writel(regval, data->base + REG_PHY_OTGCTL); +} + +static void sun4i_usb_phy0_id_vbus_det_scan(struct work_struct *work) +{ + struct sun4i_usb_phy_data *data = + container_of(work, struct sun4i_usb_phy_data, detect.work); + struct phy *phy0 = data->phys[0].phy; + bool force_session_end, id_notify = false, vbus_notify = false; + int id_det, vbus_det; + + if (phy0 == NULL) + return; + + id_det = sun4i_usb_phy0_get_id_det(data); + vbus_det = sun4i_usb_phy0_get_vbus_det(data); + + mutex_lock(&phy0->mutex); + + if (!data->phy0_init) { + mutex_unlock(&phy0->mutex); + return; + } + + force_session_end = data->force_session_end; + data->force_session_end = false; + + if (id_det != data->id_det) { + /* id-change, force session end if we've no vbus detection */ + if (data->dr_mode == USB_DR_MODE_OTG && + !sun4i_usb_phy0_have_vbus_det(data)) + force_session_end = true; + + /* When entering host mode (id = 0) force end the session now */ + if (force_session_end && id_det == 0) { + sun4i_usb_phy0_set_vbus_detect(phy0, 0); + msleep(200); + sun4i_usb_phy0_set_vbus_detect(phy0, 1); + } + sun4i_usb_phy0_set_id_detect(phy0, id_det); + data->id_det = id_det; + id_notify = true; + } + + if (vbus_det != data->vbus_det) { + sun4i_usb_phy0_set_vbus_detect(phy0, vbus_det); + data->vbus_det = vbus_det; + vbus_notify = true; + } + + mutex_unlock(&phy0->mutex); + + if (id_notify) { + extcon_set_state_sync(data->extcon, EXTCON_USB_HOST, + !id_det); + /* When leaving host mode force end the session here */ + if (force_session_end && id_det == 1) { + mutex_lock(&phy0->mutex); + sun4i_usb_phy0_set_vbus_detect(phy0, 0); + msleep(1000); + sun4i_usb_phy0_set_vbus_detect(phy0, 1); + mutex_unlock(&phy0->mutex); + } + + /* Re-route PHY0 if necessary */ + if (data->cfg->phy0_dual_route) + sun4i_usb_phy0_reroute(data, id_det); + } + + if (vbus_notify) + extcon_set_state_sync(data->extcon, EXTCON_USB, vbus_det); + + if (sun4i_usb_phy0_poll(data)) + queue_delayed_work(system_wq, &data->detect, POLL_TIME); +} + +static irqreturn_t sun4i_usb_phy0_id_vbus_det_irq(int irq, void *dev_id) +{ + struct sun4i_usb_phy_data *data = dev_id; + + /* vbus or id changed, let the pins settle and then scan them */ + mod_delayed_work(system_wq, &data->detect, DEBOUNCE_TIME); + + return IRQ_HANDLED; +} + +static int sun4i_usb_phy0_vbus_notify(struct notifier_block *nb, + unsigned long val, void *v) +{ + struct sun4i_usb_phy_data *data = + container_of(nb, struct sun4i_usb_phy_data, vbus_power_nb); + struct power_supply *psy = v; + + /* Properties on the vbus_power_supply changed, scan vbus_det */ + if (val == PSY_EVENT_PROP_CHANGED && psy == data->vbus_power_supply) + mod_delayed_work(system_wq, &data->detect, DEBOUNCE_TIME); + + return NOTIFY_OK; +} + +static struct phy *sun4i_usb_phy_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct sun4i_usb_phy_data *data = dev_get_drvdata(dev); + + if (args->args[0] >= data->cfg->num_phys) + return ERR_PTR(-ENODEV); + + return data->phys[args->args[0]].phy; +} + +static int sun4i_usb_phy_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct sun4i_usb_phy_data *data = dev_get_drvdata(dev); + + if (data->vbus_power_nb_registered) + power_supply_unreg_notifier(&data->vbus_power_nb); + if (data->id_det_irq > 0) + devm_free_irq(dev, data->id_det_irq, data); + if (data->vbus_det_irq > 0) + devm_free_irq(dev, data->vbus_det_irq, data); + + cancel_delayed_work_sync(&data->detect); + + return 0; +} + +static const unsigned int sun4i_usb_phy0_cable[] = { + EXTCON_USB, + EXTCON_USB_HOST, + EXTCON_NONE, +}; + +static int sun4i_usb_phy_probe(struct platform_device *pdev) +{ + struct sun4i_usb_phy_data *data; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct phy_provider *phy_provider; + struct resource *res; + int i, ret; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + spin_lock_init(&data->reg_lock); + INIT_DELAYED_WORK(&data->detect, sun4i_usb_phy0_id_vbus_det_scan); + dev_set_drvdata(dev, data); + data->cfg = of_device_get_match_data(dev); + if (!data->cfg) + return -EINVAL; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy_ctrl"); + data->base = devm_ioremap_resource(dev, res); + if (IS_ERR(data->base)) + return PTR_ERR(data->base); + + data->id_det_gpio = devm_gpiod_get_optional(dev, "usb0_id_det", + GPIOD_IN); + if (IS_ERR(data->id_det_gpio)) + return PTR_ERR(data->id_det_gpio); + + data->vbus_det_gpio = devm_gpiod_get_optional(dev, "usb0_vbus_det", + GPIOD_IN); + if (IS_ERR(data->vbus_det_gpio)) + return PTR_ERR(data->vbus_det_gpio); + + if (of_find_property(np, "usb0_vbus_power-supply", NULL)) { + data->vbus_power_supply = devm_power_supply_get_by_phandle(dev, + "usb0_vbus_power-supply"); + if (IS_ERR(data->vbus_power_supply)) + return PTR_ERR(data->vbus_power_supply); + + if (!data->vbus_power_supply) + return -EPROBE_DEFER; + } + + data->dr_mode = of_usb_get_dr_mode_by_phy(np, 0); + + data->extcon = devm_extcon_dev_allocate(dev, sun4i_usb_phy0_cable); + if (IS_ERR(data->extcon)) + return PTR_ERR(data->extcon); + + ret = devm_extcon_dev_register(dev, data->extcon); + if (ret) { + dev_err(dev, "failed to register extcon: %d\n", ret); + return ret; + } + + for (i = 0; i < data->cfg->num_phys; i++) { + struct sun4i_usb_phy *phy = data->phys + i; + char name[16]; + + snprintf(name, sizeof(name), "usb%d_vbus", i); + phy->vbus = devm_regulator_get_optional(dev, name); + if (IS_ERR(phy->vbus)) { + if (PTR_ERR(phy->vbus) == -EPROBE_DEFER) + return -EPROBE_DEFER; + phy->vbus = NULL; + } + + if (data->cfg->dedicated_clocks) + snprintf(name, sizeof(name), "usb%d_phy", i); + else + strlcpy(name, "usb_phy", sizeof(name)); + + phy->clk = devm_clk_get(dev, name); + if (IS_ERR(phy->clk)) { + dev_err(dev, "failed to get clock %s\n", name); + return PTR_ERR(phy->clk); + } + + snprintf(name, sizeof(name), "usb%d_reset", i); + phy->reset = devm_reset_control_get(dev, name); + if (IS_ERR(phy->reset)) { + dev_err(dev, "failed to get reset %s\n", name); + return PTR_ERR(phy->reset); + } + + if (i || data->cfg->phy0_dual_route) { /* No pmu for musb */ + snprintf(name, sizeof(name), "pmu%d", i); + res = platform_get_resource_byname(pdev, + IORESOURCE_MEM, name); + phy->pmu = devm_ioremap_resource(dev, res); + if (IS_ERR(phy->pmu)) + return PTR_ERR(phy->pmu); + } + + phy->phy = devm_phy_create(dev, NULL, &sun4i_usb_phy_ops); + if (IS_ERR(phy->phy)) { + dev_err(dev, "failed to create PHY %d\n", i); + return PTR_ERR(phy->phy); + } + + phy->index = i; + phy_set_drvdata(phy->phy, &data->phys[i]); + } + + data->id_det_irq = gpiod_to_irq(data->id_det_gpio); + if (data->id_det_irq > 0) { + ret = devm_request_irq(dev, data->id_det_irq, + sun4i_usb_phy0_id_vbus_det_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "usb0-id-det", data); + if (ret) { + dev_err(dev, "Err requesting id-det-irq: %d\n", ret); + return ret; + } + } + + data->vbus_det_irq = gpiod_to_irq(data->vbus_det_gpio); + if (data->vbus_det_irq > 0) { + ret = devm_request_irq(dev, data->vbus_det_irq, + sun4i_usb_phy0_id_vbus_det_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "usb0-vbus-det", data); + if (ret) { + dev_err(dev, "Err requesting vbus-det-irq: %d\n", ret); + data->vbus_det_irq = -1; + sun4i_usb_phy_remove(pdev); /* Stop detect work */ + return ret; + } + } + + if (data->vbus_power_supply) { + data->vbus_power_nb.notifier_call = sun4i_usb_phy0_vbus_notify; + data->vbus_power_nb.priority = 0; + ret = power_supply_reg_notifier(&data->vbus_power_nb); + if (ret) { + sun4i_usb_phy_remove(pdev); /* Stop detect work */ + return ret; + } + data->vbus_power_nb_registered = true; + } + + phy_provider = devm_of_phy_provider_register(dev, sun4i_usb_phy_xlate); + if (IS_ERR(phy_provider)) { + sun4i_usb_phy_remove(pdev); /* Stop detect work */ + return PTR_ERR(phy_provider); + } + + return 0; +} + +static const struct sun4i_usb_phy_cfg sun4i_a10_cfg = { + .num_phys = 3, + .type = sun4i_a10_phy, + .disc_thresh = 3, + .phyctl_offset = REG_PHYCTL_A10, + .dedicated_clocks = false, + .enable_pmu_unk1 = false, +}; + +static const struct sun4i_usb_phy_cfg sun5i_a13_cfg = { + .num_phys = 2, + .type = sun4i_a10_phy, + .disc_thresh = 2, + .phyctl_offset = REG_PHYCTL_A10, + .dedicated_clocks = false, + .enable_pmu_unk1 = false, +}; + +static const struct sun4i_usb_phy_cfg sun6i_a31_cfg = { + .num_phys = 3, + .type = sun6i_a31_phy, + .disc_thresh = 3, + .phyctl_offset = REG_PHYCTL_A10, + .dedicated_clocks = true, + .enable_pmu_unk1 = false, +}; + +static const struct sun4i_usb_phy_cfg sun7i_a20_cfg = { + .num_phys = 3, + .type = sun4i_a10_phy, + .disc_thresh = 2, + .phyctl_offset = REG_PHYCTL_A10, + .dedicated_clocks = false, + .enable_pmu_unk1 = false, +}; + +static const struct sun4i_usb_phy_cfg sun8i_a23_cfg = { + .num_phys = 2, + .type = sun4i_a10_phy, + .disc_thresh = 3, + .phyctl_offset = REG_PHYCTL_A10, + .dedicated_clocks = true, + .enable_pmu_unk1 = false, +}; + +static const struct sun4i_usb_phy_cfg sun8i_a33_cfg = { + .num_phys = 2, + .type = sun8i_a33_phy, + .disc_thresh = 3, + .phyctl_offset = REG_PHYCTL_A33, + .dedicated_clocks = true, + .enable_pmu_unk1 = false, +}; + +static const struct sun4i_usb_phy_cfg sun8i_h3_cfg = { + .num_phys = 4, + .type = sun8i_h3_phy, + .disc_thresh = 3, + .phyctl_offset = REG_PHYCTL_A33, + .dedicated_clocks = true, + .enable_pmu_unk1 = true, + .phy0_dual_route = true, +}; + +static const struct sun4i_usb_phy_cfg sun8i_v3s_cfg = { + .num_phys = 1, + .type = sun8i_v3s_phy, + .disc_thresh = 3, + .phyctl_offset = REG_PHYCTL_A33, + .dedicated_clocks = true, + .enable_pmu_unk1 = true, +}; + +static const struct sun4i_usb_phy_cfg sun50i_a64_cfg = { + .num_phys = 2, + .type = sun50i_a64_phy, + .disc_thresh = 3, + .phyctl_offset = REG_PHYCTL_A33, + .dedicated_clocks = true, + .enable_pmu_unk1 = true, + .phy0_dual_route = true, +}; + +static const struct of_device_id sun4i_usb_phy_of_match[] = { + { .compatible = "allwinner,sun4i-a10-usb-phy", .data = &sun4i_a10_cfg }, + { .compatible = "allwinner,sun5i-a13-usb-phy", .data = &sun5i_a13_cfg }, + { .compatible = "allwinner,sun6i-a31-usb-phy", .data = &sun6i_a31_cfg }, + { .compatible = "allwinner,sun7i-a20-usb-phy", .data = &sun7i_a20_cfg }, + { .compatible = "allwinner,sun8i-a23-usb-phy", .data = &sun8i_a23_cfg }, + { .compatible = "allwinner,sun8i-a33-usb-phy", .data = &sun8i_a33_cfg }, + { .compatible = "allwinner,sun8i-h3-usb-phy", .data = &sun8i_h3_cfg }, + { .compatible = "allwinner,sun8i-v3s-usb-phy", .data = &sun8i_v3s_cfg }, + { .compatible = "allwinner,sun50i-a64-usb-phy", + .data = &sun50i_a64_cfg}, + { }, +}; +MODULE_DEVICE_TABLE(of, sun4i_usb_phy_of_match); + +static struct platform_driver sun4i_usb_phy_driver = { + .probe = sun4i_usb_phy_probe, + .remove = sun4i_usb_phy_remove, + .driver = { + .of_match_table = sun4i_usb_phy_of_match, + .name = "sun4i-usb-phy", + } +}; +module_platform_driver(sun4i_usb_phy_driver); + +MODULE_DESCRIPTION("Allwinner sun4i USB phy driver"); +MODULE_AUTHOR("Hans de Goede "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/allwinner/phy-sun9i-usb.c b/drivers/phy/allwinner/phy-sun9i-usb.c new file mode 100644 index 000000000000..28fce4bce638 --- /dev/null +++ b/drivers/phy/allwinner/phy-sun9i-usb.c @@ -0,0 +1,202 @@ +/* + * Allwinner sun9i USB phy driver + * + * Copyright (C) 2014-2015 Chen-Yu Tsai + * + * Based on phy-sun4i-usb.c from + * Hans de Goede + * + * and code from + * Allwinner Technology Co., Ltd. + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define SUNXI_AHB_INCR16_BURST_EN BIT(11) +#define SUNXI_AHB_INCR8_BURST_EN BIT(10) +#define SUNXI_AHB_INCR4_BURST_EN BIT(9) +#define SUNXI_AHB_INCRX_ALIGN_EN BIT(8) +#define SUNXI_ULPI_BYPASS_EN BIT(0) + +/* usb1 HSIC specific bits */ +#define SUNXI_EHCI_HS_FORCE BIT(20) +#define SUNXI_HSIC_CONNECT_DET BIT(17) +#define SUNXI_HSIC_CONNECT_INT BIT(16) +#define SUNXI_HSIC BIT(1) + +struct sun9i_usb_phy { + struct phy *phy; + void __iomem *pmu; + struct reset_control *reset; + struct clk *clk; + struct clk *hsic_clk; + enum usb_phy_interface type; +}; + +static void sun9i_usb_phy_passby(struct sun9i_usb_phy *phy, int enable) +{ + u32 bits, reg_value; + + bits = SUNXI_AHB_INCR16_BURST_EN | SUNXI_AHB_INCR8_BURST_EN | + SUNXI_AHB_INCR4_BURST_EN | SUNXI_AHB_INCRX_ALIGN_EN | + SUNXI_ULPI_BYPASS_EN; + + if (phy->type == USBPHY_INTERFACE_MODE_HSIC) + bits |= SUNXI_HSIC | SUNXI_EHCI_HS_FORCE | + SUNXI_HSIC_CONNECT_DET | SUNXI_HSIC_CONNECT_INT; + + reg_value = readl(phy->pmu); + + if (enable) + reg_value |= bits; + else + reg_value &= ~bits; + + writel(reg_value, phy->pmu); +} + +static int sun9i_usb_phy_init(struct phy *_phy) +{ + struct sun9i_usb_phy *phy = phy_get_drvdata(_phy); + int ret; + + ret = clk_prepare_enable(phy->clk); + if (ret) + goto err_clk; + + ret = clk_prepare_enable(phy->hsic_clk); + if (ret) + goto err_hsic_clk; + + ret = reset_control_deassert(phy->reset); + if (ret) + goto err_reset; + + sun9i_usb_phy_passby(phy, 1); + return 0; + +err_reset: + clk_disable_unprepare(phy->hsic_clk); + +err_hsic_clk: + clk_disable_unprepare(phy->clk); + +err_clk: + return ret; +} + +static int sun9i_usb_phy_exit(struct phy *_phy) +{ + struct sun9i_usb_phy *phy = phy_get_drvdata(_phy); + + sun9i_usb_phy_passby(phy, 0); + reset_control_assert(phy->reset); + clk_disable_unprepare(phy->hsic_clk); + clk_disable_unprepare(phy->clk); + + return 0; +} + +static const struct phy_ops sun9i_usb_phy_ops = { + .init = sun9i_usb_phy_init, + .exit = sun9i_usb_phy_exit, + .owner = THIS_MODULE, +}; + +static int sun9i_usb_phy_probe(struct platform_device *pdev) +{ + struct sun9i_usb_phy *phy; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct phy_provider *phy_provider; + struct resource *res; + + phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); + if (!phy) + return -ENOMEM; + + phy->type = of_usb_get_phy_mode(np); + if (phy->type == USBPHY_INTERFACE_MODE_HSIC) { + phy->clk = devm_clk_get(dev, "hsic_480M"); + if (IS_ERR(phy->clk)) { + dev_err(dev, "failed to get hsic_480M clock\n"); + return PTR_ERR(phy->clk); + } + + phy->hsic_clk = devm_clk_get(dev, "hsic_12M"); + if (IS_ERR(phy->hsic_clk)) { + dev_err(dev, "failed to get hsic_12M clock\n"); + return PTR_ERR(phy->hsic_clk); + } + + phy->reset = devm_reset_control_get(dev, "hsic"); + if (IS_ERR(phy->reset)) { + dev_err(dev, "failed to get reset control\n"); + return PTR_ERR(phy->reset); + } + } else { + phy->clk = devm_clk_get(dev, "phy"); + if (IS_ERR(phy->clk)) { + dev_err(dev, "failed to get phy clock\n"); + return PTR_ERR(phy->clk); + } + + phy->reset = devm_reset_control_get(dev, "phy"); + if (IS_ERR(phy->reset)) { + dev_err(dev, "failed to get reset control\n"); + return PTR_ERR(phy->reset); + } + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + phy->pmu = devm_ioremap_resource(dev, res); + if (IS_ERR(phy->pmu)) + return PTR_ERR(phy->pmu); + + phy->phy = devm_phy_create(dev, NULL, &sun9i_usb_phy_ops); + if (IS_ERR(phy->phy)) { + dev_err(dev, "failed to create PHY\n"); + return PTR_ERR(phy->phy); + } + + phy_set_drvdata(phy->phy, phy); + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id sun9i_usb_phy_of_match[] = { + { .compatible = "allwinner,sun9i-a80-usb-phy" }, + { }, +}; +MODULE_DEVICE_TABLE(of, sun9i_usb_phy_of_match); + +static struct platform_driver sun9i_usb_phy_driver = { + .probe = sun9i_usb_phy_probe, + .driver = { + .of_match_table = sun9i_usb_phy_of_match, + .name = "sun9i-usb-phy", + } +}; +module_platform_driver(sun9i_usb_phy_driver); + +MODULE_DESCRIPTION("Allwinner sun9i USB phy driver"); +MODULE_AUTHOR("Chen-Yu Tsai "); +MODULE_LICENSE("GPL"); diff --git a/drivers/phy/amlogic/Kconfig b/drivers/phy/amlogic/Kconfig new file mode 100644 index 000000000000..edcd5b65179f --- /dev/null +++ b/drivers/phy/amlogic/Kconfig @@ -0,0 +1,14 @@ +# +# Phy drivers for Amlogic platforms +# +config PHY_MESON8B_USB2 + tristate "Meson8b and GXBB USB2 PHY driver" + default ARCH_MESON + depends on OF && (ARCH_MESON || COMPILE_TEST) + depends on USB_SUPPORT + select USB_COMMON + select GENERIC_PHY + help + Enable this to support the Meson USB2 PHYs found in Meson8b + and GXBB SoCs. + If unsure, say N. diff --git a/drivers/phy/amlogic/Makefile b/drivers/phy/amlogic/Makefile new file mode 100644 index 000000000000..47b6eecc3864 --- /dev/null +++ b/drivers/phy/amlogic/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_PHY_MESON8B_USB2) += phy-meson8b-usb2.o diff --git a/drivers/phy/amlogic/phy-meson8b-usb2.c b/drivers/phy/amlogic/phy-meson8b-usb2.c new file mode 100644 index 000000000000..30f56a6a411f --- /dev/null +++ b/drivers/phy/amlogic/phy-meson8b-usb2.c @@ -0,0 +1,286 @@ +/* + * Meson8b and GXBB USB2 PHY driver + * + * Copyright (C) 2016 Martin Blumenstingl + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define REG_CONFIG 0x00 + #define REG_CONFIG_CLK_EN BIT(0) + #define REG_CONFIG_CLK_SEL_MASK GENMASK(3, 1) + #define REG_CONFIG_CLK_DIV_MASK GENMASK(10, 4) + #define REG_CONFIG_CLK_32k_ALTSEL BIT(15) + #define REG_CONFIG_TEST_TRIG BIT(31) + +#define REG_CTRL 0x04 + #define REG_CTRL_SOFT_PRST BIT(0) + #define REG_CTRL_SOFT_HRESET BIT(1) + #define REG_CTRL_SS_SCALEDOWN_MODE_MASK GENMASK(3, 2) + #define REG_CTRL_CLK_DET_RST BIT(4) + #define REG_CTRL_INTR_SEL BIT(5) + #define REG_CTRL_CLK_DETECTED BIT(8) + #define REG_CTRL_SOF_SENT_RCVD_TGL BIT(9) + #define REG_CTRL_SOF_TOGGLE_OUT BIT(10) + #define REG_CTRL_POWER_ON_RESET BIT(15) + #define REG_CTRL_SLEEPM BIT(16) + #define REG_CTRL_TX_BITSTUFF_ENN_H BIT(17) + #define REG_CTRL_TX_BITSTUFF_ENN BIT(18) + #define REG_CTRL_COMMON_ON BIT(19) + #define REG_CTRL_REF_CLK_SEL_MASK GENMASK(21, 20) + #define REG_CTRL_REF_CLK_SEL_SHIFT 20 + #define REG_CTRL_FSEL_MASK GENMASK(24, 22) + #define REG_CTRL_FSEL_SHIFT 22 + #define REG_CTRL_PORT_RESET BIT(25) + #define REG_CTRL_THREAD_ID_MASK GENMASK(31, 26) + +#define REG_ENDP_INTR 0x08 + +/* bits [31:26], [24:21] and [15:3] seem to be read-only */ +#define REG_ADP_BC 0x0c + #define REG_ADP_BC_VBUS_VLD_EXT_SEL BIT(0) + #define REG_ADP_BC_VBUS_VLD_EXT BIT(1) + #define REG_ADP_BC_OTG_DISABLE BIT(2) + #define REG_ADP_BC_ID_PULLUP BIT(3) + #define REG_ADP_BC_DRV_VBUS BIT(4) + #define REG_ADP_BC_ADP_PRB_EN BIT(5) + #define REG_ADP_BC_ADP_DISCHARGE BIT(6) + #define REG_ADP_BC_ADP_CHARGE BIT(7) + #define REG_ADP_BC_SESS_END BIT(8) + #define REG_ADP_BC_DEVICE_SESS_VLD BIT(9) + #define REG_ADP_BC_B_VALID BIT(10) + #define REG_ADP_BC_A_VALID BIT(11) + #define REG_ADP_BC_ID_DIG BIT(12) + #define REG_ADP_BC_VBUS_VALID BIT(13) + #define REG_ADP_BC_ADP_PROBE BIT(14) + #define REG_ADP_BC_ADP_SENSE BIT(15) + #define REG_ADP_BC_ACA_ENABLE BIT(16) + #define REG_ADP_BC_DCD_ENABLE BIT(17) + #define REG_ADP_BC_VDAT_DET_EN_B BIT(18) + #define REG_ADP_BC_VDAT_SRC_EN_B BIT(19) + #define REG_ADP_BC_CHARGE_SEL BIT(20) + #define REG_ADP_BC_CHARGE_DETECT BIT(21) + #define REG_ADP_BC_ACA_PIN_RANGE_C BIT(22) + #define REG_ADP_BC_ACA_PIN_RANGE_B BIT(23) + #define REG_ADP_BC_ACA_PIN_RANGE_A BIT(24) + #define REG_ADP_BC_ACA_PIN_GND BIT(25) + #define REG_ADP_BC_ACA_PIN_FLOAT BIT(26) + +#define REG_DBG_UART 0x10 + +#define REG_TEST 0x14 + #define REG_TEST_DATA_IN_MASK GENMASK(3, 0) + #define REG_TEST_EN_MASK GENMASK(7, 4) + #define REG_TEST_ADDR_MASK GENMASK(11, 8) + #define REG_TEST_DATA_OUT_SEL BIT(12) + #define REG_TEST_CLK BIT(13) + #define REG_TEST_VA_TEST_EN_B_MASK GENMASK(15, 14) + #define REG_TEST_DATA_OUT_MASK GENMASK(19, 16) + #define REG_TEST_DISABLE_ID_PULLUP BIT(20) + +#define REG_TUNE 0x18 + #define REG_TUNE_TX_RES_TUNE_MASK GENMASK(1, 0) + #define REG_TUNE_TX_HSXV_TUNE_MASK GENMASK(3, 2) + #define REG_TUNE_TX_VREF_TUNE_MASK GENMASK(7, 4) + #define REG_TUNE_TX_RISE_TUNE_MASK GENMASK(9, 8) + #define REG_TUNE_TX_PREEMP_PULSE_TUNE BIT(10) + #define REG_TUNE_TX_PREEMP_AMP_TUNE_MASK GENMASK(12, 11) + #define REG_TUNE_TX_FSLS_TUNE_MASK GENMASK(16, 13) + #define REG_TUNE_SQRX_TUNE_MASK GENMASK(19, 17) + #define REG_TUNE_OTG_TUNE GENMASK(22, 20) + #define REG_TUNE_COMP_DIS_TUNE GENMASK(25, 23) + #define REG_TUNE_HOST_DM_PULLDOWN BIT(26) + #define REG_TUNE_HOST_DP_PULLDOWN BIT(27) + +#define RESET_COMPLETE_TIME 500 +#define ACA_ENABLE_COMPLETE_TIME 50 + +struct phy_meson8b_usb2_priv { + void __iomem *regs; + enum usb_dr_mode dr_mode; + struct clk *clk_usb_general; + struct clk *clk_usb; + struct reset_control *reset; +}; + +static u32 phy_meson8b_usb2_read(struct phy_meson8b_usb2_priv *phy_priv, + u32 reg) +{ + return readl(phy_priv->regs + reg); +} + +static void phy_meson8b_usb2_mask_bits(struct phy_meson8b_usb2_priv *phy_priv, + u32 reg, u32 mask, u32 value) +{ + u32 data; + + data = phy_meson8b_usb2_read(phy_priv, reg); + data &= ~mask; + data |= (value & mask); + + writel(data, phy_priv->regs + reg); +} + +static int phy_meson8b_usb2_power_on(struct phy *phy) +{ + struct phy_meson8b_usb2_priv *priv = phy_get_drvdata(phy); + int ret; + + if (!IS_ERR_OR_NULL(priv->reset)) { + ret = reset_control_reset(priv->reset); + if (ret) { + dev_err(&phy->dev, "Failed to trigger USB reset\n"); + return ret; + } + } + + ret = clk_prepare_enable(priv->clk_usb_general); + if (ret) { + dev_err(&phy->dev, "Failed to enable USB general clock\n"); + return ret; + } + + ret = clk_prepare_enable(priv->clk_usb); + if (ret) { + dev_err(&phy->dev, "Failed to enable USB DDR clock\n"); + clk_disable_unprepare(priv->clk_usb_general); + return ret; + } + + phy_meson8b_usb2_mask_bits(priv, REG_CONFIG, REG_CONFIG_CLK_32k_ALTSEL, + REG_CONFIG_CLK_32k_ALTSEL); + + phy_meson8b_usb2_mask_bits(priv, REG_CTRL, REG_CTRL_REF_CLK_SEL_MASK, + 0x2 << REG_CTRL_REF_CLK_SEL_SHIFT); + + phy_meson8b_usb2_mask_bits(priv, REG_CTRL, REG_CTRL_FSEL_MASK, + 0x5 << REG_CTRL_FSEL_SHIFT); + + /* reset the PHY */ + phy_meson8b_usb2_mask_bits(priv, REG_CTRL, REG_CTRL_POWER_ON_RESET, + REG_CTRL_POWER_ON_RESET); + udelay(RESET_COMPLETE_TIME); + phy_meson8b_usb2_mask_bits(priv, REG_CTRL, REG_CTRL_POWER_ON_RESET, 0); + udelay(RESET_COMPLETE_TIME); + + phy_meson8b_usb2_mask_bits(priv, REG_CTRL, REG_CTRL_SOF_TOGGLE_OUT, + REG_CTRL_SOF_TOGGLE_OUT); + + if (priv->dr_mode == USB_DR_MODE_HOST) { + phy_meson8b_usb2_mask_bits(priv, REG_ADP_BC, + REG_ADP_BC_ACA_ENABLE, + REG_ADP_BC_ACA_ENABLE); + + udelay(ACA_ENABLE_COMPLETE_TIME); + + if (phy_meson8b_usb2_read(priv, REG_ADP_BC) & + REG_ADP_BC_ACA_PIN_FLOAT) { + dev_warn(&phy->dev, "USB ID detect failed!\n"); + clk_disable_unprepare(priv->clk_usb); + clk_disable_unprepare(priv->clk_usb_general); + return -EINVAL; + } + } + + return 0; +} + +static int phy_meson8b_usb2_power_off(struct phy *phy) +{ + struct phy_meson8b_usb2_priv *priv = phy_get_drvdata(phy); + + clk_disable_unprepare(priv->clk_usb); + clk_disable_unprepare(priv->clk_usb_general); + + return 0; +} + +static const struct phy_ops phy_meson8b_usb2_ops = { + .power_on = phy_meson8b_usb2_power_on, + .power_off = phy_meson8b_usb2_power_off, + .owner = THIS_MODULE, +}; + +static int phy_meson8b_usb2_probe(struct platform_device *pdev) +{ + struct phy_meson8b_usb2_priv *priv; + struct resource *res; + struct phy *phy; + struct phy_provider *phy_provider; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->regs)) + return PTR_ERR(priv->regs); + + priv->clk_usb_general = devm_clk_get(&pdev->dev, "usb_general"); + if (IS_ERR(priv->clk_usb_general)) + return PTR_ERR(priv->clk_usb_general); + + priv->clk_usb = devm_clk_get(&pdev->dev, "usb"); + if (IS_ERR(priv->clk_usb)) + return PTR_ERR(priv->clk_usb); + + priv->reset = devm_reset_control_get_optional_shared(&pdev->dev, NULL); + if (PTR_ERR(priv->reset) == -EPROBE_DEFER) + return PTR_ERR(priv->reset); + + priv->dr_mode = of_usb_get_dr_mode_by_phy(pdev->dev.of_node, -1); + if (priv->dr_mode == USB_DR_MODE_UNKNOWN) { + dev_err(&pdev->dev, + "missing dual role configuration of the controller\n"); + return -EINVAL; + } + + phy = devm_phy_create(&pdev->dev, NULL, &phy_meson8b_usb2_ops); + if (IS_ERR(phy)) { + dev_err(&pdev->dev, "failed to create PHY\n"); + return PTR_ERR(phy); + } + + phy_set_drvdata(phy, priv); + + phy_provider = + devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id phy_meson8b_usb2_of_match[] = { + { .compatible = "amlogic,meson8b-usb2-phy", }, + { .compatible = "amlogic,meson-gxbb-usb2-phy", }, + { }, +}; +MODULE_DEVICE_TABLE(of, phy_meson8b_usb2_of_match); + +static struct platform_driver phy_meson8b_usb2_driver = { + .probe = phy_meson8b_usb2_probe, + .driver = { + .name = "phy-meson-usb2", + .of_match_table = phy_meson8b_usb2_of_match, + }, +}; +module_platform_driver(phy_meson8b_usb2_driver); + +MODULE_AUTHOR("Martin Blumenstingl "); +MODULE_DESCRIPTION("Meson8b and GXBB USB2 PHY driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/phy/broadcom/Kconfig b/drivers/phy/broadcom/Kconfig new file mode 100644 index 000000000000..d2d99023ec50 --- /dev/null +++ b/drivers/phy/broadcom/Kconfig @@ -0,0 +1,55 @@ +# +# Phy drivers for Broadcom platforms +# +config PHY_CYGNUS_PCIE + tristate "Broadcom Cygnus PCIe PHY driver" + depends on OF && (ARCH_BCM_CYGNUS || COMPILE_TEST) + select GENERIC_PHY + default ARCH_BCM_CYGNUS + help + Enable this to support the Broadcom Cygnus PCIe PHY. + If unsure, say N. + +config BCM_KONA_USB2_PHY + tristate "Broadcom Kona USB2 PHY Driver" + depends on HAS_IOMEM + select GENERIC_PHY + help + Enable this to support the Broadcom Kona USB 2.0 PHY. + +config PHY_BCM_NS_USB2 + tristate "Broadcom Northstar USB 2.0 PHY Driver" + depends on ARCH_BCM_IPROC || COMPILE_TEST + depends on HAS_IOMEM && OF + select GENERIC_PHY + help + Enable this to support Broadcom USB 2.0 PHY connected to the USB + controller on Northstar family. + +config PHY_BCM_NS_USB3 + tristate "Broadcom Northstar USB 3.0 PHY Driver" + depends on ARCH_BCM_IPROC || COMPILE_TEST + depends on HAS_IOMEM && OF + select GENERIC_PHY + help + Enable this to support Broadcom USB 3.0 PHY connected to the USB + controller on Northstar family. + +config PHY_NS2_PCIE + tristate "Broadcom Northstar2 PCIe PHY driver" + depends on OF && MDIO_BUS_MUX_BCM_IPROC + select GENERIC_PHY + default ARCH_BCM_IPROC + help + Enable this to support the Broadcom Northstar2 PCIe PHY. + If unsure, say N. + +config PHY_BRCM_SATA + tristate "Broadcom SATA PHY driver" + depends on ARCH_BRCMSTB || ARCH_BCM_IPROC || BMIPS_GENERIC || COMPILE_TEST + depends on OF + select GENERIC_PHY + default ARCH_BCM_IPROC + help + Enable this to support the Broadcom SATA PHY. + If unsure, say N. diff --git a/drivers/phy/broadcom/Makefile b/drivers/phy/broadcom/Makefile new file mode 100644 index 000000000000..357a7d16529f --- /dev/null +++ b/drivers/phy/broadcom/Makefile @@ -0,0 +1,6 @@ +obj-$(CONFIG_PHY_CYGNUS_PCIE) += phy-bcm-cygnus-pcie.o +obj-$(CONFIG_BCM_KONA_USB2_PHY) += phy-bcm-kona-usb2.o +obj-$(CONFIG_PHY_BCM_NS_USB2) += phy-bcm-ns-usb2.o +obj-$(CONFIG_PHY_BCM_NS_USB3) += phy-bcm-ns-usb3.o +obj-$(CONFIG_PHY_NS2_PCIE) += phy-bcm-ns2-pcie.o +obj-$(CONFIG_PHY_BRCM_SATA) += phy-brcm-sata.o diff --git a/drivers/phy/broadcom/phy-bcm-cygnus-pcie.c b/drivers/phy/broadcom/phy-bcm-cygnus-pcie.c new file mode 100644 index 000000000000..0f4ac5d63cff --- /dev/null +++ b/drivers/phy/broadcom/phy-bcm-cygnus-pcie.c @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2015 Broadcom Corporation + * + * 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. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include + +#define PCIE_CFG_OFFSET 0x00 +#define PCIE1_PHY_IDDQ_SHIFT 10 +#define PCIE0_PHY_IDDQ_SHIFT 2 + +enum cygnus_pcie_phy_id { + CYGNUS_PHY_PCIE0 = 0, + CYGNUS_PHY_PCIE1, + MAX_NUM_PHYS, +}; + +struct cygnus_pcie_phy_core; + +/** + * struct cygnus_pcie_phy - Cygnus PCIe PHY device + * @core: pointer to the Cygnus PCIe PHY core control + * @id: internal ID to identify the Cygnus PCIe PHY + * @phy: pointer to the kernel PHY device + */ +struct cygnus_pcie_phy { + struct cygnus_pcie_phy_core *core; + enum cygnus_pcie_phy_id id; + struct phy *phy; +}; + +/** + * struct cygnus_pcie_phy_core - Cygnus PCIe PHY core control + * @dev: pointer to device + * @base: base register + * @lock: mutex to protect access to individual PHYs + * @phys: pointer to Cygnus PHY device + */ +struct cygnus_pcie_phy_core { + struct device *dev; + void __iomem *base; + struct mutex lock; + struct cygnus_pcie_phy phys[MAX_NUM_PHYS]; +}; + +static int cygnus_pcie_power_config(struct cygnus_pcie_phy *phy, bool enable) +{ + struct cygnus_pcie_phy_core *core = phy->core; + unsigned shift; + u32 val; + + mutex_lock(&core->lock); + + switch (phy->id) { + case CYGNUS_PHY_PCIE0: + shift = PCIE0_PHY_IDDQ_SHIFT; + break; + + case CYGNUS_PHY_PCIE1: + shift = PCIE1_PHY_IDDQ_SHIFT; + break; + + default: + mutex_unlock(&core->lock); + dev_err(core->dev, "PCIe PHY %d invalid\n", phy->id); + return -EINVAL; + } + + if (enable) { + val = readl(core->base + PCIE_CFG_OFFSET); + val &= ~BIT(shift); + writel(val, core->base + PCIE_CFG_OFFSET); + /* + * Wait 50 ms for the PCIe Serdes to stabilize after the analog + * front end is brought up + */ + msleep(50); + } else { + val = readl(core->base + PCIE_CFG_OFFSET); + val |= BIT(shift); + writel(val, core->base + PCIE_CFG_OFFSET); + } + + mutex_unlock(&core->lock); + dev_dbg(core->dev, "PCIe PHY %d %s\n", phy->id, + enable ? "enabled" : "disabled"); + return 0; +} + +static int cygnus_pcie_phy_power_on(struct phy *p) +{ + struct cygnus_pcie_phy *phy = phy_get_drvdata(p); + + return cygnus_pcie_power_config(phy, true); +} + +static int cygnus_pcie_phy_power_off(struct phy *p) +{ + struct cygnus_pcie_phy *phy = phy_get_drvdata(p); + + return cygnus_pcie_power_config(phy, false); +} + +static const struct phy_ops cygnus_pcie_phy_ops = { + .power_on = cygnus_pcie_phy_power_on, + .power_off = cygnus_pcie_phy_power_off, + .owner = THIS_MODULE, +}; + +static int cygnus_pcie_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node, *child; + struct cygnus_pcie_phy_core *core; + struct phy_provider *provider; + struct resource *res; + unsigned cnt = 0; + int ret; + + if (of_get_child_count(node) == 0) { + dev_err(dev, "PHY no child node\n"); + return -ENODEV; + } + + core = devm_kzalloc(dev, sizeof(*core), GFP_KERNEL); + if (!core) + return -ENOMEM; + + core->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + core->base = devm_ioremap_resource(dev, res); + if (IS_ERR(core->base)) + return PTR_ERR(core->base); + + mutex_init(&core->lock); + + for_each_available_child_of_node(node, child) { + unsigned int id; + struct cygnus_pcie_phy *p; + + if (of_property_read_u32(child, "reg", &id)) { + dev_err(dev, "missing reg property for %s\n", + child->name); + ret = -EINVAL; + goto put_child; + } + + if (id >= MAX_NUM_PHYS) { + dev_err(dev, "invalid PHY id: %u\n", id); + ret = -EINVAL; + goto put_child; + } + + if (core->phys[id].phy) { + dev_err(dev, "duplicated PHY id: %u\n", id); + ret = -EINVAL; + goto put_child; + } + + p = &core->phys[id]; + p->phy = devm_phy_create(dev, child, &cygnus_pcie_phy_ops); + if (IS_ERR(p->phy)) { + dev_err(dev, "failed to create PHY\n"); + ret = PTR_ERR(p->phy); + goto put_child; + } + + p->core = core; + p->id = id; + phy_set_drvdata(p->phy, p); + cnt++; + } + + dev_set_drvdata(dev, core); + + provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(provider)) { + dev_err(dev, "failed to register PHY provider\n"); + return PTR_ERR(provider); + } + + dev_dbg(dev, "registered %u PCIe PHY(s)\n", cnt); + + return 0; +put_child: + of_node_put(child); + return ret; +} + +static const struct of_device_id cygnus_pcie_phy_match_table[] = { + { .compatible = "brcm,cygnus-pcie-phy" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, cygnus_pcie_phy_match_table); + +static struct platform_driver cygnus_pcie_phy_driver = { + .driver = { + .name = "cygnus-pcie-phy", + .of_match_table = cygnus_pcie_phy_match_table, + }, + .probe = cygnus_pcie_phy_probe, +}; +module_platform_driver(cygnus_pcie_phy_driver); + +MODULE_AUTHOR("Ray Jui "); +MODULE_DESCRIPTION("Broadcom Cygnus PCIe PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/broadcom/phy-bcm-kona-usb2.c b/drivers/phy/broadcom/phy-bcm-kona-usb2.c new file mode 100644 index 000000000000..7b67fe49e30b --- /dev/null +++ b/drivers/phy/broadcom/phy-bcm-kona-usb2.c @@ -0,0 +1,155 @@ +/* + * phy-bcm-kona-usb2.c - Broadcom Kona USB2 Phy Driver + * + * Copyright (C) 2013 Linaro Limited + * Matt Porter + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define OTGCTL (0) +#define OTGCTL_OTGSTAT2 BIT(31) +#define OTGCTL_OTGSTAT1 BIT(30) +#define OTGCTL_PRST_N_SW BIT(11) +#define OTGCTL_HRESET_N BIT(10) +#define OTGCTL_UTMI_LINE_STATE1 BIT(9) +#define OTGCTL_UTMI_LINE_STATE0 BIT(8) + +#define P1CTL (8) +#define P1CTL_SOFT_RESET BIT(1) +#define P1CTL_NON_DRIVING BIT(0) + +struct bcm_kona_usb { + void __iomem *regs; +}; + +static void bcm_kona_usb_phy_power(struct bcm_kona_usb *phy, int on) +{ + u32 val; + + val = readl(phy->regs + OTGCTL); + if (on) { + /* Configure and power PHY */ + val &= ~(OTGCTL_OTGSTAT2 | OTGCTL_OTGSTAT1 | + OTGCTL_UTMI_LINE_STATE1 | OTGCTL_UTMI_LINE_STATE0); + val |= OTGCTL_PRST_N_SW | OTGCTL_HRESET_N; + } else { + val &= ~(OTGCTL_PRST_N_SW | OTGCTL_HRESET_N); + } + writel(val, phy->regs + OTGCTL); +} + +static int bcm_kona_usb_phy_init(struct phy *gphy) +{ + struct bcm_kona_usb *phy = phy_get_drvdata(gphy); + u32 val; + + /* Soft reset PHY */ + val = readl(phy->regs + P1CTL); + val &= ~P1CTL_NON_DRIVING; + val |= P1CTL_SOFT_RESET; + writel(val, phy->regs + P1CTL); + writel(val & ~P1CTL_SOFT_RESET, phy->regs + P1CTL); + /* Reset needs to be asserted for 2ms */ + mdelay(2); + writel(val | P1CTL_SOFT_RESET, phy->regs + P1CTL); + + return 0; +} + +static int bcm_kona_usb_phy_power_on(struct phy *gphy) +{ + struct bcm_kona_usb *phy = phy_get_drvdata(gphy); + + bcm_kona_usb_phy_power(phy, 1); + + return 0; +} + +static int bcm_kona_usb_phy_power_off(struct phy *gphy) +{ + struct bcm_kona_usb *phy = phy_get_drvdata(gphy); + + bcm_kona_usb_phy_power(phy, 0); + + return 0; +} + +static const struct phy_ops ops = { + .init = bcm_kona_usb_phy_init, + .power_on = bcm_kona_usb_phy_power_on, + .power_off = bcm_kona_usb_phy_power_off, + .owner = THIS_MODULE, +}; + +static int bcm_kona_usb2_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct bcm_kona_usb *phy; + struct resource *res; + struct phy *gphy; + struct phy_provider *phy_provider; + + phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); + if (!phy) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + phy->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(phy->regs)) + return PTR_ERR(phy->regs); + + platform_set_drvdata(pdev, phy); + + gphy = devm_phy_create(dev, NULL, &ops); + if (IS_ERR(gphy)) + return PTR_ERR(gphy); + + /* The Kona PHY supports an 8-bit wide UTMI interface */ + phy_set_bus_width(gphy, 8); + + phy_set_drvdata(gphy, phy); + + phy_provider = devm_of_phy_provider_register(dev, + of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id bcm_kona_usb2_dt_ids[] = { + { .compatible = "brcm,kona-usb2-phy" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, bcm_kona_usb2_dt_ids); + +static struct platform_driver bcm_kona_usb2_driver = { + .probe = bcm_kona_usb2_probe, + .driver = { + .name = "bcm-kona-usb2", + .of_match_table = bcm_kona_usb2_dt_ids, + }, +}; + +module_platform_driver(bcm_kona_usb2_driver); + +MODULE_ALIAS("platform:bcm-kona-usb2"); +MODULE_AUTHOR("Matt Porter "); +MODULE_DESCRIPTION("BCM Kona USB 2.0 PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/broadcom/phy-bcm-ns-usb2.c b/drivers/phy/broadcom/phy-bcm-ns-usb2.c new file mode 100644 index 000000000000..58dff80e9386 --- /dev/null +++ b/drivers/phy/broadcom/phy-bcm-ns-usb2.c @@ -0,0 +1,137 @@ +/* + * Broadcom Northstar USB 2.0 PHY Driver + * + * Copyright (C) 2016 Rafał Miłecki + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct bcm_ns_usb2 { + struct device *dev; + struct clk *ref_clk; + struct phy *phy; + void __iomem *dmu; +}; + +static int bcm_ns_usb2_phy_init(struct phy *phy) +{ + struct bcm_ns_usb2 *usb2 = phy_get_drvdata(phy); + struct device *dev = usb2->dev; + void __iomem *dmu = usb2->dmu; + u32 ref_clk_rate, usb2ctl, usb_pll_ndiv, usb_pll_pdiv; + int err = 0; + + err = clk_prepare_enable(usb2->ref_clk); + if (err < 0) { + dev_err(dev, "Failed to prepare ref clock: %d\n", err); + goto err_out; + } + + ref_clk_rate = clk_get_rate(usb2->ref_clk); + if (!ref_clk_rate) { + dev_err(dev, "Failed to get ref clock rate\n"); + err = -EINVAL; + goto err_clk_off; + } + + usb2ctl = readl(dmu + BCMA_DMU_CRU_USB2_CONTROL); + + if (usb2ctl & BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_PDIV_MASK) { + usb_pll_pdiv = usb2ctl; + usb_pll_pdiv &= BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_PDIV_MASK; + usb_pll_pdiv >>= BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_PDIV_SHIFT; + } else { + usb_pll_pdiv = 1 << 3; + } + + /* Calculate ndiv based on a solid 1920 MHz that is for USB2 PHY */ + usb_pll_ndiv = (1920000000 * usb_pll_pdiv) / ref_clk_rate; + + /* Unlock DMU PLL settings with some magic value */ + writel(0x0000ea68, dmu + BCMA_DMU_CRU_CLKSET_KEY); + + /* Write USB 2.0 PLL control setting */ + usb2ctl &= ~BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_NDIV_MASK; + usb2ctl |= usb_pll_ndiv << BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_NDIV_SHIFT; + writel(usb2ctl, dmu + BCMA_DMU_CRU_USB2_CONTROL); + + /* Lock DMU PLL settings */ + writel(0x00000000, dmu + BCMA_DMU_CRU_CLKSET_KEY); + +err_clk_off: + clk_disable_unprepare(usb2->ref_clk); +err_out: + return err; +} + +static const struct phy_ops ops = { + .init = bcm_ns_usb2_phy_init, + .owner = THIS_MODULE, +}; + +static int bcm_ns_usb2_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct bcm_ns_usb2 *usb2; + struct resource *res; + struct phy_provider *phy_provider; + + usb2 = devm_kzalloc(&pdev->dev, sizeof(*usb2), GFP_KERNEL); + if (!usb2) + return -ENOMEM; + usb2->dev = dev; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dmu"); + usb2->dmu = devm_ioremap_resource(dev, res); + if (IS_ERR(usb2->dmu)) { + dev_err(dev, "Failed to map DMU regs\n"); + return PTR_ERR(usb2->dmu); + } + + usb2->ref_clk = devm_clk_get(dev, "phy-ref-clk"); + if (IS_ERR(usb2->ref_clk)) { + dev_err(dev, "Clock not defined\n"); + return PTR_ERR(usb2->ref_clk); + } + + usb2->phy = devm_phy_create(dev, NULL, &ops); + if (IS_ERR(usb2->phy)) + return PTR_ERR(usb2->phy); + + phy_set_drvdata(usb2->phy, usb2); + platform_set_drvdata(pdev, usb2); + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id bcm_ns_usb2_id_table[] = { + { .compatible = "brcm,ns-usb2-phy", }, + {}, +}; +MODULE_DEVICE_TABLE(of, bcm_ns_usb2_id_table); + +static struct platform_driver bcm_ns_usb2_driver = { + .probe = bcm_ns_usb2_probe, + .driver = { + .name = "bcm_ns_usb2", + .of_match_table = bcm_ns_usb2_id_table, + }, +}; +module_platform_driver(bcm_ns_usb2_driver); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/broadcom/phy-bcm-ns-usb3.c b/drivers/phy/broadcom/phy-bcm-ns-usb3.c new file mode 100644 index 000000000000..22b5e7047fa6 --- /dev/null +++ b/drivers/phy/broadcom/phy-bcm-ns-usb3.c @@ -0,0 +1,303 @@ +/* + * Broadcom Northstar USB 3.0 PHY Driver + * + * Copyright (C) 2016 Rafał Miłecki + * Copyright (C) 2016 Broadcom + * + * All magic values used for initialization (and related comments) were obtained + * from Broadcom's SDK: + * Copyright (c) Broadcom Corp, 2012 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define BCM_NS_USB3_MII_MNG_TIMEOUT_US 1000 /* usecs */ + +#define BCM_NS_USB3_PHY_BASE_ADDR_REG 0x1f +#define BCM_NS_USB3_PHY_PLL30_BLOCK 0x8000 +#define BCM_NS_USB3_PHY_TX_PMD_BLOCK 0x8040 +#define BCM_NS_USB3_PHY_PIPE_BLOCK 0x8060 + +/* Registers of PLL30 block */ +#define BCM_NS_USB3_PLL_CONTROL 0x01 +#define BCM_NS_USB3_PLLA_CONTROL0 0x0a +#define BCM_NS_USB3_PLLA_CONTROL1 0x0b + +/* Registers of TX PMD block */ +#define BCM_NS_USB3_TX_PMD_CONTROL1 0x01 + +/* Registers of PIPE block */ +#define BCM_NS_USB3_LFPS_CMP 0x02 +#define BCM_NS_USB3_LFPS_DEGLITCH 0x03 + +enum bcm_ns_family { + BCM_NS_UNKNOWN, + BCM_NS_AX, + BCM_NS_BX, +}; + +struct bcm_ns_usb3 { + struct device *dev; + enum bcm_ns_family family; + void __iomem *dmp; + void __iomem *ccb_mii; + struct phy *phy; +}; + +static const struct of_device_id bcm_ns_usb3_id_table[] = { + { + .compatible = "brcm,ns-ax-usb3-phy", + .data = (int *)BCM_NS_AX, + }, + { + .compatible = "brcm,ns-bx-usb3-phy", + .data = (int *)BCM_NS_BX, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, bcm_ns_usb3_id_table); + +static int bcm_ns_usb3_wait_reg(struct bcm_ns_usb3 *usb3, void __iomem *addr, + u32 mask, u32 value, unsigned long timeout) +{ + unsigned long deadline = jiffies + timeout; + u32 val; + + do { + val = readl(addr); + if ((val & mask) == value) + return 0; + cpu_relax(); + udelay(10); + } while (!time_after_eq(jiffies, deadline)); + + dev_err(usb3->dev, "Timeout waiting for register %p\n", addr); + + return -EBUSY; +} + +static inline int bcm_ns_usb3_mii_mng_wait_idle(struct bcm_ns_usb3 *usb3) +{ + return bcm_ns_usb3_wait_reg(usb3, usb3->ccb_mii + BCMA_CCB_MII_MNG_CTL, + 0x0100, 0x0000, + usecs_to_jiffies(BCM_NS_USB3_MII_MNG_TIMEOUT_US)); +} + +static int bcm_ns_usb3_mdio_phy_write(struct bcm_ns_usb3 *usb3, u16 reg, + u16 value) +{ + u32 tmp = 0; + int err; + + err = bcm_ns_usb3_mii_mng_wait_idle(usb3); + if (err < 0) { + dev_err(usb3->dev, "Couldn't write 0x%08x value\n", value); + return err; + } + + /* TODO: Use a proper MDIO bus layer */ + tmp |= 0x58020000; /* Magic value for MDIO PHY write */ + tmp |= reg << 18; + tmp |= value; + writel(tmp, usb3->ccb_mii + BCMA_CCB_MII_MNG_CMD_DATA); + + return 0; +} + +static int bcm_ns_usb3_phy_init_ns_bx(struct bcm_ns_usb3 *usb3) +{ + int err; + + /* Enable MDIO. Setting MDCDIV as 26 */ + writel(0x0000009a, usb3->ccb_mii + BCMA_CCB_MII_MNG_CTL); + + /* Wait for MDIO? */ + udelay(2); + + /* USB3 PLL Block */ + err = bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, + BCM_NS_USB3_PHY_PLL30_BLOCK); + if (err < 0) + return err; + + /* Assert Ana_Pllseq start */ + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLL_CONTROL, 0x1000); + + /* Assert CML Divider ratio to 26 */ + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLLA_CONTROL0, 0x6400); + + /* Asserting PLL Reset */ + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLLA_CONTROL1, 0xc000); + + /* Deaaserting PLL Reset */ + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLLA_CONTROL1, 0x8000); + + /* Waiting MII Mgt interface idle */ + bcm_ns_usb3_mii_mng_wait_idle(usb3); + + /* Deasserting USB3 system reset */ + writel(0, usb3->dmp + BCMA_RESET_CTL); + + /* PLL frequency monitor enable */ + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLL_CONTROL, 0x9000); + + /* PIPE Block */ + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, + BCM_NS_USB3_PHY_PIPE_BLOCK); + + /* CMPMAX & CMPMINTH setting */ + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_LFPS_CMP, 0xf30d); + + /* DEGLITCH MIN & MAX setting */ + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_LFPS_DEGLITCH, 0x6302); + + /* TXPMD block */ + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, + BCM_NS_USB3_PHY_TX_PMD_BLOCK); + + /* Enabling SSC */ + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_TX_PMD_CONTROL1, 0x1003); + + /* Waiting MII Mgt interface idle */ + bcm_ns_usb3_mii_mng_wait_idle(usb3); + + return 0; +} + +static int bcm_ns_usb3_phy_init_ns_ax(struct bcm_ns_usb3 *usb3) +{ + int err; + + /* Enable MDIO. Setting MDCDIV as 26 */ + writel(0x0000009a, usb3->ccb_mii + BCMA_CCB_MII_MNG_CTL); + + /* Wait for MDIO? */ + udelay(2); + + /* PLL30 block */ + err = bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, + BCM_NS_USB3_PHY_PLL30_BLOCK); + if (err < 0) + return err; + + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLLA_CONTROL0, 0x6400); + + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, 0x80e0); + + bcm_ns_usb3_mdio_phy_write(usb3, 0x02, 0x009c); + + /* Enable SSC */ + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, + BCM_NS_USB3_PHY_TX_PMD_BLOCK); + + bcm_ns_usb3_mdio_phy_write(usb3, 0x02, 0x21d3); + + bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_TX_PMD_CONTROL1, 0x1003); + + /* Waiting MII Mgt interface idle */ + bcm_ns_usb3_mii_mng_wait_idle(usb3); + + /* Deasserting USB3 system reset */ + writel(0, usb3->dmp + BCMA_RESET_CTL); + + return 0; +} + +static int bcm_ns_usb3_phy_init(struct phy *phy) +{ + struct bcm_ns_usb3 *usb3 = phy_get_drvdata(phy); + int err; + + /* Perform USB3 system soft reset */ + writel(BCMA_RESET_CTL_RESET, usb3->dmp + BCMA_RESET_CTL); + + switch (usb3->family) { + case BCM_NS_AX: + err = bcm_ns_usb3_phy_init_ns_ax(usb3); + break; + case BCM_NS_BX: + err = bcm_ns_usb3_phy_init_ns_bx(usb3); + break; + default: + WARN_ON(1); + err = -ENOTSUPP; + } + + return err; +} + +static const struct phy_ops ops = { + .init = bcm_ns_usb3_phy_init, + .owner = THIS_MODULE, +}; + +static int bcm_ns_usb3_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct of_device_id *of_id; + struct bcm_ns_usb3 *usb3; + struct resource *res; + struct phy_provider *phy_provider; + + usb3 = devm_kzalloc(dev, sizeof(*usb3), GFP_KERNEL); + if (!usb3) + return -ENOMEM; + + usb3->dev = dev; + + of_id = of_match_device(bcm_ns_usb3_id_table, dev); + if (!of_id) + return -EINVAL; + usb3->family = (enum bcm_ns_family)of_id->data; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dmp"); + usb3->dmp = devm_ioremap_resource(dev, res); + if (IS_ERR(usb3->dmp)) { + dev_err(dev, "Failed to map DMP regs\n"); + return PTR_ERR(usb3->dmp); + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ccb-mii"); + usb3->ccb_mii = devm_ioremap_resource(dev, res); + if (IS_ERR(usb3->ccb_mii)) { + dev_err(dev, "Failed to map ChipCommon B MII regs\n"); + return PTR_ERR(usb3->ccb_mii); + } + + usb3->phy = devm_phy_create(dev, NULL, &ops); + if (IS_ERR(usb3->phy)) { + dev_err(dev, "Failed to create PHY\n"); + return PTR_ERR(usb3->phy); + } + + phy_set_drvdata(usb3->phy, usb3); + platform_set_drvdata(pdev, usb3); + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (!IS_ERR(phy_provider)) + dev_info(dev, "Registered Broadcom Northstar USB 3.0 PHY driver\n"); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static struct platform_driver bcm_ns_usb3_driver = { + .probe = bcm_ns_usb3_probe, + .driver = { + .name = "bcm_ns_usb3", + .of_match_table = bcm_ns_usb3_id_table, + }, +}; +module_platform_driver(bcm_ns_usb3_driver); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/broadcom/phy-bcm-ns2-pcie.c b/drivers/phy/broadcom/phy-bcm-ns2-pcie.c new file mode 100644 index 000000000000..4c7d11d2b378 --- /dev/null +++ b/drivers/phy/broadcom/phy-bcm-ns2-pcie.c @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2016 Broadcom + * + * 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. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include + +#define BLK_ADDR_REG_OFFSET 0x1f +#define PLL_AFE1_100MHZ_BLK 0x2100 +#define PLL_CLK_AMP_OFFSET 0x03 +#define PLL_CLK_AMP_2P05V 0x2b18 + +static int ns2_pci_phy_init(struct phy *p) +{ + struct mdio_device *mdiodev = phy_get_drvdata(p); + int rc; + + /* select the AFE 100MHz block page */ + rc = mdiobus_write(mdiodev->bus, mdiodev->addr, + BLK_ADDR_REG_OFFSET, PLL_AFE1_100MHZ_BLK); + if (rc) + goto err; + + /* set the 100 MHz reference clock amplitude to 2.05 v */ + rc = mdiobus_write(mdiodev->bus, mdiodev->addr, + PLL_CLK_AMP_OFFSET, PLL_CLK_AMP_2P05V); + if (rc) + goto err; + + return 0; + +err: + dev_err(&mdiodev->dev, "Error %d writing to phy\n", rc); + return rc; +} + +static const struct phy_ops ns2_pci_phy_ops = { + .init = ns2_pci_phy_init, + .owner = THIS_MODULE, +}; + +static int ns2_pci_phy_probe(struct mdio_device *mdiodev) +{ + struct device *dev = &mdiodev->dev; + struct phy_provider *provider; + struct phy *phy; + + phy = devm_phy_create(dev, dev->of_node, &ns2_pci_phy_ops); + if (IS_ERR(phy)) { + dev_err(dev, "failed to create Phy\n"); + return PTR_ERR(phy); + } + + phy_set_drvdata(phy, mdiodev); + + provider = devm_of_phy_provider_register(&phy->dev, + of_phy_simple_xlate); + if (IS_ERR(provider)) { + dev_err(dev, "failed to register Phy provider\n"); + return PTR_ERR(provider); + } + + dev_info(dev, "%s PHY registered\n", dev_name(dev)); + + return 0; +} + +static const struct of_device_id ns2_pci_phy_of_match[] = { + { .compatible = "brcm,ns2-pcie-phy", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, ns2_pci_phy_of_match); + +static struct mdio_driver ns2_pci_phy_driver = { + .mdiodrv = { + .driver = { + .name = "phy-bcm-ns2-pci", + .of_match_table = ns2_pci_phy_of_match, + }, + }, + .probe = ns2_pci_phy_probe, +}; +mdio_module_driver(ns2_pci_phy_driver); + +MODULE_AUTHOR("Broadcom"); +MODULE_DESCRIPTION("Broadcom Northstar2 PCI Phy driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:phy-bcm-ns2-pci"); diff --git a/drivers/phy/broadcom/phy-brcm-sata.c b/drivers/phy/broadcom/phy-brcm-sata.c new file mode 100644 index 000000000000..ccbc3d994998 --- /dev/null +++ b/drivers/phy/broadcom/phy-brcm-sata.c @@ -0,0 +1,493 @@ +/* + * Broadcom SATA3 AHCI Controller PHY Driver + * + * Copyright (C) 2016 Broadcom + * + * 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; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SATA_PCB_BANK_OFFSET 0x23c +#define SATA_PCB_REG_OFFSET(ofs) ((ofs) * 4) + +#define MAX_PORTS 2 + +/* Register offset between PHYs in PCB space */ +#define SATA_PCB_REG_28NM_SPACE_SIZE 0x1000 + +/* The older SATA PHY registers duplicated per port registers within the map, + * rather than having a separate map per port. + */ +#define SATA_PCB_REG_40NM_SPACE_SIZE 0x10 + +/* Register offset between PHYs in PHY control space */ +#define SATA_PHY_CTRL_REG_28NM_SPACE_SIZE 0x8 + +enum brcm_sata_phy_version { + BRCM_SATA_PHY_STB_28NM, + BRCM_SATA_PHY_STB_40NM, + BRCM_SATA_PHY_IPROC_NS2, + BRCM_SATA_PHY_IPROC_NSP, +}; + +struct brcm_sata_port { + int portnum; + struct phy *phy; + struct brcm_sata_phy *phy_priv; + bool ssc_en; +}; + +struct brcm_sata_phy { + struct device *dev; + void __iomem *phy_base; + void __iomem *ctrl_base; + enum brcm_sata_phy_version version; + + struct brcm_sata_port phys[MAX_PORTS]; +}; + +enum sata_phy_regs { + BLOCK0_REG_BANK = 0x000, + BLOCK0_XGXSSTATUS = 0x81, + BLOCK0_XGXSSTATUS_PLL_LOCK = BIT(12), + BLOCK0_SPARE = 0x8d, + BLOCK0_SPARE_OOB_CLK_SEL_MASK = 0x3, + BLOCK0_SPARE_OOB_CLK_SEL_REFBY2 = 0x1, + + PLL_REG_BANK_0 = 0x050, + PLL_REG_BANK_0_PLLCONTROL_0 = 0x81, + PLLCONTROL_0_FREQ_DET_RESTART = BIT(13), + PLLCONTROL_0_FREQ_MONITOR = BIT(12), + PLLCONTROL_0_SEQ_START = BIT(15), + PLL_CAP_CONTROL = 0x85, + PLL_ACTRL2 = 0x8b, + PLL_ACTRL2_SELDIV_MASK = 0x1f, + PLL_ACTRL2_SELDIV_SHIFT = 9, + + PLL1_REG_BANK = 0x060, + PLL1_ACTRL2 = 0x82, + PLL1_ACTRL3 = 0x83, + PLL1_ACTRL4 = 0x84, + + OOB_REG_BANK = 0x150, + OOB1_REG_BANK = 0x160, + OOB_CTRL1 = 0x80, + OOB_CTRL1_BURST_MAX_MASK = 0xf, + OOB_CTRL1_BURST_MAX_SHIFT = 12, + OOB_CTRL1_BURST_MIN_MASK = 0xf, + OOB_CTRL1_BURST_MIN_SHIFT = 8, + OOB_CTRL1_WAKE_IDLE_MAX_MASK = 0xf, + OOB_CTRL1_WAKE_IDLE_MAX_SHIFT = 4, + OOB_CTRL1_WAKE_IDLE_MIN_MASK = 0xf, + OOB_CTRL1_WAKE_IDLE_MIN_SHIFT = 0, + OOB_CTRL2 = 0x81, + OOB_CTRL2_SEL_ENA_SHIFT = 15, + OOB_CTRL2_SEL_ENA_RC_SHIFT = 14, + OOB_CTRL2_RESET_IDLE_MAX_MASK = 0x3f, + OOB_CTRL2_RESET_IDLE_MAX_SHIFT = 8, + OOB_CTRL2_BURST_CNT_MASK = 0x3, + OOB_CTRL2_BURST_CNT_SHIFT = 6, + OOB_CTRL2_RESET_IDLE_MIN_MASK = 0x3f, + OOB_CTRL2_RESET_IDLE_MIN_SHIFT = 0, + + TXPMD_REG_BANK = 0x1a0, + TXPMD_CONTROL1 = 0x81, + TXPMD_CONTROL1_TX_SSC_EN_FRC = BIT(0), + TXPMD_CONTROL1_TX_SSC_EN_FRC_VAL = BIT(1), + TXPMD_TX_FREQ_CTRL_CONTROL1 = 0x82, + TXPMD_TX_FREQ_CTRL_CONTROL2 = 0x83, + TXPMD_TX_FREQ_CTRL_CONTROL2_FMIN_MASK = 0x3ff, + TXPMD_TX_FREQ_CTRL_CONTROL3 = 0x84, + TXPMD_TX_FREQ_CTRL_CONTROL3_FMAX_MASK = 0x3ff, +}; + +enum sata_phy_ctrl_regs { + PHY_CTRL_1 = 0x0, + PHY_CTRL_1_RESET = BIT(0), +}; + +static inline void __iomem *brcm_sata_pcb_base(struct brcm_sata_port *port) +{ + struct brcm_sata_phy *priv = port->phy_priv; + u32 size = 0; + + switch (priv->version) { + case BRCM_SATA_PHY_STB_28NM: + case BRCM_SATA_PHY_IPROC_NS2: + size = SATA_PCB_REG_28NM_SPACE_SIZE; + break; + case BRCM_SATA_PHY_STB_40NM: + size = SATA_PCB_REG_40NM_SPACE_SIZE; + break; + default: + dev_err(priv->dev, "invalid phy version\n"); + break; + } + + return priv->phy_base + (port->portnum * size); +} + +static inline void __iomem *brcm_sata_ctrl_base(struct brcm_sata_port *port) +{ + struct brcm_sata_phy *priv = port->phy_priv; + u32 size = 0; + + switch (priv->version) { + case BRCM_SATA_PHY_IPROC_NS2: + size = SATA_PHY_CTRL_REG_28NM_SPACE_SIZE; + break; + default: + dev_err(priv->dev, "invalid phy version\n"); + break; + } + + return priv->ctrl_base + (port->portnum * size); +} + +static void brcm_sata_phy_wr(void __iomem *pcb_base, u32 bank, + u32 ofs, u32 msk, u32 value) +{ + u32 tmp; + + writel(bank, pcb_base + SATA_PCB_BANK_OFFSET); + tmp = readl(pcb_base + SATA_PCB_REG_OFFSET(ofs)); + tmp = (tmp & msk) | value; + writel(tmp, pcb_base + SATA_PCB_REG_OFFSET(ofs)); +} + +static u32 brcm_sata_phy_rd(void __iomem *pcb_base, u32 bank, u32 ofs) +{ + writel(bank, pcb_base + SATA_PCB_BANK_OFFSET); + return readl(pcb_base + SATA_PCB_REG_OFFSET(ofs)); +} + +/* These defaults were characterized by H/W group */ +#define STB_FMIN_VAL_DEFAULT 0x3df +#define STB_FMAX_VAL_DEFAULT 0x3df +#define STB_FMAX_VAL_SSC 0x83 + +static int brcm_stb_sata_init(struct brcm_sata_port *port) +{ + void __iomem *base = brcm_sata_pcb_base(port); + struct brcm_sata_phy *priv = port->phy_priv; + u32 tmp; + + /* override the TX spread spectrum setting */ + tmp = TXPMD_CONTROL1_TX_SSC_EN_FRC_VAL | TXPMD_CONTROL1_TX_SSC_EN_FRC; + brcm_sata_phy_wr(base, TXPMD_REG_BANK, TXPMD_CONTROL1, ~tmp, tmp); + + /* set fixed min freq */ + brcm_sata_phy_wr(base, TXPMD_REG_BANK, TXPMD_TX_FREQ_CTRL_CONTROL2, + ~TXPMD_TX_FREQ_CTRL_CONTROL2_FMIN_MASK, + STB_FMIN_VAL_DEFAULT); + + /* set fixed max freq depending on SSC config */ + if (port->ssc_en) { + dev_info(priv->dev, "enabling SSC on port%d\n", port->portnum); + tmp = STB_FMAX_VAL_SSC; + } else { + tmp = STB_FMAX_VAL_DEFAULT; + } + + brcm_sata_phy_wr(base, TXPMD_REG_BANK, TXPMD_TX_FREQ_CTRL_CONTROL3, + ~TXPMD_TX_FREQ_CTRL_CONTROL3_FMAX_MASK, tmp); + + return 0; +} + +/* NS2 SATA PLL1 defaults were characterized by H/W group */ +#define NS2_PLL1_ACTRL2_MAGIC 0x1df8 +#define NS2_PLL1_ACTRL3_MAGIC 0x2b00 +#define NS2_PLL1_ACTRL4_MAGIC 0x8824 + +static int brcm_ns2_sata_init(struct brcm_sata_port *port) +{ + int try; + unsigned int val; + void __iomem *base = brcm_sata_pcb_base(port); + void __iomem *ctrl_base = brcm_sata_ctrl_base(port); + struct device *dev = port->phy_priv->dev; + + /* Configure OOB control */ + val = 0x0; + val |= (0xc << OOB_CTRL1_BURST_MAX_SHIFT); + val |= (0x4 << OOB_CTRL1_BURST_MIN_SHIFT); + val |= (0x9 << OOB_CTRL1_WAKE_IDLE_MAX_SHIFT); + val |= (0x3 << OOB_CTRL1_WAKE_IDLE_MIN_SHIFT); + brcm_sata_phy_wr(base, OOB_REG_BANK, OOB_CTRL1, 0x0, val); + val = 0x0; + val |= (0x1b << OOB_CTRL2_RESET_IDLE_MAX_SHIFT); + val |= (0x2 << OOB_CTRL2_BURST_CNT_SHIFT); + val |= (0x9 << OOB_CTRL2_RESET_IDLE_MIN_SHIFT); + brcm_sata_phy_wr(base, OOB_REG_BANK, OOB_CTRL2, 0x0, val); + + /* Configure PHY PLL register bank 1 */ + val = NS2_PLL1_ACTRL2_MAGIC; + brcm_sata_phy_wr(base, PLL1_REG_BANK, PLL1_ACTRL2, 0x0, val); + val = NS2_PLL1_ACTRL3_MAGIC; + brcm_sata_phy_wr(base, PLL1_REG_BANK, PLL1_ACTRL3, 0x0, val); + val = NS2_PLL1_ACTRL4_MAGIC; + brcm_sata_phy_wr(base, PLL1_REG_BANK, PLL1_ACTRL4, 0x0, val); + + /* Configure PHY BLOCK0 register bank */ + /* Set oob_clk_sel to refclk/2 */ + brcm_sata_phy_wr(base, BLOCK0_REG_BANK, BLOCK0_SPARE, + ~BLOCK0_SPARE_OOB_CLK_SEL_MASK, + BLOCK0_SPARE_OOB_CLK_SEL_REFBY2); + + /* Strobe PHY reset using PHY control register */ + writel(PHY_CTRL_1_RESET, ctrl_base + PHY_CTRL_1); + mdelay(1); + writel(0x0, ctrl_base + PHY_CTRL_1); + mdelay(1); + + /* Wait for PHY PLL lock by polling pll_lock bit */ + try = 50; + while (try) { + val = brcm_sata_phy_rd(base, BLOCK0_REG_BANK, + BLOCK0_XGXSSTATUS); + if (val & BLOCK0_XGXSSTATUS_PLL_LOCK) + break; + msleep(20); + try--; + } + if (!try) { + /* PLL did not lock; give up */ + dev_err(dev, "port%d PLL did not lock\n", port->portnum); + return -ETIMEDOUT; + } + + dev_dbg(dev, "port%d initialized\n", port->portnum); + + return 0; +} + +static int brcm_nsp_sata_init(struct brcm_sata_port *port) +{ + struct brcm_sata_phy *priv = port->phy_priv; + struct device *dev = port->phy_priv->dev; + void __iomem *base = priv->phy_base; + unsigned int oob_bank; + unsigned int val, try; + + /* Configure OOB control */ + if (port->portnum == 0) + oob_bank = OOB_REG_BANK; + else if (port->portnum == 1) + oob_bank = OOB1_REG_BANK; + else + return -EINVAL; + + val = 0x0; + val |= (0x0f << OOB_CTRL1_BURST_MAX_SHIFT); + val |= (0x06 << OOB_CTRL1_BURST_MIN_SHIFT); + val |= (0x0f << OOB_CTRL1_WAKE_IDLE_MAX_SHIFT); + val |= (0x06 << OOB_CTRL1_WAKE_IDLE_MIN_SHIFT); + brcm_sata_phy_wr(base, oob_bank, OOB_CTRL1, 0x0, val); + + val = 0x0; + val |= (0x2e << OOB_CTRL2_RESET_IDLE_MAX_SHIFT); + val |= (0x02 << OOB_CTRL2_BURST_CNT_SHIFT); + val |= (0x16 << OOB_CTRL2_RESET_IDLE_MIN_SHIFT); + brcm_sata_phy_wr(base, oob_bank, OOB_CTRL2, 0x0, val); + + + brcm_sata_phy_wr(base, PLL_REG_BANK_0, PLL_ACTRL2, + ~(PLL_ACTRL2_SELDIV_MASK << PLL_ACTRL2_SELDIV_SHIFT), + 0x0c << PLL_ACTRL2_SELDIV_SHIFT); + + brcm_sata_phy_wr(base, PLL_REG_BANK_0, PLL_CAP_CONTROL, + 0xff0, 0x4f0); + + val = PLLCONTROL_0_FREQ_DET_RESTART | PLLCONTROL_0_FREQ_MONITOR; + brcm_sata_phy_wr(base, PLL_REG_BANK_0, PLL_REG_BANK_0_PLLCONTROL_0, + ~val, val); + val = PLLCONTROL_0_SEQ_START; + brcm_sata_phy_wr(base, PLL_REG_BANK_0, PLL_REG_BANK_0_PLLCONTROL_0, + ~val, 0); + mdelay(10); + brcm_sata_phy_wr(base, PLL_REG_BANK_0, PLL_REG_BANK_0_PLLCONTROL_0, + ~val, val); + + /* Wait for pll_seq_done bit */ + try = 50; + while (try--) { + val = brcm_sata_phy_rd(base, BLOCK0_REG_BANK, + BLOCK0_XGXSSTATUS); + if (val & BLOCK0_XGXSSTATUS_PLL_LOCK) + break; + msleep(20); + } + if (!try) { + /* PLL did not lock; give up */ + dev_err(dev, "port%d PLL did not lock\n", port->portnum); + return -ETIMEDOUT; + } + + dev_dbg(dev, "port%d initialized\n", port->portnum); + + return 0; +} + +static int brcm_sata_phy_init(struct phy *phy) +{ + int rc; + struct brcm_sata_port *port = phy_get_drvdata(phy); + + switch (port->phy_priv->version) { + case BRCM_SATA_PHY_STB_28NM: + case BRCM_SATA_PHY_STB_40NM: + rc = brcm_stb_sata_init(port); + break; + case BRCM_SATA_PHY_IPROC_NS2: + rc = brcm_ns2_sata_init(port); + break; + case BRCM_SATA_PHY_IPROC_NSP: + rc = brcm_nsp_sata_init(port); + break; + default: + rc = -ENODEV; + } + + return rc; +} + +static const struct phy_ops phy_ops = { + .init = brcm_sata_phy_init, + .owner = THIS_MODULE, +}; + +static const struct of_device_id brcm_sata_phy_of_match[] = { + { .compatible = "brcm,bcm7445-sata-phy", + .data = (void *)BRCM_SATA_PHY_STB_28NM }, + { .compatible = "brcm,bcm7425-sata-phy", + .data = (void *)BRCM_SATA_PHY_STB_40NM }, + { .compatible = "brcm,iproc-ns2-sata-phy", + .data = (void *)BRCM_SATA_PHY_IPROC_NS2 }, + { .compatible = "brcm,iproc-nsp-sata-phy", + .data = (void *)BRCM_SATA_PHY_IPROC_NSP }, + {}, +}; +MODULE_DEVICE_TABLE(of, brcm_sata_phy_of_match); + +static int brcm_sata_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *dn = dev->of_node, *child; + const struct of_device_id *of_id; + struct brcm_sata_phy *priv; + struct resource *res; + struct phy_provider *provider; + int ret, count = 0; + + if (of_get_child_count(dn) == 0) + return -ENODEV; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + dev_set_drvdata(dev, priv); + priv->dev = dev; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy"); + priv->phy_base = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->phy_base)) + return PTR_ERR(priv->phy_base); + + of_id = of_match_node(brcm_sata_phy_of_match, dn); + if (of_id) + priv->version = (enum brcm_sata_phy_version)of_id->data; + else + priv->version = BRCM_SATA_PHY_STB_28NM; + + if (priv->version == BRCM_SATA_PHY_IPROC_NS2) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "phy-ctrl"); + priv->ctrl_base = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->ctrl_base)) + return PTR_ERR(priv->ctrl_base); + } + + for_each_available_child_of_node(dn, child) { + unsigned int id; + struct brcm_sata_port *port; + + if (of_property_read_u32(child, "reg", &id)) { + dev_err(dev, "missing reg property in node %s\n", + child->name); + ret = -EINVAL; + goto put_child; + } + + if (id >= MAX_PORTS) { + dev_err(dev, "invalid reg: %u\n", id); + ret = -EINVAL; + goto put_child; + } + if (priv->phys[id].phy) { + dev_err(dev, "already registered port %u\n", id); + ret = -EINVAL; + goto put_child; + } + + port = &priv->phys[id]; + port->portnum = id; + port->phy_priv = priv; + port->phy = devm_phy_create(dev, child, &phy_ops); + port->ssc_en = of_property_read_bool(child, "brcm,enable-ssc"); + if (IS_ERR(port->phy)) { + dev_err(dev, "failed to create PHY\n"); + ret = PTR_ERR(port->phy); + goto put_child; + } + + phy_set_drvdata(port->phy, port); + count++; + } + + provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(provider)) { + dev_err(dev, "could not register PHY provider\n"); + return PTR_ERR(provider); + } + + dev_info(dev, "registered %d port(s)\n", count); + + return 0; +put_child: + of_node_put(child); + return ret; +} + +static struct platform_driver brcm_sata_phy_driver = { + .probe = brcm_sata_phy_probe, + .driver = { + .of_match_table = brcm_sata_phy_of_match, + .name = "brcm-sata-phy", + } +}; +module_platform_driver(brcm_sata_phy_driver); + +MODULE_DESCRIPTION("Broadcom SATA PHY driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Marc Carino"); +MODULE_AUTHOR("Brian Norris"); +MODULE_ALIAS("platform:phy-brcm-sata"); diff --git a/drivers/phy/hisilicon/Kconfig b/drivers/phy/hisilicon/Kconfig new file mode 100644 index 000000000000..6164c4cd0f65 --- /dev/null +++ b/drivers/phy/hisilicon/Kconfig @@ -0,0 +1,20 @@ +# +# Phy drivers for Hisilicon platforms +# +config PHY_HI6220_USB + tristate "hi6220 USB PHY support" + depends on (ARCH_HISI && ARM64) || COMPILE_TEST + select GENERIC_PHY + select MFD_SYSCON + help + Enable this to support the HISILICON HI6220 USB PHY. + + To compile this driver as a module, choose M here. + +config PHY_HIX5HD2_SATA + tristate "HIX5HD2 SATA PHY Driver" + depends on ARCH_HIX5HD2 && OF && HAS_IOMEM + select GENERIC_PHY + select MFD_SYSCON + help + Support for SATA PHY on Hisilicon hix5hd2 Soc. diff --git a/drivers/phy/hisilicon/Makefile b/drivers/phy/hisilicon/Makefile new file mode 100644 index 000000000000..541b348187a8 --- /dev/null +++ b/drivers/phy/hisilicon/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_PHY_HI6220_USB) += phy-hi6220-usb.o +obj-$(CONFIG_PHY_HIX5HD2_SATA) += phy-hix5hd2-sata.o diff --git a/drivers/phy/hisilicon/phy-hi6220-usb.c b/drivers/phy/hisilicon/phy-hi6220-usb.c new file mode 100644 index 000000000000..398c1021deec --- /dev/null +++ b/drivers/phy/hisilicon/phy-hi6220-usb.c @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2015 Linaro Ltd. + * Copyright (c) 2015 Hisilicon Limited. + * + * 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; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include + +#define SC_PERIPH_CTRL4 0x00c + +#define CTRL4_PICO_SIDDQ BIT(6) +#define CTRL4_PICO_OGDISABLE BIT(8) +#define CTRL4_PICO_VBUSVLDEXT BIT(10) +#define CTRL4_PICO_VBUSVLDEXTSEL BIT(11) +#define CTRL4_OTG_PHY_SEL BIT(21) + +#define SC_PERIPH_CTRL5 0x010 + +#define CTRL5_USBOTG_RES_SEL BIT(3) +#define CTRL5_PICOPHY_ACAENB BIT(4) +#define CTRL5_PICOPHY_BC_MODE BIT(5) +#define CTRL5_PICOPHY_CHRGSEL BIT(6) +#define CTRL5_PICOPHY_VDATSRCEND BIT(7) +#define CTRL5_PICOPHY_VDATDETENB BIT(8) +#define CTRL5_PICOPHY_DCDENB BIT(9) +#define CTRL5_PICOPHY_IDDIG BIT(10) + +#define SC_PERIPH_CTRL8 0x018 +#define SC_PERIPH_RSTEN0 0x300 +#define SC_PERIPH_RSTDIS0 0x304 + +#define RST0_USBOTG_BUS BIT(4) +#define RST0_POR_PICOPHY BIT(5) +#define RST0_USBOTG BIT(6) +#define RST0_USBOTG_32K BIT(7) + +#define EYE_PATTERN_PARA 0x7053348c + +struct hi6220_priv { + struct regmap *reg; + struct device *dev; +}; + +static void hi6220_phy_init(struct hi6220_priv *priv) +{ + struct regmap *reg = priv->reg; + u32 val, mask; + + val = RST0_USBOTG_BUS | RST0_POR_PICOPHY | + RST0_USBOTG | RST0_USBOTG_32K; + mask = val; + regmap_update_bits(reg, SC_PERIPH_RSTEN0, mask, val); + regmap_update_bits(reg, SC_PERIPH_RSTDIS0, mask, val); +} + +static int hi6220_phy_setup(struct hi6220_priv *priv, bool on) +{ + struct regmap *reg = priv->reg; + u32 val, mask; + int ret; + + if (on) { + val = CTRL5_USBOTG_RES_SEL | CTRL5_PICOPHY_ACAENB; + mask = val | CTRL5_PICOPHY_BC_MODE; + ret = regmap_update_bits(reg, SC_PERIPH_CTRL5, mask, val); + if (ret) + goto out; + + val = CTRL4_PICO_VBUSVLDEXT | CTRL4_PICO_VBUSVLDEXTSEL | + CTRL4_OTG_PHY_SEL; + mask = val | CTRL4_PICO_SIDDQ | CTRL4_PICO_OGDISABLE; + ret = regmap_update_bits(reg, SC_PERIPH_CTRL4, mask, val); + if (ret) + goto out; + + ret = regmap_write(reg, SC_PERIPH_CTRL8, EYE_PATTERN_PARA); + if (ret) + goto out; + } else { + val = CTRL4_PICO_SIDDQ; + mask = val; + ret = regmap_update_bits(reg, SC_PERIPH_CTRL4, mask, val); + if (ret) + goto out; + } + + return 0; +out: + dev_err(priv->dev, "failed to setup phy ret: %d\n", ret); + return ret; +} + +static int hi6220_phy_start(struct phy *phy) +{ + struct hi6220_priv *priv = phy_get_drvdata(phy); + + return hi6220_phy_setup(priv, true); +} + +static int hi6220_phy_exit(struct phy *phy) +{ + struct hi6220_priv *priv = phy_get_drvdata(phy); + + return hi6220_phy_setup(priv, false); +} + +static const struct phy_ops hi6220_phy_ops = { + .init = hi6220_phy_start, + .exit = hi6220_phy_exit, + .owner = THIS_MODULE, +}; + +static int hi6220_phy_probe(struct platform_device *pdev) +{ + struct phy_provider *phy_provider; + struct device *dev = &pdev->dev; + struct phy *phy; + struct hi6220_priv *priv; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + priv->reg = syscon_regmap_lookup_by_phandle(dev->of_node, + "hisilicon,peripheral-syscon"); + if (IS_ERR(priv->reg)) { + dev_err(dev, "no hisilicon,peripheral-syscon\n"); + return PTR_ERR(priv->reg); + } + + hi6220_phy_init(priv); + + phy = devm_phy_create(dev, NULL, &hi6220_phy_ops); + if (IS_ERR(phy)) + return PTR_ERR(phy); + + phy_set_drvdata(phy, priv); + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id hi6220_phy_of_match[] = { + {.compatible = "hisilicon,hi6220-usb-phy",}, + { }, +}; +MODULE_DEVICE_TABLE(of, hi6220_phy_of_match); + +static struct platform_driver hi6220_phy_driver = { + .probe = hi6220_phy_probe, + .driver = { + .name = "hi6220-usb-phy", + .of_match_table = hi6220_phy_of_match, + } +}; +module_platform_driver(hi6220_phy_driver); + +MODULE_DESCRIPTION("HISILICON HI6220 USB PHY driver"); +MODULE_ALIAS("platform:hi6220-usb-phy"); +MODULE_LICENSE("GPL"); diff --git a/drivers/phy/hisilicon/phy-hix5hd2-sata.c b/drivers/phy/hisilicon/phy-hix5hd2-sata.c new file mode 100644 index 000000000000..e5ab3aa78b9d --- /dev/null +++ b/drivers/phy/hisilicon/phy-hix5hd2-sata.c @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2014 Linaro Ltd. + * Copyright (c) 2014 Hisilicon Limited. + * + * 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; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define SATA_PHY0_CTLL 0xa0 +#define MPLL_MULTIPLIER_SHIFT 1 +#define MPLL_MULTIPLIER_MASK 0xfe +#define MPLL_MULTIPLIER_50M 0x3c +#define MPLL_MULTIPLIER_100M 0x1e +#define PHY_RESET BIT(0) +#define REF_SSP_EN BIT(9) +#define SSC_EN BIT(10) +#define REF_USE_PAD BIT(23) + +#define SATA_PORT_PHYCTL 0x174 +#define SPEED_MODE_MASK 0x6f0000 +#define HALF_RATE_SHIFT 16 +#define PHY_CONFIG_SHIFT 18 +#define GEN2_EN_SHIFT 21 +#define SPEED_CTRL BIT(20) + +#define SATA_PORT_PHYCTL1 0x148 +#define AMPLITUDE_MASK 0x3ffffe +#define AMPLITUDE_GEN3 0x68 +#define AMPLITUDE_GEN3_SHIFT 15 +#define AMPLITUDE_GEN2 0x56 +#define AMPLITUDE_GEN2_SHIFT 8 +#define AMPLITUDE_GEN1 0x56 +#define AMPLITUDE_GEN1_SHIFT 1 + +#define SATA_PORT_PHYCTL2 0x14c +#define PREEMPH_MASK 0x3ffff +#define PREEMPH_GEN3 0x20 +#define PREEMPH_GEN3_SHIFT 12 +#define PREEMPH_GEN2 0x15 +#define PREEMPH_GEN2_SHIFT 6 +#define PREEMPH_GEN1 0x5 +#define PREEMPH_GEN1_SHIFT 0 + +struct hix5hd2_priv { + void __iomem *base; + struct regmap *peri_ctrl; +}; + +enum phy_speed_mode { + SPEED_MODE_GEN1 = 0, + SPEED_MODE_GEN2 = 1, + SPEED_MODE_GEN3 = 2, +}; + +static int hix5hd2_sata_phy_init(struct phy *phy) +{ + struct hix5hd2_priv *priv = phy_get_drvdata(phy); + u32 val, data[2]; + int ret; + + if (priv->peri_ctrl) { + ret = of_property_read_u32_array(phy->dev.of_node, + "hisilicon,power-reg", + &data[0], 2); + if (ret) { + dev_err(&phy->dev, "Fail read hisilicon,power-reg\n"); + return ret; + } + + regmap_update_bits(priv->peri_ctrl, data[0], + BIT(data[1]), BIT(data[1])); + } + + /* reset phy */ + val = readl_relaxed(priv->base + SATA_PHY0_CTLL); + val &= ~(MPLL_MULTIPLIER_MASK | REF_USE_PAD); + val |= MPLL_MULTIPLIER_50M << MPLL_MULTIPLIER_SHIFT | + REF_SSP_EN | PHY_RESET; + writel_relaxed(val, priv->base + SATA_PHY0_CTLL); + msleep(20); + val &= ~PHY_RESET; + writel_relaxed(val, priv->base + SATA_PHY0_CTLL); + + val = readl_relaxed(priv->base + SATA_PORT_PHYCTL1); + val &= ~AMPLITUDE_MASK; + val |= AMPLITUDE_GEN3 << AMPLITUDE_GEN3_SHIFT | + AMPLITUDE_GEN2 << AMPLITUDE_GEN2_SHIFT | + AMPLITUDE_GEN1 << AMPLITUDE_GEN1_SHIFT; + writel_relaxed(val, priv->base + SATA_PORT_PHYCTL1); + + val = readl_relaxed(priv->base + SATA_PORT_PHYCTL2); + val &= ~PREEMPH_MASK; + val |= PREEMPH_GEN3 << PREEMPH_GEN3_SHIFT | + PREEMPH_GEN2 << PREEMPH_GEN2_SHIFT | + PREEMPH_GEN1 << PREEMPH_GEN1_SHIFT; + writel_relaxed(val, priv->base + SATA_PORT_PHYCTL2); + + /* ensure PHYCTRL setting takes effect */ + val = readl_relaxed(priv->base + SATA_PORT_PHYCTL); + val &= ~SPEED_MODE_MASK; + val |= SPEED_MODE_GEN1 << HALF_RATE_SHIFT | + SPEED_MODE_GEN1 << PHY_CONFIG_SHIFT | + SPEED_MODE_GEN1 << GEN2_EN_SHIFT | SPEED_CTRL; + writel_relaxed(val, priv->base + SATA_PORT_PHYCTL); + + msleep(20); + val &= ~SPEED_MODE_MASK; + val |= SPEED_MODE_GEN3 << HALF_RATE_SHIFT | + SPEED_MODE_GEN3 << PHY_CONFIG_SHIFT | + SPEED_MODE_GEN3 << GEN2_EN_SHIFT | SPEED_CTRL; + writel_relaxed(val, priv->base + SATA_PORT_PHYCTL); + + val &= ~(SPEED_MODE_MASK | SPEED_CTRL); + val |= SPEED_MODE_GEN2 << HALF_RATE_SHIFT | + SPEED_MODE_GEN2 << PHY_CONFIG_SHIFT | + SPEED_MODE_GEN2 << GEN2_EN_SHIFT; + writel_relaxed(val, priv->base + SATA_PORT_PHYCTL); + + return 0; +} + +static const struct phy_ops hix5hd2_sata_phy_ops = { + .init = hix5hd2_sata_phy_init, + .owner = THIS_MODULE, +}; + +static int hix5hd2_sata_phy_probe(struct platform_device *pdev) +{ + struct phy_provider *phy_provider; + struct device *dev = &pdev->dev; + struct resource *res; + struct phy *phy; + struct hix5hd2_priv *priv; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EINVAL; + + priv->base = devm_ioremap(dev, res->start, resource_size(res)); + if (!priv->base) + return -ENOMEM; + + priv->peri_ctrl = syscon_regmap_lookup_by_phandle(dev->of_node, + "hisilicon,peripheral-syscon"); + if (IS_ERR(priv->peri_ctrl)) + priv->peri_ctrl = NULL; + + phy = devm_phy_create(dev, NULL, &hix5hd2_sata_phy_ops); + if (IS_ERR(phy)) { + dev_err(dev, "failed to create PHY\n"); + return PTR_ERR(phy); + } + + phy_set_drvdata(phy, priv); + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id hix5hd2_sata_phy_of_match[] = { + {.compatible = "hisilicon,hix5hd2-sata-phy",}, + { }, +}; +MODULE_DEVICE_TABLE(of, hix5hd2_sata_phy_of_match); + +static struct platform_driver hix5hd2_sata_phy_driver = { + .probe = hix5hd2_sata_phy_probe, + .driver = { + .name = "hix5hd2-sata-phy", + .of_match_table = hix5hd2_sata_phy_of_match, + } +}; +module_platform_driver(hix5hd2_sata_phy_driver); + +MODULE_AUTHOR("Jiancheng Xue "); +MODULE_DESCRIPTION("HISILICON HIX5HD2 SATA PHY driver"); +MODULE_ALIAS("platform:hix5hd2-sata-phy"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/marvell/Kconfig b/drivers/phy/marvell/Kconfig new file mode 100644 index 000000000000..048d8893bc2e --- /dev/null +++ b/drivers/phy/marvell/Kconfig @@ -0,0 +1,50 @@ +# +# Phy drivers for Marvell platforms +# +config ARMADA375_USBCLUSTER_PHY + def_bool y + depends on MACH_ARMADA_375 || COMPILE_TEST + depends on OF && HAS_IOMEM + select GENERIC_PHY + +config PHY_BERLIN_SATA + tristate "Marvell Berlin SATA PHY driver" + depends on ARCH_BERLIN && HAS_IOMEM && OF + select GENERIC_PHY + help + Enable this to support the SATA PHY on Marvell Berlin SoCs. + +config PHY_BERLIN_USB + tristate "Marvell Berlin USB PHY Driver" + depends on ARCH_BERLIN && RESET_CONTROLLER && HAS_IOMEM && OF + select GENERIC_PHY + help + Enable this to support the USB PHY on Marvell Berlin SoCs. + +config PHY_MVEBU_SATA + def_bool y + depends on ARCH_DOVE || MACH_DOVE || MACH_KIRKWOOD + depends on OF + select GENERIC_PHY + +config PHY_PXA_28NM_HSIC + tristate "Marvell USB HSIC 28nm PHY Driver" + depends on HAS_IOMEM + select GENERIC_PHY + help + Enable this to support Marvell USB HSIC PHY driver for Marvell + SoC. This driver will do the PHY initialization and shutdown. + The PHY driver will be used by Marvell ehci driver. + + To compile this driver as a module, choose M here. + +config PHY_PXA_28NM_USB2 + tristate "Marvell USB 2.0 28nm PHY Driver" + depends on HAS_IOMEM + select GENERIC_PHY + help + Enable this to support Marvell USB 2.0 PHY driver for Marvell + SoC. This driver will do the PHY initialization and shutdown. + The PHY driver will be used by Marvell udc/ehci/otg driver. + + To compile this driver as a module, choose M here. diff --git a/drivers/phy/marvell/Makefile b/drivers/phy/marvell/Makefile new file mode 100644 index 000000000000..3fc188f59118 --- /dev/null +++ b/drivers/phy/marvell/Makefile @@ -0,0 +1,6 @@ +obj-$(CONFIG_ARMADA375_USBCLUSTER_PHY) += phy-armada375-usb2.o +obj-$(CONFIG_PHY_BERLIN_SATA) += phy-berlin-sata.o +obj-$(CONFIG_PHY_BERLIN_USB) += phy-berlin-usb.o +obj-$(CONFIG_PHY_MVEBU_SATA) += phy-mvebu-sata.o +obj-$(CONFIG_PHY_PXA_28NM_HSIC) += phy-pxa-28nm-hsic.o +obj-$(CONFIG_PHY_PXA_28NM_USB2) += phy-pxa-28nm-usb2.o diff --git a/drivers/phy/marvell/phy-armada375-usb2.c b/drivers/phy/marvell/phy-armada375-usb2.c new file mode 100644 index 000000000000..1a3db288c0a9 --- /dev/null +++ b/drivers/phy/marvell/phy-armada375-usb2.c @@ -0,0 +1,158 @@ +/* + * USB cluster support for Armada 375 platform. + * + * Copyright (C) 2014 Marvell + * + * Gregory CLEMENT + * + * This file is licensed under the terms of the GNU General Public + * License version 2 or later. This program is licensed "as is" + * without any warranty of any kind, whether express or implied. + * + * Armada 375 comes with an USB2 host and device controller and an + * USB3 controller. The USB cluster control register allows to manage + * common features of both USB controllers. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define USB2_PHY_CONFIG_DISABLE BIT(0) + +struct armada375_cluster_phy { + struct phy *phy; + void __iomem *reg; + bool use_usb3; + int phy_provided; +}; + +static int armada375_usb_phy_init(struct phy *phy) +{ + struct armada375_cluster_phy *cluster_phy; + u32 reg; + + cluster_phy = phy_get_drvdata(phy); + if (!cluster_phy) + return -ENODEV; + + reg = readl(cluster_phy->reg); + if (cluster_phy->use_usb3) + reg |= USB2_PHY_CONFIG_DISABLE; + else + reg &= ~USB2_PHY_CONFIG_DISABLE; + writel(reg, cluster_phy->reg); + + return 0; +} + +static const struct phy_ops armada375_usb_phy_ops = { + .init = armada375_usb_phy_init, + .owner = THIS_MODULE, +}; + +/* + * Only one controller can use this PHY. We shouldn't have the case + * when two controllers want to use this PHY. But if this case occurs + * then we provide a phy to the first one and return an error for the + * next one. This error has also to be an error returned by + * devm_phy_optional_get() so different from ENODEV for USB2. In the + * USB3 case it still optional and we use ENODEV. + */ +static struct phy *armada375_usb_phy_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct armada375_cluster_phy *cluster_phy = dev_get_drvdata(dev); + + if (!cluster_phy) + return ERR_PTR(-ENODEV); + + /* + * Either the phy had never been requested and then the first + * usb claiming it can get it, or it had already been + * requested in this case, we only allow to use it with the + * same configuration. + */ + if (WARN_ON((cluster_phy->phy_provided != PHY_NONE) && + (cluster_phy->phy_provided != args->args[0]))) { + dev_err(dev, "This PHY has already been provided!\n"); + dev_err(dev, "Check your device tree, only one controller can use it\n."); + if (args->args[0] == PHY_TYPE_USB2) + return ERR_PTR(-EBUSY); + else + return ERR_PTR(-ENODEV); + } + + if (args->args[0] == PHY_TYPE_USB2) + cluster_phy->use_usb3 = false; + else if (args->args[0] == PHY_TYPE_USB3) + cluster_phy->use_usb3 = true; + else { + dev_err(dev, "Invalid PHY mode\n"); + return ERR_PTR(-ENODEV); + } + + /* Store which phy mode is used for next test */ + cluster_phy->phy_provided = args->args[0]; + + return cluster_phy->phy; +} + +static int armada375_usb_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct phy *phy; + struct phy_provider *phy_provider; + void __iomem *usb_cluster_base; + struct resource *res; + struct armada375_cluster_phy *cluster_phy; + + cluster_phy = devm_kzalloc(dev, sizeof(*cluster_phy), GFP_KERNEL); + if (!cluster_phy) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + usb_cluster_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(usb_cluster_base)) + return PTR_ERR(usb_cluster_base); + + phy = devm_phy_create(dev, NULL, &armada375_usb_phy_ops); + if (IS_ERR(phy)) { + dev_err(dev, "failed to create PHY\n"); + return PTR_ERR(phy); + } + + cluster_phy->phy = phy; + cluster_phy->reg = usb_cluster_base; + + dev_set_drvdata(dev, cluster_phy); + phy_set_drvdata(phy, cluster_phy); + + phy_provider = devm_of_phy_provider_register(&pdev->dev, + armada375_usb_phy_xlate); + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id of_usb_cluster_table[] = { + { .compatible = "marvell,armada-375-usb-cluster", }, + { /* end of list */ }, +}; +MODULE_DEVICE_TABLE(of, of_usb_cluster_table); + +static struct platform_driver armada375_usb_phy_driver = { + .probe = armada375_usb_phy_probe, + .driver = { + .of_match_table = of_usb_cluster_table, + .name = "armada-375-usb-cluster", + } +}; +module_platform_driver(armada375_usb_phy_driver); + +MODULE_DESCRIPTION("Armada 375 USB cluster driver"); +MODULE_AUTHOR("Gregory CLEMENT "); +MODULE_LICENSE("GPL"); diff --git a/drivers/phy/marvell/phy-berlin-sata.c b/drivers/phy/marvell/phy-berlin-sata.c new file mode 100644 index 000000000000..2c7a57f2d595 --- /dev/null +++ b/drivers/phy/marvell/phy-berlin-sata.c @@ -0,0 +1,299 @@ +/* + * Marvell Berlin SATA PHY driver + * + * Copyright (C) 2014 Marvell Technology Group Ltd. + * + * Antoine Ténart + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include +#include +#include +#include +#include + +#define HOST_VSA_ADDR 0x0 +#define HOST_VSA_DATA 0x4 +#define PORT_SCR_CTL 0x2c +#define PORT_VSR_ADDR 0x78 +#define PORT_VSR_DATA 0x7c + +#define CONTROL_REGISTER 0x0 +#define MBUS_SIZE_CONTROL 0x4 + +#define POWER_DOWN_PHY0 BIT(6) +#define POWER_DOWN_PHY1 BIT(14) +#define MBUS_WRITE_REQUEST_SIZE_128 (BIT(2) << 16) +#define MBUS_READ_REQUEST_SIZE_128 (BIT(2) << 19) + +#define BG2_PHY_BASE 0x080 +#define BG2Q_PHY_BASE 0x200 + +/* register 0x01 */ +#define REF_FREF_SEL_25 BIT(0) +#define PHY_MODE_SATA (0x0 << 5) + +/* register 0x02 */ +#define USE_MAX_PLL_RATE BIT(12) + +/* register 0x23 */ +#define DATA_BIT_WIDTH_10 (0x0 << 10) +#define DATA_BIT_WIDTH_20 (0x1 << 10) +#define DATA_BIT_WIDTH_40 (0x2 << 10) + +/* register 0x25 */ +#define PHY_GEN_MAX_1_5 (0x0 << 10) +#define PHY_GEN_MAX_3_0 (0x1 << 10) +#define PHY_GEN_MAX_6_0 (0x2 << 10) + +struct phy_berlin_desc { + struct phy *phy; + u32 power_bit; + unsigned index; +}; + +struct phy_berlin_priv { + void __iomem *base; + spinlock_t lock; + struct clk *clk; + struct phy_berlin_desc **phys; + unsigned nphys; + u32 phy_base; +}; + +static inline void phy_berlin_sata_reg_setbits(void __iomem *ctrl_reg, + u32 phy_base, u32 reg, u32 mask, u32 val) +{ + u32 regval; + + /* select register */ + writel(phy_base + reg, ctrl_reg + PORT_VSR_ADDR); + + /* set bits */ + regval = readl(ctrl_reg + PORT_VSR_DATA); + regval &= ~mask; + regval |= val; + writel(regval, ctrl_reg + PORT_VSR_DATA); +} + +static int phy_berlin_sata_power_on(struct phy *phy) +{ + struct phy_berlin_desc *desc = phy_get_drvdata(phy); + struct phy_berlin_priv *priv = dev_get_drvdata(phy->dev.parent); + void __iomem *ctrl_reg = priv->base + 0x60 + (desc->index * 0x80); + u32 regval; + + clk_prepare_enable(priv->clk); + + spin_lock(&priv->lock); + + /* Power on PHY */ + writel(CONTROL_REGISTER, priv->base + HOST_VSA_ADDR); + regval = readl(priv->base + HOST_VSA_DATA); + regval &= ~desc->power_bit; + writel(regval, priv->base + HOST_VSA_DATA); + + /* Configure MBus */ + writel(MBUS_SIZE_CONTROL, priv->base + HOST_VSA_ADDR); + regval = readl(priv->base + HOST_VSA_DATA); + regval |= MBUS_WRITE_REQUEST_SIZE_128 | MBUS_READ_REQUEST_SIZE_128; + writel(regval, priv->base + HOST_VSA_DATA); + + /* set PHY mode and ref freq to 25 MHz */ + phy_berlin_sata_reg_setbits(ctrl_reg, priv->phy_base, 0x01, + 0x00ff, REF_FREF_SEL_25 | PHY_MODE_SATA); + + /* set PHY up to 6 Gbps */ + phy_berlin_sata_reg_setbits(ctrl_reg, priv->phy_base, 0x25, + 0x0c00, PHY_GEN_MAX_6_0); + + /* set 40 bits width */ + phy_berlin_sata_reg_setbits(ctrl_reg, priv->phy_base, 0x23, + 0x0c00, DATA_BIT_WIDTH_40); + + /* use max pll rate */ + phy_berlin_sata_reg_setbits(ctrl_reg, priv->phy_base, 0x02, + 0x0000, USE_MAX_PLL_RATE); + + /* set Gen3 controller speed */ + regval = readl(ctrl_reg + PORT_SCR_CTL); + regval &= ~GENMASK(7, 4); + regval |= 0x30; + writel(regval, ctrl_reg + PORT_SCR_CTL); + + spin_unlock(&priv->lock); + + clk_disable_unprepare(priv->clk); + + return 0; +} + +static int phy_berlin_sata_power_off(struct phy *phy) +{ + struct phy_berlin_desc *desc = phy_get_drvdata(phy); + struct phy_berlin_priv *priv = dev_get_drvdata(phy->dev.parent); + u32 regval; + + clk_prepare_enable(priv->clk); + + spin_lock(&priv->lock); + + /* Power down PHY */ + writel(CONTROL_REGISTER, priv->base + HOST_VSA_ADDR); + regval = readl(priv->base + HOST_VSA_DATA); + regval |= desc->power_bit; + writel(regval, priv->base + HOST_VSA_DATA); + + spin_unlock(&priv->lock); + + clk_disable_unprepare(priv->clk); + + return 0; +} + +static struct phy *phy_berlin_sata_phy_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct phy_berlin_priv *priv = dev_get_drvdata(dev); + int i; + + if (WARN_ON(args->args[0] >= priv->nphys)) + return ERR_PTR(-ENODEV); + + for (i = 0; i < priv->nphys; i++) { + if (priv->phys[i]->index == args->args[0]) + break; + } + + if (i == priv->nphys) + return ERR_PTR(-ENODEV); + + return priv->phys[i]->phy; +} + +static const struct phy_ops phy_berlin_sata_ops = { + .power_on = phy_berlin_sata_power_on, + .power_off = phy_berlin_sata_power_off, + .owner = THIS_MODULE, +}; + +static u32 phy_berlin_power_down_bits[] = { + POWER_DOWN_PHY0, + POWER_DOWN_PHY1, +}; + +static int phy_berlin_sata_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *child; + struct phy *phy; + struct phy_provider *phy_provider; + struct phy_berlin_priv *priv; + struct resource *res; + int ret, i = 0; + u32 phy_id; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EINVAL; + + priv->base = devm_ioremap(dev, res->start, resource_size(res)); + if (!priv->base) + return -ENOMEM; + + priv->clk = devm_clk_get(dev, NULL); + if (IS_ERR(priv->clk)) + return PTR_ERR(priv->clk); + + priv->nphys = of_get_child_count(dev->of_node); + if (priv->nphys == 0) + return -ENODEV; + + priv->phys = devm_kcalloc(dev, priv->nphys, sizeof(*priv->phys), + GFP_KERNEL); + if (!priv->phys) + return -ENOMEM; + + if (of_device_is_compatible(dev->of_node, "marvell,berlin2-sata-phy")) + priv->phy_base = BG2_PHY_BASE; + else + priv->phy_base = BG2Q_PHY_BASE; + + dev_set_drvdata(dev, priv); + spin_lock_init(&priv->lock); + + for_each_available_child_of_node(dev->of_node, child) { + struct phy_berlin_desc *phy_desc; + + if (of_property_read_u32(child, "reg", &phy_id)) { + dev_err(dev, "missing reg property in node %s\n", + child->name); + ret = -EINVAL; + goto put_child; + } + + if (phy_id >= ARRAY_SIZE(phy_berlin_power_down_bits)) { + dev_err(dev, "invalid reg in node %s\n", child->name); + ret = -EINVAL; + goto put_child; + } + + phy_desc = devm_kzalloc(dev, sizeof(*phy_desc), GFP_KERNEL); + if (!phy_desc) { + ret = -ENOMEM; + goto put_child; + } + + phy = devm_phy_create(dev, NULL, &phy_berlin_sata_ops); + if (IS_ERR(phy)) { + dev_err(dev, "failed to create PHY %d\n", phy_id); + ret = PTR_ERR(phy); + goto put_child; + } + + phy_desc->phy = phy; + phy_desc->power_bit = phy_berlin_power_down_bits[phy_id]; + phy_desc->index = phy_id; + phy_set_drvdata(phy, phy_desc); + + priv->phys[i++] = phy_desc; + + /* Make sure the PHY is off */ + phy_berlin_sata_power_off(phy); + } + + phy_provider = + devm_of_phy_provider_register(dev, phy_berlin_sata_phy_xlate); + return PTR_ERR_OR_ZERO(phy_provider); +put_child: + of_node_put(child); + return ret; +} + +static const struct of_device_id phy_berlin_sata_of_match[] = { + { .compatible = "marvell,berlin2-sata-phy" }, + { .compatible = "marvell,berlin2q-sata-phy" }, + { }, +}; +MODULE_DEVICE_TABLE(of, phy_berlin_sata_of_match); + +static struct platform_driver phy_berlin_sata_driver = { + .probe = phy_berlin_sata_probe, + .driver = { + .name = "phy-berlin-sata", + .of_match_table = phy_berlin_sata_of_match, + }, +}; +module_platform_driver(phy_berlin_sata_driver); + +MODULE_DESCRIPTION("Marvell Berlin SATA PHY driver"); +MODULE_AUTHOR("Antoine Ténart "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/marvell/phy-berlin-usb.c b/drivers/phy/marvell/phy-berlin-usb.c new file mode 100644 index 000000000000..2017751ede26 --- /dev/null +++ b/drivers/phy/marvell/phy-berlin-usb.c @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2014 Marvell Technology Group Ltd. + * + * Antoine Tenart + * Jisheng Zhang + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include +#include +#include +#include +#include +#include + +#define USB_PHY_PLL 0x04 +#define USB_PHY_PLL_CONTROL 0x08 +#define USB_PHY_TX_CTRL0 0x10 +#define USB_PHY_TX_CTRL1 0x14 +#define USB_PHY_TX_CTRL2 0x18 +#define USB_PHY_RX_CTRL 0x20 +#define USB_PHY_ANALOG 0x34 + +/* USB_PHY_PLL */ +#define CLK_REF_DIV(x) ((x) << 4) +#define FEEDBACK_CLK_DIV(x) ((x) << 8) + +/* USB_PHY_PLL_CONTROL */ +#define CLK_STABLE BIT(0) +#define PLL_CTRL_PIN BIT(1) +#define PLL_CTRL_REG BIT(2) +#define PLL_ON BIT(3) +#define PHASE_OFF_TOL_125 (0x0 << 5) +#define PHASE_OFF_TOL_250 BIT(5) +#define KVC0_CALIB (0x0 << 9) +#define KVC0_REG_CTRL BIT(9) +#define KVC0_HIGH (0x0 << 10) +#define KVC0_LOW (0x3 << 10) +#define CLK_BLK_EN BIT(13) + +/* USB_PHY_TX_CTRL0 */ +#define EXT_HS_RCAL_EN BIT(3) +#define EXT_FS_RCAL_EN BIT(4) +#define IMPCAL_VTH_DIV(x) ((x) << 5) +#define EXT_RS_RCAL_DIV(x) ((x) << 8) +#define EXT_FS_RCAL_DIV(x) ((x) << 12) + +/* USB_PHY_TX_CTRL1 */ +#define TX_VDD15_14 (0x0 << 4) +#define TX_VDD15_15 BIT(4) +#define TX_VDD15_16 (0x2 << 4) +#define TX_VDD15_17 (0x3 << 4) +#define TX_VDD12_VDD (0x0 << 6) +#define TX_VDD12_11 BIT(6) +#define TX_VDD12_12 (0x2 << 6) +#define TX_VDD12_13 (0x3 << 6) +#define LOW_VDD_EN BIT(8) +#define TX_OUT_AMP(x) ((x) << 9) + +/* USB_PHY_TX_CTRL2 */ +#define TX_CHAN_CTRL_REG(x) ((x) << 0) +#define DRV_SLEWRATE(x) ((x) << 4) +#define IMP_CAL_FS_HS_DLY_0 (0x0 << 6) +#define IMP_CAL_FS_HS_DLY_1 BIT(6) +#define IMP_CAL_FS_HS_DLY_2 (0x2 << 6) +#define IMP_CAL_FS_HS_DLY_3 (0x3 << 6) +#define FS_DRV_EN_MASK(x) ((x) << 8) +#define HS_DRV_EN_MASK(x) ((x) << 12) + +/* USB_PHY_RX_CTRL */ +#define PHASE_FREEZE_DLY_2_CL (0x0 << 0) +#define PHASE_FREEZE_DLY_4_CL BIT(0) +#define ACK_LENGTH_8_CL (0x0 << 2) +#define ACK_LENGTH_12_CL BIT(2) +#define ACK_LENGTH_16_CL (0x2 << 2) +#define ACK_LENGTH_20_CL (0x3 << 2) +#define SQ_LENGTH_3 (0x0 << 4) +#define SQ_LENGTH_6 BIT(4) +#define SQ_LENGTH_9 (0x2 << 4) +#define SQ_LENGTH_12 (0x3 << 4) +#define DISCON_THRESHOLD_260 (0x0 << 6) +#define DISCON_THRESHOLD_270 BIT(6) +#define DISCON_THRESHOLD_280 (0x2 << 6) +#define DISCON_THRESHOLD_290 (0x3 << 6) +#define SQ_THRESHOLD(x) ((x) << 8) +#define LPF_COEF(x) ((x) << 12) +#define INTPL_CUR_10 (0x0 << 14) +#define INTPL_CUR_20 BIT(14) +#define INTPL_CUR_30 (0x2 << 14) +#define INTPL_CUR_40 (0x3 << 14) + +/* USB_PHY_ANALOG */ +#define ANA_PWR_UP BIT(1) +#define ANA_PWR_DOWN BIT(2) +#define V2I_VCO_RATIO(x) ((x) << 7) +#define R_ROTATE_90 (0x0 << 10) +#define R_ROTATE_0 BIT(10) +#define MODE_TEST_EN BIT(11) +#define ANA_TEST_DC_CTRL(x) ((x) << 12) + +static const u32 phy_berlin_pll_dividers[] = { + /* Berlin 2 */ + CLK_REF_DIV(0x6) | FEEDBACK_CLK_DIV(0x55), + /* Berlin 2CD/Q */ + CLK_REF_DIV(0xc) | FEEDBACK_CLK_DIV(0x54), +}; + +struct phy_berlin_usb_priv { + void __iomem *base; + struct reset_control *rst_ctrl; + u32 pll_divider; +}; + +static int phy_berlin_usb_power_on(struct phy *phy) +{ + struct phy_berlin_usb_priv *priv = phy_get_drvdata(phy); + + reset_control_reset(priv->rst_ctrl); + + writel(priv->pll_divider, + priv->base + USB_PHY_PLL); + writel(CLK_STABLE | PLL_CTRL_REG | PHASE_OFF_TOL_250 | KVC0_REG_CTRL | + CLK_BLK_EN, priv->base + USB_PHY_PLL_CONTROL); + writel(V2I_VCO_RATIO(0x5) | R_ROTATE_0 | ANA_TEST_DC_CTRL(0x5), + priv->base + USB_PHY_ANALOG); + writel(PHASE_FREEZE_DLY_4_CL | ACK_LENGTH_16_CL | SQ_LENGTH_12 | + DISCON_THRESHOLD_260 | SQ_THRESHOLD(0xa) | LPF_COEF(0x2) | + INTPL_CUR_30, priv->base + USB_PHY_RX_CTRL); + + writel(TX_VDD12_13 | TX_OUT_AMP(0x3), priv->base + USB_PHY_TX_CTRL1); + writel(EXT_HS_RCAL_EN | IMPCAL_VTH_DIV(0x3) | EXT_RS_RCAL_DIV(0x4), + priv->base + USB_PHY_TX_CTRL0); + + writel(EXT_HS_RCAL_EN | IMPCAL_VTH_DIV(0x3) | EXT_RS_RCAL_DIV(0x4) | + EXT_FS_RCAL_DIV(0x2), priv->base + USB_PHY_TX_CTRL0); + + writel(EXT_HS_RCAL_EN | IMPCAL_VTH_DIV(0x3) | EXT_RS_RCAL_DIV(0x4), + priv->base + USB_PHY_TX_CTRL0); + writel(TX_CHAN_CTRL_REG(0xf) | DRV_SLEWRATE(0x3) | IMP_CAL_FS_HS_DLY_3 | + FS_DRV_EN_MASK(0xd), priv->base + USB_PHY_TX_CTRL2); + + return 0; +} + +static const struct phy_ops phy_berlin_usb_ops = { + .power_on = phy_berlin_usb_power_on, + .owner = THIS_MODULE, +}; + +static const struct of_device_id phy_berlin_usb_of_match[] = { + { + .compatible = "marvell,berlin2-usb-phy", + .data = &phy_berlin_pll_dividers[0], + }, + { + .compatible = "marvell,berlin2cd-usb-phy", + .data = &phy_berlin_pll_dividers[1], + }, + { }, +}; +MODULE_DEVICE_TABLE(of, phy_berlin_usb_of_match); + +static int phy_berlin_usb_probe(struct platform_device *pdev) +{ + const struct of_device_id *match = + of_match_device(phy_berlin_usb_of_match, &pdev->dev); + struct phy_berlin_usb_priv *priv; + struct resource *res; + struct phy *phy; + struct phy_provider *phy_provider; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + priv->rst_ctrl = devm_reset_control_get(&pdev->dev, NULL); + if (IS_ERR(priv->rst_ctrl)) + return PTR_ERR(priv->rst_ctrl); + + priv->pll_divider = *((u32 *)match->data); + + phy = devm_phy_create(&pdev->dev, NULL, &phy_berlin_usb_ops); + if (IS_ERR(phy)) { + dev_err(&pdev->dev, "failed to create PHY\n"); + return PTR_ERR(phy); + } + + phy_set_drvdata(phy, priv); + + phy_provider = + devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate); + return PTR_ERR_OR_ZERO(phy_provider); +} + +static struct platform_driver phy_berlin_usb_driver = { + .probe = phy_berlin_usb_probe, + .driver = { + .name = "phy-berlin-usb", + .of_match_table = phy_berlin_usb_of_match, + }, +}; +module_platform_driver(phy_berlin_usb_driver); + +MODULE_AUTHOR("Antoine Tenart "); +MODULE_DESCRIPTION("Marvell Berlin PHY driver for USB"); +MODULE_LICENSE("GPL"); diff --git a/drivers/phy/marvell/phy-mvebu-sata.c b/drivers/phy/marvell/phy-mvebu-sata.c new file mode 100644 index 000000000000..768ce92e81ce --- /dev/null +++ b/drivers/phy/marvell/phy-mvebu-sata.c @@ -0,0 +1,138 @@ +/* + * phy-mvebu-sata.c: SATA Phy driver for the Marvell mvebu SoCs. + * + * Copyright (C) 2013 Andrew Lunn + * + * 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; either version + * 2 of the License, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include + +struct priv { + struct clk *clk; + void __iomem *base; +}; + +#define SATA_PHY_MODE_2 0x0330 +#define MODE_2_FORCE_PU_TX BIT(0) +#define MODE_2_FORCE_PU_RX BIT(1) +#define MODE_2_PU_PLL BIT(2) +#define MODE_2_PU_IVREF BIT(3) +#define SATA_IF_CTRL 0x0050 +#define CTRL_PHY_SHUTDOWN BIT(9) + +static int phy_mvebu_sata_power_on(struct phy *phy) +{ + struct priv *priv = phy_get_drvdata(phy); + u32 reg; + + clk_prepare_enable(priv->clk); + + /* Enable PLL and IVREF */ + reg = readl(priv->base + SATA_PHY_MODE_2); + reg |= (MODE_2_FORCE_PU_TX | MODE_2_FORCE_PU_RX | + MODE_2_PU_PLL | MODE_2_PU_IVREF); + writel(reg , priv->base + SATA_PHY_MODE_2); + + /* Enable PHY */ + reg = readl(priv->base + SATA_IF_CTRL); + reg &= ~CTRL_PHY_SHUTDOWN; + writel(reg, priv->base + SATA_IF_CTRL); + + clk_disable_unprepare(priv->clk); + + return 0; +} + +static int phy_mvebu_sata_power_off(struct phy *phy) +{ + struct priv *priv = phy_get_drvdata(phy); + u32 reg; + + clk_prepare_enable(priv->clk); + + /* Disable PLL and IVREF */ + reg = readl(priv->base + SATA_PHY_MODE_2); + reg &= ~(MODE_2_FORCE_PU_TX | MODE_2_FORCE_PU_RX | + MODE_2_PU_PLL | MODE_2_PU_IVREF); + writel(reg, priv->base + SATA_PHY_MODE_2); + + /* Disable PHY */ + reg = readl(priv->base + SATA_IF_CTRL); + reg |= CTRL_PHY_SHUTDOWN; + writel(reg, priv->base + SATA_IF_CTRL); + + clk_disable_unprepare(priv->clk); + + return 0; +} + +static const struct phy_ops phy_mvebu_sata_ops = { + .power_on = phy_mvebu_sata_power_on, + .power_off = phy_mvebu_sata_power_off, + .owner = THIS_MODULE, +}; + +static int phy_mvebu_sata_probe(struct platform_device *pdev) +{ + struct phy_provider *phy_provider; + struct resource *res; + struct priv *priv; + struct phy *phy; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + priv->clk = devm_clk_get(&pdev->dev, "sata"); + if (IS_ERR(priv->clk)) + return PTR_ERR(priv->clk); + + phy = devm_phy_create(&pdev->dev, NULL, &phy_mvebu_sata_ops); + if (IS_ERR(phy)) + return PTR_ERR(phy); + + phy_set_drvdata(phy, priv); + + phy_provider = devm_of_phy_provider_register(&pdev->dev, + of_phy_simple_xlate); + if (IS_ERR(phy_provider)) + return PTR_ERR(phy_provider); + + /* The boot loader may of left it on. Turn it off. */ + phy_mvebu_sata_power_off(phy); + + return 0; +} + +static const struct of_device_id phy_mvebu_sata_of_match[] = { + { .compatible = "marvell,mvebu-sata-phy" }, + { }, +}; +MODULE_DEVICE_TABLE(of, phy_mvebu_sata_of_match); + +static struct platform_driver phy_mvebu_sata_driver = { + .probe = phy_mvebu_sata_probe, + .driver = { + .name = "phy-mvebu-sata", + .of_match_table = phy_mvebu_sata_of_match, + } +}; +module_platform_driver(phy_mvebu_sata_driver); + +MODULE_AUTHOR("Andrew Lunn "); +MODULE_DESCRIPTION("Marvell MVEBU SATA PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/marvell/phy-pxa-28nm-hsic.c b/drivers/phy/marvell/phy-pxa-28nm-hsic.c new file mode 100644 index 000000000000..234aacf4db20 --- /dev/null +++ b/drivers/phy/marvell/phy-pxa-28nm-hsic.c @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2015 Linaro, Ltd. + * Rob Herring + * + * Based on vendor driver: + * Copyright (C) 2013 Marvell Inc. + * Author: Chao Xie + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PHY_28NM_HSIC_CTRL 0x08 +#define PHY_28NM_HSIC_IMPCAL_CAL 0x18 +#define PHY_28NM_HSIC_PLL_CTRL01 0x1c +#define PHY_28NM_HSIC_PLL_CTRL2 0x20 +#define PHY_28NM_HSIC_INT 0x28 + +#define PHY_28NM_HSIC_PLL_SELLPFR_SHIFT 26 +#define PHY_28NM_HSIC_PLL_FBDIV_SHIFT 0 +#define PHY_28NM_HSIC_PLL_REFDIV_SHIFT 9 + +#define PHY_28NM_HSIC_S2H_PU_PLL BIT(10) +#define PHY_28NM_HSIC_H2S_PLL_LOCK BIT(15) +#define PHY_28NM_HSIC_S2H_HSIC_EN BIT(7) +#define S2H_DRV_SE0_4RESUME BIT(14) +#define PHY_28NM_HSIC_H2S_IMPCAL_DONE BIT(27) + +#define PHY_28NM_HSIC_CONNECT_INT BIT(1) +#define PHY_28NM_HSIC_HS_READY_INT BIT(2) + +struct mv_hsic_phy { + struct phy *phy; + struct platform_device *pdev; + void __iomem *base; + struct clk *clk; +}; + +static bool wait_for_reg(void __iomem *reg, u32 mask, unsigned long timeout) +{ + timeout += jiffies; + while (time_is_after_eq_jiffies(timeout)) { + if ((readl(reg) & mask) == mask) + return true; + msleep(1); + } + return false; +} + +static int mv_hsic_phy_init(struct phy *phy) +{ + struct mv_hsic_phy *mv_phy = phy_get_drvdata(phy); + struct platform_device *pdev = mv_phy->pdev; + void __iomem *base = mv_phy->base; + + clk_prepare_enable(mv_phy->clk); + + /* Set reference clock */ + writel(0x1 << PHY_28NM_HSIC_PLL_SELLPFR_SHIFT | + 0xf0 << PHY_28NM_HSIC_PLL_FBDIV_SHIFT | + 0xd << PHY_28NM_HSIC_PLL_REFDIV_SHIFT, + base + PHY_28NM_HSIC_PLL_CTRL01); + + /* Turn on PLL */ + writel(readl(base + PHY_28NM_HSIC_PLL_CTRL2) | + PHY_28NM_HSIC_S2H_PU_PLL, + base + PHY_28NM_HSIC_PLL_CTRL2); + + /* Make sure PHY PLL is locked */ + if (!wait_for_reg(base + PHY_28NM_HSIC_PLL_CTRL2, + PHY_28NM_HSIC_H2S_PLL_LOCK, HZ / 10)) { + dev_err(&pdev->dev, "HSIC PHY PLL not locked after 100mS."); + clk_disable_unprepare(mv_phy->clk); + return -ETIMEDOUT; + } + + return 0; +} + +static int mv_hsic_phy_power_on(struct phy *phy) +{ + struct mv_hsic_phy *mv_phy = phy_get_drvdata(phy); + struct platform_device *pdev = mv_phy->pdev; + void __iomem *base = mv_phy->base; + u32 reg; + + reg = readl(base + PHY_28NM_HSIC_CTRL); + /* Avoid SE0 state when resume for some device will take it as reset */ + reg &= ~S2H_DRV_SE0_4RESUME; + reg |= PHY_28NM_HSIC_S2H_HSIC_EN; /* Enable HSIC PHY */ + writel(reg, base + PHY_28NM_HSIC_CTRL); + + /* + * Calibration Timing + * ____________________________ + * CAL START ___| + * ____________________ + * CAL_DONE ___________| + * | 400us | + */ + + /* Make sure PHY Calibration is ready */ + if (!wait_for_reg(base + PHY_28NM_HSIC_IMPCAL_CAL, + PHY_28NM_HSIC_H2S_IMPCAL_DONE, HZ / 10)) { + dev_warn(&pdev->dev, "HSIC PHY READY not set after 100mS."); + return -ETIMEDOUT; + } + + /* Waiting for HSIC connect int*/ + if (!wait_for_reg(base + PHY_28NM_HSIC_INT, + PHY_28NM_HSIC_CONNECT_INT, HZ / 5)) { + dev_warn(&pdev->dev, "HSIC wait for connect interrupt timeout."); + return -ETIMEDOUT; + } + + return 0; +} + +static int mv_hsic_phy_power_off(struct phy *phy) +{ + struct mv_hsic_phy *mv_phy = phy_get_drvdata(phy); + void __iomem *base = mv_phy->base; + + writel(readl(base + PHY_28NM_HSIC_CTRL) & ~PHY_28NM_HSIC_S2H_HSIC_EN, + base + PHY_28NM_HSIC_CTRL); + + return 0; +} + +static int mv_hsic_phy_exit(struct phy *phy) +{ + struct mv_hsic_phy *mv_phy = phy_get_drvdata(phy); + void __iomem *base = mv_phy->base; + + /* Turn off PLL */ + writel(readl(base + PHY_28NM_HSIC_PLL_CTRL2) & + ~PHY_28NM_HSIC_S2H_PU_PLL, + base + PHY_28NM_HSIC_PLL_CTRL2); + + clk_disable_unprepare(mv_phy->clk); + return 0; +} + + +static const struct phy_ops hsic_ops = { + .init = mv_hsic_phy_init, + .power_on = mv_hsic_phy_power_on, + .power_off = mv_hsic_phy_power_off, + .exit = mv_hsic_phy_exit, + .owner = THIS_MODULE, +}; + +static int mv_hsic_phy_probe(struct platform_device *pdev) +{ + struct phy_provider *phy_provider; + struct mv_hsic_phy *mv_phy; + struct resource *r; + + mv_phy = devm_kzalloc(&pdev->dev, sizeof(*mv_phy), GFP_KERNEL); + if (!mv_phy) + return -ENOMEM; + + mv_phy->pdev = pdev; + + mv_phy->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(mv_phy->clk)) { + dev_err(&pdev->dev, "failed to get clock.\n"); + return PTR_ERR(mv_phy->clk); + } + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + mv_phy->base = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(mv_phy->base)) + return PTR_ERR(mv_phy->base); + + mv_phy->phy = devm_phy_create(&pdev->dev, pdev->dev.of_node, &hsic_ops); + if (IS_ERR(mv_phy->phy)) + return PTR_ERR(mv_phy->phy); + + phy_set_drvdata(mv_phy->phy, mv_phy); + + phy_provider = devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate); + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id mv_hsic_phy_dt_match[] = { + { .compatible = "marvell,pxa1928-hsic-phy", }, + {}, +}; +MODULE_DEVICE_TABLE(of, mv_hsic_phy_dt_match); + +static struct platform_driver mv_hsic_phy_driver = { + .probe = mv_hsic_phy_probe, + .driver = { + .name = "mv-hsic-phy", + .of_match_table = of_match_ptr(mv_hsic_phy_dt_match), + }, +}; +module_platform_driver(mv_hsic_phy_driver); + +MODULE_AUTHOR("Rob Herring "); +MODULE_DESCRIPTION("Marvell HSIC phy driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/marvell/phy-pxa-28nm-usb2.c b/drivers/phy/marvell/phy-pxa-28nm-usb2.c new file mode 100644 index 000000000000..37e9c8ca4983 --- /dev/null +++ b/drivers/phy/marvell/phy-pxa-28nm-usb2.c @@ -0,0 +1,355 @@ +/* + * Copyright (C) 2015 Linaro, Ltd. + * Rob Herring + * + * Based on vendor driver: + * Copyright (C) 2013 Marvell Inc. + * Author: Chao Xie + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* USB PXA1928 PHY mapping */ +#define PHY_28NM_PLL_REG0 0x0 +#define PHY_28NM_PLL_REG1 0x4 +#define PHY_28NM_CAL_REG 0x8 +#define PHY_28NM_TX_REG0 0x0c +#define PHY_28NM_TX_REG1 0x10 +#define PHY_28NM_RX_REG0 0x14 +#define PHY_28NM_RX_REG1 0x18 +#define PHY_28NM_DIG_REG0 0x1c +#define PHY_28NM_DIG_REG1 0x20 +#define PHY_28NM_TEST_REG0 0x24 +#define PHY_28NM_TEST_REG1 0x28 +#define PHY_28NM_MOC_REG 0x2c +#define PHY_28NM_PHY_RESERVE 0x30 +#define PHY_28NM_OTG_REG 0x34 +#define PHY_28NM_CHRG_DET 0x38 +#define PHY_28NM_CTRL_REG0 0xc4 +#define PHY_28NM_CTRL_REG1 0xc8 +#define PHY_28NM_CTRL_REG2 0xd4 +#define PHY_28NM_CTRL_REG3 0xdc + +/* PHY_28NM_PLL_REG0 */ +#define PHY_28NM_PLL_READY BIT(31) + +#define PHY_28NM_PLL_SELLPFR_SHIFT 28 +#define PHY_28NM_PLL_SELLPFR_MASK (0x3 << 28) + +#define PHY_28NM_PLL_FBDIV_SHIFT 16 +#define PHY_28NM_PLL_FBDIV_MASK (0x1ff << 16) + +#define PHY_28NM_PLL_ICP_SHIFT 8 +#define PHY_28NM_PLL_ICP_MASK (0x7 << 8) + +#define PHY_28NM_PLL_REFDIV_SHIFT 0 +#define PHY_28NM_PLL_REFDIV_MASK 0x7f + +/* PHY_28NM_PLL_REG1 */ +#define PHY_28NM_PLL_PU_BY_REG BIT(1) + +#define PHY_28NM_PLL_PU_PLL BIT(0) + +/* PHY_28NM_CAL_REG */ +#define PHY_28NM_PLL_PLLCAL_DONE BIT(31) + +#define PHY_28NM_PLL_IMPCAL_DONE BIT(23) + +#define PHY_28NM_PLL_KVCO_SHIFT 16 +#define PHY_28NM_PLL_KVCO_MASK (0x7 << 16) + +#define PHY_28NM_PLL_CAL12_SHIFT 20 +#define PHY_28NM_PLL_CAL12_MASK (0x3 << 20) + +#define PHY_28NM_IMPCAL_VTH_SHIFT 8 +#define PHY_28NM_IMPCAL_VTH_MASK (0x7 << 8) + +#define PHY_28NM_PLLCAL_START_SHIFT 22 +#define PHY_28NM_IMPCAL_START_SHIFT 13 + +/* PHY_28NM_TX_REG0 */ +#define PHY_28NM_TX_PU_BY_REG BIT(25) + +#define PHY_28NM_TX_PU_ANA BIT(24) + +#define PHY_28NM_TX_AMP_SHIFT 20 +#define PHY_28NM_TX_AMP_MASK (0x7 << 20) + +/* PHY_28NM_RX_REG0 */ +#define PHY_28NM_RX_SQ_THRESH_SHIFT 0 +#define PHY_28NM_RX_SQ_THRESH_MASK (0xf << 0) + +/* PHY_28NM_RX_REG1 */ +#define PHY_28NM_RX_SQCAL_DONE BIT(31) + +/* PHY_28NM_DIG_REG0 */ +#define PHY_28NM_DIG_BITSTAFFING_ERR BIT(31) +#define PHY_28NM_DIG_SYNC_ERR BIT(30) + +#define PHY_28NM_DIG_SQ_FILT_SHIFT 16 +#define PHY_28NM_DIG_SQ_FILT_MASK (0x7 << 16) + +#define PHY_28NM_DIG_SQ_BLK_SHIFT 12 +#define PHY_28NM_DIG_SQ_BLK_MASK (0x7 << 12) + +#define PHY_28NM_DIG_SYNC_NUM_SHIFT 0 +#define PHY_28NM_DIG_SYNC_NUM_MASK (0x3 << 0) + +#define PHY_28NM_PLL_LOCK_BYPASS BIT(7) + +/* PHY_28NM_OTG_REG */ +#define PHY_28NM_OTG_CONTROL_BY_PIN BIT(5) +#define PHY_28NM_OTG_PU_OTG BIT(4) + +#define PHY_28NM_CHGDTC_ENABLE_SWITCH_DM_SHIFT_28 13 +#define PHY_28NM_CHGDTC_ENABLE_SWITCH_DP_SHIFT_28 12 +#define PHY_28NM_CHGDTC_VSRC_CHARGE_SHIFT_28 10 +#define PHY_28NM_CHGDTC_VDAT_CHARGE_SHIFT_28 8 +#define PHY_28NM_CHGDTC_CDP_DM_AUTO_SWITCH_SHIFT_28 7 +#define PHY_28NM_CHGDTC_DP_DM_SWAP_SHIFT_28 6 +#define PHY_28NM_CHGDTC_PU_CHRG_DTC_SHIFT_28 5 +#define PHY_28NM_CHGDTC_PD_EN_SHIFT_28 4 +#define PHY_28NM_CHGDTC_DCP_EN_SHIFT_28 3 +#define PHY_28NM_CHGDTC_CDP_EN_SHIFT_28 2 +#define PHY_28NM_CHGDTC_TESTMON_CHRGDTC_SHIFT_28 0 + +#define PHY_28NM_CTRL1_CHRG_DTC_OUT_SHIFT_28 4 +#define PHY_28NM_CTRL1_VBUSDTC_OUT_SHIFT_28 2 + +#define PHY_28NM_CTRL3_OVERWRITE BIT(0) +#define PHY_28NM_CTRL3_VBUS_VALID BIT(4) +#define PHY_28NM_CTRL3_AVALID BIT(5) +#define PHY_28NM_CTRL3_BVALID BIT(6) + +struct mv_usb2_phy { + struct phy *phy; + struct platform_device *pdev; + void __iomem *base; + struct clk *clk; +}; + +static bool wait_for_reg(void __iomem *reg, u32 mask, unsigned long timeout) +{ + timeout += jiffies; + while (time_is_after_eq_jiffies(timeout)) { + if ((readl(reg) & mask) == mask) + return true; + msleep(1); + } + return false; +} + +static int mv_usb2_phy_28nm_init(struct phy *phy) +{ + struct mv_usb2_phy *mv_phy = phy_get_drvdata(phy); + struct platform_device *pdev = mv_phy->pdev; + void __iomem *base = mv_phy->base; + u32 reg; + int ret; + + clk_prepare_enable(mv_phy->clk); + + /* PHY_28NM_PLL_REG0 */ + reg = readl(base + PHY_28NM_PLL_REG0) & + ~(PHY_28NM_PLL_SELLPFR_MASK | PHY_28NM_PLL_FBDIV_MASK + | PHY_28NM_PLL_ICP_MASK | PHY_28NM_PLL_REFDIV_MASK); + writel(reg | (0x1 << PHY_28NM_PLL_SELLPFR_SHIFT + | 0xf0 << PHY_28NM_PLL_FBDIV_SHIFT + | 0x3 << PHY_28NM_PLL_ICP_SHIFT + | 0xd << PHY_28NM_PLL_REFDIV_SHIFT), + base + PHY_28NM_PLL_REG0); + + /* PHY_28NM_PLL_REG1 */ + reg = readl(base + PHY_28NM_PLL_REG1); + writel(reg | PHY_28NM_PLL_PU_PLL | PHY_28NM_PLL_PU_BY_REG, + base + PHY_28NM_PLL_REG1); + + /* PHY_28NM_TX_REG0 */ + reg = readl(base + PHY_28NM_TX_REG0) & ~PHY_28NM_TX_AMP_MASK; + writel(reg | PHY_28NM_TX_PU_BY_REG | 0x3 << PHY_28NM_TX_AMP_SHIFT | + PHY_28NM_TX_PU_ANA, + base + PHY_28NM_TX_REG0); + + /* PHY_28NM_RX_REG0 */ + reg = readl(base + PHY_28NM_RX_REG0) & ~PHY_28NM_RX_SQ_THRESH_MASK; + writel(reg | 0xa << PHY_28NM_RX_SQ_THRESH_SHIFT, + base + PHY_28NM_RX_REG0); + + /* PHY_28NM_DIG_REG0 */ + reg = readl(base + PHY_28NM_DIG_REG0) & + ~(PHY_28NM_DIG_BITSTAFFING_ERR | PHY_28NM_DIG_SYNC_ERR | + PHY_28NM_DIG_SQ_FILT_MASK | PHY_28NM_DIG_SQ_BLK_MASK | + PHY_28NM_DIG_SYNC_NUM_MASK); + writel(reg | (0x1 << PHY_28NM_DIG_SYNC_NUM_SHIFT | + PHY_28NM_PLL_LOCK_BYPASS), + base + PHY_28NM_DIG_REG0); + + /* PHY_28NM_OTG_REG */ + reg = readl(base + PHY_28NM_OTG_REG) | PHY_28NM_OTG_PU_OTG; + writel(reg & ~PHY_28NM_OTG_CONTROL_BY_PIN, base + PHY_28NM_OTG_REG); + + /* + * Calibration Timing + * ____________________________ + * CAL START ___| + * ____________________ + * CAL_DONE ___________| + * | 400us | + */ + + /* Make sure PHY Calibration is ready */ + if (!wait_for_reg(base + PHY_28NM_CAL_REG, + PHY_28NM_PLL_PLLCAL_DONE | PHY_28NM_PLL_IMPCAL_DONE, + HZ / 10)) { + dev_warn(&pdev->dev, "USB PHY PLL calibrate not done after 100mS."); + ret = -ETIMEDOUT; + goto err_clk; + } + if (!wait_for_reg(base + PHY_28NM_RX_REG1, + PHY_28NM_RX_SQCAL_DONE, HZ / 10)) { + dev_warn(&pdev->dev, "USB PHY RX SQ calibrate not done after 100mS."); + ret = -ETIMEDOUT; + goto err_clk; + } + /* Make sure PHY PLL is ready */ + if (!wait_for_reg(base + PHY_28NM_PLL_REG0, + PHY_28NM_PLL_READY, HZ / 10)) { + dev_warn(&pdev->dev, "PLL_READY not set after 100mS."); + ret = -ETIMEDOUT; + goto err_clk; + } + + return 0; +err_clk: + clk_disable_unprepare(mv_phy->clk); + return ret; +} + +static int mv_usb2_phy_28nm_power_on(struct phy *phy) +{ + struct mv_usb2_phy *mv_phy = phy_get_drvdata(phy); + void __iomem *base = mv_phy->base; + + writel(readl(base + PHY_28NM_CTRL_REG3) | + (PHY_28NM_CTRL3_OVERWRITE | PHY_28NM_CTRL3_VBUS_VALID | + PHY_28NM_CTRL3_AVALID | PHY_28NM_CTRL3_BVALID), + base + PHY_28NM_CTRL_REG3); + + return 0; +} + +static int mv_usb2_phy_28nm_power_off(struct phy *phy) +{ + struct mv_usb2_phy *mv_phy = phy_get_drvdata(phy); + void __iomem *base = mv_phy->base; + + writel(readl(base + PHY_28NM_CTRL_REG3) | + ~(PHY_28NM_CTRL3_OVERWRITE | PHY_28NM_CTRL3_VBUS_VALID + | PHY_28NM_CTRL3_AVALID | PHY_28NM_CTRL3_BVALID), + base + PHY_28NM_CTRL_REG3); + + return 0; +} + +static int mv_usb2_phy_28nm_exit(struct phy *phy) +{ + struct mv_usb2_phy *mv_phy = phy_get_drvdata(phy); + void __iomem *base = mv_phy->base; + unsigned int val; + + val = readw(base + PHY_28NM_PLL_REG1); + val &= ~PHY_28NM_PLL_PU_PLL; + writew(val, base + PHY_28NM_PLL_REG1); + + /* power down PHY Analog part */ + val = readw(base + PHY_28NM_TX_REG0); + val &= ~PHY_28NM_TX_PU_ANA; + writew(val, base + PHY_28NM_TX_REG0); + + /* power down PHY OTG part */ + val = readw(base + PHY_28NM_OTG_REG); + val &= ~PHY_28NM_OTG_PU_OTG; + writew(val, base + PHY_28NM_OTG_REG); + + clk_disable_unprepare(mv_phy->clk); + return 0; +} + +static const struct phy_ops usb_ops = { + .init = mv_usb2_phy_28nm_init, + .power_on = mv_usb2_phy_28nm_power_on, + .power_off = mv_usb2_phy_28nm_power_off, + .exit = mv_usb2_phy_28nm_exit, + .owner = THIS_MODULE, +}; + +static int mv_usb2_phy_probe(struct platform_device *pdev) +{ + struct phy_provider *phy_provider; + struct mv_usb2_phy *mv_phy; + struct resource *r; + + mv_phy = devm_kzalloc(&pdev->dev, sizeof(*mv_phy), GFP_KERNEL); + if (!mv_phy) + return -ENOMEM; + + mv_phy->pdev = pdev; + + mv_phy->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(mv_phy->clk)) { + dev_err(&pdev->dev, "failed to get clock.\n"); + return PTR_ERR(mv_phy->clk); + } + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + mv_phy->base = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(mv_phy->base)) + return PTR_ERR(mv_phy->base); + + mv_phy->phy = devm_phy_create(&pdev->dev, pdev->dev.of_node, &usb_ops); + if (IS_ERR(mv_phy->phy)) + return PTR_ERR(mv_phy->phy); + + phy_set_drvdata(mv_phy->phy, mv_phy); + + phy_provider = devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate); + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id mv_usbphy_dt_match[] = { + { .compatible = "marvell,pxa1928-usb-phy", }, + {}, +}; +MODULE_DEVICE_TABLE(of, mv_usbphy_dt_match); + +static struct platform_driver mv_usb2_phy_driver = { + .probe = mv_usb2_phy_probe, + .driver = { + .name = "mv-usb2-phy", + .of_match_table = of_match_ptr(mv_usbphy_dt_match), + }, +}; +module_platform_driver(mv_usb2_phy_driver); + +MODULE_AUTHOR("Rob Herring "); +MODULE_DESCRIPTION("Marvell USB2 phy driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-armada375-usb2.c b/drivers/phy/phy-armada375-usb2.c deleted file mode 100644 index 1a3db288c0a9..000000000000 --- a/drivers/phy/phy-armada375-usb2.c +++ /dev/null @@ -1,158 +0,0 @@ -/* - * USB cluster support for Armada 375 platform. - * - * Copyright (C) 2014 Marvell - * - * Gregory CLEMENT - * - * This file is licensed under the terms of the GNU General Public - * License version 2 or later. This program is licensed "as is" - * without any warranty of any kind, whether express or implied. - * - * Armada 375 comes with an USB2 host and device controller and an - * USB3 controller. The USB cluster control register allows to manage - * common features of both USB controllers. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#define USB2_PHY_CONFIG_DISABLE BIT(0) - -struct armada375_cluster_phy { - struct phy *phy; - void __iomem *reg; - bool use_usb3; - int phy_provided; -}; - -static int armada375_usb_phy_init(struct phy *phy) -{ - struct armada375_cluster_phy *cluster_phy; - u32 reg; - - cluster_phy = phy_get_drvdata(phy); - if (!cluster_phy) - return -ENODEV; - - reg = readl(cluster_phy->reg); - if (cluster_phy->use_usb3) - reg |= USB2_PHY_CONFIG_DISABLE; - else - reg &= ~USB2_PHY_CONFIG_DISABLE; - writel(reg, cluster_phy->reg); - - return 0; -} - -static const struct phy_ops armada375_usb_phy_ops = { - .init = armada375_usb_phy_init, - .owner = THIS_MODULE, -}; - -/* - * Only one controller can use this PHY. We shouldn't have the case - * when two controllers want to use this PHY. But if this case occurs - * then we provide a phy to the first one and return an error for the - * next one. This error has also to be an error returned by - * devm_phy_optional_get() so different from ENODEV for USB2. In the - * USB3 case it still optional and we use ENODEV. - */ -static struct phy *armada375_usb_phy_xlate(struct device *dev, - struct of_phandle_args *args) -{ - struct armada375_cluster_phy *cluster_phy = dev_get_drvdata(dev); - - if (!cluster_phy) - return ERR_PTR(-ENODEV); - - /* - * Either the phy had never been requested and then the first - * usb claiming it can get it, or it had already been - * requested in this case, we only allow to use it with the - * same configuration. - */ - if (WARN_ON((cluster_phy->phy_provided != PHY_NONE) && - (cluster_phy->phy_provided != args->args[0]))) { - dev_err(dev, "This PHY has already been provided!\n"); - dev_err(dev, "Check your device tree, only one controller can use it\n."); - if (args->args[0] == PHY_TYPE_USB2) - return ERR_PTR(-EBUSY); - else - return ERR_PTR(-ENODEV); - } - - if (args->args[0] == PHY_TYPE_USB2) - cluster_phy->use_usb3 = false; - else if (args->args[0] == PHY_TYPE_USB3) - cluster_phy->use_usb3 = true; - else { - dev_err(dev, "Invalid PHY mode\n"); - return ERR_PTR(-ENODEV); - } - - /* Store which phy mode is used for next test */ - cluster_phy->phy_provided = args->args[0]; - - return cluster_phy->phy; -} - -static int armada375_usb_phy_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct phy *phy; - struct phy_provider *phy_provider; - void __iomem *usb_cluster_base; - struct resource *res; - struct armada375_cluster_phy *cluster_phy; - - cluster_phy = devm_kzalloc(dev, sizeof(*cluster_phy), GFP_KERNEL); - if (!cluster_phy) - return -ENOMEM; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - usb_cluster_base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(usb_cluster_base)) - return PTR_ERR(usb_cluster_base); - - phy = devm_phy_create(dev, NULL, &armada375_usb_phy_ops); - if (IS_ERR(phy)) { - dev_err(dev, "failed to create PHY\n"); - return PTR_ERR(phy); - } - - cluster_phy->phy = phy; - cluster_phy->reg = usb_cluster_base; - - dev_set_drvdata(dev, cluster_phy); - phy_set_drvdata(phy, cluster_phy); - - phy_provider = devm_of_phy_provider_register(&pdev->dev, - armada375_usb_phy_xlate); - return PTR_ERR_OR_ZERO(phy_provider); -} - -static const struct of_device_id of_usb_cluster_table[] = { - { .compatible = "marvell,armada-375-usb-cluster", }, - { /* end of list */ }, -}; -MODULE_DEVICE_TABLE(of, of_usb_cluster_table); - -static struct platform_driver armada375_usb_phy_driver = { - .probe = armada375_usb_phy_probe, - .driver = { - .of_match_table = of_usb_cluster_table, - .name = "armada-375-usb-cluster", - } -}; -module_platform_driver(armada375_usb_phy_driver); - -MODULE_DESCRIPTION("Armada 375 USB cluster driver"); -MODULE_AUTHOR("Gregory CLEMENT "); -MODULE_LICENSE("GPL"); diff --git a/drivers/phy/phy-bcm-cygnus-pcie.c b/drivers/phy/phy-bcm-cygnus-pcie.c deleted file mode 100644 index 0f4ac5d63cff..000000000000 --- a/drivers/phy/phy-bcm-cygnus-pcie.c +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Copyright (C) 2015 Broadcom Corporation - * - * 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. - * - * This program is distributed "as is" WITHOUT ANY WARRANTY of any - * kind, whether express or implied; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include - -#define PCIE_CFG_OFFSET 0x00 -#define PCIE1_PHY_IDDQ_SHIFT 10 -#define PCIE0_PHY_IDDQ_SHIFT 2 - -enum cygnus_pcie_phy_id { - CYGNUS_PHY_PCIE0 = 0, - CYGNUS_PHY_PCIE1, - MAX_NUM_PHYS, -}; - -struct cygnus_pcie_phy_core; - -/** - * struct cygnus_pcie_phy - Cygnus PCIe PHY device - * @core: pointer to the Cygnus PCIe PHY core control - * @id: internal ID to identify the Cygnus PCIe PHY - * @phy: pointer to the kernel PHY device - */ -struct cygnus_pcie_phy { - struct cygnus_pcie_phy_core *core; - enum cygnus_pcie_phy_id id; - struct phy *phy; -}; - -/** - * struct cygnus_pcie_phy_core - Cygnus PCIe PHY core control - * @dev: pointer to device - * @base: base register - * @lock: mutex to protect access to individual PHYs - * @phys: pointer to Cygnus PHY device - */ -struct cygnus_pcie_phy_core { - struct device *dev; - void __iomem *base; - struct mutex lock; - struct cygnus_pcie_phy phys[MAX_NUM_PHYS]; -}; - -static int cygnus_pcie_power_config(struct cygnus_pcie_phy *phy, bool enable) -{ - struct cygnus_pcie_phy_core *core = phy->core; - unsigned shift; - u32 val; - - mutex_lock(&core->lock); - - switch (phy->id) { - case CYGNUS_PHY_PCIE0: - shift = PCIE0_PHY_IDDQ_SHIFT; - break; - - case CYGNUS_PHY_PCIE1: - shift = PCIE1_PHY_IDDQ_SHIFT; - break; - - default: - mutex_unlock(&core->lock); - dev_err(core->dev, "PCIe PHY %d invalid\n", phy->id); - return -EINVAL; - } - - if (enable) { - val = readl(core->base + PCIE_CFG_OFFSET); - val &= ~BIT(shift); - writel(val, core->base + PCIE_CFG_OFFSET); - /* - * Wait 50 ms for the PCIe Serdes to stabilize after the analog - * front end is brought up - */ - msleep(50); - } else { - val = readl(core->base + PCIE_CFG_OFFSET); - val |= BIT(shift); - writel(val, core->base + PCIE_CFG_OFFSET); - } - - mutex_unlock(&core->lock); - dev_dbg(core->dev, "PCIe PHY %d %s\n", phy->id, - enable ? "enabled" : "disabled"); - return 0; -} - -static int cygnus_pcie_phy_power_on(struct phy *p) -{ - struct cygnus_pcie_phy *phy = phy_get_drvdata(p); - - return cygnus_pcie_power_config(phy, true); -} - -static int cygnus_pcie_phy_power_off(struct phy *p) -{ - struct cygnus_pcie_phy *phy = phy_get_drvdata(p); - - return cygnus_pcie_power_config(phy, false); -} - -static const struct phy_ops cygnus_pcie_phy_ops = { - .power_on = cygnus_pcie_phy_power_on, - .power_off = cygnus_pcie_phy_power_off, - .owner = THIS_MODULE, -}; - -static int cygnus_pcie_phy_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct device_node *node = dev->of_node, *child; - struct cygnus_pcie_phy_core *core; - struct phy_provider *provider; - struct resource *res; - unsigned cnt = 0; - int ret; - - if (of_get_child_count(node) == 0) { - dev_err(dev, "PHY no child node\n"); - return -ENODEV; - } - - core = devm_kzalloc(dev, sizeof(*core), GFP_KERNEL); - if (!core) - return -ENOMEM; - - core->dev = dev; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - core->base = devm_ioremap_resource(dev, res); - if (IS_ERR(core->base)) - return PTR_ERR(core->base); - - mutex_init(&core->lock); - - for_each_available_child_of_node(node, child) { - unsigned int id; - struct cygnus_pcie_phy *p; - - if (of_property_read_u32(child, "reg", &id)) { - dev_err(dev, "missing reg property for %s\n", - child->name); - ret = -EINVAL; - goto put_child; - } - - if (id >= MAX_NUM_PHYS) { - dev_err(dev, "invalid PHY id: %u\n", id); - ret = -EINVAL; - goto put_child; - } - - if (core->phys[id].phy) { - dev_err(dev, "duplicated PHY id: %u\n", id); - ret = -EINVAL; - goto put_child; - } - - p = &core->phys[id]; - p->phy = devm_phy_create(dev, child, &cygnus_pcie_phy_ops); - if (IS_ERR(p->phy)) { - dev_err(dev, "failed to create PHY\n"); - ret = PTR_ERR(p->phy); - goto put_child; - } - - p->core = core; - p->id = id; - phy_set_drvdata(p->phy, p); - cnt++; - } - - dev_set_drvdata(dev, core); - - provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); - if (IS_ERR(provider)) { - dev_err(dev, "failed to register PHY provider\n"); - return PTR_ERR(provider); - } - - dev_dbg(dev, "registered %u PCIe PHY(s)\n", cnt); - - return 0; -put_child: - of_node_put(child); - return ret; -} - -static const struct of_device_id cygnus_pcie_phy_match_table[] = { - { .compatible = "brcm,cygnus-pcie-phy" }, - { /* sentinel */ } -}; -MODULE_DEVICE_TABLE(of, cygnus_pcie_phy_match_table); - -static struct platform_driver cygnus_pcie_phy_driver = { - .driver = { - .name = "cygnus-pcie-phy", - .of_match_table = cygnus_pcie_phy_match_table, - }, - .probe = cygnus_pcie_phy_probe, -}; -module_platform_driver(cygnus_pcie_phy_driver); - -MODULE_AUTHOR("Ray Jui "); -MODULE_DESCRIPTION("Broadcom Cygnus PCIe PHY driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-bcm-kona-usb2.c b/drivers/phy/phy-bcm-kona-usb2.c deleted file mode 100644 index 7b67fe49e30b..000000000000 --- a/drivers/phy/phy-bcm-kona-usb2.c +++ /dev/null @@ -1,155 +0,0 @@ -/* - * phy-bcm-kona-usb2.c - Broadcom Kona USB2 Phy Driver - * - * Copyright (C) 2013 Linaro Limited - * Matt Porter - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#define OTGCTL (0) -#define OTGCTL_OTGSTAT2 BIT(31) -#define OTGCTL_OTGSTAT1 BIT(30) -#define OTGCTL_PRST_N_SW BIT(11) -#define OTGCTL_HRESET_N BIT(10) -#define OTGCTL_UTMI_LINE_STATE1 BIT(9) -#define OTGCTL_UTMI_LINE_STATE0 BIT(8) - -#define P1CTL (8) -#define P1CTL_SOFT_RESET BIT(1) -#define P1CTL_NON_DRIVING BIT(0) - -struct bcm_kona_usb { - void __iomem *regs; -}; - -static void bcm_kona_usb_phy_power(struct bcm_kona_usb *phy, int on) -{ - u32 val; - - val = readl(phy->regs + OTGCTL); - if (on) { - /* Configure and power PHY */ - val &= ~(OTGCTL_OTGSTAT2 | OTGCTL_OTGSTAT1 | - OTGCTL_UTMI_LINE_STATE1 | OTGCTL_UTMI_LINE_STATE0); - val |= OTGCTL_PRST_N_SW | OTGCTL_HRESET_N; - } else { - val &= ~(OTGCTL_PRST_N_SW | OTGCTL_HRESET_N); - } - writel(val, phy->regs + OTGCTL); -} - -static int bcm_kona_usb_phy_init(struct phy *gphy) -{ - struct bcm_kona_usb *phy = phy_get_drvdata(gphy); - u32 val; - - /* Soft reset PHY */ - val = readl(phy->regs + P1CTL); - val &= ~P1CTL_NON_DRIVING; - val |= P1CTL_SOFT_RESET; - writel(val, phy->regs + P1CTL); - writel(val & ~P1CTL_SOFT_RESET, phy->regs + P1CTL); - /* Reset needs to be asserted for 2ms */ - mdelay(2); - writel(val | P1CTL_SOFT_RESET, phy->regs + P1CTL); - - return 0; -} - -static int bcm_kona_usb_phy_power_on(struct phy *gphy) -{ - struct bcm_kona_usb *phy = phy_get_drvdata(gphy); - - bcm_kona_usb_phy_power(phy, 1); - - return 0; -} - -static int bcm_kona_usb_phy_power_off(struct phy *gphy) -{ - struct bcm_kona_usb *phy = phy_get_drvdata(gphy); - - bcm_kona_usb_phy_power(phy, 0); - - return 0; -} - -static const struct phy_ops ops = { - .init = bcm_kona_usb_phy_init, - .power_on = bcm_kona_usb_phy_power_on, - .power_off = bcm_kona_usb_phy_power_off, - .owner = THIS_MODULE, -}; - -static int bcm_kona_usb2_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct bcm_kona_usb *phy; - struct resource *res; - struct phy *gphy; - struct phy_provider *phy_provider; - - phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); - if (!phy) - return -ENOMEM; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - phy->regs = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(phy->regs)) - return PTR_ERR(phy->regs); - - platform_set_drvdata(pdev, phy); - - gphy = devm_phy_create(dev, NULL, &ops); - if (IS_ERR(gphy)) - return PTR_ERR(gphy); - - /* The Kona PHY supports an 8-bit wide UTMI interface */ - phy_set_bus_width(gphy, 8); - - phy_set_drvdata(gphy, phy); - - phy_provider = devm_of_phy_provider_register(dev, - of_phy_simple_xlate); - - return PTR_ERR_OR_ZERO(phy_provider); -} - -static const struct of_device_id bcm_kona_usb2_dt_ids[] = { - { .compatible = "brcm,kona-usb2-phy" }, - { /* sentinel */ } -}; - -MODULE_DEVICE_TABLE(of, bcm_kona_usb2_dt_ids); - -static struct platform_driver bcm_kona_usb2_driver = { - .probe = bcm_kona_usb2_probe, - .driver = { - .name = "bcm-kona-usb2", - .of_match_table = bcm_kona_usb2_dt_ids, - }, -}; - -module_platform_driver(bcm_kona_usb2_driver); - -MODULE_ALIAS("platform:bcm-kona-usb2"); -MODULE_AUTHOR("Matt Porter "); -MODULE_DESCRIPTION("BCM Kona USB 2.0 PHY driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-bcm-ns-usb2.c b/drivers/phy/phy-bcm-ns-usb2.c deleted file mode 100644 index 58dff80e9386..000000000000 --- a/drivers/phy/phy-bcm-ns-usb2.c +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Broadcom Northstar USB 2.0 PHY Driver - * - * Copyright (C) 2016 Rafał Miłecki - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct bcm_ns_usb2 { - struct device *dev; - struct clk *ref_clk; - struct phy *phy; - void __iomem *dmu; -}; - -static int bcm_ns_usb2_phy_init(struct phy *phy) -{ - struct bcm_ns_usb2 *usb2 = phy_get_drvdata(phy); - struct device *dev = usb2->dev; - void __iomem *dmu = usb2->dmu; - u32 ref_clk_rate, usb2ctl, usb_pll_ndiv, usb_pll_pdiv; - int err = 0; - - err = clk_prepare_enable(usb2->ref_clk); - if (err < 0) { - dev_err(dev, "Failed to prepare ref clock: %d\n", err); - goto err_out; - } - - ref_clk_rate = clk_get_rate(usb2->ref_clk); - if (!ref_clk_rate) { - dev_err(dev, "Failed to get ref clock rate\n"); - err = -EINVAL; - goto err_clk_off; - } - - usb2ctl = readl(dmu + BCMA_DMU_CRU_USB2_CONTROL); - - if (usb2ctl & BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_PDIV_MASK) { - usb_pll_pdiv = usb2ctl; - usb_pll_pdiv &= BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_PDIV_MASK; - usb_pll_pdiv >>= BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_PDIV_SHIFT; - } else { - usb_pll_pdiv = 1 << 3; - } - - /* Calculate ndiv based on a solid 1920 MHz that is for USB2 PHY */ - usb_pll_ndiv = (1920000000 * usb_pll_pdiv) / ref_clk_rate; - - /* Unlock DMU PLL settings with some magic value */ - writel(0x0000ea68, dmu + BCMA_DMU_CRU_CLKSET_KEY); - - /* Write USB 2.0 PLL control setting */ - usb2ctl &= ~BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_NDIV_MASK; - usb2ctl |= usb_pll_ndiv << BCMA_DMU_CRU_USB2_CONTROL_USB_PLL_NDIV_SHIFT; - writel(usb2ctl, dmu + BCMA_DMU_CRU_USB2_CONTROL); - - /* Lock DMU PLL settings */ - writel(0x00000000, dmu + BCMA_DMU_CRU_CLKSET_KEY); - -err_clk_off: - clk_disable_unprepare(usb2->ref_clk); -err_out: - return err; -} - -static const struct phy_ops ops = { - .init = bcm_ns_usb2_phy_init, - .owner = THIS_MODULE, -}; - -static int bcm_ns_usb2_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct bcm_ns_usb2 *usb2; - struct resource *res; - struct phy_provider *phy_provider; - - usb2 = devm_kzalloc(&pdev->dev, sizeof(*usb2), GFP_KERNEL); - if (!usb2) - return -ENOMEM; - usb2->dev = dev; - - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dmu"); - usb2->dmu = devm_ioremap_resource(dev, res); - if (IS_ERR(usb2->dmu)) { - dev_err(dev, "Failed to map DMU regs\n"); - return PTR_ERR(usb2->dmu); - } - - usb2->ref_clk = devm_clk_get(dev, "phy-ref-clk"); - if (IS_ERR(usb2->ref_clk)) { - dev_err(dev, "Clock not defined\n"); - return PTR_ERR(usb2->ref_clk); - } - - usb2->phy = devm_phy_create(dev, NULL, &ops); - if (IS_ERR(usb2->phy)) - return PTR_ERR(usb2->phy); - - phy_set_drvdata(usb2->phy, usb2); - platform_set_drvdata(pdev, usb2); - - phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); - return PTR_ERR_OR_ZERO(phy_provider); -} - -static const struct of_device_id bcm_ns_usb2_id_table[] = { - { .compatible = "brcm,ns-usb2-phy", }, - {}, -}; -MODULE_DEVICE_TABLE(of, bcm_ns_usb2_id_table); - -static struct platform_driver bcm_ns_usb2_driver = { - .probe = bcm_ns_usb2_probe, - .driver = { - .name = "bcm_ns_usb2", - .of_match_table = bcm_ns_usb2_id_table, - }, -}; -module_platform_driver(bcm_ns_usb2_driver); - -MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-bcm-ns-usb3.c b/drivers/phy/phy-bcm-ns-usb3.c deleted file mode 100644 index 22b5e7047fa6..000000000000 --- a/drivers/phy/phy-bcm-ns-usb3.c +++ /dev/null @@ -1,303 +0,0 @@ -/* - * Broadcom Northstar USB 3.0 PHY Driver - * - * Copyright (C) 2016 Rafał Miłecki - * Copyright (C) 2016 Broadcom - * - * All magic values used for initialization (and related comments) were obtained - * from Broadcom's SDK: - * Copyright (c) Broadcom Corp, 2012 - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#define BCM_NS_USB3_MII_MNG_TIMEOUT_US 1000 /* usecs */ - -#define BCM_NS_USB3_PHY_BASE_ADDR_REG 0x1f -#define BCM_NS_USB3_PHY_PLL30_BLOCK 0x8000 -#define BCM_NS_USB3_PHY_TX_PMD_BLOCK 0x8040 -#define BCM_NS_USB3_PHY_PIPE_BLOCK 0x8060 - -/* Registers of PLL30 block */ -#define BCM_NS_USB3_PLL_CONTROL 0x01 -#define BCM_NS_USB3_PLLA_CONTROL0 0x0a -#define BCM_NS_USB3_PLLA_CONTROL1 0x0b - -/* Registers of TX PMD block */ -#define BCM_NS_USB3_TX_PMD_CONTROL1 0x01 - -/* Registers of PIPE block */ -#define BCM_NS_USB3_LFPS_CMP 0x02 -#define BCM_NS_USB3_LFPS_DEGLITCH 0x03 - -enum bcm_ns_family { - BCM_NS_UNKNOWN, - BCM_NS_AX, - BCM_NS_BX, -}; - -struct bcm_ns_usb3 { - struct device *dev; - enum bcm_ns_family family; - void __iomem *dmp; - void __iomem *ccb_mii; - struct phy *phy; -}; - -static const struct of_device_id bcm_ns_usb3_id_table[] = { - { - .compatible = "brcm,ns-ax-usb3-phy", - .data = (int *)BCM_NS_AX, - }, - { - .compatible = "brcm,ns-bx-usb3-phy", - .data = (int *)BCM_NS_BX, - }, - {}, -}; -MODULE_DEVICE_TABLE(of, bcm_ns_usb3_id_table); - -static int bcm_ns_usb3_wait_reg(struct bcm_ns_usb3 *usb3, void __iomem *addr, - u32 mask, u32 value, unsigned long timeout) -{ - unsigned long deadline = jiffies + timeout; - u32 val; - - do { - val = readl(addr); - if ((val & mask) == value) - return 0; - cpu_relax(); - udelay(10); - } while (!time_after_eq(jiffies, deadline)); - - dev_err(usb3->dev, "Timeout waiting for register %p\n", addr); - - return -EBUSY; -} - -static inline int bcm_ns_usb3_mii_mng_wait_idle(struct bcm_ns_usb3 *usb3) -{ - return bcm_ns_usb3_wait_reg(usb3, usb3->ccb_mii + BCMA_CCB_MII_MNG_CTL, - 0x0100, 0x0000, - usecs_to_jiffies(BCM_NS_USB3_MII_MNG_TIMEOUT_US)); -} - -static int bcm_ns_usb3_mdio_phy_write(struct bcm_ns_usb3 *usb3, u16 reg, - u16 value) -{ - u32 tmp = 0; - int err; - - err = bcm_ns_usb3_mii_mng_wait_idle(usb3); - if (err < 0) { - dev_err(usb3->dev, "Couldn't write 0x%08x value\n", value); - return err; - } - - /* TODO: Use a proper MDIO bus layer */ - tmp |= 0x58020000; /* Magic value for MDIO PHY write */ - tmp |= reg << 18; - tmp |= value; - writel(tmp, usb3->ccb_mii + BCMA_CCB_MII_MNG_CMD_DATA); - - return 0; -} - -static int bcm_ns_usb3_phy_init_ns_bx(struct bcm_ns_usb3 *usb3) -{ - int err; - - /* Enable MDIO. Setting MDCDIV as 26 */ - writel(0x0000009a, usb3->ccb_mii + BCMA_CCB_MII_MNG_CTL); - - /* Wait for MDIO? */ - udelay(2); - - /* USB3 PLL Block */ - err = bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, - BCM_NS_USB3_PHY_PLL30_BLOCK); - if (err < 0) - return err; - - /* Assert Ana_Pllseq start */ - bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLL_CONTROL, 0x1000); - - /* Assert CML Divider ratio to 26 */ - bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLLA_CONTROL0, 0x6400); - - /* Asserting PLL Reset */ - bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLLA_CONTROL1, 0xc000); - - /* Deaaserting PLL Reset */ - bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLLA_CONTROL1, 0x8000); - - /* Waiting MII Mgt interface idle */ - bcm_ns_usb3_mii_mng_wait_idle(usb3); - - /* Deasserting USB3 system reset */ - writel(0, usb3->dmp + BCMA_RESET_CTL); - - /* PLL frequency monitor enable */ - bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLL_CONTROL, 0x9000); - - /* PIPE Block */ - bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, - BCM_NS_USB3_PHY_PIPE_BLOCK); - - /* CMPMAX & CMPMINTH setting */ - bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_LFPS_CMP, 0xf30d); - - /* DEGLITCH MIN & MAX setting */ - bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_LFPS_DEGLITCH, 0x6302); - - /* TXPMD block */ - bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, - BCM_NS_USB3_PHY_TX_PMD_BLOCK); - - /* Enabling SSC */ - bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_TX_PMD_CONTROL1, 0x1003); - - /* Waiting MII Mgt interface idle */ - bcm_ns_usb3_mii_mng_wait_idle(usb3); - - return 0; -} - -static int bcm_ns_usb3_phy_init_ns_ax(struct bcm_ns_usb3 *usb3) -{ - int err; - - /* Enable MDIO. Setting MDCDIV as 26 */ - writel(0x0000009a, usb3->ccb_mii + BCMA_CCB_MII_MNG_CTL); - - /* Wait for MDIO? */ - udelay(2); - - /* PLL30 block */ - err = bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, - BCM_NS_USB3_PHY_PLL30_BLOCK); - if (err < 0) - return err; - - bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLLA_CONTROL0, 0x6400); - - bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, 0x80e0); - - bcm_ns_usb3_mdio_phy_write(usb3, 0x02, 0x009c); - - /* Enable SSC */ - bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, - BCM_NS_USB3_PHY_TX_PMD_BLOCK); - - bcm_ns_usb3_mdio_phy_write(usb3, 0x02, 0x21d3); - - bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_TX_PMD_CONTROL1, 0x1003); - - /* Waiting MII Mgt interface idle */ - bcm_ns_usb3_mii_mng_wait_idle(usb3); - - /* Deasserting USB3 system reset */ - writel(0, usb3->dmp + BCMA_RESET_CTL); - - return 0; -} - -static int bcm_ns_usb3_phy_init(struct phy *phy) -{ - struct bcm_ns_usb3 *usb3 = phy_get_drvdata(phy); - int err; - - /* Perform USB3 system soft reset */ - writel(BCMA_RESET_CTL_RESET, usb3->dmp + BCMA_RESET_CTL); - - switch (usb3->family) { - case BCM_NS_AX: - err = bcm_ns_usb3_phy_init_ns_ax(usb3); - break; - case BCM_NS_BX: - err = bcm_ns_usb3_phy_init_ns_bx(usb3); - break; - default: - WARN_ON(1); - err = -ENOTSUPP; - } - - return err; -} - -static const struct phy_ops ops = { - .init = bcm_ns_usb3_phy_init, - .owner = THIS_MODULE, -}; - -static int bcm_ns_usb3_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - const struct of_device_id *of_id; - struct bcm_ns_usb3 *usb3; - struct resource *res; - struct phy_provider *phy_provider; - - usb3 = devm_kzalloc(dev, sizeof(*usb3), GFP_KERNEL); - if (!usb3) - return -ENOMEM; - - usb3->dev = dev; - - of_id = of_match_device(bcm_ns_usb3_id_table, dev); - if (!of_id) - return -EINVAL; - usb3->family = (enum bcm_ns_family)of_id->data; - - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dmp"); - usb3->dmp = devm_ioremap_resource(dev, res); - if (IS_ERR(usb3->dmp)) { - dev_err(dev, "Failed to map DMP regs\n"); - return PTR_ERR(usb3->dmp); - } - - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ccb-mii"); - usb3->ccb_mii = devm_ioremap_resource(dev, res); - if (IS_ERR(usb3->ccb_mii)) { - dev_err(dev, "Failed to map ChipCommon B MII regs\n"); - return PTR_ERR(usb3->ccb_mii); - } - - usb3->phy = devm_phy_create(dev, NULL, &ops); - if (IS_ERR(usb3->phy)) { - dev_err(dev, "Failed to create PHY\n"); - return PTR_ERR(usb3->phy); - } - - phy_set_drvdata(usb3->phy, usb3); - platform_set_drvdata(pdev, usb3); - - phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); - if (!IS_ERR(phy_provider)) - dev_info(dev, "Registered Broadcom Northstar USB 3.0 PHY driver\n"); - - return PTR_ERR_OR_ZERO(phy_provider); -} - -static struct platform_driver bcm_ns_usb3_driver = { - .probe = bcm_ns_usb3_probe, - .driver = { - .name = "bcm_ns_usb3", - .of_match_table = bcm_ns_usb3_id_table, - }, -}; -module_platform_driver(bcm_ns_usb3_driver); - -MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-bcm-ns2-pcie.c b/drivers/phy/phy-bcm-ns2-pcie.c deleted file mode 100644 index 4c7d11d2b378..000000000000 --- a/drivers/phy/phy-bcm-ns2-pcie.c +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2016 Broadcom - * - * 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. - * - * This program is distributed "as is" WITHOUT ANY WARRANTY of any - * kind, whether express or implied; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include - -#define BLK_ADDR_REG_OFFSET 0x1f -#define PLL_AFE1_100MHZ_BLK 0x2100 -#define PLL_CLK_AMP_OFFSET 0x03 -#define PLL_CLK_AMP_2P05V 0x2b18 - -static int ns2_pci_phy_init(struct phy *p) -{ - struct mdio_device *mdiodev = phy_get_drvdata(p); - int rc; - - /* select the AFE 100MHz block page */ - rc = mdiobus_write(mdiodev->bus, mdiodev->addr, - BLK_ADDR_REG_OFFSET, PLL_AFE1_100MHZ_BLK); - if (rc) - goto err; - - /* set the 100 MHz reference clock amplitude to 2.05 v */ - rc = mdiobus_write(mdiodev->bus, mdiodev->addr, - PLL_CLK_AMP_OFFSET, PLL_CLK_AMP_2P05V); - if (rc) - goto err; - - return 0; - -err: - dev_err(&mdiodev->dev, "Error %d writing to phy\n", rc); - return rc; -} - -static const struct phy_ops ns2_pci_phy_ops = { - .init = ns2_pci_phy_init, - .owner = THIS_MODULE, -}; - -static int ns2_pci_phy_probe(struct mdio_device *mdiodev) -{ - struct device *dev = &mdiodev->dev; - struct phy_provider *provider; - struct phy *phy; - - phy = devm_phy_create(dev, dev->of_node, &ns2_pci_phy_ops); - if (IS_ERR(phy)) { - dev_err(dev, "failed to create Phy\n"); - return PTR_ERR(phy); - } - - phy_set_drvdata(phy, mdiodev); - - provider = devm_of_phy_provider_register(&phy->dev, - of_phy_simple_xlate); - if (IS_ERR(provider)) { - dev_err(dev, "failed to register Phy provider\n"); - return PTR_ERR(provider); - } - - dev_info(dev, "%s PHY registered\n", dev_name(dev)); - - return 0; -} - -static const struct of_device_id ns2_pci_phy_of_match[] = { - { .compatible = "brcm,ns2-pcie-phy", }, - { /* sentinel */ }, -}; -MODULE_DEVICE_TABLE(of, ns2_pci_phy_of_match); - -static struct mdio_driver ns2_pci_phy_driver = { - .mdiodrv = { - .driver = { - .name = "phy-bcm-ns2-pci", - .of_match_table = ns2_pci_phy_of_match, - }, - }, - .probe = ns2_pci_phy_probe, -}; -mdio_module_driver(ns2_pci_phy_driver); - -MODULE_AUTHOR("Broadcom"); -MODULE_DESCRIPTION("Broadcom Northstar2 PCI Phy driver"); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:phy-bcm-ns2-pci"); diff --git a/drivers/phy/phy-berlin-sata.c b/drivers/phy/phy-berlin-sata.c deleted file mode 100644 index 2c7a57f2d595..000000000000 --- a/drivers/phy/phy-berlin-sata.c +++ /dev/null @@ -1,299 +0,0 @@ -/* - * Marvell Berlin SATA PHY driver - * - * Copyright (C) 2014 Marvell Technology Group Ltd. - * - * Antoine Ténart - * - * This file is licensed under the terms of the GNU General Public - * License version 2. This program is licensed "as is" without any - * warranty of any kind, whether express or implied. - */ - -#include -#include -#include -#include -#include - -#define HOST_VSA_ADDR 0x0 -#define HOST_VSA_DATA 0x4 -#define PORT_SCR_CTL 0x2c -#define PORT_VSR_ADDR 0x78 -#define PORT_VSR_DATA 0x7c - -#define CONTROL_REGISTER 0x0 -#define MBUS_SIZE_CONTROL 0x4 - -#define POWER_DOWN_PHY0 BIT(6) -#define POWER_DOWN_PHY1 BIT(14) -#define MBUS_WRITE_REQUEST_SIZE_128 (BIT(2) << 16) -#define MBUS_READ_REQUEST_SIZE_128 (BIT(2) << 19) - -#define BG2_PHY_BASE 0x080 -#define BG2Q_PHY_BASE 0x200 - -/* register 0x01 */ -#define REF_FREF_SEL_25 BIT(0) -#define PHY_MODE_SATA (0x0 << 5) - -/* register 0x02 */ -#define USE_MAX_PLL_RATE BIT(12) - -/* register 0x23 */ -#define DATA_BIT_WIDTH_10 (0x0 << 10) -#define DATA_BIT_WIDTH_20 (0x1 << 10) -#define DATA_BIT_WIDTH_40 (0x2 << 10) - -/* register 0x25 */ -#define PHY_GEN_MAX_1_5 (0x0 << 10) -#define PHY_GEN_MAX_3_0 (0x1 << 10) -#define PHY_GEN_MAX_6_0 (0x2 << 10) - -struct phy_berlin_desc { - struct phy *phy; - u32 power_bit; - unsigned index; -}; - -struct phy_berlin_priv { - void __iomem *base; - spinlock_t lock; - struct clk *clk; - struct phy_berlin_desc **phys; - unsigned nphys; - u32 phy_base; -}; - -static inline void phy_berlin_sata_reg_setbits(void __iomem *ctrl_reg, - u32 phy_base, u32 reg, u32 mask, u32 val) -{ - u32 regval; - - /* select register */ - writel(phy_base + reg, ctrl_reg + PORT_VSR_ADDR); - - /* set bits */ - regval = readl(ctrl_reg + PORT_VSR_DATA); - regval &= ~mask; - regval |= val; - writel(regval, ctrl_reg + PORT_VSR_DATA); -} - -static int phy_berlin_sata_power_on(struct phy *phy) -{ - struct phy_berlin_desc *desc = phy_get_drvdata(phy); - struct phy_berlin_priv *priv = dev_get_drvdata(phy->dev.parent); - void __iomem *ctrl_reg = priv->base + 0x60 + (desc->index * 0x80); - u32 regval; - - clk_prepare_enable(priv->clk); - - spin_lock(&priv->lock); - - /* Power on PHY */ - writel(CONTROL_REGISTER, priv->base + HOST_VSA_ADDR); - regval = readl(priv->base + HOST_VSA_DATA); - regval &= ~desc->power_bit; - writel(regval, priv->base + HOST_VSA_DATA); - - /* Configure MBus */ - writel(MBUS_SIZE_CONTROL, priv->base + HOST_VSA_ADDR); - regval = readl(priv->base + HOST_VSA_DATA); - regval |= MBUS_WRITE_REQUEST_SIZE_128 | MBUS_READ_REQUEST_SIZE_128; - writel(regval, priv->base + HOST_VSA_DATA); - - /* set PHY mode and ref freq to 25 MHz */ - phy_berlin_sata_reg_setbits(ctrl_reg, priv->phy_base, 0x01, - 0x00ff, REF_FREF_SEL_25 | PHY_MODE_SATA); - - /* set PHY up to 6 Gbps */ - phy_berlin_sata_reg_setbits(ctrl_reg, priv->phy_base, 0x25, - 0x0c00, PHY_GEN_MAX_6_0); - - /* set 40 bits width */ - phy_berlin_sata_reg_setbits(ctrl_reg, priv->phy_base, 0x23, - 0x0c00, DATA_BIT_WIDTH_40); - - /* use max pll rate */ - phy_berlin_sata_reg_setbits(ctrl_reg, priv->phy_base, 0x02, - 0x0000, USE_MAX_PLL_RATE); - - /* set Gen3 controller speed */ - regval = readl(ctrl_reg + PORT_SCR_CTL); - regval &= ~GENMASK(7, 4); - regval |= 0x30; - writel(regval, ctrl_reg + PORT_SCR_CTL); - - spin_unlock(&priv->lock); - - clk_disable_unprepare(priv->clk); - - return 0; -} - -static int phy_berlin_sata_power_off(struct phy *phy) -{ - struct phy_berlin_desc *desc = phy_get_drvdata(phy); - struct phy_berlin_priv *priv = dev_get_drvdata(phy->dev.parent); - u32 regval; - - clk_prepare_enable(priv->clk); - - spin_lock(&priv->lock); - - /* Power down PHY */ - writel(CONTROL_REGISTER, priv->base + HOST_VSA_ADDR); - regval = readl(priv->base + HOST_VSA_DATA); - regval |= desc->power_bit; - writel(regval, priv->base + HOST_VSA_DATA); - - spin_unlock(&priv->lock); - - clk_disable_unprepare(priv->clk); - - return 0; -} - -static struct phy *phy_berlin_sata_phy_xlate(struct device *dev, - struct of_phandle_args *args) -{ - struct phy_berlin_priv *priv = dev_get_drvdata(dev); - int i; - - if (WARN_ON(args->args[0] >= priv->nphys)) - return ERR_PTR(-ENODEV); - - for (i = 0; i < priv->nphys; i++) { - if (priv->phys[i]->index == args->args[0]) - break; - } - - if (i == priv->nphys) - return ERR_PTR(-ENODEV); - - return priv->phys[i]->phy; -} - -static const struct phy_ops phy_berlin_sata_ops = { - .power_on = phy_berlin_sata_power_on, - .power_off = phy_berlin_sata_power_off, - .owner = THIS_MODULE, -}; - -static u32 phy_berlin_power_down_bits[] = { - POWER_DOWN_PHY0, - POWER_DOWN_PHY1, -}; - -static int phy_berlin_sata_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct device_node *child; - struct phy *phy; - struct phy_provider *phy_provider; - struct phy_berlin_priv *priv; - struct resource *res; - int ret, i = 0; - u32 phy_id; - - priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) - return -EINVAL; - - priv->base = devm_ioremap(dev, res->start, resource_size(res)); - if (!priv->base) - return -ENOMEM; - - priv->clk = devm_clk_get(dev, NULL); - if (IS_ERR(priv->clk)) - return PTR_ERR(priv->clk); - - priv->nphys = of_get_child_count(dev->of_node); - if (priv->nphys == 0) - return -ENODEV; - - priv->phys = devm_kcalloc(dev, priv->nphys, sizeof(*priv->phys), - GFP_KERNEL); - if (!priv->phys) - return -ENOMEM; - - if (of_device_is_compatible(dev->of_node, "marvell,berlin2-sata-phy")) - priv->phy_base = BG2_PHY_BASE; - else - priv->phy_base = BG2Q_PHY_BASE; - - dev_set_drvdata(dev, priv); - spin_lock_init(&priv->lock); - - for_each_available_child_of_node(dev->of_node, child) { - struct phy_berlin_desc *phy_desc; - - if (of_property_read_u32(child, "reg", &phy_id)) { - dev_err(dev, "missing reg property in node %s\n", - child->name); - ret = -EINVAL; - goto put_child; - } - - if (phy_id >= ARRAY_SIZE(phy_berlin_power_down_bits)) { - dev_err(dev, "invalid reg in node %s\n", child->name); - ret = -EINVAL; - goto put_child; - } - - phy_desc = devm_kzalloc(dev, sizeof(*phy_desc), GFP_KERNEL); - if (!phy_desc) { - ret = -ENOMEM; - goto put_child; - } - - phy = devm_phy_create(dev, NULL, &phy_berlin_sata_ops); - if (IS_ERR(phy)) { - dev_err(dev, "failed to create PHY %d\n", phy_id); - ret = PTR_ERR(phy); - goto put_child; - } - - phy_desc->phy = phy; - phy_desc->power_bit = phy_berlin_power_down_bits[phy_id]; - phy_desc->index = phy_id; - phy_set_drvdata(phy, phy_desc); - - priv->phys[i++] = phy_desc; - - /* Make sure the PHY is off */ - phy_berlin_sata_power_off(phy); - } - - phy_provider = - devm_of_phy_provider_register(dev, phy_berlin_sata_phy_xlate); - return PTR_ERR_OR_ZERO(phy_provider); -put_child: - of_node_put(child); - return ret; -} - -static const struct of_device_id phy_berlin_sata_of_match[] = { - { .compatible = "marvell,berlin2-sata-phy" }, - { .compatible = "marvell,berlin2q-sata-phy" }, - { }, -}; -MODULE_DEVICE_TABLE(of, phy_berlin_sata_of_match); - -static struct platform_driver phy_berlin_sata_driver = { - .probe = phy_berlin_sata_probe, - .driver = { - .name = "phy-berlin-sata", - .of_match_table = phy_berlin_sata_of_match, - }, -}; -module_platform_driver(phy_berlin_sata_driver); - -MODULE_DESCRIPTION("Marvell Berlin SATA PHY driver"); -MODULE_AUTHOR("Antoine Ténart "); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-berlin-usb.c b/drivers/phy/phy-berlin-usb.c deleted file mode 100644 index 2017751ede26..000000000000 --- a/drivers/phy/phy-berlin-usb.c +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright (C) 2014 Marvell Technology Group Ltd. - * - * Antoine Tenart - * Jisheng Zhang - * - * This file is licensed under the terms of the GNU General Public - * License version 2. This program is licensed "as is" without any - * warranty of any kind, whether express or implied. - */ - -#include -#include -#include -#include -#include -#include - -#define USB_PHY_PLL 0x04 -#define USB_PHY_PLL_CONTROL 0x08 -#define USB_PHY_TX_CTRL0 0x10 -#define USB_PHY_TX_CTRL1 0x14 -#define USB_PHY_TX_CTRL2 0x18 -#define USB_PHY_RX_CTRL 0x20 -#define USB_PHY_ANALOG 0x34 - -/* USB_PHY_PLL */ -#define CLK_REF_DIV(x) ((x) << 4) -#define FEEDBACK_CLK_DIV(x) ((x) << 8) - -/* USB_PHY_PLL_CONTROL */ -#define CLK_STABLE BIT(0) -#define PLL_CTRL_PIN BIT(1) -#define PLL_CTRL_REG BIT(2) -#define PLL_ON BIT(3) -#define PHASE_OFF_TOL_125 (0x0 << 5) -#define PHASE_OFF_TOL_250 BIT(5) -#define KVC0_CALIB (0x0 << 9) -#define KVC0_REG_CTRL BIT(9) -#define KVC0_HIGH (0x0 << 10) -#define KVC0_LOW (0x3 << 10) -#define CLK_BLK_EN BIT(13) - -/* USB_PHY_TX_CTRL0 */ -#define EXT_HS_RCAL_EN BIT(3) -#define EXT_FS_RCAL_EN BIT(4) -#define IMPCAL_VTH_DIV(x) ((x) << 5) -#define EXT_RS_RCAL_DIV(x) ((x) << 8) -#define EXT_FS_RCAL_DIV(x) ((x) << 12) - -/* USB_PHY_TX_CTRL1 */ -#define TX_VDD15_14 (0x0 << 4) -#define TX_VDD15_15 BIT(4) -#define TX_VDD15_16 (0x2 << 4) -#define TX_VDD15_17 (0x3 << 4) -#define TX_VDD12_VDD (0x0 << 6) -#define TX_VDD12_11 BIT(6) -#define TX_VDD12_12 (0x2 << 6) -#define TX_VDD12_13 (0x3 << 6) -#define LOW_VDD_EN BIT(8) -#define TX_OUT_AMP(x) ((x) << 9) - -/* USB_PHY_TX_CTRL2 */ -#define TX_CHAN_CTRL_REG(x) ((x) << 0) -#define DRV_SLEWRATE(x) ((x) << 4) -#define IMP_CAL_FS_HS_DLY_0 (0x0 << 6) -#define IMP_CAL_FS_HS_DLY_1 BIT(6) -#define IMP_CAL_FS_HS_DLY_2 (0x2 << 6) -#define IMP_CAL_FS_HS_DLY_3 (0x3 << 6) -#define FS_DRV_EN_MASK(x) ((x) << 8) -#define HS_DRV_EN_MASK(x) ((x) << 12) - -/* USB_PHY_RX_CTRL */ -#define PHASE_FREEZE_DLY_2_CL (0x0 << 0) -#define PHASE_FREEZE_DLY_4_CL BIT(0) -#define ACK_LENGTH_8_CL (0x0 << 2) -#define ACK_LENGTH_12_CL BIT(2) -#define ACK_LENGTH_16_CL (0x2 << 2) -#define ACK_LENGTH_20_CL (0x3 << 2) -#define SQ_LENGTH_3 (0x0 << 4) -#define SQ_LENGTH_6 BIT(4) -#define SQ_LENGTH_9 (0x2 << 4) -#define SQ_LENGTH_12 (0x3 << 4) -#define DISCON_THRESHOLD_260 (0x0 << 6) -#define DISCON_THRESHOLD_270 BIT(6) -#define DISCON_THRESHOLD_280 (0x2 << 6) -#define DISCON_THRESHOLD_290 (0x3 << 6) -#define SQ_THRESHOLD(x) ((x) << 8) -#define LPF_COEF(x) ((x) << 12) -#define INTPL_CUR_10 (0x0 << 14) -#define INTPL_CUR_20 BIT(14) -#define INTPL_CUR_30 (0x2 << 14) -#define INTPL_CUR_40 (0x3 << 14) - -/* USB_PHY_ANALOG */ -#define ANA_PWR_UP BIT(1) -#define ANA_PWR_DOWN BIT(2) -#define V2I_VCO_RATIO(x) ((x) << 7) -#define R_ROTATE_90 (0x0 << 10) -#define R_ROTATE_0 BIT(10) -#define MODE_TEST_EN BIT(11) -#define ANA_TEST_DC_CTRL(x) ((x) << 12) - -static const u32 phy_berlin_pll_dividers[] = { - /* Berlin 2 */ - CLK_REF_DIV(0x6) | FEEDBACK_CLK_DIV(0x55), - /* Berlin 2CD/Q */ - CLK_REF_DIV(0xc) | FEEDBACK_CLK_DIV(0x54), -}; - -struct phy_berlin_usb_priv { - void __iomem *base; - struct reset_control *rst_ctrl; - u32 pll_divider; -}; - -static int phy_berlin_usb_power_on(struct phy *phy) -{ - struct phy_berlin_usb_priv *priv = phy_get_drvdata(phy); - - reset_control_reset(priv->rst_ctrl); - - writel(priv->pll_divider, - priv->base + USB_PHY_PLL); - writel(CLK_STABLE | PLL_CTRL_REG | PHASE_OFF_TOL_250 | KVC0_REG_CTRL | - CLK_BLK_EN, priv->base + USB_PHY_PLL_CONTROL); - writel(V2I_VCO_RATIO(0x5) | R_ROTATE_0 | ANA_TEST_DC_CTRL(0x5), - priv->base + USB_PHY_ANALOG); - writel(PHASE_FREEZE_DLY_4_CL | ACK_LENGTH_16_CL | SQ_LENGTH_12 | - DISCON_THRESHOLD_260 | SQ_THRESHOLD(0xa) | LPF_COEF(0x2) | - INTPL_CUR_30, priv->base + USB_PHY_RX_CTRL); - - writel(TX_VDD12_13 | TX_OUT_AMP(0x3), priv->base + USB_PHY_TX_CTRL1); - writel(EXT_HS_RCAL_EN | IMPCAL_VTH_DIV(0x3) | EXT_RS_RCAL_DIV(0x4), - priv->base + USB_PHY_TX_CTRL0); - - writel(EXT_HS_RCAL_EN | IMPCAL_VTH_DIV(0x3) | EXT_RS_RCAL_DIV(0x4) | - EXT_FS_RCAL_DIV(0x2), priv->base + USB_PHY_TX_CTRL0); - - writel(EXT_HS_RCAL_EN | IMPCAL_VTH_DIV(0x3) | EXT_RS_RCAL_DIV(0x4), - priv->base + USB_PHY_TX_CTRL0); - writel(TX_CHAN_CTRL_REG(0xf) | DRV_SLEWRATE(0x3) | IMP_CAL_FS_HS_DLY_3 | - FS_DRV_EN_MASK(0xd), priv->base + USB_PHY_TX_CTRL2); - - return 0; -} - -static const struct phy_ops phy_berlin_usb_ops = { - .power_on = phy_berlin_usb_power_on, - .owner = THIS_MODULE, -}; - -static const struct of_device_id phy_berlin_usb_of_match[] = { - { - .compatible = "marvell,berlin2-usb-phy", - .data = &phy_berlin_pll_dividers[0], - }, - { - .compatible = "marvell,berlin2cd-usb-phy", - .data = &phy_berlin_pll_dividers[1], - }, - { }, -}; -MODULE_DEVICE_TABLE(of, phy_berlin_usb_of_match); - -static int phy_berlin_usb_probe(struct platform_device *pdev) -{ - const struct of_device_id *match = - of_match_device(phy_berlin_usb_of_match, &pdev->dev); - struct phy_berlin_usb_priv *priv; - struct resource *res; - struct phy *phy; - struct phy_provider *phy_provider; - - priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - priv->base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(priv->base)) - return PTR_ERR(priv->base); - - priv->rst_ctrl = devm_reset_control_get(&pdev->dev, NULL); - if (IS_ERR(priv->rst_ctrl)) - return PTR_ERR(priv->rst_ctrl); - - priv->pll_divider = *((u32 *)match->data); - - phy = devm_phy_create(&pdev->dev, NULL, &phy_berlin_usb_ops); - if (IS_ERR(phy)) { - dev_err(&pdev->dev, "failed to create PHY\n"); - return PTR_ERR(phy); - } - - phy_set_drvdata(phy, priv); - - phy_provider = - devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate); - return PTR_ERR_OR_ZERO(phy_provider); -} - -static struct platform_driver phy_berlin_usb_driver = { - .probe = phy_berlin_usb_probe, - .driver = { - .name = "phy-berlin-usb", - .of_match_table = phy_berlin_usb_of_match, - }, -}; -module_platform_driver(phy_berlin_usb_driver); - -MODULE_AUTHOR("Antoine Tenart "); -MODULE_DESCRIPTION("Marvell Berlin PHY driver for USB"); -MODULE_LICENSE("GPL"); diff --git a/drivers/phy/phy-brcm-sata.c b/drivers/phy/phy-brcm-sata.c deleted file mode 100644 index ccbc3d994998..000000000000 --- a/drivers/phy/phy-brcm-sata.c +++ /dev/null @@ -1,493 +0,0 @@ -/* - * Broadcom SATA3 AHCI Controller PHY Driver - * - * Copyright (C) 2016 Broadcom - * - * 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; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define SATA_PCB_BANK_OFFSET 0x23c -#define SATA_PCB_REG_OFFSET(ofs) ((ofs) * 4) - -#define MAX_PORTS 2 - -/* Register offset between PHYs in PCB space */ -#define SATA_PCB_REG_28NM_SPACE_SIZE 0x1000 - -/* The older SATA PHY registers duplicated per port registers within the map, - * rather than having a separate map per port. - */ -#define SATA_PCB_REG_40NM_SPACE_SIZE 0x10 - -/* Register offset between PHYs in PHY control space */ -#define SATA_PHY_CTRL_REG_28NM_SPACE_SIZE 0x8 - -enum brcm_sata_phy_version { - BRCM_SATA_PHY_STB_28NM, - BRCM_SATA_PHY_STB_40NM, - BRCM_SATA_PHY_IPROC_NS2, - BRCM_SATA_PHY_IPROC_NSP, -}; - -struct brcm_sata_port { - int portnum; - struct phy *phy; - struct brcm_sata_phy *phy_priv; - bool ssc_en; -}; - -struct brcm_sata_phy { - struct device *dev; - void __iomem *phy_base; - void __iomem *ctrl_base; - enum brcm_sata_phy_version version; - - struct brcm_sata_port phys[MAX_PORTS]; -}; - -enum sata_phy_regs { - BLOCK0_REG_BANK = 0x000, - BLOCK0_XGXSSTATUS = 0x81, - BLOCK0_XGXSSTATUS_PLL_LOCK = BIT(12), - BLOCK0_SPARE = 0x8d, - BLOCK0_SPARE_OOB_CLK_SEL_MASK = 0x3, - BLOCK0_SPARE_OOB_CLK_SEL_REFBY2 = 0x1, - - PLL_REG_BANK_0 = 0x050, - PLL_REG_BANK_0_PLLCONTROL_0 = 0x81, - PLLCONTROL_0_FREQ_DET_RESTART = BIT(13), - PLLCONTROL_0_FREQ_MONITOR = BIT(12), - PLLCONTROL_0_SEQ_START = BIT(15), - PLL_CAP_CONTROL = 0x85, - PLL_ACTRL2 = 0x8b, - PLL_ACTRL2_SELDIV_MASK = 0x1f, - PLL_ACTRL2_SELDIV_SHIFT = 9, - - PLL1_REG_BANK = 0x060, - PLL1_ACTRL2 = 0x82, - PLL1_ACTRL3 = 0x83, - PLL1_ACTRL4 = 0x84, - - OOB_REG_BANK = 0x150, - OOB1_REG_BANK = 0x160, - OOB_CTRL1 = 0x80, - OOB_CTRL1_BURST_MAX_MASK = 0xf, - OOB_CTRL1_BURST_MAX_SHIFT = 12, - OOB_CTRL1_BURST_MIN_MASK = 0xf, - OOB_CTRL1_BURST_MIN_SHIFT = 8, - OOB_CTRL1_WAKE_IDLE_MAX_MASK = 0xf, - OOB_CTRL1_WAKE_IDLE_MAX_SHIFT = 4, - OOB_CTRL1_WAKE_IDLE_MIN_MASK = 0xf, - OOB_CTRL1_WAKE_IDLE_MIN_SHIFT = 0, - OOB_CTRL2 = 0x81, - OOB_CTRL2_SEL_ENA_SHIFT = 15, - OOB_CTRL2_SEL_ENA_RC_SHIFT = 14, - OOB_CTRL2_RESET_IDLE_MAX_MASK = 0x3f, - OOB_CTRL2_RESET_IDLE_MAX_SHIFT = 8, - OOB_CTRL2_BURST_CNT_MASK = 0x3, - OOB_CTRL2_BURST_CNT_SHIFT = 6, - OOB_CTRL2_RESET_IDLE_MIN_MASK = 0x3f, - OOB_CTRL2_RESET_IDLE_MIN_SHIFT = 0, - - TXPMD_REG_BANK = 0x1a0, - TXPMD_CONTROL1 = 0x81, - TXPMD_CONTROL1_TX_SSC_EN_FRC = BIT(0), - TXPMD_CONTROL1_TX_SSC_EN_FRC_VAL = BIT(1), - TXPMD_TX_FREQ_CTRL_CONTROL1 = 0x82, - TXPMD_TX_FREQ_CTRL_CONTROL2 = 0x83, - TXPMD_TX_FREQ_CTRL_CONTROL2_FMIN_MASK = 0x3ff, - TXPMD_TX_FREQ_CTRL_CONTROL3 = 0x84, - TXPMD_TX_FREQ_CTRL_CONTROL3_FMAX_MASK = 0x3ff, -}; - -enum sata_phy_ctrl_regs { - PHY_CTRL_1 = 0x0, - PHY_CTRL_1_RESET = BIT(0), -}; - -static inline void __iomem *brcm_sata_pcb_base(struct brcm_sata_port *port) -{ - struct brcm_sata_phy *priv = port->phy_priv; - u32 size = 0; - - switch (priv->version) { - case BRCM_SATA_PHY_STB_28NM: - case BRCM_SATA_PHY_IPROC_NS2: - size = SATA_PCB_REG_28NM_SPACE_SIZE; - break; - case BRCM_SATA_PHY_STB_40NM: - size = SATA_PCB_REG_40NM_SPACE_SIZE; - break; - default: - dev_err(priv->dev, "invalid phy version\n"); - break; - } - - return priv->phy_base + (port->portnum * size); -} - -static inline void __iomem *brcm_sata_ctrl_base(struct brcm_sata_port *port) -{ - struct brcm_sata_phy *priv = port->phy_priv; - u32 size = 0; - - switch (priv->version) { - case BRCM_SATA_PHY_IPROC_NS2: - size = SATA_PHY_CTRL_REG_28NM_SPACE_SIZE; - break; - default: - dev_err(priv->dev, "invalid phy version\n"); - break; - } - - return priv->ctrl_base + (port->portnum * size); -} - -static void brcm_sata_phy_wr(void __iomem *pcb_base, u32 bank, - u32 ofs, u32 msk, u32 value) -{ - u32 tmp; - - writel(bank, pcb_base + SATA_PCB_BANK_OFFSET); - tmp = readl(pcb_base + SATA_PCB_REG_OFFSET(ofs)); - tmp = (tmp & msk) | value; - writel(tmp, pcb_base + SATA_PCB_REG_OFFSET(ofs)); -} - -static u32 brcm_sata_phy_rd(void __iomem *pcb_base, u32 bank, u32 ofs) -{ - writel(bank, pcb_base + SATA_PCB_BANK_OFFSET); - return readl(pcb_base + SATA_PCB_REG_OFFSET(ofs)); -} - -/* These defaults were characterized by H/W group */ -#define STB_FMIN_VAL_DEFAULT 0x3df -#define STB_FMAX_VAL_DEFAULT 0x3df -#define STB_FMAX_VAL_SSC 0x83 - -static int brcm_stb_sata_init(struct brcm_sata_port *port) -{ - void __iomem *base = brcm_sata_pcb_base(port); - struct brcm_sata_phy *priv = port->phy_priv; - u32 tmp; - - /* override the TX spread spectrum setting */ - tmp = TXPMD_CONTROL1_TX_SSC_EN_FRC_VAL | TXPMD_CONTROL1_TX_SSC_EN_FRC; - brcm_sata_phy_wr(base, TXPMD_REG_BANK, TXPMD_CONTROL1, ~tmp, tmp); - - /* set fixed min freq */ - brcm_sata_phy_wr(base, TXPMD_REG_BANK, TXPMD_TX_FREQ_CTRL_CONTROL2, - ~TXPMD_TX_FREQ_CTRL_CONTROL2_FMIN_MASK, - STB_FMIN_VAL_DEFAULT); - - /* set fixed max freq depending on SSC config */ - if (port->ssc_en) { - dev_info(priv->dev, "enabling SSC on port%d\n", port->portnum); - tmp = STB_FMAX_VAL_SSC; - } else { - tmp = STB_FMAX_VAL_DEFAULT; - } - - brcm_sata_phy_wr(base, TXPMD_REG_BANK, TXPMD_TX_FREQ_CTRL_CONTROL3, - ~TXPMD_TX_FREQ_CTRL_CONTROL3_FMAX_MASK, tmp); - - return 0; -} - -/* NS2 SATA PLL1 defaults were characterized by H/W group */ -#define NS2_PLL1_ACTRL2_MAGIC 0x1df8 -#define NS2_PLL1_ACTRL3_MAGIC 0x2b00 -#define NS2_PLL1_ACTRL4_MAGIC 0x8824 - -static int brcm_ns2_sata_init(struct brcm_sata_port *port) -{ - int try; - unsigned int val; - void __iomem *base = brcm_sata_pcb_base(port); - void __iomem *ctrl_base = brcm_sata_ctrl_base(port); - struct device *dev = port->phy_priv->dev; - - /* Configure OOB control */ - val = 0x0; - val |= (0xc << OOB_CTRL1_BURST_MAX_SHIFT); - val |= (0x4 << OOB_CTRL1_BURST_MIN_SHIFT); - val |= (0x9 << OOB_CTRL1_WAKE_IDLE_MAX_SHIFT); - val |= (0x3 << OOB_CTRL1_WAKE_IDLE_MIN_SHIFT); - brcm_sata_phy_wr(base, OOB_REG_BANK, OOB_CTRL1, 0x0, val); - val = 0x0; - val |= (0x1b << OOB_CTRL2_RESET_IDLE_MAX_SHIFT); - val |= (0x2 << OOB_CTRL2_BURST_CNT_SHIFT); - val |= (0x9 << OOB_CTRL2_RESET_IDLE_MIN_SHIFT); - brcm_sata_phy_wr(base, OOB_REG_BANK, OOB_CTRL2, 0x0, val); - - /* Configure PHY PLL register bank 1 */ - val = NS2_PLL1_ACTRL2_MAGIC; - brcm_sata_phy_wr(base, PLL1_REG_BANK, PLL1_ACTRL2, 0x0, val); - val = NS2_PLL1_ACTRL3_MAGIC; - brcm_sata_phy_wr(base, PLL1_REG_BANK, PLL1_ACTRL3, 0x0, val); - val = NS2_PLL1_ACTRL4_MAGIC; - brcm_sata_phy_wr(base, PLL1_REG_BANK, PLL1_ACTRL4, 0x0, val); - - /* Configure PHY BLOCK0 register bank */ - /* Set oob_clk_sel to refclk/2 */ - brcm_sata_phy_wr(base, BLOCK0_REG_BANK, BLOCK0_SPARE, - ~BLOCK0_SPARE_OOB_CLK_SEL_MASK, - BLOCK0_SPARE_OOB_CLK_SEL_REFBY2); - - /* Strobe PHY reset using PHY control register */ - writel(PHY_CTRL_1_RESET, ctrl_base + PHY_CTRL_1); - mdelay(1); - writel(0x0, ctrl_base + PHY_CTRL_1); - mdelay(1); - - /* Wait for PHY PLL lock by polling pll_lock bit */ - try = 50; - while (try) { - val = brcm_sata_phy_rd(base, BLOCK0_REG_BANK, - BLOCK0_XGXSSTATUS); - if (val & BLOCK0_XGXSSTATUS_PLL_LOCK) - break; - msleep(20); - try--; - } - if (!try) { - /* PLL did not lock; give up */ - dev_err(dev, "port%d PLL did not lock\n", port->portnum); - return -ETIMEDOUT; - } - - dev_dbg(dev, "port%d initialized\n", port->portnum); - - return 0; -} - -static int brcm_nsp_sata_init(struct brcm_sata_port *port) -{ - struct brcm_sata_phy *priv = port->phy_priv; - struct device *dev = port->phy_priv->dev; - void __iomem *base = priv->phy_base; - unsigned int oob_bank; - unsigned int val, try; - - /* Configure OOB control */ - if (port->portnum == 0) - oob_bank = OOB_REG_BANK; - else if (port->portnum == 1) - oob_bank = OOB1_REG_BANK; - else - return -EINVAL; - - val = 0x0; - val |= (0x0f << OOB_CTRL1_BURST_MAX_SHIFT); - val |= (0x06 << OOB_CTRL1_BURST_MIN_SHIFT); - val |= (0x0f << OOB_CTRL1_WAKE_IDLE_MAX_SHIFT); - val |= (0x06 << OOB_CTRL1_WAKE_IDLE_MIN_SHIFT); - brcm_sata_phy_wr(base, oob_bank, OOB_CTRL1, 0x0, val); - - val = 0x0; - val |= (0x2e << OOB_CTRL2_RESET_IDLE_MAX_SHIFT); - val |= (0x02 << OOB_CTRL2_BURST_CNT_SHIFT); - val |= (0x16 << OOB_CTRL2_RESET_IDLE_MIN_SHIFT); - brcm_sata_phy_wr(base, oob_bank, OOB_CTRL2, 0x0, val); - - - brcm_sata_phy_wr(base, PLL_REG_BANK_0, PLL_ACTRL2, - ~(PLL_ACTRL2_SELDIV_MASK << PLL_ACTRL2_SELDIV_SHIFT), - 0x0c << PLL_ACTRL2_SELDIV_SHIFT); - - brcm_sata_phy_wr(base, PLL_REG_BANK_0, PLL_CAP_CONTROL, - 0xff0, 0x4f0); - - val = PLLCONTROL_0_FREQ_DET_RESTART | PLLCONTROL_0_FREQ_MONITOR; - brcm_sata_phy_wr(base, PLL_REG_BANK_0, PLL_REG_BANK_0_PLLCONTROL_0, - ~val, val); - val = PLLCONTROL_0_SEQ_START; - brcm_sata_phy_wr(base, PLL_REG_BANK_0, PLL_REG_BANK_0_PLLCONTROL_0, - ~val, 0); - mdelay(10); - brcm_sata_phy_wr(base, PLL_REG_BANK_0, PLL_REG_BANK_0_PLLCONTROL_0, - ~val, val); - - /* Wait for pll_seq_done bit */ - try = 50; - while (try--) { - val = brcm_sata_phy_rd(base, BLOCK0_REG_BANK, - BLOCK0_XGXSSTATUS); - if (val & BLOCK0_XGXSSTATUS_PLL_LOCK) - break; - msleep(20); - } - if (!try) { - /* PLL did not lock; give up */ - dev_err(dev, "port%d PLL did not lock\n", port->portnum); - return -ETIMEDOUT; - } - - dev_dbg(dev, "port%d initialized\n", port->portnum); - - return 0; -} - -static int brcm_sata_phy_init(struct phy *phy) -{ - int rc; - struct brcm_sata_port *port = phy_get_drvdata(phy); - - switch (port->phy_priv->version) { - case BRCM_SATA_PHY_STB_28NM: - case BRCM_SATA_PHY_STB_40NM: - rc = brcm_stb_sata_init(port); - break; - case BRCM_SATA_PHY_IPROC_NS2: - rc = brcm_ns2_sata_init(port); - break; - case BRCM_SATA_PHY_IPROC_NSP: - rc = brcm_nsp_sata_init(port); - break; - default: - rc = -ENODEV; - } - - return rc; -} - -static const struct phy_ops phy_ops = { - .init = brcm_sata_phy_init, - .owner = THIS_MODULE, -}; - -static const struct of_device_id brcm_sata_phy_of_match[] = { - { .compatible = "brcm,bcm7445-sata-phy", - .data = (void *)BRCM_SATA_PHY_STB_28NM }, - { .compatible = "brcm,bcm7425-sata-phy", - .data = (void *)BRCM_SATA_PHY_STB_40NM }, - { .compatible = "brcm,iproc-ns2-sata-phy", - .data = (void *)BRCM_SATA_PHY_IPROC_NS2 }, - { .compatible = "brcm,iproc-nsp-sata-phy", - .data = (void *)BRCM_SATA_PHY_IPROC_NSP }, - {}, -}; -MODULE_DEVICE_TABLE(of, brcm_sata_phy_of_match); - -static int brcm_sata_phy_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct device_node *dn = dev->of_node, *child; - const struct of_device_id *of_id; - struct brcm_sata_phy *priv; - struct resource *res; - struct phy_provider *provider; - int ret, count = 0; - - if (of_get_child_count(dn) == 0) - return -ENODEV; - - priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - dev_set_drvdata(dev, priv); - priv->dev = dev; - - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy"); - priv->phy_base = devm_ioremap_resource(dev, res); - if (IS_ERR(priv->phy_base)) - return PTR_ERR(priv->phy_base); - - of_id = of_match_node(brcm_sata_phy_of_match, dn); - if (of_id) - priv->version = (enum brcm_sata_phy_version)of_id->data; - else - priv->version = BRCM_SATA_PHY_STB_28NM; - - if (priv->version == BRCM_SATA_PHY_IPROC_NS2) { - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, - "phy-ctrl"); - priv->ctrl_base = devm_ioremap_resource(dev, res); - if (IS_ERR(priv->ctrl_base)) - return PTR_ERR(priv->ctrl_base); - } - - for_each_available_child_of_node(dn, child) { - unsigned int id; - struct brcm_sata_port *port; - - if (of_property_read_u32(child, "reg", &id)) { - dev_err(dev, "missing reg property in node %s\n", - child->name); - ret = -EINVAL; - goto put_child; - } - - if (id >= MAX_PORTS) { - dev_err(dev, "invalid reg: %u\n", id); - ret = -EINVAL; - goto put_child; - } - if (priv->phys[id].phy) { - dev_err(dev, "already registered port %u\n", id); - ret = -EINVAL; - goto put_child; - } - - port = &priv->phys[id]; - port->portnum = id; - port->phy_priv = priv; - port->phy = devm_phy_create(dev, child, &phy_ops); - port->ssc_en = of_property_read_bool(child, "brcm,enable-ssc"); - if (IS_ERR(port->phy)) { - dev_err(dev, "failed to create PHY\n"); - ret = PTR_ERR(port->phy); - goto put_child; - } - - phy_set_drvdata(port->phy, port); - count++; - } - - provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); - if (IS_ERR(provider)) { - dev_err(dev, "could not register PHY provider\n"); - return PTR_ERR(provider); - } - - dev_info(dev, "registered %d port(s)\n", count); - - return 0; -put_child: - of_node_put(child); - return ret; -} - -static struct platform_driver brcm_sata_phy_driver = { - .probe = brcm_sata_phy_probe, - .driver = { - .of_match_table = brcm_sata_phy_of_match, - .name = "brcm-sata-phy", - } -}; -module_platform_driver(brcm_sata_phy_driver); - -MODULE_DESCRIPTION("Broadcom SATA PHY driver"); -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Marc Carino"); -MODULE_AUTHOR("Brian Norris"); -MODULE_ALIAS("platform:phy-brcm-sata"); diff --git a/drivers/phy/phy-da8xx-usb.c b/drivers/phy/phy-da8xx-usb.c deleted file mode 100644 index 1b82bff6330f..000000000000 --- a/drivers/phy/phy-da8xx-usb.c +++ /dev/null @@ -1,251 +0,0 @@ -/* - * phy-da8xx-usb - TI DaVinci DA8xx USB PHY driver - * - * Copyright (C) 2016 David Lechner - * - * 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. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define PHY_INIT_BITS (CFGCHIP2_SESENDEN | CFGCHIP2_VBDTCTEN) - -struct da8xx_usb_phy { - struct phy_provider *phy_provider; - struct phy *usb11_phy; - struct phy *usb20_phy; - struct clk *usb11_clk; - struct clk *usb20_clk; - struct regmap *regmap; -}; - -static int da8xx_usb11_phy_power_on(struct phy *phy) -{ - struct da8xx_usb_phy *d_phy = phy_get_drvdata(phy); - int ret; - - ret = clk_prepare_enable(d_phy->usb11_clk); - if (ret) - return ret; - - regmap_write_bits(d_phy->regmap, CFGCHIP(2), CFGCHIP2_USB1SUSPENDM, - CFGCHIP2_USB1SUSPENDM); - - return 0; -} - -static int da8xx_usb11_phy_power_off(struct phy *phy) -{ - struct da8xx_usb_phy *d_phy = phy_get_drvdata(phy); - - regmap_write_bits(d_phy->regmap, CFGCHIP(2), CFGCHIP2_USB1SUSPENDM, 0); - - clk_disable_unprepare(d_phy->usb11_clk); - - return 0; -} - -static const struct phy_ops da8xx_usb11_phy_ops = { - .power_on = da8xx_usb11_phy_power_on, - .power_off = da8xx_usb11_phy_power_off, - .owner = THIS_MODULE, -}; - -static int da8xx_usb20_phy_power_on(struct phy *phy) -{ - struct da8xx_usb_phy *d_phy = phy_get_drvdata(phy); - int ret; - - ret = clk_prepare_enable(d_phy->usb20_clk); - if (ret) - return ret; - - regmap_write_bits(d_phy->regmap, CFGCHIP(2), CFGCHIP2_OTGPWRDN, 0); - - return 0; -} - -static int da8xx_usb20_phy_power_off(struct phy *phy) -{ - struct da8xx_usb_phy *d_phy = phy_get_drvdata(phy); - - regmap_write_bits(d_phy->regmap, CFGCHIP(2), CFGCHIP2_OTGPWRDN, - CFGCHIP2_OTGPWRDN); - - clk_disable_unprepare(d_phy->usb20_clk); - - return 0; -} - -static int da8xx_usb20_phy_set_mode(struct phy *phy, enum phy_mode mode) -{ - struct da8xx_usb_phy *d_phy = phy_get_drvdata(phy); - u32 val; - - switch (mode) { - case PHY_MODE_USB_HOST: /* Force VBUS valid, ID = 0 */ - val = CFGCHIP2_OTGMODE_FORCE_HOST; - break; - case PHY_MODE_USB_DEVICE: /* Force VBUS valid, ID = 1 */ - val = CFGCHIP2_OTGMODE_FORCE_DEVICE; - break; - case PHY_MODE_USB_OTG: /* Don't override the VBUS/ID comparators */ - val = CFGCHIP2_OTGMODE_NO_OVERRIDE; - break; - default: - return -EINVAL; - } - - regmap_write_bits(d_phy->regmap, CFGCHIP(2), CFGCHIP2_OTGMODE_MASK, - val); - - return 0; -} - -static const struct phy_ops da8xx_usb20_phy_ops = { - .power_on = da8xx_usb20_phy_power_on, - .power_off = da8xx_usb20_phy_power_off, - .set_mode = da8xx_usb20_phy_set_mode, - .owner = THIS_MODULE, -}; - -static struct phy *da8xx_usb_phy_of_xlate(struct device *dev, - struct of_phandle_args *args) -{ - struct da8xx_usb_phy *d_phy = dev_get_drvdata(dev); - - if (!d_phy) - return ERR_PTR(-ENODEV); - - switch (args->args[0]) { - case 0: - return d_phy->usb20_phy; - case 1: - return d_phy->usb11_phy; - default: - return ERR_PTR(-EINVAL); - } -} - -static int da8xx_usb_phy_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct device_node *node = dev->of_node; - struct da8xx_usb_phy *d_phy; - - d_phy = devm_kzalloc(dev, sizeof(*d_phy), GFP_KERNEL); - if (!d_phy) - return -ENOMEM; - - if (node) - d_phy->regmap = syscon_regmap_lookup_by_compatible( - "ti,da830-cfgchip"); - else - d_phy->regmap = syscon_regmap_lookup_by_pdevname("syscon"); - if (IS_ERR(d_phy->regmap)) { - dev_err(dev, "Failed to get syscon\n"); - return PTR_ERR(d_phy->regmap); - } - - d_phy->usb11_clk = devm_clk_get(dev, "usb11_phy"); - if (IS_ERR(d_phy->usb11_clk)) { - dev_err(dev, "Failed to get usb11_phy clock\n"); - return PTR_ERR(d_phy->usb11_clk); - } - - d_phy->usb20_clk = devm_clk_get(dev, "usb20_phy"); - if (IS_ERR(d_phy->usb20_clk)) { - dev_err(dev, "Failed to get usb20_phy clock\n"); - return PTR_ERR(d_phy->usb20_clk); - } - - d_phy->usb11_phy = devm_phy_create(dev, node, &da8xx_usb11_phy_ops); - if (IS_ERR(d_phy->usb11_phy)) { - dev_err(dev, "Failed to create usb11 phy\n"); - return PTR_ERR(d_phy->usb11_phy); - } - - d_phy->usb20_phy = devm_phy_create(dev, node, &da8xx_usb20_phy_ops); - if (IS_ERR(d_phy->usb20_phy)) { - dev_err(dev, "Failed to create usb20 phy\n"); - return PTR_ERR(d_phy->usb20_phy); - } - - platform_set_drvdata(pdev, d_phy); - phy_set_drvdata(d_phy->usb11_phy, d_phy); - phy_set_drvdata(d_phy->usb20_phy, d_phy); - - if (node) { - d_phy->phy_provider = devm_of_phy_provider_register(dev, - da8xx_usb_phy_of_xlate); - if (IS_ERR(d_phy->phy_provider)) { - dev_err(dev, "Failed to create phy provider\n"); - return PTR_ERR(d_phy->phy_provider); - } - } else { - int ret; - - ret = phy_create_lookup(d_phy->usb11_phy, "usb-phy", - "ohci-da8xx"); - if (ret) - dev_warn(dev, "Failed to create usb11 phy lookup\n"); - ret = phy_create_lookup(d_phy->usb20_phy, "usb-phy", - "musb-da8xx"); - if (ret) - dev_warn(dev, "Failed to create usb20 phy lookup\n"); - } - - regmap_write_bits(d_phy->regmap, CFGCHIP(2), - PHY_INIT_BITS, PHY_INIT_BITS); - - return 0; -} - -static int da8xx_usb_phy_remove(struct platform_device *pdev) -{ - struct da8xx_usb_phy *d_phy = platform_get_drvdata(pdev); - - if (!pdev->dev.of_node) { - phy_remove_lookup(d_phy->usb20_phy, "usb-phy", "musb-da8xx"); - phy_remove_lookup(d_phy->usb11_phy, "usb-phy", "ohci-da8xx"); - } - - return 0; -} - -static const struct of_device_id da8xx_usb_phy_ids[] = { - { .compatible = "ti,da830-usb-phy" }, - { } -}; -MODULE_DEVICE_TABLE(of, da8xx_usb_phy_ids); - -static struct platform_driver da8xx_usb_phy_driver = { - .probe = da8xx_usb_phy_probe, - .remove = da8xx_usb_phy_remove, - .driver = { - .name = "da8xx-usb-phy", - .of_match_table = da8xx_usb_phy_ids, - }, -}; - -module_platform_driver(da8xx_usb_phy_driver); - -MODULE_ALIAS("platform:da8xx-usb-phy"); -MODULE_AUTHOR("David Lechner "); -MODULE_DESCRIPTION("TI DA8xx USB PHY driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-dm816x-usb.c b/drivers/phy/phy-dm816x-usb.c deleted file mode 100644 index cbcce7cf0028..000000000000 --- a/drivers/phy/phy-dm816x-usb.c +++ /dev/null @@ -1,290 +0,0 @@ -/* - * 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. - * - * This program is distributed "as is" WITHOUT ANY WARRANTY of any - * kind, whether express or implied; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -/* - * TRM has two sets of USB_CTRL registers.. The correct register bits - * are in TRM section 24.9.8.2 USB_CTRL Register. The TRM documents the - * phy as being SR70LX Synopsys USB 2.0 OTG nanoPHY. It also seems at - * least dm816x rev c ignores writes to USB_CTRL register, but the TI - * kernel is writing to those so it's possible that later revisions - * have worknig USB_CTRL register. - * - * Also note that At least USB_CTRL register seems to be dm816x specific - * according to the TRM. It's possible that USBPHY_CTRL is more generic, - * but that would have to be checked against the SR70LX documentation - * which does not seem to be publicly available. - * - * Finally, the phy on dm814x and am335x is different from dm816x. - */ -#define DM816X_USB_CTRL_PHYCLKSRC BIT(8) /* 1 = PLL ref clock */ -#define DM816X_USB_CTRL_PHYSLEEP1 BIT(1) /* Enable the first phy */ -#define DM816X_USB_CTRL_PHYSLEEP0 BIT(0) /* Enable the second phy */ - -#define DM816X_USBPHY_CTRL_TXRISETUNE 1 -#define DM816X_USBPHY_CTRL_TXVREFTUNE 0xc -#define DM816X_USBPHY_CTRL_TXPREEMTUNE 0x2 - -struct dm816x_usb_phy { - struct regmap *syscon; - struct device *dev; - unsigned int instance; - struct clk *refclk; - struct usb_phy phy; - unsigned int usb_ctrl; /* Shared between phy0 and phy1 */ - unsigned int usbphy_ctrl; -}; - -static int dm816x_usb_phy_set_host(struct usb_otg *otg, struct usb_bus *host) -{ - otg->host = host; - if (!host) - otg->state = OTG_STATE_UNDEFINED; - - return 0; -} - -static int dm816x_usb_phy_set_peripheral(struct usb_otg *otg, - struct usb_gadget *gadget) -{ - otg->gadget = gadget; - if (!gadget) - otg->state = OTG_STATE_UNDEFINED; - - return 0; -} - -static int dm816x_usb_phy_init(struct phy *x) -{ - struct dm816x_usb_phy *phy = phy_get_drvdata(x); - unsigned int val; - int error; - - if (clk_get_rate(phy->refclk) != 24000000) - dev_warn(phy->dev, "nonstandard phy refclk\n"); - - /* Set PLL ref clock and put phys to sleep */ - error = regmap_update_bits(phy->syscon, phy->usb_ctrl, - DM816X_USB_CTRL_PHYCLKSRC | - DM816X_USB_CTRL_PHYSLEEP1 | - DM816X_USB_CTRL_PHYSLEEP0, - 0); - regmap_read(phy->syscon, phy->usb_ctrl, &val); - if ((val & 3) != 0) - dev_info(phy->dev, - "Working dm816x USB_CTRL! (0x%08x)\n", - val); - - /* - * TI kernel sets these values for "symmetrical eye diagram and - * better signal quality" so let's assume somebody checked the - * values with a scope and set them here too. - */ - regmap_read(phy->syscon, phy->usbphy_ctrl, &val); - val |= DM816X_USBPHY_CTRL_TXRISETUNE | - DM816X_USBPHY_CTRL_TXVREFTUNE | - DM816X_USBPHY_CTRL_TXPREEMTUNE; - regmap_write(phy->syscon, phy->usbphy_ctrl, val); - - return 0; -} - -static const struct phy_ops ops = { - .init = dm816x_usb_phy_init, - .owner = THIS_MODULE, -}; - -static int __maybe_unused dm816x_usb_phy_runtime_suspend(struct device *dev) -{ - struct dm816x_usb_phy *phy = dev_get_drvdata(dev); - unsigned int mask, val; - int error = 0; - - mask = BIT(phy->instance); - val = ~BIT(phy->instance); - error = regmap_update_bits(phy->syscon, phy->usb_ctrl, - mask, val); - if (error) - dev_err(phy->dev, "phy%i failed to power off\n", - phy->instance); - clk_disable(phy->refclk); - - return 0; -} - -static int __maybe_unused dm816x_usb_phy_runtime_resume(struct device *dev) -{ - struct dm816x_usb_phy *phy = dev_get_drvdata(dev); - unsigned int mask, val; - int error; - - error = clk_enable(phy->refclk); - if (error) - return error; - - /* - * Note that at least dm816x rev c does not seem to do - * anything with the USB_CTRL register. But let's follow - * what the TI tree is doing in case later revisions use - * USB_CTRL. - */ - mask = BIT(phy->instance); - val = BIT(phy->instance); - error = regmap_update_bits(phy->syscon, phy->usb_ctrl, - mask, val); - if (error) { - dev_err(phy->dev, "phy%i failed to power on\n", - phy->instance); - clk_disable(phy->refclk); - return error; - } - - return 0; -} - -static UNIVERSAL_DEV_PM_OPS(dm816x_usb_phy_pm_ops, - dm816x_usb_phy_runtime_suspend, - dm816x_usb_phy_runtime_resume, - NULL); - -#ifdef CONFIG_OF -static const struct of_device_id dm816x_usb_phy_id_table[] = { - { - .compatible = "ti,dm8168-usb-phy", - }, - {}, -}; -MODULE_DEVICE_TABLE(of, dm816x_usb_phy_id_table); -#endif - -static int dm816x_usb_phy_probe(struct platform_device *pdev) -{ - struct dm816x_usb_phy *phy; - struct resource *res; - struct phy *generic_phy; - struct phy_provider *phy_provider; - struct usb_otg *otg; - const struct of_device_id *of_id; - const struct usb_phy_data *phy_data; - int error; - - of_id = of_match_device(of_match_ptr(dm816x_usb_phy_id_table), - &pdev->dev); - if (!of_id) - return -EINVAL; - - phy = devm_kzalloc(&pdev->dev, sizeof(*phy), GFP_KERNEL); - if (!phy) - return -ENOMEM; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) - return -ENOENT; - - phy->syscon = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, - "syscon"); - if (IS_ERR(phy->syscon)) - return PTR_ERR(phy->syscon); - - /* - * According to sprs614e.pdf, the first usb_ctrl is shared and - * the second instance for usb_ctrl is reserved.. Also the - * register bits are different from earlier TRMs. - */ - phy->usb_ctrl = 0x20; - phy->usbphy_ctrl = (res->start & 0xff) + 4; - if (phy->usbphy_ctrl == 0x2c) - phy->instance = 1; - - phy_data = of_id->data; - - otg = devm_kzalloc(&pdev->dev, sizeof(*otg), GFP_KERNEL); - if (!otg) - return -ENOMEM; - - phy->dev = &pdev->dev; - phy->phy.dev = phy->dev; - phy->phy.label = "dm8168_usb_phy"; - phy->phy.otg = otg; - phy->phy.type = USB_PHY_TYPE_USB2; - otg->set_host = dm816x_usb_phy_set_host; - otg->set_peripheral = dm816x_usb_phy_set_peripheral; - otg->usb_phy = &phy->phy; - - platform_set_drvdata(pdev, phy); - - phy->refclk = devm_clk_get(phy->dev, "refclk"); - if (IS_ERR(phy->refclk)) - return PTR_ERR(phy->refclk); - error = clk_prepare(phy->refclk); - if (error) - return error; - - pm_runtime_enable(phy->dev); - generic_phy = devm_phy_create(phy->dev, NULL, &ops); - if (IS_ERR(generic_phy)) - return PTR_ERR(generic_phy); - - phy_set_drvdata(generic_phy, phy); - - phy_provider = devm_of_phy_provider_register(phy->dev, - of_phy_simple_xlate); - if (IS_ERR(phy_provider)) - return PTR_ERR(phy_provider); - - usb_add_phy_dev(&phy->phy); - - return 0; -} - -static int dm816x_usb_phy_remove(struct platform_device *pdev) -{ - struct dm816x_usb_phy *phy = platform_get_drvdata(pdev); - - usb_remove_phy(&phy->phy); - pm_runtime_disable(phy->dev); - clk_unprepare(phy->refclk); - - return 0; -} - -static struct platform_driver dm816x_usb_phy_driver = { - .probe = dm816x_usb_phy_probe, - .remove = dm816x_usb_phy_remove, - .driver = { - .name = "dm816x-usb-phy", - .pm = &dm816x_usb_phy_pm_ops, - .of_match_table = of_match_ptr(dm816x_usb_phy_id_table), - }, -}; - -module_platform_driver(dm816x_usb_phy_driver); - -MODULE_ALIAS("platform:dm816x_usb"); -MODULE_AUTHOR("Tony Lindgren "); -MODULE_DESCRIPTION("dm816x usb phy driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-exynos-dp-video.c b/drivers/phy/phy-exynos-dp-video.c deleted file mode 100644 index bb3279dbf88c..000000000000 --- a/drivers/phy/phy-exynos-dp-video.c +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Samsung EXYNOS SoC series Display Port PHY driver - * - * Copyright (C) 2013 Samsung Electronics Co., Ltd. - * Author: Jingoo Han - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct exynos_dp_video_phy_drvdata { - u32 phy_ctrl_offset; -}; - -struct exynos_dp_video_phy { - struct regmap *regs; - const struct exynos_dp_video_phy_drvdata *drvdata; -}; - -static int exynos_dp_video_phy_power_on(struct phy *phy) -{ - struct exynos_dp_video_phy *state = phy_get_drvdata(phy); - - /* Disable power isolation on DP-PHY */ - return regmap_update_bits(state->regs, state->drvdata->phy_ctrl_offset, - EXYNOS4_PHY_ENABLE, EXYNOS4_PHY_ENABLE); -} - -static int exynos_dp_video_phy_power_off(struct phy *phy) -{ - struct exynos_dp_video_phy *state = phy_get_drvdata(phy); - - /* Enable power isolation on DP-PHY */ - return regmap_update_bits(state->regs, state->drvdata->phy_ctrl_offset, - EXYNOS4_PHY_ENABLE, 0); -} - -static const struct phy_ops exynos_dp_video_phy_ops = { - .power_on = exynos_dp_video_phy_power_on, - .power_off = exynos_dp_video_phy_power_off, - .owner = THIS_MODULE, -}; - -static const struct exynos_dp_video_phy_drvdata exynos5250_dp_video_phy = { - .phy_ctrl_offset = EXYNOS5_DPTX_PHY_CONTROL, -}; - -static const struct exynos_dp_video_phy_drvdata exynos5420_dp_video_phy = { - .phy_ctrl_offset = EXYNOS5420_DPTX_PHY_CONTROL, -}; - -static const struct of_device_id exynos_dp_video_phy_of_match[] = { - { - .compatible = "samsung,exynos5250-dp-video-phy", - .data = &exynos5250_dp_video_phy, - }, { - .compatible = "samsung,exynos5420-dp-video-phy", - .data = &exynos5420_dp_video_phy, - }, - { }, -}; -MODULE_DEVICE_TABLE(of, exynos_dp_video_phy_of_match); - -static int exynos_dp_video_phy_probe(struct platform_device *pdev) -{ - struct exynos_dp_video_phy *state; - struct device *dev = &pdev->dev; - const struct of_device_id *match; - struct phy_provider *phy_provider; - struct phy *phy; - - state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL); - if (!state) - return -ENOMEM; - - state->regs = syscon_regmap_lookup_by_phandle(dev->of_node, - "samsung,pmu-syscon"); - if (IS_ERR(state->regs)) { - dev_err(dev, "Failed to lookup PMU regmap\n"); - return PTR_ERR(state->regs); - } - - match = of_match_node(exynos_dp_video_phy_of_match, dev->of_node); - state->drvdata = match->data; - - phy = devm_phy_create(dev, NULL, &exynos_dp_video_phy_ops); - if (IS_ERR(phy)) { - dev_err(dev, "failed to create Display Port PHY\n"); - return PTR_ERR(phy); - } - phy_set_drvdata(phy, state); - - phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); - - return PTR_ERR_OR_ZERO(phy_provider); -} - -static struct platform_driver exynos_dp_video_phy_driver = { - .probe = exynos_dp_video_phy_probe, - .driver = { - .name = "exynos-dp-video-phy", - .of_match_table = exynos_dp_video_phy_of_match, - } -}; -module_platform_driver(exynos_dp_video_phy_driver); - -MODULE_AUTHOR("Jingoo Han "); -MODULE_DESCRIPTION("Samsung EXYNOS SoC DP PHY driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-exynos-mipi-video.c b/drivers/phy/phy-exynos-mipi-video.c deleted file mode 100644 index c198886f80a3..000000000000 --- a/drivers/phy/phy-exynos-mipi-video.c +++ /dev/null @@ -1,377 +0,0 @@ -/* - * Samsung S5P/EXYNOS SoC series MIPI CSIS/DSIM DPHY driver - * - * Copyright (C) 2013,2016 Samsung Electronics Co., Ltd. - * Author: Sylwester Nawrocki - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -enum exynos_mipi_phy_id { - EXYNOS_MIPI_PHY_ID_NONE = -1, - EXYNOS_MIPI_PHY_ID_CSIS0, - EXYNOS_MIPI_PHY_ID_DSIM0, - EXYNOS_MIPI_PHY_ID_CSIS1, - EXYNOS_MIPI_PHY_ID_DSIM1, - EXYNOS_MIPI_PHY_ID_CSIS2, - EXYNOS_MIPI_PHYS_NUM -}; - -enum exynos_mipi_phy_regmap_id { - EXYNOS_MIPI_REGMAP_PMU, - EXYNOS_MIPI_REGMAP_DISP, - EXYNOS_MIPI_REGMAP_CAM0, - EXYNOS_MIPI_REGMAP_CAM1, - EXYNOS_MIPI_REGMAPS_NUM -}; - -struct mipi_phy_device_desc { - int num_phys; - int num_regmaps; - const char *regmap_names[EXYNOS_MIPI_REGMAPS_NUM]; - struct exynos_mipi_phy_desc { - enum exynos_mipi_phy_id coupled_phy_id; - u32 enable_val; - unsigned int enable_reg; - enum exynos_mipi_phy_regmap_id enable_map; - u32 resetn_val; - unsigned int resetn_reg; - enum exynos_mipi_phy_regmap_id resetn_map; - } phys[EXYNOS_MIPI_PHYS_NUM]; -}; - -static const struct mipi_phy_device_desc s5pv210_mipi_phy = { - .num_regmaps = 1, - .regmap_names = {"syscon"}, - .num_phys = 4, - .phys = { - { - /* EXYNOS_MIPI_PHY_ID_CSIS0 */ - .coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM0, - .enable_val = EXYNOS4_PHY_ENABLE, - .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(0), - .enable_map = EXYNOS_MIPI_REGMAP_PMU, - .resetn_val = EXYNOS4_MIPI_PHY_SRESETN, - .resetn_reg = EXYNOS4_MIPI_PHY_CONTROL(0), - .resetn_map = EXYNOS_MIPI_REGMAP_PMU, - }, { - /* EXYNOS_MIPI_PHY_ID_DSIM0 */ - .coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS0, - .enable_val = EXYNOS4_PHY_ENABLE, - .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(0), - .enable_map = EXYNOS_MIPI_REGMAP_PMU, - .resetn_val = EXYNOS4_MIPI_PHY_MRESETN, - .resetn_reg = EXYNOS4_MIPI_PHY_CONTROL(0), - .resetn_map = EXYNOS_MIPI_REGMAP_PMU, - }, { - /* EXYNOS_MIPI_PHY_ID_CSIS1 */ - .coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM1, - .enable_val = EXYNOS4_PHY_ENABLE, - .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(1), - .enable_map = EXYNOS_MIPI_REGMAP_PMU, - .resetn_val = EXYNOS4_MIPI_PHY_SRESETN, - .resetn_reg = EXYNOS4_MIPI_PHY_CONTROL(1), - .resetn_map = EXYNOS_MIPI_REGMAP_PMU, - }, { - /* EXYNOS_MIPI_PHY_ID_DSIM1 */ - .coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS1, - .enable_val = EXYNOS4_PHY_ENABLE, - .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(1), - .enable_map = EXYNOS_MIPI_REGMAP_PMU, - .resetn_val = EXYNOS4_MIPI_PHY_MRESETN, - .resetn_reg = EXYNOS4_MIPI_PHY_CONTROL(1), - .resetn_map = EXYNOS_MIPI_REGMAP_PMU, - }, - }, -}; - -static const struct mipi_phy_device_desc exynos5420_mipi_phy = { - .num_regmaps = 1, - .regmap_names = {"syscon"}, - .num_phys = 5, - .phys = { - { - /* EXYNOS_MIPI_PHY_ID_CSIS0 */ - .coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM0, - .enable_val = EXYNOS4_PHY_ENABLE, - .enable_reg = EXYNOS5420_MIPI_PHY_CONTROL(0), - .enable_map = EXYNOS_MIPI_REGMAP_PMU, - .resetn_val = EXYNOS4_MIPI_PHY_SRESETN, - .resetn_reg = EXYNOS5420_MIPI_PHY_CONTROL(0), - .resetn_map = EXYNOS_MIPI_REGMAP_PMU, - }, { - /* EXYNOS_MIPI_PHY_ID_DSIM0 */ - .coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS0, - .enable_val = EXYNOS4_PHY_ENABLE, - .enable_reg = EXYNOS5420_MIPI_PHY_CONTROL(0), - .enable_map = EXYNOS_MIPI_REGMAP_PMU, - .resetn_val = EXYNOS4_MIPI_PHY_MRESETN, - .resetn_reg = EXYNOS5420_MIPI_PHY_CONTROL(0), - .resetn_map = EXYNOS_MIPI_REGMAP_PMU, - }, { - /* EXYNOS_MIPI_PHY_ID_CSIS1 */ - .coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM1, - .enable_val = EXYNOS4_PHY_ENABLE, - .enable_reg = EXYNOS5420_MIPI_PHY_CONTROL(1), - .enable_map = EXYNOS_MIPI_REGMAP_PMU, - .resetn_val = EXYNOS4_MIPI_PHY_SRESETN, - .resetn_reg = EXYNOS5420_MIPI_PHY_CONTROL(1), - .resetn_map = EXYNOS_MIPI_REGMAP_PMU, - }, { - /* EXYNOS_MIPI_PHY_ID_DSIM1 */ - .coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS1, - .enable_val = EXYNOS4_PHY_ENABLE, - .enable_reg = EXYNOS5420_MIPI_PHY_CONTROL(1), - .enable_map = EXYNOS_MIPI_REGMAP_PMU, - .resetn_val = EXYNOS4_MIPI_PHY_MRESETN, - .resetn_reg = EXYNOS5420_MIPI_PHY_CONTROL(1), - .resetn_map = EXYNOS_MIPI_REGMAP_PMU, - }, { - /* EXYNOS_MIPI_PHY_ID_CSIS2 */ - .coupled_phy_id = EXYNOS_MIPI_PHY_ID_NONE, - .enable_val = EXYNOS4_PHY_ENABLE, - .enable_reg = EXYNOS5420_MIPI_PHY_CONTROL(2), - .enable_map = EXYNOS_MIPI_REGMAP_PMU, - .resetn_val = EXYNOS4_MIPI_PHY_SRESETN, - .resetn_reg = EXYNOS5420_MIPI_PHY_CONTROL(2), - .resetn_map = EXYNOS_MIPI_REGMAP_PMU, - }, - }, -}; - -#define EXYNOS5433_SYSREG_DISP_MIPI_PHY 0x100C -#define EXYNOS5433_SYSREG_CAM0_MIPI_DPHY_CON 0x1014 -#define EXYNOS5433_SYSREG_CAM1_MIPI_DPHY_CON 0x1020 - -static const struct mipi_phy_device_desc exynos5433_mipi_phy = { - .num_regmaps = 4, - .regmap_names = { - "samsung,pmu-syscon", - "samsung,disp-sysreg", - "samsung,cam0-sysreg", - "samsung,cam1-sysreg" - }, - .num_phys = 5, - .phys = { - { - /* EXYNOS_MIPI_PHY_ID_CSIS0 */ - .coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM0, - .enable_val = EXYNOS4_PHY_ENABLE, - .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(0), - .enable_map = EXYNOS_MIPI_REGMAP_PMU, - .resetn_val = BIT(0), - .resetn_reg = EXYNOS5433_SYSREG_CAM0_MIPI_DPHY_CON, - .resetn_map = EXYNOS_MIPI_REGMAP_CAM0, - }, { - /* EXYNOS_MIPI_PHY_ID_DSIM0 */ - .coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS0, - .enable_val = EXYNOS4_PHY_ENABLE, - .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(0), - .enable_map = EXYNOS_MIPI_REGMAP_PMU, - .resetn_val = BIT(0), - .resetn_reg = EXYNOS5433_SYSREG_DISP_MIPI_PHY, - .resetn_map = EXYNOS_MIPI_REGMAP_DISP, - }, { - /* EXYNOS_MIPI_PHY_ID_CSIS1 */ - .coupled_phy_id = EXYNOS_MIPI_PHY_ID_NONE, - .enable_val = EXYNOS4_PHY_ENABLE, - .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(1), - .enable_map = EXYNOS_MIPI_REGMAP_PMU, - .resetn_val = BIT(1), - .resetn_reg = EXYNOS5433_SYSREG_CAM0_MIPI_DPHY_CON, - .resetn_map = EXYNOS_MIPI_REGMAP_CAM0, - }, { - /* EXYNOS_MIPI_PHY_ID_DSIM1 */ - .coupled_phy_id = EXYNOS_MIPI_PHY_ID_NONE, - .enable_val = EXYNOS4_PHY_ENABLE, - .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(1), - .enable_map = EXYNOS_MIPI_REGMAP_PMU, - .resetn_val = BIT(1), - .resetn_reg = EXYNOS5433_SYSREG_DISP_MIPI_PHY, - .resetn_map = EXYNOS_MIPI_REGMAP_DISP, - }, { - /* EXYNOS_MIPI_PHY_ID_CSIS2 */ - .coupled_phy_id = EXYNOS_MIPI_PHY_ID_NONE, - .enable_val = EXYNOS4_PHY_ENABLE, - .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(2), - .enable_map = EXYNOS_MIPI_REGMAP_PMU, - .resetn_val = BIT(0), - .resetn_reg = EXYNOS5433_SYSREG_CAM1_MIPI_DPHY_CON, - .resetn_map = EXYNOS_MIPI_REGMAP_CAM1, - }, - }, -}; - -struct exynos_mipi_video_phy { - struct regmap *regmaps[EXYNOS_MIPI_REGMAPS_NUM]; - int num_phys; - struct video_phy_desc { - struct phy *phy; - unsigned int index; - const struct exynos_mipi_phy_desc *data; - } phys[EXYNOS_MIPI_PHYS_NUM]; - spinlock_t slock; -}; - -static int __set_phy_state(const struct exynos_mipi_phy_desc *data, - struct exynos_mipi_video_phy *state, unsigned int on) -{ - u32 val; - - spin_lock(&state->slock); - - /* disable in PMU sysreg */ - if (!on && data->coupled_phy_id >= 0 && - state->phys[data->coupled_phy_id].phy->power_count == 0) { - regmap_read(state->regmaps[data->enable_map], data->enable_reg, - &val); - val &= ~data->enable_val; - regmap_write(state->regmaps[data->enable_map], data->enable_reg, - val); - } - - /* PHY reset */ - regmap_read(state->regmaps[data->resetn_map], data->resetn_reg, &val); - val = on ? (val | data->resetn_val) : (val & ~data->resetn_val); - regmap_write(state->regmaps[data->resetn_map], data->resetn_reg, val); - - /* enable in PMU sysreg */ - if (on) { - regmap_read(state->regmaps[data->enable_map], data->enable_reg, - &val); - val |= data->enable_val; - regmap_write(state->regmaps[data->enable_map], data->enable_reg, - val); - } - - spin_unlock(&state->slock); - - return 0; -} - -#define to_mipi_video_phy(desc) \ - container_of((desc), struct exynos_mipi_video_phy, phys[(desc)->index]) - -static int exynos_mipi_video_phy_power_on(struct phy *phy) -{ - struct video_phy_desc *phy_desc = phy_get_drvdata(phy); - struct exynos_mipi_video_phy *state = to_mipi_video_phy(phy_desc); - - return __set_phy_state(phy_desc->data, state, 1); -} - -static int exynos_mipi_video_phy_power_off(struct phy *phy) -{ - struct video_phy_desc *phy_desc = phy_get_drvdata(phy); - struct exynos_mipi_video_phy *state = to_mipi_video_phy(phy_desc); - - return __set_phy_state(phy_desc->data, state, 0); -} - -static struct phy *exynos_mipi_video_phy_xlate(struct device *dev, - struct of_phandle_args *args) -{ - struct exynos_mipi_video_phy *state = dev_get_drvdata(dev); - - if (WARN_ON(args->args[0] >= state->num_phys)) - return ERR_PTR(-ENODEV); - - return state->phys[args->args[0]].phy; -} - -static const struct phy_ops exynos_mipi_video_phy_ops = { - .power_on = exynos_mipi_video_phy_power_on, - .power_off = exynos_mipi_video_phy_power_off, - .owner = THIS_MODULE, -}; - -static int exynos_mipi_video_phy_probe(struct platform_device *pdev) -{ - const struct mipi_phy_device_desc *phy_dev; - struct exynos_mipi_video_phy *state; - struct device *dev = &pdev->dev; - struct device_node *np = dev->of_node; - struct phy_provider *phy_provider; - unsigned int i; - - phy_dev = of_device_get_match_data(dev); - if (!phy_dev) - return -ENODEV; - - state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL); - if (!state) - return -ENOMEM; - - for (i = 0; i < phy_dev->num_regmaps; i++) { - state->regmaps[i] = syscon_regmap_lookup_by_phandle(np, - phy_dev->regmap_names[i]); - if (IS_ERR(state->regmaps[i])) - return PTR_ERR(state->regmaps[i]); - } - state->num_phys = phy_dev->num_phys; - spin_lock_init(&state->slock); - - dev_set_drvdata(dev, state); - - for (i = 0; i < state->num_phys; i++) { - struct phy *phy = devm_phy_create(dev, NULL, - &exynos_mipi_video_phy_ops); - if (IS_ERR(phy)) { - dev_err(dev, "failed to create PHY %d\n", i); - return PTR_ERR(phy); - } - - state->phys[i].phy = phy; - state->phys[i].index = i; - state->phys[i].data = &phy_dev->phys[i]; - phy_set_drvdata(phy, &state->phys[i]); - } - - phy_provider = devm_of_phy_provider_register(dev, - exynos_mipi_video_phy_xlate); - - return PTR_ERR_OR_ZERO(phy_provider); -} - -static const struct of_device_id exynos_mipi_video_phy_of_match[] = { - { - .compatible = "samsung,s5pv210-mipi-video-phy", - .data = &s5pv210_mipi_phy, - }, { - .compatible = "samsung,exynos5420-mipi-video-phy", - .data = &exynos5420_mipi_phy, - }, { - .compatible = "samsung,exynos5433-mipi-video-phy", - .data = &exynos5433_mipi_phy, - }, - { /* sentinel */ }, -}; -MODULE_DEVICE_TABLE(of, exynos_mipi_video_phy_of_match); - -static struct platform_driver exynos_mipi_video_phy_driver = { - .probe = exynos_mipi_video_phy_probe, - .driver = { - .of_match_table = exynos_mipi_video_phy_of_match, - .name = "exynos-mipi-video-phy", - } -}; -module_platform_driver(exynos_mipi_video_phy_driver); - -MODULE_DESCRIPTION("Samsung S5P/EXYNOS SoC MIPI CSI-2/DSI PHY driver"); -MODULE_AUTHOR("Sylwester Nawrocki "); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-exynos-pcie.c b/drivers/phy/phy-exynos-pcie.c deleted file mode 100644 index a89c12faff39..000000000000 --- a/drivers/phy/phy-exynos-pcie.c +++ /dev/null @@ -1,281 +0,0 @@ -/* - * Samsung EXYNOS SoC series PCIe PHY driver - * - * Phy provider for PCIe controller on Exynos SoC series - * - * Copyright (C) 2017 Samsung Electronics Co., Ltd. - * Jaehoon Chung - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* PCIe Purple registers */ -#define PCIE_PHY_GLOBAL_RESET 0x000 -#define PCIE_PHY_COMMON_RESET 0x004 -#define PCIE_PHY_CMN_REG 0x008 -#define PCIE_PHY_MAC_RESET 0x00c -#define PCIE_PHY_PLL_LOCKED 0x010 -#define PCIE_PHY_TRSVREG_RESET 0x020 -#define PCIE_PHY_TRSV_RESET 0x024 - -/* PCIe PHY registers */ -#define PCIE_PHY_IMPEDANCE 0x004 -#define PCIE_PHY_PLL_DIV_0 0x008 -#define PCIE_PHY_PLL_BIAS 0x00c -#define PCIE_PHY_DCC_FEEDBACK 0x014 -#define PCIE_PHY_PLL_DIV_1 0x05c -#define PCIE_PHY_COMMON_POWER 0x064 -#define PCIE_PHY_COMMON_PD_CMN BIT(3) -#define PCIE_PHY_TRSV0_EMP_LVL 0x084 -#define PCIE_PHY_TRSV0_DRV_LVL 0x088 -#define PCIE_PHY_TRSV0_RXCDR 0x0ac -#define PCIE_PHY_TRSV0_POWER 0x0c4 -#define PCIE_PHY_TRSV0_PD_TSV BIT(7) -#define PCIE_PHY_TRSV0_LVCC 0x0dc -#define PCIE_PHY_TRSV1_EMP_LVL 0x144 -#define PCIE_PHY_TRSV1_RXCDR 0x16c -#define PCIE_PHY_TRSV1_POWER 0x184 -#define PCIE_PHY_TRSV1_PD_TSV BIT(7) -#define PCIE_PHY_TRSV1_LVCC 0x19c -#define PCIE_PHY_TRSV2_EMP_LVL 0x204 -#define PCIE_PHY_TRSV2_RXCDR 0x22c -#define PCIE_PHY_TRSV2_POWER 0x244 -#define PCIE_PHY_TRSV2_PD_TSV BIT(7) -#define PCIE_PHY_TRSV2_LVCC 0x25c -#define PCIE_PHY_TRSV3_EMP_LVL 0x2c4 -#define PCIE_PHY_TRSV3_RXCDR 0x2ec -#define PCIE_PHY_TRSV3_POWER 0x304 -#define PCIE_PHY_TRSV3_PD_TSV BIT(7) -#define PCIE_PHY_TRSV3_LVCC 0x31c - -struct exynos_pcie_phy_data { - const struct phy_ops *ops; -}; - -/* For Exynos pcie phy */ -struct exynos_pcie_phy { - const struct exynos_pcie_phy_data *drv_data; - void __iomem *phy_base; - void __iomem *blk_base; /* For exynos5440 */ -}; - -static void exynos_pcie_phy_writel(void __iomem *base, u32 val, u32 offset) -{ - writel(val, base + offset); -} - -static u32 exynos_pcie_phy_readl(void __iomem *base, u32 offset) -{ - return readl(base + offset); -} - -/* For Exynos5440 specific functions */ -static int exynos5440_pcie_phy_init(struct phy *phy) -{ - struct exynos_pcie_phy *ep = phy_get_drvdata(phy); - - /* DCC feedback control off */ - exynos_pcie_phy_writel(ep->phy_base, 0x29, PCIE_PHY_DCC_FEEDBACK); - - /* set TX/RX impedance */ - exynos_pcie_phy_writel(ep->phy_base, 0xd5, PCIE_PHY_IMPEDANCE); - - /* set 50Mhz PHY clock */ - exynos_pcie_phy_writel(ep->phy_base, 0x14, PCIE_PHY_PLL_DIV_0); - exynos_pcie_phy_writel(ep->phy_base, 0x12, PCIE_PHY_PLL_DIV_1); - - /* set TX Differential output for lane 0 */ - exynos_pcie_phy_writel(ep->phy_base, 0x7f, PCIE_PHY_TRSV0_DRV_LVL); - - /* set TX Pre-emphasis Level Control for lane 0 to minimum */ - exynos_pcie_phy_writel(ep->phy_base, 0x0, PCIE_PHY_TRSV0_EMP_LVL); - - /* set RX clock and data recovery bandwidth */ - exynos_pcie_phy_writel(ep->phy_base, 0xe7, PCIE_PHY_PLL_BIAS); - exynos_pcie_phy_writel(ep->phy_base, 0x82, PCIE_PHY_TRSV0_RXCDR); - exynos_pcie_phy_writel(ep->phy_base, 0x82, PCIE_PHY_TRSV1_RXCDR); - exynos_pcie_phy_writel(ep->phy_base, 0x82, PCIE_PHY_TRSV2_RXCDR); - exynos_pcie_phy_writel(ep->phy_base, 0x82, PCIE_PHY_TRSV3_RXCDR); - - /* change TX Pre-emphasis Level Control for lanes */ - exynos_pcie_phy_writel(ep->phy_base, 0x39, PCIE_PHY_TRSV0_EMP_LVL); - exynos_pcie_phy_writel(ep->phy_base, 0x39, PCIE_PHY_TRSV1_EMP_LVL); - exynos_pcie_phy_writel(ep->phy_base, 0x39, PCIE_PHY_TRSV2_EMP_LVL); - exynos_pcie_phy_writel(ep->phy_base, 0x39, PCIE_PHY_TRSV3_EMP_LVL); - - /* set LVCC */ - exynos_pcie_phy_writel(ep->phy_base, 0x20, PCIE_PHY_TRSV0_LVCC); - exynos_pcie_phy_writel(ep->phy_base, 0xa0, PCIE_PHY_TRSV1_LVCC); - exynos_pcie_phy_writel(ep->phy_base, 0xa0, PCIE_PHY_TRSV2_LVCC); - exynos_pcie_phy_writel(ep->phy_base, 0xa0, PCIE_PHY_TRSV3_LVCC); - - /* pulse for common reset */ - exynos_pcie_phy_writel(ep->blk_base, 1, PCIE_PHY_COMMON_RESET); - udelay(500); - exynos_pcie_phy_writel(ep->blk_base, 0, PCIE_PHY_COMMON_RESET); - - return 0; -} - -static int exynos5440_pcie_phy_power_on(struct phy *phy) -{ - struct exynos_pcie_phy *ep = phy_get_drvdata(phy); - u32 val; - - exynos_pcie_phy_writel(ep->blk_base, 0, PCIE_PHY_COMMON_RESET); - exynos_pcie_phy_writel(ep->blk_base, 0, PCIE_PHY_CMN_REG); - exynos_pcie_phy_writel(ep->blk_base, 0, PCIE_PHY_TRSVREG_RESET); - exynos_pcie_phy_writel(ep->blk_base, 0, PCIE_PHY_TRSV_RESET); - - val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_COMMON_POWER); - val &= ~PCIE_PHY_COMMON_PD_CMN; - exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_COMMON_POWER); - - val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_TRSV0_POWER); - val &= ~PCIE_PHY_TRSV0_PD_TSV; - exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_TRSV0_POWER); - - val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_TRSV1_POWER); - val &= ~PCIE_PHY_TRSV1_PD_TSV; - exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_TRSV1_POWER); - - val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_TRSV2_POWER); - val &= ~PCIE_PHY_TRSV2_PD_TSV; - exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_TRSV2_POWER); - - val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_TRSV3_POWER); - val &= ~PCIE_PHY_TRSV3_PD_TSV; - exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_TRSV3_POWER); - - return 0; -} - -static int exynos5440_pcie_phy_power_off(struct phy *phy) -{ - struct exynos_pcie_phy *ep = phy_get_drvdata(phy); - u32 val; - - if (readl_poll_timeout(ep->phy_base + PCIE_PHY_PLL_LOCKED, val, - (val != 0), 1, 500)) { - dev_err(&phy->dev, "PLL Locked: 0x%x\n", val); - return -ETIMEDOUT; - } - - val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_COMMON_POWER); - val |= PCIE_PHY_COMMON_PD_CMN; - exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_COMMON_POWER); - - val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_TRSV0_POWER); - val |= PCIE_PHY_TRSV0_PD_TSV; - exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_TRSV0_POWER); - - val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_TRSV1_POWER); - val |= PCIE_PHY_TRSV1_PD_TSV; - exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_TRSV1_POWER); - - val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_TRSV2_POWER); - val |= PCIE_PHY_TRSV2_PD_TSV; - exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_TRSV2_POWER); - - val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_TRSV3_POWER); - val |= PCIE_PHY_TRSV3_PD_TSV; - exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_TRSV3_POWER); - - return 0; -} - -static int exynos5440_pcie_phy_reset(struct phy *phy) -{ - struct exynos_pcie_phy *ep = phy_get_drvdata(phy); - - exynos_pcie_phy_writel(ep->blk_base, 0, PCIE_PHY_MAC_RESET); - exynos_pcie_phy_writel(ep->blk_base, 1, PCIE_PHY_GLOBAL_RESET); - exynos_pcie_phy_writel(ep->blk_base, 0, PCIE_PHY_GLOBAL_RESET); - - return 0; -} - -static const struct phy_ops exynos5440_phy_ops = { - .init = exynos5440_pcie_phy_init, - .power_on = exynos5440_pcie_phy_power_on, - .power_off = exynos5440_pcie_phy_power_off, - .reset = exynos5440_pcie_phy_reset, - .owner = THIS_MODULE, -}; - -static const struct exynos_pcie_phy_data exynos5440_pcie_phy_data = { - .ops = &exynos5440_phy_ops, -}; - -static const struct of_device_id exynos_pcie_phy_match[] = { - { - .compatible = "samsung,exynos5440-pcie-phy", - .data = &exynos5440_pcie_phy_data, - }, - {}, -}; - -static int exynos_pcie_phy_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct exynos_pcie_phy *exynos_phy; - struct phy *generic_phy; - struct phy_provider *phy_provider; - struct resource *res; - const struct exynos_pcie_phy_data *drv_data; - - drv_data = of_device_get_match_data(dev); - if (!drv_data) - return -ENODEV; - - exynos_phy = devm_kzalloc(dev, sizeof(*exynos_phy), GFP_KERNEL); - if (!exynos_phy) - return -ENOMEM; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - exynos_phy->phy_base = devm_ioremap_resource(dev, res); - if (IS_ERR(exynos_phy->phy_base)) - return PTR_ERR(exynos_phy->phy_base); - - res = platform_get_resource(pdev, IORESOURCE_MEM, 1); - exynos_phy->blk_base = devm_ioremap_resource(dev, res); - if (IS_ERR(exynos_phy->blk_base)) - return PTR_ERR(exynos_phy->blk_base); - - exynos_phy->drv_data = drv_data; - - generic_phy = devm_phy_create(dev, dev->of_node, drv_data->ops); - if (IS_ERR(generic_phy)) { - dev_err(dev, "failed to create PHY\n"); - return PTR_ERR(generic_phy); - } - - phy_set_drvdata(generic_phy, exynos_phy); - phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); - - return PTR_ERR_OR_ZERO(phy_provider); -} - -static struct platform_driver exynos_pcie_phy_driver = { - .probe = exynos_pcie_phy_probe, - .driver = { - .of_match_table = exynos_pcie_phy_match, - .name = "exynos_pcie_phy", - } -}; - -builtin_platform_driver(exynos_pcie_phy_driver); diff --git a/drivers/phy/phy-exynos4210-usb2.c b/drivers/phy/phy-exynos4210-usb2.c deleted file mode 100644 index 1f50e1004828..000000000000 --- a/drivers/phy/phy-exynos4210-usb2.c +++ /dev/null @@ -1,260 +0,0 @@ -/* - * Samsung SoC USB 1.1/2.0 PHY driver - Exynos 4210 support - * - * Copyright (C) 2013 Samsung Electronics Co., Ltd. - * Author: Kamil Debski - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include "phy-samsung-usb2.h" - -/* Exynos USB PHY registers */ - -/* PHY power control */ -#define EXYNOS_4210_UPHYPWR 0x0 - -#define EXYNOS_4210_UPHYPWR_PHY0_SUSPEND BIT(0) -#define EXYNOS_4210_UPHYPWR_PHY0_PWR BIT(3) -#define EXYNOS_4210_UPHYPWR_PHY0_OTG_PWR BIT(4) -#define EXYNOS_4210_UPHYPWR_PHY0_SLEEP BIT(5) -#define EXYNOS_4210_UPHYPWR_PHY0 ( \ - EXYNOS_4210_UPHYPWR_PHY0_SUSPEND | \ - EXYNOS_4210_UPHYPWR_PHY0_PWR | \ - EXYNOS_4210_UPHYPWR_PHY0_OTG_PWR | \ - EXYNOS_4210_UPHYPWR_PHY0_SLEEP) - -#define EXYNOS_4210_UPHYPWR_PHY1_SUSPEND BIT(6) -#define EXYNOS_4210_UPHYPWR_PHY1_PWR BIT(7) -#define EXYNOS_4210_UPHYPWR_PHY1_SLEEP BIT(8) -#define EXYNOS_4210_UPHYPWR_PHY1 ( \ - EXYNOS_4210_UPHYPWR_PHY1_SUSPEND | \ - EXYNOS_4210_UPHYPWR_PHY1_PWR | \ - EXYNOS_4210_UPHYPWR_PHY1_SLEEP) - -#define EXYNOS_4210_UPHYPWR_HSIC0_SUSPEND BIT(9) -#define EXYNOS_4210_UPHYPWR_HSIC0_SLEEP BIT(10) -#define EXYNOS_4210_UPHYPWR_HSIC0 ( \ - EXYNOS_4210_UPHYPWR_HSIC0_SUSPEND | \ - EXYNOS_4210_UPHYPWR_HSIC0_SLEEP) - -#define EXYNOS_4210_UPHYPWR_HSIC1_SUSPEND BIT(11) -#define EXYNOS_4210_UPHYPWR_HSIC1_SLEEP BIT(12) -#define EXYNOS_4210_UPHYPWR_HSIC1 ( \ - EXYNOS_4210_UPHYPWR_HSIC1_SUSPEND | \ - EXYNOS_4210_UPHYPWR_HSIC1_SLEEP) - -/* PHY clock control */ -#define EXYNOS_4210_UPHYCLK 0x4 - -#define EXYNOS_4210_UPHYCLK_PHYFSEL_MASK (0x3 << 0) -#define EXYNOS_4210_UPHYCLK_PHYFSEL_OFFSET 0 -#define EXYNOS_4210_UPHYCLK_PHYFSEL_48MHZ (0x0 << 0) -#define EXYNOS_4210_UPHYCLK_PHYFSEL_24MHZ (0x3 << 0) -#define EXYNOS_4210_UPHYCLK_PHYFSEL_12MHZ (0x2 << 0) - -#define EXYNOS_4210_UPHYCLK_PHY0_ID_PULLUP BIT(2) -#define EXYNOS_4210_UPHYCLK_PHY0_COMMON_ON BIT(4) -#define EXYNOS_4210_UPHYCLK_PHY1_COMMON_ON BIT(7) - -/* PHY reset control */ -#define EXYNOS_4210_UPHYRST 0x8 - -#define EXYNOS_4210_URSTCON_PHY0 BIT(0) -#define EXYNOS_4210_URSTCON_OTG_HLINK BIT(1) -#define EXYNOS_4210_URSTCON_OTG_PHYLINK BIT(2) -#define EXYNOS_4210_URSTCON_PHY1_ALL BIT(3) -#define EXYNOS_4210_URSTCON_PHY1_P0 BIT(4) -#define EXYNOS_4210_URSTCON_PHY1_P1P2 BIT(5) -#define EXYNOS_4210_URSTCON_HOST_LINK_ALL BIT(6) -#define EXYNOS_4210_URSTCON_HOST_LINK_P0 BIT(7) -#define EXYNOS_4210_URSTCON_HOST_LINK_P1 BIT(8) -#define EXYNOS_4210_URSTCON_HOST_LINK_P2 BIT(9) - -/* Isolation, configured in the power management unit */ -#define EXYNOS_4210_USB_ISOL_DEVICE_OFFSET 0x704 -#define EXYNOS_4210_USB_ISOL_DEVICE BIT(0) -#define EXYNOS_4210_USB_ISOL_HOST_OFFSET 0x708 -#define EXYNOS_4210_USB_ISOL_HOST BIT(0) - -/* USBYPHY1 Floating prevention */ -#define EXYNOS_4210_UPHY1CON 0x34 -#define EXYNOS_4210_UPHY1CON_FLOAT_PREVENTION 0x1 - -/* Mode switching SUB Device <-> Host */ -#define EXYNOS_4210_MODE_SWITCH_OFFSET 0x21c -#define EXYNOS_4210_MODE_SWITCH_MASK 1 -#define EXYNOS_4210_MODE_SWITCH_DEVICE 0 -#define EXYNOS_4210_MODE_SWITCH_HOST 1 - -enum exynos4210_phy_id { - EXYNOS4210_DEVICE, - EXYNOS4210_HOST, - EXYNOS4210_HSIC0, - EXYNOS4210_HSIC1, - EXYNOS4210_NUM_PHYS, -}; - -/* - * exynos4210_rate_to_clk() converts the supplied clock rate to the value that - * can be written to the phy register. - */ -static int exynos4210_rate_to_clk(unsigned long rate, u32 *reg) -{ - switch (rate) { - case 12 * MHZ: - *reg = EXYNOS_4210_UPHYCLK_PHYFSEL_12MHZ; - break; - case 24 * MHZ: - *reg = EXYNOS_4210_UPHYCLK_PHYFSEL_24MHZ; - break; - case 48 * MHZ: - *reg = EXYNOS_4210_UPHYCLK_PHYFSEL_48MHZ; - break; - default: - return -EINVAL; - } - - return 0; -} - -static void exynos4210_isol(struct samsung_usb2_phy_instance *inst, bool on) -{ - struct samsung_usb2_phy_driver *drv = inst->drv; - u32 offset; - u32 mask; - - switch (inst->cfg->id) { - case EXYNOS4210_DEVICE: - offset = EXYNOS_4210_USB_ISOL_DEVICE_OFFSET; - mask = EXYNOS_4210_USB_ISOL_DEVICE; - break; - case EXYNOS4210_HOST: - offset = EXYNOS_4210_USB_ISOL_HOST_OFFSET; - mask = EXYNOS_4210_USB_ISOL_HOST; - break; - default: - return; - } - - regmap_update_bits(drv->reg_pmu, offset, mask, on ? 0 : mask); -} - -static void exynos4210_phy_pwr(struct samsung_usb2_phy_instance *inst, bool on) -{ - struct samsung_usb2_phy_driver *drv = inst->drv; - u32 rstbits = 0; - u32 phypwr = 0; - u32 rst; - u32 pwr; - u32 clk; - - switch (inst->cfg->id) { - case EXYNOS4210_DEVICE: - phypwr = EXYNOS_4210_UPHYPWR_PHY0; - rstbits = EXYNOS_4210_URSTCON_PHY0; - break; - case EXYNOS4210_HOST: - phypwr = EXYNOS_4210_UPHYPWR_PHY1; - rstbits = EXYNOS_4210_URSTCON_PHY1_ALL | - EXYNOS_4210_URSTCON_PHY1_P0 | - EXYNOS_4210_URSTCON_PHY1_P1P2 | - EXYNOS_4210_URSTCON_HOST_LINK_ALL | - EXYNOS_4210_URSTCON_HOST_LINK_P0; - writel(on, drv->reg_phy + EXYNOS_4210_UPHY1CON); - break; - case EXYNOS4210_HSIC0: - phypwr = EXYNOS_4210_UPHYPWR_HSIC0; - rstbits = EXYNOS_4210_URSTCON_PHY1_P1P2 | - EXYNOS_4210_URSTCON_HOST_LINK_P1; - break; - case EXYNOS4210_HSIC1: - phypwr = EXYNOS_4210_UPHYPWR_HSIC1; - rstbits = EXYNOS_4210_URSTCON_PHY1_P1P2 | - EXYNOS_4210_URSTCON_HOST_LINK_P2; - break; - } - - if (on) { - clk = readl(drv->reg_phy + EXYNOS_4210_UPHYCLK); - clk &= ~EXYNOS_4210_UPHYCLK_PHYFSEL_MASK; - clk |= drv->ref_reg_val << EXYNOS_4210_UPHYCLK_PHYFSEL_OFFSET; - writel(clk, drv->reg_phy + EXYNOS_4210_UPHYCLK); - - pwr = readl(drv->reg_phy + EXYNOS_4210_UPHYPWR); - pwr &= ~phypwr; - writel(pwr, drv->reg_phy + EXYNOS_4210_UPHYPWR); - - rst = readl(drv->reg_phy + EXYNOS_4210_UPHYRST); - rst |= rstbits; - writel(rst, drv->reg_phy + EXYNOS_4210_UPHYRST); - udelay(10); - rst &= ~rstbits; - writel(rst, drv->reg_phy + EXYNOS_4210_UPHYRST); - /* The following delay is necessary for the reset sequence to be - * completed */ - udelay(80); - } else { - pwr = readl(drv->reg_phy + EXYNOS_4210_UPHYPWR); - pwr |= phypwr; - writel(pwr, drv->reg_phy + EXYNOS_4210_UPHYPWR); - } -} - -static int exynos4210_power_on(struct samsung_usb2_phy_instance *inst) -{ - /* Order of initialisation is important - first power then isolation */ - exynos4210_phy_pwr(inst, 1); - exynos4210_isol(inst, 0); - - return 0; -} - -static int exynos4210_power_off(struct samsung_usb2_phy_instance *inst) -{ - exynos4210_isol(inst, 1); - exynos4210_phy_pwr(inst, 0); - - return 0; -} - - -static const struct samsung_usb2_common_phy exynos4210_phys[] = { - { - .label = "device", - .id = EXYNOS4210_DEVICE, - .power_on = exynos4210_power_on, - .power_off = exynos4210_power_off, - }, - { - .label = "host", - .id = EXYNOS4210_HOST, - .power_on = exynos4210_power_on, - .power_off = exynos4210_power_off, - }, - { - .label = "hsic0", - .id = EXYNOS4210_HSIC0, - .power_on = exynos4210_power_on, - .power_off = exynos4210_power_off, - }, - { - .label = "hsic1", - .id = EXYNOS4210_HSIC1, - .power_on = exynos4210_power_on, - .power_off = exynos4210_power_off, - }, -}; - -const struct samsung_usb2_phy_config exynos4210_usb2_phy_config = { - .has_mode_switch = 0, - .num_phys = EXYNOS4210_NUM_PHYS, - .phys = exynos4210_phys, - .rate_to_clk = exynos4210_rate_to_clk, -}; diff --git a/drivers/phy/phy-exynos4x12-usb2.c b/drivers/phy/phy-exynos4x12-usb2.c deleted file mode 100644 index 7f27a91acf87..000000000000 --- a/drivers/phy/phy-exynos4x12-usb2.c +++ /dev/null @@ -1,378 +0,0 @@ -/* - * Samsung SoC USB 1.1/2.0 PHY driver - Exynos 4x12 support - * - * Copyright (C) 2013 Samsung Electronics Co., Ltd. - * Author: Kamil Debski - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include "phy-samsung-usb2.h" - -/* Exynos USB PHY registers */ - -/* PHY power control */ -#define EXYNOS_4x12_UPHYPWR 0x0 - -#define EXYNOS_4x12_UPHYPWR_PHY0_SUSPEND BIT(0) -#define EXYNOS_4x12_UPHYPWR_PHY0_PWR BIT(3) -#define EXYNOS_4x12_UPHYPWR_PHY0_OTG_PWR BIT(4) -#define EXYNOS_4x12_UPHYPWR_PHY0_SLEEP BIT(5) -#define EXYNOS_4x12_UPHYPWR_PHY0 ( \ - EXYNOS_4x12_UPHYPWR_PHY0_SUSPEND | \ - EXYNOS_4x12_UPHYPWR_PHY0_PWR | \ - EXYNOS_4x12_UPHYPWR_PHY0_OTG_PWR | \ - EXYNOS_4x12_UPHYPWR_PHY0_SLEEP) - -#define EXYNOS_4x12_UPHYPWR_PHY1_SUSPEND BIT(6) -#define EXYNOS_4x12_UPHYPWR_PHY1_PWR BIT(7) -#define EXYNOS_4x12_UPHYPWR_PHY1_SLEEP BIT(8) -#define EXYNOS_4x12_UPHYPWR_PHY1 ( \ - EXYNOS_4x12_UPHYPWR_PHY1_SUSPEND | \ - EXYNOS_4x12_UPHYPWR_PHY1_PWR | \ - EXYNOS_4x12_UPHYPWR_PHY1_SLEEP) - -#define EXYNOS_4x12_UPHYPWR_HSIC0_SUSPEND BIT(9) -#define EXYNOS_4x12_UPHYPWR_HSIC0_PWR BIT(10) -#define EXYNOS_4x12_UPHYPWR_HSIC0_SLEEP BIT(11) -#define EXYNOS_4x12_UPHYPWR_HSIC0 ( \ - EXYNOS_4x12_UPHYPWR_HSIC0_SUSPEND | \ - EXYNOS_4x12_UPHYPWR_HSIC0_PWR | \ - EXYNOS_4x12_UPHYPWR_HSIC0_SLEEP) - -#define EXYNOS_4x12_UPHYPWR_HSIC1_SUSPEND BIT(12) -#define EXYNOS_4x12_UPHYPWR_HSIC1_PWR BIT(13) -#define EXYNOS_4x12_UPHYPWR_HSIC1_SLEEP BIT(14) -#define EXYNOS_4x12_UPHYPWR_HSIC1 ( \ - EXYNOS_4x12_UPHYPWR_HSIC1_SUSPEND | \ - EXYNOS_4x12_UPHYPWR_HSIC1_PWR | \ - EXYNOS_4x12_UPHYPWR_HSIC1_SLEEP) - -/* PHY clock control */ -#define EXYNOS_4x12_UPHYCLK 0x4 - -#define EXYNOS_4x12_UPHYCLK_PHYFSEL_MASK (0x7 << 0) -#define EXYNOS_4x12_UPHYCLK_PHYFSEL_OFFSET 0 -#define EXYNOS_4x12_UPHYCLK_PHYFSEL_9MHZ6 (0x0 << 0) -#define EXYNOS_4x12_UPHYCLK_PHYFSEL_10MHZ (0x1 << 0) -#define EXYNOS_4x12_UPHYCLK_PHYFSEL_12MHZ (0x2 << 0) -#define EXYNOS_4x12_UPHYCLK_PHYFSEL_19MHZ2 (0x3 << 0) -#define EXYNOS_4x12_UPHYCLK_PHYFSEL_20MHZ (0x4 << 0) -#define EXYNOS_4x12_UPHYCLK_PHYFSEL_24MHZ (0x5 << 0) -#define EXYNOS_4x12_UPHYCLK_PHYFSEL_50MHZ (0x7 << 0) - -#define EXYNOS_3250_UPHYCLK_REFCLKSEL (0x2 << 8) - -#define EXYNOS_4x12_UPHYCLK_PHY0_ID_PULLUP BIT(3) -#define EXYNOS_4x12_UPHYCLK_PHY0_COMMON_ON BIT(4) -#define EXYNOS_4x12_UPHYCLK_PHY1_COMMON_ON BIT(7) - -#define EXYNOS_4x12_UPHYCLK_HSIC_REFCLK_MASK (0x7f << 10) -#define EXYNOS_4x12_UPHYCLK_HSIC_REFCLK_OFFSET 10 -#define EXYNOS_4x12_UPHYCLK_HSIC_REFCLK_12MHZ (0x24 << 10) -#define EXYNOS_4x12_UPHYCLK_HSIC_REFCLK_15MHZ (0x1c << 10) -#define EXYNOS_4x12_UPHYCLK_HSIC_REFCLK_16MHZ (0x1a << 10) -#define EXYNOS_4x12_UPHYCLK_HSIC_REFCLK_19MHZ2 (0x15 << 10) -#define EXYNOS_4x12_UPHYCLK_HSIC_REFCLK_20MHZ (0x14 << 10) - -/* PHY reset control */ -#define EXYNOS_4x12_UPHYRST 0x8 - -#define EXYNOS_4x12_URSTCON_PHY0 BIT(0) -#define EXYNOS_4x12_URSTCON_OTG_HLINK BIT(1) -#define EXYNOS_4x12_URSTCON_OTG_PHYLINK BIT(2) -#define EXYNOS_4x12_URSTCON_HOST_PHY BIT(3) -/* The following bit defines are presented in the - * order taken from the Exynos4412 reference manual. - * - * During experiments with the hardware and debugging - * it was determined that the hardware behaves contrary - * to the manual. - * - * The following bit values were chaned accordingly to the - * results of real hardware experiments. - */ -#define EXYNOS_4x12_URSTCON_PHY1 BIT(4) -#define EXYNOS_4x12_URSTCON_HSIC0 BIT(6) -#define EXYNOS_4x12_URSTCON_HSIC1 BIT(5) -#define EXYNOS_4x12_URSTCON_HOST_LINK_ALL BIT(7) -#define EXYNOS_4x12_URSTCON_HOST_LINK_P0 BIT(10) -#define EXYNOS_4x12_URSTCON_HOST_LINK_P1 BIT(9) -#define EXYNOS_4x12_URSTCON_HOST_LINK_P2 BIT(8) - -/* Isolation, configured in the power management unit */ -#define EXYNOS_4x12_USB_ISOL_OFFSET 0x704 -#define EXYNOS_4x12_USB_ISOL_OTG BIT(0) -#define EXYNOS_4x12_USB_ISOL_HSIC0_OFFSET 0x708 -#define EXYNOS_4x12_USB_ISOL_HSIC0 BIT(0) -#define EXYNOS_4x12_USB_ISOL_HSIC1_OFFSET 0x70c -#define EXYNOS_4x12_USB_ISOL_HSIC1 BIT(0) - -/* Mode switching SUB Device <-> Host */ -#define EXYNOS_4x12_MODE_SWITCH_OFFSET 0x21c -#define EXYNOS_4x12_MODE_SWITCH_MASK 1 -#define EXYNOS_4x12_MODE_SWITCH_DEVICE 0 -#define EXYNOS_4x12_MODE_SWITCH_HOST 1 - -enum exynos4x12_phy_id { - EXYNOS4x12_DEVICE, - EXYNOS4x12_HOST, - EXYNOS4x12_HSIC0, - EXYNOS4x12_HSIC1, - EXYNOS4x12_NUM_PHYS, -}; - -/* - * exynos4x12_rate_to_clk() converts the supplied clock rate to the value that - * can be written to the phy register. - */ -static int exynos4x12_rate_to_clk(unsigned long rate, u32 *reg) -{ - /* EXYNOS_4x12_UPHYCLK_PHYFSEL_MASK */ - - switch (rate) { - case 9600 * KHZ: - *reg = EXYNOS_4x12_UPHYCLK_PHYFSEL_9MHZ6; - break; - case 10 * MHZ: - *reg = EXYNOS_4x12_UPHYCLK_PHYFSEL_10MHZ; - break; - case 12 * MHZ: - *reg = EXYNOS_4x12_UPHYCLK_PHYFSEL_12MHZ; - break; - case 19200 * KHZ: - *reg = EXYNOS_4x12_UPHYCLK_PHYFSEL_19MHZ2; - break; - case 20 * MHZ: - *reg = EXYNOS_4x12_UPHYCLK_PHYFSEL_20MHZ; - break; - case 24 * MHZ: - *reg = EXYNOS_4x12_UPHYCLK_PHYFSEL_24MHZ; - break; - case 50 * MHZ: - *reg = EXYNOS_4x12_UPHYCLK_PHYFSEL_50MHZ; - break; - default: - return -EINVAL; - } - - return 0; -} - -static void exynos4x12_isol(struct samsung_usb2_phy_instance *inst, bool on) -{ - struct samsung_usb2_phy_driver *drv = inst->drv; - u32 offset; - u32 mask; - - switch (inst->cfg->id) { - case EXYNOS4x12_DEVICE: - case EXYNOS4x12_HOST: - offset = EXYNOS_4x12_USB_ISOL_OFFSET; - mask = EXYNOS_4x12_USB_ISOL_OTG; - break; - case EXYNOS4x12_HSIC0: - offset = EXYNOS_4x12_USB_ISOL_HSIC0_OFFSET; - mask = EXYNOS_4x12_USB_ISOL_HSIC0; - break; - case EXYNOS4x12_HSIC1: - offset = EXYNOS_4x12_USB_ISOL_HSIC1_OFFSET; - mask = EXYNOS_4x12_USB_ISOL_HSIC1; - break; - default: - return; - } - - regmap_update_bits(drv->reg_pmu, offset, mask, on ? 0 : mask); -} - -static void exynos4x12_setup_clk(struct samsung_usb2_phy_instance *inst) -{ - struct samsung_usb2_phy_driver *drv = inst->drv; - u32 clk; - - clk = readl(drv->reg_phy + EXYNOS_4x12_UPHYCLK); - clk &= ~EXYNOS_4x12_UPHYCLK_PHYFSEL_MASK; - - if (drv->cfg->has_refclk_sel) - clk = EXYNOS_3250_UPHYCLK_REFCLKSEL; - - clk |= drv->ref_reg_val << EXYNOS_4x12_UPHYCLK_PHYFSEL_OFFSET; - clk |= EXYNOS_4x12_UPHYCLK_PHY1_COMMON_ON; - writel(clk, drv->reg_phy + EXYNOS_4x12_UPHYCLK); -} - -static void exynos4x12_phy_pwr(struct samsung_usb2_phy_instance *inst, bool on) -{ - struct samsung_usb2_phy_driver *drv = inst->drv; - u32 rstbits = 0; - u32 phypwr = 0; - u32 rst; - u32 pwr; - - switch (inst->cfg->id) { - case EXYNOS4x12_DEVICE: - phypwr = EXYNOS_4x12_UPHYPWR_PHY0; - rstbits = EXYNOS_4x12_URSTCON_PHY0; - break; - case EXYNOS4x12_HOST: - phypwr = EXYNOS_4x12_UPHYPWR_PHY1; - rstbits = EXYNOS_4x12_URSTCON_HOST_PHY | - EXYNOS_4x12_URSTCON_PHY1 | - EXYNOS_4x12_URSTCON_HOST_LINK_P0; - break; - case EXYNOS4x12_HSIC0: - phypwr = EXYNOS_4x12_UPHYPWR_HSIC0; - rstbits = EXYNOS_4x12_URSTCON_HSIC0 | - EXYNOS_4x12_URSTCON_HOST_LINK_P1; - break; - case EXYNOS4x12_HSIC1: - phypwr = EXYNOS_4x12_UPHYPWR_HSIC1; - rstbits = EXYNOS_4x12_URSTCON_HSIC1 | - EXYNOS_4x12_URSTCON_HOST_LINK_P1; - break; - } - - if (on) { - pwr = readl(drv->reg_phy + EXYNOS_4x12_UPHYPWR); - pwr &= ~phypwr; - writel(pwr, drv->reg_phy + EXYNOS_4x12_UPHYPWR); - - rst = readl(drv->reg_phy + EXYNOS_4x12_UPHYRST); - rst |= rstbits; - writel(rst, drv->reg_phy + EXYNOS_4x12_UPHYRST); - udelay(10); - rst &= ~rstbits; - writel(rst, drv->reg_phy + EXYNOS_4x12_UPHYRST); - /* The following delay is necessary for the reset sequence to be - * completed */ - udelay(80); - } else { - pwr = readl(drv->reg_phy + EXYNOS_4x12_UPHYPWR); - pwr |= phypwr; - writel(pwr, drv->reg_phy + EXYNOS_4x12_UPHYPWR); - } -} - -static void exynos4x12_power_on_int(struct samsung_usb2_phy_instance *inst) -{ - if (inst->int_cnt++ > 0) - return; - - exynos4x12_setup_clk(inst); - exynos4x12_isol(inst, 0); - exynos4x12_phy_pwr(inst, 1); -} - -static int exynos4x12_power_on(struct samsung_usb2_phy_instance *inst) -{ - struct samsung_usb2_phy_driver *drv = inst->drv; - - if (inst->ext_cnt++ > 0) - return 0; - - if (inst->cfg->id == EXYNOS4x12_HOST) { - regmap_update_bits(drv->reg_sys, EXYNOS_4x12_MODE_SWITCH_OFFSET, - EXYNOS_4x12_MODE_SWITCH_MASK, - EXYNOS_4x12_MODE_SWITCH_HOST); - exynos4x12_power_on_int(&drv->instances[EXYNOS4x12_DEVICE]); - } - - if (inst->cfg->id == EXYNOS4x12_DEVICE && drv->cfg->has_mode_switch) - regmap_update_bits(drv->reg_sys, EXYNOS_4x12_MODE_SWITCH_OFFSET, - EXYNOS_4x12_MODE_SWITCH_MASK, - EXYNOS_4x12_MODE_SWITCH_DEVICE); - - if (inst->cfg->id == EXYNOS4x12_HSIC0 || - inst->cfg->id == EXYNOS4x12_HSIC1) { - exynos4x12_power_on_int(&drv->instances[EXYNOS4x12_DEVICE]); - exynos4x12_power_on_int(&drv->instances[EXYNOS4x12_HOST]); - } - - exynos4x12_power_on_int(inst); - - return 0; -} - -static void exynos4x12_power_off_int(struct samsung_usb2_phy_instance *inst) -{ - if (inst->int_cnt-- > 1) - return; - - exynos4x12_isol(inst, 1); - exynos4x12_phy_pwr(inst, 0); -} - -static int exynos4x12_power_off(struct samsung_usb2_phy_instance *inst) -{ - struct samsung_usb2_phy_driver *drv = inst->drv; - - if (inst->ext_cnt-- > 1) - return 0; - - if (inst->cfg->id == EXYNOS4x12_DEVICE && drv->cfg->has_mode_switch) - regmap_update_bits(drv->reg_sys, EXYNOS_4x12_MODE_SWITCH_OFFSET, - EXYNOS_4x12_MODE_SWITCH_MASK, - EXYNOS_4x12_MODE_SWITCH_HOST); - - if (inst->cfg->id == EXYNOS4x12_HOST) - exynos4x12_power_off_int(&drv->instances[EXYNOS4x12_DEVICE]); - - if (inst->cfg->id == EXYNOS4x12_HSIC0 || - inst->cfg->id == EXYNOS4x12_HSIC1) { - exynos4x12_power_off_int(&drv->instances[EXYNOS4x12_DEVICE]); - exynos4x12_power_off_int(&drv->instances[EXYNOS4x12_HOST]); - } - - exynos4x12_power_off_int(inst); - - return 0; -} - - -static const struct samsung_usb2_common_phy exynos4x12_phys[] = { - { - .label = "device", - .id = EXYNOS4x12_DEVICE, - .power_on = exynos4x12_power_on, - .power_off = exynos4x12_power_off, - }, - { - .label = "host", - .id = EXYNOS4x12_HOST, - .power_on = exynos4x12_power_on, - .power_off = exynos4x12_power_off, - }, - { - .label = "hsic0", - .id = EXYNOS4x12_HSIC0, - .power_on = exynos4x12_power_on, - .power_off = exynos4x12_power_off, - }, - { - .label = "hsic1", - .id = EXYNOS4x12_HSIC1, - .power_on = exynos4x12_power_on, - .power_off = exynos4x12_power_off, - }, -}; - -const struct samsung_usb2_phy_config exynos3250_usb2_phy_config = { - .has_refclk_sel = 1, - .num_phys = 1, - .phys = exynos4x12_phys, - .rate_to_clk = exynos4x12_rate_to_clk, -}; - -const struct samsung_usb2_phy_config exynos4x12_usb2_phy_config = { - .has_mode_switch = 1, - .num_phys = EXYNOS4x12_NUM_PHYS, - .phys = exynos4x12_phys, - .rate_to_clk = exynos4x12_rate_to_clk, -}; diff --git a/drivers/phy/phy-exynos5-usbdrd.c b/drivers/phy/phy-exynos5-usbdrd.c deleted file mode 100644 index 7c41daa2c625..000000000000 --- a/drivers/phy/phy-exynos5-usbdrd.c +++ /dev/null @@ -1,782 +0,0 @@ -/* - * Samsung EXYNOS5 SoC series USB DRD PHY driver - * - * Phy provider for USB 3.0 DRD controller on Exynos5 SoC series - * - * Copyright (C) 2014 Samsung Electronics Co., Ltd. - * Author: Vivek Gautam - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* Exynos USB PHY registers */ -#define EXYNOS5_FSEL_9MHZ6 0x0 -#define EXYNOS5_FSEL_10MHZ 0x1 -#define EXYNOS5_FSEL_12MHZ 0x2 -#define EXYNOS5_FSEL_19MHZ2 0x3 -#define EXYNOS5_FSEL_20MHZ 0x4 -#define EXYNOS5_FSEL_24MHZ 0x5 -#define EXYNOS5_FSEL_50MHZ 0x7 - -/* EXYNOS5: USB 3.0 DRD PHY registers */ -#define EXYNOS5_DRD_LINKSYSTEM 0x04 - -#define LINKSYSTEM_FLADJ_MASK (0x3f << 1) -#define LINKSYSTEM_FLADJ(_x) ((_x) << 1) -#define LINKSYSTEM_XHCI_VERSION_CONTROL BIT(27) - -#define EXYNOS5_DRD_PHYUTMI 0x08 - -#define PHYUTMI_OTGDISABLE BIT(6) -#define PHYUTMI_FORCESUSPEND BIT(1) -#define PHYUTMI_FORCESLEEP BIT(0) - -#define EXYNOS5_DRD_PHYPIPE 0x0c - -#define EXYNOS5_DRD_PHYCLKRST 0x10 - -#define PHYCLKRST_EN_UTMISUSPEND BIT(31) - -#define PHYCLKRST_SSC_REFCLKSEL_MASK (0xff << 23) -#define PHYCLKRST_SSC_REFCLKSEL(_x) ((_x) << 23) - -#define PHYCLKRST_SSC_RANGE_MASK (0x03 << 21) -#define PHYCLKRST_SSC_RANGE(_x) ((_x) << 21) - -#define PHYCLKRST_SSC_EN BIT(20) -#define PHYCLKRST_REF_SSP_EN BIT(19) -#define PHYCLKRST_REF_CLKDIV2 BIT(18) - -#define PHYCLKRST_MPLL_MULTIPLIER_MASK (0x7f << 11) -#define PHYCLKRST_MPLL_MULTIPLIER_100MHZ_REF (0x19 << 11) -#define PHYCLKRST_MPLL_MULTIPLIER_50M_REF (0x32 << 11) -#define PHYCLKRST_MPLL_MULTIPLIER_24MHZ_REF (0x68 << 11) -#define PHYCLKRST_MPLL_MULTIPLIER_20MHZ_REF (0x7d << 11) -#define PHYCLKRST_MPLL_MULTIPLIER_19200KHZ_REF (0x02 << 11) - -#define PHYCLKRST_FSEL_UTMI_MASK (0x7 << 5) -#define PHYCLKRST_FSEL_PIPE_MASK (0x7 << 8) -#define PHYCLKRST_FSEL(_x) ((_x) << 5) -#define PHYCLKRST_FSEL_PAD_100MHZ (0x27 << 5) -#define PHYCLKRST_FSEL_PAD_24MHZ (0x2a << 5) -#define PHYCLKRST_FSEL_PAD_20MHZ (0x31 << 5) -#define PHYCLKRST_FSEL_PAD_19_2MHZ (0x38 << 5) - -#define PHYCLKRST_RETENABLEN BIT(4) - -#define PHYCLKRST_REFCLKSEL_MASK (0x03 << 2) -#define PHYCLKRST_REFCLKSEL_PAD_REFCLK (0x2 << 2) -#define PHYCLKRST_REFCLKSEL_EXT_REFCLK (0x3 << 2) - -#define PHYCLKRST_PORTRESET BIT(1) -#define PHYCLKRST_COMMONONN BIT(0) - -#define EXYNOS5_DRD_PHYREG0 0x14 -#define EXYNOS5_DRD_PHYREG1 0x18 - -#define EXYNOS5_DRD_PHYPARAM0 0x1c - -#define PHYPARAM0_REF_USE_PAD BIT(31) -#define PHYPARAM0_REF_LOSLEVEL_MASK (0x1f << 26) -#define PHYPARAM0_REF_LOSLEVEL (0x9 << 26) - -#define EXYNOS5_DRD_PHYPARAM1 0x20 - -#define PHYPARAM1_PCS_TXDEEMPH_MASK (0x1f << 0) -#define PHYPARAM1_PCS_TXDEEMPH (0x1c) - -#define EXYNOS5_DRD_PHYTERM 0x24 - -#define EXYNOS5_DRD_PHYTEST 0x28 - -#define PHYTEST_POWERDOWN_SSP BIT(3) -#define PHYTEST_POWERDOWN_HSP BIT(2) - -#define EXYNOS5_DRD_PHYADP 0x2c - -#define EXYNOS5_DRD_PHYUTMICLKSEL 0x30 - -#define PHYUTMICLKSEL_UTMI_CLKSEL BIT(2) - -#define EXYNOS5_DRD_PHYRESUME 0x34 -#define EXYNOS5_DRD_LINKPORT 0x44 - -#define KHZ 1000 -#define MHZ (KHZ * KHZ) - -enum exynos5_usbdrd_phy_id { - EXYNOS5_DRDPHY_UTMI, - EXYNOS5_DRDPHY_PIPE3, - EXYNOS5_DRDPHYS_NUM, -}; - -struct phy_usb_instance; -struct exynos5_usbdrd_phy; - -struct exynos5_usbdrd_phy_config { - u32 id; - void (*phy_isol)(struct phy_usb_instance *inst, u32 on); - void (*phy_init)(struct exynos5_usbdrd_phy *phy_drd); - unsigned int (*set_refclk)(struct phy_usb_instance *inst); -}; - -struct exynos5_usbdrd_phy_drvdata { - const struct exynos5_usbdrd_phy_config *phy_cfg; - u32 pmu_offset_usbdrd0_phy; - u32 pmu_offset_usbdrd1_phy; - bool has_common_clk_gate; -}; - -/** - * struct exynos5_usbdrd_phy - driver data for USB 3.0 PHY - * @dev: pointer to device instance of this platform device - * @reg_phy: usb phy controller register memory base - * @clk: phy clock for register access - * @pipeclk: clock for pipe3 phy - * @utmiclk: clock for utmi+ phy - * @itpclk: clock for ITP generation - * @drv_data: pointer to SoC level driver data structure - * @phys[]: array for 'EXYNOS5_DRDPHYS_NUM' number of PHY - * instances each with its 'phy' and 'phy_cfg'. - * @extrefclk: frequency select settings when using 'separate - * reference clocks' for SS and HS operations - * @ref_clk: reference clock to PHY block from which PHY's - * operational clocks are derived - * vbus: VBUS regulator for phy - * vbus_boost: Boost regulator for VBUS present on few Exynos boards - */ -struct exynos5_usbdrd_phy { - struct device *dev; - void __iomem *reg_phy; - struct clk *clk; - struct clk *pipeclk; - struct clk *utmiclk; - struct clk *itpclk; - const struct exynos5_usbdrd_phy_drvdata *drv_data; - struct phy_usb_instance { - struct phy *phy; - u32 index; - struct regmap *reg_pmu; - u32 pmu_offset; - const struct exynos5_usbdrd_phy_config *phy_cfg; - } phys[EXYNOS5_DRDPHYS_NUM]; - u32 extrefclk; - struct clk *ref_clk; - struct regulator *vbus; - struct regulator *vbus_boost; -}; - -static inline -struct exynos5_usbdrd_phy *to_usbdrd_phy(struct phy_usb_instance *inst) -{ - return container_of((inst), struct exynos5_usbdrd_phy, - phys[(inst)->index]); -} - -/* - * exynos5_rate_to_clk() converts the supplied clock rate to the value that - * can be written to the phy register. - */ -static unsigned int exynos5_rate_to_clk(unsigned long rate, u32 *reg) -{ - /* EXYNOS5_FSEL_MASK */ - - switch (rate) { - case 9600 * KHZ: - *reg = EXYNOS5_FSEL_9MHZ6; - break; - case 10 * MHZ: - *reg = EXYNOS5_FSEL_10MHZ; - break; - case 12 * MHZ: - *reg = EXYNOS5_FSEL_12MHZ; - break; - case 19200 * KHZ: - *reg = EXYNOS5_FSEL_19MHZ2; - break; - case 20 * MHZ: - *reg = EXYNOS5_FSEL_20MHZ; - break; - case 24 * MHZ: - *reg = EXYNOS5_FSEL_24MHZ; - break; - case 50 * MHZ: - *reg = EXYNOS5_FSEL_50MHZ; - break; - default: - return -EINVAL; - } - - return 0; -} - -static void exynos5_usbdrd_phy_isol(struct phy_usb_instance *inst, - unsigned int on) -{ - unsigned int val; - - if (!inst->reg_pmu) - return; - - val = on ? 0 : EXYNOS4_PHY_ENABLE; - - regmap_update_bits(inst->reg_pmu, inst->pmu_offset, - EXYNOS4_PHY_ENABLE, val); -} - -/* - * Sets the pipe3 phy's clk as EXTREFCLK (XXTI) which is internal clock - * from clock core. Further sets multiplier values and spread spectrum - * clock settings for SuperSpeed operations. - */ -static unsigned int -exynos5_usbdrd_pipe3_set_refclk(struct phy_usb_instance *inst) -{ - u32 reg; - struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst); - - /* restore any previous reference clock settings */ - reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYCLKRST); - - /* Use EXTREFCLK as ref clock */ - reg &= ~PHYCLKRST_REFCLKSEL_MASK; - reg |= PHYCLKRST_REFCLKSEL_EXT_REFCLK; - - /* FSEL settings corresponding to reference clock */ - reg &= ~PHYCLKRST_FSEL_PIPE_MASK | - PHYCLKRST_MPLL_MULTIPLIER_MASK | - PHYCLKRST_SSC_REFCLKSEL_MASK; - switch (phy_drd->extrefclk) { - case EXYNOS5_FSEL_50MHZ: - reg |= (PHYCLKRST_MPLL_MULTIPLIER_50M_REF | - PHYCLKRST_SSC_REFCLKSEL(0x00)); - break; - case EXYNOS5_FSEL_24MHZ: - reg |= (PHYCLKRST_MPLL_MULTIPLIER_24MHZ_REF | - PHYCLKRST_SSC_REFCLKSEL(0x88)); - break; - case EXYNOS5_FSEL_20MHZ: - reg |= (PHYCLKRST_MPLL_MULTIPLIER_20MHZ_REF | - PHYCLKRST_SSC_REFCLKSEL(0x00)); - break; - case EXYNOS5_FSEL_19MHZ2: - reg |= (PHYCLKRST_MPLL_MULTIPLIER_19200KHZ_REF | - PHYCLKRST_SSC_REFCLKSEL(0x88)); - break; - default: - dev_dbg(phy_drd->dev, "unsupported ref clk\n"); - break; - } - - return reg; -} - -/* - * Sets the utmi phy's clk as EXTREFCLK (XXTI) which is internal clock - * from clock core. Further sets the FSEL values for HighSpeed operations. - */ -static unsigned int -exynos5_usbdrd_utmi_set_refclk(struct phy_usb_instance *inst) -{ - u32 reg; - struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst); - - /* restore any previous reference clock settings */ - reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYCLKRST); - - reg &= ~PHYCLKRST_REFCLKSEL_MASK; - reg |= PHYCLKRST_REFCLKSEL_EXT_REFCLK; - - reg &= ~PHYCLKRST_FSEL_UTMI_MASK | - PHYCLKRST_MPLL_MULTIPLIER_MASK | - PHYCLKRST_SSC_REFCLKSEL_MASK; - reg |= PHYCLKRST_FSEL(phy_drd->extrefclk); - - return reg; -} - -static void exynos5_usbdrd_pipe3_init(struct exynos5_usbdrd_phy *phy_drd) -{ - u32 reg; - - reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM1); - /* Set Tx De-Emphasis level */ - reg &= ~PHYPARAM1_PCS_TXDEEMPH_MASK; - reg |= PHYPARAM1_PCS_TXDEEMPH; - writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM1); - - reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYTEST); - reg &= ~PHYTEST_POWERDOWN_SSP; - writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYTEST); -} - -static void exynos5_usbdrd_utmi_init(struct exynos5_usbdrd_phy *phy_drd) -{ - u32 reg; - - reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM0); - /* Set Loss-of-Signal Detector sensitivity */ - reg &= ~PHYPARAM0_REF_LOSLEVEL_MASK; - reg |= PHYPARAM0_REF_LOSLEVEL; - writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM0); - - reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM1); - /* Set Tx De-Emphasis level */ - reg &= ~PHYPARAM1_PCS_TXDEEMPH_MASK; - reg |= PHYPARAM1_PCS_TXDEEMPH; - writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM1); - - /* UTMI Power Control */ - writel(PHYUTMI_OTGDISABLE, phy_drd->reg_phy + EXYNOS5_DRD_PHYUTMI); - - reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYTEST); - reg &= ~PHYTEST_POWERDOWN_HSP; - writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYTEST); -} - -static int exynos5_usbdrd_phy_init(struct phy *phy) -{ - int ret; - u32 reg; - struct phy_usb_instance *inst = phy_get_drvdata(phy); - struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst); - - ret = clk_prepare_enable(phy_drd->clk); - if (ret) - return ret; - - /* Reset USB 3.0 PHY */ - writel(0x0, phy_drd->reg_phy + EXYNOS5_DRD_PHYREG0); - writel(0x0, phy_drd->reg_phy + EXYNOS5_DRD_PHYRESUME); - - /* - * Setting the Frame length Adj value[6:1] to default 0x20 - * See xHCI 1.0 spec, 5.2.4 - */ - reg = LINKSYSTEM_XHCI_VERSION_CONTROL | - LINKSYSTEM_FLADJ(0x20); - writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_LINKSYSTEM); - - reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM0); - /* Select PHY CLK source */ - reg &= ~PHYPARAM0_REF_USE_PAD; - writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM0); - - /* This bit must be set for both HS and SS operations */ - reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYUTMICLKSEL); - reg |= PHYUTMICLKSEL_UTMI_CLKSEL; - writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYUTMICLKSEL); - - /* UTMI or PIPE3 specific init */ - inst->phy_cfg->phy_init(phy_drd); - - /* reference clock settings */ - reg = inst->phy_cfg->set_refclk(inst); - - /* Digital power supply in normal operating mode */ - reg |= PHYCLKRST_RETENABLEN | - /* Enable ref clock for SS function */ - PHYCLKRST_REF_SSP_EN | - /* Enable spread spectrum */ - PHYCLKRST_SSC_EN | - /* Power down HS Bias and PLL blocks in suspend mode */ - PHYCLKRST_COMMONONN | - /* Reset the port */ - PHYCLKRST_PORTRESET; - - writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYCLKRST); - - udelay(10); - - reg &= ~PHYCLKRST_PORTRESET; - writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYCLKRST); - - clk_disable_unprepare(phy_drd->clk); - - return 0; -} - -static int exynos5_usbdrd_phy_exit(struct phy *phy) -{ - int ret; - u32 reg; - struct phy_usb_instance *inst = phy_get_drvdata(phy); - struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst); - - ret = clk_prepare_enable(phy_drd->clk); - if (ret) - return ret; - - reg = PHYUTMI_OTGDISABLE | - PHYUTMI_FORCESUSPEND | - PHYUTMI_FORCESLEEP; - writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYUTMI); - - /* Resetting the PHYCLKRST enable bits to reduce leakage current */ - reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYCLKRST); - reg &= ~(PHYCLKRST_REF_SSP_EN | - PHYCLKRST_SSC_EN | - PHYCLKRST_COMMONONN); - writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYCLKRST); - - /* Control PHYTEST to remove leakage current */ - reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYTEST); - reg |= PHYTEST_POWERDOWN_SSP | - PHYTEST_POWERDOWN_HSP; - writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYTEST); - - clk_disable_unprepare(phy_drd->clk); - - return 0; -} - -static int exynos5_usbdrd_phy_power_on(struct phy *phy) -{ - int ret; - struct phy_usb_instance *inst = phy_get_drvdata(phy); - struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst); - - dev_dbg(phy_drd->dev, "Request to power_on usbdrd_phy phy\n"); - - clk_prepare_enable(phy_drd->ref_clk); - if (!phy_drd->drv_data->has_common_clk_gate) { - clk_prepare_enable(phy_drd->pipeclk); - clk_prepare_enable(phy_drd->utmiclk); - clk_prepare_enable(phy_drd->itpclk); - } - - /* Enable VBUS supply */ - if (phy_drd->vbus_boost) { - ret = regulator_enable(phy_drd->vbus_boost); - if (ret) { - dev_err(phy_drd->dev, - "Failed to enable VBUS boost supply\n"); - goto fail_vbus; - } - } - - if (phy_drd->vbus) { - ret = regulator_enable(phy_drd->vbus); - if (ret) { - dev_err(phy_drd->dev, "Failed to enable VBUS supply\n"); - goto fail_vbus_boost; - } - } - - /* Power-on PHY*/ - inst->phy_cfg->phy_isol(inst, 0); - - return 0; - -fail_vbus_boost: - if (phy_drd->vbus_boost) - regulator_disable(phy_drd->vbus_boost); - -fail_vbus: - clk_disable_unprepare(phy_drd->ref_clk); - if (!phy_drd->drv_data->has_common_clk_gate) { - clk_disable_unprepare(phy_drd->itpclk); - clk_disable_unprepare(phy_drd->utmiclk); - clk_disable_unprepare(phy_drd->pipeclk); - } - - return ret; -} - -static int exynos5_usbdrd_phy_power_off(struct phy *phy) -{ - struct phy_usb_instance *inst = phy_get_drvdata(phy); - struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst); - - dev_dbg(phy_drd->dev, "Request to power_off usbdrd_phy phy\n"); - - /* Power-off the PHY */ - inst->phy_cfg->phy_isol(inst, 1); - - /* Disable VBUS supply */ - if (phy_drd->vbus) - regulator_disable(phy_drd->vbus); - if (phy_drd->vbus_boost) - regulator_disable(phy_drd->vbus_boost); - - clk_disable_unprepare(phy_drd->ref_clk); - if (!phy_drd->drv_data->has_common_clk_gate) { - clk_disable_unprepare(phy_drd->itpclk); - clk_disable_unprepare(phy_drd->pipeclk); - clk_disable_unprepare(phy_drd->utmiclk); - } - - return 0; -} - -static struct phy *exynos5_usbdrd_phy_xlate(struct device *dev, - struct of_phandle_args *args) -{ - struct exynos5_usbdrd_phy *phy_drd = dev_get_drvdata(dev); - - if (WARN_ON(args->args[0] >= EXYNOS5_DRDPHYS_NUM)) - return ERR_PTR(-ENODEV); - - return phy_drd->phys[args->args[0]].phy; -} - -static const struct phy_ops exynos5_usbdrd_phy_ops = { - .init = exynos5_usbdrd_phy_init, - .exit = exynos5_usbdrd_phy_exit, - .power_on = exynos5_usbdrd_phy_power_on, - .power_off = exynos5_usbdrd_phy_power_off, - .owner = THIS_MODULE, -}; - -static int exynos5_usbdrd_phy_clk_handle(struct exynos5_usbdrd_phy *phy_drd) -{ - unsigned long ref_rate; - int ret; - - phy_drd->clk = devm_clk_get(phy_drd->dev, "phy"); - if (IS_ERR(phy_drd->clk)) { - dev_err(phy_drd->dev, "Failed to get phy clock\n"); - return PTR_ERR(phy_drd->clk); - } - - phy_drd->ref_clk = devm_clk_get(phy_drd->dev, "ref"); - if (IS_ERR(phy_drd->ref_clk)) { - dev_err(phy_drd->dev, "Failed to get phy reference clock\n"); - return PTR_ERR(phy_drd->ref_clk); - } - ref_rate = clk_get_rate(phy_drd->ref_clk); - - ret = exynos5_rate_to_clk(ref_rate, &phy_drd->extrefclk); - if (ret) { - dev_err(phy_drd->dev, "Clock rate (%ld) not supported\n", - ref_rate); - return ret; - } - - if (!phy_drd->drv_data->has_common_clk_gate) { - phy_drd->pipeclk = devm_clk_get(phy_drd->dev, "phy_pipe"); - if (IS_ERR(phy_drd->pipeclk)) { - dev_info(phy_drd->dev, - "PIPE3 phy operational clock not specified\n"); - phy_drd->pipeclk = NULL; - } - - phy_drd->utmiclk = devm_clk_get(phy_drd->dev, "phy_utmi"); - if (IS_ERR(phy_drd->utmiclk)) { - dev_info(phy_drd->dev, - "UTMI phy operational clock not specified\n"); - phy_drd->utmiclk = NULL; - } - - phy_drd->itpclk = devm_clk_get(phy_drd->dev, "itp"); - if (IS_ERR(phy_drd->itpclk)) { - dev_info(phy_drd->dev, - "ITP clock from main OSC not specified\n"); - phy_drd->itpclk = NULL; - } - } - - return 0; -} - -static const struct exynos5_usbdrd_phy_config phy_cfg_exynos5[] = { - { - .id = EXYNOS5_DRDPHY_UTMI, - .phy_isol = exynos5_usbdrd_phy_isol, - .phy_init = exynos5_usbdrd_utmi_init, - .set_refclk = exynos5_usbdrd_utmi_set_refclk, - }, - { - .id = EXYNOS5_DRDPHY_PIPE3, - .phy_isol = exynos5_usbdrd_phy_isol, - .phy_init = exynos5_usbdrd_pipe3_init, - .set_refclk = exynos5_usbdrd_pipe3_set_refclk, - }, -}; - -static const struct exynos5_usbdrd_phy_drvdata exynos5420_usbdrd_phy = { - .phy_cfg = phy_cfg_exynos5, - .pmu_offset_usbdrd0_phy = EXYNOS5_USBDRD_PHY_CONTROL, - .pmu_offset_usbdrd1_phy = EXYNOS5420_USBDRD1_PHY_CONTROL, - .has_common_clk_gate = true, -}; - -static const struct exynos5_usbdrd_phy_drvdata exynos5250_usbdrd_phy = { - .phy_cfg = phy_cfg_exynos5, - .pmu_offset_usbdrd0_phy = EXYNOS5_USBDRD_PHY_CONTROL, - .has_common_clk_gate = true, -}; - -static const struct exynos5_usbdrd_phy_drvdata exynos5433_usbdrd_phy = { - .phy_cfg = phy_cfg_exynos5, - .pmu_offset_usbdrd0_phy = EXYNOS5_USBDRD_PHY_CONTROL, - .pmu_offset_usbdrd1_phy = EXYNOS5433_USBHOST30_PHY_CONTROL, - .has_common_clk_gate = false, -}; - -static const struct exynos5_usbdrd_phy_drvdata exynos7_usbdrd_phy = { - .phy_cfg = phy_cfg_exynos5, - .pmu_offset_usbdrd0_phy = EXYNOS5_USBDRD_PHY_CONTROL, - .has_common_clk_gate = false, -}; - -static const struct of_device_id exynos5_usbdrd_phy_of_match[] = { - { - .compatible = "samsung,exynos5250-usbdrd-phy", - .data = &exynos5250_usbdrd_phy - }, { - .compatible = "samsung,exynos5420-usbdrd-phy", - .data = &exynos5420_usbdrd_phy - }, { - .compatible = "samsung,exynos5433-usbdrd-phy", - .data = &exynos5433_usbdrd_phy - }, { - .compatible = "samsung,exynos7-usbdrd-phy", - .data = &exynos7_usbdrd_phy - }, - { }, -}; -MODULE_DEVICE_TABLE(of, exynos5_usbdrd_phy_of_match); - -static int exynos5_usbdrd_phy_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct device_node *node = dev->of_node; - struct exynos5_usbdrd_phy *phy_drd; - struct phy_provider *phy_provider; - struct resource *res; - const struct of_device_id *match; - const struct exynos5_usbdrd_phy_drvdata *drv_data; - struct regmap *reg_pmu; - u32 pmu_offset; - int i, ret; - int channel; - - phy_drd = devm_kzalloc(dev, sizeof(*phy_drd), GFP_KERNEL); - if (!phy_drd) - return -ENOMEM; - - dev_set_drvdata(dev, phy_drd); - phy_drd->dev = dev; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - phy_drd->reg_phy = devm_ioremap_resource(dev, res); - if (IS_ERR(phy_drd->reg_phy)) - return PTR_ERR(phy_drd->reg_phy); - - match = of_match_node(exynos5_usbdrd_phy_of_match, pdev->dev.of_node); - - drv_data = match->data; - phy_drd->drv_data = drv_data; - - ret = exynos5_usbdrd_phy_clk_handle(phy_drd); - if (ret) { - dev_err(dev, "Failed to initialize clocks\n"); - return ret; - } - - reg_pmu = syscon_regmap_lookup_by_phandle(dev->of_node, - "samsung,pmu-syscon"); - if (IS_ERR(reg_pmu)) { - dev_err(dev, "Failed to lookup PMU regmap\n"); - return PTR_ERR(reg_pmu); - } - - /* - * Exynos5420 SoC has multiple channels for USB 3.0 PHY, with - * each having separate power control registers. - * 'channel' facilitates to set such registers. - */ - channel = of_alias_get_id(node, "usbdrdphy"); - if (channel < 0) - dev_dbg(dev, "Not a multi-controller usbdrd phy\n"); - - switch (channel) { - case 1: - pmu_offset = phy_drd->drv_data->pmu_offset_usbdrd1_phy; - break; - case 0: - default: - pmu_offset = phy_drd->drv_data->pmu_offset_usbdrd0_phy; - break; - } - - /* Get Vbus regulators */ - phy_drd->vbus = devm_regulator_get(dev, "vbus"); - if (IS_ERR(phy_drd->vbus)) { - ret = PTR_ERR(phy_drd->vbus); - if (ret == -EPROBE_DEFER) - return ret; - - dev_warn(dev, "Failed to get VBUS supply regulator\n"); - phy_drd->vbus = NULL; - } - - phy_drd->vbus_boost = devm_regulator_get(dev, "vbus-boost"); - if (IS_ERR(phy_drd->vbus_boost)) { - ret = PTR_ERR(phy_drd->vbus_boost); - if (ret == -EPROBE_DEFER) - return ret; - - dev_warn(dev, "Failed to get VBUS boost supply regulator\n"); - phy_drd->vbus_boost = NULL; - } - - dev_vdbg(dev, "Creating usbdrd_phy phy\n"); - - for (i = 0; i < EXYNOS5_DRDPHYS_NUM; i++) { - struct phy *phy = devm_phy_create(dev, NULL, - &exynos5_usbdrd_phy_ops); - if (IS_ERR(phy)) { - dev_err(dev, "Failed to create usbdrd_phy phy\n"); - return PTR_ERR(phy); - } - - phy_drd->phys[i].phy = phy; - phy_drd->phys[i].index = i; - phy_drd->phys[i].reg_pmu = reg_pmu; - phy_drd->phys[i].pmu_offset = pmu_offset; - phy_drd->phys[i].phy_cfg = &drv_data->phy_cfg[i]; - phy_set_drvdata(phy, &phy_drd->phys[i]); - } - - phy_provider = devm_of_phy_provider_register(dev, - exynos5_usbdrd_phy_xlate); - if (IS_ERR(phy_provider)) { - dev_err(phy_drd->dev, "Failed to register phy provider\n"); - return PTR_ERR(phy_provider); - } - - return 0; -} - -static struct platform_driver exynos5_usb3drd_phy = { - .probe = exynos5_usbdrd_phy_probe, - .driver = { - .of_match_table = exynos5_usbdrd_phy_of_match, - .name = "exynos5_usb3drd_phy", - } -}; - -module_platform_driver(exynos5_usb3drd_phy); -MODULE_DESCRIPTION("Samsung EXYNOS5 SoCs USB 3.0 DRD controller PHY driver"); -MODULE_AUTHOR("Vivek Gautam "); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:exynos5_usb3drd_phy"); diff --git a/drivers/phy/phy-exynos5250-sata.c b/drivers/phy/phy-exynos5250-sata.c deleted file mode 100644 index 60e13afcd9b8..000000000000 --- a/drivers/phy/phy-exynos5250-sata.c +++ /dev/null @@ -1,250 +0,0 @@ -/* - * Samsung SATA SerDes(PHY) driver - * - * Copyright (C) 2013 Samsung Electronics Co., Ltd. - * Authors: Girish K S - * Yuvaraj Kumar C D - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define SATAPHY_CONTROL_OFFSET 0x0724 -#define EXYNOS5_SATAPHY_PMU_ENABLE BIT(0) -#define EXYNOS5_SATA_RESET 0x4 -#define RESET_GLOBAL_RST_N BIT(0) -#define RESET_CMN_RST_N BIT(1) -#define RESET_CMN_BLOCK_RST_N BIT(2) -#define RESET_CMN_I2C_RST_N BIT(3) -#define RESET_TX_RX_PIPE_RST_N BIT(4) -#define RESET_TX_RX_BLOCK_RST_N BIT(5) -#define RESET_TX_RX_I2C_RST_N (BIT(6) | BIT(7)) -#define LINK_RESET 0xf0000 -#define EXYNOS5_SATA_MODE0 0x10 -#define SATA_SPD_GEN3 BIT(1) -#define EXYNOS5_SATA_CTRL0 0x14 -#define CTRL0_P0_PHY_CALIBRATED_SEL BIT(9) -#define CTRL0_P0_PHY_CALIBRATED BIT(8) -#define EXYNOS5_SATA_PHSATA_CTRLM 0xe0 -#define PHCTRLM_REF_RATE BIT(1) -#define PHCTRLM_HIGH_SPEED BIT(0) -#define EXYNOS5_SATA_PHSATA_STATM 0xf0 -#define PHSTATM_PLL_LOCKED BIT(0) - -#define PHY_PLL_TIMEOUT (usecs_to_jiffies(1000)) - -struct exynos_sata_phy { - struct phy *phy; - struct clk *phyclk; - void __iomem *regs; - struct regmap *pmureg; - struct i2c_client *client; -}; - -static int wait_for_reg_status(void __iomem *base, u32 reg, u32 checkbit, - u32 status) -{ - unsigned long timeout = jiffies + PHY_PLL_TIMEOUT; - - while (time_before(jiffies, timeout)) { - if ((readl(base + reg) & checkbit) == status) - return 0; - } - - return -EFAULT; -} - -static int exynos_sata_phy_power_on(struct phy *phy) -{ - struct exynos_sata_phy *sata_phy = phy_get_drvdata(phy); - - return regmap_update_bits(sata_phy->pmureg, SATAPHY_CONTROL_OFFSET, - EXYNOS5_SATAPHY_PMU_ENABLE, true); - -} - -static int exynos_sata_phy_power_off(struct phy *phy) -{ - struct exynos_sata_phy *sata_phy = phy_get_drvdata(phy); - - return regmap_update_bits(sata_phy->pmureg, SATAPHY_CONTROL_OFFSET, - EXYNOS5_SATAPHY_PMU_ENABLE, false); - -} - -static int exynos_sata_phy_init(struct phy *phy) -{ - u32 val = 0; - int ret = 0; - u8 buf[] = { 0x3a, 0x0b }; - struct exynos_sata_phy *sata_phy = phy_get_drvdata(phy); - - ret = regmap_update_bits(sata_phy->pmureg, SATAPHY_CONTROL_OFFSET, - EXYNOS5_SATAPHY_PMU_ENABLE, true); - if (ret != 0) - dev_err(&sata_phy->phy->dev, "phy init failed\n"); - - writel(val, sata_phy->regs + EXYNOS5_SATA_RESET); - - val = readl(sata_phy->regs + EXYNOS5_SATA_RESET); - val |= RESET_GLOBAL_RST_N | RESET_CMN_RST_N | RESET_CMN_BLOCK_RST_N - | RESET_CMN_I2C_RST_N | RESET_TX_RX_PIPE_RST_N - | RESET_TX_RX_BLOCK_RST_N | RESET_TX_RX_I2C_RST_N; - writel(val, sata_phy->regs + EXYNOS5_SATA_RESET); - - val = readl(sata_phy->regs + EXYNOS5_SATA_RESET); - val |= LINK_RESET; - writel(val, sata_phy->regs + EXYNOS5_SATA_RESET); - - val = readl(sata_phy->regs + EXYNOS5_SATA_RESET); - val |= RESET_CMN_RST_N; - writel(val, sata_phy->regs + EXYNOS5_SATA_RESET); - - val = readl(sata_phy->regs + EXYNOS5_SATA_PHSATA_CTRLM); - val &= ~PHCTRLM_REF_RATE; - writel(val, sata_phy->regs + EXYNOS5_SATA_PHSATA_CTRLM); - - /* High speed enable for Gen3 */ - val = readl(sata_phy->regs + EXYNOS5_SATA_PHSATA_CTRLM); - val |= PHCTRLM_HIGH_SPEED; - writel(val, sata_phy->regs + EXYNOS5_SATA_PHSATA_CTRLM); - - val = readl(sata_phy->regs + EXYNOS5_SATA_CTRL0); - val |= CTRL0_P0_PHY_CALIBRATED_SEL | CTRL0_P0_PHY_CALIBRATED; - writel(val, sata_phy->regs + EXYNOS5_SATA_CTRL0); - - val = readl(sata_phy->regs + EXYNOS5_SATA_MODE0); - val |= SATA_SPD_GEN3; - writel(val, sata_phy->regs + EXYNOS5_SATA_MODE0); - - ret = i2c_master_send(sata_phy->client, buf, sizeof(buf)); - if (ret < 0) - return ret; - - /* release cmu reset */ - val = readl(sata_phy->regs + EXYNOS5_SATA_RESET); - val &= ~RESET_CMN_RST_N; - writel(val, sata_phy->regs + EXYNOS5_SATA_RESET); - - val = readl(sata_phy->regs + EXYNOS5_SATA_RESET); - val |= RESET_CMN_RST_N; - writel(val, sata_phy->regs + EXYNOS5_SATA_RESET); - - ret = wait_for_reg_status(sata_phy->regs, - EXYNOS5_SATA_PHSATA_STATM, - PHSTATM_PLL_LOCKED, 1); - if (ret < 0) - dev_err(&sata_phy->phy->dev, - "PHY PLL locking failed\n"); - return ret; -} - -static const struct phy_ops exynos_sata_phy_ops = { - .init = exynos_sata_phy_init, - .power_on = exynos_sata_phy_power_on, - .power_off = exynos_sata_phy_power_off, - .owner = THIS_MODULE, -}; - -static int exynos_sata_phy_probe(struct platform_device *pdev) -{ - struct exynos_sata_phy *sata_phy; - struct device *dev = &pdev->dev; - struct resource *res; - struct phy_provider *phy_provider; - struct device_node *node; - int ret = 0; - - sata_phy = devm_kzalloc(dev, sizeof(*sata_phy), GFP_KERNEL); - if (!sata_phy) - return -ENOMEM; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - - sata_phy->regs = devm_ioremap_resource(dev, res); - if (IS_ERR(sata_phy->regs)) - return PTR_ERR(sata_phy->regs); - - sata_phy->pmureg = syscon_regmap_lookup_by_phandle(dev->of_node, - "samsung,syscon-phandle"); - if (IS_ERR(sata_phy->pmureg)) { - dev_err(dev, "syscon regmap lookup failed.\n"); - return PTR_ERR(sata_phy->pmureg); - } - - node = of_parse_phandle(dev->of_node, - "samsung,exynos-sataphy-i2c-phandle", 0); - if (!node) - return -EINVAL; - - sata_phy->client = of_find_i2c_device_by_node(node); - if (!sata_phy->client) - return -EPROBE_DEFER; - - dev_set_drvdata(dev, sata_phy); - - sata_phy->phyclk = devm_clk_get(dev, "sata_phyctrl"); - if (IS_ERR(sata_phy->phyclk)) { - dev_err(dev, "failed to get clk for PHY\n"); - return PTR_ERR(sata_phy->phyclk); - } - - ret = clk_prepare_enable(sata_phy->phyclk); - if (ret < 0) { - dev_err(dev, "failed to enable source clk\n"); - return ret; - } - - sata_phy->phy = devm_phy_create(dev, NULL, &exynos_sata_phy_ops); - if (IS_ERR(sata_phy->phy)) { - clk_disable_unprepare(sata_phy->phyclk); - dev_err(dev, "failed to create PHY\n"); - return PTR_ERR(sata_phy->phy); - } - - phy_set_drvdata(sata_phy->phy, sata_phy); - - phy_provider = devm_of_phy_provider_register(dev, - of_phy_simple_xlate); - if (IS_ERR(phy_provider)) { - clk_disable_unprepare(sata_phy->phyclk); - return PTR_ERR(phy_provider); - } - - return 0; -} - -static const struct of_device_id exynos_sata_phy_of_match[] = { - { .compatible = "samsung,exynos5250-sata-phy" }, - { }, -}; -MODULE_DEVICE_TABLE(of, exynos_sata_phy_of_match); - -static struct platform_driver exynos_sata_phy_driver = { - .probe = exynos_sata_phy_probe, - .driver = { - .of_match_table = exynos_sata_phy_of_match, - .name = "samsung,sata-phy", - } -}; -module_platform_driver(exynos_sata_phy_driver); - -MODULE_DESCRIPTION("Samsung SerDes PHY driver"); -MODULE_LICENSE("GPL v2"); -MODULE_AUTHOR("Girish K S "); -MODULE_AUTHOR("Yuvaraj C D "); diff --git a/drivers/phy/phy-exynos5250-usb2.c b/drivers/phy/phy-exynos5250-usb2.c deleted file mode 100644 index aad806272305..000000000000 --- a/drivers/phy/phy-exynos5250-usb2.c +++ /dev/null @@ -1,401 +0,0 @@ -/* - * Samsung SoC USB 1.1/2.0 PHY driver - Exynos 5250 support - * - * Copyright (C) 2013 Samsung Electronics Co., Ltd. - * Author: Kamil Debski - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include "phy-samsung-usb2.h" - -/* Exynos USB PHY registers */ -#define EXYNOS_5250_REFCLKSEL_CRYSTAL 0x0 -#define EXYNOS_5250_REFCLKSEL_XO 0x1 -#define EXYNOS_5250_REFCLKSEL_CLKCORE 0x2 - -#define EXYNOS_5250_FSEL_9MHZ6 0x0 -#define EXYNOS_5250_FSEL_10MHZ 0x1 -#define EXYNOS_5250_FSEL_12MHZ 0x2 -#define EXYNOS_5250_FSEL_19MHZ2 0x3 -#define EXYNOS_5250_FSEL_20MHZ 0x4 -#define EXYNOS_5250_FSEL_24MHZ 0x5 -#define EXYNOS_5250_FSEL_50MHZ 0x7 - -/* Normal host */ -#define EXYNOS_5250_HOSTPHYCTRL0 0x0 - -#define EXYNOS_5250_HOSTPHYCTRL0_PHYSWRSTALL BIT(31) -#define EXYNOS_5250_HOSTPHYCTRL0_REFCLKSEL_SHIFT 19 -#define EXYNOS_5250_HOSTPHYCTRL0_REFCLKSEL_MASK \ - (0x3 << EXYNOS_5250_HOSTPHYCTRL0_REFCLKSEL_SHIFT) -#define EXYNOS_5250_HOSTPHYCTRL0_FSEL_SHIFT 16 -#define EXYNOS_5250_HOSTPHYCTRL0_FSEL_MASK \ - (0x7 << EXYNOS_5250_HOSTPHYCTRL0_FSEL_SHIFT) -#define EXYNOS_5250_HOSTPHYCTRL0_TESTBURNIN BIT(11) -#define EXYNOS_5250_HOSTPHYCTRL0_RETENABLE BIT(10) -#define EXYNOS_5250_HOSTPHYCTRL0_COMMON_ON_N BIT(9) -#define EXYNOS_5250_HOSTPHYCTRL0_VATESTENB_MASK (0x3 << 7) -#define EXYNOS_5250_HOSTPHYCTRL0_VATESTENB_DUAL (0x0 << 7) -#define EXYNOS_5250_HOSTPHYCTRL0_VATESTENB_ID0 (0x1 << 7) -#define EXYNOS_5250_HOSTPHYCTRL0_VATESTENB_ANALOGTEST (0x2 << 7) -#define EXYNOS_5250_HOSTPHYCTRL0_SIDDQ BIT(6) -#define EXYNOS_5250_HOSTPHYCTRL0_FORCESLEEP BIT(5) -#define EXYNOS_5250_HOSTPHYCTRL0_FORCESUSPEND BIT(4) -#define EXYNOS_5250_HOSTPHYCTRL0_WORDINTERFACE BIT(3) -#define EXYNOS_5250_HOSTPHYCTRL0_UTMISWRST BIT(2) -#define EXYNOS_5250_HOSTPHYCTRL0_LINKSWRST BIT(1) -#define EXYNOS_5250_HOSTPHYCTRL0_PHYSWRST BIT(0) - -/* HSIC0 & HSIC1 */ -#define EXYNOS_5250_HSICPHYCTRL1 0x10 -#define EXYNOS_5250_HSICPHYCTRL2 0x20 - -#define EXYNOS_5250_HSICPHYCTRLX_REFCLKSEL_MASK (0x3 << 23) -#define EXYNOS_5250_HSICPHYCTRLX_REFCLKSEL_DEFAULT (0x2 << 23) -#define EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_MASK (0x7f << 16) -#define EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_12 (0x24 << 16) -#define EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_15 (0x1c << 16) -#define EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_16 (0x1a << 16) -#define EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_19_2 (0x15 << 16) -#define EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_20 (0x14 << 16) -#define EXYNOS_5250_HSICPHYCTRLX_SIDDQ BIT(6) -#define EXYNOS_5250_HSICPHYCTRLX_FORCESLEEP BIT(5) -#define EXYNOS_5250_HSICPHYCTRLX_FORCESUSPEND BIT(4) -#define EXYNOS_5250_HSICPHYCTRLX_WORDINTERFACE BIT(3) -#define EXYNOS_5250_HSICPHYCTRLX_UTMISWRST BIT(2) -#define EXYNOS_5250_HSICPHYCTRLX_PHYSWRST BIT(0) - -/* EHCI control */ -#define EXYNOS_5250_HOSTEHCICTRL 0x30 -#define EXYNOS_5250_HOSTEHCICTRL_ENAINCRXALIGN BIT(29) -#define EXYNOS_5250_HOSTEHCICTRL_ENAINCR4 BIT(28) -#define EXYNOS_5250_HOSTEHCICTRL_ENAINCR8 BIT(27) -#define EXYNOS_5250_HOSTEHCICTRL_ENAINCR16 BIT(26) -#define EXYNOS_5250_HOSTEHCICTRL_AUTOPPDONOVRCUREN BIT(25) -#define EXYNOS_5250_HOSTEHCICTRL_FLADJVAL0_SHIFT 19 -#define EXYNOS_5250_HOSTEHCICTRL_FLADJVAL0_MASK \ - (0x3f << EXYNOS_5250_HOSTEHCICTRL_FLADJVAL0_SHIFT) -#define EXYNOS_5250_HOSTEHCICTRL_FLADJVAL1_SHIFT 13 -#define EXYNOS_5250_HOSTEHCICTRL_FLADJVAL1_MASK \ - (0x3f << EXYNOS_5250_HOSTEHCICTRL_FLADJVAL1_SHIFT) -#define EXYNOS_5250_HOSTEHCICTRL_FLADJVAL2_SHIFT 7 -#define EXYNOS_5250_HOSTEHCICTRL_FLADJVAL0_MASK \ - (0x3f << EXYNOS_5250_HOSTEHCICTRL_FLADJVAL0_SHIFT) -#define EXYNOS_5250_HOSTEHCICTRL_FLADJVALHOST_SHIFT 1 -#define EXYNOS_5250_HOSTEHCICTRL_FLADJVALHOST_MASK \ - (0x1 << EXYNOS_5250_HOSTEHCICTRL_FLADJVALHOST_SHIFT) -#define EXYNOS_5250_HOSTEHCICTRL_SIMULATIONMODE BIT(0) - -/* OHCI control */ -#define EXYNOS_5250_HOSTOHCICTRL 0x34 -#define EXYNOS_5250_HOSTOHCICTRL_FRAMELENVAL_SHIFT 1 -#define EXYNOS_5250_HOSTOHCICTRL_FRAMELENVAL_MASK \ - (0x3ff << EXYNOS_5250_HOSTOHCICTRL_FRAMELENVAL_SHIFT) -#define EXYNOS_5250_HOSTOHCICTRL_FRAMELENVALEN BIT(0) - -/* USBOTG */ -#define EXYNOS_5250_USBOTGSYS 0x38 -#define EXYNOS_5250_USBOTGSYS_PHYLINK_SW_RESET BIT(14) -#define EXYNOS_5250_USBOTGSYS_LINK_SW_RST_UOTG BIT(13) -#define EXYNOS_5250_USBOTGSYS_PHY_SW_RST BIT(12) -#define EXYNOS_5250_USBOTGSYS_REFCLKSEL_SHIFT 9 -#define EXYNOS_5250_USBOTGSYS_REFCLKSEL_MASK \ - (0x3 << EXYNOS_5250_USBOTGSYS_REFCLKSEL_SHIFT) -#define EXYNOS_5250_USBOTGSYS_ID_PULLUP BIT(8) -#define EXYNOS_5250_USBOTGSYS_COMMON_ON BIT(7) -#define EXYNOS_5250_USBOTGSYS_FSEL_SHIFT 4 -#define EXYNOS_5250_USBOTGSYS_FSEL_MASK \ - (0x3 << EXYNOS_5250_USBOTGSYS_FSEL_SHIFT) -#define EXYNOS_5250_USBOTGSYS_FORCE_SLEEP BIT(3) -#define EXYNOS_5250_USBOTGSYS_OTGDISABLE BIT(2) -#define EXYNOS_5250_USBOTGSYS_SIDDQ_UOTG BIT(1) -#define EXYNOS_5250_USBOTGSYS_FORCE_SUSPEND BIT(0) - -/* Isolation, configured in the power management unit */ -#define EXYNOS_5250_USB_ISOL_OTG_OFFSET 0x704 -#define EXYNOS_5250_USB_ISOL_OTG BIT(0) -#define EXYNOS_5250_USB_ISOL_HOST_OFFSET 0x708 -#define EXYNOS_5250_USB_ISOL_HOST BIT(0) - -/* Mode swtich register */ -#define EXYNOS_5250_MODE_SWITCH_OFFSET 0x230 -#define EXYNOS_5250_MODE_SWITCH_MASK 1 -#define EXYNOS_5250_MODE_SWITCH_DEVICE 0 -#define EXYNOS_5250_MODE_SWITCH_HOST 1 - -enum exynos4x12_phy_id { - EXYNOS5250_DEVICE, - EXYNOS5250_HOST, - EXYNOS5250_HSIC0, - EXYNOS5250_HSIC1, - EXYNOS5250_NUM_PHYS, -}; - -/* - * exynos5250_rate_to_clk() converts the supplied clock rate to the value that - * can be written to the phy register. - */ -static int exynos5250_rate_to_clk(unsigned long rate, u32 *reg) -{ - /* EXYNOS_5250_FSEL_MASK */ - - switch (rate) { - case 9600 * KHZ: - *reg = EXYNOS_5250_FSEL_9MHZ6; - break; - case 10 * MHZ: - *reg = EXYNOS_5250_FSEL_10MHZ; - break; - case 12 * MHZ: - *reg = EXYNOS_5250_FSEL_12MHZ; - break; - case 19200 * KHZ: - *reg = EXYNOS_5250_FSEL_19MHZ2; - break; - case 20 * MHZ: - *reg = EXYNOS_5250_FSEL_20MHZ; - break; - case 24 * MHZ: - *reg = EXYNOS_5250_FSEL_24MHZ; - break; - case 50 * MHZ: - *reg = EXYNOS_5250_FSEL_50MHZ; - break; - default: - return -EINVAL; - } - - return 0; -} - -static void exynos5250_isol(struct samsung_usb2_phy_instance *inst, bool on) -{ - struct samsung_usb2_phy_driver *drv = inst->drv; - u32 offset; - u32 mask; - - switch (inst->cfg->id) { - case EXYNOS5250_DEVICE: - offset = EXYNOS_5250_USB_ISOL_OTG_OFFSET; - mask = EXYNOS_5250_USB_ISOL_OTG; - break; - case EXYNOS5250_HOST: - offset = EXYNOS_5250_USB_ISOL_HOST_OFFSET; - mask = EXYNOS_5250_USB_ISOL_HOST; - break; - default: - return; - } - - regmap_update_bits(drv->reg_pmu, offset, mask, on ? 0 : mask); -} - -static int exynos5250_power_on(struct samsung_usb2_phy_instance *inst) -{ - struct samsung_usb2_phy_driver *drv = inst->drv; - u32 ctrl0; - u32 otg; - u32 ehci; - u32 ohci; - u32 hsic; - - switch (inst->cfg->id) { - case EXYNOS5250_DEVICE: - regmap_update_bits(drv->reg_sys, - EXYNOS_5250_MODE_SWITCH_OFFSET, - EXYNOS_5250_MODE_SWITCH_MASK, - EXYNOS_5250_MODE_SWITCH_DEVICE); - - /* OTG configuration */ - otg = readl(drv->reg_phy + EXYNOS_5250_USBOTGSYS); - /* The clock */ - otg &= ~EXYNOS_5250_USBOTGSYS_FSEL_MASK; - otg |= drv->ref_reg_val << EXYNOS_5250_USBOTGSYS_FSEL_SHIFT; - /* Reset */ - otg &= ~(EXYNOS_5250_USBOTGSYS_FORCE_SUSPEND | - EXYNOS_5250_USBOTGSYS_FORCE_SLEEP | - EXYNOS_5250_USBOTGSYS_SIDDQ_UOTG); - otg |= EXYNOS_5250_USBOTGSYS_PHY_SW_RST | - EXYNOS_5250_USBOTGSYS_PHYLINK_SW_RESET | - EXYNOS_5250_USBOTGSYS_LINK_SW_RST_UOTG | - EXYNOS_5250_USBOTGSYS_OTGDISABLE; - /* Ref clock */ - otg &= ~EXYNOS_5250_USBOTGSYS_REFCLKSEL_MASK; - otg |= EXYNOS_5250_REFCLKSEL_CLKCORE << - EXYNOS_5250_USBOTGSYS_REFCLKSEL_SHIFT; - writel(otg, drv->reg_phy + EXYNOS_5250_USBOTGSYS); - udelay(100); - otg &= ~(EXYNOS_5250_USBOTGSYS_PHY_SW_RST | - EXYNOS_5250_USBOTGSYS_LINK_SW_RST_UOTG | - EXYNOS_5250_USBOTGSYS_PHYLINK_SW_RESET | - EXYNOS_5250_USBOTGSYS_OTGDISABLE); - writel(otg, drv->reg_phy + EXYNOS_5250_USBOTGSYS); - - - break; - case EXYNOS5250_HOST: - case EXYNOS5250_HSIC0: - case EXYNOS5250_HSIC1: - /* Host registers configuration */ - ctrl0 = readl(drv->reg_phy + EXYNOS_5250_HOSTPHYCTRL0); - /* The clock */ - ctrl0 &= ~EXYNOS_5250_HOSTPHYCTRL0_FSEL_MASK; - ctrl0 |= drv->ref_reg_val << - EXYNOS_5250_HOSTPHYCTRL0_FSEL_SHIFT; - - /* Reset */ - ctrl0 &= ~(EXYNOS_5250_HOSTPHYCTRL0_PHYSWRST | - EXYNOS_5250_HOSTPHYCTRL0_PHYSWRSTALL | - EXYNOS_5250_HOSTPHYCTRL0_SIDDQ | - EXYNOS_5250_HOSTPHYCTRL0_FORCESUSPEND | - EXYNOS_5250_HOSTPHYCTRL0_FORCESLEEP); - ctrl0 |= EXYNOS_5250_HOSTPHYCTRL0_LINKSWRST | - EXYNOS_5250_HOSTPHYCTRL0_UTMISWRST | - EXYNOS_5250_HOSTPHYCTRL0_COMMON_ON_N; - writel(ctrl0, drv->reg_phy + EXYNOS_5250_HOSTPHYCTRL0); - udelay(10); - ctrl0 &= ~(EXYNOS_5250_HOSTPHYCTRL0_LINKSWRST | - EXYNOS_5250_HOSTPHYCTRL0_UTMISWRST); - writel(ctrl0, drv->reg_phy + EXYNOS_5250_HOSTPHYCTRL0); - - /* OTG configuration */ - otg = readl(drv->reg_phy + EXYNOS_5250_USBOTGSYS); - /* The clock */ - otg &= ~EXYNOS_5250_USBOTGSYS_FSEL_MASK; - otg |= drv->ref_reg_val << EXYNOS_5250_USBOTGSYS_FSEL_SHIFT; - /* Reset */ - otg &= ~(EXYNOS_5250_USBOTGSYS_FORCE_SUSPEND | - EXYNOS_5250_USBOTGSYS_FORCE_SLEEP | - EXYNOS_5250_USBOTGSYS_SIDDQ_UOTG); - otg |= EXYNOS_5250_USBOTGSYS_PHY_SW_RST | - EXYNOS_5250_USBOTGSYS_PHYLINK_SW_RESET | - EXYNOS_5250_USBOTGSYS_LINK_SW_RST_UOTG | - EXYNOS_5250_USBOTGSYS_OTGDISABLE; - /* Ref clock */ - otg &= ~EXYNOS_5250_USBOTGSYS_REFCLKSEL_MASK; - otg |= EXYNOS_5250_REFCLKSEL_CLKCORE << - EXYNOS_5250_USBOTGSYS_REFCLKSEL_SHIFT; - writel(otg, drv->reg_phy + EXYNOS_5250_USBOTGSYS); - udelay(10); - otg &= ~(EXYNOS_5250_USBOTGSYS_PHY_SW_RST | - EXYNOS_5250_USBOTGSYS_LINK_SW_RST_UOTG | - EXYNOS_5250_USBOTGSYS_PHYLINK_SW_RESET); - - /* HSIC phy configuration */ - hsic = (EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_12 | - EXYNOS_5250_HSICPHYCTRLX_REFCLKSEL_DEFAULT | - EXYNOS_5250_HSICPHYCTRLX_PHYSWRST); - writel(hsic, drv->reg_phy + EXYNOS_5250_HSICPHYCTRL1); - writel(hsic, drv->reg_phy + EXYNOS_5250_HSICPHYCTRL2); - udelay(10); - hsic &= ~EXYNOS_5250_HSICPHYCTRLX_PHYSWRST; - writel(hsic, drv->reg_phy + EXYNOS_5250_HSICPHYCTRL1); - writel(hsic, drv->reg_phy + EXYNOS_5250_HSICPHYCTRL2); - /* The following delay is necessary for the reset sequence to be - * completed */ - udelay(80); - - /* Enable EHCI DMA burst */ - ehci = readl(drv->reg_phy + EXYNOS_5250_HOSTEHCICTRL); - ehci |= EXYNOS_5250_HOSTEHCICTRL_ENAINCRXALIGN | - EXYNOS_5250_HOSTEHCICTRL_ENAINCR4 | - EXYNOS_5250_HOSTEHCICTRL_ENAINCR8 | - EXYNOS_5250_HOSTEHCICTRL_ENAINCR16; - writel(ehci, drv->reg_phy + EXYNOS_5250_HOSTEHCICTRL); - - /* OHCI settings */ - ohci = readl(drv->reg_phy + EXYNOS_5250_HOSTOHCICTRL); - /* Following code is based on the old driver */ - ohci |= 0x1 << 3; - writel(ohci, drv->reg_phy + EXYNOS_5250_HOSTOHCICTRL); - - break; - } - exynos5250_isol(inst, 0); - - return 0; -} - -static int exynos5250_power_off(struct samsung_usb2_phy_instance *inst) -{ - struct samsung_usb2_phy_driver *drv = inst->drv; - u32 ctrl0; - u32 otg; - u32 hsic; - - exynos5250_isol(inst, 1); - - switch (inst->cfg->id) { - case EXYNOS5250_DEVICE: - otg = readl(drv->reg_phy + EXYNOS_5250_USBOTGSYS); - otg |= (EXYNOS_5250_USBOTGSYS_FORCE_SUSPEND | - EXYNOS_5250_USBOTGSYS_SIDDQ_UOTG | - EXYNOS_5250_USBOTGSYS_FORCE_SLEEP); - writel(otg, drv->reg_phy + EXYNOS_5250_USBOTGSYS); - break; - case EXYNOS5250_HOST: - ctrl0 = readl(drv->reg_phy + EXYNOS_5250_HOSTPHYCTRL0); - ctrl0 |= (EXYNOS_5250_HOSTPHYCTRL0_SIDDQ | - EXYNOS_5250_HOSTPHYCTRL0_FORCESUSPEND | - EXYNOS_5250_HOSTPHYCTRL0_FORCESLEEP | - EXYNOS_5250_HOSTPHYCTRL0_PHYSWRST | - EXYNOS_5250_HOSTPHYCTRL0_PHYSWRSTALL); - writel(ctrl0, drv->reg_phy + EXYNOS_5250_HOSTPHYCTRL0); - break; - case EXYNOS5250_HSIC0: - case EXYNOS5250_HSIC1: - hsic = (EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_12 | - EXYNOS_5250_HSICPHYCTRLX_REFCLKSEL_DEFAULT | - EXYNOS_5250_HSICPHYCTRLX_SIDDQ | - EXYNOS_5250_HSICPHYCTRLX_FORCESLEEP | - EXYNOS_5250_HSICPHYCTRLX_FORCESUSPEND - ); - writel(hsic, drv->reg_phy + EXYNOS_5250_HSICPHYCTRL1); - writel(hsic, drv->reg_phy + EXYNOS_5250_HSICPHYCTRL2); - break; - } - - return 0; -} - - -static const struct samsung_usb2_common_phy exynos5250_phys[] = { - { - .label = "device", - .id = EXYNOS5250_DEVICE, - .power_on = exynos5250_power_on, - .power_off = exynos5250_power_off, - }, - { - .label = "host", - .id = EXYNOS5250_HOST, - .power_on = exynos5250_power_on, - .power_off = exynos5250_power_off, - }, - { - .label = "hsic0", - .id = EXYNOS5250_HSIC0, - .power_on = exynos5250_power_on, - .power_off = exynos5250_power_off, - }, - { - .label = "hsic1", - .id = EXYNOS5250_HSIC1, - .power_on = exynos5250_power_on, - .power_off = exynos5250_power_off, - }, -}; - -const struct samsung_usb2_phy_config exynos5250_usb2_phy_config = { - .has_mode_switch = 1, - .num_phys = EXYNOS5250_NUM_PHYS, - .phys = exynos5250_phys, - .rate_to_clk = exynos5250_rate_to_clk, -}; diff --git a/drivers/phy/phy-hi6220-usb.c b/drivers/phy/phy-hi6220-usb.c deleted file mode 100644 index 398c1021deec..000000000000 --- a/drivers/phy/phy-hi6220-usb.c +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright (c) 2015 Linaro Ltd. - * Copyright (c) 2015 Hisilicon Limited. - * - * 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; either version 2 of the License, or - * (at your option) any later version. - */ - -#include -#include -#include -#include -#include - -#define SC_PERIPH_CTRL4 0x00c - -#define CTRL4_PICO_SIDDQ BIT(6) -#define CTRL4_PICO_OGDISABLE BIT(8) -#define CTRL4_PICO_VBUSVLDEXT BIT(10) -#define CTRL4_PICO_VBUSVLDEXTSEL BIT(11) -#define CTRL4_OTG_PHY_SEL BIT(21) - -#define SC_PERIPH_CTRL5 0x010 - -#define CTRL5_USBOTG_RES_SEL BIT(3) -#define CTRL5_PICOPHY_ACAENB BIT(4) -#define CTRL5_PICOPHY_BC_MODE BIT(5) -#define CTRL5_PICOPHY_CHRGSEL BIT(6) -#define CTRL5_PICOPHY_VDATSRCEND BIT(7) -#define CTRL5_PICOPHY_VDATDETENB BIT(8) -#define CTRL5_PICOPHY_DCDENB BIT(9) -#define CTRL5_PICOPHY_IDDIG BIT(10) - -#define SC_PERIPH_CTRL8 0x018 -#define SC_PERIPH_RSTEN0 0x300 -#define SC_PERIPH_RSTDIS0 0x304 - -#define RST0_USBOTG_BUS BIT(4) -#define RST0_POR_PICOPHY BIT(5) -#define RST0_USBOTG BIT(6) -#define RST0_USBOTG_32K BIT(7) - -#define EYE_PATTERN_PARA 0x7053348c - -struct hi6220_priv { - struct regmap *reg; - struct device *dev; -}; - -static void hi6220_phy_init(struct hi6220_priv *priv) -{ - struct regmap *reg = priv->reg; - u32 val, mask; - - val = RST0_USBOTG_BUS | RST0_POR_PICOPHY | - RST0_USBOTG | RST0_USBOTG_32K; - mask = val; - regmap_update_bits(reg, SC_PERIPH_RSTEN0, mask, val); - regmap_update_bits(reg, SC_PERIPH_RSTDIS0, mask, val); -} - -static int hi6220_phy_setup(struct hi6220_priv *priv, bool on) -{ - struct regmap *reg = priv->reg; - u32 val, mask; - int ret; - - if (on) { - val = CTRL5_USBOTG_RES_SEL | CTRL5_PICOPHY_ACAENB; - mask = val | CTRL5_PICOPHY_BC_MODE; - ret = regmap_update_bits(reg, SC_PERIPH_CTRL5, mask, val); - if (ret) - goto out; - - val = CTRL4_PICO_VBUSVLDEXT | CTRL4_PICO_VBUSVLDEXTSEL | - CTRL4_OTG_PHY_SEL; - mask = val | CTRL4_PICO_SIDDQ | CTRL4_PICO_OGDISABLE; - ret = regmap_update_bits(reg, SC_PERIPH_CTRL4, mask, val); - if (ret) - goto out; - - ret = regmap_write(reg, SC_PERIPH_CTRL8, EYE_PATTERN_PARA); - if (ret) - goto out; - } else { - val = CTRL4_PICO_SIDDQ; - mask = val; - ret = regmap_update_bits(reg, SC_PERIPH_CTRL4, mask, val); - if (ret) - goto out; - } - - return 0; -out: - dev_err(priv->dev, "failed to setup phy ret: %d\n", ret); - return ret; -} - -static int hi6220_phy_start(struct phy *phy) -{ - struct hi6220_priv *priv = phy_get_drvdata(phy); - - return hi6220_phy_setup(priv, true); -} - -static int hi6220_phy_exit(struct phy *phy) -{ - struct hi6220_priv *priv = phy_get_drvdata(phy); - - return hi6220_phy_setup(priv, false); -} - -static const struct phy_ops hi6220_phy_ops = { - .init = hi6220_phy_start, - .exit = hi6220_phy_exit, - .owner = THIS_MODULE, -}; - -static int hi6220_phy_probe(struct platform_device *pdev) -{ - struct phy_provider *phy_provider; - struct device *dev = &pdev->dev; - struct phy *phy; - struct hi6220_priv *priv; - - priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - priv->dev = dev; - priv->reg = syscon_regmap_lookup_by_phandle(dev->of_node, - "hisilicon,peripheral-syscon"); - if (IS_ERR(priv->reg)) { - dev_err(dev, "no hisilicon,peripheral-syscon\n"); - return PTR_ERR(priv->reg); - } - - hi6220_phy_init(priv); - - phy = devm_phy_create(dev, NULL, &hi6220_phy_ops); - if (IS_ERR(phy)) - return PTR_ERR(phy); - - phy_set_drvdata(phy, priv); - phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); - return PTR_ERR_OR_ZERO(phy_provider); -} - -static const struct of_device_id hi6220_phy_of_match[] = { - {.compatible = "hisilicon,hi6220-usb-phy",}, - { }, -}; -MODULE_DEVICE_TABLE(of, hi6220_phy_of_match); - -static struct platform_driver hi6220_phy_driver = { - .probe = hi6220_phy_probe, - .driver = { - .name = "hi6220-usb-phy", - .of_match_table = hi6220_phy_of_match, - } -}; -module_platform_driver(hi6220_phy_driver); - -MODULE_DESCRIPTION("HISILICON HI6220 USB PHY driver"); -MODULE_ALIAS("platform:hi6220-usb-phy"); -MODULE_LICENSE("GPL"); diff --git a/drivers/phy/phy-hix5hd2-sata.c b/drivers/phy/phy-hix5hd2-sata.c deleted file mode 100644 index e5ab3aa78b9d..000000000000 --- a/drivers/phy/phy-hix5hd2-sata.c +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (c) 2014 Linaro Ltd. - * Copyright (c) 2014 Hisilicon Limited. - * - * 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; either version 2 of the License, or - * (at your option) any later version. - */ - -#include -#include -#include -#include -#include -#include -#include - -#define SATA_PHY0_CTLL 0xa0 -#define MPLL_MULTIPLIER_SHIFT 1 -#define MPLL_MULTIPLIER_MASK 0xfe -#define MPLL_MULTIPLIER_50M 0x3c -#define MPLL_MULTIPLIER_100M 0x1e -#define PHY_RESET BIT(0) -#define REF_SSP_EN BIT(9) -#define SSC_EN BIT(10) -#define REF_USE_PAD BIT(23) - -#define SATA_PORT_PHYCTL 0x174 -#define SPEED_MODE_MASK 0x6f0000 -#define HALF_RATE_SHIFT 16 -#define PHY_CONFIG_SHIFT 18 -#define GEN2_EN_SHIFT 21 -#define SPEED_CTRL BIT(20) - -#define SATA_PORT_PHYCTL1 0x148 -#define AMPLITUDE_MASK 0x3ffffe -#define AMPLITUDE_GEN3 0x68 -#define AMPLITUDE_GEN3_SHIFT 15 -#define AMPLITUDE_GEN2 0x56 -#define AMPLITUDE_GEN2_SHIFT 8 -#define AMPLITUDE_GEN1 0x56 -#define AMPLITUDE_GEN1_SHIFT 1 - -#define SATA_PORT_PHYCTL2 0x14c -#define PREEMPH_MASK 0x3ffff -#define PREEMPH_GEN3 0x20 -#define PREEMPH_GEN3_SHIFT 12 -#define PREEMPH_GEN2 0x15 -#define PREEMPH_GEN2_SHIFT 6 -#define PREEMPH_GEN1 0x5 -#define PREEMPH_GEN1_SHIFT 0 - -struct hix5hd2_priv { - void __iomem *base; - struct regmap *peri_ctrl; -}; - -enum phy_speed_mode { - SPEED_MODE_GEN1 = 0, - SPEED_MODE_GEN2 = 1, - SPEED_MODE_GEN3 = 2, -}; - -static int hix5hd2_sata_phy_init(struct phy *phy) -{ - struct hix5hd2_priv *priv = phy_get_drvdata(phy); - u32 val, data[2]; - int ret; - - if (priv->peri_ctrl) { - ret = of_property_read_u32_array(phy->dev.of_node, - "hisilicon,power-reg", - &data[0], 2); - if (ret) { - dev_err(&phy->dev, "Fail read hisilicon,power-reg\n"); - return ret; - } - - regmap_update_bits(priv->peri_ctrl, data[0], - BIT(data[1]), BIT(data[1])); - } - - /* reset phy */ - val = readl_relaxed(priv->base + SATA_PHY0_CTLL); - val &= ~(MPLL_MULTIPLIER_MASK | REF_USE_PAD); - val |= MPLL_MULTIPLIER_50M << MPLL_MULTIPLIER_SHIFT | - REF_SSP_EN | PHY_RESET; - writel_relaxed(val, priv->base + SATA_PHY0_CTLL); - msleep(20); - val &= ~PHY_RESET; - writel_relaxed(val, priv->base + SATA_PHY0_CTLL); - - val = readl_relaxed(priv->base + SATA_PORT_PHYCTL1); - val &= ~AMPLITUDE_MASK; - val |= AMPLITUDE_GEN3 << AMPLITUDE_GEN3_SHIFT | - AMPLITUDE_GEN2 << AMPLITUDE_GEN2_SHIFT | - AMPLITUDE_GEN1 << AMPLITUDE_GEN1_SHIFT; - writel_relaxed(val, priv->base + SATA_PORT_PHYCTL1); - - val = readl_relaxed(priv->base + SATA_PORT_PHYCTL2); - val &= ~PREEMPH_MASK; - val |= PREEMPH_GEN3 << PREEMPH_GEN3_SHIFT | - PREEMPH_GEN2 << PREEMPH_GEN2_SHIFT | - PREEMPH_GEN1 << PREEMPH_GEN1_SHIFT; - writel_relaxed(val, priv->base + SATA_PORT_PHYCTL2); - - /* ensure PHYCTRL setting takes effect */ - val = readl_relaxed(priv->base + SATA_PORT_PHYCTL); - val &= ~SPEED_MODE_MASK; - val |= SPEED_MODE_GEN1 << HALF_RATE_SHIFT | - SPEED_MODE_GEN1 << PHY_CONFIG_SHIFT | - SPEED_MODE_GEN1 << GEN2_EN_SHIFT | SPEED_CTRL; - writel_relaxed(val, priv->base + SATA_PORT_PHYCTL); - - msleep(20); - val &= ~SPEED_MODE_MASK; - val |= SPEED_MODE_GEN3 << HALF_RATE_SHIFT | - SPEED_MODE_GEN3 << PHY_CONFIG_SHIFT | - SPEED_MODE_GEN3 << GEN2_EN_SHIFT | SPEED_CTRL; - writel_relaxed(val, priv->base + SATA_PORT_PHYCTL); - - val &= ~(SPEED_MODE_MASK | SPEED_CTRL); - val |= SPEED_MODE_GEN2 << HALF_RATE_SHIFT | - SPEED_MODE_GEN2 << PHY_CONFIG_SHIFT | - SPEED_MODE_GEN2 << GEN2_EN_SHIFT; - writel_relaxed(val, priv->base + SATA_PORT_PHYCTL); - - return 0; -} - -static const struct phy_ops hix5hd2_sata_phy_ops = { - .init = hix5hd2_sata_phy_init, - .owner = THIS_MODULE, -}; - -static int hix5hd2_sata_phy_probe(struct platform_device *pdev) -{ - struct phy_provider *phy_provider; - struct device *dev = &pdev->dev; - struct resource *res; - struct phy *phy; - struct hix5hd2_priv *priv; - - priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) - return -EINVAL; - - priv->base = devm_ioremap(dev, res->start, resource_size(res)); - if (!priv->base) - return -ENOMEM; - - priv->peri_ctrl = syscon_regmap_lookup_by_phandle(dev->of_node, - "hisilicon,peripheral-syscon"); - if (IS_ERR(priv->peri_ctrl)) - priv->peri_ctrl = NULL; - - phy = devm_phy_create(dev, NULL, &hix5hd2_sata_phy_ops); - if (IS_ERR(phy)) { - dev_err(dev, "failed to create PHY\n"); - return PTR_ERR(phy); - } - - phy_set_drvdata(phy, priv); - phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); - return PTR_ERR_OR_ZERO(phy_provider); -} - -static const struct of_device_id hix5hd2_sata_phy_of_match[] = { - {.compatible = "hisilicon,hix5hd2-sata-phy",}, - { }, -}; -MODULE_DEVICE_TABLE(of, hix5hd2_sata_phy_of_match); - -static struct platform_driver hix5hd2_sata_phy_driver = { - .probe = hix5hd2_sata_phy_probe, - .driver = { - .name = "hix5hd2-sata-phy", - .of_match_table = hix5hd2_sata_phy_of_match, - } -}; -module_platform_driver(hix5hd2_sata_phy_driver); - -MODULE_AUTHOR("Jiancheng Xue "); -MODULE_DESCRIPTION("HISILICON HIX5HD2 SATA PHY driver"); -MODULE_ALIAS("platform:hix5hd2-sata-phy"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-meson8b-usb2.c b/drivers/phy/phy-meson8b-usb2.c deleted file mode 100644 index 30f56a6a411f..000000000000 --- a/drivers/phy/phy-meson8b-usb2.c +++ /dev/null @@ -1,286 +0,0 @@ -/* - * Meson8b and GXBB USB2 PHY driver - * - * Copyright (C) 2016 Martin Blumenstingl - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define REG_CONFIG 0x00 - #define REG_CONFIG_CLK_EN BIT(0) - #define REG_CONFIG_CLK_SEL_MASK GENMASK(3, 1) - #define REG_CONFIG_CLK_DIV_MASK GENMASK(10, 4) - #define REG_CONFIG_CLK_32k_ALTSEL BIT(15) - #define REG_CONFIG_TEST_TRIG BIT(31) - -#define REG_CTRL 0x04 - #define REG_CTRL_SOFT_PRST BIT(0) - #define REG_CTRL_SOFT_HRESET BIT(1) - #define REG_CTRL_SS_SCALEDOWN_MODE_MASK GENMASK(3, 2) - #define REG_CTRL_CLK_DET_RST BIT(4) - #define REG_CTRL_INTR_SEL BIT(5) - #define REG_CTRL_CLK_DETECTED BIT(8) - #define REG_CTRL_SOF_SENT_RCVD_TGL BIT(9) - #define REG_CTRL_SOF_TOGGLE_OUT BIT(10) - #define REG_CTRL_POWER_ON_RESET BIT(15) - #define REG_CTRL_SLEEPM BIT(16) - #define REG_CTRL_TX_BITSTUFF_ENN_H BIT(17) - #define REG_CTRL_TX_BITSTUFF_ENN BIT(18) - #define REG_CTRL_COMMON_ON BIT(19) - #define REG_CTRL_REF_CLK_SEL_MASK GENMASK(21, 20) - #define REG_CTRL_REF_CLK_SEL_SHIFT 20 - #define REG_CTRL_FSEL_MASK GENMASK(24, 22) - #define REG_CTRL_FSEL_SHIFT 22 - #define REG_CTRL_PORT_RESET BIT(25) - #define REG_CTRL_THREAD_ID_MASK GENMASK(31, 26) - -#define REG_ENDP_INTR 0x08 - -/* bits [31:26], [24:21] and [15:3] seem to be read-only */ -#define REG_ADP_BC 0x0c - #define REG_ADP_BC_VBUS_VLD_EXT_SEL BIT(0) - #define REG_ADP_BC_VBUS_VLD_EXT BIT(1) - #define REG_ADP_BC_OTG_DISABLE BIT(2) - #define REG_ADP_BC_ID_PULLUP BIT(3) - #define REG_ADP_BC_DRV_VBUS BIT(4) - #define REG_ADP_BC_ADP_PRB_EN BIT(5) - #define REG_ADP_BC_ADP_DISCHARGE BIT(6) - #define REG_ADP_BC_ADP_CHARGE BIT(7) - #define REG_ADP_BC_SESS_END BIT(8) - #define REG_ADP_BC_DEVICE_SESS_VLD BIT(9) - #define REG_ADP_BC_B_VALID BIT(10) - #define REG_ADP_BC_A_VALID BIT(11) - #define REG_ADP_BC_ID_DIG BIT(12) - #define REG_ADP_BC_VBUS_VALID BIT(13) - #define REG_ADP_BC_ADP_PROBE BIT(14) - #define REG_ADP_BC_ADP_SENSE BIT(15) - #define REG_ADP_BC_ACA_ENABLE BIT(16) - #define REG_ADP_BC_DCD_ENABLE BIT(17) - #define REG_ADP_BC_VDAT_DET_EN_B BIT(18) - #define REG_ADP_BC_VDAT_SRC_EN_B BIT(19) - #define REG_ADP_BC_CHARGE_SEL BIT(20) - #define REG_ADP_BC_CHARGE_DETECT BIT(21) - #define REG_ADP_BC_ACA_PIN_RANGE_C BIT(22) - #define REG_ADP_BC_ACA_PIN_RANGE_B BIT(23) - #define REG_ADP_BC_ACA_PIN_RANGE_A BIT(24) - #define REG_ADP_BC_ACA_PIN_GND BIT(25) - #define REG_ADP_BC_ACA_PIN_FLOAT BIT(26) - -#define REG_DBG_UART 0x10 - -#define REG_TEST 0x14 - #define REG_TEST_DATA_IN_MASK GENMASK(3, 0) - #define REG_TEST_EN_MASK GENMASK(7, 4) - #define REG_TEST_ADDR_MASK GENMASK(11, 8) - #define REG_TEST_DATA_OUT_SEL BIT(12) - #define REG_TEST_CLK BIT(13) - #define REG_TEST_VA_TEST_EN_B_MASK GENMASK(15, 14) - #define REG_TEST_DATA_OUT_MASK GENMASK(19, 16) - #define REG_TEST_DISABLE_ID_PULLUP BIT(20) - -#define REG_TUNE 0x18 - #define REG_TUNE_TX_RES_TUNE_MASK GENMASK(1, 0) - #define REG_TUNE_TX_HSXV_TUNE_MASK GENMASK(3, 2) - #define REG_TUNE_TX_VREF_TUNE_MASK GENMASK(7, 4) - #define REG_TUNE_TX_RISE_TUNE_MASK GENMASK(9, 8) - #define REG_TUNE_TX_PREEMP_PULSE_TUNE BIT(10) - #define REG_TUNE_TX_PREEMP_AMP_TUNE_MASK GENMASK(12, 11) - #define REG_TUNE_TX_FSLS_TUNE_MASK GENMASK(16, 13) - #define REG_TUNE_SQRX_TUNE_MASK GENMASK(19, 17) - #define REG_TUNE_OTG_TUNE GENMASK(22, 20) - #define REG_TUNE_COMP_DIS_TUNE GENMASK(25, 23) - #define REG_TUNE_HOST_DM_PULLDOWN BIT(26) - #define REG_TUNE_HOST_DP_PULLDOWN BIT(27) - -#define RESET_COMPLETE_TIME 500 -#define ACA_ENABLE_COMPLETE_TIME 50 - -struct phy_meson8b_usb2_priv { - void __iomem *regs; - enum usb_dr_mode dr_mode; - struct clk *clk_usb_general; - struct clk *clk_usb; - struct reset_control *reset; -}; - -static u32 phy_meson8b_usb2_read(struct phy_meson8b_usb2_priv *phy_priv, - u32 reg) -{ - return readl(phy_priv->regs + reg); -} - -static void phy_meson8b_usb2_mask_bits(struct phy_meson8b_usb2_priv *phy_priv, - u32 reg, u32 mask, u32 value) -{ - u32 data; - - data = phy_meson8b_usb2_read(phy_priv, reg); - data &= ~mask; - data |= (value & mask); - - writel(data, phy_priv->regs + reg); -} - -static int phy_meson8b_usb2_power_on(struct phy *phy) -{ - struct phy_meson8b_usb2_priv *priv = phy_get_drvdata(phy); - int ret; - - if (!IS_ERR_OR_NULL(priv->reset)) { - ret = reset_control_reset(priv->reset); - if (ret) { - dev_err(&phy->dev, "Failed to trigger USB reset\n"); - return ret; - } - } - - ret = clk_prepare_enable(priv->clk_usb_general); - if (ret) { - dev_err(&phy->dev, "Failed to enable USB general clock\n"); - return ret; - } - - ret = clk_prepare_enable(priv->clk_usb); - if (ret) { - dev_err(&phy->dev, "Failed to enable USB DDR clock\n"); - clk_disable_unprepare(priv->clk_usb_general); - return ret; - } - - phy_meson8b_usb2_mask_bits(priv, REG_CONFIG, REG_CONFIG_CLK_32k_ALTSEL, - REG_CONFIG_CLK_32k_ALTSEL); - - phy_meson8b_usb2_mask_bits(priv, REG_CTRL, REG_CTRL_REF_CLK_SEL_MASK, - 0x2 << REG_CTRL_REF_CLK_SEL_SHIFT); - - phy_meson8b_usb2_mask_bits(priv, REG_CTRL, REG_CTRL_FSEL_MASK, - 0x5 << REG_CTRL_FSEL_SHIFT); - - /* reset the PHY */ - phy_meson8b_usb2_mask_bits(priv, REG_CTRL, REG_CTRL_POWER_ON_RESET, - REG_CTRL_POWER_ON_RESET); - udelay(RESET_COMPLETE_TIME); - phy_meson8b_usb2_mask_bits(priv, REG_CTRL, REG_CTRL_POWER_ON_RESET, 0); - udelay(RESET_COMPLETE_TIME); - - phy_meson8b_usb2_mask_bits(priv, REG_CTRL, REG_CTRL_SOF_TOGGLE_OUT, - REG_CTRL_SOF_TOGGLE_OUT); - - if (priv->dr_mode == USB_DR_MODE_HOST) { - phy_meson8b_usb2_mask_bits(priv, REG_ADP_BC, - REG_ADP_BC_ACA_ENABLE, - REG_ADP_BC_ACA_ENABLE); - - udelay(ACA_ENABLE_COMPLETE_TIME); - - if (phy_meson8b_usb2_read(priv, REG_ADP_BC) & - REG_ADP_BC_ACA_PIN_FLOAT) { - dev_warn(&phy->dev, "USB ID detect failed!\n"); - clk_disable_unprepare(priv->clk_usb); - clk_disable_unprepare(priv->clk_usb_general); - return -EINVAL; - } - } - - return 0; -} - -static int phy_meson8b_usb2_power_off(struct phy *phy) -{ - struct phy_meson8b_usb2_priv *priv = phy_get_drvdata(phy); - - clk_disable_unprepare(priv->clk_usb); - clk_disable_unprepare(priv->clk_usb_general); - - return 0; -} - -static const struct phy_ops phy_meson8b_usb2_ops = { - .power_on = phy_meson8b_usb2_power_on, - .power_off = phy_meson8b_usb2_power_off, - .owner = THIS_MODULE, -}; - -static int phy_meson8b_usb2_probe(struct platform_device *pdev) -{ - struct phy_meson8b_usb2_priv *priv; - struct resource *res; - struct phy *phy; - struct phy_provider *phy_provider; - - priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - priv->regs = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(priv->regs)) - return PTR_ERR(priv->regs); - - priv->clk_usb_general = devm_clk_get(&pdev->dev, "usb_general"); - if (IS_ERR(priv->clk_usb_general)) - return PTR_ERR(priv->clk_usb_general); - - priv->clk_usb = devm_clk_get(&pdev->dev, "usb"); - if (IS_ERR(priv->clk_usb)) - return PTR_ERR(priv->clk_usb); - - priv->reset = devm_reset_control_get_optional_shared(&pdev->dev, NULL); - if (PTR_ERR(priv->reset) == -EPROBE_DEFER) - return PTR_ERR(priv->reset); - - priv->dr_mode = of_usb_get_dr_mode_by_phy(pdev->dev.of_node, -1); - if (priv->dr_mode == USB_DR_MODE_UNKNOWN) { - dev_err(&pdev->dev, - "missing dual role configuration of the controller\n"); - return -EINVAL; - } - - phy = devm_phy_create(&pdev->dev, NULL, &phy_meson8b_usb2_ops); - if (IS_ERR(phy)) { - dev_err(&pdev->dev, "failed to create PHY\n"); - return PTR_ERR(phy); - } - - phy_set_drvdata(phy, priv); - - phy_provider = - devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate); - - return PTR_ERR_OR_ZERO(phy_provider); -} - -static const struct of_device_id phy_meson8b_usb2_of_match[] = { - { .compatible = "amlogic,meson8b-usb2-phy", }, - { .compatible = "amlogic,meson-gxbb-usb2-phy", }, - { }, -}; -MODULE_DEVICE_TABLE(of, phy_meson8b_usb2_of_match); - -static struct platform_driver phy_meson8b_usb2_driver = { - .probe = phy_meson8b_usb2_probe, - .driver = { - .name = "phy-meson-usb2", - .of_match_table = phy_meson8b_usb2_of_match, - }, -}; -module_platform_driver(phy_meson8b_usb2_driver); - -MODULE_AUTHOR("Martin Blumenstingl "); -MODULE_DESCRIPTION("Meson8b and GXBB USB2 PHY driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/phy/phy-miphy28lp.c b/drivers/phy/phy-miphy28lp.c deleted file mode 100644 index 213e2e15339c..000000000000 --- a/drivers/phy/phy-miphy28lp.c +++ /dev/null @@ -1,1286 +0,0 @@ -/* - * Copyright (C) 2014 STMicroelectronics - * - * STMicroelectronics PHY driver MiPHY28lp (for SoC STiH407). - * - * Author: Alexandre Torgue - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2, as - * published by the Free Software Foundation. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -/* MiPHY registers */ -#define MIPHY_CONF_RESET 0x00 -#define RST_APPLI_SW BIT(0) -#define RST_CONF_SW BIT(1) -#define RST_MACRO_SW BIT(2) - -#define MIPHY_RESET 0x01 -#define RST_PLL_SW BIT(0) -#define RST_COMP_SW BIT(2) - -#define MIPHY_STATUS_1 0x02 -#define PHY_RDY BIT(0) -#define HFC_RDY BIT(1) -#define HFC_PLL BIT(2) - -#define MIPHY_CONTROL 0x04 -#define TERM_EN_SW BIT(2) -#define DIS_LINK_RST BIT(3) -#define AUTO_RST_RX BIT(4) -#define PX_RX_POL BIT(5) - -#define MIPHY_BOUNDARY_SEL 0x0a -#define TX_SEL BIT(6) -#define SSC_SEL BIT(4) -#define GENSEL_SEL BIT(0) - -#define MIPHY_BOUNDARY_1 0x0b -#define MIPHY_BOUNDARY_2 0x0c -#define SSC_EN_SW BIT(2) - -#define MIPHY_PLL_CLKREF_FREQ 0x0d -#define MIPHY_SPEED 0x0e -#define TX_SPDSEL_80DEC 0 -#define TX_SPDSEL_40DEC 1 -#define TX_SPDSEL_20DEC 2 -#define RX_SPDSEL_80DEC 0 -#define RX_SPDSEL_40DEC (1 << 2) -#define RX_SPDSEL_20DEC (2 << 2) - -#define MIPHY_CONF 0x0f -#define MIPHY_CTRL_TEST_SEL 0x20 -#define MIPHY_CTRL_TEST_1 0x21 -#define MIPHY_CTRL_TEST_2 0x22 -#define MIPHY_CTRL_TEST_3 0x23 -#define MIPHY_CTRL_TEST_4 0x24 -#define MIPHY_FEEDBACK_TEST 0x25 -#define MIPHY_DEBUG_BUS 0x26 -#define MIPHY_DEBUG_STATUS_MSB 0x27 -#define MIPHY_DEBUG_STATUS_LSB 0x28 -#define MIPHY_PWR_RAIL_1 0x29 -#define MIPHY_PWR_RAIL_2 0x2a -#define MIPHY_SYNCHAR_CONTROL 0x30 - -#define MIPHY_COMP_FSM_1 0x3a -#define COMP_START BIT(6) - -#define MIPHY_COMP_FSM_6 0x3f -#define COMP_DONE BIT(7) - -#define MIPHY_COMP_POSTP 0x42 -#define MIPHY_TX_CTRL_1 0x49 -#define TX_REG_STEP_0V 0 -#define TX_REG_STEP_P_25MV 1 -#define TX_REG_STEP_P_50MV 2 -#define TX_REG_STEP_N_25MV 7 -#define TX_REG_STEP_N_50MV 6 -#define TX_REG_STEP_N_75MV 5 - -#define MIPHY_TX_CTRL_2 0x4a -#define TX_SLEW_SW_40_PS 0 -#define TX_SLEW_SW_80_PS 1 -#define TX_SLEW_SW_120_PS 2 - -#define MIPHY_TX_CTRL_3 0x4b -#define MIPHY_TX_CAL_MAN 0x4e -#define TX_SLEW_CAL_MAN_EN BIT(0) - -#define MIPHY_TST_BIAS_BOOST_2 0x62 -#define MIPHY_BIAS_BOOST_1 0x63 -#define MIPHY_BIAS_BOOST_2 0x64 -#define MIPHY_RX_DESBUFF_FDB_2 0x67 -#define MIPHY_RX_DESBUFF_FDB_3 0x68 -#define MIPHY_SIGDET_COMPENS1 0x69 -#define MIPHY_SIGDET_COMPENS2 0x6a -#define MIPHY_JITTER_PERIOD 0x6b -#define MIPHY_JITTER_AMPLITUDE_1 0x6c -#define MIPHY_JITTER_AMPLITUDE_2 0x6d -#define MIPHY_JITTER_AMPLITUDE_3 0x6e -#define MIPHY_RX_K_GAIN 0x78 -#define MIPHY_RX_BUFFER_CTRL 0x7a -#define VGA_GAIN BIT(0) -#define EQ_DC_GAIN BIT(2) -#define EQ_BOOST_GAIN BIT(3) - -#define MIPHY_RX_VGA_GAIN 0x7b -#define MIPHY_RX_EQU_GAIN_1 0x7f -#define MIPHY_RX_EQU_GAIN_2 0x80 -#define MIPHY_RX_EQU_GAIN_3 0x81 -#define MIPHY_RX_CAL_CTRL_1 0x97 -#define MIPHY_RX_CAL_CTRL_2 0x98 - -#define MIPHY_RX_CAL_OFFSET_CTRL 0x99 -#define CAL_OFFSET_VGA_64 (0x03 << 0) -#define CAL_OFFSET_THRESHOLD_64 (0x03 << 2) -#define VGA_OFFSET_POLARITY BIT(4) -#define OFFSET_COMPENSATION_EN BIT(6) - -#define MIPHY_RX_CAL_VGA_STEP 0x9a -#define MIPHY_RX_CAL_EYE_MIN 0x9d -#define MIPHY_RX_CAL_OPT_LENGTH 0x9f -#define MIPHY_RX_LOCK_CTRL_1 0xc1 -#define MIPHY_RX_LOCK_SETTINGS_OPT 0xc2 -#define MIPHY_RX_LOCK_STEP 0xc4 - -#define MIPHY_RX_SIGDET_SLEEP_OA 0xc9 -#define MIPHY_RX_SIGDET_SLEEP_SEL 0xca -#define MIPHY_RX_SIGDET_WAIT_SEL 0xcb -#define MIPHY_RX_SIGDET_DATA_SEL 0xcc -#define EN_ULTRA_LOW_POWER BIT(0) -#define EN_FIRST_HALF BIT(1) -#define EN_SECOND_HALF BIT(2) -#define EN_DIGIT_SIGNAL_CHECK BIT(3) - -#define MIPHY_RX_POWER_CTRL_1 0xcd -#define MIPHY_RX_POWER_CTRL_2 0xce -#define MIPHY_PLL_CALSET_CTRL 0xd3 -#define MIPHY_PLL_CALSET_1 0xd4 -#define MIPHY_PLL_CALSET_2 0xd5 -#define MIPHY_PLL_CALSET_3 0xd6 -#define MIPHY_PLL_CALSET_4 0xd7 -#define MIPHY_PLL_SBR_1 0xe3 -#define SET_NEW_CHANGE BIT(1) - -#define MIPHY_PLL_SBR_2 0xe4 -#define MIPHY_PLL_SBR_3 0xe5 -#define MIPHY_PLL_SBR_4 0xe6 -#define MIPHY_PLL_COMMON_MISC_2 0xe9 -#define START_ACT_FILT BIT(6) - -#define MIPHY_PLL_SPAREIN 0xeb - -/* - * On STiH407 the glue logic can be different among MiPHY devices; for example: - * MiPHY0: OSC_FORCE_EXT means: - * 0: 30MHz crystal clk - 1: 100MHz ext clk routed through MiPHY1 - * MiPHY1: OSC_FORCE_EXT means: - * 1: 30MHz crystal clk - 0: 100MHz ext clk routed through MiPHY1 - * Some devices have not the possibility to check if the osc is ready. - */ -#define MIPHY_OSC_FORCE_EXT BIT(3) -#define MIPHY_OSC_RDY BIT(5) - -#define MIPHY_CTRL_MASK 0x0f -#define MIPHY_CTRL_DEFAULT 0 -#define MIPHY_CTRL_SYNC_D_EN BIT(2) - -/* SATA / PCIe defines */ -#define SATA_CTRL_MASK 0x07 -#define PCIE_CTRL_MASK 0xff -#define SATA_CTRL_SELECT_SATA 1 -#define SATA_CTRL_SELECT_PCIE 0 -#define SYSCFG_PCIE_PCIE_VAL 0x80 -#define SATA_SPDMODE 1 - -#define MIPHY_SATA_BANK_NB 3 -#define MIPHY_PCIE_BANK_NB 2 - -enum { - SYSCFG_CTRL, - SYSCFG_STATUS, - SYSCFG_PCI, - SYSCFG_SATA, - SYSCFG_REG_MAX, -}; - -struct miphy28lp_phy { - struct phy *phy; - struct miphy28lp_dev *phydev; - void __iomem *base; - void __iomem *pipebase; - - bool osc_force_ext; - bool osc_rdy; - bool px_rx_pol_inv; - bool ssc; - bool tx_impedance; - - struct reset_control *miphy_rst; - - u32 sata_gen; - - /* Sysconfig registers offsets needed to configure the device */ - u32 syscfg_reg[SYSCFG_REG_MAX]; - u8 type; -}; - -struct miphy28lp_dev { - struct device *dev; - struct regmap *regmap; - struct mutex miphy_mutex; - struct miphy28lp_phy **phys; - int nphys; -}; - -struct miphy_initval { - u16 reg; - u16 val; -}; - -enum miphy_sata_gen { SATA_GEN1, SATA_GEN2, SATA_GEN3 }; - -static char *PHY_TYPE_name[] = { "sata-up", "pcie-up", "", "usb3-up" }; - -struct pll_ratio { - int clk_ref; - int calset_1; - int calset_2; - int calset_3; - int calset_4; - int cal_ctrl; -}; - -static struct pll_ratio sata_pll_ratio = { - .clk_ref = 0x1e, - .calset_1 = 0xc8, - .calset_2 = 0x00, - .calset_3 = 0x00, - .calset_4 = 0x00, - .cal_ctrl = 0x00, -}; - -static struct pll_ratio pcie_pll_ratio = { - .clk_ref = 0x1e, - .calset_1 = 0xa6, - .calset_2 = 0xaa, - .calset_3 = 0xaa, - .calset_4 = 0x00, - .cal_ctrl = 0x00, -}; - -static struct pll_ratio usb3_pll_ratio = { - .clk_ref = 0x1e, - .calset_1 = 0xa6, - .calset_2 = 0xaa, - .calset_3 = 0xaa, - .calset_4 = 0x04, - .cal_ctrl = 0x00, -}; - -struct miphy28lp_pll_gen { - int bank; - int speed; - int bias_boost_1; - int bias_boost_2; - int tx_ctrl_1; - int tx_ctrl_2; - int tx_ctrl_3; - int rx_k_gain; - int rx_vga_gain; - int rx_equ_gain_1; - int rx_equ_gain_2; - int rx_equ_gain_3; - int rx_buff_ctrl; -}; - -static struct miphy28lp_pll_gen sata_pll_gen[] = { - { - .bank = 0x00, - .speed = TX_SPDSEL_80DEC | RX_SPDSEL_80DEC, - .bias_boost_1 = 0x00, - .bias_boost_2 = 0xae, - .tx_ctrl_2 = 0x53, - .tx_ctrl_3 = 0x00, - .rx_buff_ctrl = EQ_BOOST_GAIN | EQ_DC_GAIN | VGA_GAIN, - .rx_vga_gain = 0x00, - .rx_equ_gain_1 = 0x7d, - .rx_equ_gain_2 = 0x56, - .rx_equ_gain_3 = 0x00, - }, - { - .bank = 0x01, - .speed = TX_SPDSEL_40DEC | RX_SPDSEL_40DEC, - .bias_boost_1 = 0x00, - .bias_boost_2 = 0xae, - .tx_ctrl_2 = 0x72, - .tx_ctrl_3 = 0x20, - .rx_buff_ctrl = EQ_BOOST_GAIN | EQ_DC_GAIN | VGA_GAIN, - .rx_vga_gain = 0x00, - .rx_equ_gain_1 = 0x7d, - .rx_equ_gain_2 = 0x56, - .rx_equ_gain_3 = 0x00, - }, - { - .bank = 0x02, - .speed = TX_SPDSEL_20DEC | RX_SPDSEL_20DEC, - .bias_boost_1 = 0x00, - .bias_boost_2 = 0xae, - .tx_ctrl_2 = 0xc0, - .tx_ctrl_3 = 0x20, - .rx_buff_ctrl = EQ_BOOST_GAIN | EQ_DC_GAIN | VGA_GAIN, - .rx_vga_gain = 0x00, - .rx_equ_gain_1 = 0x7d, - .rx_equ_gain_2 = 0x56, - .rx_equ_gain_3 = 0x00, - }, -}; - -static struct miphy28lp_pll_gen pcie_pll_gen[] = { - { - .bank = 0x00, - .speed = TX_SPDSEL_40DEC | RX_SPDSEL_40DEC, - .bias_boost_1 = 0x00, - .bias_boost_2 = 0xa5, - .tx_ctrl_1 = TX_REG_STEP_N_25MV, - .tx_ctrl_2 = 0x71, - .tx_ctrl_3 = 0x60, - .rx_k_gain = 0x98, - .rx_buff_ctrl = EQ_BOOST_GAIN | EQ_DC_GAIN | VGA_GAIN, - .rx_vga_gain = 0x00, - .rx_equ_gain_1 = 0x79, - .rx_equ_gain_2 = 0x56, - }, - { - .bank = 0x01, - .speed = TX_SPDSEL_20DEC | RX_SPDSEL_20DEC, - .bias_boost_1 = 0x00, - .bias_boost_2 = 0xa5, - .tx_ctrl_1 = TX_REG_STEP_N_25MV, - .tx_ctrl_2 = 0x70, - .tx_ctrl_3 = 0x60, - .rx_k_gain = 0xcc, - .rx_buff_ctrl = EQ_BOOST_GAIN | EQ_DC_GAIN | VGA_GAIN, - .rx_vga_gain = 0x00, - .rx_equ_gain_1 = 0x78, - .rx_equ_gain_2 = 0x07, - }, -}; - -static inline void miphy28lp_set_reset(struct miphy28lp_phy *miphy_phy) -{ - void __iomem *base = miphy_phy->base; - u8 val; - - /* Putting Macro in reset */ - writeb_relaxed(RST_APPLI_SW, base + MIPHY_CONF_RESET); - - val = RST_APPLI_SW | RST_CONF_SW; - writeb_relaxed(val, base + MIPHY_CONF_RESET); - - writeb_relaxed(RST_APPLI_SW, base + MIPHY_CONF_RESET); - - /* Bringing the MIPHY-CPU registers out of reset */ - if (miphy_phy->type == PHY_TYPE_PCIE) { - val = AUTO_RST_RX | TERM_EN_SW; - writeb_relaxed(val, base + MIPHY_CONTROL); - } else { - val = AUTO_RST_RX | TERM_EN_SW | DIS_LINK_RST; - writeb_relaxed(val, base + MIPHY_CONTROL); - } -} - -static inline void miphy28lp_pll_calibration(struct miphy28lp_phy *miphy_phy, - struct pll_ratio *pll_ratio) -{ - void __iomem *base = miphy_phy->base; - u8 val; - - /* Applying PLL Settings */ - writeb_relaxed(0x1d, base + MIPHY_PLL_SPAREIN); - writeb_relaxed(pll_ratio->clk_ref, base + MIPHY_PLL_CLKREF_FREQ); - - /* PLL Ratio */ - writeb_relaxed(pll_ratio->calset_1, base + MIPHY_PLL_CALSET_1); - writeb_relaxed(pll_ratio->calset_2, base + MIPHY_PLL_CALSET_2); - writeb_relaxed(pll_ratio->calset_3, base + MIPHY_PLL_CALSET_3); - writeb_relaxed(pll_ratio->calset_4, base + MIPHY_PLL_CALSET_4); - writeb_relaxed(pll_ratio->cal_ctrl, base + MIPHY_PLL_CALSET_CTRL); - - writeb_relaxed(TX_SEL, base + MIPHY_BOUNDARY_SEL); - - val = (0x68 << 1) | TX_SLEW_CAL_MAN_EN; - writeb_relaxed(val, base + MIPHY_TX_CAL_MAN); - - val = VGA_OFFSET_POLARITY | CAL_OFFSET_THRESHOLD_64 | CAL_OFFSET_VGA_64; - - if (miphy_phy->type != PHY_TYPE_SATA) - val |= OFFSET_COMPENSATION_EN; - - writeb_relaxed(val, base + MIPHY_RX_CAL_OFFSET_CTRL); - - if (miphy_phy->type == PHY_TYPE_USB3) { - writeb_relaxed(0x00, base + MIPHY_CONF); - writeb_relaxed(0x70, base + MIPHY_RX_LOCK_STEP); - writeb_relaxed(EN_FIRST_HALF, base + MIPHY_RX_SIGDET_SLEEP_OA); - writeb_relaxed(EN_FIRST_HALF, base + MIPHY_RX_SIGDET_SLEEP_SEL); - writeb_relaxed(EN_FIRST_HALF, base + MIPHY_RX_SIGDET_WAIT_SEL); - - val = EN_DIGIT_SIGNAL_CHECK | EN_FIRST_HALF; - writeb_relaxed(val, base + MIPHY_RX_SIGDET_DATA_SEL); - } - -} - -static inline void miphy28lp_sata_config_gen(struct miphy28lp_phy *miphy_phy) -{ - void __iomem *base = miphy_phy->base; - int i; - - for (i = 0; i < ARRAY_SIZE(sata_pll_gen); i++) { - struct miphy28lp_pll_gen *gen = &sata_pll_gen[i]; - - /* Banked settings */ - writeb_relaxed(gen->bank, base + MIPHY_CONF); - writeb_relaxed(gen->speed, base + MIPHY_SPEED); - writeb_relaxed(gen->bias_boost_1, base + MIPHY_BIAS_BOOST_1); - writeb_relaxed(gen->bias_boost_2, base + MIPHY_BIAS_BOOST_2); - - /* TX buffer Settings */ - writeb_relaxed(gen->tx_ctrl_2, base + MIPHY_TX_CTRL_2); - writeb_relaxed(gen->tx_ctrl_3, base + MIPHY_TX_CTRL_3); - - /* RX Buffer Settings */ - writeb_relaxed(gen->rx_buff_ctrl, base + MIPHY_RX_BUFFER_CTRL); - writeb_relaxed(gen->rx_vga_gain, base + MIPHY_RX_VGA_GAIN); - writeb_relaxed(gen->rx_equ_gain_1, base + MIPHY_RX_EQU_GAIN_1); - writeb_relaxed(gen->rx_equ_gain_2, base + MIPHY_RX_EQU_GAIN_2); - writeb_relaxed(gen->rx_equ_gain_3, base + MIPHY_RX_EQU_GAIN_3); - } -} - -static inline void miphy28lp_pcie_config_gen(struct miphy28lp_phy *miphy_phy) -{ - void __iomem *base = miphy_phy->base; - int i; - - for (i = 0; i < ARRAY_SIZE(pcie_pll_gen); i++) { - struct miphy28lp_pll_gen *gen = &pcie_pll_gen[i]; - - /* Banked settings */ - writeb_relaxed(gen->bank, base + MIPHY_CONF); - writeb_relaxed(gen->speed, base + MIPHY_SPEED); - writeb_relaxed(gen->bias_boost_1, base + MIPHY_BIAS_BOOST_1); - writeb_relaxed(gen->bias_boost_2, base + MIPHY_BIAS_BOOST_2); - - /* TX buffer Settings */ - writeb_relaxed(gen->tx_ctrl_1, base + MIPHY_TX_CTRL_1); - writeb_relaxed(gen->tx_ctrl_2, base + MIPHY_TX_CTRL_2); - writeb_relaxed(gen->tx_ctrl_3, base + MIPHY_TX_CTRL_3); - - writeb_relaxed(gen->rx_k_gain, base + MIPHY_RX_K_GAIN); - - /* RX Buffer Settings */ - writeb_relaxed(gen->rx_buff_ctrl, base + MIPHY_RX_BUFFER_CTRL); - writeb_relaxed(gen->rx_vga_gain, base + MIPHY_RX_VGA_GAIN); - writeb_relaxed(gen->rx_equ_gain_1, base + MIPHY_RX_EQU_GAIN_1); - writeb_relaxed(gen->rx_equ_gain_2, base + MIPHY_RX_EQU_GAIN_2); - } -} - -static inline int miphy28lp_wait_compensation(struct miphy28lp_phy *miphy_phy) -{ - unsigned long finish = jiffies + 5 * HZ; - u8 val; - - /* Waiting for Compensation to complete */ - do { - val = readb_relaxed(miphy_phy->base + MIPHY_COMP_FSM_6); - - if (time_after_eq(jiffies, finish)) - return -EBUSY; - cpu_relax(); - } while (!(val & COMP_DONE)); - - return 0; -} - - -static inline int miphy28lp_compensation(struct miphy28lp_phy *miphy_phy, - struct pll_ratio *pll_ratio) -{ - void __iomem *base = miphy_phy->base; - - /* Poll for HFC ready after reset release */ - /* Compensation measurement */ - writeb_relaxed(RST_PLL_SW | RST_COMP_SW, base + MIPHY_RESET); - - writeb_relaxed(0x00, base + MIPHY_PLL_COMMON_MISC_2); - writeb_relaxed(pll_ratio->clk_ref, base + MIPHY_PLL_CLKREF_FREQ); - writeb_relaxed(COMP_START, base + MIPHY_COMP_FSM_1); - - if (miphy_phy->type == PHY_TYPE_PCIE) - writeb_relaxed(RST_PLL_SW, base + MIPHY_RESET); - - writeb_relaxed(0x00, base + MIPHY_RESET); - writeb_relaxed(START_ACT_FILT, base + MIPHY_PLL_COMMON_MISC_2); - writeb_relaxed(SET_NEW_CHANGE, base + MIPHY_PLL_SBR_1); - - /* TX compensation offset to re-center TX impedance */ - writeb_relaxed(0x00, base + MIPHY_COMP_POSTP); - - if (miphy_phy->type == PHY_TYPE_PCIE) - return miphy28lp_wait_compensation(miphy_phy); - - return 0; -} - -static inline void miphy28_usb3_miphy_reset(struct miphy28lp_phy *miphy_phy) -{ - void __iomem *base = miphy_phy->base; - u8 val; - - /* MIPHY Reset */ - writeb_relaxed(RST_APPLI_SW, base + MIPHY_CONF_RESET); - writeb_relaxed(0x00, base + MIPHY_CONF_RESET); - writeb_relaxed(RST_COMP_SW, base + MIPHY_RESET); - - val = RST_COMP_SW | RST_PLL_SW; - writeb_relaxed(val, base + MIPHY_RESET); - - writeb_relaxed(0x00, base + MIPHY_PLL_COMMON_MISC_2); - writeb_relaxed(0x1e, base + MIPHY_PLL_CLKREF_FREQ); - writeb_relaxed(COMP_START, base + MIPHY_COMP_FSM_1); - writeb_relaxed(RST_PLL_SW, base + MIPHY_RESET); - writeb_relaxed(0x00, base + MIPHY_RESET); - writeb_relaxed(START_ACT_FILT, base + MIPHY_PLL_COMMON_MISC_2); - writeb_relaxed(0x00, base + MIPHY_CONF); - writeb_relaxed(0x00, base + MIPHY_BOUNDARY_1); - writeb_relaxed(0x00, base + MIPHY_TST_BIAS_BOOST_2); - writeb_relaxed(0x00, base + MIPHY_CONF); - writeb_relaxed(SET_NEW_CHANGE, base + MIPHY_PLL_SBR_1); - writeb_relaxed(0xa5, base + MIPHY_DEBUG_BUS); - writeb_relaxed(0x00, base + MIPHY_CONF); -} - -static void miphy_sata_tune_ssc(struct miphy28lp_phy *miphy_phy) -{ - void __iomem *base = miphy_phy->base; - u8 val; - - /* Compensate Tx impedance to avoid out of range values */ - /* - * Enable the SSC on PLL for all banks - * SSC Modulation @ 31 KHz and 4000 ppm modulation amp - */ - val = readb_relaxed(base + MIPHY_BOUNDARY_2); - val |= SSC_EN_SW; - writeb_relaxed(val, base + MIPHY_BOUNDARY_2); - - val = readb_relaxed(base + MIPHY_BOUNDARY_SEL); - val |= SSC_SEL; - writeb_relaxed(val, base + MIPHY_BOUNDARY_SEL); - - for (val = 0; val < MIPHY_SATA_BANK_NB; val++) { - writeb_relaxed(val, base + MIPHY_CONF); - - /* Add value to each reference clock cycle */ - /* and define the period length of the SSC */ - writeb_relaxed(0x3c, base + MIPHY_PLL_SBR_2); - writeb_relaxed(0x6c, base + MIPHY_PLL_SBR_3); - writeb_relaxed(0x81, base + MIPHY_PLL_SBR_4); - - /* Clear any previous request */ - writeb_relaxed(0x00, base + MIPHY_PLL_SBR_1); - - /* requests the PLL to take in account new parameters */ - writeb_relaxed(SET_NEW_CHANGE, base + MIPHY_PLL_SBR_1); - - /* To be sure there is no other pending requests */ - writeb_relaxed(0x00, base + MIPHY_PLL_SBR_1); - } -} - -static void miphy_pcie_tune_ssc(struct miphy28lp_phy *miphy_phy) -{ - void __iomem *base = miphy_phy->base; - u8 val; - - /* Compensate Tx impedance to avoid out of range values */ - /* - * Enable the SSC on PLL for all banks - * SSC Modulation @ 31 KHz and 4000 ppm modulation amp - */ - val = readb_relaxed(base + MIPHY_BOUNDARY_2); - val |= SSC_EN_SW; - writeb_relaxed(val, base + MIPHY_BOUNDARY_2); - - val = readb_relaxed(base + MIPHY_BOUNDARY_SEL); - val |= SSC_SEL; - writeb_relaxed(val, base + MIPHY_BOUNDARY_SEL); - - for (val = 0; val < MIPHY_PCIE_BANK_NB; val++) { - writeb_relaxed(val, base + MIPHY_CONF); - - /* Validate Step component */ - writeb_relaxed(0x69, base + MIPHY_PLL_SBR_3); - writeb_relaxed(0x21, base + MIPHY_PLL_SBR_4); - - /* Validate Period component */ - writeb_relaxed(0x3c, base + MIPHY_PLL_SBR_2); - writeb_relaxed(0x21, base + MIPHY_PLL_SBR_4); - - /* Clear any previous request */ - writeb_relaxed(0x00, base + MIPHY_PLL_SBR_1); - - /* requests the PLL to take in account new parameters */ - writeb_relaxed(SET_NEW_CHANGE, base + MIPHY_PLL_SBR_1); - - /* To be sure there is no other pending requests */ - writeb_relaxed(0x00, base + MIPHY_PLL_SBR_1); - } -} - -static inline void miphy_tune_tx_impedance(struct miphy28lp_phy *miphy_phy) -{ - /* Compensate Tx impedance to avoid out of range values */ - writeb_relaxed(0x02, miphy_phy->base + MIPHY_COMP_POSTP); -} - -static inline int miphy28lp_configure_sata(struct miphy28lp_phy *miphy_phy) -{ - void __iomem *base = miphy_phy->base; - int err; - u8 val; - - /* Putting Macro in reset */ - miphy28lp_set_reset(miphy_phy); - - /* PLL calibration */ - miphy28lp_pll_calibration(miphy_phy, &sata_pll_ratio); - - /* Banked settings Gen1/Gen2/Gen3 */ - miphy28lp_sata_config_gen(miphy_phy); - - /* Power control */ - /* Input bridge enable, manual input bridge control */ - writeb_relaxed(0x21, base + MIPHY_RX_POWER_CTRL_1); - - /* Macro out of reset */ - writeb_relaxed(0x00, base + MIPHY_CONF_RESET); - - /* Poll for HFC ready after reset release */ - /* Compensation measurement */ - err = miphy28lp_compensation(miphy_phy, &sata_pll_ratio); - if (err) - return err; - - if (miphy_phy->px_rx_pol_inv) { - /* Invert Rx polarity */ - val = readb_relaxed(miphy_phy->base + MIPHY_CONTROL); - val |= PX_RX_POL; - writeb_relaxed(val, miphy_phy->base + MIPHY_CONTROL); - } - - if (miphy_phy->ssc) - miphy_sata_tune_ssc(miphy_phy); - - if (miphy_phy->tx_impedance) - miphy_tune_tx_impedance(miphy_phy); - - return 0; -} - -static inline int miphy28lp_configure_pcie(struct miphy28lp_phy *miphy_phy) -{ - void __iomem *base = miphy_phy->base; - int err; - - /* Putting Macro in reset */ - miphy28lp_set_reset(miphy_phy); - - /* PLL calibration */ - miphy28lp_pll_calibration(miphy_phy, &pcie_pll_ratio); - - /* Banked settings Gen1/Gen2 */ - miphy28lp_pcie_config_gen(miphy_phy); - - /* Power control */ - /* Input bridge enable, manual input bridge control */ - writeb_relaxed(0x21, base + MIPHY_RX_POWER_CTRL_1); - - /* Macro out of reset */ - writeb_relaxed(0x00, base + MIPHY_CONF_RESET); - - /* Poll for HFC ready after reset release */ - /* Compensation measurement */ - err = miphy28lp_compensation(miphy_phy, &pcie_pll_ratio); - if (err) - return err; - - if (miphy_phy->ssc) - miphy_pcie_tune_ssc(miphy_phy); - - if (miphy_phy->tx_impedance) - miphy_tune_tx_impedance(miphy_phy); - - return 0; -} - - -static inline void miphy28lp_configure_usb3(struct miphy28lp_phy *miphy_phy) -{ - void __iomem *base = miphy_phy->base; - u8 val; - - /* Putting Macro in reset */ - miphy28lp_set_reset(miphy_phy); - - /* PLL calibration */ - miphy28lp_pll_calibration(miphy_phy, &usb3_pll_ratio); - - /* Writing The Speed Rate */ - writeb_relaxed(0x00, base + MIPHY_CONF); - - val = RX_SPDSEL_20DEC | TX_SPDSEL_20DEC; - writeb_relaxed(val, base + MIPHY_SPEED); - - /* RX Channel compensation and calibration */ - writeb_relaxed(0x1c, base + MIPHY_RX_LOCK_SETTINGS_OPT); - writeb_relaxed(0x51, base + MIPHY_RX_CAL_CTRL_1); - writeb_relaxed(0x70, base + MIPHY_RX_CAL_CTRL_2); - - val = OFFSET_COMPENSATION_EN | VGA_OFFSET_POLARITY | - CAL_OFFSET_THRESHOLD_64 | CAL_OFFSET_VGA_64; - writeb_relaxed(val, base + MIPHY_RX_CAL_OFFSET_CTRL); - writeb_relaxed(0x22, base + MIPHY_RX_CAL_VGA_STEP); - writeb_relaxed(0x0e, base + MIPHY_RX_CAL_OPT_LENGTH); - - val = EQ_DC_GAIN | VGA_GAIN; - writeb_relaxed(val, base + MIPHY_RX_BUFFER_CTRL); - writeb_relaxed(0x78, base + MIPHY_RX_EQU_GAIN_1); - writeb_relaxed(0x1b, base + MIPHY_SYNCHAR_CONTROL); - - /* TX compensation offset to re-center TX impedance */ - writeb_relaxed(0x02, base + MIPHY_COMP_POSTP); - - /* Enable GENSEL_SEL and SSC */ - /* TX_SEL=0 swing preemp forced by pipe registres */ - val = SSC_SEL | GENSEL_SEL; - writeb_relaxed(val, base + MIPHY_BOUNDARY_SEL); - - /* MIPHY Bias boost */ - writeb_relaxed(0x00, base + MIPHY_BIAS_BOOST_1); - writeb_relaxed(0xa7, base + MIPHY_BIAS_BOOST_2); - - /* SSC modulation */ - writeb_relaxed(SSC_EN_SW, base + MIPHY_BOUNDARY_2); - - /* MIPHY TX control */ - writeb_relaxed(0x00, base + MIPHY_CONF); - - /* Validate Step component */ - writeb_relaxed(0x5a, base + MIPHY_PLL_SBR_3); - writeb_relaxed(0xa0, base + MIPHY_PLL_SBR_4); - - /* Validate Period component */ - writeb_relaxed(0x3c, base + MIPHY_PLL_SBR_2); - writeb_relaxed(0xa1, base + MIPHY_PLL_SBR_4); - - /* Clear any previous request */ - writeb_relaxed(0x00, base + MIPHY_PLL_SBR_1); - - /* requests the PLL to take in account new parameters */ - writeb_relaxed(0x02, base + MIPHY_PLL_SBR_1); - - /* To be sure there is no other pending requests */ - writeb_relaxed(0x00, base + MIPHY_PLL_SBR_1); - - /* Rx PI controller settings */ - writeb_relaxed(0xca, base + MIPHY_RX_K_GAIN); - - /* MIPHY RX input bridge control */ - /* INPUT_BRIDGE_EN_SW=1, manual input bridge control[0]=1 */ - writeb_relaxed(0x21, base + MIPHY_RX_POWER_CTRL_1); - writeb_relaxed(0x29, base + MIPHY_RX_POWER_CTRL_1); - writeb_relaxed(0x1a, base + MIPHY_RX_POWER_CTRL_2); - - /* MIPHY Reset for usb3 */ - miphy28_usb3_miphy_reset(miphy_phy); -} - -static inline int miphy_is_ready(struct miphy28lp_phy *miphy_phy) -{ - unsigned long finish = jiffies + 5 * HZ; - u8 mask = HFC_PLL | HFC_RDY; - u8 val; - - /* - * For PCIe and USB3 check only that PLL and HFC are ready - * For SATA check also that phy is ready! - */ - if (miphy_phy->type == PHY_TYPE_SATA) - mask |= PHY_RDY; - - do { - val = readb_relaxed(miphy_phy->base + MIPHY_STATUS_1); - if ((val & mask) != mask) - cpu_relax(); - else - return 0; - } while (!time_after_eq(jiffies, finish)); - - return -EBUSY; -} - -static int miphy_osc_is_ready(struct miphy28lp_phy *miphy_phy) -{ - struct miphy28lp_dev *miphy_dev = miphy_phy->phydev; - unsigned long finish = jiffies + 5 * HZ; - u32 val; - - if (!miphy_phy->osc_rdy) - return 0; - - if (!miphy_phy->syscfg_reg[SYSCFG_STATUS]) - return -EINVAL; - - do { - regmap_read(miphy_dev->regmap, - miphy_phy->syscfg_reg[SYSCFG_STATUS], &val); - - if ((val & MIPHY_OSC_RDY) != MIPHY_OSC_RDY) - cpu_relax(); - else - return 0; - } while (!time_after_eq(jiffies, finish)); - - return -EBUSY; -} - -static int miphy28lp_get_resource_byname(struct device_node *child, - char *rname, struct resource *res) -{ - int index; - - index = of_property_match_string(child, "reg-names", rname); - if (index < 0) - return -ENODEV; - - return of_address_to_resource(child, index, res); -} - -static int miphy28lp_get_one_addr(struct device *dev, - struct device_node *child, char *rname, - void __iomem **base) -{ - struct resource res; - int ret; - - ret = miphy28lp_get_resource_byname(child, rname, &res); - if (!ret) { - *base = devm_ioremap(dev, res.start, resource_size(&res)); - if (!*base) { - dev_err(dev, "failed to ioremap %s address region\n" - , rname); - return -ENOENT; - } - } - - return 0; -} - -/* MiPHY reset and sysconf setup */ -static int miphy28lp_setup(struct miphy28lp_phy *miphy_phy, u32 miphy_val) -{ - int err; - struct miphy28lp_dev *miphy_dev = miphy_phy->phydev; - - if (!miphy_phy->syscfg_reg[SYSCFG_CTRL]) - return -EINVAL; - - err = reset_control_assert(miphy_phy->miphy_rst); - if (err) { - dev_err(miphy_dev->dev, "unable to bring out of miphy reset\n"); - return err; - } - - if (miphy_phy->osc_force_ext) - miphy_val |= MIPHY_OSC_FORCE_EXT; - - regmap_update_bits(miphy_dev->regmap, - miphy_phy->syscfg_reg[SYSCFG_CTRL], - MIPHY_CTRL_MASK, miphy_val); - - err = reset_control_deassert(miphy_phy->miphy_rst); - if (err) { - dev_err(miphy_dev->dev, "unable to bring out of miphy reset\n"); - return err; - } - - return miphy_osc_is_ready(miphy_phy); -} - -static int miphy28lp_init_sata(struct miphy28lp_phy *miphy_phy) -{ - struct miphy28lp_dev *miphy_dev = miphy_phy->phydev; - int err, sata_conf = SATA_CTRL_SELECT_SATA; - - if ((!miphy_phy->syscfg_reg[SYSCFG_SATA]) || - (!miphy_phy->syscfg_reg[SYSCFG_PCI]) || - (!miphy_phy->base)) - return -EINVAL; - - dev_info(miphy_dev->dev, "sata-up mode, addr 0x%p\n", miphy_phy->base); - - /* Configure the glue-logic */ - sata_conf |= ((miphy_phy->sata_gen - SATA_GEN1) << SATA_SPDMODE); - - regmap_update_bits(miphy_dev->regmap, - miphy_phy->syscfg_reg[SYSCFG_SATA], - SATA_CTRL_MASK, sata_conf); - - regmap_update_bits(miphy_dev->regmap, miphy_phy->syscfg_reg[SYSCFG_PCI], - PCIE_CTRL_MASK, SATA_CTRL_SELECT_PCIE); - - /* MiPHY path and clocking init */ - err = miphy28lp_setup(miphy_phy, MIPHY_CTRL_DEFAULT); - - if (err) { - dev_err(miphy_dev->dev, "SATA phy setup failed\n"); - return err; - } - - /* initialize miphy */ - miphy28lp_configure_sata(miphy_phy); - - return miphy_is_ready(miphy_phy); -} - -static int miphy28lp_init_pcie(struct miphy28lp_phy *miphy_phy) -{ - struct miphy28lp_dev *miphy_dev = miphy_phy->phydev; - int err; - - if ((!miphy_phy->syscfg_reg[SYSCFG_SATA]) || - (!miphy_phy->syscfg_reg[SYSCFG_PCI]) - || (!miphy_phy->base) || (!miphy_phy->pipebase)) - return -EINVAL; - - dev_info(miphy_dev->dev, "pcie-up mode, addr 0x%p\n", miphy_phy->base); - - /* Configure the glue-logic */ - regmap_update_bits(miphy_dev->regmap, - miphy_phy->syscfg_reg[SYSCFG_SATA], - SATA_CTRL_MASK, SATA_CTRL_SELECT_PCIE); - - regmap_update_bits(miphy_dev->regmap, miphy_phy->syscfg_reg[SYSCFG_PCI], - PCIE_CTRL_MASK, SYSCFG_PCIE_PCIE_VAL); - - /* MiPHY path and clocking init */ - err = miphy28lp_setup(miphy_phy, MIPHY_CTRL_DEFAULT); - - if (err) { - dev_err(miphy_dev->dev, "PCIe phy setup failed\n"); - return err; - } - - /* initialize miphy */ - err = miphy28lp_configure_pcie(miphy_phy); - if (err) - return err; - - /* PIPE Wrapper Configuration */ - writeb_relaxed(0x68, miphy_phy->pipebase + 0x104); /* Rise_0 */ - writeb_relaxed(0x61, miphy_phy->pipebase + 0x105); /* Rise_1 */ - writeb_relaxed(0x68, miphy_phy->pipebase + 0x108); /* Fall_0 */ - writeb_relaxed(0x61, miphy_phy->pipebase + 0x109); /* Fall-1 */ - writeb_relaxed(0x68, miphy_phy->pipebase + 0x10c); /* Threshold_0 */ - writeb_relaxed(0x60, miphy_phy->pipebase + 0x10d); /* Threshold_1 */ - - /* Wait for phy_ready */ - return miphy_is_ready(miphy_phy); -} - -static int miphy28lp_init_usb3(struct miphy28lp_phy *miphy_phy) -{ - struct miphy28lp_dev *miphy_dev = miphy_phy->phydev; - int err; - - if ((!miphy_phy->base) || (!miphy_phy->pipebase)) - return -EINVAL; - - dev_info(miphy_dev->dev, "usb3-up mode, addr 0x%p\n", miphy_phy->base); - - /* MiPHY path and clocking init */ - err = miphy28lp_setup(miphy_phy, MIPHY_CTRL_SYNC_D_EN); - if (err) { - dev_err(miphy_dev->dev, "USB3 phy setup failed\n"); - return err; - } - - /* initialize miphy */ - miphy28lp_configure_usb3(miphy_phy); - - /* PIPE Wrapper Configuration */ - writeb_relaxed(0x68, miphy_phy->pipebase + 0x23); - writeb_relaxed(0x61, miphy_phy->pipebase + 0x24); - writeb_relaxed(0x68, miphy_phy->pipebase + 0x26); - writeb_relaxed(0x61, miphy_phy->pipebase + 0x27); - writeb_relaxed(0x18, miphy_phy->pipebase + 0x29); - writeb_relaxed(0x61, miphy_phy->pipebase + 0x2a); - - /* pipe Wrapper usb3 TX swing de-emph margin PREEMPH[7:4], SWING[3:0] */ - writeb_relaxed(0X67, miphy_phy->pipebase + 0x68); - writeb_relaxed(0x0d, miphy_phy->pipebase + 0x69); - writeb_relaxed(0X67, miphy_phy->pipebase + 0x6a); - writeb_relaxed(0X0d, miphy_phy->pipebase + 0x6b); - writeb_relaxed(0X67, miphy_phy->pipebase + 0x6c); - writeb_relaxed(0X0d, miphy_phy->pipebase + 0x6d); - writeb_relaxed(0X67, miphy_phy->pipebase + 0x6e); - writeb_relaxed(0X0d, miphy_phy->pipebase + 0x6f); - - return miphy_is_ready(miphy_phy); -} - -static int miphy28lp_init(struct phy *phy) -{ - struct miphy28lp_phy *miphy_phy = phy_get_drvdata(phy); - struct miphy28lp_dev *miphy_dev = miphy_phy->phydev; - int ret; - - mutex_lock(&miphy_dev->miphy_mutex); - - switch (miphy_phy->type) { - - case PHY_TYPE_SATA: - ret = miphy28lp_init_sata(miphy_phy); - break; - case PHY_TYPE_PCIE: - ret = miphy28lp_init_pcie(miphy_phy); - break; - case PHY_TYPE_USB3: - ret = miphy28lp_init_usb3(miphy_phy); - break; - default: - ret = -EINVAL; - break; - } - - mutex_unlock(&miphy_dev->miphy_mutex); - - return ret; -} - -static int miphy28lp_get_addr(struct miphy28lp_phy *miphy_phy) -{ - struct miphy28lp_dev *miphy_dev = miphy_phy->phydev; - struct device_node *phynode = miphy_phy->phy->dev.of_node; - int err; - - if ((miphy_phy->type != PHY_TYPE_SATA) && - (miphy_phy->type != PHY_TYPE_PCIE) && - (miphy_phy->type != PHY_TYPE_USB3)) { - return -EINVAL; - } - - err = miphy28lp_get_one_addr(miphy_dev->dev, phynode, - PHY_TYPE_name[miphy_phy->type - PHY_TYPE_SATA], - &miphy_phy->base); - if (err) - return err; - - if ((miphy_phy->type == PHY_TYPE_PCIE) || - (miphy_phy->type == PHY_TYPE_USB3)) { - err = miphy28lp_get_one_addr(miphy_dev->dev, phynode, "pipew", - &miphy_phy->pipebase); - if (err) - return err; - } - - return 0; -} - -static struct phy *miphy28lp_xlate(struct device *dev, - struct of_phandle_args *args) -{ - struct miphy28lp_dev *miphy_dev = dev_get_drvdata(dev); - struct miphy28lp_phy *miphy_phy = NULL; - struct device_node *phynode = args->np; - int ret, index = 0; - - if (args->args_count != 1) { - dev_err(dev, "Invalid number of cells in 'phy' property\n"); - return ERR_PTR(-EINVAL); - } - - for (index = 0; index < miphy_dev->nphys; index++) - if (phynode == miphy_dev->phys[index]->phy->dev.of_node) { - miphy_phy = miphy_dev->phys[index]; - break; - } - - if (!miphy_phy) { - dev_err(dev, "Failed to find appropriate phy\n"); - return ERR_PTR(-EINVAL); - } - - miphy_phy->type = args->args[0]; - - ret = miphy28lp_get_addr(miphy_phy); - if (ret < 0) - return ERR_PTR(ret); - - return miphy_phy->phy; -} - -static const struct phy_ops miphy28lp_ops = { - .init = miphy28lp_init, - .owner = THIS_MODULE, -}; - -static int miphy28lp_probe_resets(struct device_node *node, - struct miphy28lp_phy *miphy_phy) -{ - struct miphy28lp_dev *miphy_dev = miphy_phy->phydev; - int err; - - miphy_phy->miphy_rst = - of_reset_control_get_shared(node, "miphy-sw-rst"); - - if (IS_ERR(miphy_phy->miphy_rst)) { - dev_err(miphy_dev->dev, - "miphy soft reset control not defined\n"); - return PTR_ERR(miphy_phy->miphy_rst); - } - - err = reset_control_deassert(miphy_phy->miphy_rst); - if (err) { - dev_err(miphy_dev->dev, "unable to bring out of miphy reset\n"); - return err; - } - - return 0; -} - -static int miphy28lp_of_probe(struct device_node *np, - struct miphy28lp_phy *miphy_phy) -{ - int i; - u32 ctrlreg; - - miphy_phy->osc_force_ext = - of_property_read_bool(np, "st,osc-force-ext"); - - miphy_phy->osc_rdy = of_property_read_bool(np, "st,osc-rdy"); - - miphy_phy->px_rx_pol_inv = - of_property_read_bool(np, "st,px_rx_pol_inv"); - - miphy_phy->ssc = of_property_read_bool(np, "st,ssc-on"); - - miphy_phy->tx_impedance = - of_property_read_bool(np, "st,tx-impedance-comp"); - - of_property_read_u32(np, "st,sata-gen", &miphy_phy->sata_gen); - if (!miphy_phy->sata_gen) - miphy_phy->sata_gen = SATA_GEN1; - - for (i = 0; i < SYSCFG_REG_MAX; i++) { - if (!of_property_read_u32_index(np, "st,syscfg", i, &ctrlreg)) - miphy_phy->syscfg_reg[i] = ctrlreg; - } - - return 0; -} - -static int miphy28lp_probe(struct platform_device *pdev) -{ - struct device_node *child, *np = pdev->dev.of_node; - struct miphy28lp_dev *miphy_dev; - struct phy_provider *provider; - struct phy *phy; - int ret, port = 0; - - miphy_dev = devm_kzalloc(&pdev->dev, sizeof(*miphy_dev), GFP_KERNEL); - if (!miphy_dev) - return -ENOMEM; - - miphy_dev->nphys = of_get_child_count(np); - miphy_dev->phys = devm_kcalloc(&pdev->dev, miphy_dev->nphys, - sizeof(*miphy_dev->phys), GFP_KERNEL); - if (!miphy_dev->phys) - return -ENOMEM; - - miphy_dev->regmap = syscon_regmap_lookup_by_phandle(np, "st,syscfg"); - if (IS_ERR(miphy_dev->regmap)) { - dev_err(miphy_dev->dev, "No syscfg phandle specified\n"); - return PTR_ERR(miphy_dev->regmap); - } - - miphy_dev->dev = &pdev->dev; - - dev_set_drvdata(&pdev->dev, miphy_dev); - - mutex_init(&miphy_dev->miphy_mutex); - - for_each_child_of_node(np, child) { - struct miphy28lp_phy *miphy_phy; - - miphy_phy = devm_kzalloc(&pdev->dev, sizeof(*miphy_phy), - GFP_KERNEL); - if (!miphy_phy) { - ret = -ENOMEM; - goto put_child; - } - - miphy_dev->phys[port] = miphy_phy; - - phy = devm_phy_create(&pdev->dev, child, &miphy28lp_ops); - if (IS_ERR(phy)) { - dev_err(&pdev->dev, "failed to create PHY\n"); - ret = PTR_ERR(phy); - goto put_child; - } - - miphy_dev->phys[port]->phy = phy; - miphy_dev->phys[port]->phydev = miphy_dev; - - ret = miphy28lp_of_probe(child, miphy_phy); - if (ret) - goto put_child; - - ret = miphy28lp_probe_resets(child, miphy_dev->phys[port]); - if (ret) - goto put_child; - - phy_set_drvdata(phy, miphy_dev->phys[port]); - port++; - - } - - provider = devm_of_phy_provider_register(&pdev->dev, miphy28lp_xlate); - return PTR_ERR_OR_ZERO(provider); -put_child: - of_node_put(child); - return ret; -} - -static const struct of_device_id miphy28lp_of_match[] = { - {.compatible = "st,miphy28lp-phy", }, - {}, -}; - -MODULE_DEVICE_TABLE(of, miphy28lp_of_match); - -static struct platform_driver miphy28lp_driver = { - .probe = miphy28lp_probe, - .driver = { - .name = "miphy28lp-phy", - .of_match_table = miphy28lp_of_match, - } -}; - -module_platform_driver(miphy28lp_driver); - -MODULE_AUTHOR("Alexandre Torgue "); -MODULE_DESCRIPTION("STMicroelectronics miphy28lp driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-mvebu-sata.c b/drivers/phy/phy-mvebu-sata.c deleted file mode 100644 index 768ce92e81ce..000000000000 --- a/drivers/phy/phy-mvebu-sata.c +++ /dev/null @@ -1,138 +0,0 @@ -/* - * phy-mvebu-sata.c: SATA Phy driver for the Marvell mvebu SoCs. - * - * Copyright (C) 2013 Andrew Lunn - * - * 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; either version - * 2 of the License, or (at your option) any later version. - */ - -#include -#include -#include -#include -#include -#include - -struct priv { - struct clk *clk; - void __iomem *base; -}; - -#define SATA_PHY_MODE_2 0x0330 -#define MODE_2_FORCE_PU_TX BIT(0) -#define MODE_2_FORCE_PU_RX BIT(1) -#define MODE_2_PU_PLL BIT(2) -#define MODE_2_PU_IVREF BIT(3) -#define SATA_IF_CTRL 0x0050 -#define CTRL_PHY_SHUTDOWN BIT(9) - -static int phy_mvebu_sata_power_on(struct phy *phy) -{ - struct priv *priv = phy_get_drvdata(phy); - u32 reg; - - clk_prepare_enable(priv->clk); - - /* Enable PLL and IVREF */ - reg = readl(priv->base + SATA_PHY_MODE_2); - reg |= (MODE_2_FORCE_PU_TX | MODE_2_FORCE_PU_RX | - MODE_2_PU_PLL | MODE_2_PU_IVREF); - writel(reg , priv->base + SATA_PHY_MODE_2); - - /* Enable PHY */ - reg = readl(priv->base + SATA_IF_CTRL); - reg &= ~CTRL_PHY_SHUTDOWN; - writel(reg, priv->base + SATA_IF_CTRL); - - clk_disable_unprepare(priv->clk); - - return 0; -} - -static int phy_mvebu_sata_power_off(struct phy *phy) -{ - struct priv *priv = phy_get_drvdata(phy); - u32 reg; - - clk_prepare_enable(priv->clk); - - /* Disable PLL and IVREF */ - reg = readl(priv->base + SATA_PHY_MODE_2); - reg &= ~(MODE_2_FORCE_PU_TX | MODE_2_FORCE_PU_RX | - MODE_2_PU_PLL | MODE_2_PU_IVREF); - writel(reg, priv->base + SATA_PHY_MODE_2); - - /* Disable PHY */ - reg = readl(priv->base + SATA_IF_CTRL); - reg |= CTRL_PHY_SHUTDOWN; - writel(reg, priv->base + SATA_IF_CTRL); - - clk_disable_unprepare(priv->clk); - - return 0; -} - -static const struct phy_ops phy_mvebu_sata_ops = { - .power_on = phy_mvebu_sata_power_on, - .power_off = phy_mvebu_sata_power_off, - .owner = THIS_MODULE, -}; - -static int phy_mvebu_sata_probe(struct platform_device *pdev) -{ - struct phy_provider *phy_provider; - struct resource *res; - struct priv *priv; - struct phy *phy; - - priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - priv->base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(priv->base)) - return PTR_ERR(priv->base); - - priv->clk = devm_clk_get(&pdev->dev, "sata"); - if (IS_ERR(priv->clk)) - return PTR_ERR(priv->clk); - - phy = devm_phy_create(&pdev->dev, NULL, &phy_mvebu_sata_ops); - if (IS_ERR(phy)) - return PTR_ERR(phy); - - phy_set_drvdata(phy, priv); - - phy_provider = devm_of_phy_provider_register(&pdev->dev, - of_phy_simple_xlate); - if (IS_ERR(phy_provider)) - return PTR_ERR(phy_provider); - - /* The boot loader may of left it on. Turn it off. */ - phy_mvebu_sata_power_off(phy); - - return 0; -} - -static const struct of_device_id phy_mvebu_sata_of_match[] = { - { .compatible = "marvell,mvebu-sata-phy" }, - { }, -}; -MODULE_DEVICE_TABLE(of, phy_mvebu_sata_of_match); - -static struct platform_driver phy_mvebu_sata_driver = { - .probe = phy_mvebu_sata_probe, - .driver = { - .name = "phy-mvebu-sata", - .of_match_table = phy_mvebu_sata_of_match, - } -}; -module_platform_driver(phy_mvebu_sata_driver); - -MODULE_AUTHOR("Andrew Lunn "); -MODULE_DESCRIPTION("Marvell MVEBU SATA PHY driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-omap-control.c b/drivers/phy/phy-omap-control.c deleted file mode 100644 index e9c41b3fa0ee..000000000000 --- a/drivers/phy/phy-omap-control.c +++ /dev/null @@ -1,360 +0,0 @@ -/* - * omap-control-phy.c - The PHY part of control module. - * - * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com - * 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; either version 2 of the License, or - * (at your option) any later version. - * - * Author: Kishon Vijay Abraham I - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/** - * omap_control_pcie_pcs - set the PCS delay count - * @dev: the control module device - * @delay: 8 bit delay value - */ -void omap_control_pcie_pcs(struct device *dev, u8 delay) -{ - u32 val; - struct omap_control_phy *control_phy; - - if (IS_ERR(dev) || !dev) { - pr_err("%s: invalid device\n", __func__); - return; - } - - control_phy = dev_get_drvdata(dev); - if (!control_phy) { - dev_err(dev, "%s: invalid control phy device\n", __func__); - return; - } - - if (control_phy->type != OMAP_CTRL_TYPE_PCIE) { - dev_err(dev, "%s: unsupported operation\n", __func__); - return; - } - - val = readl(control_phy->pcie_pcs); - val &= ~(OMAP_CTRL_PCIE_PCS_MASK << - OMAP_CTRL_PCIE_PCS_DELAY_COUNT_SHIFT); - val |= (delay << OMAP_CTRL_PCIE_PCS_DELAY_COUNT_SHIFT); - writel(val, control_phy->pcie_pcs); -} -EXPORT_SYMBOL_GPL(omap_control_pcie_pcs); - -/** - * omap_control_phy_power - power on/off the phy using control module reg - * @dev: the control module device - * @on: 0 or 1, based on powering on or off the PHY - */ -void omap_control_phy_power(struct device *dev, int on) -{ - u32 val; - unsigned long rate; - struct omap_control_phy *control_phy; - - if (IS_ERR(dev) || !dev) { - pr_err("%s: invalid device\n", __func__); - return; - } - - control_phy = dev_get_drvdata(dev); - if (!control_phy) { - dev_err(dev, "%s: invalid control phy device\n", __func__); - return; - } - - if (control_phy->type == OMAP_CTRL_TYPE_OTGHS) - return; - - val = readl(control_phy->power); - - switch (control_phy->type) { - case OMAP_CTRL_TYPE_USB2: - if (on) - val &= ~OMAP_CTRL_DEV_PHY_PD; - else - val |= OMAP_CTRL_DEV_PHY_PD; - break; - - case OMAP_CTRL_TYPE_PCIE: - case OMAP_CTRL_TYPE_PIPE3: - rate = clk_get_rate(control_phy->sys_clk); - rate = rate/1000000; - - if (on) { - val &= ~(OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_MASK | - OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_MASK); - val |= OMAP_CTRL_PIPE3_PHY_TX_RX_POWERON << - OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT; - val |= rate << - OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_SHIFT; - } else { - val &= ~OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_MASK; - val |= OMAP_CTRL_PIPE3_PHY_TX_RX_POWEROFF << - OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT; - } - break; - - case OMAP_CTRL_TYPE_DRA7USB2: - if (on) - val &= ~OMAP_CTRL_USB2_PHY_PD; - else - val |= OMAP_CTRL_USB2_PHY_PD; - break; - - case OMAP_CTRL_TYPE_AM437USB2: - if (on) { - val &= ~(AM437X_CTRL_USB2_PHY_PD | - AM437X_CTRL_USB2_OTG_PD); - val |= (AM437X_CTRL_USB2_OTGVDET_EN | - AM437X_CTRL_USB2_OTGSESSEND_EN); - } else { - val &= ~(AM437X_CTRL_USB2_OTGVDET_EN | - AM437X_CTRL_USB2_OTGSESSEND_EN); - val |= (AM437X_CTRL_USB2_PHY_PD | - AM437X_CTRL_USB2_OTG_PD); - } - break; - default: - dev_err(dev, "%s: type %d not recognized\n", - __func__, control_phy->type); - break; - } - - writel(val, control_phy->power); -} -EXPORT_SYMBOL_GPL(omap_control_phy_power); - -/** - * omap_control_usb_host_mode - set AVALID, VBUSVALID and ID pin in grounded - * @ctrl_phy: struct omap_control_phy * - * - * Writes to the mailbox register to notify the usb core that a usb - * device has been connected. - */ -static void omap_control_usb_host_mode(struct omap_control_phy *ctrl_phy) -{ - u32 val; - - val = readl(ctrl_phy->otghs_control); - val &= ~(OMAP_CTRL_DEV_IDDIG | OMAP_CTRL_DEV_SESSEND); - val |= OMAP_CTRL_DEV_AVALID | OMAP_CTRL_DEV_VBUSVALID; - writel(val, ctrl_phy->otghs_control); -} - -/** - * omap_control_usb_device_mode - set AVALID, VBUSVALID and ID pin in high - * impedance - * @ctrl_phy: struct omap_control_phy * - * - * Writes to the mailbox register to notify the usb core that it has been - * connected to a usb host. - */ -static void omap_control_usb_device_mode(struct omap_control_phy *ctrl_phy) -{ - u32 val; - - val = readl(ctrl_phy->otghs_control); - val &= ~OMAP_CTRL_DEV_SESSEND; - val |= OMAP_CTRL_DEV_IDDIG | OMAP_CTRL_DEV_AVALID | - OMAP_CTRL_DEV_VBUSVALID; - writel(val, ctrl_phy->otghs_control); -} - -/** - * omap_control_usb_set_sessionend - Enable SESSIONEND and IDIG to high - * impedance - * @ctrl_phy: struct omap_control_phy * - * - * Writes to the mailbox register to notify the usb core it's now in - * disconnected state. - */ -static void omap_control_usb_set_sessionend(struct omap_control_phy *ctrl_phy) -{ - u32 val; - - val = readl(ctrl_phy->otghs_control); - val &= ~(OMAP_CTRL_DEV_AVALID | OMAP_CTRL_DEV_VBUSVALID); - val |= OMAP_CTRL_DEV_IDDIG | OMAP_CTRL_DEV_SESSEND; - writel(val, ctrl_phy->otghs_control); -} - -/** - * omap_control_usb_set_mode - Calls to functions to set USB in one of host mode - * or device mode or to denote disconnected state - * @dev: the control module device - * @mode: The mode to which usb should be configured - * - * This is an API to write to the mailbox register to notify the usb core that - * a usb device has been connected. - */ -void omap_control_usb_set_mode(struct device *dev, - enum omap_control_usb_mode mode) -{ - struct omap_control_phy *ctrl_phy; - - if (IS_ERR(dev) || !dev) - return; - - ctrl_phy = dev_get_drvdata(dev); - if (!ctrl_phy) { - dev_err(dev, "Invalid control phy device\n"); - return; - } - - if (ctrl_phy->type != OMAP_CTRL_TYPE_OTGHS) - return; - - switch (mode) { - case USB_MODE_HOST: - omap_control_usb_host_mode(ctrl_phy); - break; - case USB_MODE_DEVICE: - omap_control_usb_device_mode(ctrl_phy); - break; - case USB_MODE_DISCONNECT: - omap_control_usb_set_sessionend(ctrl_phy); - break; - default: - dev_vdbg(dev, "invalid omap control usb mode\n"); - } -} -EXPORT_SYMBOL_GPL(omap_control_usb_set_mode); - -static const enum omap_control_phy_type otghs_data = OMAP_CTRL_TYPE_OTGHS; -static const enum omap_control_phy_type usb2_data = OMAP_CTRL_TYPE_USB2; -static const enum omap_control_phy_type pipe3_data = OMAP_CTRL_TYPE_PIPE3; -static const enum omap_control_phy_type pcie_data = OMAP_CTRL_TYPE_PCIE; -static const enum omap_control_phy_type dra7usb2_data = OMAP_CTRL_TYPE_DRA7USB2; -static const enum omap_control_phy_type am437usb2_data = OMAP_CTRL_TYPE_AM437USB2; - -static const struct of_device_id omap_control_phy_id_table[] = { - { - .compatible = "ti,control-phy-otghs", - .data = &otghs_data, - }, - { - .compatible = "ti,control-phy-usb2", - .data = &usb2_data, - }, - { - .compatible = "ti,control-phy-pipe3", - .data = &pipe3_data, - }, - { - .compatible = "ti,control-phy-pcie", - .data = &pcie_data, - }, - { - .compatible = "ti,control-phy-usb2-dra7", - .data = &dra7usb2_data, - }, - { - .compatible = "ti,control-phy-usb2-am437", - .data = &am437usb2_data, - }, - {}, -}; -MODULE_DEVICE_TABLE(of, omap_control_phy_id_table); - -static int omap_control_phy_probe(struct platform_device *pdev) -{ - struct resource *res; - const struct of_device_id *of_id; - struct omap_control_phy *control_phy; - - of_id = of_match_device(omap_control_phy_id_table, &pdev->dev); - if (!of_id) - return -EINVAL; - - control_phy = devm_kzalloc(&pdev->dev, sizeof(*control_phy), - GFP_KERNEL); - if (!control_phy) - return -ENOMEM; - - control_phy->dev = &pdev->dev; - control_phy->type = *(enum omap_control_phy_type *)of_id->data; - - if (control_phy->type == OMAP_CTRL_TYPE_OTGHS) { - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, - "otghs_control"); - control_phy->otghs_control = devm_ioremap_resource( - &pdev->dev, res); - if (IS_ERR(control_phy->otghs_control)) - return PTR_ERR(control_phy->otghs_control); - } else { - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, - "power"); - control_phy->power = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(control_phy->power)) { - dev_err(&pdev->dev, "Couldn't get power register\n"); - return PTR_ERR(control_phy->power); - } - } - - if (control_phy->type == OMAP_CTRL_TYPE_PIPE3 || - control_phy->type == OMAP_CTRL_TYPE_PCIE) { - control_phy->sys_clk = devm_clk_get(control_phy->dev, - "sys_clkin"); - if (IS_ERR(control_phy->sys_clk)) { - pr_err("%s: unable to get sys_clkin\n", __func__); - return -EINVAL; - } - } - - if (control_phy->type == OMAP_CTRL_TYPE_PCIE) { - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, - "pcie_pcs"); - control_phy->pcie_pcs = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(control_phy->pcie_pcs)) - return PTR_ERR(control_phy->pcie_pcs); - } - - dev_set_drvdata(control_phy->dev, control_phy); - - return 0; -} - -static struct platform_driver omap_control_phy_driver = { - .probe = omap_control_phy_probe, - .driver = { - .name = "omap-control-phy", - .of_match_table = omap_control_phy_id_table, - }, -}; - -static int __init omap_control_phy_init(void) -{ - return platform_driver_register(&omap_control_phy_driver); -} -subsys_initcall(omap_control_phy_init); - -static void __exit omap_control_phy_exit(void) -{ - platform_driver_unregister(&omap_control_phy_driver); -} -module_exit(omap_control_phy_exit); - -MODULE_ALIAS("platform:omap_control_phy"); -MODULE_AUTHOR("Texas Instruments Inc."); -MODULE_DESCRIPTION("OMAP Control Module PHY Driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-omap-usb2.c b/drivers/phy/phy-omap-usb2.c deleted file mode 100644 index fe909fd8144f..000000000000 --- a/drivers/phy/phy-omap-usb2.c +++ /dev/null @@ -1,439 +0,0 @@ -/* - * omap-usb2.c - USB PHY, talking to musb controller in OMAP. - * - * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com - * 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; either version 2 of the License, or - * (at your option) any later version. - * - * Author: Kishon Vijay Abraham I - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define USB2PHY_DISCON_BYP_LATCH (1 << 31) -#define USB2PHY_ANA_CONFIG1 0x4c - -/** - * omap_usb2_set_comparator - links the comparator present in the sytem with - * this phy - * @comparator - the companion phy(comparator) for this phy - * - * The phy companion driver should call this API passing the phy_companion - * filled with set_vbus and start_srp to be used by usb phy. - * - * For use by phy companion driver - */ -int omap_usb2_set_comparator(struct phy_companion *comparator) -{ - struct omap_usb *phy; - struct usb_phy *x = usb_get_phy(USB_PHY_TYPE_USB2); - - if (IS_ERR(x)) - return -ENODEV; - - phy = phy_to_omapusb(x); - phy->comparator = comparator; - return 0; -} -EXPORT_SYMBOL_GPL(omap_usb2_set_comparator); - -static int omap_usb_set_vbus(struct usb_otg *otg, bool enabled) -{ - struct omap_usb *phy = phy_to_omapusb(otg->usb_phy); - - if (!phy->comparator) - return -ENODEV; - - return phy->comparator->set_vbus(phy->comparator, enabled); -} - -static int omap_usb_start_srp(struct usb_otg *otg) -{ - struct omap_usb *phy = phy_to_omapusb(otg->usb_phy); - - if (!phy->comparator) - return -ENODEV; - - return phy->comparator->start_srp(phy->comparator); -} - -static int omap_usb_set_host(struct usb_otg *otg, struct usb_bus *host) -{ - otg->host = host; - if (!host) - otg->state = OTG_STATE_UNDEFINED; - - return 0; -} - -static int omap_usb_set_peripheral(struct usb_otg *otg, - struct usb_gadget *gadget) -{ - otg->gadget = gadget; - if (!gadget) - otg->state = OTG_STATE_UNDEFINED; - - return 0; -} - -static int omap_usb_phy_power(struct omap_usb *phy, int on) -{ - u32 val; - int ret; - - if (!phy->syscon_phy_power) { - omap_control_phy_power(phy->control_dev, on); - return 0; - } - - if (on) - val = phy->power_on; - else - val = phy->power_off; - - ret = regmap_update_bits(phy->syscon_phy_power, phy->power_reg, - phy->mask, val); - return ret; -} - -static int omap_usb_power_off(struct phy *x) -{ - struct omap_usb *phy = phy_get_drvdata(x); - - return omap_usb_phy_power(phy, false); -} - -static int omap_usb_power_on(struct phy *x) -{ - struct omap_usb *phy = phy_get_drvdata(x); - - return omap_usb_phy_power(phy, true); -} - -static int omap_usb2_disable_clocks(struct omap_usb *phy) -{ - clk_disable(phy->wkupclk); - if (!IS_ERR(phy->optclk)) - clk_disable(phy->optclk); - - return 0; -} - -static int omap_usb2_enable_clocks(struct omap_usb *phy) -{ - int ret; - - ret = clk_enable(phy->wkupclk); - if (ret < 0) { - dev_err(phy->dev, "Failed to enable wkupclk %d\n", ret); - goto err0; - } - - if (!IS_ERR(phy->optclk)) { - ret = clk_enable(phy->optclk); - if (ret < 0) { - dev_err(phy->dev, "Failed to enable optclk %d\n", ret); - goto err1; - } - } - - return 0; - -err1: - clk_disable(phy->wkupclk); - -err0: - return ret; -} - -static int omap_usb_init(struct phy *x) -{ - struct omap_usb *phy = phy_get_drvdata(x); - u32 val; - - omap_usb2_enable_clocks(phy); - - if (phy->flags & OMAP_USB2_CALIBRATE_FALSE_DISCONNECT) { - /* - * - * Reduce the sensitivity of internal PHY by enabling the - * DISCON_BYP_LATCH of the USB2PHY_ANA_CONFIG1 register. This - * resolves issues with certain devices which can otherwise - * be prone to false disconnects. - * - */ - val = omap_usb_readl(phy->phy_base, USB2PHY_ANA_CONFIG1); - val |= USB2PHY_DISCON_BYP_LATCH; - omap_usb_writel(phy->phy_base, USB2PHY_ANA_CONFIG1, val); - } - - return 0; -} - -static int omap_usb_exit(struct phy *x) -{ - struct omap_usb *phy = phy_get_drvdata(x); - - return omap_usb2_disable_clocks(phy); -} - -static const struct phy_ops ops = { - .init = omap_usb_init, - .exit = omap_usb_exit, - .power_on = omap_usb_power_on, - .power_off = omap_usb_power_off, - .owner = THIS_MODULE, -}; - -static const struct usb_phy_data omap_usb2_data = { - .label = "omap_usb2", - .flags = OMAP_USB2_HAS_START_SRP | OMAP_USB2_HAS_SET_VBUS, - .mask = OMAP_DEV_PHY_PD, - .power_off = OMAP_DEV_PHY_PD, -}; - -static const struct usb_phy_data omap5_usb2_data = { - .label = "omap5_usb2", - .flags = 0, - .mask = OMAP_DEV_PHY_PD, - .power_off = OMAP_DEV_PHY_PD, -}; - -static const struct usb_phy_data dra7x_usb2_data = { - .label = "dra7x_usb2", - .flags = OMAP_USB2_CALIBRATE_FALSE_DISCONNECT, - .mask = OMAP_DEV_PHY_PD, - .power_off = OMAP_DEV_PHY_PD, -}; - -static const struct usb_phy_data dra7x_usb2_phy2_data = { - .label = "dra7x_usb2_phy2", - .flags = OMAP_USB2_CALIBRATE_FALSE_DISCONNECT, - .mask = OMAP_USB2_PHY_PD, - .power_off = OMAP_USB2_PHY_PD, -}; - -static const struct usb_phy_data am437x_usb2_data = { - .label = "am437x_usb2", - .flags = 0, - .mask = AM437X_USB2_PHY_PD | AM437X_USB2_OTG_PD | - AM437X_USB2_OTGVDET_EN | AM437X_USB2_OTGSESSEND_EN, - .power_on = AM437X_USB2_OTGVDET_EN | AM437X_USB2_OTGSESSEND_EN, - .power_off = AM437X_USB2_PHY_PD | AM437X_USB2_OTG_PD, -}; - -static const struct of_device_id omap_usb2_id_table[] = { - { - .compatible = "ti,omap-usb2", - .data = &omap_usb2_data, - }, - { - .compatible = "ti,omap5-usb2", - .data = &omap5_usb2_data, - }, - { - .compatible = "ti,dra7x-usb2", - .data = &dra7x_usb2_data, - }, - { - .compatible = "ti,dra7x-usb2-phy2", - .data = &dra7x_usb2_phy2_data, - }, - { - .compatible = "ti,am437x-usb2", - .data = &am437x_usb2_data, - }, - {}, -}; -MODULE_DEVICE_TABLE(of, omap_usb2_id_table); - -static int omap_usb2_probe(struct platform_device *pdev) -{ - struct omap_usb *phy; - struct phy *generic_phy; - struct resource *res; - struct phy_provider *phy_provider; - struct usb_otg *otg; - struct device_node *node = pdev->dev.of_node; - struct device_node *control_node; - struct platform_device *control_pdev; - const struct of_device_id *of_id; - struct usb_phy_data *phy_data; - - of_id = of_match_device(omap_usb2_id_table, &pdev->dev); - - if (!of_id) - return -EINVAL; - - phy_data = (struct usb_phy_data *)of_id->data; - - phy = devm_kzalloc(&pdev->dev, sizeof(*phy), GFP_KERNEL); - if (!phy) - return -ENOMEM; - - otg = devm_kzalloc(&pdev->dev, sizeof(*otg), GFP_KERNEL); - if (!otg) - return -ENOMEM; - - phy->dev = &pdev->dev; - - phy->phy.dev = phy->dev; - phy->phy.label = phy_data->label; - phy->phy.otg = otg; - phy->phy.type = USB_PHY_TYPE_USB2; - phy->mask = phy_data->mask; - phy->power_on = phy_data->power_on; - phy->power_off = phy_data->power_off; - - if (phy_data->flags & OMAP_USB2_CALIBRATE_FALSE_DISCONNECT) { - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - phy->phy_base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(phy->phy_base)) - return PTR_ERR(phy->phy_base); - phy->flags |= OMAP_USB2_CALIBRATE_FALSE_DISCONNECT; - } - - phy->syscon_phy_power = syscon_regmap_lookup_by_phandle(node, - "syscon-phy-power"); - if (IS_ERR(phy->syscon_phy_power)) { - dev_dbg(&pdev->dev, - "can't get syscon-phy-power, using control device\n"); - phy->syscon_phy_power = NULL; - - control_node = of_parse_phandle(node, "ctrl-module", 0); - if (!control_node) { - dev_err(&pdev->dev, - "Failed to get control device phandle\n"); - return -EINVAL; - } - - control_pdev = of_find_device_by_node(control_node); - if (!control_pdev) { - dev_err(&pdev->dev, "Failed to get control device\n"); - return -EINVAL; - } - phy->control_dev = &control_pdev->dev; - } else { - if (of_property_read_u32_index(node, - "syscon-phy-power", 1, - &phy->power_reg)) { - dev_err(&pdev->dev, - "couldn't get power reg. offset\n"); - return -EINVAL; - } - } - - otg->set_host = omap_usb_set_host; - otg->set_peripheral = omap_usb_set_peripheral; - if (phy_data->flags & OMAP_USB2_HAS_SET_VBUS) - otg->set_vbus = omap_usb_set_vbus; - if (phy_data->flags & OMAP_USB2_HAS_START_SRP) - otg->start_srp = omap_usb_start_srp; - otg->usb_phy = &phy->phy; - - platform_set_drvdata(pdev, phy); - pm_runtime_enable(phy->dev); - - generic_phy = devm_phy_create(phy->dev, NULL, &ops); - if (IS_ERR(generic_phy)) { - pm_runtime_disable(phy->dev); - return PTR_ERR(generic_phy); - } - - phy_set_drvdata(generic_phy, phy); - omap_usb_power_off(generic_phy); - - phy_provider = devm_of_phy_provider_register(phy->dev, - of_phy_simple_xlate); - if (IS_ERR(phy_provider)) { - pm_runtime_disable(phy->dev); - return PTR_ERR(phy_provider); - } - - phy->wkupclk = devm_clk_get(phy->dev, "wkupclk"); - if (IS_ERR(phy->wkupclk)) { - dev_warn(&pdev->dev, "unable to get wkupclk, trying old name\n"); - phy->wkupclk = devm_clk_get(phy->dev, "usb_phy_cm_clk32k"); - if (IS_ERR(phy->wkupclk)) { - dev_err(&pdev->dev, "unable to get usb_phy_cm_clk32k\n"); - pm_runtime_disable(phy->dev); - return PTR_ERR(phy->wkupclk); - } else { - dev_warn(&pdev->dev, - "found usb_phy_cm_clk32k, please fix DTS\n"); - } - } - clk_prepare(phy->wkupclk); - - phy->optclk = devm_clk_get(phy->dev, "refclk"); - if (IS_ERR(phy->optclk)) { - dev_dbg(&pdev->dev, "unable to get refclk, trying old name\n"); - phy->optclk = devm_clk_get(phy->dev, "usb_otg_ss_refclk960m"); - if (IS_ERR(phy->optclk)) { - dev_dbg(&pdev->dev, - "unable to get usb_otg_ss_refclk960m\n"); - } else { - dev_warn(&pdev->dev, - "found usb_otg_ss_refclk960m, please fix DTS\n"); - } - } - - if (!IS_ERR(phy->optclk)) - clk_prepare(phy->optclk); - - usb_add_phy_dev(&phy->phy); - - return 0; -} - -static int omap_usb2_remove(struct platform_device *pdev) -{ - struct omap_usb *phy = platform_get_drvdata(pdev); - - clk_unprepare(phy->wkupclk); - if (!IS_ERR(phy->optclk)) - clk_unprepare(phy->optclk); - usb_remove_phy(&phy->phy); - pm_runtime_disable(phy->dev); - - return 0; -} - -static struct platform_driver omap_usb2_driver = { - .probe = omap_usb2_probe, - .remove = omap_usb2_remove, - .driver = { - .name = "omap-usb2", - .of_match_table = omap_usb2_id_table, - }, -}; - -module_platform_driver(omap_usb2_driver); - -MODULE_ALIAS("platform:omap_usb2"); -MODULE_AUTHOR("Texas Instruments Inc."); -MODULE_DESCRIPTION("OMAP USB2 phy driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-pxa-28nm-hsic.c b/drivers/phy/phy-pxa-28nm-hsic.c deleted file mode 100644 index 234aacf4db20..000000000000 --- a/drivers/phy/phy-pxa-28nm-hsic.c +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Copyright (C) 2015 Linaro, Ltd. - * Rob Herring - * - * Based on vendor driver: - * Copyright (C) 2013 Marvell Inc. - * Author: Chao Xie - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define PHY_28NM_HSIC_CTRL 0x08 -#define PHY_28NM_HSIC_IMPCAL_CAL 0x18 -#define PHY_28NM_HSIC_PLL_CTRL01 0x1c -#define PHY_28NM_HSIC_PLL_CTRL2 0x20 -#define PHY_28NM_HSIC_INT 0x28 - -#define PHY_28NM_HSIC_PLL_SELLPFR_SHIFT 26 -#define PHY_28NM_HSIC_PLL_FBDIV_SHIFT 0 -#define PHY_28NM_HSIC_PLL_REFDIV_SHIFT 9 - -#define PHY_28NM_HSIC_S2H_PU_PLL BIT(10) -#define PHY_28NM_HSIC_H2S_PLL_LOCK BIT(15) -#define PHY_28NM_HSIC_S2H_HSIC_EN BIT(7) -#define S2H_DRV_SE0_4RESUME BIT(14) -#define PHY_28NM_HSIC_H2S_IMPCAL_DONE BIT(27) - -#define PHY_28NM_HSIC_CONNECT_INT BIT(1) -#define PHY_28NM_HSIC_HS_READY_INT BIT(2) - -struct mv_hsic_phy { - struct phy *phy; - struct platform_device *pdev; - void __iomem *base; - struct clk *clk; -}; - -static bool wait_for_reg(void __iomem *reg, u32 mask, unsigned long timeout) -{ - timeout += jiffies; - while (time_is_after_eq_jiffies(timeout)) { - if ((readl(reg) & mask) == mask) - return true; - msleep(1); - } - return false; -} - -static int mv_hsic_phy_init(struct phy *phy) -{ - struct mv_hsic_phy *mv_phy = phy_get_drvdata(phy); - struct platform_device *pdev = mv_phy->pdev; - void __iomem *base = mv_phy->base; - - clk_prepare_enable(mv_phy->clk); - - /* Set reference clock */ - writel(0x1 << PHY_28NM_HSIC_PLL_SELLPFR_SHIFT | - 0xf0 << PHY_28NM_HSIC_PLL_FBDIV_SHIFT | - 0xd << PHY_28NM_HSIC_PLL_REFDIV_SHIFT, - base + PHY_28NM_HSIC_PLL_CTRL01); - - /* Turn on PLL */ - writel(readl(base + PHY_28NM_HSIC_PLL_CTRL2) | - PHY_28NM_HSIC_S2H_PU_PLL, - base + PHY_28NM_HSIC_PLL_CTRL2); - - /* Make sure PHY PLL is locked */ - if (!wait_for_reg(base + PHY_28NM_HSIC_PLL_CTRL2, - PHY_28NM_HSIC_H2S_PLL_LOCK, HZ / 10)) { - dev_err(&pdev->dev, "HSIC PHY PLL not locked after 100mS."); - clk_disable_unprepare(mv_phy->clk); - return -ETIMEDOUT; - } - - return 0; -} - -static int mv_hsic_phy_power_on(struct phy *phy) -{ - struct mv_hsic_phy *mv_phy = phy_get_drvdata(phy); - struct platform_device *pdev = mv_phy->pdev; - void __iomem *base = mv_phy->base; - u32 reg; - - reg = readl(base + PHY_28NM_HSIC_CTRL); - /* Avoid SE0 state when resume for some device will take it as reset */ - reg &= ~S2H_DRV_SE0_4RESUME; - reg |= PHY_28NM_HSIC_S2H_HSIC_EN; /* Enable HSIC PHY */ - writel(reg, base + PHY_28NM_HSIC_CTRL); - - /* - * Calibration Timing - * ____________________________ - * CAL START ___| - * ____________________ - * CAL_DONE ___________| - * | 400us | - */ - - /* Make sure PHY Calibration is ready */ - if (!wait_for_reg(base + PHY_28NM_HSIC_IMPCAL_CAL, - PHY_28NM_HSIC_H2S_IMPCAL_DONE, HZ / 10)) { - dev_warn(&pdev->dev, "HSIC PHY READY not set after 100mS."); - return -ETIMEDOUT; - } - - /* Waiting for HSIC connect int*/ - if (!wait_for_reg(base + PHY_28NM_HSIC_INT, - PHY_28NM_HSIC_CONNECT_INT, HZ / 5)) { - dev_warn(&pdev->dev, "HSIC wait for connect interrupt timeout."); - return -ETIMEDOUT; - } - - return 0; -} - -static int mv_hsic_phy_power_off(struct phy *phy) -{ - struct mv_hsic_phy *mv_phy = phy_get_drvdata(phy); - void __iomem *base = mv_phy->base; - - writel(readl(base + PHY_28NM_HSIC_CTRL) & ~PHY_28NM_HSIC_S2H_HSIC_EN, - base + PHY_28NM_HSIC_CTRL); - - return 0; -} - -static int mv_hsic_phy_exit(struct phy *phy) -{ - struct mv_hsic_phy *mv_phy = phy_get_drvdata(phy); - void __iomem *base = mv_phy->base; - - /* Turn off PLL */ - writel(readl(base + PHY_28NM_HSIC_PLL_CTRL2) & - ~PHY_28NM_HSIC_S2H_PU_PLL, - base + PHY_28NM_HSIC_PLL_CTRL2); - - clk_disable_unprepare(mv_phy->clk); - return 0; -} - - -static const struct phy_ops hsic_ops = { - .init = mv_hsic_phy_init, - .power_on = mv_hsic_phy_power_on, - .power_off = mv_hsic_phy_power_off, - .exit = mv_hsic_phy_exit, - .owner = THIS_MODULE, -}; - -static int mv_hsic_phy_probe(struct platform_device *pdev) -{ - struct phy_provider *phy_provider; - struct mv_hsic_phy *mv_phy; - struct resource *r; - - mv_phy = devm_kzalloc(&pdev->dev, sizeof(*mv_phy), GFP_KERNEL); - if (!mv_phy) - return -ENOMEM; - - mv_phy->pdev = pdev; - - mv_phy->clk = devm_clk_get(&pdev->dev, NULL); - if (IS_ERR(mv_phy->clk)) { - dev_err(&pdev->dev, "failed to get clock.\n"); - return PTR_ERR(mv_phy->clk); - } - - r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - mv_phy->base = devm_ioremap_resource(&pdev->dev, r); - if (IS_ERR(mv_phy->base)) - return PTR_ERR(mv_phy->base); - - mv_phy->phy = devm_phy_create(&pdev->dev, pdev->dev.of_node, &hsic_ops); - if (IS_ERR(mv_phy->phy)) - return PTR_ERR(mv_phy->phy); - - phy_set_drvdata(mv_phy->phy, mv_phy); - - phy_provider = devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate); - return PTR_ERR_OR_ZERO(phy_provider); -} - -static const struct of_device_id mv_hsic_phy_dt_match[] = { - { .compatible = "marvell,pxa1928-hsic-phy", }, - {}, -}; -MODULE_DEVICE_TABLE(of, mv_hsic_phy_dt_match); - -static struct platform_driver mv_hsic_phy_driver = { - .probe = mv_hsic_phy_probe, - .driver = { - .name = "mv-hsic-phy", - .of_match_table = of_match_ptr(mv_hsic_phy_dt_match), - }, -}; -module_platform_driver(mv_hsic_phy_driver); - -MODULE_AUTHOR("Rob Herring "); -MODULE_DESCRIPTION("Marvell HSIC phy driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-pxa-28nm-usb2.c b/drivers/phy/phy-pxa-28nm-usb2.c deleted file mode 100644 index 37e9c8ca4983..000000000000 --- a/drivers/phy/phy-pxa-28nm-usb2.c +++ /dev/null @@ -1,355 +0,0 @@ -/* - * Copyright (C) 2015 Linaro, Ltd. - * Rob Herring - * - * Based on vendor driver: - * Copyright (C) 2013 Marvell Inc. - * Author: Chao Xie - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* USB PXA1928 PHY mapping */ -#define PHY_28NM_PLL_REG0 0x0 -#define PHY_28NM_PLL_REG1 0x4 -#define PHY_28NM_CAL_REG 0x8 -#define PHY_28NM_TX_REG0 0x0c -#define PHY_28NM_TX_REG1 0x10 -#define PHY_28NM_RX_REG0 0x14 -#define PHY_28NM_RX_REG1 0x18 -#define PHY_28NM_DIG_REG0 0x1c -#define PHY_28NM_DIG_REG1 0x20 -#define PHY_28NM_TEST_REG0 0x24 -#define PHY_28NM_TEST_REG1 0x28 -#define PHY_28NM_MOC_REG 0x2c -#define PHY_28NM_PHY_RESERVE 0x30 -#define PHY_28NM_OTG_REG 0x34 -#define PHY_28NM_CHRG_DET 0x38 -#define PHY_28NM_CTRL_REG0 0xc4 -#define PHY_28NM_CTRL_REG1 0xc8 -#define PHY_28NM_CTRL_REG2 0xd4 -#define PHY_28NM_CTRL_REG3 0xdc - -/* PHY_28NM_PLL_REG0 */ -#define PHY_28NM_PLL_READY BIT(31) - -#define PHY_28NM_PLL_SELLPFR_SHIFT 28 -#define PHY_28NM_PLL_SELLPFR_MASK (0x3 << 28) - -#define PHY_28NM_PLL_FBDIV_SHIFT 16 -#define PHY_28NM_PLL_FBDIV_MASK (0x1ff << 16) - -#define PHY_28NM_PLL_ICP_SHIFT 8 -#define PHY_28NM_PLL_ICP_MASK (0x7 << 8) - -#define PHY_28NM_PLL_REFDIV_SHIFT 0 -#define PHY_28NM_PLL_REFDIV_MASK 0x7f - -/* PHY_28NM_PLL_REG1 */ -#define PHY_28NM_PLL_PU_BY_REG BIT(1) - -#define PHY_28NM_PLL_PU_PLL BIT(0) - -/* PHY_28NM_CAL_REG */ -#define PHY_28NM_PLL_PLLCAL_DONE BIT(31) - -#define PHY_28NM_PLL_IMPCAL_DONE BIT(23) - -#define PHY_28NM_PLL_KVCO_SHIFT 16 -#define PHY_28NM_PLL_KVCO_MASK (0x7 << 16) - -#define PHY_28NM_PLL_CAL12_SHIFT 20 -#define PHY_28NM_PLL_CAL12_MASK (0x3 << 20) - -#define PHY_28NM_IMPCAL_VTH_SHIFT 8 -#define PHY_28NM_IMPCAL_VTH_MASK (0x7 << 8) - -#define PHY_28NM_PLLCAL_START_SHIFT 22 -#define PHY_28NM_IMPCAL_START_SHIFT 13 - -/* PHY_28NM_TX_REG0 */ -#define PHY_28NM_TX_PU_BY_REG BIT(25) - -#define PHY_28NM_TX_PU_ANA BIT(24) - -#define PHY_28NM_TX_AMP_SHIFT 20 -#define PHY_28NM_TX_AMP_MASK (0x7 << 20) - -/* PHY_28NM_RX_REG0 */ -#define PHY_28NM_RX_SQ_THRESH_SHIFT 0 -#define PHY_28NM_RX_SQ_THRESH_MASK (0xf << 0) - -/* PHY_28NM_RX_REG1 */ -#define PHY_28NM_RX_SQCAL_DONE BIT(31) - -/* PHY_28NM_DIG_REG0 */ -#define PHY_28NM_DIG_BITSTAFFING_ERR BIT(31) -#define PHY_28NM_DIG_SYNC_ERR BIT(30) - -#define PHY_28NM_DIG_SQ_FILT_SHIFT 16 -#define PHY_28NM_DIG_SQ_FILT_MASK (0x7 << 16) - -#define PHY_28NM_DIG_SQ_BLK_SHIFT 12 -#define PHY_28NM_DIG_SQ_BLK_MASK (0x7 << 12) - -#define PHY_28NM_DIG_SYNC_NUM_SHIFT 0 -#define PHY_28NM_DIG_SYNC_NUM_MASK (0x3 << 0) - -#define PHY_28NM_PLL_LOCK_BYPASS BIT(7) - -/* PHY_28NM_OTG_REG */ -#define PHY_28NM_OTG_CONTROL_BY_PIN BIT(5) -#define PHY_28NM_OTG_PU_OTG BIT(4) - -#define PHY_28NM_CHGDTC_ENABLE_SWITCH_DM_SHIFT_28 13 -#define PHY_28NM_CHGDTC_ENABLE_SWITCH_DP_SHIFT_28 12 -#define PHY_28NM_CHGDTC_VSRC_CHARGE_SHIFT_28 10 -#define PHY_28NM_CHGDTC_VDAT_CHARGE_SHIFT_28 8 -#define PHY_28NM_CHGDTC_CDP_DM_AUTO_SWITCH_SHIFT_28 7 -#define PHY_28NM_CHGDTC_DP_DM_SWAP_SHIFT_28 6 -#define PHY_28NM_CHGDTC_PU_CHRG_DTC_SHIFT_28 5 -#define PHY_28NM_CHGDTC_PD_EN_SHIFT_28 4 -#define PHY_28NM_CHGDTC_DCP_EN_SHIFT_28 3 -#define PHY_28NM_CHGDTC_CDP_EN_SHIFT_28 2 -#define PHY_28NM_CHGDTC_TESTMON_CHRGDTC_SHIFT_28 0 - -#define PHY_28NM_CTRL1_CHRG_DTC_OUT_SHIFT_28 4 -#define PHY_28NM_CTRL1_VBUSDTC_OUT_SHIFT_28 2 - -#define PHY_28NM_CTRL3_OVERWRITE BIT(0) -#define PHY_28NM_CTRL3_VBUS_VALID BIT(4) -#define PHY_28NM_CTRL3_AVALID BIT(5) -#define PHY_28NM_CTRL3_BVALID BIT(6) - -struct mv_usb2_phy { - struct phy *phy; - struct platform_device *pdev; - void __iomem *base; - struct clk *clk; -}; - -static bool wait_for_reg(void __iomem *reg, u32 mask, unsigned long timeout) -{ - timeout += jiffies; - while (time_is_after_eq_jiffies(timeout)) { - if ((readl(reg) & mask) == mask) - return true; - msleep(1); - } - return false; -} - -static int mv_usb2_phy_28nm_init(struct phy *phy) -{ - struct mv_usb2_phy *mv_phy = phy_get_drvdata(phy); - struct platform_device *pdev = mv_phy->pdev; - void __iomem *base = mv_phy->base; - u32 reg; - int ret; - - clk_prepare_enable(mv_phy->clk); - - /* PHY_28NM_PLL_REG0 */ - reg = readl(base + PHY_28NM_PLL_REG0) & - ~(PHY_28NM_PLL_SELLPFR_MASK | PHY_28NM_PLL_FBDIV_MASK - | PHY_28NM_PLL_ICP_MASK | PHY_28NM_PLL_REFDIV_MASK); - writel(reg | (0x1 << PHY_28NM_PLL_SELLPFR_SHIFT - | 0xf0 << PHY_28NM_PLL_FBDIV_SHIFT - | 0x3 << PHY_28NM_PLL_ICP_SHIFT - | 0xd << PHY_28NM_PLL_REFDIV_SHIFT), - base + PHY_28NM_PLL_REG0); - - /* PHY_28NM_PLL_REG1 */ - reg = readl(base + PHY_28NM_PLL_REG1); - writel(reg | PHY_28NM_PLL_PU_PLL | PHY_28NM_PLL_PU_BY_REG, - base + PHY_28NM_PLL_REG1); - - /* PHY_28NM_TX_REG0 */ - reg = readl(base + PHY_28NM_TX_REG0) & ~PHY_28NM_TX_AMP_MASK; - writel(reg | PHY_28NM_TX_PU_BY_REG | 0x3 << PHY_28NM_TX_AMP_SHIFT | - PHY_28NM_TX_PU_ANA, - base + PHY_28NM_TX_REG0); - - /* PHY_28NM_RX_REG0 */ - reg = readl(base + PHY_28NM_RX_REG0) & ~PHY_28NM_RX_SQ_THRESH_MASK; - writel(reg | 0xa << PHY_28NM_RX_SQ_THRESH_SHIFT, - base + PHY_28NM_RX_REG0); - - /* PHY_28NM_DIG_REG0 */ - reg = readl(base + PHY_28NM_DIG_REG0) & - ~(PHY_28NM_DIG_BITSTAFFING_ERR | PHY_28NM_DIG_SYNC_ERR | - PHY_28NM_DIG_SQ_FILT_MASK | PHY_28NM_DIG_SQ_BLK_MASK | - PHY_28NM_DIG_SYNC_NUM_MASK); - writel(reg | (0x1 << PHY_28NM_DIG_SYNC_NUM_SHIFT | - PHY_28NM_PLL_LOCK_BYPASS), - base + PHY_28NM_DIG_REG0); - - /* PHY_28NM_OTG_REG */ - reg = readl(base + PHY_28NM_OTG_REG) | PHY_28NM_OTG_PU_OTG; - writel(reg & ~PHY_28NM_OTG_CONTROL_BY_PIN, base + PHY_28NM_OTG_REG); - - /* - * Calibration Timing - * ____________________________ - * CAL START ___| - * ____________________ - * CAL_DONE ___________| - * | 400us | - */ - - /* Make sure PHY Calibration is ready */ - if (!wait_for_reg(base + PHY_28NM_CAL_REG, - PHY_28NM_PLL_PLLCAL_DONE | PHY_28NM_PLL_IMPCAL_DONE, - HZ / 10)) { - dev_warn(&pdev->dev, "USB PHY PLL calibrate not done after 100mS."); - ret = -ETIMEDOUT; - goto err_clk; - } - if (!wait_for_reg(base + PHY_28NM_RX_REG1, - PHY_28NM_RX_SQCAL_DONE, HZ / 10)) { - dev_warn(&pdev->dev, "USB PHY RX SQ calibrate not done after 100mS."); - ret = -ETIMEDOUT; - goto err_clk; - } - /* Make sure PHY PLL is ready */ - if (!wait_for_reg(base + PHY_28NM_PLL_REG0, - PHY_28NM_PLL_READY, HZ / 10)) { - dev_warn(&pdev->dev, "PLL_READY not set after 100mS."); - ret = -ETIMEDOUT; - goto err_clk; - } - - return 0; -err_clk: - clk_disable_unprepare(mv_phy->clk); - return ret; -} - -static int mv_usb2_phy_28nm_power_on(struct phy *phy) -{ - struct mv_usb2_phy *mv_phy = phy_get_drvdata(phy); - void __iomem *base = mv_phy->base; - - writel(readl(base + PHY_28NM_CTRL_REG3) | - (PHY_28NM_CTRL3_OVERWRITE | PHY_28NM_CTRL3_VBUS_VALID | - PHY_28NM_CTRL3_AVALID | PHY_28NM_CTRL3_BVALID), - base + PHY_28NM_CTRL_REG3); - - return 0; -} - -static int mv_usb2_phy_28nm_power_off(struct phy *phy) -{ - struct mv_usb2_phy *mv_phy = phy_get_drvdata(phy); - void __iomem *base = mv_phy->base; - - writel(readl(base + PHY_28NM_CTRL_REG3) | - ~(PHY_28NM_CTRL3_OVERWRITE | PHY_28NM_CTRL3_VBUS_VALID - | PHY_28NM_CTRL3_AVALID | PHY_28NM_CTRL3_BVALID), - base + PHY_28NM_CTRL_REG3); - - return 0; -} - -static int mv_usb2_phy_28nm_exit(struct phy *phy) -{ - struct mv_usb2_phy *mv_phy = phy_get_drvdata(phy); - void __iomem *base = mv_phy->base; - unsigned int val; - - val = readw(base + PHY_28NM_PLL_REG1); - val &= ~PHY_28NM_PLL_PU_PLL; - writew(val, base + PHY_28NM_PLL_REG1); - - /* power down PHY Analog part */ - val = readw(base + PHY_28NM_TX_REG0); - val &= ~PHY_28NM_TX_PU_ANA; - writew(val, base + PHY_28NM_TX_REG0); - - /* power down PHY OTG part */ - val = readw(base + PHY_28NM_OTG_REG); - val &= ~PHY_28NM_OTG_PU_OTG; - writew(val, base + PHY_28NM_OTG_REG); - - clk_disable_unprepare(mv_phy->clk); - return 0; -} - -static const struct phy_ops usb_ops = { - .init = mv_usb2_phy_28nm_init, - .power_on = mv_usb2_phy_28nm_power_on, - .power_off = mv_usb2_phy_28nm_power_off, - .exit = mv_usb2_phy_28nm_exit, - .owner = THIS_MODULE, -}; - -static int mv_usb2_phy_probe(struct platform_device *pdev) -{ - struct phy_provider *phy_provider; - struct mv_usb2_phy *mv_phy; - struct resource *r; - - mv_phy = devm_kzalloc(&pdev->dev, sizeof(*mv_phy), GFP_KERNEL); - if (!mv_phy) - return -ENOMEM; - - mv_phy->pdev = pdev; - - mv_phy->clk = devm_clk_get(&pdev->dev, NULL); - if (IS_ERR(mv_phy->clk)) { - dev_err(&pdev->dev, "failed to get clock.\n"); - return PTR_ERR(mv_phy->clk); - } - - r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - mv_phy->base = devm_ioremap_resource(&pdev->dev, r); - if (IS_ERR(mv_phy->base)) - return PTR_ERR(mv_phy->base); - - mv_phy->phy = devm_phy_create(&pdev->dev, pdev->dev.of_node, &usb_ops); - if (IS_ERR(mv_phy->phy)) - return PTR_ERR(mv_phy->phy); - - phy_set_drvdata(mv_phy->phy, mv_phy); - - phy_provider = devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate); - return PTR_ERR_OR_ZERO(phy_provider); -} - -static const struct of_device_id mv_usbphy_dt_match[] = { - { .compatible = "marvell,pxa1928-usb-phy", }, - {}, -}; -MODULE_DEVICE_TABLE(of, mv_usbphy_dt_match); - -static struct platform_driver mv_usb2_phy_driver = { - .probe = mv_usb2_phy_probe, - .driver = { - .name = "mv-usb2-phy", - .of_match_table = of_match_ptr(mv_usbphy_dt_match), - }, -}; -module_platform_driver(mv_usb2_phy_driver); - -MODULE_AUTHOR("Rob Herring "); -MODULE_DESCRIPTION("Marvell USB2 phy driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-qcom-apq8064-sata.c b/drivers/phy/phy-qcom-apq8064-sata.c deleted file mode 100644 index 69ce2afac015..000000000000 --- a/drivers/phy/phy-qcom-apq8064-sata.c +++ /dev/null @@ -1,287 +0,0 @@ -/* - * Copyright (c) 2014, The Linux Foundation. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* PHY registers */ -#define UNIPHY_PLL_REFCLK_CFG 0x000 -#define UNIPHY_PLL_PWRGEN_CFG 0x014 -#define UNIPHY_PLL_GLB_CFG 0x020 -#define UNIPHY_PLL_SDM_CFG0 0x038 -#define UNIPHY_PLL_SDM_CFG1 0x03C -#define UNIPHY_PLL_SDM_CFG2 0x040 -#define UNIPHY_PLL_SDM_CFG3 0x044 -#define UNIPHY_PLL_SDM_CFG4 0x048 -#define UNIPHY_PLL_SSC_CFG0 0x04C -#define UNIPHY_PLL_SSC_CFG1 0x050 -#define UNIPHY_PLL_SSC_CFG2 0x054 -#define UNIPHY_PLL_SSC_CFG3 0x058 -#define UNIPHY_PLL_LKDET_CFG0 0x05C -#define UNIPHY_PLL_LKDET_CFG1 0x060 -#define UNIPHY_PLL_LKDET_CFG2 0x064 -#define UNIPHY_PLL_CAL_CFG0 0x06C -#define UNIPHY_PLL_CAL_CFG8 0x08C -#define UNIPHY_PLL_CAL_CFG9 0x090 -#define UNIPHY_PLL_CAL_CFG10 0x094 -#define UNIPHY_PLL_CAL_CFG11 0x098 -#define UNIPHY_PLL_STATUS 0x0C0 - -#define SATA_PHY_SER_CTRL 0x100 -#define SATA_PHY_TX_DRIV_CTRL0 0x104 -#define SATA_PHY_TX_DRIV_CTRL1 0x108 -#define SATA_PHY_TX_IMCAL0 0x11C -#define SATA_PHY_TX_IMCAL2 0x124 -#define SATA_PHY_RX_IMCAL0 0x128 -#define SATA_PHY_EQUAL 0x13C -#define SATA_PHY_OOB_TERM 0x144 -#define SATA_PHY_CDR_CTRL0 0x148 -#define SATA_PHY_CDR_CTRL1 0x14C -#define SATA_PHY_CDR_CTRL2 0x150 -#define SATA_PHY_CDR_CTRL3 0x154 -#define SATA_PHY_PI_CTRL0 0x168 -#define SATA_PHY_POW_DWN_CTRL0 0x180 -#define SATA_PHY_POW_DWN_CTRL1 0x184 -#define SATA_PHY_TX_DATA_CTRL 0x188 -#define SATA_PHY_ALIGNP 0x1A4 -#define SATA_PHY_TX_IMCAL_STAT 0x1E4 -#define SATA_PHY_RX_IMCAL_STAT 0x1E8 - -#define UNIPHY_PLL_LOCK BIT(0) -#define SATA_PHY_TX_CAL BIT(0) -#define SATA_PHY_RX_CAL BIT(0) - -/* default timeout set to 1 sec */ -#define TIMEOUT_MS 10000 -#define DELAY_INTERVAL_US 100 - -struct qcom_apq8064_sata_phy { - void __iomem *mmio; - struct clk *cfg_clk; - struct device *dev; -}; - -/* Helper function to do poll and timeout */ -static int read_poll_timeout(void __iomem *addr, u32 mask) -{ - unsigned long timeout = jiffies + msecs_to_jiffies(TIMEOUT_MS); - - do { - if (readl_relaxed(addr) & mask) - return 0; - - usleep_range(DELAY_INTERVAL_US, DELAY_INTERVAL_US + 50); - } while (!time_after(jiffies, timeout)); - - return (readl_relaxed(addr) & mask) ? 0 : -ETIMEDOUT; -} - -static int qcom_apq8064_sata_phy_init(struct phy *generic_phy) -{ - struct qcom_apq8064_sata_phy *phy = phy_get_drvdata(generic_phy); - void __iomem *base = phy->mmio; - int ret = 0; - - /* SATA phy initialization */ - writel_relaxed(0x01, base + SATA_PHY_SER_CTRL); - writel_relaxed(0xB1, base + SATA_PHY_POW_DWN_CTRL0); - /* Make sure the power down happens before power up */ - mb(); - usleep_range(10, 60); - - writel_relaxed(0x01, base + SATA_PHY_POW_DWN_CTRL0); - writel_relaxed(0x3E, base + SATA_PHY_POW_DWN_CTRL1); - writel_relaxed(0x01, base + SATA_PHY_RX_IMCAL0); - writel_relaxed(0x01, base + SATA_PHY_TX_IMCAL0); - writel_relaxed(0x02, base + SATA_PHY_TX_IMCAL2); - - /* Write UNIPHYPLL registers to configure PLL */ - writel_relaxed(0x04, base + UNIPHY_PLL_REFCLK_CFG); - writel_relaxed(0x00, base + UNIPHY_PLL_PWRGEN_CFG); - - writel_relaxed(0x0A, base + UNIPHY_PLL_CAL_CFG0); - writel_relaxed(0xF3, base + UNIPHY_PLL_CAL_CFG8); - writel_relaxed(0x01, base + UNIPHY_PLL_CAL_CFG9); - writel_relaxed(0xED, base + UNIPHY_PLL_CAL_CFG10); - writel_relaxed(0x02, base + UNIPHY_PLL_CAL_CFG11); - - writel_relaxed(0x36, base + UNIPHY_PLL_SDM_CFG0); - writel_relaxed(0x0D, base + UNIPHY_PLL_SDM_CFG1); - writel_relaxed(0xA3, base + UNIPHY_PLL_SDM_CFG2); - writel_relaxed(0xF0, base + UNIPHY_PLL_SDM_CFG3); - writel_relaxed(0x00, base + UNIPHY_PLL_SDM_CFG4); - - writel_relaxed(0x19, base + UNIPHY_PLL_SSC_CFG0); - writel_relaxed(0xE1, base + UNIPHY_PLL_SSC_CFG1); - writel_relaxed(0x00, base + UNIPHY_PLL_SSC_CFG2); - writel_relaxed(0x11, base + UNIPHY_PLL_SSC_CFG3); - - writel_relaxed(0x04, base + UNIPHY_PLL_LKDET_CFG0); - writel_relaxed(0xFF, base + UNIPHY_PLL_LKDET_CFG1); - - writel_relaxed(0x02, base + UNIPHY_PLL_GLB_CFG); - /* make sure global config LDO power down happens before power up */ - mb(); - - writel_relaxed(0x03, base + UNIPHY_PLL_GLB_CFG); - writel_relaxed(0x05, base + UNIPHY_PLL_LKDET_CFG2); - - /* PLL Lock wait */ - ret = read_poll_timeout(base + UNIPHY_PLL_STATUS, UNIPHY_PLL_LOCK); - if (ret) { - dev_err(phy->dev, "poll timeout UNIPHY_PLL_STATUS\n"); - return ret; - } - - /* TX Calibration */ - ret = read_poll_timeout(base + SATA_PHY_TX_IMCAL_STAT, SATA_PHY_TX_CAL); - if (ret) { - dev_err(phy->dev, "poll timeout SATA_PHY_TX_IMCAL_STAT\n"); - return ret; - } - - /* RX Calibration */ - ret = read_poll_timeout(base + SATA_PHY_RX_IMCAL_STAT, SATA_PHY_RX_CAL); - if (ret) { - dev_err(phy->dev, "poll timeout SATA_PHY_RX_IMCAL_STAT\n"); - return ret; - } - - /* SATA phy calibrated succesfully, power up to functional mode */ - writel_relaxed(0x3E, base + SATA_PHY_POW_DWN_CTRL1); - writel_relaxed(0x01, base + SATA_PHY_RX_IMCAL0); - writel_relaxed(0x01, base + SATA_PHY_TX_IMCAL0); - - writel_relaxed(0x00, base + SATA_PHY_POW_DWN_CTRL1); - writel_relaxed(0x59, base + SATA_PHY_CDR_CTRL0); - writel_relaxed(0x04, base + SATA_PHY_CDR_CTRL1); - writel_relaxed(0x00, base + SATA_PHY_CDR_CTRL2); - writel_relaxed(0x00, base + SATA_PHY_PI_CTRL0); - writel_relaxed(0x00, base + SATA_PHY_CDR_CTRL3); - writel_relaxed(0x01, base + SATA_PHY_POW_DWN_CTRL0); - - writel_relaxed(0x11, base + SATA_PHY_TX_DATA_CTRL); - writel_relaxed(0x43, base + SATA_PHY_ALIGNP); - writel_relaxed(0x04, base + SATA_PHY_OOB_TERM); - - writel_relaxed(0x01, base + SATA_PHY_EQUAL); - writel_relaxed(0x09, base + SATA_PHY_TX_DRIV_CTRL0); - writel_relaxed(0x09, base + SATA_PHY_TX_DRIV_CTRL1); - - return 0; -} - -static int qcom_apq8064_sata_phy_exit(struct phy *generic_phy) -{ - struct qcom_apq8064_sata_phy *phy = phy_get_drvdata(generic_phy); - void __iomem *base = phy->mmio; - - /* Power down PHY */ - writel_relaxed(0xF8, base + SATA_PHY_POW_DWN_CTRL0); - writel_relaxed(0xFE, base + SATA_PHY_POW_DWN_CTRL1); - - /* Power down PLL block */ - writel_relaxed(0x00, base + UNIPHY_PLL_GLB_CFG); - - return 0; -} - -static const struct phy_ops qcom_apq8064_sata_phy_ops = { - .init = qcom_apq8064_sata_phy_init, - .exit = qcom_apq8064_sata_phy_exit, - .owner = THIS_MODULE, -}; - -static int qcom_apq8064_sata_phy_probe(struct platform_device *pdev) -{ - struct qcom_apq8064_sata_phy *phy; - struct device *dev = &pdev->dev; - struct resource *res; - struct phy_provider *phy_provider; - struct phy *generic_phy; - int ret; - - phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); - if (!phy) - return -ENOMEM; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - phy->mmio = devm_ioremap_resource(dev, res); - if (IS_ERR(phy->mmio)) - return PTR_ERR(phy->mmio); - - generic_phy = devm_phy_create(dev, NULL, &qcom_apq8064_sata_phy_ops); - if (IS_ERR(generic_phy)) { - dev_err(dev, "%s: failed to create phy\n", __func__); - return PTR_ERR(generic_phy); - } - - phy->dev = dev; - phy_set_drvdata(generic_phy, phy); - platform_set_drvdata(pdev, phy); - - phy->cfg_clk = devm_clk_get(dev, "cfg"); - if (IS_ERR(phy->cfg_clk)) { - dev_err(dev, "Failed to get sata cfg clock\n"); - return PTR_ERR(phy->cfg_clk); - } - - ret = clk_prepare_enable(phy->cfg_clk); - if (ret) - return ret; - - phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); - if (IS_ERR(phy_provider)) { - clk_disable_unprepare(phy->cfg_clk); - dev_err(dev, "%s: failed to register phy\n", __func__); - return PTR_ERR(phy_provider); - } - - return 0; -} - -static int qcom_apq8064_sata_phy_remove(struct platform_device *pdev) -{ - struct qcom_apq8064_sata_phy *phy = platform_get_drvdata(pdev); - - clk_disable_unprepare(phy->cfg_clk); - - return 0; -} - -static const struct of_device_id qcom_apq8064_sata_phy_of_match[] = { - { .compatible = "qcom,apq8064-sata-phy" }, - { }, -}; -MODULE_DEVICE_TABLE(of, qcom_apq8064_sata_phy_of_match); - -static struct platform_driver qcom_apq8064_sata_phy_driver = { - .probe = qcom_apq8064_sata_phy_probe, - .remove = qcom_apq8064_sata_phy_remove, - .driver = { - .name = "qcom-apq8064-sata-phy", - .of_match_table = qcom_apq8064_sata_phy_of_match, - } -}; -module_platform_driver(qcom_apq8064_sata_phy_driver); - -MODULE_DESCRIPTION("QCOM apq8064 SATA PHY driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-qcom-ipq806x-sata.c b/drivers/phy/phy-qcom-ipq806x-sata.c deleted file mode 100644 index 0ad127cc9298..000000000000 --- a/drivers/phy/phy-qcom-ipq806x-sata.c +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright (c) 2014, The Linux Foundation. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct qcom_ipq806x_sata_phy { - void __iomem *mmio; - struct clk *cfg_clk; - struct device *dev; -}; - -#define __set(v, a, b) (((v) << (b)) & GENMASK(a, b)) - -#define SATA_PHY_P0_PARAM0 0x200 -#define SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN3(x) __set(x, 17, 12) -#define SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN3_MASK GENMASK(17, 12) -#define SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN2(x) __set(x, 11, 6) -#define SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN2_MASK GENMASK(11, 6) -#define SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN1(x) __set(x, 5, 0) -#define SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN1_MASK GENMASK(5, 0) - -#define SATA_PHY_P0_PARAM1 0x204 -#define SATA_PHY_P0_PARAM1_RESERVED_BITS31_21(x) __set(x, 31, 21) -#define SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN3(x) __set(x, 20, 14) -#define SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN3_MASK GENMASK(20, 14) -#define SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN2(x) __set(x, 13, 7) -#define SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN2_MASK GENMASK(13, 7) -#define SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN1(x) __set(x, 6, 0) -#define SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN1_MASK GENMASK(6, 0) - -#define SATA_PHY_P0_PARAM2 0x208 -#define SATA_PHY_P0_PARAM2_RX_EQ(x) __set(x, 20, 18) -#define SATA_PHY_P0_PARAM2_RX_EQ_MASK GENMASK(20, 18) - -#define SATA_PHY_P0_PARAM3 0x20C -#define SATA_PHY_SSC_EN 0x8 -#define SATA_PHY_P0_PARAM4 0x210 -#define SATA_PHY_REF_SSP_EN 0x2 -#define SATA_PHY_RESET 0x1 - -static int qcom_ipq806x_sata_phy_init(struct phy *generic_phy) -{ - struct qcom_ipq806x_sata_phy *phy = phy_get_drvdata(generic_phy); - u32 reg; - - /* Setting SSC_EN to 1 */ - reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM3); - reg = reg | SATA_PHY_SSC_EN; - writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM3); - - reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM0) & - ~(SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN3_MASK | - SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN2_MASK | - SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN1_MASK); - reg |= SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN3(0xf); - writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM0); - - reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM1) & - ~(SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN3_MASK | - SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN2_MASK | - SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN1_MASK); - reg |= SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN3(0x55) | - SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN2(0x55) | - SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN1(0x55); - writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM1); - - reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM2) & - ~SATA_PHY_P0_PARAM2_RX_EQ_MASK; - reg |= SATA_PHY_P0_PARAM2_RX_EQ(0x3); - writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM2); - - /* Setting PHY_RESET to 1 */ - reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM4); - reg = reg | SATA_PHY_RESET; - writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM4); - - /* Setting REF_SSP_EN to 1 */ - reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM4); - reg = reg | SATA_PHY_REF_SSP_EN | SATA_PHY_RESET; - writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM4); - - /* make sure all changes complete before we let the PHY out of reset */ - mb(); - - /* sleep for max. 50us more to combine processor wakeups */ - usleep_range(20, 20 + 50); - - /* Clearing PHY_RESET to 0 */ - reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM4); - reg = reg & ~SATA_PHY_RESET; - writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM4); - - return 0; -} - -static int qcom_ipq806x_sata_phy_exit(struct phy *generic_phy) -{ - struct qcom_ipq806x_sata_phy *phy = phy_get_drvdata(generic_phy); - u32 reg; - - /* Setting PHY_RESET to 1 */ - reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM4); - reg = reg | SATA_PHY_RESET; - writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM4); - - return 0; -} - -static const struct phy_ops qcom_ipq806x_sata_phy_ops = { - .init = qcom_ipq806x_sata_phy_init, - .exit = qcom_ipq806x_sata_phy_exit, - .owner = THIS_MODULE, -}; - -static int qcom_ipq806x_sata_phy_probe(struct platform_device *pdev) -{ - struct qcom_ipq806x_sata_phy *phy; - struct device *dev = &pdev->dev; - struct resource *res; - struct phy_provider *phy_provider; - struct phy *generic_phy; - int ret; - - phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); - if (!phy) - return -ENOMEM; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - phy->mmio = devm_ioremap_resource(dev, res); - if (IS_ERR(phy->mmio)) - return PTR_ERR(phy->mmio); - - generic_phy = devm_phy_create(dev, NULL, &qcom_ipq806x_sata_phy_ops); - if (IS_ERR(generic_phy)) { - dev_err(dev, "%s: failed to create phy\n", __func__); - return PTR_ERR(generic_phy); - } - - phy->dev = dev; - phy_set_drvdata(generic_phy, phy); - platform_set_drvdata(pdev, phy); - - phy->cfg_clk = devm_clk_get(dev, "cfg"); - if (IS_ERR(phy->cfg_clk)) { - dev_err(dev, "Failed to get sata cfg clock\n"); - return PTR_ERR(phy->cfg_clk); - } - - ret = clk_prepare_enable(phy->cfg_clk); - if (ret) - return ret; - - phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); - if (IS_ERR(phy_provider)) { - clk_disable_unprepare(phy->cfg_clk); - dev_err(dev, "%s: failed to register phy\n", __func__); - return PTR_ERR(phy_provider); - } - - return 0; -} - -static int qcom_ipq806x_sata_phy_remove(struct platform_device *pdev) -{ - struct qcom_ipq806x_sata_phy *phy = platform_get_drvdata(pdev); - - clk_disable_unprepare(phy->cfg_clk); - - return 0; -} - -static const struct of_device_id qcom_ipq806x_sata_phy_of_match[] = { - { .compatible = "qcom,ipq806x-sata-phy" }, - { }, -}; -MODULE_DEVICE_TABLE(of, qcom_ipq806x_sata_phy_of_match); - -static struct platform_driver qcom_ipq806x_sata_phy_driver = { - .probe = qcom_ipq806x_sata_phy_probe, - .remove = qcom_ipq806x_sata_phy_remove, - .driver = { - .name = "qcom-ipq806x-sata-phy", - .of_match_table = qcom_ipq806x_sata_phy_of_match, - } -}; -module_platform_driver(qcom_ipq806x_sata_phy_driver); - -MODULE_DESCRIPTION("QCOM IPQ806x SATA PHY driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-qcom-qmp.c b/drivers/phy/phy-qcom-qmp.c deleted file mode 100644 index 78ca62897784..000000000000 --- a/drivers/phy/phy-qcom-qmp.c +++ /dev/null @@ -1,1153 +0,0 @@ -/* - * Copyright (c) 2017, The Linux Foundation. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -/* QMP PHY QSERDES COM registers */ -#define QSERDES_COM_BG_TIMER 0x00c -#define QSERDES_COM_SSC_EN_CENTER 0x010 -#define QSERDES_COM_SSC_ADJ_PER1 0x014 -#define QSERDES_COM_SSC_ADJ_PER2 0x018 -#define QSERDES_COM_SSC_PER1 0x01c -#define QSERDES_COM_SSC_PER2 0x020 -#define QSERDES_COM_SSC_STEP_SIZE1 0x024 -#define QSERDES_COM_SSC_STEP_SIZE2 0x028 -#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN 0x034 -#define QSERDES_COM_CLK_ENABLE1 0x038 -#define QSERDES_COM_SYS_CLK_CTRL 0x03c -#define QSERDES_COM_SYSCLK_BUF_ENABLE 0x040 -#define QSERDES_COM_PLL_IVCO 0x048 -#define QSERDES_COM_LOCK_CMP1_MODE0 0x04c -#define QSERDES_COM_LOCK_CMP2_MODE0 0x050 -#define QSERDES_COM_LOCK_CMP3_MODE0 0x054 -#define QSERDES_COM_LOCK_CMP1_MODE1 0x058 -#define QSERDES_COM_LOCK_CMP2_MODE1 0x05c -#define QSERDES_COM_LOCK_CMP3_MODE1 0x060 -#define QSERDES_COM_BG_TRIM 0x070 -#define QSERDES_COM_CLK_EP_DIV 0x074 -#define QSERDES_COM_CP_CTRL_MODE0 0x078 -#define QSERDES_COM_CP_CTRL_MODE1 0x07c -#define QSERDES_COM_PLL_RCTRL_MODE0 0x084 -#define QSERDES_COM_PLL_RCTRL_MODE1 0x088 -#define QSERDES_COM_PLL_CCTRL_MODE0 0x090 -#define QSERDES_COM_PLL_CCTRL_MODE1 0x094 -#define QSERDES_COM_SYSCLK_EN_SEL 0x0ac -#define QSERDES_COM_RESETSM_CNTRL 0x0b4 -#define QSERDES_COM_RESTRIM_CTRL 0x0bc -#define QSERDES_COM_RESCODE_DIV_NUM 0x0c4 -#define QSERDES_COM_LOCK_CMP_EN 0x0c8 -#define QSERDES_COM_LOCK_CMP_CFG 0x0cc -#define QSERDES_COM_DEC_START_MODE0 0x0d0 -#define QSERDES_COM_DEC_START_MODE1 0x0d4 -#define QSERDES_COM_DIV_FRAC_START1_MODE0 0x0dc -#define QSERDES_COM_DIV_FRAC_START2_MODE0 0x0e0 -#define QSERDES_COM_DIV_FRAC_START3_MODE0 0x0e4 -#define QSERDES_COM_DIV_FRAC_START1_MODE1 0x0e8 -#define QSERDES_COM_DIV_FRAC_START2_MODE1 0x0ec -#define QSERDES_COM_DIV_FRAC_START3_MODE1 0x0f0 -#define QSERDES_COM_INTEGLOOP_GAIN0_MODE0 0x108 -#define QSERDES_COM_INTEGLOOP_GAIN1_MODE0 0x10c -#define QSERDES_COM_INTEGLOOP_GAIN0_MODE1 0x110 -#define QSERDES_COM_INTEGLOOP_GAIN1_MODE1 0x114 -#define QSERDES_COM_VCO_TUNE_CTRL 0x124 -#define QSERDES_COM_VCO_TUNE_MAP 0x128 -#define QSERDES_COM_VCO_TUNE1_MODE0 0x12c -#define QSERDES_COM_VCO_TUNE2_MODE0 0x130 -#define QSERDES_COM_VCO_TUNE1_MODE1 0x134 -#define QSERDES_COM_VCO_TUNE2_MODE1 0x138 -#define QSERDES_COM_VCO_TUNE_TIMER1 0x144 -#define QSERDES_COM_VCO_TUNE_TIMER2 0x148 -#define QSERDES_COM_BG_CTRL 0x170 -#define QSERDES_COM_CLK_SELECT 0x174 -#define QSERDES_COM_HSCLK_SEL 0x178 -#define QSERDES_COM_CORECLK_DIV 0x184 -#define QSERDES_COM_CORE_CLK_EN 0x18c -#define QSERDES_COM_C_READY_STATUS 0x190 -#define QSERDES_COM_CMN_CONFIG 0x194 -#define QSERDES_COM_SVS_MODE_CLK_SEL 0x19c -#define QSERDES_COM_DEBUG_BUS0 0x1a0 -#define QSERDES_COM_DEBUG_BUS1 0x1a4 -#define QSERDES_COM_DEBUG_BUS2 0x1a8 -#define QSERDES_COM_DEBUG_BUS3 0x1ac -#define QSERDES_COM_DEBUG_BUS_SEL 0x1b0 -#define QSERDES_COM_CORECLK_DIV_MODE1 0x1bc - -/* QMP PHY TX registers */ -#define QSERDES_TX_RES_CODE_LANE_OFFSET 0x054 -#define QSERDES_TX_DEBUG_BUS_SEL 0x064 -#define QSERDES_TX_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN 0x068 -#define QSERDES_TX_LANE_MODE 0x094 -#define QSERDES_TX_RCV_DETECT_LVL_2 0x0ac - -/* QMP PHY RX registers */ -#define QSERDES_RX_UCDR_SO_GAIN_HALF 0x010 -#define QSERDES_RX_UCDR_SO_GAIN 0x01c -#define QSERDES_RX_UCDR_FASTLOCK_FO_GAIN 0x040 -#define QSERDES_RX_UCDR_SO_SATURATION_AND_ENABLE 0x048 -#define QSERDES_RX_RX_TERM_BW 0x090 -#define QSERDES_RX_RX_EQ_GAIN1_LSB 0x0c4 -#define QSERDES_RX_RX_EQ_GAIN1_MSB 0x0c8 -#define QSERDES_RX_RX_EQ_GAIN2_LSB 0x0cc -#define QSERDES_RX_RX_EQ_GAIN2_MSB 0x0d0 -#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2 0x0d8 -#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL3 0x0dc -#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL4 0x0e0 -#define QSERDES_RX_RX_EQ_OFFSET_ADAPTOR_CNTRL1 0x108 -#define QSERDES_RX_RX_OFFSET_ADAPTOR_CNTRL2 0x10c -#define QSERDES_RX_SIGDET_ENABLES 0x110 -#define QSERDES_RX_SIGDET_CNTRL 0x114 -#define QSERDES_RX_SIGDET_LVL 0x118 -#define QSERDES_RX_SIGDET_DEGLITCH_CNTRL 0x11c -#define QSERDES_RX_RX_BAND 0x120 -#define QSERDES_RX_RX_INTERFACE_MODE 0x12c - -/* QMP PHY PCS registers */ -#define QPHY_POWER_DOWN_CONTROL 0x04 -#define QPHY_TXDEEMPH_M6DB_V0 0x24 -#define QPHY_TXDEEMPH_M3P5DB_V0 0x28 -#define QPHY_ENDPOINT_REFCLK_DRIVE 0x54 -#define QPHY_RX_IDLE_DTCT_CNTRL 0x58 -#define QPHY_POWER_STATE_CONFIG1 0x60 -#define QPHY_POWER_STATE_CONFIG2 0x64 -#define QPHY_POWER_STATE_CONFIG4 0x6c -#define QPHY_LOCK_DETECT_CONFIG1 0x80 -#define QPHY_LOCK_DETECT_CONFIG2 0x84 -#define QPHY_LOCK_DETECT_CONFIG3 0x88 -#define QPHY_PWRUP_RESET_DLY_TIME_AUXCLK 0xa0 -#define QPHY_LP_WAKEUP_DLY_TIME_AUXCLK 0xa4 - -/* QPHY_SW_RESET bit */ -#define SW_RESET BIT(0) -/* QPHY_POWER_DOWN_CONTROL */ -#define SW_PWRDN BIT(0) -#define REFCLK_DRV_DSBL BIT(1) -/* QPHY_START_CONTROL bits */ -#define SERDES_START BIT(0) -#define PCS_START BIT(1) -#define PLL_READY_GATE_EN BIT(3) -/* QPHY_PCS_STATUS bit */ -#define PHYSTATUS BIT(6) -/* QPHY_COM_PCS_READY_STATUS bit */ -#define PCS_READY BIT(0) - -#define PHY_INIT_COMPLETE_TIMEOUT 1000 -#define POWER_DOWN_DELAY_US_MIN 10 -#define POWER_DOWN_DELAY_US_MAX 11 - -#define MAX_PROP_NAME 32 - -struct qmp_phy_init_tbl { - unsigned int offset; - unsigned int val; - /* - * register part of layout ? - * if yes, then offset gives index in the reg-layout - */ - int in_layout; -}; - -#define QMP_PHY_INIT_CFG(o, v) \ - { \ - .offset = o, \ - .val = v, \ - } - -#define QMP_PHY_INIT_CFG_L(o, v) \ - { \ - .offset = o, \ - .val = v, \ - .in_layout = 1, \ - } - -/* set of registers with offsets different per-PHY */ -enum qphy_reg_layout { - /* Common block control registers */ - QPHY_COM_SW_RESET, - QPHY_COM_POWER_DOWN_CONTROL, - QPHY_COM_START_CONTROL, - QPHY_COM_PCS_READY_STATUS, - /* PCS registers */ - QPHY_PLL_LOCK_CHK_DLY_TIME, - QPHY_FLL_CNTRL1, - QPHY_FLL_CNTRL2, - QPHY_FLL_CNT_VAL_L, - QPHY_FLL_CNT_VAL_H_TOL, - QPHY_FLL_MAN_CODE, - QPHY_SW_RESET, - QPHY_START_CTRL, - QPHY_PCS_READY_STATUS, -}; - -static const unsigned int pciephy_regs_layout[] = { - [QPHY_COM_SW_RESET] = 0x400, - [QPHY_COM_POWER_DOWN_CONTROL] = 0x404, - [QPHY_COM_START_CONTROL] = 0x408, - [QPHY_COM_PCS_READY_STATUS] = 0x448, - [QPHY_PLL_LOCK_CHK_DLY_TIME] = 0xa8, - [QPHY_FLL_CNTRL1] = 0xc4, - [QPHY_FLL_CNTRL2] = 0xc8, - [QPHY_FLL_CNT_VAL_L] = 0xcc, - [QPHY_FLL_CNT_VAL_H_TOL] = 0xd0, - [QPHY_FLL_MAN_CODE] = 0xd4, - [QPHY_SW_RESET] = 0x00, - [QPHY_START_CTRL] = 0x08, - [QPHY_PCS_READY_STATUS] = 0x174, -}; - -static const unsigned int usb3phy_regs_layout[] = { - [QPHY_FLL_CNTRL1] = 0xc0, - [QPHY_FLL_CNTRL2] = 0xc4, - [QPHY_FLL_CNT_VAL_L] = 0xc8, - [QPHY_FLL_CNT_VAL_H_TOL] = 0xcc, - [QPHY_FLL_MAN_CODE] = 0xd0, - [QPHY_SW_RESET] = 0x00, - [QPHY_START_CTRL] = 0x08, - [QPHY_PCS_READY_STATUS] = 0x17c, -}; - -static const struct qmp_phy_init_tbl msm8996_pcie_serdes_tbl[] = { - QMP_PHY_INIT_CFG(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x1c), - QMP_PHY_INIT_CFG(QSERDES_COM_CLK_ENABLE1, 0x10), - QMP_PHY_INIT_CFG(QSERDES_COM_CLK_SELECT, 0x33), - QMP_PHY_INIT_CFG(QSERDES_COM_CMN_CONFIG, 0x06), - QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP_EN, 0x42), - QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_MAP, 0x00), - QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_TIMER1, 0xff), - QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_TIMER2, 0x1f), - QMP_PHY_INIT_CFG(QSERDES_COM_HSCLK_SEL, 0x01), - QMP_PHY_INIT_CFG(QSERDES_COM_SVS_MODE_CLK_SEL, 0x01), - QMP_PHY_INIT_CFG(QSERDES_COM_CORE_CLK_EN, 0x00), - QMP_PHY_INIT_CFG(QSERDES_COM_CORECLK_DIV, 0x0a), - QMP_PHY_INIT_CFG(QSERDES_COM_BG_TIMER, 0x09), - QMP_PHY_INIT_CFG(QSERDES_COM_DEC_START_MODE0, 0x82), - QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START3_MODE0, 0x03), - QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START2_MODE0, 0x55), - QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START1_MODE0, 0x55), - QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP3_MODE0, 0x00), - QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP2_MODE0, 0x1a), - QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP1_MODE0, 0x0a), - QMP_PHY_INIT_CFG(QSERDES_COM_CLK_SELECT, 0x33), - QMP_PHY_INIT_CFG(QSERDES_COM_SYS_CLK_CTRL, 0x02), - QMP_PHY_INIT_CFG(QSERDES_COM_SYSCLK_BUF_ENABLE, 0x1f), - QMP_PHY_INIT_CFG(QSERDES_COM_SYSCLK_EN_SEL, 0x04), - QMP_PHY_INIT_CFG(QSERDES_COM_CP_CTRL_MODE0, 0x0b), - QMP_PHY_INIT_CFG(QSERDES_COM_PLL_RCTRL_MODE0, 0x16), - QMP_PHY_INIT_CFG(QSERDES_COM_PLL_CCTRL_MODE0, 0x28), - QMP_PHY_INIT_CFG(QSERDES_COM_INTEGLOOP_GAIN1_MODE0, 0x00), - QMP_PHY_INIT_CFG(QSERDES_COM_INTEGLOOP_GAIN0_MODE0, 0x80), - QMP_PHY_INIT_CFG(QSERDES_COM_SSC_EN_CENTER, 0x01), - QMP_PHY_INIT_CFG(QSERDES_COM_SSC_PER1, 0x31), - QMP_PHY_INIT_CFG(QSERDES_COM_SSC_PER2, 0x01), - QMP_PHY_INIT_CFG(QSERDES_COM_SSC_ADJ_PER1, 0x02), - QMP_PHY_INIT_CFG(QSERDES_COM_SSC_ADJ_PER2, 0x00), - QMP_PHY_INIT_CFG(QSERDES_COM_SSC_STEP_SIZE1, 0x2f), - QMP_PHY_INIT_CFG(QSERDES_COM_SSC_STEP_SIZE2, 0x19), - QMP_PHY_INIT_CFG(QSERDES_COM_RESCODE_DIV_NUM, 0x15), - QMP_PHY_INIT_CFG(QSERDES_COM_BG_TRIM, 0x0f), - QMP_PHY_INIT_CFG(QSERDES_COM_PLL_IVCO, 0x0f), - QMP_PHY_INIT_CFG(QSERDES_COM_CLK_EP_DIV, 0x19), - QMP_PHY_INIT_CFG(QSERDES_COM_CLK_ENABLE1, 0x10), - QMP_PHY_INIT_CFG(QSERDES_COM_HSCLK_SEL, 0x00), - QMP_PHY_INIT_CFG(QSERDES_COM_RESCODE_DIV_NUM, 0x40), -}; - -static const struct qmp_phy_init_tbl msm8996_pcie_tx_tbl[] = { - QMP_PHY_INIT_CFG(QSERDES_TX_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN, 0x45), - QMP_PHY_INIT_CFG(QSERDES_TX_LANE_MODE, 0x06), -}; - -static const struct qmp_phy_init_tbl msm8996_pcie_rx_tbl[] = { - QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_ENABLES, 0x1c), - QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2, 0x01), - QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL3, 0x00), - QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL4, 0xdb), - QMP_PHY_INIT_CFG(QSERDES_RX_RX_BAND, 0x18), - QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_SO_GAIN, 0x04), - QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_SO_GAIN_HALF, 0x04), - QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_SO_SATURATION_AND_ENABLE, 0x4b), - QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_DEGLITCH_CNTRL, 0x14), - QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_LVL, 0x19), -}; - -static const struct qmp_phy_init_tbl msm8996_pcie_pcs_tbl[] = { - QMP_PHY_INIT_CFG(QPHY_RX_IDLE_DTCT_CNTRL, 0x4c), - QMP_PHY_INIT_CFG(QPHY_PWRUP_RESET_DLY_TIME_AUXCLK, 0x00), - QMP_PHY_INIT_CFG(QPHY_LP_WAKEUP_DLY_TIME_AUXCLK, 0x01), - - QMP_PHY_INIT_CFG_L(QPHY_PLL_LOCK_CHK_DLY_TIME, 0x05), - - QMP_PHY_INIT_CFG(QPHY_ENDPOINT_REFCLK_DRIVE, 0x05), - QMP_PHY_INIT_CFG(QPHY_POWER_DOWN_CONTROL, 0x02), - QMP_PHY_INIT_CFG(QPHY_POWER_STATE_CONFIG4, 0x00), - QMP_PHY_INIT_CFG(QPHY_POWER_STATE_CONFIG1, 0xa3), - QMP_PHY_INIT_CFG(QPHY_TXDEEMPH_M3P5DB_V0, 0x0e), -}; - -static const struct qmp_phy_init_tbl msm8996_usb3_serdes_tbl[] = { - QMP_PHY_INIT_CFG(QSERDES_COM_SYSCLK_EN_SEL, 0x14), - QMP_PHY_INIT_CFG(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x08), - QMP_PHY_INIT_CFG(QSERDES_COM_CLK_SELECT, 0x30), - QMP_PHY_INIT_CFG(QSERDES_COM_CMN_CONFIG, 0x06), - QMP_PHY_INIT_CFG(QSERDES_COM_SVS_MODE_CLK_SEL, 0x01), - QMP_PHY_INIT_CFG(QSERDES_COM_HSCLK_SEL, 0x00), - QMP_PHY_INIT_CFG(QSERDES_COM_BG_TRIM, 0x0f), - QMP_PHY_INIT_CFG(QSERDES_COM_PLL_IVCO, 0x0f), - QMP_PHY_INIT_CFG(QSERDES_COM_SYS_CLK_CTRL, 0x04), - /* PLL and Loop filter settings */ - QMP_PHY_INIT_CFG(QSERDES_COM_DEC_START_MODE0, 0x82), - QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START1_MODE0, 0x55), - QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START2_MODE0, 0x55), - QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START3_MODE0, 0x03), - QMP_PHY_INIT_CFG(QSERDES_COM_CP_CTRL_MODE0, 0x0b), - QMP_PHY_INIT_CFG(QSERDES_COM_PLL_RCTRL_MODE0, 0x16), - QMP_PHY_INIT_CFG(QSERDES_COM_PLL_CCTRL_MODE0, 0x28), - QMP_PHY_INIT_CFG(QSERDES_COM_INTEGLOOP_GAIN0_MODE0, 0x80), - QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_CTRL, 0x00), - QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP1_MODE0, 0x15), - QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP2_MODE0, 0x34), - QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP3_MODE0, 0x00), - QMP_PHY_INIT_CFG(QSERDES_COM_CORE_CLK_EN, 0x00), - QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP_CFG, 0x00), - QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_MAP, 0x00), - QMP_PHY_INIT_CFG(QSERDES_COM_BG_TIMER, 0x0a), - /* SSC settings */ - QMP_PHY_INIT_CFG(QSERDES_COM_SSC_EN_CENTER, 0x01), - QMP_PHY_INIT_CFG(QSERDES_COM_SSC_PER1, 0x31), - QMP_PHY_INIT_CFG(QSERDES_COM_SSC_PER2, 0x01), - QMP_PHY_INIT_CFG(QSERDES_COM_SSC_ADJ_PER1, 0x00), - QMP_PHY_INIT_CFG(QSERDES_COM_SSC_ADJ_PER2, 0x00), - QMP_PHY_INIT_CFG(QSERDES_COM_SSC_STEP_SIZE1, 0xde), - QMP_PHY_INIT_CFG(QSERDES_COM_SSC_STEP_SIZE2, 0x07), -}; - -static const struct qmp_phy_init_tbl msm8996_usb3_tx_tbl[] = { - QMP_PHY_INIT_CFG(QSERDES_TX_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN, 0x45), - QMP_PHY_INIT_CFG(QSERDES_TX_RCV_DETECT_LVL_2, 0x12), - QMP_PHY_INIT_CFG(QSERDES_TX_LANE_MODE, 0x06), -}; - -static const struct qmp_phy_init_tbl msm8996_usb3_rx_tbl[] = { - QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_FASTLOCK_FO_GAIN, 0x0b), - QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_SO_GAIN, 0x04), - QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2, 0x02), - QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL3, 0x4c), - QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL4, 0xbb), - QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQ_OFFSET_ADAPTOR_CNTRL1, 0x77), - QMP_PHY_INIT_CFG(QSERDES_RX_RX_OFFSET_ADAPTOR_CNTRL2, 0x80), - QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_CNTRL, 0x03), - QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_LVL, 0x18), - QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_DEGLITCH_CNTRL, 0x16), -}; - -static const struct qmp_phy_init_tbl msm8996_usb3_pcs_tbl[] = { - /* FLL settings */ - QMP_PHY_INIT_CFG_L(QPHY_FLL_CNTRL2, 0x03), - QMP_PHY_INIT_CFG_L(QPHY_FLL_CNTRL1, 0x02), - QMP_PHY_INIT_CFG_L(QPHY_FLL_CNT_VAL_L, 0x09), - QMP_PHY_INIT_CFG_L(QPHY_FLL_CNT_VAL_H_TOL, 0x42), - QMP_PHY_INIT_CFG_L(QPHY_FLL_MAN_CODE, 0x85), - - /* Lock Det settings */ - QMP_PHY_INIT_CFG(QPHY_LOCK_DETECT_CONFIG1, 0xd1), - QMP_PHY_INIT_CFG(QPHY_LOCK_DETECT_CONFIG2, 0x1f), - QMP_PHY_INIT_CFG(QPHY_LOCK_DETECT_CONFIG3, 0x47), - QMP_PHY_INIT_CFG(QPHY_POWER_STATE_CONFIG2, 0x08), -}; - -/* struct qmp_phy_cfg - per-PHY initialization config */ -struct qmp_phy_cfg { - /* phy-type - PCIE/UFS/USB */ - unsigned int type; - /* number of lanes provided by phy */ - int nlanes; - - /* Init sequence for PHY blocks - serdes, tx, rx, pcs */ - const struct qmp_phy_init_tbl *serdes_tbl; - int serdes_tbl_num; - const struct qmp_phy_init_tbl *tx_tbl; - int tx_tbl_num; - const struct qmp_phy_init_tbl *rx_tbl; - int rx_tbl_num; - const struct qmp_phy_init_tbl *pcs_tbl; - int pcs_tbl_num; - - /* clock ids to be requested */ - const char * const *clk_list; - int num_clks; - /* resets to be requested */ - const char * const *reset_list; - int num_resets; - /* regulators to be requested */ - const char * const *vreg_list; - int num_vregs; - - /* array of registers with different offsets */ - const unsigned int *regs; - - unsigned int start_ctrl; - unsigned int pwrdn_ctrl; - unsigned int mask_pcs_ready; - unsigned int mask_com_pcs_ready; - - /* true, if PHY has a separate PHY_COM control block */ - bool has_phy_com_ctrl; - /* true, if PHY has a reset for individual lanes */ - bool has_lane_rst; - /* true, if PHY needs delay after POWER_DOWN */ - bool has_pwrdn_delay; - /* power_down delay in usec */ - int pwrdn_delay_min; - int pwrdn_delay_max; -}; - -/** - * struct qmp_phy - per-lane phy descriptor - * - * @phy: generic phy - * @tx: iomapped memory space for lane's tx - * @rx: iomapped memory space for lane's rx - * @pcs: iomapped memory space for lane's pcs - * @pipe_clk: pipe lock - * @index: lane index - * @qmp: QMP phy to which this lane belongs - * @lane_rst: lane's reset controller - */ -struct qmp_phy { - struct phy *phy; - void __iomem *tx; - void __iomem *rx; - void __iomem *pcs; - struct clk *pipe_clk; - unsigned int index; - struct qcom_qmp *qmp; - struct reset_control *lane_rst; -}; - -/** - * struct qcom_qmp - structure holding QMP phy block attributes - * - * @dev: device - * @serdes: iomapped memory space for phy's serdes - * - * @clks: array of clocks required by phy - * @resets: array of resets required by phy - * @vregs: regulator supplies bulk data - * - * @cfg: phy specific configuration - * @phys: array of per-lane phy descriptors - * @phy_mutex: mutex lock for PHY common block initialization - * @init_count: phy common block initialization count - */ -struct qcom_qmp { - struct device *dev; - void __iomem *serdes; - - struct clk **clks; - struct reset_control **resets; - struct regulator_bulk_data *vregs; - - const struct qmp_phy_cfg *cfg; - struct qmp_phy **phys; - - struct mutex phy_mutex; - int init_count; -}; - -static inline void qphy_setbits(void __iomem *base, u32 offset, u32 val) -{ - u32 reg; - - reg = readl(base + offset); - reg |= val; - writel(reg, base + offset); - - /* ensure that above write is through */ - readl(base + offset); -} - -static inline void qphy_clrbits(void __iomem *base, u32 offset, u32 val) -{ - u32 reg; - - reg = readl(base + offset); - reg &= ~val; - writel(reg, base + offset); - - /* ensure that above write is through */ - readl(base + offset); -} - -/* list of clocks required by phy */ -static const char * const msm8996_phy_clk_l[] = { - "aux", "cfg_ahb", "ref", -}; - -/* list of resets */ -static const char * const msm8996_pciephy_reset_l[] = { - "phy", "common", "cfg", -}; - -static const char * const msm8996_usb3phy_reset_l[] = { - "phy", "common", -}; - -/* list of regulators */ -static const char * const msm8996_phy_vreg_l[] = { - "vdda-phy", "vdda-pll", -}; - -static const struct qmp_phy_cfg msm8996_pciephy_cfg = { - .type = PHY_TYPE_PCIE, - .nlanes = 3, - - .serdes_tbl = msm8996_pcie_serdes_tbl, - .serdes_tbl_num = ARRAY_SIZE(msm8996_pcie_serdes_tbl), - .tx_tbl = msm8996_pcie_tx_tbl, - .tx_tbl_num = ARRAY_SIZE(msm8996_pcie_tx_tbl), - .rx_tbl = msm8996_pcie_rx_tbl, - .rx_tbl_num = ARRAY_SIZE(msm8996_pcie_rx_tbl), - .pcs_tbl = msm8996_pcie_pcs_tbl, - .pcs_tbl_num = ARRAY_SIZE(msm8996_pcie_pcs_tbl), - .clk_list = msm8996_phy_clk_l, - .num_clks = ARRAY_SIZE(msm8996_phy_clk_l), - .reset_list = msm8996_pciephy_reset_l, - .num_resets = ARRAY_SIZE(msm8996_pciephy_reset_l), - .vreg_list = msm8996_phy_vreg_l, - .num_vregs = ARRAY_SIZE(msm8996_phy_vreg_l), - .regs = pciephy_regs_layout, - - .start_ctrl = PCS_START | PLL_READY_GATE_EN, - .pwrdn_ctrl = SW_PWRDN | REFCLK_DRV_DSBL, - .mask_com_pcs_ready = PCS_READY, - - .has_phy_com_ctrl = true, - .has_lane_rst = true, - .has_pwrdn_delay = true, - .pwrdn_delay_min = POWER_DOWN_DELAY_US_MIN, - .pwrdn_delay_max = POWER_DOWN_DELAY_US_MAX, -}; - -static const struct qmp_phy_cfg msm8996_usb3phy_cfg = { - .type = PHY_TYPE_USB3, - .nlanes = 1, - - .serdes_tbl = msm8996_usb3_serdes_tbl, - .serdes_tbl_num = ARRAY_SIZE(msm8996_usb3_serdes_tbl), - .tx_tbl = msm8996_usb3_tx_tbl, - .tx_tbl_num = ARRAY_SIZE(msm8996_usb3_tx_tbl), - .rx_tbl = msm8996_usb3_rx_tbl, - .rx_tbl_num = ARRAY_SIZE(msm8996_usb3_rx_tbl), - .pcs_tbl = msm8996_usb3_pcs_tbl, - .pcs_tbl_num = ARRAY_SIZE(msm8996_usb3_pcs_tbl), - .clk_list = msm8996_phy_clk_l, - .num_clks = ARRAY_SIZE(msm8996_phy_clk_l), - .reset_list = msm8996_usb3phy_reset_l, - .num_resets = ARRAY_SIZE(msm8996_usb3phy_reset_l), - .vreg_list = msm8996_phy_vreg_l, - .num_vregs = ARRAY_SIZE(msm8996_phy_vreg_l), - .regs = usb3phy_regs_layout, - - .start_ctrl = SERDES_START | PCS_START, - .pwrdn_ctrl = SW_PWRDN, - .mask_pcs_ready = PHYSTATUS, -}; - -static void qcom_qmp_phy_configure(void __iomem *base, - const unsigned int *regs, - const struct qmp_phy_init_tbl tbl[], - int num) -{ - int i; - const struct qmp_phy_init_tbl *t = tbl; - - if (!t) - return; - - for (i = 0; i < num; i++, t++) { - if (t->in_layout) - writel(t->val, base + regs[t->offset]); - else - writel(t->val, base + t->offset); - } -} - -static int qcom_qmp_phy_poweron(struct phy *phy) -{ - struct qmp_phy *qphy = phy_get_drvdata(phy); - struct qcom_qmp *qmp = qphy->qmp; - int num = qmp->cfg->num_vregs; - int ret; - - dev_vdbg(&phy->dev, "Powering on QMP phy\n"); - - /* turn on regulator supplies */ - ret = regulator_bulk_enable(num, qmp->vregs); - if (ret) { - dev_err(qmp->dev, "failed to enable regulators, err=%d\n", ret); - return ret; - } - - ret = clk_prepare_enable(qphy->pipe_clk); - if (ret) { - dev_err(qmp->dev, "pipe_clk enable failed, err=%d\n", ret); - regulator_bulk_disable(num, qmp->vregs); - return ret; - } - - return 0; -} - -static int qcom_qmp_phy_poweroff(struct phy *phy) -{ - struct qmp_phy *qphy = phy_get_drvdata(phy); - struct qcom_qmp *qmp = qphy->qmp; - - clk_disable_unprepare(qphy->pipe_clk); - - regulator_bulk_disable(qmp->cfg->num_vregs, qmp->vregs); - - return 0; -} - -static int qcom_qmp_phy_com_init(struct qcom_qmp *qmp) -{ - const struct qmp_phy_cfg *cfg = qmp->cfg; - void __iomem *serdes = qmp->serdes; - int ret, i; - - mutex_lock(&qmp->phy_mutex); - if (qmp->init_count++) { - mutex_unlock(&qmp->phy_mutex); - return 0; - } - - for (i = 0; i < cfg->num_resets; i++) { - ret = reset_control_deassert(qmp->resets[i]); - if (ret) { - dev_err(qmp->dev, "%s reset deassert failed\n", - qmp->cfg->reset_list[i]); - while (--i >= 0) - reset_control_assert(qmp->resets[i]); - goto err_rst; - } - } - - if (cfg->has_phy_com_ctrl) - qphy_setbits(serdes, cfg->regs[QPHY_COM_POWER_DOWN_CONTROL], - SW_PWRDN); - - /* Serdes configuration */ - qcom_qmp_phy_configure(serdes, cfg->regs, cfg->serdes_tbl, - cfg->serdes_tbl_num); - - if (cfg->has_phy_com_ctrl) { - void __iomem *status; - unsigned int mask, val; - - qphy_clrbits(serdes, cfg->regs[QPHY_COM_SW_RESET], SW_RESET); - qphy_setbits(serdes, cfg->regs[QPHY_COM_START_CONTROL], - SERDES_START | PCS_START); - - status = serdes + cfg->regs[QPHY_COM_PCS_READY_STATUS]; - mask = cfg->mask_com_pcs_ready; - - ret = readl_poll_timeout(status, val, (val & mask), 10, - PHY_INIT_COMPLETE_TIMEOUT); - if (ret) { - dev_err(qmp->dev, - "phy common block init timed-out\n"); - goto err_com_init; - } - } - - mutex_unlock(&qmp->phy_mutex); - - return 0; - -err_com_init: - while (--i >= 0) - reset_control_assert(qmp->resets[i]); -err_rst: - mutex_unlock(&qmp->phy_mutex); - return ret; -} - -static int qcom_qmp_phy_com_exit(struct qcom_qmp *qmp) -{ - const struct qmp_phy_cfg *cfg = qmp->cfg; - void __iomem *serdes = qmp->serdes; - int i = cfg->num_resets; - - mutex_lock(&qmp->phy_mutex); - if (--qmp->init_count) { - mutex_unlock(&qmp->phy_mutex); - return 0; - } - - if (cfg->has_phy_com_ctrl) { - qphy_setbits(serdes, cfg->regs[QPHY_COM_START_CONTROL], - SERDES_START | PCS_START); - qphy_clrbits(serdes, cfg->regs[QPHY_COM_SW_RESET], - SW_RESET); - qphy_setbits(serdes, cfg->regs[QPHY_COM_POWER_DOWN_CONTROL], - SW_PWRDN); - } - - while (--i >= 0) - reset_control_assert(qmp->resets[i]); - - mutex_unlock(&qmp->phy_mutex); - - return 0; -} - -/* PHY Initialization */ -static int qcom_qmp_phy_init(struct phy *phy) -{ - struct qmp_phy *qphy = phy_get_drvdata(phy); - struct qcom_qmp *qmp = qphy->qmp; - const struct qmp_phy_cfg *cfg = qmp->cfg; - void __iomem *tx = qphy->tx; - void __iomem *rx = qphy->rx; - void __iomem *pcs = qphy->pcs; - void __iomem *status; - unsigned int mask, val; - int ret, i; - - dev_vdbg(qmp->dev, "Initializing QMP phy\n"); - - for (i = 0; i < qmp->cfg->num_clks; i++) { - ret = clk_prepare_enable(qmp->clks[i]); - if (ret) { - dev_err(qmp->dev, "failed to enable %s clk, err=%d\n", - qmp->cfg->clk_list[i], ret); - while (--i >= 0) - clk_disable_unprepare(qmp->clks[i]); - } - } - - ret = qcom_qmp_phy_com_init(qmp); - if (ret) - goto err_com_init; - - if (cfg->has_lane_rst) { - ret = reset_control_deassert(qphy->lane_rst); - if (ret) { - dev_err(qmp->dev, "lane%d reset deassert failed\n", - qphy->index); - goto err_lane_rst; - } - } - - /* Tx, Rx, and PCS configurations */ - qcom_qmp_phy_configure(tx, cfg->regs, cfg->tx_tbl, cfg->tx_tbl_num); - qcom_qmp_phy_configure(rx, cfg->regs, cfg->rx_tbl, cfg->rx_tbl_num); - qcom_qmp_phy_configure(pcs, cfg->regs, cfg->pcs_tbl, cfg->pcs_tbl_num); - - /* - * Pull out PHY from POWER DOWN state. - * This is active low enable signal to power-down PHY. - */ - qphy_setbits(pcs, QPHY_POWER_DOWN_CONTROL, cfg->pwrdn_ctrl); - - if (cfg->has_pwrdn_delay) - usleep_range(cfg->pwrdn_delay_min, cfg->pwrdn_delay_max); - - /* start SerDes and Phy-Coding-Sublayer */ - qphy_setbits(pcs, cfg->regs[QPHY_START_CTRL], cfg->start_ctrl); - - /* Pull PHY out of reset state */ - qphy_clrbits(pcs, cfg->regs[QPHY_SW_RESET], SW_RESET); - - status = pcs + cfg->regs[QPHY_PCS_READY_STATUS]; - mask = cfg->mask_pcs_ready; - - ret = readl_poll_timeout(status, val, !(val & mask), 1, - PHY_INIT_COMPLETE_TIMEOUT); - if (ret) { - dev_err(qmp->dev, "phy initialization timed-out\n"); - goto err_pcs_ready; - } - - return ret; - -err_pcs_ready: - if (cfg->has_lane_rst) - reset_control_assert(qphy->lane_rst); -err_lane_rst: - qcom_qmp_phy_com_exit(qmp); -err_com_init: - while (--i >= 0) - clk_disable_unprepare(qmp->clks[i]); - - return ret; -} - -static int qcom_qmp_phy_exit(struct phy *phy) -{ - struct qmp_phy *qphy = phy_get_drvdata(phy); - struct qcom_qmp *qmp = qphy->qmp; - const struct qmp_phy_cfg *cfg = qmp->cfg; - int i = cfg->num_clks; - - /* PHY reset */ - qphy_setbits(qphy->pcs, cfg->regs[QPHY_SW_RESET], SW_RESET); - - /* stop SerDes and Phy-Coding-Sublayer */ - qphy_clrbits(qphy->pcs, cfg->regs[QPHY_START_CTRL], cfg->start_ctrl); - - /* Put PHY into POWER DOWN state: active low */ - qphy_clrbits(qphy->pcs, QPHY_POWER_DOWN_CONTROL, cfg->pwrdn_ctrl); - - if (cfg->has_lane_rst) - reset_control_assert(qphy->lane_rst); - - qcom_qmp_phy_com_exit(qmp); - - while (--i >= 0) - clk_disable_unprepare(qmp->clks[i]); - - return 0; -} - -static int qcom_qmp_phy_vreg_init(struct device *dev) -{ - struct qcom_qmp *qmp = dev_get_drvdata(dev); - int num = qmp->cfg->num_vregs; - int i; - - qmp->vregs = devm_kcalloc(dev, num, sizeof(*qmp->vregs), GFP_KERNEL); - if (!qmp->vregs) - return -ENOMEM; - - for (i = 0; i < num; i++) - qmp->vregs[i].supply = qmp->cfg->vreg_list[i]; - - return devm_regulator_bulk_get(dev, num, qmp->vregs); -} - -static int qcom_qmp_phy_reset_init(struct device *dev) -{ - struct qcom_qmp *qmp = dev_get_drvdata(dev); - int i; - - qmp->resets = devm_kcalloc(dev, qmp->cfg->num_resets, - sizeof(*qmp->resets), GFP_KERNEL); - if (!qmp->resets) - return -ENOMEM; - - for (i = 0; i < qmp->cfg->num_resets; i++) { - struct reset_control *rst; - const char *name = qmp->cfg->reset_list[i]; - - rst = devm_reset_control_get(dev, name); - if (IS_ERR(rst)) { - dev_err(dev, "failed to get %s reset\n", name); - return PTR_ERR(rst); - } - qmp->resets[i] = rst; - } - - return 0; -} - -static int qcom_qmp_phy_clk_init(struct device *dev) -{ - struct qcom_qmp *qmp = dev_get_drvdata(dev); - int ret, i; - - qmp->clks = devm_kcalloc(dev, qmp->cfg->num_clks, - sizeof(*qmp->clks), GFP_KERNEL); - if (!qmp->clks) - return -ENOMEM; - - for (i = 0; i < qmp->cfg->num_clks; i++) { - struct clk *_clk; - const char *name = qmp->cfg->clk_list[i]; - - _clk = devm_clk_get(dev, name); - if (IS_ERR(_clk)) { - ret = PTR_ERR(_clk); - if (ret != -EPROBE_DEFER) - dev_err(dev, "failed to get %s clk, %d\n", - name, ret); - return ret; - } - qmp->clks[i] = _clk; - } - - return 0; -} - -/* - * Register a fixed rate pipe clock. - * - * The _pipe_clksrc generated by PHY goes to the GCC that gate - * controls it. The _pipe_clk coming out of the GCC is requested - * by the PHY driver for its operations. - * We register the _pipe_clksrc here. The gcc driver takes care - * of assigning this _pipe_clksrc as parent to _pipe_clk. - * Below picture shows this relationship. - * - * +---------------+ - * | PHY block |<<---------------------------------------+ - * | | | - * | +-------+ | +-----+ | - * I/P---^-->| PLL |---^--->pipe_clksrc--->| GCC |--->pipe_clk---+ - * clk | +-------+ | +-----+ - * +---------------+ - */ -static int phy_pipe_clk_register(struct qcom_qmp *qmp, int id) -{ - char name[24]; - struct clk_fixed_rate *fixed; - struct clk_init_data init = { }; - - switch (qmp->cfg->type) { - case PHY_TYPE_USB3: - snprintf(name, sizeof(name), "usb3_phy_pipe_clk_src"); - break; - case PHY_TYPE_PCIE: - snprintf(name, sizeof(name), "pcie_%d_pipe_clk_src", id); - break; - default: - /* not all phys register pipe clocks, so return success */ - return 0; - } - - fixed = devm_kzalloc(qmp->dev, sizeof(*fixed), GFP_KERNEL); - if (!fixed) - return -ENOMEM; - - init.name = name; - init.ops = &clk_fixed_rate_ops; - - /* controllers using QMP phys use 125MHz pipe clock interface */ - fixed->fixed_rate = 125000000; - fixed->hw.init = &init; - - return devm_clk_hw_register(qmp->dev, &fixed->hw); -} - -static const struct phy_ops qcom_qmp_phy_gen_ops = { - .init = qcom_qmp_phy_init, - .exit = qcom_qmp_phy_exit, - .power_on = qcom_qmp_phy_poweron, - .power_off = qcom_qmp_phy_poweroff, - .owner = THIS_MODULE, -}; - -static -int qcom_qmp_phy_create(struct device *dev, struct device_node *np, int id) -{ - struct qcom_qmp *qmp = dev_get_drvdata(dev); - struct phy *generic_phy; - struct qmp_phy *qphy; - char prop_name[MAX_PROP_NAME]; - int ret; - - qphy = devm_kzalloc(dev, sizeof(*qphy), GFP_KERNEL); - if (!qphy) - return -ENOMEM; - - /* - * Get memory resources for each phy lane: - * Resources are indexed as: tx -> 0; rx -> 1; pcs -> 2. - */ - qphy->tx = of_iomap(np, 0); - if (!qphy->tx) - return -ENOMEM; - - qphy->rx = of_iomap(np, 1); - if (!qphy->rx) - return -ENOMEM; - - qphy->pcs = of_iomap(np, 2); - if (!qphy->pcs) - return -ENOMEM; - - /* - * Get PHY's Pipe clock, if any. USB3 and PCIe are PIPE3 - * based phys, so they essentially have pipe clock. So, - * we return error in case phy is USB3 or PIPE type. - * Otherwise, we initialize pipe clock to NULL for - * all phys that don't need this. - */ - snprintf(prop_name, sizeof(prop_name), "pipe%d", id); - qphy->pipe_clk = of_clk_get_by_name(np, prop_name); - if (IS_ERR(qphy->pipe_clk)) { - if (qmp->cfg->type == PHY_TYPE_PCIE || - qmp->cfg->type == PHY_TYPE_USB3) { - ret = PTR_ERR(qphy->pipe_clk); - if (ret != -EPROBE_DEFER) - dev_err(dev, - "failed to get lane%d pipe_clk, %d\n", - id, ret); - return ret; - } - qphy->pipe_clk = NULL; - } - - /* Get lane reset, if any */ - if (qmp->cfg->has_lane_rst) { - snprintf(prop_name, sizeof(prop_name), "lane%d", id); - qphy->lane_rst = of_reset_control_get(np, prop_name); - if (IS_ERR(qphy->lane_rst)) { - dev_err(dev, "failed to get lane%d reset\n", id); - return PTR_ERR(qphy->lane_rst); - } - } - - generic_phy = devm_phy_create(dev, np, &qcom_qmp_phy_gen_ops); - if (IS_ERR(generic_phy)) { - ret = PTR_ERR(generic_phy); - dev_err(dev, "failed to create qphy %d\n", ret); - return ret; - } - - qphy->phy = generic_phy; - qphy->index = id; - qphy->qmp = qmp; - qmp->phys[id] = qphy; - phy_set_drvdata(generic_phy, qphy); - - return 0; -} - -static const struct of_device_id qcom_qmp_phy_of_match_table[] = { - { - .compatible = "qcom,msm8996-qmp-pcie-phy", - .data = &msm8996_pciephy_cfg, - }, { - .compatible = "qcom,msm8996-qmp-usb3-phy", - .data = &msm8996_usb3phy_cfg, - }, - { }, -}; -MODULE_DEVICE_TABLE(of, qcom_qmp_phy_of_match_table); - -static int qcom_qmp_phy_probe(struct platform_device *pdev) -{ - struct qcom_qmp *qmp; - struct device *dev = &pdev->dev; - struct resource *res; - struct device_node *child; - struct phy_provider *phy_provider; - void __iomem *base; - int num, id; - int ret; - - qmp = devm_kzalloc(dev, sizeof(*qmp), GFP_KERNEL); - if (!qmp) - return -ENOMEM; - - qmp->dev = dev; - dev_set_drvdata(dev, qmp); - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - base = devm_ioremap_resource(dev, res); - if (IS_ERR(base)) - return PTR_ERR(base); - - /* per PHY serdes; usually located at base address */ - qmp->serdes = base; - - mutex_init(&qmp->phy_mutex); - - /* Get the specific init parameters of QMP phy */ - qmp->cfg = of_device_get_match_data(dev); - - ret = qcom_qmp_phy_clk_init(dev); - if (ret) - return ret; - - ret = qcom_qmp_phy_reset_init(dev); - if (ret) - return ret; - - ret = qcom_qmp_phy_vreg_init(dev); - if (ret) { - dev_err(dev, "failed to get regulator supplies\n"); - return ret; - } - - num = of_get_available_child_count(dev->of_node); - /* do we have a rogue child node ? */ - if (num > qmp->cfg->nlanes) - return -EINVAL; - - qmp->phys = devm_kcalloc(dev, num, sizeof(*qmp->phys), GFP_KERNEL); - if (!qmp->phys) - return -ENOMEM; - - id = 0; - for_each_available_child_of_node(dev->of_node, child) { - /* Create per-lane phy */ - ret = qcom_qmp_phy_create(dev, child, id); - if (ret) { - dev_err(dev, "failed to create lane%d phy, %d\n", - id, ret); - return ret; - } - - /* - * Register the pipe clock provided by phy. - * See function description to see details of this pipe clock. - */ - ret = phy_pipe_clk_register(qmp, id); - if (ret) { - dev_err(qmp->dev, - "failed to register pipe clock source\n"); - return ret; - } - id++; - } - - phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); - if (!IS_ERR(phy_provider)) - dev_info(dev, "Registered Qcom-QMP phy\n"); - - return PTR_ERR_OR_ZERO(phy_provider); -} - -static struct platform_driver qcom_qmp_phy_driver = { - .probe = qcom_qmp_phy_probe, - .driver = { - .name = "qcom-qmp-phy", - .of_match_table = qcom_qmp_phy_of_match_table, - }, -}; - -module_platform_driver(qcom_qmp_phy_driver); - -MODULE_AUTHOR("Vivek Gautam "); -MODULE_DESCRIPTION("Qualcomm QMP PHY driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-qcom-qusb2.c b/drivers/phy/phy-qcom-qusb2.c deleted file mode 100644 index 6c575244c0fb..000000000000 --- a/drivers/phy/phy-qcom-qusb2.c +++ /dev/null @@ -1,493 +0,0 @@ -/* - * Copyright (c) 2017, The Linux Foundation. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define QUSB2PHY_PLL_TEST 0x04 -#define CLK_REF_SEL BIT(7) - -#define QUSB2PHY_PLL_TUNE 0x08 -#define QUSB2PHY_PLL_USER_CTL1 0x0c -#define QUSB2PHY_PLL_USER_CTL2 0x10 -#define QUSB2PHY_PLL_AUTOPGM_CTL1 0x1c -#define QUSB2PHY_PLL_PWR_CTRL 0x18 - -#define QUSB2PHY_PLL_STATUS 0x38 -#define PLL_LOCKED BIT(5) - -#define QUSB2PHY_PORT_TUNE1 0x80 -#define QUSB2PHY_PORT_TUNE2 0x84 -#define QUSB2PHY_PORT_TUNE3 0x88 -#define QUSB2PHY_PORT_TUNE4 0x8c -#define QUSB2PHY_PORT_TUNE5 0x90 -#define QUSB2PHY_PORT_TEST2 0x9c - -#define QUSB2PHY_PORT_POWERDOWN 0xb4 -#define CLAMP_N_EN BIT(5) -#define FREEZIO_N BIT(1) -#define POWER_DOWN BIT(0) - -#define QUSB2PHY_REFCLK_ENABLE BIT(0) - -#define PHY_CLK_SCHEME_SEL BIT(0) - -struct qusb2_phy_init_tbl { - unsigned int offset; - unsigned int val; -}; - -#define QUSB2_PHY_INIT_CFG(o, v) \ - { \ - .offset = o, \ - .val = v, \ - } - -static const struct qusb2_phy_init_tbl msm8996_init_tbl[] = { - QUSB2_PHY_INIT_CFG(QUSB2PHY_PORT_TUNE1, 0xf8), - QUSB2_PHY_INIT_CFG(QUSB2PHY_PORT_TUNE2, 0xb3), - QUSB2_PHY_INIT_CFG(QUSB2PHY_PORT_TUNE3, 0x83), - QUSB2_PHY_INIT_CFG(QUSB2PHY_PORT_TUNE4, 0xc0), - QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_TUNE, 0x30), - QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_USER_CTL1, 0x79), - QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_USER_CTL2, 0x21), - QUSB2_PHY_INIT_CFG(QUSB2PHY_PORT_TEST2, 0x14), - QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_AUTOPGM_CTL1, 0x9f), - QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_PWR_CTRL, 0x00), -}; - -struct qusb2_phy_cfg { - const struct qusb2_phy_init_tbl *tbl; - /* number of entries in the table */ - unsigned int tbl_num; - /* offset to PHY_CLK_SCHEME register in TCSR map */ - unsigned int clk_scheme_offset; -}; - -static const struct qusb2_phy_cfg msm8996_phy_cfg = { - .tbl = msm8996_init_tbl, - .tbl_num = ARRAY_SIZE(msm8996_init_tbl), -}; - -static const char * const qusb2_phy_vreg_names[] = { - "vdda-pll", "vdda-phy-dpdm", -}; - -#define QUSB2_NUM_VREGS ARRAY_SIZE(qusb2_phy_vreg_names) - -/** - * struct qusb2_phy - structure holding qusb2 phy attributes - * - * @phy: generic phy - * @base: iomapped memory space for qubs2 phy - * - * @cfg_ahb_clk: AHB2PHY interface clock - * @ref_clk: phy reference clock - * @iface_clk: phy interface clock - * @phy_reset: phy reset control - * @vregs: regulator supplies bulk data - * - * @tcsr: TCSR syscon register map - * @cell: nvmem cell containing phy tuning value - * - * @cfg: phy config data - * @has_se_clk_scheme: indicate if PHY has single-ended ref clock scheme - */ -struct qusb2_phy { - struct phy *phy; - void __iomem *base; - - struct clk *cfg_ahb_clk; - struct clk *ref_clk; - struct clk *iface_clk; - struct reset_control *phy_reset; - struct regulator_bulk_data vregs[QUSB2_NUM_VREGS]; - - struct regmap *tcsr; - struct nvmem_cell *cell; - - const struct qusb2_phy_cfg *cfg; - bool has_se_clk_scheme; -}; - -static inline void qusb2_setbits(void __iomem *base, u32 offset, u32 val) -{ - u32 reg; - - reg = readl(base + offset); - reg |= val; - writel(reg, base + offset); - - /* Ensure above write is completed */ - readl(base + offset); -} - -static inline void qusb2_clrbits(void __iomem *base, u32 offset, u32 val) -{ - u32 reg; - - reg = readl(base + offset); - reg &= ~val; - writel(reg, base + offset); - - /* Ensure above write is completed */ - readl(base + offset); -} - -static inline -void qcom_qusb2_phy_configure(void __iomem *base, - const struct qusb2_phy_init_tbl tbl[], int num) -{ - int i; - - for (i = 0; i < num; i++) - writel(tbl[i].val, base + tbl[i].offset); -} - -/* - * Fetches HS Tx tuning value from nvmem and sets the - * QUSB2PHY_PORT_TUNE2 register. - * For error case, skip setting the value and use the default value. - */ -static void qusb2_phy_set_tune2_param(struct qusb2_phy *qphy) -{ - struct device *dev = &qphy->phy->dev; - u8 *val; - - /* - * Read efuse register having TUNE2 parameter's high nibble. - * If efuse register shows value as 0x0, or if we fail to find - * a valid efuse register settings, then use default value - * as 0xB for high nibble that we have already set while - * configuring phy. - */ - val = nvmem_cell_read(qphy->cell, NULL); - if (IS_ERR(val) || !val[0]) { - dev_dbg(dev, "failed to read a valid hs-tx trim value\n"); - return; - } - - /* Fused TUNE2 value is the higher nibble only */ - qusb2_setbits(qphy->base, QUSB2PHY_PORT_TUNE2, val[0] << 0x4); -} - -static int qusb2_phy_poweron(struct phy *phy) -{ - struct qusb2_phy *qphy = phy_get_drvdata(phy); - int num = ARRAY_SIZE(qphy->vregs); - int ret; - - dev_vdbg(&phy->dev, "%s(): Powering-on QUSB2 phy\n", __func__); - - /* turn on regulator supplies */ - ret = regulator_bulk_enable(num, qphy->vregs); - if (ret) - return ret; - - ret = clk_prepare_enable(qphy->iface_clk); - if (ret) { - dev_err(&phy->dev, "failed to enable iface_clk, %d\n", ret); - regulator_bulk_disable(num, qphy->vregs); - return ret; - } - - return 0; -} - -static int qusb2_phy_poweroff(struct phy *phy) -{ - struct qusb2_phy *qphy = phy_get_drvdata(phy); - - clk_disable_unprepare(qphy->iface_clk); - - regulator_bulk_disable(ARRAY_SIZE(qphy->vregs), qphy->vregs); - - return 0; -} - -static int qusb2_phy_init(struct phy *phy) -{ - struct qusb2_phy *qphy = phy_get_drvdata(phy); - unsigned int val; - unsigned int clk_scheme; - int ret; - - dev_vdbg(&phy->dev, "%s(): Initializing QUSB2 phy\n", __func__); - - /* enable ahb interface clock to program phy */ - ret = clk_prepare_enable(qphy->cfg_ahb_clk); - if (ret) { - dev_err(&phy->dev, "failed to enable cfg ahb clock, %d\n", ret); - return ret; - } - - /* Perform phy reset */ - ret = reset_control_assert(qphy->phy_reset); - if (ret) { - dev_err(&phy->dev, "failed to assert phy_reset, %d\n", ret); - goto disable_ahb_clk; - } - - /* 100 us delay to keep PHY in reset mode */ - usleep_range(100, 150); - - ret = reset_control_deassert(qphy->phy_reset); - if (ret) { - dev_err(&phy->dev, "failed to de-assert phy_reset, %d\n", ret); - goto disable_ahb_clk; - } - - /* Disable the PHY */ - qusb2_setbits(qphy->base, QUSB2PHY_PORT_POWERDOWN, - CLAMP_N_EN | FREEZIO_N | POWER_DOWN); - - /* save reset value to override reference clock scheme later */ - val = readl(qphy->base + QUSB2PHY_PLL_TEST); - - qcom_qusb2_phy_configure(qphy->base, qphy->cfg->tbl, - qphy->cfg->tbl_num); - - /* Set efuse value for tuning the PHY */ - qusb2_phy_set_tune2_param(qphy); - - /* Enable the PHY */ - qusb2_clrbits(qphy->base, QUSB2PHY_PORT_POWERDOWN, POWER_DOWN); - - /* Required to get phy pll lock successfully */ - usleep_range(150, 160); - - /* Default is single-ended clock on msm8996 */ - qphy->has_se_clk_scheme = true; - /* - * read TCSR_PHY_CLK_SCHEME register to check if single-ended - * clock scheme is selected. If yes, then disable differential - * ref_clk and use single-ended clock, otherwise use differential - * ref_clk only. - */ - if (qphy->tcsr) { - ret = regmap_read(qphy->tcsr, qphy->cfg->clk_scheme_offset, - &clk_scheme); - if (ret) { - dev_err(&phy->dev, "failed to read clk scheme reg\n"); - goto assert_phy_reset; - } - - /* is it a differential clock scheme ? */ - if (!(clk_scheme & PHY_CLK_SCHEME_SEL)) { - dev_vdbg(&phy->dev, "%s(): select differential clk\n", - __func__); - qphy->has_se_clk_scheme = false; - } else { - dev_vdbg(&phy->dev, "%s(): select single-ended clk\n", - __func__); - } - } - - if (!qphy->has_se_clk_scheme) { - val &= ~CLK_REF_SEL; - ret = clk_prepare_enable(qphy->ref_clk); - if (ret) { - dev_err(&phy->dev, "failed to enable ref clk, %d\n", - ret); - goto assert_phy_reset; - } - } else { - val |= CLK_REF_SEL; - } - - writel(val, qphy->base + QUSB2PHY_PLL_TEST); - - /* ensure above write is through */ - readl(qphy->base + QUSB2PHY_PLL_TEST); - - /* Required to get phy pll lock successfully */ - usleep_range(100, 110); - - val = readb(qphy->base + QUSB2PHY_PLL_STATUS); - if (!(val & PLL_LOCKED)) { - dev_err(&phy->dev, - "QUSB2PHY pll lock failed: status reg = %x\n", val); - ret = -EBUSY; - goto disable_ref_clk; - } - - return 0; - -disable_ref_clk: - if (!qphy->has_se_clk_scheme) - clk_disable_unprepare(qphy->ref_clk); -assert_phy_reset: - reset_control_assert(qphy->phy_reset); -disable_ahb_clk: - clk_disable_unprepare(qphy->cfg_ahb_clk); - return ret; -} - -static int qusb2_phy_exit(struct phy *phy) -{ - struct qusb2_phy *qphy = phy_get_drvdata(phy); - - /* Disable the PHY */ - qusb2_setbits(qphy->base, QUSB2PHY_PORT_POWERDOWN, - CLAMP_N_EN | FREEZIO_N | POWER_DOWN); - - if (!qphy->has_se_clk_scheme) - clk_disable_unprepare(qphy->ref_clk); - - reset_control_assert(qphy->phy_reset); - - clk_disable_unprepare(qphy->cfg_ahb_clk); - - return 0; -} - -static const struct phy_ops qusb2_phy_gen_ops = { - .init = qusb2_phy_init, - .exit = qusb2_phy_exit, - .power_on = qusb2_phy_poweron, - .power_off = qusb2_phy_poweroff, - .owner = THIS_MODULE, -}; - -static const struct of_device_id qusb2_phy_of_match_table[] = { - { - .compatible = "qcom,msm8996-qusb2-phy", - .data = &msm8996_phy_cfg, - }, - { }, -}; -MODULE_DEVICE_TABLE(of, qusb2_phy_of_match_table); - -static int qusb2_phy_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct qusb2_phy *qphy; - struct phy_provider *phy_provider; - struct phy *generic_phy; - struct resource *res; - int ret, i; - int num; - - qphy = devm_kzalloc(dev, sizeof(*qphy), GFP_KERNEL); - if (!qphy) - return -ENOMEM; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - qphy->base = devm_ioremap_resource(dev, res); - if (IS_ERR(qphy->base)) - return PTR_ERR(qphy->base); - - qphy->cfg_ahb_clk = devm_clk_get(dev, "cfg_ahb"); - if (IS_ERR(qphy->cfg_ahb_clk)) { - ret = PTR_ERR(qphy->cfg_ahb_clk); - if (ret != -EPROBE_DEFER) - dev_err(dev, "failed to get cfg ahb clk, %d\n", ret); - return ret; - } - - qphy->ref_clk = devm_clk_get(dev, "ref"); - if (IS_ERR(qphy->ref_clk)) { - ret = PTR_ERR(qphy->ref_clk); - if (ret != -EPROBE_DEFER) - dev_err(dev, "failed to get ref clk, %d\n", ret); - return ret; - } - - qphy->iface_clk = devm_clk_get(dev, "iface"); - if (IS_ERR(qphy->iface_clk)) { - ret = PTR_ERR(qphy->iface_clk); - if (ret == -EPROBE_DEFER) - return ret; - qphy->iface_clk = NULL; - dev_dbg(dev, "failed to get iface clk, %d\n", ret); - } - - qphy->phy_reset = devm_reset_control_get_by_index(&pdev->dev, 0); - if (IS_ERR(qphy->phy_reset)) { - dev_err(dev, "failed to get phy core reset\n"); - return PTR_ERR(qphy->phy_reset); - } - - num = ARRAY_SIZE(qphy->vregs); - for (i = 0; i < num; i++) - qphy->vregs[i].supply = qusb2_phy_vreg_names[i]; - - ret = devm_regulator_bulk_get(dev, num, qphy->vregs); - if (ret) { - dev_err(dev, "failed to get regulator supplies\n"); - return ret; - } - - /* Get the specific init parameters of QMP phy */ - qphy->cfg = of_device_get_match_data(dev); - - qphy->tcsr = syscon_regmap_lookup_by_phandle(dev->of_node, - "qcom,tcsr-syscon"); - if (IS_ERR(qphy->tcsr)) { - dev_dbg(dev, "failed to lookup TCSR regmap\n"); - qphy->tcsr = NULL; - } - - qphy->cell = devm_nvmem_cell_get(dev, NULL); - if (IS_ERR(qphy->cell)) { - if (PTR_ERR(qphy->cell) == -EPROBE_DEFER) - return -EPROBE_DEFER; - qphy->cell = NULL; - dev_dbg(dev, "failed to lookup tune2 hstx trim value\n"); - } - - generic_phy = devm_phy_create(dev, NULL, &qusb2_phy_gen_ops); - if (IS_ERR(generic_phy)) { - ret = PTR_ERR(generic_phy); - dev_err(dev, "failed to create phy, %d\n", ret); - return ret; - } - qphy->phy = generic_phy; - - dev_set_drvdata(dev, qphy); - phy_set_drvdata(generic_phy, qphy); - - phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); - if (!IS_ERR(phy_provider)) - dev_info(dev, "Registered Qcom-QUSB2 phy\n"); - - return PTR_ERR_OR_ZERO(phy_provider); -} - -static struct platform_driver qusb2_phy_driver = { - .probe = qusb2_phy_probe, - .driver = { - .name = "qcom-qusb2-phy", - .of_match_table = qusb2_phy_of_match_table, - }, -}; - -module_platform_driver(qusb2_phy_driver); - -MODULE_AUTHOR("Vivek Gautam "); -MODULE_DESCRIPTION("Qualcomm QUSB2 PHY driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-qcom-ufs-i.h b/drivers/phy/phy-qcom-ufs-i.h deleted file mode 100644 index 13b02b7de30b..000000000000 --- a/drivers/phy/phy-qcom-ufs-i.h +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (c) 2013-2015, Linux Foundation. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -#ifndef UFS_QCOM_PHY_I_H_ -#define UFS_QCOM_PHY_I_H_ - -#include -#include -#include -#include -#include -#include -#include -#include - -#define readl_poll_timeout(addr, val, cond, sleep_us, timeout_us) \ -({ \ - ktime_t timeout = ktime_add_us(ktime_get(), timeout_us); \ - might_sleep_if(timeout_us); \ - for (;;) { \ - (val) = readl(addr); \ - if (cond) \ - break; \ - if (timeout_us && ktime_compare(ktime_get(), timeout) > 0) { \ - (val) = readl(addr); \ - break; \ - } \ - if (sleep_us) \ - usleep_range(DIV_ROUND_UP(sleep_us, 4), sleep_us); \ - } \ - (cond) ? 0 : -ETIMEDOUT; \ -}) - -#define UFS_QCOM_PHY_CAL_ENTRY(reg, val) \ - { \ - .reg_offset = reg, \ - .cfg_value = val, \ - } - -#define UFS_QCOM_PHY_NAME_LEN 30 - -enum { - MASK_SERDES_START = 0x1, - MASK_PCS_READY = 0x1, -}; - -enum { - OFFSET_SERDES_START = 0x0, -}; - -struct ufs_qcom_phy_stored_attributes { - u32 att; - u32 value; -}; - - -struct ufs_qcom_phy_calibration { - u32 reg_offset; - u32 cfg_value; -}; - -struct ufs_qcom_phy_vreg { - const char *name; - struct regulator *reg; - int max_uA; - int min_uV; - int max_uV; - bool enabled; -}; - -struct ufs_qcom_phy { - struct list_head list; - struct device *dev; - void __iomem *mmio; - void __iomem *dev_ref_clk_ctrl_mmio; - struct clk *tx_iface_clk; - struct clk *rx_iface_clk; - bool is_iface_clk_enabled; - struct clk *ref_clk_src; - struct clk *ref_clk_parent; - struct clk *ref_clk; - bool is_ref_clk_enabled; - bool is_dev_ref_clk_enabled; - struct ufs_qcom_phy_vreg vdda_pll; - struct ufs_qcom_phy_vreg vdda_phy; - struct ufs_qcom_phy_vreg vddp_ref_clk; - unsigned int quirks; - - /* - * If UFS link is put into Hibern8 and if UFS PHY analog hardware is - * power collapsed (by clearing UFS_PHY_POWER_DOWN_CONTROL), Hibern8 - * exit might fail even after powering on UFS PHY analog hardware. - * Enabling this quirk will help to solve above issue by doing - * custom PHY settings just before PHY analog power collapse. - */ - #define UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE BIT(0) - - u8 host_ctrl_rev_major; - u16 host_ctrl_rev_minor; - u16 host_ctrl_rev_step; - - char name[UFS_QCOM_PHY_NAME_LEN]; - struct ufs_qcom_phy_calibration *cached_regs; - int cached_regs_table_size; - bool is_powered_on; - struct ufs_qcom_phy_specific_ops *phy_spec_ops; -}; - -/** - * struct ufs_qcom_phy_specific_ops - set of pointers to functions which have a - * specific implementation per phy. Each UFS phy, should implement - * those functions according to its spec and requirements - * @calibrate_phy: pointer to a function that calibrate the phy - * @start_serdes: pointer to a function that starts the serdes - * @is_physical_coding_sublayer_ready: pointer to a function that - * checks pcs readiness. returns 0 for success and non-zero for error. - * @set_tx_lane_enable: pointer to a function that enable tx lanes - * @power_control: pointer to a function that controls analog rail of phy - * and writes to QSERDES_RX_SIGDET_CNTRL attribute - */ -struct ufs_qcom_phy_specific_ops { - int (*calibrate_phy)(struct ufs_qcom_phy *phy, bool is_rate_B); - void (*start_serdes)(struct ufs_qcom_phy *phy); - int (*is_physical_coding_sublayer_ready)(struct ufs_qcom_phy *phy); - void (*set_tx_lane_enable)(struct ufs_qcom_phy *phy, u32 val); - void (*power_control)(struct ufs_qcom_phy *phy, bool val); -}; - -struct ufs_qcom_phy *get_ufs_qcom_phy(struct phy *generic_phy); -int ufs_qcom_phy_power_on(struct phy *generic_phy); -int ufs_qcom_phy_power_off(struct phy *generic_phy); -int ufs_qcom_phy_init_clks(struct ufs_qcom_phy *phy_common); -int ufs_qcom_phy_init_vregulators(struct ufs_qcom_phy *phy_common); -int ufs_qcom_phy_remove(struct phy *generic_phy, - struct ufs_qcom_phy *ufs_qcom_phy); -struct phy *ufs_qcom_phy_generic_probe(struct platform_device *pdev, - struct ufs_qcom_phy *common_cfg, - const struct phy_ops *ufs_qcom_phy_gen_ops, - struct ufs_qcom_phy_specific_ops *phy_spec_ops); -int ufs_qcom_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy, - struct ufs_qcom_phy_calibration *tbl_A, int tbl_size_A, - struct ufs_qcom_phy_calibration *tbl_B, int tbl_size_B, - bool is_rate_B); -#endif diff --git a/drivers/phy/phy-qcom-ufs-qmp-14nm.c b/drivers/phy/phy-qcom-ufs-qmp-14nm.c deleted file mode 100644 index 12a1b498dc4b..000000000000 --- a/drivers/phy/phy-qcom-ufs-qmp-14nm.c +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright (c) 2013-2015, Linux Foundation. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -#include "phy-qcom-ufs-qmp-14nm.h" - -#define UFS_PHY_NAME "ufs_phy_qmp_14nm" -#define UFS_PHY_VDDA_PHY_UV (925000) - -static -int ufs_qcom_phy_qmp_14nm_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy, - bool is_rate_B) -{ - int tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A); - int tbl_size_B = ARRAY_SIZE(phy_cal_table_rate_B); - int err; - - err = ufs_qcom_phy_calibrate(ufs_qcom_phy, phy_cal_table_rate_A, - tbl_size_A, phy_cal_table_rate_B, tbl_size_B, is_rate_B); - - if (err) - dev_err(ufs_qcom_phy->dev, - "%s: ufs_qcom_phy_calibrate() failed %d\n", - __func__, err); - return err; -} - -static -void ufs_qcom_phy_qmp_14nm_advertise_quirks(struct ufs_qcom_phy *phy_common) -{ - phy_common->quirks = - UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE; -} - -static int ufs_qcom_phy_qmp_14nm_init(struct phy *generic_phy) -{ - return 0; -} - -static int ufs_qcom_phy_qmp_14nm_exit(struct phy *generic_phy) -{ - return 0; -} - -static -void ufs_qcom_phy_qmp_14nm_power_control(struct ufs_qcom_phy *phy, bool val) -{ - writel_relaxed(val ? 0x1 : 0x0, phy->mmio + UFS_PHY_POWER_DOWN_CONTROL); - /* - * Before any transactions involving PHY, ensure PHY knows - * that it's analog rail is powered ON (or OFF). - */ - mb(); -} - -static inline -void ufs_qcom_phy_qmp_14nm_set_tx_lane_enable(struct ufs_qcom_phy *phy, u32 val) -{ - /* - * 14nm PHY does not have TX_LANE_ENABLE register. - * Implement this function so as not to propagate error to caller. - */ -} - -static inline void ufs_qcom_phy_qmp_14nm_start_serdes(struct ufs_qcom_phy *phy) -{ - u32 tmp; - - tmp = readl_relaxed(phy->mmio + UFS_PHY_PHY_START); - tmp &= ~MASK_SERDES_START; - tmp |= (1 << OFFSET_SERDES_START); - writel_relaxed(tmp, phy->mmio + UFS_PHY_PHY_START); - /* Ensure register value is committed */ - mb(); -} - -static int ufs_qcom_phy_qmp_14nm_is_pcs_ready(struct ufs_qcom_phy *phy_common) -{ - int err = 0; - u32 val; - - err = readl_poll_timeout(phy_common->mmio + UFS_PHY_PCS_READY_STATUS, - val, (val & MASK_PCS_READY), 10, 1000000); - if (err) - dev_err(phy_common->dev, "%s: poll for pcs failed err = %d\n", - __func__, err); - return err; -} - -static const struct phy_ops ufs_qcom_phy_qmp_14nm_phy_ops = { - .init = ufs_qcom_phy_qmp_14nm_init, - .exit = ufs_qcom_phy_qmp_14nm_exit, - .power_on = ufs_qcom_phy_power_on, - .power_off = ufs_qcom_phy_power_off, - .owner = THIS_MODULE, -}; - -static struct ufs_qcom_phy_specific_ops phy_14nm_ops = { - .calibrate_phy = ufs_qcom_phy_qmp_14nm_phy_calibrate, - .start_serdes = ufs_qcom_phy_qmp_14nm_start_serdes, - .is_physical_coding_sublayer_ready = ufs_qcom_phy_qmp_14nm_is_pcs_ready, - .set_tx_lane_enable = ufs_qcom_phy_qmp_14nm_set_tx_lane_enable, - .power_control = ufs_qcom_phy_qmp_14nm_power_control, -}; - -static int ufs_qcom_phy_qmp_14nm_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct phy *generic_phy; - struct ufs_qcom_phy_qmp_14nm *phy; - struct ufs_qcom_phy *phy_common; - int err = 0; - - phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); - if (!phy) { - err = -ENOMEM; - goto out; - } - phy_common = &phy->common_cfg; - - generic_phy = ufs_qcom_phy_generic_probe(pdev, phy_common, - &ufs_qcom_phy_qmp_14nm_phy_ops, &phy_14nm_ops); - - if (!generic_phy) { - err = -EIO; - goto out; - } - - err = ufs_qcom_phy_init_clks(phy_common); - if (err) - goto out; - - err = ufs_qcom_phy_init_vregulators(phy_common); - if (err) - goto out; - - phy_common->vdda_phy.max_uV = UFS_PHY_VDDA_PHY_UV; - phy_common->vdda_phy.min_uV = UFS_PHY_VDDA_PHY_UV; - - ufs_qcom_phy_qmp_14nm_advertise_quirks(phy_common); - - phy_set_drvdata(generic_phy, phy); - - strlcpy(phy_common->name, UFS_PHY_NAME, sizeof(phy_common->name)); - -out: - return err; -} - -static const struct of_device_id ufs_qcom_phy_qmp_14nm_of_match[] = { - {.compatible = "qcom,ufs-phy-qmp-14nm"}, - {.compatible = "qcom,msm8996-ufs-phy-qmp-14nm"}, - {}, -}; -MODULE_DEVICE_TABLE(of, ufs_qcom_phy_qmp_14nm_of_match); - -static struct platform_driver ufs_qcom_phy_qmp_14nm_driver = { - .probe = ufs_qcom_phy_qmp_14nm_probe, - .driver = { - .of_match_table = ufs_qcom_phy_qmp_14nm_of_match, - .name = "ufs_qcom_phy_qmp_14nm", - }, -}; - -module_platform_driver(ufs_qcom_phy_qmp_14nm_driver); - -MODULE_DESCRIPTION("Universal Flash Storage (UFS) QCOM PHY QMP 14nm"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-qcom-ufs-qmp-14nm.h b/drivers/phy/phy-qcom-ufs-qmp-14nm.h deleted file mode 100644 index 3aefdbacbcd0..000000000000 --- a/drivers/phy/phy-qcom-ufs-qmp-14nm.h +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (c) 2013-2015, Linux Foundation. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -#ifndef UFS_QCOM_PHY_QMP_14NM_H_ -#define UFS_QCOM_PHY_QMP_14NM_H_ - -#include "phy-qcom-ufs-i.h" - -/* QCOM UFS PHY control registers */ -#define COM_OFF(x) (0x000 + x) -#define PHY_OFF(x) (0xC00 + x) -#define TX_OFF(n, x) (0x400 + (0x400 * n) + x) -#define RX_OFF(n, x) (0x600 + (0x400 * n) + x) - -/* UFS PHY QSERDES COM registers */ -#define QSERDES_COM_BG_TIMER COM_OFF(0x0C) -#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN COM_OFF(0x34) -#define QSERDES_COM_SYS_CLK_CTRL COM_OFF(0x3C) -#define QSERDES_COM_LOCK_CMP1_MODE0 COM_OFF(0x4C) -#define QSERDES_COM_LOCK_CMP2_MODE0 COM_OFF(0x50) -#define QSERDES_COM_LOCK_CMP3_MODE0 COM_OFF(0x54) -#define QSERDES_COM_LOCK_CMP1_MODE1 COM_OFF(0x58) -#define QSERDES_COM_LOCK_CMP2_MODE1 COM_OFF(0x5C) -#define QSERDES_COM_LOCK_CMP3_MODE1 COM_OFF(0x60) -#define QSERDES_COM_CP_CTRL_MODE0 COM_OFF(0x78) -#define QSERDES_COM_CP_CTRL_MODE1 COM_OFF(0x7C) -#define QSERDES_COM_PLL_RCTRL_MODE0 COM_OFF(0x84) -#define QSERDES_COM_PLL_RCTRL_MODE1 COM_OFF(0x88) -#define QSERDES_COM_PLL_CCTRL_MODE0 COM_OFF(0x90) -#define QSERDES_COM_PLL_CCTRL_MODE1 COM_OFF(0x94) -#define QSERDES_COM_SYSCLK_EN_SEL COM_OFF(0xAC) -#define QSERDES_COM_RESETSM_CNTRL COM_OFF(0xB4) -#define QSERDES_COM_LOCK_CMP_EN COM_OFF(0xC8) -#define QSERDES_COM_LOCK_CMP_CFG COM_OFF(0xCC) -#define QSERDES_COM_DEC_START_MODE0 COM_OFF(0xD0) -#define QSERDES_COM_DEC_START_MODE1 COM_OFF(0xD4) -#define QSERDES_COM_DIV_FRAC_START1_MODE0 COM_OFF(0xDC) -#define QSERDES_COM_DIV_FRAC_START2_MODE0 COM_OFF(0xE0) -#define QSERDES_COM_DIV_FRAC_START3_MODE0 COM_OFF(0xE4) -#define QSERDES_COM_DIV_FRAC_START1_MODE1 COM_OFF(0xE8) -#define QSERDES_COM_DIV_FRAC_START2_MODE1 COM_OFF(0xEC) -#define QSERDES_COM_DIV_FRAC_START3_MODE1 COM_OFF(0xF0) -#define QSERDES_COM_INTEGLOOP_GAIN0_MODE0 COM_OFF(0x108) -#define QSERDES_COM_INTEGLOOP_GAIN1_MODE0 COM_OFF(0x10C) -#define QSERDES_COM_INTEGLOOP_GAIN0_MODE1 COM_OFF(0x110) -#define QSERDES_COM_INTEGLOOP_GAIN1_MODE1 COM_OFF(0x114) -#define QSERDES_COM_VCO_TUNE_CTRL COM_OFF(0x124) -#define QSERDES_COM_VCO_TUNE_MAP COM_OFF(0x128) -#define QSERDES_COM_VCO_TUNE1_MODE0 COM_OFF(0x12C) -#define QSERDES_COM_VCO_TUNE2_MODE0 COM_OFF(0x130) -#define QSERDES_COM_VCO_TUNE1_MODE1 COM_OFF(0x134) -#define QSERDES_COM_VCO_TUNE2_MODE1 COM_OFF(0x138) -#define QSERDES_COM_VCO_TUNE_TIMER1 COM_OFF(0x144) -#define QSERDES_COM_VCO_TUNE_TIMER2 COM_OFF(0x148) -#define QSERDES_COM_CLK_SELECT COM_OFF(0x174) -#define QSERDES_COM_HSCLK_SEL COM_OFF(0x178) -#define QSERDES_COM_CORECLK_DIV COM_OFF(0x184) -#define QSERDES_COM_CORE_CLK_EN COM_OFF(0x18C) -#define QSERDES_COM_CMN_CONFIG COM_OFF(0x194) -#define QSERDES_COM_SVS_MODE_CLK_SEL COM_OFF(0x19C) -#define QSERDES_COM_CORECLK_DIV_MODE1 COM_OFF(0x1BC) - -/* UFS PHY registers */ -#define UFS_PHY_PHY_START PHY_OFF(0x00) -#define UFS_PHY_POWER_DOWN_CONTROL PHY_OFF(0x04) -#define UFS_PHY_PCS_READY_STATUS PHY_OFF(0x168) - -/* UFS PHY TX registers */ -#define QSERDES_TX_HIGHZ_TRANSCEIVER_BIAS_DRVR_EN TX_OFF(0, 0x68) -#define QSERDES_TX_LANE_MODE TX_OFF(0, 0x94) - -/* UFS PHY RX registers */ -#define QSERDES_RX_UCDR_FASTLOCK_FO_GAIN RX_OFF(0, 0x40) -#define QSERDES_RX_RX_TERM_BW RX_OFF(0, 0x90) -#define QSERDES_RX_RX_EQ_GAIN1_LSB RX_OFF(0, 0xC4) -#define QSERDES_RX_RX_EQ_GAIN1_MSB RX_OFF(0, 0xC8) -#define QSERDES_RX_RX_EQ_GAIN2_LSB RX_OFF(0, 0xCC) -#define QSERDES_RX_RX_EQ_GAIN2_MSB RX_OFF(0, 0xD0) -#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2 RX_OFF(0, 0xD8) -#define QSERDES_RX_SIGDET_CNTRL RX_OFF(0, 0x114) -#define QSERDES_RX_SIGDET_LVL RX_OFF(0, 0x118) -#define QSERDES_RX_SIGDET_DEGLITCH_CNTRL RX_OFF(0, 0x11C) -#define QSERDES_RX_RX_INTERFACE_MODE RX_OFF(0, 0x12C) - -/* - * This structure represents the 14nm specific phy. - * common_cfg MUST remain the first field in this structure - * in case extra fields are added. This way, when calling - * get_ufs_qcom_phy() of generic phy, we can extract the - * common phy structure (struct ufs_qcom_phy) out of it - * regardless of the relevant specific phy. - */ -struct ufs_qcom_phy_qmp_14nm { - struct ufs_qcom_phy common_cfg; -}; - -static struct ufs_qcom_phy_calibration phy_cal_table_rate_A[] = { - UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_POWER_DOWN_CONTROL, 0x01), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CMN_CONFIG, 0x0e), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYSCLK_EN_SEL, 0xd7), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CLK_SELECT, 0x30), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYS_CLK_CTRL, 0x06), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x08), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BG_TIMER, 0x0a), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_HSCLK_SEL, 0x05), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CORECLK_DIV, 0x0a), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CORECLK_DIV_MODE1, 0x0a), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP_EN, 0x01), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_CTRL, 0x10), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL, 0x20), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CORE_CLK_EN, 0x00), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP_CFG, 0x00), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_TIMER1, 0xff), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_TIMER2, 0x3f), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_MAP, 0x14), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SVS_MODE_CLK_SEL, 0x05), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START_MODE0, 0x82), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1_MODE0, 0x00), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2_MODE0, 0x00), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3_MODE0, 0x00), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CP_CTRL_MODE0, 0x0b), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RCTRL_MODE0, 0x16), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CCTRL_MODE0, 0x28), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN0_MODE0, 0x80), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN1_MODE0, 0x00), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE1_MODE0, 0x28), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE2_MODE0, 0x02), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP1_MODE0, 0xff), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP2_MODE0, 0x0c), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP3_MODE0, 0x00), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START_MODE1, 0x98), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1_MODE1, 0x00), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2_MODE1, 0x00), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3_MODE1, 0x00), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CP_CTRL_MODE1, 0x0b), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RCTRL_MODE1, 0x16), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CCTRL_MODE1, 0x28), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN0_MODE1, 0x80), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN1_MODE1, 0x00), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE1_MODE1, 0xd6), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE2_MODE1, 0x00), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP1_MODE1, 0x32), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP2_MODE1, 0x0f), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP3_MODE1, 0x00), - - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_HIGHZ_TRANSCEIVER_BIAS_DRVR_EN, 0x45), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE, 0x02), - - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_SIGDET_LVL, 0x24), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_SIGDET_CNTRL, 0x02), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_INTERFACE_MODE, 0x00), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_SIGDET_DEGLITCH_CNTRL, 0x18), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_UCDR_FASTLOCK_FO_GAIN, 0x0B), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_TERM_BW, 0x5B), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB, 0xFF), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB, 0x3F), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB, 0xFF), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB, 0x0F), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2, 0x0E), -}; - -static struct ufs_qcom_phy_calibration phy_cal_table_rate_B[] = { - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_MAP, 0x54), -}; - -#endif diff --git a/drivers/phy/phy-qcom-ufs-qmp-20nm.c b/drivers/phy/phy-qcom-ufs-qmp-20nm.c deleted file mode 100644 index 4f68acb58b73..000000000000 --- a/drivers/phy/phy-qcom-ufs-qmp-20nm.c +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright (c) 2013-2015, Linux Foundation. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -#include "phy-qcom-ufs-qmp-20nm.h" - -#define UFS_PHY_NAME "ufs_phy_qmp_20nm" - -static -int ufs_qcom_phy_qmp_20nm_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy, - bool is_rate_B) -{ - struct ufs_qcom_phy_calibration *tbl_A, *tbl_B; - int tbl_size_A, tbl_size_B; - u8 major = ufs_qcom_phy->host_ctrl_rev_major; - u16 minor = ufs_qcom_phy->host_ctrl_rev_minor; - u16 step = ufs_qcom_phy->host_ctrl_rev_step; - int err; - - if ((major == 0x1) && (minor == 0x002) && (step == 0x0000)) { - tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A_1_2_0); - tbl_A = phy_cal_table_rate_A_1_2_0; - } else if ((major == 0x1) && (minor == 0x003) && (step == 0x0000)) { - tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A_1_3_0); - tbl_A = phy_cal_table_rate_A_1_3_0; - } else { - dev_err(ufs_qcom_phy->dev, "%s: Unknown UFS-PHY version, no calibration values\n", - __func__); - err = -ENODEV; - goto out; - } - - tbl_size_B = ARRAY_SIZE(phy_cal_table_rate_B); - tbl_B = phy_cal_table_rate_B; - - err = ufs_qcom_phy_calibrate(ufs_qcom_phy, tbl_A, tbl_size_A, - tbl_B, tbl_size_B, is_rate_B); - - if (err) - dev_err(ufs_qcom_phy->dev, "%s: ufs_qcom_phy_calibrate() failed %d\n", - __func__, err); - -out: - return err; -} - -static -void ufs_qcom_phy_qmp_20nm_advertise_quirks(struct ufs_qcom_phy *phy_common) -{ - phy_common->quirks = - UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE; -} - -static int ufs_qcom_phy_qmp_20nm_init(struct phy *generic_phy) -{ - return 0; -} - -static int ufs_qcom_phy_qmp_20nm_exit(struct phy *generic_phy) -{ - return 0; -} - -static -void ufs_qcom_phy_qmp_20nm_power_control(struct ufs_qcom_phy *phy, bool val) -{ - bool hibern8_exit_after_pwr_collapse = phy->quirks & - UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE; - - if (val) { - writel_relaxed(0x1, phy->mmio + UFS_PHY_POWER_DOWN_CONTROL); - /* - * Before any transactions involving PHY, ensure PHY knows - * that it's analog rail is powered ON. - */ - mb(); - - if (hibern8_exit_after_pwr_collapse) { - /* - * Give atleast 1us delay after restoring PHY analog - * power. - */ - usleep_range(1, 2); - writel_relaxed(0x0A, phy->mmio + - QSERDES_COM_SYSCLK_EN_SEL_TXBAND); - writel_relaxed(0x08, phy->mmio + - QSERDES_COM_SYSCLK_EN_SEL_TXBAND); - /* - * Make sure workaround is deactivated before proceeding - * with normal PHY operations. - */ - mb(); - } - } else { - if (hibern8_exit_after_pwr_collapse) { - writel_relaxed(0x0A, phy->mmio + - QSERDES_COM_SYSCLK_EN_SEL_TXBAND); - writel_relaxed(0x02, phy->mmio + - QSERDES_COM_SYSCLK_EN_SEL_TXBAND); - /* - * Make sure that above workaround is activated before - * PHY analog power collapse. - */ - mb(); - } - - writel_relaxed(0x0, phy->mmio + UFS_PHY_POWER_DOWN_CONTROL); - /* - * ensure that PHY knows its PHY analog rail is going - * to be powered down - */ - mb(); - } -} - -static -void ufs_qcom_phy_qmp_20nm_set_tx_lane_enable(struct ufs_qcom_phy *phy, u32 val) -{ - writel_relaxed(val & UFS_PHY_TX_LANE_ENABLE_MASK, - phy->mmio + UFS_PHY_TX_LANE_ENABLE); - mb(); -} - -static inline void ufs_qcom_phy_qmp_20nm_start_serdes(struct ufs_qcom_phy *phy) -{ - u32 tmp; - - tmp = readl_relaxed(phy->mmio + UFS_PHY_PHY_START); - tmp &= ~MASK_SERDES_START; - tmp |= (1 << OFFSET_SERDES_START); - writel_relaxed(tmp, phy->mmio + UFS_PHY_PHY_START); - mb(); -} - -static int ufs_qcom_phy_qmp_20nm_is_pcs_ready(struct ufs_qcom_phy *phy_common) -{ - int err = 0; - u32 val; - - err = readl_poll_timeout(phy_common->mmio + UFS_PHY_PCS_READY_STATUS, - val, (val & MASK_PCS_READY), 10, 1000000); - if (err) - dev_err(phy_common->dev, "%s: poll for pcs failed err = %d\n", - __func__, err); - return err; -} - -static const struct phy_ops ufs_qcom_phy_qmp_20nm_phy_ops = { - .init = ufs_qcom_phy_qmp_20nm_init, - .exit = ufs_qcom_phy_qmp_20nm_exit, - .power_on = ufs_qcom_phy_power_on, - .power_off = ufs_qcom_phy_power_off, - .owner = THIS_MODULE, -}; - -static struct ufs_qcom_phy_specific_ops phy_20nm_ops = { - .calibrate_phy = ufs_qcom_phy_qmp_20nm_phy_calibrate, - .start_serdes = ufs_qcom_phy_qmp_20nm_start_serdes, - .is_physical_coding_sublayer_ready = ufs_qcom_phy_qmp_20nm_is_pcs_ready, - .set_tx_lane_enable = ufs_qcom_phy_qmp_20nm_set_tx_lane_enable, - .power_control = ufs_qcom_phy_qmp_20nm_power_control, -}; - -static int ufs_qcom_phy_qmp_20nm_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct phy *generic_phy; - struct ufs_qcom_phy_qmp_20nm *phy; - struct ufs_qcom_phy *phy_common; - int err = 0; - - phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); - if (!phy) { - err = -ENOMEM; - goto out; - } - phy_common = &phy->common_cfg; - - generic_phy = ufs_qcom_phy_generic_probe(pdev, phy_common, - &ufs_qcom_phy_qmp_20nm_phy_ops, &phy_20nm_ops); - - if (!generic_phy) { - err = -EIO; - goto out; - } - - err = ufs_qcom_phy_init_clks(phy_common); - if (err) - goto out; - - err = ufs_qcom_phy_init_vregulators(phy_common); - if (err) - goto out; - - ufs_qcom_phy_qmp_20nm_advertise_quirks(phy_common); - - phy_set_drvdata(generic_phy, phy); - - strlcpy(phy_common->name, UFS_PHY_NAME, sizeof(phy_common->name)); - -out: - return err; -} - -static const struct of_device_id ufs_qcom_phy_qmp_20nm_of_match[] = { - {.compatible = "qcom,ufs-phy-qmp-20nm"}, - {}, -}; -MODULE_DEVICE_TABLE(of, ufs_qcom_phy_qmp_20nm_of_match); - -static struct platform_driver ufs_qcom_phy_qmp_20nm_driver = { - .probe = ufs_qcom_phy_qmp_20nm_probe, - .driver = { - .of_match_table = ufs_qcom_phy_qmp_20nm_of_match, - .name = "ufs_qcom_phy_qmp_20nm", - }, -}; - -module_platform_driver(ufs_qcom_phy_qmp_20nm_driver); - -MODULE_DESCRIPTION("Universal Flash Storage (UFS) QCOM PHY QMP 20nm"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-qcom-ufs-qmp-20nm.h b/drivers/phy/phy-qcom-ufs-qmp-20nm.h deleted file mode 100644 index 4f3076bb3d71..000000000000 --- a/drivers/phy/phy-qcom-ufs-qmp-20nm.h +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright (c) 2013-2015, Linux Foundation. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -#ifndef UFS_QCOM_PHY_QMP_20NM_H_ -#define UFS_QCOM_PHY_QMP_20NM_H_ - -#include "phy-qcom-ufs-i.h" - -/* QCOM UFS PHY control registers */ - -#define COM_OFF(x) (0x000 + x) -#define PHY_OFF(x) (0xC00 + x) -#define TX_OFF(n, x) (0x400 + (0x400 * n) + x) -#define RX_OFF(n, x) (0x600 + (0x400 * n) + x) - -/* UFS PHY PLL block registers */ -#define QSERDES_COM_SYS_CLK_CTRL COM_OFF(0x0) -#define QSERDES_COM_PLL_VCOTAIL_EN COM_OFF(0x04) -#define QSERDES_COM_PLL_CNTRL COM_OFF(0x14) -#define QSERDES_COM_PLL_IP_SETI COM_OFF(0x24) -#define QSERDES_COM_CORE_CLK_IN_SYNC_SEL COM_OFF(0x28) -#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN COM_OFF(0x30) -#define QSERDES_COM_PLL_CP_SETI COM_OFF(0x34) -#define QSERDES_COM_PLL_IP_SETP COM_OFF(0x38) -#define QSERDES_COM_PLL_CP_SETP COM_OFF(0x3C) -#define QSERDES_COM_SYSCLK_EN_SEL_TXBAND COM_OFF(0x48) -#define QSERDES_COM_RESETSM_CNTRL COM_OFF(0x4C) -#define QSERDES_COM_RESETSM_CNTRL2 COM_OFF(0x50) -#define QSERDES_COM_PLLLOCK_CMP1 COM_OFF(0x90) -#define QSERDES_COM_PLLLOCK_CMP2 COM_OFF(0x94) -#define QSERDES_COM_PLLLOCK_CMP3 COM_OFF(0x98) -#define QSERDES_COM_PLLLOCK_CMP_EN COM_OFF(0x9C) -#define QSERDES_COM_BGTC COM_OFF(0xA0) -#define QSERDES_COM_DEC_START1 COM_OFF(0xAC) -#define QSERDES_COM_PLL_AMP_OS COM_OFF(0xB0) -#define QSERDES_COM_RES_CODE_UP_OFFSET COM_OFF(0xD8) -#define QSERDES_COM_RES_CODE_DN_OFFSET COM_OFF(0xDC) -#define QSERDES_COM_DIV_FRAC_START1 COM_OFF(0x100) -#define QSERDES_COM_DIV_FRAC_START2 COM_OFF(0x104) -#define QSERDES_COM_DIV_FRAC_START3 COM_OFF(0x108) -#define QSERDES_COM_DEC_START2 COM_OFF(0x10C) -#define QSERDES_COM_PLL_RXTXEPCLK_EN COM_OFF(0x110) -#define QSERDES_COM_PLL_CRCTRL COM_OFF(0x114) -#define QSERDES_COM_PLL_CLKEPDIV COM_OFF(0x118) - -/* TX LANE n (0, 1) registers */ -#define QSERDES_TX_EMP_POST1_LVL(n) TX_OFF(n, 0x08) -#define QSERDES_TX_DRV_LVL(n) TX_OFF(n, 0x0C) -#define QSERDES_TX_LANE_MODE(n) TX_OFF(n, 0x54) - -/* RX LANE n (0, 1) registers */ -#define QSERDES_RX_CDR_CONTROL1(n) RX_OFF(n, 0x0) -#define QSERDES_RX_CDR_CONTROL_HALF(n) RX_OFF(n, 0x8) -#define QSERDES_RX_RX_EQ_GAIN1_LSB(n) RX_OFF(n, 0xA8) -#define QSERDES_RX_RX_EQ_GAIN1_MSB(n) RX_OFF(n, 0xAC) -#define QSERDES_RX_RX_EQ_GAIN2_LSB(n) RX_OFF(n, 0xB0) -#define QSERDES_RX_RX_EQ_GAIN2_MSB(n) RX_OFF(n, 0xB4) -#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(n) RX_OFF(n, 0xBC) -#define QSERDES_RX_CDR_CONTROL_QUARTER(n) RX_OFF(n, 0xC) -#define QSERDES_RX_SIGDET_CNTRL(n) RX_OFF(n, 0x100) - -/* UFS PHY registers */ -#define UFS_PHY_PHY_START PHY_OFF(0x00) -#define UFS_PHY_POWER_DOWN_CONTROL PHY_OFF(0x4) -#define UFS_PHY_TX_LANE_ENABLE PHY_OFF(0x44) -#define UFS_PHY_PWM_G1_CLK_DIVIDER PHY_OFF(0x08) -#define UFS_PHY_PWM_G2_CLK_DIVIDER PHY_OFF(0x0C) -#define UFS_PHY_PWM_G3_CLK_DIVIDER PHY_OFF(0x10) -#define UFS_PHY_PWM_G4_CLK_DIVIDER PHY_OFF(0x14) -#define UFS_PHY_CORECLK_PWM_G1_CLK_DIVIDER PHY_OFF(0x34) -#define UFS_PHY_CORECLK_PWM_G2_CLK_DIVIDER PHY_OFF(0x38) -#define UFS_PHY_CORECLK_PWM_G3_CLK_DIVIDER PHY_OFF(0x3C) -#define UFS_PHY_CORECLK_PWM_G4_CLK_DIVIDER PHY_OFF(0x40) -#define UFS_PHY_OMC_STATUS_RDVAL PHY_OFF(0x68) -#define UFS_PHY_LINE_RESET_TIME PHY_OFF(0x28) -#define UFS_PHY_LINE_RESET_GRANULARITY PHY_OFF(0x2C) -#define UFS_PHY_TSYNC_RSYNC_CNTL PHY_OFF(0x48) -#define UFS_PHY_PLL_CNTL PHY_OFF(0x50) -#define UFS_PHY_TX_LARGE_AMP_DRV_LVL PHY_OFF(0x54) -#define UFS_PHY_TX_SMALL_AMP_DRV_LVL PHY_OFF(0x5C) -#define UFS_PHY_TX_LARGE_AMP_POST_EMP_LVL PHY_OFF(0x58) -#define UFS_PHY_TX_SMALL_AMP_POST_EMP_LVL PHY_OFF(0x60) -#define UFS_PHY_CFG_CHANGE_CNT_VAL PHY_OFF(0x64) -#define UFS_PHY_RX_SYNC_WAIT_TIME PHY_OFF(0x6C) -#define UFS_PHY_TX_MIN_SLEEP_NOCONFIG_TIME_CAPABILITY PHY_OFF(0xB4) -#define UFS_PHY_RX_MIN_SLEEP_NOCONFIG_TIME_CAPABILITY PHY_OFF(0xE0) -#define UFS_PHY_TX_MIN_STALL_NOCONFIG_TIME_CAPABILITY PHY_OFF(0xB8) -#define UFS_PHY_RX_MIN_STALL_NOCONFIG_TIME_CAPABILITY PHY_OFF(0xE4) -#define UFS_PHY_TX_MIN_SAVE_CONFIG_TIME_CAPABILITY PHY_OFF(0xBC) -#define UFS_PHY_RX_MIN_SAVE_CONFIG_TIME_CAPABILITY PHY_OFF(0xE8) -#define UFS_PHY_RX_PWM_BURST_CLOSURE_LENGTH_CAPABILITY PHY_OFF(0xFC) -#define UFS_PHY_RX_MIN_ACTIVATETIME_CAPABILITY PHY_OFF(0x100) -#define UFS_PHY_RX_SIGDET_CTRL3 PHY_OFF(0x14c) -#define UFS_PHY_RMMI_ATTR_CTRL PHY_OFF(0x160) -#define UFS_PHY_RMMI_RX_CFGUPDT_L1 (1 << 7) -#define UFS_PHY_RMMI_TX_CFGUPDT_L1 (1 << 6) -#define UFS_PHY_RMMI_CFGWR_L1 (1 << 5) -#define UFS_PHY_RMMI_CFGRD_L1 (1 << 4) -#define UFS_PHY_RMMI_RX_CFGUPDT_L0 (1 << 3) -#define UFS_PHY_RMMI_TX_CFGUPDT_L0 (1 << 2) -#define UFS_PHY_RMMI_CFGWR_L0 (1 << 1) -#define UFS_PHY_RMMI_CFGRD_L0 (1 << 0) -#define UFS_PHY_RMMI_ATTRID PHY_OFF(0x164) -#define UFS_PHY_RMMI_ATTRWRVAL PHY_OFF(0x168) -#define UFS_PHY_RMMI_ATTRRDVAL_L0_STATUS PHY_OFF(0x16C) -#define UFS_PHY_RMMI_ATTRRDVAL_L1_STATUS PHY_OFF(0x170) -#define UFS_PHY_PCS_READY_STATUS PHY_OFF(0x174) - -#define UFS_PHY_TX_LANE_ENABLE_MASK 0x3 - -/* - * This structure represents the 20nm specific phy. - * common_cfg MUST remain the first field in this structure - * in case extra fields are added. This way, when calling - * get_ufs_qcom_phy() of generic phy, we can extract the - * common phy structure (struct ufs_qcom_phy) out of it - * regardless of the relevant specific phy. - */ -struct ufs_qcom_phy_qmp_20nm { - struct ufs_qcom_phy common_cfg; -}; - -static struct ufs_qcom_phy_calibration phy_cal_table_rate_A_1_2_0[] = { - UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_POWER_DOWN_CONTROL, 0x01), - UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_RX_SIGDET_CTRL3, 0x0D), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_VCOTAIL_EN, 0xe1), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CRCTRL, 0xcc), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYSCLK_EN_SEL_TXBAND, 0x08), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CLKEPDIV, 0x03), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RXTXEPCLK_EN, 0x10), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START1, 0x82), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START2, 0x03), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1, 0x80), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2, 0x80), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3, 0x40), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP1, 0xff), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP2, 0x19), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP3, 0x00), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP_EN, 0x03), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL, 0x90), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL2, 0x03), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(0), 0xf2), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(0), 0x0c), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(0), 0x12), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(1), 0xf2), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(1), 0x0c), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(1), 0x12), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(0), 0xff), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(0), 0xff), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(0), 0xff), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(0), 0x00), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(1), 0xff), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(1), 0xff), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(1), 0xff), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(1), 0x00), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETI, 0x3f), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETP, 0x1b), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETP, 0x0f), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETI, 0x01), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_EMP_POST1_LVL(0), 0x2F), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_DRV_LVL(0), 0x20), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_EMP_POST1_LVL(1), 0x2F), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_DRV_LVL(1), 0x20), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(0), 0x68), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(1), 0x68), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(1), 0xdc), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(0), 0xdc), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x3), -}; - -static struct ufs_qcom_phy_calibration phy_cal_table_rate_A_1_3_0[] = { - UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_POWER_DOWN_CONTROL, 0x01), - UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_RX_SIGDET_CTRL3, 0x0D), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_VCOTAIL_EN, 0xe1), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CRCTRL, 0xcc), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYSCLK_EN_SEL_TXBAND, 0x08), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CLKEPDIV, 0x03), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RXTXEPCLK_EN, 0x10), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START1, 0x82), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START2, 0x03), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1, 0x80), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2, 0x80), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3, 0x40), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP1, 0xff), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP2, 0x19), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP3, 0x00), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP_EN, 0x03), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL, 0x90), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL2, 0x03), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(0), 0xf2), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(0), 0x0c), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(0), 0x12), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(1), 0xf2), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(1), 0x0c), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(1), 0x12), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(0), 0xff), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(0), 0xff), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(0), 0xff), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(0), 0x00), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(1), 0xff), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(1), 0xff), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(1), 0xff), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(1), 0x00), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETI, 0x2b), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETP, 0x38), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETP, 0x3c), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RES_CODE_UP_OFFSET, 0x02), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RES_CODE_DN_OFFSET, 0x02), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETI, 0x01), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CNTRL, 0x40), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(0), 0x68), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(1), 0x68), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(1), 0xdc), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(0), 0xdc), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x3), -}; - -static struct ufs_qcom_phy_calibration phy_cal_table_rate_B[] = { - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START1, 0x98), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP1, 0x65), - UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP2, 0x1e), -}; - -#endif diff --git a/drivers/phy/phy-qcom-ufs.c b/drivers/phy/phy-qcom-ufs.c deleted file mode 100644 index 43865ef340e2..000000000000 --- a/drivers/phy/phy-qcom-ufs.c +++ /dev/null @@ -1,691 +0,0 @@ -/* - * Copyright (c) 2013-2015, Linux Foundation. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 and - * only version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -#include "phy-qcom-ufs-i.h" - -#define MAX_PROP_NAME 32 -#define VDDA_PHY_MIN_UV 1000000 -#define VDDA_PHY_MAX_UV 1000000 -#define VDDA_PLL_MIN_UV 1800000 -#define VDDA_PLL_MAX_UV 1800000 -#define VDDP_REF_CLK_MIN_UV 1200000 -#define VDDP_REF_CLK_MAX_UV 1200000 - -int ufs_qcom_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy, - struct ufs_qcom_phy_calibration *tbl_A, - int tbl_size_A, - struct ufs_qcom_phy_calibration *tbl_B, - int tbl_size_B, bool is_rate_B) -{ - int i; - int ret = 0; - - if (!tbl_A) { - dev_err(ufs_qcom_phy->dev, "%s: tbl_A is NULL", __func__); - ret = EINVAL; - goto out; - } - - for (i = 0; i < tbl_size_A; i++) - writel_relaxed(tbl_A[i].cfg_value, - ufs_qcom_phy->mmio + tbl_A[i].reg_offset); - - /* - * In case we would like to work in rate B, we need - * to override a registers that were configured in rate A table - * with registers of rate B table. - * table. - */ - if (is_rate_B) { - if (!tbl_B) { - dev_err(ufs_qcom_phy->dev, "%s: tbl_B is NULL", - __func__); - ret = EINVAL; - goto out; - } - - for (i = 0; i < tbl_size_B; i++) - writel_relaxed(tbl_B[i].cfg_value, - ufs_qcom_phy->mmio + tbl_B[i].reg_offset); - } - - /* flush buffered writes */ - mb(); - -out: - return ret; -} -EXPORT_SYMBOL_GPL(ufs_qcom_phy_calibrate); - -/* - * This assumes the embedded phy structure inside generic_phy is of type - * struct ufs_qcom_phy. In order to function properly it's crucial - * to keep the embedded struct "struct ufs_qcom_phy common_cfg" - * as the first inside generic_phy. - */ -struct ufs_qcom_phy *get_ufs_qcom_phy(struct phy *generic_phy) -{ - return (struct ufs_qcom_phy *)phy_get_drvdata(generic_phy); -} -EXPORT_SYMBOL_GPL(get_ufs_qcom_phy); - -static -int ufs_qcom_phy_base_init(struct platform_device *pdev, - struct ufs_qcom_phy *phy_common) -{ - struct device *dev = &pdev->dev; - struct resource *res; - int err = 0; - - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy_mem"); - phy_common->mmio = devm_ioremap_resource(dev, res); - if (IS_ERR((void const *)phy_common->mmio)) { - err = PTR_ERR((void const *)phy_common->mmio); - phy_common->mmio = NULL; - dev_err(dev, "%s: ioremap for phy_mem resource failed %d\n", - __func__, err); - return err; - } - - /* "dev_ref_clk_ctrl_mem" is optional resource */ - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, - "dev_ref_clk_ctrl_mem"); - phy_common->dev_ref_clk_ctrl_mmio = devm_ioremap_resource(dev, res); - if (IS_ERR((void const *)phy_common->dev_ref_clk_ctrl_mmio)) - phy_common->dev_ref_clk_ctrl_mmio = NULL; - - return 0; -} - -struct phy *ufs_qcom_phy_generic_probe(struct platform_device *pdev, - struct ufs_qcom_phy *common_cfg, - const struct phy_ops *ufs_qcom_phy_gen_ops, - struct ufs_qcom_phy_specific_ops *phy_spec_ops) -{ - int err; - struct device *dev = &pdev->dev; - struct phy *generic_phy = NULL; - struct phy_provider *phy_provider; - - err = ufs_qcom_phy_base_init(pdev, common_cfg); - if (err) { - dev_err(dev, "%s: phy base init failed %d\n", __func__, err); - goto out; - } - - phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); - if (IS_ERR(phy_provider)) { - err = PTR_ERR(phy_provider); - dev_err(dev, "%s: failed to register phy %d\n", __func__, err); - goto out; - } - - generic_phy = devm_phy_create(dev, NULL, ufs_qcom_phy_gen_ops); - if (IS_ERR(generic_phy)) { - err = PTR_ERR(generic_phy); - dev_err(dev, "%s: failed to create phy %d\n", __func__, err); - generic_phy = NULL; - goto out; - } - - common_cfg->phy_spec_ops = phy_spec_ops; - common_cfg->dev = dev; - -out: - return generic_phy; -} -EXPORT_SYMBOL_GPL(ufs_qcom_phy_generic_probe); - -static int __ufs_qcom_phy_clk_get(struct device *dev, - const char *name, struct clk **clk_out, bool err_print) -{ - struct clk *clk; - int err = 0; - - clk = devm_clk_get(dev, name); - if (IS_ERR(clk)) { - err = PTR_ERR(clk); - if (err_print) - dev_err(dev, "failed to get %s err %d", name, err); - } else { - *clk_out = clk; - } - - return err; -} - -static int ufs_qcom_phy_clk_get(struct device *dev, - const char *name, struct clk **clk_out) -{ - return __ufs_qcom_phy_clk_get(dev, name, clk_out, true); -} - -int ufs_qcom_phy_init_clks(struct ufs_qcom_phy *phy_common) -{ - int err; - - if (of_device_is_compatible(phy_common->dev->of_node, - "qcom,msm8996-ufs-phy-qmp-14nm")) - goto skip_txrx_clk; - - err = ufs_qcom_phy_clk_get(phy_common->dev, "tx_iface_clk", - &phy_common->tx_iface_clk); - if (err) - goto out; - - err = ufs_qcom_phy_clk_get(phy_common->dev, "rx_iface_clk", - &phy_common->rx_iface_clk); - if (err) - goto out; - -skip_txrx_clk: - err = ufs_qcom_phy_clk_get(phy_common->dev, "ref_clk_src", - &phy_common->ref_clk_src); - if (err) - goto out; - - /* - * "ref_clk_parent" is optional hence don't abort init if it's not - * found. - */ - __ufs_qcom_phy_clk_get(phy_common->dev, "ref_clk_parent", - &phy_common->ref_clk_parent, false); - - err = ufs_qcom_phy_clk_get(phy_common->dev, "ref_clk", - &phy_common->ref_clk); - -out: - return err; -} -EXPORT_SYMBOL_GPL(ufs_qcom_phy_init_clks); - -static int ufs_qcom_phy_init_vreg(struct device *dev, - struct ufs_qcom_phy_vreg *vreg, - const char *name) -{ - int err = 0; - - char prop_name[MAX_PROP_NAME]; - - vreg->name = name; - vreg->reg = devm_regulator_get(dev, name); - if (IS_ERR(vreg->reg)) { - err = PTR_ERR(vreg->reg); - dev_err(dev, "failed to get %s, %d\n", name, err); - goto out; - } - - if (dev->of_node) { - snprintf(prop_name, MAX_PROP_NAME, "%s-max-microamp", name); - err = of_property_read_u32(dev->of_node, - prop_name, &vreg->max_uA); - if (err && err != -EINVAL) { - dev_err(dev, "%s: failed to read %s\n", - __func__, prop_name); - goto out; - } else if (err == -EINVAL || !vreg->max_uA) { - if (regulator_count_voltages(vreg->reg) > 0) { - dev_err(dev, "%s: %s is mandatory\n", - __func__, prop_name); - goto out; - } - err = 0; - } - } - - if (!strcmp(name, "vdda-pll")) { - vreg->max_uV = VDDA_PLL_MAX_UV; - vreg->min_uV = VDDA_PLL_MIN_UV; - } else if (!strcmp(name, "vdda-phy")) { - vreg->max_uV = VDDA_PHY_MAX_UV; - vreg->min_uV = VDDA_PHY_MIN_UV; - } else if (!strcmp(name, "vddp-ref-clk")) { - vreg->max_uV = VDDP_REF_CLK_MAX_UV; - vreg->min_uV = VDDP_REF_CLK_MIN_UV; - } - -out: - return err; -} - -int ufs_qcom_phy_init_vregulators(struct ufs_qcom_phy *phy_common) -{ - int err; - - err = ufs_qcom_phy_init_vreg(phy_common->dev, &phy_common->vdda_pll, - "vdda-pll"); - if (err) - goto out; - - err = ufs_qcom_phy_init_vreg(phy_common->dev, &phy_common->vdda_phy, - "vdda-phy"); - - if (err) - goto out; - - err = ufs_qcom_phy_init_vreg(phy_common->dev, &phy_common->vddp_ref_clk, - "vddp-ref-clk"); - -out: - return err; -} -EXPORT_SYMBOL_GPL(ufs_qcom_phy_init_vregulators); - -static int ufs_qcom_phy_cfg_vreg(struct device *dev, - struct ufs_qcom_phy_vreg *vreg, bool on) -{ - int ret = 0; - struct regulator *reg = vreg->reg; - const char *name = vreg->name; - int min_uV; - int uA_load; - - if (regulator_count_voltages(reg) > 0) { - min_uV = on ? vreg->min_uV : 0; - ret = regulator_set_voltage(reg, min_uV, vreg->max_uV); - if (ret) { - dev_err(dev, "%s: %s set voltage failed, err=%d\n", - __func__, name, ret); - goto out; - } - uA_load = on ? vreg->max_uA : 0; - ret = regulator_set_load(reg, uA_load); - if (ret >= 0) { - /* - * regulator_set_load() returns new regulator - * mode upon success. - */ - ret = 0; - } else { - dev_err(dev, "%s: %s set optimum mode(uA_load=%d) failed, err=%d\n", - __func__, name, uA_load, ret); - goto out; - } - } -out: - return ret; -} - -static int ufs_qcom_phy_enable_vreg(struct device *dev, - struct ufs_qcom_phy_vreg *vreg) -{ - int ret = 0; - - if (!vreg || vreg->enabled) - goto out; - - ret = ufs_qcom_phy_cfg_vreg(dev, vreg, true); - if (ret) { - dev_err(dev, "%s: ufs_qcom_phy_cfg_vreg() failed, err=%d\n", - __func__, ret); - goto out; - } - - ret = regulator_enable(vreg->reg); - if (ret) { - dev_err(dev, "%s: enable failed, err=%d\n", - __func__, ret); - goto out; - } - - vreg->enabled = true; -out: - return ret; -} - -static int ufs_qcom_phy_enable_ref_clk(struct ufs_qcom_phy *phy) -{ - int ret = 0; - - if (phy->is_ref_clk_enabled) - goto out; - - /* - * reference clock is propagated in a daisy-chained manner from - * source to phy, so ungate them at each stage. - */ - ret = clk_prepare_enable(phy->ref_clk_src); - if (ret) { - dev_err(phy->dev, "%s: ref_clk_src enable failed %d\n", - __func__, ret); - goto out; - } - - /* - * "ref_clk_parent" is optional clock hence make sure that clk reference - * is available before trying to enable the clock. - */ - if (phy->ref_clk_parent) { - ret = clk_prepare_enable(phy->ref_clk_parent); - if (ret) { - dev_err(phy->dev, "%s: ref_clk_parent enable failed %d\n", - __func__, ret); - goto out_disable_src; - } - } - - ret = clk_prepare_enable(phy->ref_clk); - if (ret) { - dev_err(phy->dev, "%s: ref_clk enable failed %d\n", - __func__, ret); - goto out_disable_parent; - } - - phy->is_ref_clk_enabled = true; - goto out; - -out_disable_parent: - if (phy->ref_clk_parent) - clk_disable_unprepare(phy->ref_clk_parent); -out_disable_src: - clk_disable_unprepare(phy->ref_clk_src); -out: - return ret; -} - -static int ufs_qcom_phy_disable_vreg(struct device *dev, - struct ufs_qcom_phy_vreg *vreg) -{ - int ret = 0; - - if (!vreg || !vreg->enabled) - goto out; - - ret = regulator_disable(vreg->reg); - - if (!ret) { - /* ignore errors on applying disable config */ - ufs_qcom_phy_cfg_vreg(dev, vreg, false); - vreg->enabled = false; - } else { - dev_err(dev, "%s: %s disable failed, err=%d\n", - __func__, vreg->name, ret); - } -out: - return ret; -} - -static void ufs_qcom_phy_disable_ref_clk(struct ufs_qcom_phy *phy) -{ - if (phy->is_ref_clk_enabled) { - clk_disable_unprepare(phy->ref_clk); - /* - * "ref_clk_parent" is optional clock hence make sure that clk - * reference is available before trying to disable the clock. - */ - if (phy->ref_clk_parent) - clk_disable_unprepare(phy->ref_clk_parent); - clk_disable_unprepare(phy->ref_clk_src); - phy->is_ref_clk_enabled = false; - } -} - -#define UFS_REF_CLK_EN (1 << 5) - -static void ufs_qcom_phy_dev_ref_clk_ctrl(struct phy *generic_phy, bool enable) -{ - struct ufs_qcom_phy *phy = get_ufs_qcom_phy(generic_phy); - - if (phy->dev_ref_clk_ctrl_mmio && - (enable ^ phy->is_dev_ref_clk_enabled)) { - u32 temp = readl_relaxed(phy->dev_ref_clk_ctrl_mmio); - - if (enable) - temp |= UFS_REF_CLK_EN; - else - temp &= ~UFS_REF_CLK_EN; - - /* - * If we are here to disable this clock immediately after - * entering into hibern8, we need to make sure that device - * ref_clk is active atleast 1us after the hibern8 enter. - */ - if (!enable) - udelay(1); - - writel_relaxed(temp, phy->dev_ref_clk_ctrl_mmio); - /* ensure that ref_clk is enabled/disabled before we return */ - wmb(); - /* - * If we call hibern8 exit after this, we need to make sure that - * device ref_clk is stable for atleast 1us before the hibern8 - * exit command. - */ - if (enable) - udelay(1); - - phy->is_dev_ref_clk_enabled = enable; - } -} - -void ufs_qcom_phy_enable_dev_ref_clk(struct phy *generic_phy) -{ - ufs_qcom_phy_dev_ref_clk_ctrl(generic_phy, true); -} -EXPORT_SYMBOL_GPL(ufs_qcom_phy_enable_dev_ref_clk); - -void ufs_qcom_phy_disable_dev_ref_clk(struct phy *generic_phy) -{ - ufs_qcom_phy_dev_ref_clk_ctrl(generic_phy, false); -} -EXPORT_SYMBOL_GPL(ufs_qcom_phy_disable_dev_ref_clk); - -/* Turn ON M-PHY RMMI interface clocks */ -static int ufs_qcom_phy_enable_iface_clk(struct ufs_qcom_phy *phy) -{ - int ret = 0; - - if (phy->is_iface_clk_enabled) - goto out; - - ret = clk_prepare_enable(phy->tx_iface_clk); - if (ret) { - dev_err(phy->dev, "%s: tx_iface_clk enable failed %d\n", - __func__, ret); - goto out; - } - ret = clk_prepare_enable(phy->rx_iface_clk); - if (ret) { - clk_disable_unprepare(phy->tx_iface_clk); - dev_err(phy->dev, "%s: rx_iface_clk enable failed %d. disabling also tx_iface_clk\n", - __func__, ret); - goto out; - } - phy->is_iface_clk_enabled = true; - -out: - return ret; -} - -/* Turn OFF M-PHY RMMI interface clocks */ -void ufs_qcom_phy_disable_iface_clk(struct ufs_qcom_phy *phy) -{ - if (phy->is_iface_clk_enabled) { - clk_disable_unprepare(phy->tx_iface_clk); - clk_disable_unprepare(phy->rx_iface_clk); - phy->is_iface_clk_enabled = false; - } -} - -int ufs_qcom_phy_start_serdes(struct phy *generic_phy) -{ - struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy); - int ret = 0; - - if (!ufs_qcom_phy->phy_spec_ops->start_serdes) { - dev_err(ufs_qcom_phy->dev, "%s: start_serdes() callback is not supported\n", - __func__); - ret = -ENOTSUPP; - } else { - ufs_qcom_phy->phy_spec_ops->start_serdes(ufs_qcom_phy); - } - - return ret; -} -EXPORT_SYMBOL_GPL(ufs_qcom_phy_start_serdes); - -int ufs_qcom_phy_set_tx_lane_enable(struct phy *generic_phy, u32 tx_lanes) -{ - struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy); - int ret = 0; - - if (!ufs_qcom_phy->phy_spec_ops->set_tx_lane_enable) { - dev_err(ufs_qcom_phy->dev, "%s: set_tx_lane_enable() callback is not supported\n", - __func__); - ret = -ENOTSUPP; - } else { - ufs_qcom_phy->phy_spec_ops->set_tx_lane_enable(ufs_qcom_phy, - tx_lanes); - } - - return ret; -} -EXPORT_SYMBOL_GPL(ufs_qcom_phy_set_tx_lane_enable); - -void ufs_qcom_phy_save_controller_version(struct phy *generic_phy, - u8 major, u16 minor, u16 step) -{ - struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy); - - ufs_qcom_phy->host_ctrl_rev_major = major; - ufs_qcom_phy->host_ctrl_rev_minor = minor; - ufs_qcom_phy->host_ctrl_rev_step = step; -} -EXPORT_SYMBOL_GPL(ufs_qcom_phy_save_controller_version); - -int ufs_qcom_phy_calibrate_phy(struct phy *generic_phy, bool is_rate_B) -{ - struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy); - int ret = 0; - - if (!ufs_qcom_phy->phy_spec_ops->calibrate_phy) { - dev_err(ufs_qcom_phy->dev, "%s: calibrate_phy() callback is not supported\n", - __func__); - ret = -ENOTSUPP; - } else { - ret = ufs_qcom_phy->phy_spec_ops-> - calibrate_phy(ufs_qcom_phy, is_rate_B); - if (ret) - dev_err(ufs_qcom_phy->dev, "%s: calibrate_phy() failed %d\n", - __func__, ret); - } - - return ret; -} -EXPORT_SYMBOL_GPL(ufs_qcom_phy_calibrate_phy); - -int ufs_qcom_phy_is_pcs_ready(struct phy *generic_phy) -{ - struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy); - - if (!ufs_qcom_phy->phy_spec_ops->is_physical_coding_sublayer_ready) { - dev_err(ufs_qcom_phy->dev, "%s: is_physical_coding_sublayer_ready() callback is not supported\n", - __func__); - return -ENOTSUPP; - } - - return ufs_qcom_phy->phy_spec_ops-> - is_physical_coding_sublayer_ready(ufs_qcom_phy); -} -EXPORT_SYMBOL_GPL(ufs_qcom_phy_is_pcs_ready); - -int ufs_qcom_phy_power_on(struct phy *generic_phy) -{ - struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy); - struct device *dev = phy_common->dev; - int err; - - if (phy_common->is_powered_on) - return 0; - - err = ufs_qcom_phy_enable_vreg(dev, &phy_common->vdda_phy); - if (err) { - dev_err(dev, "%s enable vdda_phy failed, err=%d\n", - __func__, err); - goto out; - } - - phy_common->phy_spec_ops->power_control(phy_common, true); - - /* vdda_pll also enables ref clock LDOs so enable it first */ - err = ufs_qcom_phy_enable_vreg(dev, &phy_common->vdda_pll); - if (err) { - dev_err(dev, "%s enable vdda_pll failed, err=%d\n", - __func__, err); - goto out_disable_phy; - } - - err = ufs_qcom_phy_enable_iface_clk(phy_common); - if (err) { - dev_err(dev, "%s enable phy iface clock failed, err=%d\n", - __func__, err); - goto out_disable_pll; - } - - err = ufs_qcom_phy_enable_ref_clk(phy_common); - if (err) { - dev_err(dev, "%s enable phy ref clock failed, err=%d\n", - __func__, err); - goto out_disable_iface_clk; - } - - /* enable device PHY ref_clk pad rail */ - if (phy_common->vddp_ref_clk.reg) { - err = ufs_qcom_phy_enable_vreg(dev, - &phy_common->vddp_ref_clk); - if (err) { - dev_err(dev, "%s enable vddp_ref_clk failed, err=%d\n", - __func__, err); - goto out_disable_ref_clk; - } - } - - phy_common->is_powered_on = true; - goto out; - -out_disable_ref_clk: - ufs_qcom_phy_disable_ref_clk(phy_common); -out_disable_iface_clk: - ufs_qcom_phy_disable_iface_clk(phy_common); -out_disable_pll: - ufs_qcom_phy_disable_vreg(dev, &phy_common->vdda_pll); -out_disable_phy: - ufs_qcom_phy_disable_vreg(dev, &phy_common->vdda_phy); -out: - return err; -} -EXPORT_SYMBOL_GPL(ufs_qcom_phy_power_on); - -int ufs_qcom_phy_power_off(struct phy *generic_phy) -{ - struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy); - - if (!phy_common->is_powered_on) - return 0; - - phy_common->phy_spec_ops->power_control(phy_common, false); - - if (phy_common->vddp_ref_clk.reg) - ufs_qcom_phy_disable_vreg(phy_common->dev, - &phy_common->vddp_ref_clk); - ufs_qcom_phy_disable_ref_clk(phy_common); - ufs_qcom_phy_disable_iface_clk(phy_common); - - ufs_qcom_phy_disable_vreg(phy_common->dev, &phy_common->vdda_pll); - ufs_qcom_phy_disable_vreg(phy_common->dev, &phy_common->vdda_phy); - phy_common->is_powered_on = false; - - return 0; -} -EXPORT_SYMBOL_GPL(ufs_qcom_phy_power_off); diff --git a/drivers/phy/phy-qcom-usb-hs.c b/drivers/phy/phy-qcom-usb-hs.c deleted file mode 100644 index 4b20abc3ae2f..000000000000 --- a/drivers/phy/phy-qcom-usb-hs.c +++ /dev/null @@ -1,295 +0,0 @@ -/** - * Copyright (C) 2016 Linaro Ltd - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define ULPI_PWR_CLK_MNG_REG 0x88 -# define ULPI_PWR_OTG_COMP_DISABLE BIT(0) - -#define ULPI_MISC_A 0x96 -# define ULPI_MISC_A_VBUSVLDEXTSEL BIT(1) -# define ULPI_MISC_A_VBUSVLDEXT BIT(0) - - -struct ulpi_seq { - u8 addr; - u8 val; -}; - -struct qcom_usb_hs_phy { - struct ulpi *ulpi; - struct phy *phy; - struct clk *ref_clk; - struct clk *sleep_clk; - struct regulator *v1p8; - struct regulator *v3p3; - struct reset_control *reset; - struct ulpi_seq *init_seq; - struct extcon_dev *vbus_edev; - struct notifier_block vbus_notify; -}; - -static int qcom_usb_hs_phy_set_mode(struct phy *phy, enum phy_mode mode) -{ - struct qcom_usb_hs_phy *uphy = phy_get_drvdata(phy); - u8 addr; - int ret; - - if (!uphy->vbus_edev) { - u8 val = 0; - - switch (mode) { - case PHY_MODE_USB_OTG: - case PHY_MODE_USB_HOST: - val |= ULPI_INT_IDGRD; - case PHY_MODE_USB_DEVICE: - val |= ULPI_INT_SESS_VALID; - default: - break; - } - - ret = ulpi_write(uphy->ulpi, ULPI_USB_INT_EN_RISE, val); - if (ret) - return ret; - ret = ulpi_write(uphy->ulpi, ULPI_USB_INT_EN_FALL, val); - } else { - switch (mode) { - case PHY_MODE_USB_OTG: - case PHY_MODE_USB_DEVICE: - addr = ULPI_SET(ULPI_MISC_A); - break; - case PHY_MODE_USB_HOST: - addr = ULPI_CLR(ULPI_MISC_A); - break; - default: - return -EINVAL; - } - - ret = ulpi_write(uphy->ulpi, ULPI_SET(ULPI_PWR_CLK_MNG_REG), - ULPI_PWR_OTG_COMP_DISABLE); - if (ret) - return ret; - ret = ulpi_write(uphy->ulpi, addr, ULPI_MISC_A_VBUSVLDEXTSEL); - } - - return ret; -} - -static int -qcom_usb_hs_phy_vbus_notifier(struct notifier_block *nb, unsigned long event, - void *ptr) -{ - struct qcom_usb_hs_phy *uphy; - u8 addr; - - uphy = container_of(nb, struct qcom_usb_hs_phy, vbus_notify); - - if (event) - addr = ULPI_SET(ULPI_MISC_A); - else - addr = ULPI_CLR(ULPI_MISC_A); - - return ulpi_write(uphy->ulpi, addr, ULPI_MISC_A_VBUSVLDEXT); -} - -static int qcom_usb_hs_phy_power_on(struct phy *phy) -{ - struct qcom_usb_hs_phy *uphy = phy_get_drvdata(phy); - struct ulpi *ulpi = uphy->ulpi; - const struct ulpi_seq *seq; - int ret, state; - - ret = clk_prepare_enable(uphy->ref_clk); - if (ret) - return ret; - - ret = clk_prepare_enable(uphy->sleep_clk); - if (ret) - goto err_sleep; - - ret = regulator_set_load(uphy->v1p8, 50000); - if (ret < 0) - goto err_1p8; - - ret = regulator_enable(uphy->v1p8); - if (ret) - goto err_1p8; - - ret = regulator_set_voltage_triplet(uphy->v3p3, 3050000, 3300000, - 3300000); - if (ret) - goto err_3p3; - - ret = regulator_set_load(uphy->v3p3, 50000); - if (ret < 0) - goto err_3p3; - - ret = regulator_enable(uphy->v3p3); - if (ret) - goto err_3p3; - - for (seq = uphy->init_seq; seq->addr; seq++) { - ret = ulpi_write(ulpi, ULPI_EXT_VENDOR_SPECIFIC + seq->addr, - seq->val); - if (ret) - goto err_ulpi; - } - - if (uphy->reset) { - ret = reset_control_reset(uphy->reset); - if (ret) - goto err_ulpi; - } - - if (uphy->vbus_edev) { - state = extcon_get_cable_state_(uphy->vbus_edev, EXTCON_USB); - /* setup initial state */ - qcom_usb_hs_phy_vbus_notifier(&uphy->vbus_notify, state, - uphy->vbus_edev); - ret = extcon_register_notifier(uphy->vbus_edev, EXTCON_USB, - &uphy->vbus_notify); - if (ret) - goto err_ulpi; - } - - return 0; -err_ulpi: - regulator_disable(uphy->v3p3); -err_3p3: - regulator_disable(uphy->v1p8); -err_1p8: - clk_disable_unprepare(uphy->sleep_clk); -err_sleep: - clk_disable_unprepare(uphy->ref_clk); - return ret; -} - -static int qcom_usb_hs_phy_power_off(struct phy *phy) -{ - int ret; - struct qcom_usb_hs_phy *uphy = phy_get_drvdata(phy); - - if (uphy->vbus_edev) { - ret = extcon_unregister_notifier(uphy->vbus_edev, EXTCON_USB, - &uphy->vbus_notify); - if (ret) - return ret; - } - - regulator_disable(uphy->v3p3); - regulator_disable(uphy->v1p8); - clk_disable_unprepare(uphy->sleep_clk); - clk_disable_unprepare(uphy->ref_clk); - - return 0; -} - -static const struct phy_ops qcom_usb_hs_phy_ops = { - .power_on = qcom_usb_hs_phy_power_on, - .power_off = qcom_usb_hs_phy_power_off, - .set_mode = qcom_usb_hs_phy_set_mode, - .owner = THIS_MODULE, -}; - -static int qcom_usb_hs_phy_probe(struct ulpi *ulpi) -{ - struct qcom_usb_hs_phy *uphy; - struct phy_provider *p; - struct clk *clk; - struct regulator *reg; - struct reset_control *reset; - int size; - int ret; - - uphy = devm_kzalloc(&ulpi->dev, sizeof(*uphy), GFP_KERNEL); - if (!uphy) - return -ENOMEM; - ulpi_set_drvdata(ulpi, uphy); - uphy->ulpi = ulpi; - - size = of_property_count_u8_elems(ulpi->dev.of_node, "qcom,init-seq"); - if (size < 0) - size = 0; - uphy->init_seq = devm_kmalloc_array(&ulpi->dev, (size / 2) + 1, - sizeof(*uphy->init_seq), GFP_KERNEL); - if (!uphy->init_seq) - return -ENOMEM; - ret = of_property_read_u8_array(ulpi->dev.of_node, "qcom,init-seq", - (u8 *)uphy->init_seq, size); - if (ret && size) - return ret; - /* NUL terminate */ - uphy->init_seq[size / 2].addr = uphy->init_seq[size / 2].val = 0; - - uphy->ref_clk = clk = devm_clk_get(&ulpi->dev, "ref"); - if (IS_ERR(clk)) - return PTR_ERR(clk); - - uphy->sleep_clk = clk = devm_clk_get(&ulpi->dev, "sleep"); - if (IS_ERR(clk)) - return PTR_ERR(clk); - - uphy->v1p8 = reg = devm_regulator_get(&ulpi->dev, "v1p8"); - if (IS_ERR(reg)) - return PTR_ERR(reg); - - uphy->v3p3 = reg = devm_regulator_get(&ulpi->dev, "v3p3"); - if (IS_ERR(reg)) - return PTR_ERR(reg); - - uphy->reset = reset = devm_reset_control_get(&ulpi->dev, "por"); - if (IS_ERR(reset)) { - if (PTR_ERR(reset) == -EPROBE_DEFER) - return PTR_ERR(reset); - uphy->reset = NULL; - } - - uphy->phy = devm_phy_create(&ulpi->dev, ulpi->dev.of_node, - &qcom_usb_hs_phy_ops); - if (IS_ERR(uphy->phy)) - return PTR_ERR(uphy->phy); - - uphy->vbus_edev = extcon_get_edev_by_phandle(&ulpi->dev, 0); - if (IS_ERR(uphy->vbus_edev)) { - if (PTR_ERR(uphy->vbus_edev) != -ENODEV) - return PTR_ERR(uphy->vbus_edev); - uphy->vbus_edev = NULL; - } - - uphy->vbus_notify.notifier_call = qcom_usb_hs_phy_vbus_notifier; - phy_set_drvdata(uphy->phy, uphy); - - p = devm_of_phy_provider_register(&ulpi->dev, of_phy_simple_xlate); - return PTR_ERR_OR_ZERO(p); -} - -static const struct of_device_id qcom_usb_hs_phy_match[] = { - { .compatible = "qcom,usb-hs-phy", }, - { } -}; -MODULE_DEVICE_TABLE(of, qcom_usb_hs_phy_match); - -static struct ulpi_driver qcom_usb_hs_phy_driver = { - .probe = qcom_usb_hs_phy_probe, - .driver = { - .name = "qcom_usb_hs_phy", - .of_match_table = qcom_usb_hs_phy_match, - }, -}; -module_ulpi_driver(qcom_usb_hs_phy_driver); - -MODULE_DESCRIPTION("Qualcomm USB HS phy"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-qcom-usb-hsic.c b/drivers/phy/phy-qcom-usb-hsic.c deleted file mode 100644 index c110563a73cb..000000000000 --- a/drivers/phy/phy-qcom-usb-hsic.c +++ /dev/null @@ -1,159 +0,0 @@ -/** - * Copyright (C) 2016 Linaro Ltd - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ -#include -#include -#include -#include -#include -#include -#include -#include - -#define ULPI_HSIC_CFG 0x30 -#define ULPI_HSIC_IO_CAL 0x33 - -struct qcom_usb_hsic_phy { - struct ulpi *ulpi; - struct phy *phy; - struct pinctrl *pctl; - struct clk *phy_clk; - struct clk *cal_clk; - struct clk *cal_sleep_clk; -}; - -static int qcom_usb_hsic_phy_power_on(struct phy *phy) -{ - struct qcom_usb_hsic_phy *uphy = phy_get_drvdata(phy); - struct ulpi *ulpi = uphy->ulpi; - struct pinctrl_state *pins_default; - int ret; - - ret = clk_prepare_enable(uphy->phy_clk); - if (ret) - return ret; - - ret = clk_prepare_enable(uphy->cal_clk); - if (ret) - goto err_cal; - - ret = clk_prepare_enable(uphy->cal_sleep_clk); - if (ret) - goto err_sleep; - - /* Set periodic calibration interval to ~2.048sec in HSIC_IO_CAL_REG */ - ret = ulpi_write(ulpi, ULPI_HSIC_IO_CAL, 0xff); - if (ret) - goto err_ulpi; - - /* Enable periodic IO calibration in HSIC_CFG register */ - ret = ulpi_write(ulpi, ULPI_HSIC_CFG, 0xa8); - if (ret) - goto err_ulpi; - - /* Configure pins for HSIC functionality */ - pins_default = pinctrl_lookup_state(uphy->pctl, PINCTRL_STATE_DEFAULT); - if (IS_ERR(pins_default)) - return PTR_ERR(pins_default); - - ret = pinctrl_select_state(uphy->pctl, pins_default); - if (ret) - goto err_ulpi; - - /* Enable HSIC mode in HSIC_CFG register */ - ret = ulpi_write(ulpi, ULPI_SET(ULPI_HSIC_CFG), 0x01); - if (ret) - goto err_ulpi; - - /* Disable auto-resume */ - ret = ulpi_write(ulpi, ULPI_CLR(ULPI_IFC_CTRL), - ULPI_IFC_CTRL_AUTORESUME); - if (ret) - goto err_ulpi; - - return ret; -err_ulpi: - clk_disable_unprepare(uphy->cal_sleep_clk); -err_sleep: - clk_disable_unprepare(uphy->cal_clk); -err_cal: - clk_disable_unprepare(uphy->phy_clk); - return ret; -} - -static int qcom_usb_hsic_phy_power_off(struct phy *phy) -{ - struct qcom_usb_hsic_phy *uphy = phy_get_drvdata(phy); - - clk_disable_unprepare(uphy->cal_sleep_clk); - clk_disable_unprepare(uphy->cal_clk); - clk_disable_unprepare(uphy->phy_clk); - - return 0; -} - -static const struct phy_ops qcom_usb_hsic_phy_ops = { - .power_on = qcom_usb_hsic_phy_power_on, - .power_off = qcom_usb_hsic_phy_power_off, - .owner = THIS_MODULE, -}; - -static int qcom_usb_hsic_phy_probe(struct ulpi *ulpi) -{ - struct qcom_usb_hsic_phy *uphy; - struct phy_provider *p; - struct clk *clk; - - uphy = devm_kzalloc(&ulpi->dev, sizeof(*uphy), GFP_KERNEL); - if (!uphy) - return -ENOMEM; - ulpi_set_drvdata(ulpi, uphy); - - uphy->ulpi = ulpi; - uphy->pctl = devm_pinctrl_get(&ulpi->dev); - if (IS_ERR(uphy->pctl)) - return PTR_ERR(uphy->pctl); - - uphy->phy_clk = clk = devm_clk_get(&ulpi->dev, "phy"); - if (IS_ERR(clk)) - return PTR_ERR(clk); - - uphy->cal_clk = clk = devm_clk_get(&ulpi->dev, "cal"); - if (IS_ERR(clk)) - return PTR_ERR(clk); - - uphy->cal_sleep_clk = clk = devm_clk_get(&ulpi->dev, "cal_sleep"); - if (IS_ERR(clk)) - return PTR_ERR(clk); - - uphy->phy = devm_phy_create(&ulpi->dev, ulpi->dev.of_node, - &qcom_usb_hsic_phy_ops); - if (IS_ERR(uphy->phy)) - return PTR_ERR(uphy->phy); - phy_set_drvdata(uphy->phy, uphy); - - p = devm_of_phy_provider_register(&ulpi->dev, of_phy_simple_xlate); - return PTR_ERR_OR_ZERO(p); -} - -static const struct of_device_id qcom_usb_hsic_phy_match[] = { - { .compatible = "qcom,usb-hsic-phy", }, - { } -}; -MODULE_DEVICE_TABLE(of, qcom_usb_hsic_phy_match); - -static struct ulpi_driver qcom_usb_hsic_phy_driver = { - .probe = qcom_usb_hsic_phy_probe, - .driver = { - .name = "qcom_usb_hsic_phy", - .of_match_table = qcom_usb_hsic_phy_match, - }, -}; -module_ulpi_driver(qcom_usb_hsic_phy_driver); - -MODULE_DESCRIPTION("Qualcomm USB HSIC phy"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-rcar-gen2.c b/drivers/phy/phy-rcar-gen2.c deleted file mode 100644 index 97d4dd6ea924..000000000000 --- a/drivers/phy/phy-rcar-gen2.c +++ /dev/null @@ -1,337 +0,0 @@ -/* - * Renesas R-Car Gen2 PHY driver - * - * Copyright (C) 2014 Renesas Solutions Corp. - * Copyright (C) 2014 Cogent Embedded, Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define USBHS_LPSTS 0x02 -#define USBHS_UGCTRL 0x80 -#define USBHS_UGCTRL2 0x84 -#define USBHS_UGSTS 0x88 /* From technical update */ - -/* Low Power Status register (LPSTS) */ -#define USBHS_LPSTS_SUSPM 0x4000 - -/* USB General control register (UGCTRL) */ -#define USBHS_UGCTRL_CONNECT 0x00000004 -#define USBHS_UGCTRL_PLLRESET 0x00000001 - -/* USB General control register 2 (UGCTRL2) */ -#define USBHS_UGCTRL2_USB2SEL 0x80000000 -#define USBHS_UGCTRL2_USB2SEL_PCI 0x00000000 -#define USBHS_UGCTRL2_USB2SEL_USB30 0x80000000 -#define USBHS_UGCTRL2_USB0SEL 0x00000030 -#define USBHS_UGCTRL2_USB0SEL_PCI 0x00000010 -#define USBHS_UGCTRL2_USB0SEL_HS_USB 0x00000030 - -/* USB General status register (UGSTS) */ -#define USBHS_UGSTS_LOCK 0x00000100 /* From technical update */ - -#define PHYS_PER_CHANNEL 2 - -struct rcar_gen2_phy { - struct phy *phy; - struct rcar_gen2_channel *channel; - int number; - u32 select_value; -}; - -struct rcar_gen2_channel { - struct device_node *of_node; - struct rcar_gen2_phy_driver *drv; - struct rcar_gen2_phy phys[PHYS_PER_CHANNEL]; - int selected_phy; - u32 select_mask; -}; - -struct rcar_gen2_phy_driver { - void __iomem *base; - struct clk *clk; - spinlock_t lock; - int num_channels; - struct rcar_gen2_channel *channels; -}; - -static int rcar_gen2_phy_init(struct phy *p) -{ - struct rcar_gen2_phy *phy = phy_get_drvdata(p); - struct rcar_gen2_channel *channel = phy->channel; - struct rcar_gen2_phy_driver *drv = channel->drv; - unsigned long flags; - u32 ugctrl2; - - /* - * Try to acquire exclusive access to PHY. The first driver calling - * phy_init() on a given channel wins, and all attempts to use another - * PHY on this channel will fail until phy_exit() is called by the first - * driver. Achieving this with cmpxcgh() should be SMP-safe. - */ - if (cmpxchg(&channel->selected_phy, -1, phy->number) != -1) - return -EBUSY; - - clk_prepare_enable(drv->clk); - - spin_lock_irqsave(&drv->lock, flags); - ugctrl2 = readl(drv->base + USBHS_UGCTRL2); - ugctrl2 &= ~channel->select_mask; - ugctrl2 |= phy->select_value; - writel(ugctrl2, drv->base + USBHS_UGCTRL2); - spin_unlock_irqrestore(&drv->lock, flags); - return 0; -} - -static int rcar_gen2_phy_exit(struct phy *p) -{ - struct rcar_gen2_phy *phy = phy_get_drvdata(p); - struct rcar_gen2_channel *channel = phy->channel; - - clk_disable_unprepare(channel->drv->clk); - - channel->selected_phy = -1; - - return 0; -} - -static int rcar_gen2_phy_power_on(struct phy *p) -{ - struct rcar_gen2_phy *phy = phy_get_drvdata(p); - struct rcar_gen2_phy_driver *drv = phy->channel->drv; - void __iomem *base = drv->base; - unsigned long flags; - u32 value; - int err = 0, i; - - /* Skip if it's not USBHS */ - if (phy->select_value != USBHS_UGCTRL2_USB0SEL_HS_USB) - return 0; - - spin_lock_irqsave(&drv->lock, flags); - - /* Power on USBHS PHY */ - value = readl(base + USBHS_UGCTRL); - value &= ~USBHS_UGCTRL_PLLRESET; - writel(value, base + USBHS_UGCTRL); - - value = readw(base + USBHS_LPSTS); - value |= USBHS_LPSTS_SUSPM; - writew(value, base + USBHS_LPSTS); - - for (i = 0; i < 20; i++) { - value = readl(base + USBHS_UGSTS); - if ((value & USBHS_UGSTS_LOCK) == USBHS_UGSTS_LOCK) { - value = readl(base + USBHS_UGCTRL); - value |= USBHS_UGCTRL_CONNECT; - writel(value, base + USBHS_UGCTRL); - goto out; - } - udelay(1); - } - - /* Timed out waiting for the PLL lock */ - err = -ETIMEDOUT; - -out: - spin_unlock_irqrestore(&drv->lock, flags); - - return err; -} - -static int rcar_gen2_phy_power_off(struct phy *p) -{ - struct rcar_gen2_phy *phy = phy_get_drvdata(p); - struct rcar_gen2_phy_driver *drv = phy->channel->drv; - void __iomem *base = drv->base; - unsigned long flags; - u32 value; - - /* Skip if it's not USBHS */ - if (phy->select_value != USBHS_UGCTRL2_USB0SEL_HS_USB) - return 0; - - spin_lock_irqsave(&drv->lock, flags); - - /* Power off USBHS PHY */ - value = readl(base + USBHS_UGCTRL); - value &= ~USBHS_UGCTRL_CONNECT; - writel(value, base + USBHS_UGCTRL); - - value = readw(base + USBHS_LPSTS); - value &= ~USBHS_LPSTS_SUSPM; - writew(value, base + USBHS_LPSTS); - - value = readl(base + USBHS_UGCTRL); - value |= USBHS_UGCTRL_PLLRESET; - writel(value, base + USBHS_UGCTRL); - - spin_unlock_irqrestore(&drv->lock, flags); - - return 0; -} - -static const struct phy_ops rcar_gen2_phy_ops = { - .init = rcar_gen2_phy_init, - .exit = rcar_gen2_phy_exit, - .power_on = rcar_gen2_phy_power_on, - .power_off = rcar_gen2_phy_power_off, - .owner = THIS_MODULE, -}; - -static const struct of_device_id rcar_gen2_phy_match_table[] = { - { .compatible = "renesas,usb-phy-r8a7790" }, - { .compatible = "renesas,usb-phy-r8a7791" }, - { .compatible = "renesas,usb-phy-r8a7794" }, - { .compatible = "renesas,rcar-gen2-usb-phy" }, - { } -}; -MODULE_DEVICE_TABLE(of, rcar_gen2_phy_match_table); - -static struct phy *rcar_gen2_phy_xlate(struct device *dev, - struct of_phandle_args *args) -{ - struct rcar_gen2_phy_driver *drv; - struct device_node *np = args->np; - int i; - - drv = dev_get_drvdata(dev); - if (!drv) - return ERR_PTR(-EINVAL); - - for (i = 0; i < drv->num_channels; i++) { - if (np == drv->channels[i].of_node) - break; - } - - if (i >= drv->num_channels || args->args[0] >= 2) - return ERR_PTR(-ENODEV); - - return drv->channels[i].phys[args->args[0]].phy; -} - -static const u32 select_mask[] = { - [0] = USBHS_UGCTRL2_USB0SEL, - [2] = USBHS_UGCTRL2_USB2SEL, -}; - -static const u32 select_value[][PHYS_PER_CHANNEL] = { - [0] = { USBHS_UGCTRL2_USB0SEL_PCI, USBHS_UGCTRL2_USB0SEL_HS_USB }, - [2] = { USBHS_UGCTRL2_USB2SEL_PCI, USBHS_UGCTRL2_USB2SEL_USB30 }, -}; - -static int rcar_gen2_phy_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct rcar_gen2_phy_driver *drv; - struct phy_provider *provider; - struct device_node *np; - struct resource *res; - void __iomem *base; - struct clk *clk; - int i = 0; - - if (!dev->of_node) { - dev_err(dev, - "This driver is required to be instantiated from device tree\n"); - return -EINVAL; - } - - clk = devm_clk_get(dev, "usbhs"); - if (IS_ERR(clk)) { - dev_err(dev, "Can't get USBHS clock\n"); - return PTR_ERR(clk); - } - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - base = devm_ioremap_resource(dev, res); - if (IS_ERR(base)) - return PTR_ERR(base); - - drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL); - if (!drv) - return -ENOMEM; - - spin_lock_init(&drv->lock); - - drv->clk = clk; - drv->base = base; - - drv->num_channels = of_get_child_count(dev->of_node); - drv->channels = devm_kcalloc(dev, drv->num_channels, - sizeof(struct rcar_gen2_channel), - GFP_KERNEL); - if (!drv->channels) - return -ENOMEM; - - for_each_child_of_node(dev->of_node, np) { - struct rcar_gen2_channel *channel = drv->channels + i; - u32 channel_num; - int error, n; - - channel->of_node = np; - channel->drv = drv; - channel->selected_phy = -1; - - error = of_property_read_u32(np, "reg", &channel_num); - if (error || channel_num > 2) { - dev_err(dev, "Invalid \"reg\" property\n"); - return error; - } - channel->select_mask = select_mask[channel_num]; - - for (n = 0; n < PHYS_PER_CHANNEL; n++) { - struct rcar_gen2_phy *phy = &channel->phys[n]; - - phy->channel = channel; - phy->number = n; - phy->select_value = select_value[channel_num][n]; - - phy->phy = devm_phy_create(dev, NULL, - &rcar_gen2_phy_ops); - if (IS_ERR(phy->phy)) { - dev_err(dev, "Failed to create PHY\n"); - return PTR_ERR(phy->phy); - } - phy_set_drvdata(phy->phy, phy); - } - - i++; - } - - provider = devm_of_phy_provider_register(dev, rcar_gen2_phy_xlate); - if (IS_ERR(provider)) { - dev_err(dev, "Failed to register PHY provider\n"); - return PTR_ERR(provider); - } - - dev_set_drvdata(dev, drv); - - return 0; -} - -static struct platform_driver rcar_gen2_phy_driver = { - .driver = { - .name = "phy_rcar_gen2", - .of_match_table = rcar_gen2_phy_match_table, - }, - .probe = rcar_gen2_phy_probe, -}; - -module_platform_driver(rcar_gen2_phy_driver); - -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("Renesas R-Car Gen2 PHY"); -MODULE_AUTHOR("Sergei Shtylyov "); diff --git a/drivers/phy/phy-rcar-gen3-usb2.c b/drivers/phy/phy-rcar-gen3-usb2.c deleted file mode 100644 index 54c34298a000..000000000000 --- a/drivers/phy/phy-rcar-gen3-usb2.c +++ /dev/null @@ -1,507 +0,0 @@ -/* - * Renesas R-Car Gen3 for USB2.0 PHY driver - * - * Copyright (C) 2015 Renesas Electronics Corporation - * - * This is based on the phy-rcar-gen2 driver: - * Copyright (C) 2014 Renesas Solutions Corp. - * Copyright (C) 2014 Cogent Embedded, Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/******* USB2.0 Host registers (original offset is +0x200) *******/ -#define USB2_INT_ENABLE 0x000 -#define USB2_USBCTR 0x00c -#define USB2_SPD_RSM_TIMSET 0x10c -#define USB2_OC_TIMSET 0x110 -#define USB2_COMMCTRL 0x600 -#define USB2_OBINTSTA 0x604 -#define USB2_OBINTEN 0x608 -#define USB2_VBCTRL 0x60c -#define USB2_LINECTRL1 0x610 -#define USB2_ADPCTRL 0x630 - -/* INT_ENABLE */ -#define USB2_INT_ENABLE_UCOM_INTEN BIT(3) -#define USB2_INT_ENABLE_USBH_INTB_EN BIT(2) -#define USB2_INT_ENABLE_USBH_INTA_EN BIT(1) -#define USB2_INT_ENABLE_INIT (USB2_INT_ENABLE_UCOM_INTEN | \ - USB2_INT_ENABLE_USBH_INTB_EN | \ - USB2_INT_ENABLE_USBH_INTA_EN) - -/* USBCTR */ -#define USB2_USBCTR_DIRPD BIT(2) -#define USB2_USBCTR_PLL_RST BIT(1) - -/* SPD_RSM_TIMSET */ -#define USB2_SPD_RSM_TIMSET_INIT 0x014e029b - -/* OC_TIMSET */ -#define USB2_OC_TIMSET_INIT 0x000209ab - -/* COMMCTRL */ -#define USB2_COMMCTRL_OTG_PERI BIT(31) /* 1 = Peripheral mode */ - -/* OBINTSTA and OBINTEN */ -#define USB2_OBINT_SESSVLDCHG BIT(12) -#define USB2_OBINT_IDDIGCHG BIT(11) -#define USB2_OBINT_BITS (USB2_OBINT_SESSVLDCHG | \ - USB2_OBINT_IDDIGCHG) - -/* VBCTRL */ -#define USB2_VBCTRL_DRVVBUSSEL BIT(8) - -/* LINECTRL1 */ -#define USB2_LINECTRL1_DPRPD_EN BIT(19) -#define USB2_LINECTRL1_DP_RPD BIT(18) -#define USB2_LINECTRL1_DMRPD_EN BIT(17) -#define USB2_LINECTRL1_DM_RPD BIT(16) -#define USB2_LINECTRL1_OPMODE_NODRV BIT(6) - -/* ADPCTRL */ -#define USB2_ADPCTRL_OTGSESSVLD BIT(20) -#define USB2_ADPCTRL_IDDIG BIT(19) -#define USB2_ADPCTRL_IDPULLUP BIT(5) /* 1 = ID sampling is enabled */ -#define USB2_ADPCTRL_DRVVBUS BIT(4) - -struct rcar_gen3_chan { - void __iomem *base; - struct extcon_dev *extcon; - struct phy *phy; - struct regulator *vbus; - struct work_struct work; - bool extcon_host; - bool has_otg; -}; - -static void rcar_gen3_phy_usb2_work(struct work_struct *work) -{ - struct rcar_gen3_chan *ch = container_of(work, struct rcar_gen3_chan, - work); - - if (ch->extcon_host) { - extcon_set_state_sync(ch->extcon, EXTCON_USB_HOST, true); - extcon_set_state_sync(ch->extcon, EXTCON_USB, false); - } else { - extcon_set_state_sync(ch->extcon, EXTCON_USB_HOST, false); - extcon_set_state_sync(ch->extcon, EXTCON_USB, true); - } -} - -static void rcar_gen3_set_host_mode(struct rcar_gen3_chan *ch, int host) -{ - void __iomem *usb2_base = ch->base; - u32 val = readl(usb2_base + USB2_COMMCTRL); - - dev_vdbg(&ch->phy->dev, "%s: %08x, %d\n", __func__, val, host); - if (host) - val &= ~USB2_COMMCTRL_OTG_PERI; - else - val |= USB2_COMMCTRL_OTG_PERI; - writel(val, usb2_base + USB2_COMMCTRL); -} - -static void rcar_gen3_set_linectrl(struct rcar_gen3_chan *ch, int dp, int dm) -{ - void __iomem *usb2_base = ch->base; - u32 val = readl(usb2_base + USB2_LINECTRL1); - - dev_vdbg(&ch->phy->dev, "%s: %08x, %d, %d\n", __func__, val, dp, dm); - val &= ~(USB2_LINECTRL1_DP_RPD | USB2_LINECTRL1_DM_RPD); - if (dp) - val |= USB2_LINECTRL1_DP_RPD; - if (dm) - val |= USB2_LINECTRL1_DM_RPD; - writel(val, usb2_base + USB2_LINECTRL1); -} - -static void rcar_gen3_enable_vbus_ctrl(struct rcar_gen3_chan *ch, int vbus) -{ - void __iomem *usb2_base = ch->base; - u32 val = readl(usb2_base + USB2_ADPCTRL); - - dev_vdbg(&ch->phy->dev, "%s: %08x, %d\n", __func__, val, vbus); - if (vbus) - val |= USB2_ADPCTRL_DRVVBUS; - else - val &= ~USB2_ADPCTRL_DRVVBUS; - writel(val, usb2_base + USB2_ADPCTRL); -} - -static void rcar_gen3_init_for_host(struct rcar_gen3_chan *ch) -{ - rcar_gen3_set_linectrl(ch, 1, 1); - rcar_gen3_set_host_mode(ch, 1); - rcar_gen3_enable_vbus_ctrl(ch, 1); - - ch->extcon_host = true; - schedule_work(&ch->work); -} - -static void rcar_gen3_init_for_peri(struct rcar_gen3_chan *ch) -{ - rcar_gen3_set_linectrl(ch, 0, 1); - rcar_gen3_set_host_mode(ch, 0); - rcar_gen3_enable_vbus_ctrl(ch, 0); - - ch->extcon_host = false; - schedule_work(&ch->work); -} - -static void rcar_gen3_init_for_b_host(struct rcar_gen3_chan *ch) -{ - void __iomem *usb2_base = ch->base; - u32 val; - - val = readl(usb2_base + USB2_LINECTRL1); - writel(val | USB2_LINECTRL1_OPMODE_NODRV, usb2_base + USB2_LINECTRL1); - - rcar_gen3_set_linectrl(ch, 1, 1); - rcar_gen3_set_host_mode(ch, 1); - rcar_gen3_enable_vbus_ctrl(ch, 0); - - val = readl(usb2_base + USB2_LINECTRL1); - writel(val & ~USB2_LINECTRL1_OPMODE_NODRV, usb2_base + USB2_LINECTRL1); -} - -static void rcar_gen3_init_for_a_peri(struct rcar_gen3_chan *ch) -{ - rcar_gen3_set_linectrl(ch, 0, 1); - rcar_gen3_set_host_mode(ch, 0); - rcar_gen3_enable_vbus_ctrl(ch, 1); -} - -static void rcar_gen3_init_from_a_peri_to_a_host(struct rcar_gen3_chan *ch) -{ - void __iomem *usb2_base = ch->base; - u32 val; - - val = readl(usb2_base + USB2_OBINTEN); - writel(val & ~USB2_OBINT_BITS, usb2_base + USB2_OBINTEN); - - rcar_gen3_enable_vbus_ctrl(ch, 0); - rcar_gen3_init_for_host(ch); - - writel(val | USB2_OBINT_BITS, usb2_base + USB2_OBINTEN); -} - -static bool rcar_gen3_check_id(struct rcar_gen3_chan *ch) -{ - return !!(readl(ch->base + USB2_ADPCTRL) & USB2_ADPCTRL_IDDIG); -} - -static void rcar_gen3_device_recognition(struct rcar_gen3_chan *ch) -{ - if (!rcar_gen3_check_id(ch)) - rcar_gen3_init_for_host(ch); - else - rcar_gen3_init_for_peri(ch); -} - -static bool rcar_gen3_is_host(struct rcar_gen3_chan *ch) -{ - return !(readl(ch->base + USB2_COMMCTRL) & USB2_COMMCTRL_OTG_PERI); -} - -static ssize_t role_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - struct rcar_gen3_chan *ch = dev_get_drvdata(dev); - bool is_b_device, is_host, new_mode_is_host; - - if (!ch->has_otg || !ch->phy->init_count) - return -EIO; - - /* - * is_b_device: true is B-Device. false is A-Device. - * If {new_mode_}is_host: true is Host mode. false is Peripheral mode. - */ - is_b_device = rcar_gen3_check_id(ch); - is_host = rcar_gen3_is_host(ch); - if (!strncmp(buf, "host", strlen("host"))) - new_mode_is_host = true; - else if (!strncmp(buf, "peripheral", strlen("peripheral"))) - new_mode_is_host = false; - else - return -EINVAL; - - /* If current and new mode is the same, this returns the error */ - if (is_host == new_mode_is_host) - return -EINVAL; - - if (new_mode_is_host) { /* And is_host must be false */ - if (!is_b_device) /* A-Peripheral */ - rcar_gen3_init_from_a_peri_to_a_host(ch); - else /* B-Peripheral */ - rcar_gen3_init_for_b_host(ch); - } else { /* And is_host must be true */ - if (!is_b_device) /* A-Host */ - rcar_gen3_init_for_a_peri(ch); - else /* B-Host */ - rcar_gen3_init_for_peri(ch); - } - - return count; -} - -static ssize_t role_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct rcar_gen3_chan *ch = dev_get_drvdata(dev); - - if (!ch->has_otg || !ch->phy->init_count) - return -EIO; - - return sprintf(buf, "%s\n", rcar_gen3_is_host(ch) ? "host" : - "peripheral"); -} -static DEVICE_ATTR_RW(role); - -static void rcar_gen3_init_otg(struct rcar_gen3_chan *ch) -{ - void __iomem *usb2_base = ch->base; - u32 val; - - val = readl(usb2_base + USB2_VBCTRL); - writel(val | USB2_VBCTRL_DRVVBUSSEL, usb2_base + USB2_VBCTRL); - writel(USB2_OBINT_BITS, usb2_base + USB2_OBINTSTA); - val = readl(usb2_base + USB2_OBINTEN); - writel(val | USB2_OBINT_BITS, usb2_base + USB2_OBINTEN); - val = readl(usb2_base + USB2_ADPCTRL); - writel(val | USB2_ADPCTRL_IDPULLUP, usb2_base + USB2_ADPCTRL); - val = readl(usb2_base + USB2_LINECTRL1); - rcar_gen3_set_linectrl(ch, 0, 0); - writel(val | USB2_LINECTRL1_DPRPD_EN | USB2_LINECTRL1_DMRPD_EN, - usb2_base + USB2_LINECTRL1); - - rcar_gen3_device_recognition(ch); -} - -static int rcar_gen3_phy_usb2_init(struct phy *p) -{ - struct rcar_gen3_chan *channel = phy_get_drvdata(p); - void __iomem *usb2_base = channel->base; - - /* Initialize USB2 part */ - writel(USB2_INT_ENABLE_INIT, usb2_base + USB2_INT_ENABLE); - writel(USB2_SPD_RSM_TIMSET_INIT, usb2_base + USB2_SPD_RSM_TIMSET); - writel(USB2_OC_TIMSET_INIT, usb2_base + USB2_OC_TIMSET); - - /* Initialize otg part */ - if (channel->has_otg) - rcar_gen3_init_otg(channel); - - return 0; -} - -static int rcar_gen3_phy_usb2_exit(struct phy *p) -{ - struct rcar_gen3_chan *channel = phy_get_drvdata(p); - - writel(0, channel->base + USB2_INT_ENABLE); - - return 0; -} - -static int rcar_gen3_phy_usb2_power_on(struct phy *p) -{ - struct rcar_gen3_chan *channel = phy_get_drvdata(p); - void __iomem *usb2_base = channel->base; - u32 val; - int ret; - - if (channel->vbus) { - ret = regulator_enable(channel->vbus); - if (ret) - return ret; - } - - val = readl(usb2_base + USB2_USBCTR); - val |= USB2_USBCTR_PLL_RST; - writel(val, usb2_base + USB2_USBCTR); - val &= ~USB2_USBCTR_PLL_RST; - writel(val, usb2_base + USB2_USBCTR); - - return 0; -} - -static int rcar_gen3_phy_usb2_power_off(struct phy *p) -{ - struct rcar_gen3_chan *channel = phy_get_drvdata(p); - int ret = 0; - - if (channel->vbus) - ret = regulator_disable(channel->vbus); - - return ret; -} - -static const struct phy_ops rcar_gen3_phy_usb2_ops = { - .init = rcar_gen3_phy_usb2_init, - .exit = rcar_gen3_phy_usb2_exit, - .power_on = rcar_gen3_phy_usb2_power_on, - .power_off = rcar_gen3_phy_usb2_power_off, - .owner = THIS_MODULE, -}; - -static irqreturn_t rcar_gen3_phy_usb2_irq(int irq, void *_ch) -{ - struct rcar_gen3_chan *ch = _ch; - void __iomem *usb2_base = ch->base; - u32 status = readl(usb2_base + USB2_OBINTSTA); - irqreturn_t ret = IRQ_NONE; - - if (status & USB2_OBINT_BITS) { - dev_vdbg(&ch->phy->dev, "%s: %08x\n", __func__, status); - writel(USB2_OBINT_BITS, usb2_base + USB2_OBINTSTA); - rcar_gen3_device_recognition(ch); - ret = IRQ_HANDLED; - } - - return ret; -} - -static const struct of_device_id rcar_gen3_phy_usb2_match_table[] = { - { .compatible = "renesas,usb2-phy-r8a7795" }, - { .compatible = "renesas,usb2-phy-r8a7796" }, - { .compatible = "renesas,rcar-gen3-usb2-phy" }, - { } -}; -MODULE_DEVICE_TABLE(of, rcar_gen3_phy_usb2_match_table); - -static const unsigned int rcar_gen3_phy_cable[] = { - EXTCON_USB, - EXTCON_USB_HOST, - EXTCON_NONE, -}; - -static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct rcar_gen3_chan *channel; - struct phy_provider *provider; - struct resource *res; - int irq, ret = 0; - - if (!dev->of_node) { - dev_err(dev, "This driver needs device tree\n"); - return -EINVAL; - } - - channel = devm_kzalloc(dev, sizeof(*channel), GFP_KERNEL); - if (!channel) - return -ENOMEM; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - channel->base = devm_ioremap_resource(dev, res); - if (IS_ERR(channel->base)) - return PTR_ERR(channel->base); - - /* call request_irq for OTG */ - irq = platform_get_irq(pdev, 0); - if (irq >= 0) { - int ret; - - INIT_WORK(&channel->work, rcar_gen3_phy_usb2_work); - irq = devm_request_irq(dev, irq, rcar_gen3_phy_usb2_irq, - IRQF_SHARED, dev_name(dev), channel); - if (irq < 0) - dev_err(dev, "No irq handler (%d)\n", irq); - channel->has_otg = true; - channel->extcon = devm_extcon_dev_allocate(dev, - rcar_gen3_phy_cable); - if (IS_ERR(channel->extcon)) - return PTR_ERR(channel->extcon); - - ret = devm_extcon_dev_register(dev, channel->extcon); - if (ret < 0) { - dev_err(dev, "Failed to register extcon\n"); - return ret; - } - } - - /* - * devm_phy_create() will call pm_runtime_enable(&phy->dev); - * And then, phy-core will manage runtime pm for this device. - */ - pm_runtime_enable(dev); - channel->phy = devm_phy_create(dev, NULL, &rcar_gen3_phy_usb2_ops); - if (IS_ERR(channel->phy)) { - dev_err(dev, "Failed to create USB2 PHY\n"); - ret = PTR_ERR(channel->phy); - goto error; - } - - channel->vbus = devm_regulator_get_optional(dev, "vbus"); - if (IS_ERR(channel->vbus)) { - if (PTR_ERR(channel->vbus) == -EPROBE_DEFER) { - ret = PTR_ERR(channel->vbus); - goto error; - } - channel->vbus = NULL; - } - - platform_set_drvdata(pdev, channel); - phy_set_drvdata(channel->phy, channel); - - provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); - if (IS_ERR(provider)) { - dev_err(dev, "Failed to register PHY provider\n"); - ret = PTR_ERR(provider); - goto error; - } else if (channel->has_otg) { - int ret; - - ret = device_create_file(dev, &dev_attr_role); - if (ret < 0) - goto error; - } - - return 0; - -error: - pm_runtime_disable(dev); - - return ret; -} - -static int rcar_gen3_phy_usb2_remove(struct platform_device *pdev) -{ - struct rcar_gen3_chan *channel = platform_get_drvdata(pdev); - - if (channel->has_otg) - device_remove_file(&pdev->dev, &dev_attr_role); - - pm_runtime_disable(&pdev->dev); - - return 0; -}; - -static struct platform_driver rcar_gen3_phy_usb2_driver = { - .driver = { - .name = "phy_rcar_gen3_usb2", - .of_match_table = rcar_gen3_phy_usb2_match_table, - }, - .probe = rcar_gen3_phy_usb2_probe, - .remove = rcar_gen3_phy_usb2_remove, -}; -module_platform_driver(rcar_gen3_phy_usb2_driver); - -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("Renesas R-Car Gen3 USB 2.0 PHY"); -MODULE_AUTHOR("Yoshihiro Shimoda "); diff --git a/drivers/phy/phy-rockchip-dp.c b/drivers/phy/phy-rockchip-dp.c deleted file mode 100644 index 8b267a746576..000000000000 --- a/drivers/phy/phy-rockchip-dp.c +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Rockchip DP PHY driver - * - * Copyright (C) 2016 FuZhou Rockchip Co., Ltd. - * Author: Yakir Yang - * - * 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; either version 2 of the License. - */ - -#include -#include -#include -#include -#include -#include -#include - -#define GRF_SOC_CON12 0x0274 - -#define GRF_EDP_REF_CLK_SEL_INTER_HIWORD_MASK BIT(20) -#define GRF_EDP_REF_CLK_SEL_INTER BIT(4) - -#define GRF_EDP_PHY_SIDDQ_HIWORD_MASK BIT(21) -#define GRF_EDP_PHY_SIDDQ_ON 0 -#define GRF_EDP_PHY_SIDDQ_OFF BIT(5) - -struct rockchip_dp_phy { - struct device *dev; - struct regmap *grf; - struct clk *phy_24m; -}; - -static int rockchip_set_phy_state(struct phy *phy, bool enable) -{ - struct rockchip_dp_phy *dp = phy_get_drvdata(phy); - int ret; - - if (enable) { - ret = regmap_write(dp->grf, GRF_SOC_CON12, - GRF_EDP_PHY_SIDDQ_HIWORD_MASK | - GRF_EDP_PHY_SIDDQ_ON); - if (ret < 0) { - dev_err(dp->dev, "Can't enable PHY power %d\n", ret); - return ret; - } - - ret = clk_prepare_enable(dp->phy_24m); - } else { - clk_disable_unprepare(dp->phy_24m); - - ret = regmap_write(dp->grf, GRF_SOC_CON12, - GRF_EDP_PHY_SIDDQ_HIWORD_MASK | - GRF_EDP_PHY_SIDDQ_OFF); - } - - return ret; -} - -static int rockchip_dp_phy_power_on(struct phy *phy) -{ - return rockchip_set_phy_state(phy, true); -} - -static int rockchip_dp_phy_power_off(struct phy *phy) -{ - return rockchip_set_phy_state(phy, false); -} - -static const struct phy_ops rockchip_dp_phy_ops = { - .power_on = rockchip_dp_phy_power_on, - .power_off = rockchip_dp_phy_power_off, - .owner = THIS_MODULE, -}; - -static int rockchip_dp_phy_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct device_node *np = dev->of_node; - struct phy_provider *phy_provider; - struct rockchip_dp_phy *dp; - struct phy *phy; - int ret; - - if (!np) - return -ENODEV; - - if (!dev->parent || !dev->parent->of_node) - return -ENODEV; - - dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL); - if (!dp) - return -ENOMEM; - - dp->dev = dev; - - dp->phy_24m = devm_clk_get(dev, "24m"); - if (IS_ERR(dp->phy_24m)) { - dev_err(dev, "cannot get clock 24m\n"); - return PTR_ERR(dp->phy_24m); - } - - ret = clk_set_rate(dp->phy_24m, 24000000); - if (ret < 0) { - dev_err(dp->dev, "cannot set clock phy_24m %d\n", ret); - return ret; - } - - dp->grf = syscon_node_to_regmap(dev->parent->of_node); - if (IS_ERR(dp->grf)) { - dev_err(dev, "rk3288-dp needs the General Register Files syscon\n"); - return PTR_ERR(dp->grf); - } - - ret = regmap_write(dp->grf, GRF_SOC_CON12, GRF_EDP_REF_CLK_SEL_INTER | - GRF_EDP_REF_CLK_SEL_INTER_HIWORD_MASK); - if (ret != 0) { - dev_err(dp->dev, "Could not config GRF edp ref clk: %d\n", ret); - return ret; - } - - phy = devm_phy_create(dev, np, &rockchip_dp_phy_ops); - if (IS_ERR(phy)) { - dev_err(dev, "failed to create phy\n"); - return PTR_ERR(phy); - } - phy_set_drvdata(phy, dp); - - phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); - - return PTR_ERR_OR_ZERO(phy_provider); -} - -static const struct of_device_id rockchip_dp_phy_dt_ids[] = { - { .compatible = "rockchip,rk3288-dp-phy" }, - {} -}; - -MODULE_DEVICE_TABLE(of, rockchip_dp_phy_dt_ids); - -static struct platform_driver rockchip_dp_phy_driver = { - .probe = rockchip_dp_phy_probe, - .driver = { - .name = "rockchip-dp-phy", - .of_match_table = rockchip_dp_phy_dt_ids, - }, -}; - -module_platform_driver(rockchip_dp_phy_driver); - -MODULE_AUTHOR("Yakir Yang "); -MODULE_DESCRIPTION("Rockchip DP PHY driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-rockchip-emmc.c b/drivers/phy/phy-rockchip-emmc.c deleted file mode 100644 index f1b24f18e9b2..000000000000 --- a/drivers/phy/phy-rockchip-emmc.c +++ /dev/null @@ -1,383 +0,0 @@ -/* - * Rockchip emmc PHY driver - * - * Copyright (C) 2016 Shawn Lin - * Copyright (C) 2016 ROCKCHIP, Inc. - * - * 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; either 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. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* - * The higher 16-bit of this register is used for write protection - * only if BIT(x + 16) set to 1 the BIT(x) can be written. - */ -#define HIWORD_UPDATE(val, mask, shift) \ - ((val) << (shift) | (mask) << ((shift) + 16)) - -/* Register definition */ -#define GRF_EMMCPHY_CON0 0x0 -#define GRF_EMMCPHY_CON1 0x4 -#define GRF_EMMCPHY_CON2 0x8 -#define GRF_EMMCPHY_CON3 0xc -#define GRF_EMMCPHY_CON4 0x10 -#define GRF_EMMCPHY_CON5 0x14 -#define GRF_EMMCPHY_CON6 0x18 -#define GRF_EMMCPHY_STATUS 0x20 - -#define PHYCTRL_PDB_MASK 0x1 -#define PHYCTRL_PDB_SHIFT 0x0 -#define PHYCTRL_PDB_PWR_ON 0x1 -#define PHYCTRL_PDB_PWR_OFF 0x0 -#define PHYCTRL_ENDLL_MASK 0x1 -#define PHYCTRL_ENDLL_SHIFT 0x1 -#define PHYCTRL_ENDLL_ENABLE 0x1 -#define PHYCTRL_ENDLL_DISABLE 0x0 -#define PHYCTRL_CALDONE_MASK 0x1 -#define PHYCTRL_CALDONE_SHIFT 0x6 -#define PHYCTRL_CALDONE_DONE 0x1 -#define PHYCTRL_CALDONE_GOING 0x0 -#define PHYCTRL_DLLRDY_MASK 0x1 -#define PHYCTRL_DLLRDY_SHIFT 0x5 -#define PHYCTRL_DLLRDY_DONE 0x1 -#define PHYCTRL_DLLRDY_GOING 0x0 -#define PHYCTRL_FREQSEL_200M 0x0 -#define PHYCTRL_FREQSEL_50M 0x1 -#define PHYCTRL_FREQSEL_100M 0x2 -#define PHYCTRL_FREQSEL_150M 0x3 -#define PHYCTRL_FREQSEL_MASK 0x3 -#define PHYCTRL_FREQSEL_SHIFT 0xc -#define PHYCTRL_DR_MASK 0x7 -#define PHYCTRL_DR_SHIFT 0x4 -#define PHYCTRL_DR_50OHM 0x0 -#define PHYCTRL_DR_33OHM 0x1 -#define PHYCTRL_DR_66OHM 0x2 -#define PHYCTRL_DR_100OHM 0x3 -#define PHYCTRL_DR_40OHM 0x4 -#define PHYCTRL_OTAPDLYENA 0x1 -#define PHYCTRL_OTAPDLYENA_MASK 0x1 -#define PHYCTRL_OTAPDLYENA_SHIFT 0xb -#define PHYCTRL_OTAPDLYSEL_MASK 0xf -#define PHYCTRL_OTAPDLYSEL_SHIFT 0x7 - -struct rockchip_emmc_phy { - unsigned int reg_offset; - struct regmap *reg_base; - struct clk *emmcclk; -}; - -static int rockchip_emmc_phy_power(struct phy *phy, bool on_off) -{ - struct rockchip_emmc_phy *rk_phy = phy_get_drvdata(phy); - unsigned int caldone; - unsigned int dllrdy; - unsigned int freqsel = PHYCTRL_FREQSEL_200M; - unsigned long rate; - unsigned long timeout; - - /* - * Keep phyctrl_pdb and phyctrl_endll low to allow - * initialization of CALIO state M/C DFFs - */ - regmap_write(rk_phy->reg_base, - rk_phy->reg_offset + GRF_EMMCPHY_CON6, - HIWORD_UPDATE(PHYCTRL_PDB_PWR_OFF, - PHYCTRL_PDB_MASK, - PHYCTRL_PDB_SHIFT)); - regmap_write(rk_phy->reg_base, - rk_phy->reg_offset + GRF_EMMCPHY_CON6, - HIWORD_UPDATE(PHYCTRL_ENDLL_DISABLE, - PHYCTRL_ENDLL_MASK, - PHYCTRL_ENDLL_SHIFT)); - - /* Already finish power_off above */ - if (on_off == PHYCTRL_PDB_PWR_OFF) - return 0; - - rate = clk_get_rate(rk_phy->emmcclk); - - if (rate != 0) { - unsigned long ideal_rate; - unsigned long diff; - - switch (rate) { - case 1 ... 74999999: - ideal_rate = 50000000; - freqsel = PHYCTRL_FREQSEL_50M; - break; - case 75000000 ... 124999999: - ideal_rate = 100000000; - freqsel = PHYCTRL_FREQSEL_100M; - break; - case 125000000 ... 174999999: - ideal_rate = 150000000; - freqsel = PHYCTRL_FREQSEL_150M; - break; - default: - ideal_rate = 200000000; - break; - } - - diff = (rate > ideal_rate) ? - rate - ideal_rate : ideal_rate - rate; - - /* - * In order for tuning delays to be accurate we need to be - * pretty spot on for the DLL range, so warn if we're too - * far off. Also warn if we're above the 200 MHz max. Don't - * warn for really slow rates since we won't be tuning then. - */ - if ((rate > 50000000 && diff > 15000000) || (rate > 200000000)) - dev_warn(&phy->dev, "Unsupported rate: %lu\n", rate); - } - - /* - * According to the user manual, calpad calibration - * cycle takes more than 2us without the minimal recommended - * value, so we may need a little margin here - */ - udelay(3); - regmap_write(rk_phy->reg_base, - rk_phy->reg_offset + GRF_EMMCPHY_CON6, - HIWORD_UPDATE(PHYCTRL_PDB_PWR_ON, - PHYCTRL_PDB_MASK, - PHYCTRL_PDB_SHIFT)); - - /* - * According to the user manual, it asks driver to - * wait 5us for calpad busy trimming - */ - udelay(5); - regmap_read(rk_phy->reg_base, - rk_phy->reg_offset + GRF_EMMCPHY_STATUS, - &caldone); - caldone = (caldone >> PHYCTRL_CALDONE_SHIFT) & PHYCTRL_CALDONE_MASK; - if (caldone != PHYCTRL_CALDONE_DONE) { - pr_err("rockchip_emmc_phy_power: caldone timeout.\n"); - return -ETIMEDOUT; - } - - /* Set the frequency of the DLL operation */ - regmap_write(rk_phy->reg_base, - rk_phy->reg_offset + GRF_EMMCPHY_CON0, - HIWORD_UPDATE(freqsel, PHYCTRL_FREQSEL_MASK, - PHYCTRL_FREQSEL_SHIFT)); - - /* Turn on the DLL */ - regmap_write(rk_phy->reg_base, - rk_phy->reg_offset + GRF_EMMCPHY_CON6, - HIWORD_UPDATE(PHYCTRL_ENDLL_ENABLE, - PHYCTRL_ENDLL_MASK, - PHYCTRL_ENDLL_SHIFT)); - - /* - * We turned on the DLL even though the rate was 0 because we the - * clock might be turned on later. ...but we can't wait for the DLL - * to lock when the rate is 0 because it will never lock with no - * input clock. - * - * Technically we should be checking the lock later when the clock - * is turned on, but for now we won't. - */ - if (rate == 0) - return 0; - - /* - * After enabling analog DLL circuits docs say that we need 10.2 us if - * our source clock is at 50 MHz and that lock time scales linearly - * with clock speed. If we are powering on the PHY and the card clock - * is super slow (like 100 kHZ) this could take as long as 5.1 ms as - * per the math: 10.2 us * (50000000 Hz / 100000 Hz) => 5.1 ms - * Hopefully we won't be running at 100 kHz, but we should still make - * sure we wait long enough. - * - * NOTE: There appear to be corner cases where the DLL seems to take - * extra long to lock for reasons that aren't understood. In some - * extreme cases we've seen it take up to over 10ms (!). We'll be - * generous and give it 50ms. We still busy wait here because: - * - In most cases it should be super fast. - * - This is not called lots during normal operation so it shouldn't - * be a power or performance problem to busy wait. We expect it - * only at boot / resume. In both cases, eMMC is probably on the - * critical path so busy waiting a little extra time should be OK. - */ - timeout = jiffies + msecs_to_jiffies(50); - do { - udelay(1); - - regmap_read(rk_phy->reg_base, - rk_phy->reg_offset + GRF_EMMCPHY_STATUS, - &dllrdy); - dllrdy = (dllrdy >> PHYCTRL_DLLRDY_SHIFT) & PHYCTRL_DLLRDY_MASK; - if (dllrdy == PHYCTRL_DLLRDY_DONE) - break; - } while (!time_after(jiffies, timeout)); - - if (dllrdy != PHYCTRL_DLLRDY_DONE) { - pr_err("rockchip_emmc_phy_power: dllrdy timeout.\n"); - return -ETIMEDOUT; - } - - return 0; -} - -static int rockchip_emmc_phy_init(struct phy *phy) -{ - struct rockchip_emmc_phy *rk_phy = phy_get_drvdata(phy); - int ret = 0; - - /* - * We purposely get the clock here and not in probe to avoid the - * circular dependency problem. We expect: - * - PHY driver to probe - * - SDHCI driver to start probe - * - SDHCI driver to register it's clock - * - SDHCI driver to get the PHY - * - SDHCI driver to init the PHY - * - * The clock is optional, so upon any error we just set to NULL. - * - * NOTE: we don't do anything special for EPROBE_DEFER here. Given the - * above expected use case, EPROBE_DEFER isn't sensible to expect, so - * it's just like any other error. - */ - rk_phy->emmcclk = clk_get(&phy->dev, "emmcclk"); - if (IS_ERR(rk_phy->emmcclk)) { - dev_dbg(&phy->dev, "Error getting emmcclk: %d\n", ret); - rk_phy->emmcclk = NULL; - } - - return ret; -} - -static int rockchip_emmc_phy_exit(struct phy *phy) -{ - struct rockchip_emmc_phy *rk_phy = phy_get_drvdata(phy); - - clk_put(rk_phy->emmcclk); - - return 0; -} - -static int rockchip_emmc_phy_power_off(struct phy *phy) -{ - /* Power down emmc phy analog blocks */ - return rockchip_emmc_phy_power(phy, PHYCTRL_PDB_PWR_OFF); -} - -static int rockchip_emmc_phy_power_on(struct phy *phy) -{ - struct rockchip_emmc_phy *rk_phy = phy_get_drvdata(phy); - - /* Drive impedance: 50 Ohm */ - regmap_write(rk_phy->reg_base, - rk_phy->reg_offset + GRF_EMMCPHY_CON6, - HIWORD_UPDATE(PHYCTRL_DR_50OHM, - PHYCTRL_DR_MASK, - PHYCTRL_DR_SHIFT)); - - /* Output tap delay: enable */ - regmap_write(rk_phy->reg_base, - rk_phy->reg_offset + GRF_EMMCPHY_CON0, - HIWORD_UPDATE(PHYCTRL_OTAPDLYENA, - PHYCTRL_OTAPDLYENA_MASK, - PHYCTRL_OTAPDLYENA_SHIFT)); - - /* Output tap delay */ - regmap_write(rk_phy->reg_base, - rk_phy->reg_offset + GRF_EMMCPHY_CON0, - HIWORD_UPDATE(4, - PHYCTRL_OTAPDLYSEL_MASK, - PHYCTRL_OTAPDLYSEL_SHIFT)); - - /* Power up emmc phy analog blocks */ - return rockchip_emmc_phy_power(phy, PHYCTRL_PDB_PWR_ON); -} - -static const struct phy_ops ops = { - .init = rockchip_emmc_phy_init, - .exit = rockchip_emmc_phy_exit, - .power_on = rockchip_emmc_phy_power_on, - .power_off = rockchip_emmc_phy_power_off, - .owner = THIS_MODULE, -}; - -static int rockchip_emmc_phy_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct rockchip_emmc_phy *rk_phy; - struct phy *generic_phy; - struct phy_provider *phy_provider; - struct regmap *grf; - unsigned int reg_offset; - - if (!dev->parent || !dev->parent->of_node) - return -ENODEV; - - grf = syscon_node_to_regmap(dev->parent->of_node); - if (IS_ERR(grf)) { - dev_err(dev, "Missing rockchip,grf property\n"); - return PTR_ERR(grf); - } - - rk_phy = devm_kzalloc(dev, sizeof(*rk_phy), GFP_KERNEL); - if (!rk_phy) - return -ENOMEM; - - if (of_property_read_u32(dev->of_node, "reg", ®_offset)) { - dev_err(dev, "missing reg property in node %s\n", - dev->of_node->name); - return -EINVAL; - } - - rk_phy->reg_offset = reg_offset; - rk_phy->reg_base = grf; - - generic_phy = devm_phy_create(dev, dev->of_node, &ops); - if (IS_ERR(generic_phy)) { - dev_err(dev, "failed to create PHY\n"); - return PTR_ERR(generic_phy); - } - - phy_set_drvdata(generic_phy, rk_phy); - phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); - - return PTR_ERR_OR_ZERO(phy_provider); -} - -static const struct of_device_id rockchip_emmc_phy_dt_ids[] = { - { .compatible = "rockchip,rk3399-emmc-phy" }, - {} -}; - -MODULE_DEVICE_TABLE(of, rockchip_emmc_phy_dt_ids); - -static struct platform_driver rockchip_emmc_driver = { - .probe = rockchip_emmc_phy_probe, - .driver = { - .name = "rockchip-emmc-phy", - .of_match_table = rockchip_emmc_phy_dt_ids, - }, -}; - -module_platform_driver(rockchip_emmc_driver); - -MODULE_AUTHOR("Shawn Lin "); -MODULE_DESCRIPTION("Rockchip EMMC PHY driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-rockchip-inno-usb2.c b/drivers/phy/phy-rockchip-inno-usb2.c deleted file mode 100644 index 8efe78a49916..000000000000 --- a/drivers/phy/phy-rockchip-inno-usb2.c +++ /dev/null @@ -1,1284 +0,0 @@ -/* - * Rockchip USB2.0 PHY with Innosilicon IP block driver - * - * Copyright (C) 2016 Fuzhou Rockchip Electronics Co., Ltd - * - * 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; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define BIT_WRITEABLE_SHIFT 16 -#define SCHEDULE_DELAY (60 * HZ) -#define OTG_SCHEDULE_DELAY (2 * HZ) - -enum rockchip_usb2phy_port_id { - USB2PHY_PORT_OTG, - USB2PHY_PORT_HOST, - USB2PHY_NUM_PORTS, -}; - -enum rockchip_usb2phy_host_state { - PHY_STATE_HS_ONLINE = 0, - PHY_STATE_DISCONNECT = 1, - PHY_STATE_CONNECT = 2, - PHY_STATE_FS_LS_ONLINE = 4, -}; - -/** - * Different states involved in USB charger detection. - * USB_CHG_STATE_UNDEFINED USB charger is not connected or detection - * process is not yet started. - * USB_CHG_STATE_WAIT_FOR_DCD Waiting for Data pins contact. - * USB_CHG_STATE_DCD_DONE Data pin contact is detected. - * USB_CHG_STATE_PRIMARY_DONE Primary detection is completed (Detects - * between SDP and DCP/CDP). - * USB_CHG_STATE_SECONDARY_DONE Secondary detection is completed (Detects - * between DCP and CDP). - * USB_CHG_STATE_DETECTED USB charger type is determined. - */ -enum usb_chg_state { - USB_CHG_STATE_UNDEFINED = 0, - USB_CHG_STATE_WAIT_FOR_DCD, - USB_CHG_STATE_DCD_DONE, - USB_CHG_STATE_PRIMARY_DONE, - USB_CHG_STATE_SECONDARY_DONE, - USB_CHG_STATE_DETECTED, -}; - -static const unsigned int rockchip_usb2phy_extcon_cable[] = { - EXTCON_USB, - EXTCON_USB_HOST, - EXTCON_CHG_USB_SDP, - EXTCON_CHG_USB_CDP, - EXTCON_CHG_USB_DCP, - EXTCON_CHG_USB_SLOW, - EXTCON_NONE, -}; - -struct usb2phy_reg { - unsigned int offset; - unsigned int bitend; - unsigned int bitstart; - unsigned int disable; - unsigned int enable; -}; - -/** - * struct rockchip_chg_det_reg: usb charger detect registers - * @cp_det: charging port detected successfully. - * @dcp_det: dedicated charging port detected successfully. - * @dp_det: assert data pin connect successfully. - * @idm_sink_en: open dm sink curren. - * @idp_sink_en: open dp sink current. - * @idp_src_en: open dm source current. - * @rdm_pdwn_en: open dm pull down resistor. - * @vdm_src_en: open dm voltage source. - * @vdp_src_en: open dp voltage source. - * @opmode: utmi operational mode. - */ -struct rockchip_chg_det_reg { - struct usb2phy_reg cp_det; - struct usb2phy_reg dcp_det; - struct usb2phy_reg dp_det; - struct usb2phy_reg idm_sink_en; - struct usb2phy_reg idp_sink_en; - struct usb2phy_reg idp_src_en; - struct usb2phy_reg rdm_pdwn_en; - struct usb2phy_reg vdm_src_en; - struct usb2phy_reg vdp_src_en; - struct usb2phy_reg opmode; -}; - -/** - * struct rockchip_usb2phy_port_cfg: usb-phy port configuration. - * @phy_sus: phy suspend register. - * @bvalid_det_en: vbus valid rise detection enable register. - * @bvalid_det_st: vbus valid rise detection status register. - * @bvalid_det_clr: vbus valid rise detection clear register. - * @ls_det_en: linestate detection enable register. - * @ls_det_st: linestate detection state register. - * @ls_det_clr: linestate detection clear register. - * @utmi_avalid: utmi vbus avalid status register. - * @utmi_bvalid: utmi vbus bvalid status register. - * @utmi_ls: utmi linestate state register. - * @utmi_hstdet: utmi host disconnect register. - */ -struct rockchip_usb2phy_port_cfg { - struct usb2phy_reg phy_sus; - struct usb2phy_reg bvalid_det_en; - struct usb2phy_reg bvalid_det_st; - struct usb2phy_reg bvalid_det_clr; - struct usb2phy_reg ls_det_en; - struct usb2phy_reg ls_det_st; - struct usb2phy_reg ls_det_clr; - struct usb2phy_reg utmi_avalid; - struct usb2phy_reg utmi_bvalid; - struct usb2phy_reg utmi_ls; - struct usb2phy_reg utmi_hstdet; -}; - -/** - * struct rockchip_usb2phy_cfg: usb-phy configuration. - * @reg: the address offset of grf for usb-phy config. - * @num_ports: specify how many ports that the phy has. - * @clkout_ctl: keep on/turn off output clk of phy. - * @chg_det: charger detection registers. - */ -struct rockchip_usb2phy_cfg { - unsigned int reg; - unsigned int num_ports; - struct usb2phy_reg clkout_ctl; - const struct rockchip_usb2phy_port_cfg port_cfgs[USB2PHY_NUM_PORTS]; - const struct rockchip_chg_det_reg chg_det; -}; - -/** - * struct rockchip_usb2phy_port: usb-phy port data. - * @port_id: flag for otg port or host port. - * @suspended: phy suspended flag. - * @utmi_avalid: utmi avalid status usage flag. - * true - use avalid to get vbus status - * flase - use bvalid to get vbus status - * @vbus_attached: otg device vbus status. - * @bvalid_irq: IRQ number assigned for vbus valid rise detection. - * @ls_irq: IRQ number assigned for linestate detection. - * @mutex: for register updating in sm_work. - * @chg_work: charge detect work. - * @otg_sm_work: OTG state machine work. - * @sm_work: HOST state machine work. - * @phy_cfg: port register configuration, assigned by driver data. - * @event_nb: hold event notification callback. - * @state: define OTG enumeration states before device reset. - * @mode: the dr_mode of the controller. - */ -struct rockchip_usb2phy_port { - struct phy *phy; - unsigned int port_id; - bool suspended; - bool utmi_avalid; - bool vbus_attached; - int bvalid_irq; - int ls_irq; - struct mutex mutex; - struct delayed_work chg_work; - struct delayed_work otg_sm_work; - struct delayed_work sm_work; - const struct rockchip_usb2phy_port_cfg *port_cfg; - struct notifier_block event_nb; - enum usb_otg_state state; - enum usb_dr_mode mode; -}; - -/** - * struct rockchip_usb2phy: usb2.0 phy driver data. - * @grf: General Register Files regmap. - * @clk: clock struct of phy input clk. - * @clk480m: clock struct of phy output clk. - * @clk_hw: clock struct of phy output clk management. - * @chg_state: states involved in USB charger detection. - * @chg_type: USB charger types. - * @dcd_retries: The retry count used to track Data contact - * detection process. - * @edev: extcon device for notification registration - * @phy_cfg: phy register configuration, assigned by driver data. - * @ports: phy port instance. - */ -struct rockchip_usb2phy { - struct device *dev; - struct regmap *grf; - struct clk *clk; - struct clk *clk480m; - struct clk_hw clk480m_hw; - enum usb_chg_state chg_state; - enum power_supply_type chg_type; - u8 dcd_retries; - struct extcon_dev *edev; - const struct rockchip_usb2phy_cfg *phy_cfg; - struct rockchip_usb2phy_port ports[USB2PHY_NUM_PORTS]; -}; - -static inline int property_enable(struct rockchip_usb2phy *rphy, - const struct usb2phy_reg *reg, bool en) -{ - unsigned int val, mask, tmp; - - tmp = en ? reg->enable : reg->disable; - mask = GENMASK(reg->bitend, reg->bitstart); - val = (tmp << reg->bitstart) | (mask << BIT_WRITEABLE_SHIFT); - - return regmap_write(rphy->grf, reg->offset, val); -} - -static inline bool property_enabled(struct rockchip_usb2phy *rphy, - const struct usb2phy_reg *reg) -{ - int ret; - unsigned int tmp, orig; - unsigned int mask = GENMASK(reg->bitend, reg->bitstart); - - ret = regmap_read(rphy->grf, reg->offset, &orig); - if (ret) - return false; - - tmp = (orig & mask) >> reg->bitstart; - return tmp == reg->enable; -} - -static int rockchip_usb2phy_clk480m_prepare(struct clk_hw *hw) -{ - struct rockchip_usb2phy *rphy = - container_of(hw, struct rockchip_usb2phy, clk480m_hw); - int ret; - - /* turn on 480m clk output if it is off */ - if (!property_enabled(rphy, &rphy->phy_cfg->clkout_ctl)) { - ret = property_enable(rphy, &rphy->phy_cfg->clkout_ctl, true); - if (ret) - return ret; - - /* waiting for the clk become stable */ - usleep_range(1200, 1300); - } - - return 0; -} - -static void rockchip_usb2phy_clk480m_unprepare(struct clk_hw *hw) -{ - struct rockchip_usb2phy *rphy = - container_of(hw, struct rockchip_usb2phy, clk480m_hw); - - /* turn off 480m clk output */ - property_enable(rphy, &rphy->phy_cfg->clkout_ctl, false); -} - -static int rockchip_usb2phy_clk480m_prepared(struct clk_hw *hw) -{ - struct rockchip_usb2phy *rphy = - container_of(hw, struct rockchip_usb2phy, clk480m_hw); - - return property_enabled(rphy, &rphy->phy_cfg->clkout_ctl); -} - -static unsigned long -rockchip_usb2phy_clk480m_recalc_rate(struct clk_hw *hw, - unsigned long parent_rate) -{ - return 480000000; -} - -static const struct clk_ops rockchip_usb2phy_clkout_ops = { - .prepare = rockchip_usb2phy_clk480m_prepare, - .unprepare = rockchip_usb2phy_clk480m_unprepare, - .is_prepared = rockchip_usb2phy_clk480m_prepared, - .recalc_rate = rockchip_usb2phy_clk480m_recalc_rate, -}; - -static void rockchip_usb2phy_clk480m_unregister(void *data) -{ - struct rockchip_usb2phy *rphy = data; - - of_clk_del_provider(rphy->dev->of_node); - clk_unregister(rphy->clk480m); -} - -static int -rockchip_usb2phy_clk480m_register(struct rockchip_usb2phy *rphy) -{ - struct device_node *node = rphy->dev->of_node; - struct clk_init_data init; - const char *clk_name; - int ret; - - init.flags = 0; - init.name = "clk_usbphy_480m"; - init.ops = &rockchip_usb2phy_clkout_ops; - - /* optional override of the clockname */ - of_property_read_string(node, "clock-output-names", &init.name); - - if (rphy->clk) { - clk_name = __clk_get_name(rphy->clk); - init.parent_names = &clk_name; - init.num_parents = 1; - } else { - init.parent_names = NULL; - init.num_parents = 0; - } - - rphy->clk480m_hw.init = &init; - - /* register the clock */ - rphy->clk480m = clk_register(rphy->dev, &rphy->clk480m_hw); - if (IS_ERR(rphy->clk480m)) { - ret = PTR_ERR(rphy->clk480m); - goto err_ret; - } - - ret = of_clk_add_provider(node, of_clk_src_simple_get, rphy->clk480m); - if (ret < 0) - goto err_clk_provider; - - ret = devm_add_action(rphy->dev, rockchip_usb2phy_clk480m_unregister, - rphy); - if (ret < 0) - goto err_unreg_action; - - return 0; - -err_unreg_action: - of_clk_del_provider(node); -err_clk_provider: - clk_unregister(rphy->clk480m); -err_ret: - return ret; -} - -static int rockchip_usb2phy_extcon_register(struct rockchip_usb2phy *rphy) -{ - int ret; - struct device_node *node = rphy->dev->of_node; - struct extcon_dev *edev; - - if (of_property_read_bool(node, "extcon")) { - edev = extcon_get_edev_by_phandle(rphy->dev, 0); - if (IS_ERR(edev)) { - if (PTR_ERR(edev) != -EPROBE_DEFER) - dev_err(rphy->dev, "Invalid or missing extcon\n"); - return PTR_ERR(edev); - } - } else { - /* Initialize extcon device */ - edev = devm_extcon_dev_allocate(rphy->dev, - rockchip_usb2phy_extcon_cable); - - if (IS_ERR(edev)) - return -ENOMEM; - - ret = devm_extcon_dev_register(rphy->dev, edev); - if (ret) { - dev_err(rphy->dev, "failed to register extcon device\n"); - return ret; - } - } - - rphy->edev = edev; - - return 0; -} - -static int rockchip_usb2phy_init(struct phy *phy) -{ - struct rockchip_usb2phy_port *rport = phy_get_drvdata(phy); - struct rockchip_usb2phy *rphy = dev_get_drvdata(phy->dev.parent); - int ret = 0; - - mutex_lock(&rport->mutex); - - if (rport->port_id == USB2PHY_PORT_OTG) { - if (rport->mode != USB_DR_MODE_HOST) { - /* clear bvalid status and enable bvalid detect irq */ - ret = property_enable(rphy, - &rport->port_cfg->bvalid_det_clr, - true); - if (ret) - goto out; - - ret = property_enable(rphy, - &rport->port_cfg->bvalid_det_en, - true); - if (ret) - goto out; - - schedule_delayed_work(&rport->otg_sm_work, - OTG_SCHEDULE_DELAY); - } else { - /* If OTG works in host only mode, do nothing. */ - dev_dbg(&rport->phy->dev, "mode %d\n", rport->mode); - } - } else if (rport->port_id == USB2PHY_PORT_HOST) { - /* clear linestate and enable linestate detect irq */ - ret = property_enable(rphy, &rport->port_cfg->ls_det_clr, true); - if (ret) - goto out; - - ret = property_enable(rphy, &rport->port_cfg->ls_det_en, true); - if (ret) - goto out; - - schedule_delayed_work(&rport->sm_work, SCHEDULE_DELAY); - } - -out: - mutex_unlock(&rport->mutex); - return ret; -} - -static int rockchip_usb2phy_power_on(struct phy *phy) -{ - struct rockchip_usb2phy_port *rport = phy_get_drvdata(phy); - struct rockchip_usb2phy *rphy = dev_get_drvdata(phy->dev.parent); - int ret; - - dev_dbg(&rport->phy->dev, "port power on\n"); - - if (!rport->suspended) - return 0; - - ret = clk_prepare_enable(rphy->clk480m); - if (ret) - return ret; - - ret = property_enable(rphy, &rport->port_cfg->phy_sus, false); - if (ret) - return ret; - - rport->suspended = false; - return 0; -} - -static int rockchip_usb2phy_power_off(struct phy *phy) -{ - struct rockchip_usb2phy_port *rport = phy_get_drvdata(phy); - struct rockchip_usb2phy *rphy = dev_get_drvdata(phy->dev.parent); - int ret; - - dev_dbg(&rport->phy->dev, "port power off\n"); - - if (rport->suspended) - return 0; - - ret = property_enable(rphy, &rport->port_cfg->phy_sus, true); - if (ret) - return ret; - - rport->suspended = true; - clk_disable_unprepare(rphy->clk480m); - - return 0; -} - -static int rockchip_usb2phy_exit(struct phy *phy) -{ - struct rockchip_usb2phy_port *rport = phy_get_drvdata(phy); - - if (rport->port_id == USB2PHY_PORT_OTG && - rport->mode != USB_DR_MODE_HOST) { - cancel_delayed_work_sync(&rport->otg_sm_work); - cancel_delayed_work_sync(&rport->chg_work); - } else if (rport->port_id == USB2PHY_PORT_HOST) - cancel_delayed_work_sync(&rport->sm_work); - - return 0; -} - -static const struct phy_ops rockchip_usb2phy_ops = { - .init = rockchip_usb2phy_init, - .exit = rockchip_usb2phy_exit, - .power_on = rockchip_usb2phy_power_on, - .power_off = rockchip_usb2phy_power_off, - .owner = THIS_MODULE, -}; - -static void rockchip_usb2phy_otg_sm_work(struct work_struct *work) -{ - struct rockchip_usb2phy_port *rport = - container_of(work, struct rockchip_usb2phy_port, - otg_sm_work.work); - struct rockchip_usb2phy *rphy = dev_get_drvdata(rport->phy->dev.parent); - static unsigned int cable; - unsigned long delay; - bool vbus_attach, sch_work, notify_charger; - - if (rport->utmi_avalid) - vbus_attach = - property_enabled(rphy, &rport->port_cfg->utmi_avalid); - else - vbus_attach = - property_enabled(rphy, &rport->port_cfg->utmi_bvalid); - - sch_work = false; - notify_charger = false; - delay = OTG_SCHEDULE_DELAY; - dev_dbg(&rport->phy->dev, "%s otg sm work\n", - usb_otg_state_string(rport->state)); - - switch (rport->state) { - case OTG_STATE_UNDEFINED: - rport->state = OTG_STATE_B_IDLE; - if (!vbus_attach) - rockchip_usb2phy_power_off(rport->phy); - /* fall through */ - case OTG_STATE_B_IDLE: - if (extcon_get_cable_state_(rphy->edev, EXTCON_USB_HOST) > 0) { - dev_dbg(&rport->phy->dev, "usb otg host connect\n"); - rport->state = OTG_STATE_A_HOST; - rockchip_usb2phy_power_on(rport->phy); - return; - } else if (vbus_attach) { - dev_dbg(&rport->phy->dev, "vbus_attach\n"); - switch (rphy->chg_state) { - case USB_CHG_STATE_UNDEFINED: - schedule_delayed_work(&rport->chg_work, 0); - return; - case USB_CHG_STATE_DETECTED: - switch (rphy->chg_type) { - case POWER_SUPPLY_TYPE_USB: - dev_dbg(&rport->phy->dev, "sdp cable is connected\n"); - rockchip_usb2phy_power_on(rport->phy); - rport->state = OTG_STATE_B_PERIPHERAL; - notify_charger = true; - sch_work = true; - cable = EXTCON_CHG_USB_SDP; - break; - case POWER_SUPPLY_TYPE_USB_DCP: - dev_dbg(&rport->phy->dev, "dcp cable is connected\n"); - rockchip_usb2phy_power_off(rport->phy); - notify_charger = true; - sch_work = true; - cable = EXTCON_CHG_USB_DCP; - break; - case POWER_SUPPLY_TYPE_USB_CDP: - dev_dbg(&rport->phy->dev, "cdp cable is connected\n"); - rockchip_usb2phy_power_on(rport->phy); - rport->state = OTG_STATE_B_PERIPHERAL; - notify_charger = true; - sch_work = true; - cable = EXTCON_CHG_USB_CDP; - break; - default: - break; - } - break; - default: - break; - } - } else { - notify_charger = true; - rphy->chg_state = USB_CHG_STATE_UNDEFINED; - rphy->chg_type = POWER_SUPPLY_TYPE_UNKNOWN; - } - - if (rport->vbus_attached != vbus_attach) { - rport->vbus_attached = vbus_attach; - - if (notify_charger && rphy->edev) { - extcon_set_cable_state_(rphy->edev, - cable, vbus_attach); - if (cable == EXTCON_CHG_USB_SDP) - extcon_set_state_sync(rphy->edev, - EXTCON_USB, - vbus_attach); - } - } - break; - case OTG_STATE_B_PERIPHERAL: - if (!vbus_attach) { - dev_dbg(&rport->phy->dev, "usb disconnect\n"); - rphy->chg_state = USB_CHG_STATE_UNDEFINED; - rphy->chg_type = POWER_SUPPLY_TYPE_UNKNOWN; - rport->state = OTG_STATE_B_IDLE; - delay = 0; - rockchip_usb2phy_power_off(rport->phy); - } - sch_work = true; - break; - case OTG_STATE_A_HOST: - if (extcon_get_cable_state_(rphy->edev, EXTCON_USB_HOST) == 0) { - dev_dbg(&rport->phy->dev, "usb otg host disconnect\n"); - rport->state = OTG_STATE_B_IDLE; - rockchip_usb2phy_power_off(rport->phy); - } - break; - default: - break; - } - - if (sch_work) - schedule_delayed_work(&rport->otg_sm_work, delay); -} - -static const char *chg_to_string(enum power_supply_type chg_type) -{ - switch (chg_type) { - case POWER_SUPPLY_TYPE_USB: - return "USB_SDP_CHARGER"; - case POWER_SUPPLY_TYPE_USB_DCP: - return "USB_DCP_CHARGER"; - case POWER_SUPPLY_TYPE_USB_CDP: - return "USB_CDP_CHARGER"; - default: - return "INVALID_CHARGER"; - } -} - -static void rockchip_chg_enable_dcd(struct rockchip_usb2phy *rphy, - bool en) -{ - property_enable(rphy, &rphy->phy_cfg->chg_det.rdm_pdwn_en, en); - property_enable(rphy, &rphy->phy_cfg->chg_det.idp_src_en, en); -} - -static void rockchip_chg_enable_primary_det(struct rockchip_usb2phy *rphy, - bool en) -{ - property_enable(rphy, &rphy->phy_cfg->chg_det.vdp_src_en, en); - property_enable(rphy, &rphy->phy_cfg->chg_det.idm_sink_en, en); -} - -static void rockchip_chg_enable_secondary_det(struct rockchip_usb2phy *rphy, - bool en) -{ - property_enable(rphy, &rphy->phy_cfg->chg_det.vdm_src_en, en); - property_enable(rphy, &rphy->phy_cfg->chg_det.idp_sink_en, en); -} - -#define CHG_DCD_POLL_TIME (100 * HZ / 1000) -#define CHG_DCD_MAX_RETRIES 6 -#define CHG_PRIMARY_DET_TIME (40 * HZ / 1000) -#define CHG_SECONDARY_DET_TIME (40 * HZ / 1000) -static void rockchip_chg_detect_work(struct work_struct *work) -{ - struct rockchip_usb2phy_port *rport = - container_of(work, struct rockchip_usb2phy_port, chg_work.work); - struct rockchip_usb2phy *rphy = dev_get_drvdata(rport->phy->dev.parent); - bool is_dcd, tmout, vout; - unsigned long delay; - - dev_dbg(&rport->phy->dev, "chg detection work state = %d\n", - rphy->chg_state); - switch (rphy->chg_state) { - case USB_CHG_STATE_UNDEFINED: - if (!rport->suspended) - rockchip_usb2phy_power_off(rport->phy); - /* put the controller in non-driving mode */ - property_enable(rphy, &rphy->phy_cfg->chg_det.opmode, false); - /* Start DCD processing stage 1 */ - rockchip_chg_enable_dcd(rphy, true); - rphy->chg_state = USB_CHG_STATE_WAIT_FOR_DCD; - rphy->dcd_retries = 0; - delay = CHG_DCD_POLL_TIME; - break; - case USB_CHG_STATE_WAIT_FOR_DCD: - /* get data contact detection status */ - is_dcd = property_enabled(rphy, &rphy->phy_cfg->chg_det.dp_det); - tmout = ++rphy->dcd_retries == CHG_DCD_MAX_RETRIES; - /* stage 2 */ - if (is_dcd || tmout) { - /* stage 4 */ - /* Turn off DCD circuitry */ - rockchip_chg_enable_dcd(rphy, false); - /* Voltage Source on DP, Probe on DM */ - rockchip_chg_enable_primary_det(rphy, true); - delay = CHG_PRIMARY_DET_TIME; - rphy->chg_state = USB_CHG_STATE_DCD_DONE; - } else { - /* stage 3 */ - delay = CHG_DCD_POLL_TIME; - } - break; - case USB_CHG_STATE_DCD_DONE: - vout = property_enabled(rphy, &rphy->phy_cfg->chg_det.cp_det); - rockchip_chg_enable_primary_det(rphy, false); - if (vout) { - /* Voltage Source on DM, Probe on DP */ - rockchip_chg_enable_secondary_det(rphy, true); - delay = CHG_SECONDARY_DET_TIME; - rphy->chg_state = USB_CHG_STATE_PRIMARY_DONE; - } else { - if (rphy->dcd_retries == CHG_DCD_MAX_RETRIES) { - /* floating charger found */ - rphy->chg_type = POWER_SUPPLY_TYPE_USB_DCP; - rphy->chg_state = USB_CHG_STATE_DETECTED; - delay = 0; - } else { - rphy->chg_type = POWER_SUPPLY_TYPE_USB; - rphy->chg_state = USB_CHG_STATE_DETECTED; - delay = 0; - } - } - break; - case USB_CHG_STATE_PRIMARY_DONE: - vout = property_enabled(rphy, &rphy->phy_cfg->chg_det.dcp_det); - /* Turn off voltage source */ - rockchip_chg_enable_secondary_det(rphy, false); - if (vout) - rphy->chg_type = POWER_SUPPLY_TYPE_USB_DCP; - else - rphy->chg_type = POWER_SUPPLY_TYPE_USB_CDP; - /* fall through */ - case USB_CHG_STATE_SECONDARY_DONE: - rphy->chg_state = USB_CHG_STATE_DETECTED; - delay = 0; - /* fall through */ - case USB_CHG_STATE_DETECTED: - /* put the controller in normal mode */ - property_enable(rphy, &rphy->phy_cfg->chg_det.opmode, true); - rockchip_usb2phy_otg_sm_work(&rport->otg_sm_work.work); - dev_info(&rport->phy->dev, "charger = %s\n", - chg_to_string(rphy->chg_type)); - return; - default: - return; - } - - schedule_delayed_work(&rport->chg_work, delay); -} - -/* - * The function manage host-phy port state and suspend/resume phy port - * to save power. - * - * we rely on utmi_linestate and utmi_hostdisconnect to identify whether - * devices is disconnect or not. Besides, we do not need care it is FS/LS - * disconnected or HS disconnected, actually, we just only need get the - * device is disconnected at last through rearm the delayed work, - * to suspend the phy port in _PHY_STATE_DISCONNECT_ case. - * - * NOTE: It may invoke *phy_powr_off or *phy_power_on which will invoke - * some clk related APIs, so do not invoke it from interrupt context directly. - */ -static void rockchip_usb2phy_sm_work(struct work_struct *work) -{ - struct rockchip_usb2phy_port *rport = - container_of(work, struct rockchip_usb2phy_port, sm_work.work); - struct rockchip_usb2phy *rphy = dev_get_drvdata(rport->phy->dev.parent); - unsigned int sh = rport->port_cfg->utmi_hstdet.bitend - - rport->port_cfg->utmi_hstdet.bitstart + 1; - unsigned int ul, uhd, state; - unsigned int ul_mask, uhd_mask; - int ret; - - mutex_lock(&rport->mutex); - - ret = regmap_read(rphy->grf, rport->port_cfg->utmi_ls.offset, &ul); - if (ret < 0) - goto next_schedule; - - ret = regmap_read(rphy->grf, rport->port_cfg->utmi_hstdet.offset, - &uhd); - if (ret < 0) - goto next_schedule; - - uhd_mask = GENMASK(rport->port_cfg->utmi_hstdet.bitend, - rport->port_cfg->utmi_hstdet.bitstart); - ul_mask = GENMASK(rport->port_cfg->utmi_ls.bitend, - rport->port_cfg->utmi_ls.bitstart); - - /* stitch on utmi_ls and utmi_hstdet as phy state */ - state = ((uhd & uhd_mask) >> rport->port_cfg->utmi_hstdet.bitstart) | - (((ul & ul_mask) >> rport->port_cfg->utmi_ls.bitstart) << sh); - - switch (state) { - case PHY_STATE_HS_ONLINE: - dev_dbg(&rport->phy->dev, "HS online\n"); - break; - case PHY_STATE_FS_LS_ONLINE: - /* - * For FS/LS device, the online state share with connect state - * from utmi_ls and utmi_hstdet register, so we distinguish - * them via suspended flag. - * - * Plus, there are two cases, one is D- Line pull-up, and D+ - * line pull-down, the state is 4; another is D+ line pull-up, - * and D- line pull-down, the state is 2. - */ - if (!rport->suspended) { - /* D- line pull-up, D+ line pull-down */ - dev_dbg(&rport->phy->dev, "FS/LS online\n"); - break; - } - /* fall through */ - case PHY_STATE_CONNECT: - if (rport->suspended) { - dev_dbg(&rport->phy->dev, "Connected\n"); - rockchip_usb2phy_power_on(rport->phy); - rport->suspended = false; - } else { - /* D+ line pull-up, D- line pull-down */ - dev_dbg(&rport->phy->dev, "FS/LS online\n"); - } - break; - case PHY_STATE_DISCONNECT: - if (!rport->suspended) { - dev_dbg(&rport->phy->dev, "Disconnected\n"); - rockchip_usb2phy_power_off(rport->phy); - rport->suspended = true; - } - - /* - * activate the linestate detection to get the next device - * plug-in irq. - */ - property_enable(rphy, &rport->port_cfg->ls_det_clr, true); - property_enable(rphy, &rport->port_cfg->ls_det_en, true); - - /* - * we don't need to rearm the delayed work when the phy port - * is suspended. - */ - mutex_unlock(&rport->mutex); - return; - default: - dev_dbg(&rport->phy->dev, "unknown phy state\n"); - break; - } - -next_schedule: - mutex_unlock(&rport->mutex); - schedule_delayed_work(&rport->sm_work, SCHEDULE_DELAY); -} - -static irqreturn_t rockchip_usb2phy_linestate_irq(int irq, void *data) -{ - struct rockchip_usb2phy_port *rport = data; - struct rockchip_usb2phy *rphy = dev_get_drvdata(rport->phy->dev.parent); - - if (!property_enabled(rphy, &rport->port_cfg->ls_det_st)) - return IRQ_NONE; - - mutex_lock(&rport->mutex); - - /* disable linestate detect irq and clear its status */ - property_enable(rphy, &rport->port_cfg->ls_det_en, false); - property_enable(rphy, &rport->port_cfg->ls_det_clr, true); - - mutex_unlock(&rport->mutex); - - /* - * In this case for host phy port, a new device is plugged in, - * meanwhile, if the phy port is suspended, we need rearm the work to - * resume it and mange its states; otherwise, we do nothing about that. - */ - if (rport->suspended && rport->port_id == USB2PHY_PORT_HOST) - rockchip_usb2phy_sm_work(&rport->sm_work.work); - - return IRQ_HANDLED; -} - -static irqreturn_t rockchip_usb2phy_bvalid_irq(int irq, void *data) -{ - struct rockchip_usb2phy_port *rport = data; - struct rockchip_usb2phy *rphy = dev_get_drvdata(rport->phy->dev.parent); - - if (!property_enabled(rphy, &rport->port_cfg->bvalid_det_st)) - return IRQ_NONE; - - mutex_lock(&rport->mutex); - - /* clear bvalid detect irq pending status */ - property_enable(rphy, &rport->port_cfg->bvalid_det_clr, true); - - mutex_unlock(&rport->mutex); - - rockchip_usb2phy_otg_sm_work(&rport->otg_sm_work.work); - - return IRQ_HANDLED; -} - -static int rockchip_usb2phy_host_port_init(struct rockchip_usb2phy *rphy, - struct rockchip_usb2phy_port *rport, - struct device_node *child_np) -{ - int ret; - - rport->port_id = USB2PHY_PORT_HOST; - rport->port_cfg = &rphy->phy_cfg->port_cfgs[USB2PHY_PORT_HOST]; - rport->suspended = true; - - mutex_init(&rport->mutex); - INIT_DELAYED_WORK(&rport->sm_work, rockchip_usb2phy_sm_work); - - rport->ls_irq = of_irq_get_byname(child_np, "linestate"); - if (rport->ls_irq < 0) { - dev_err(rphy->dev, "no linestate irq provided\n"); - return rport->ls_irq; - } - - ret = devm_request_threaded_irq(rphy->dev, rport->ls_irq, NULL, - rockchip_usb2phy_linestate_irq, - IRQF_ONESHOT, - "rockchip_usb2phy", rport); - if (ret) { - dev_err(rphy->dev, "failed to request linestate irq handle\n"); - return ret; - } - - return 0; -} - -static int rockchip_otg_event(struct notifier_block *nb, - unsigned long event, void *ptr) -{ - struct rockchip_usb2phy_port *rport = - container_of(nb, struct rockchip_usb2phy_port, event_nb); - - schedule_delayed_work(&rport->otg_sm_work, OTG_SCHEDULE_DELAY); - - return NOTIFY_DONE; -} - -static int rockchip_usb2phy_otg_port_init(struct rockchip_usb2phy *rphy, - struct rockchip_usb2phy_port *rport, - struct device_node *child_np) -{ - int ret; - - rport->port_id = USB2PHY_PORT_OTG; - rport->port_cfg = &rphy->phy_cfg->port_cfgs[USB2PHY_PORT_OTG]; - rport->state = OTG_STATE_UNDEFINED; - - /* - * set suspended flag to true, but actually don't - * put phy in suspend mode, it aims to enable usb - * phy and clock in power_on() called by usb controller - * driver during probe. - */ - rport->suspended = true; - rport->vbus_attached = false; - - mutex_init(&rport->mutex); - - rport->mode = of_usb_get_dr_mode_by_phy(child_np, -1); - if (rport->mode == USB_DR_MODE_HOST) { - ret = 0; - goto out; - } - - INIT_DELAYED_WORK(&rport->chg_work, rockchip_chg_detect_work); - INIT_DELAYED_WORK(&rport->otg_sm_work, rockchip_usb2phy_otg_sm_work); - - rport->utmi_avalid = - of_property_read_bool(child_np, "rockchip,utmi-avalid"); - - rport->bvalid_irq = of_irq_get_byname(child_np, "otg-bvalid"); - if (rport->bvalid_irq < 0) { - dev_err(rphy->dev, "no vbus valid irq provided\n"); - ret = rport->bvalid_irq; - goto out; - } - - ret = devm_request_threaded_irq(rphy->dev, rport->bvalid_irq, NULL, - rockchip_usb2phy_bvalid_irq, - IRQF_ONESHOT, - "rockchip_usb2phy_bvalid", rport); - if (ret) { - dev_err(rphy->dev, "failed to request otg-bvalid irq handle\n"); - goto out; - } - - if (!IS_ERR(rphy->edev)) { - rport->event_nb.notifier_call = rockchip_otg_event; - - ret = extcon_register_notifier(rphy->edev, EXTCON_USB_HOST, - &rport->event_nb); - if (ret) - dev_err(rphy->dev, "register USB HOST notifier failed\n"); - } - -out: - return ret; -} - -static int rockchip_usb2phy_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct device_node *np = dev->of_node; - struct device_node *child_np; - struct phy_provider *provider; - struct rockchip_usb2phy *rphy; - const struct rockchip_usb2phy_cfg *phy_cfgs; - const struct of_device_id *match; - unsigned int reg; - int index, ret; - - rphy = devm_kzalloc(dev, sizeof(*rphy), GFP_KERNEL); - if (!rphy) - return -ENOMEM; - - match = of_match_device(dev->driver->of_match_table, dev); - if (!match || !match->data) { - dev_err(dev, "phy configs are not assigned!\n"); - return -EINVAL; - } - - if (!dev->parent || !dev->parent->of_node) - return -EINVAL; - - rphy->grf = syscon_node_to_regmap(dev->parent->of_node); - if (IS_ERR(rphy->grf)) - return PTR_ERR(rphy->grf); - - if (of_property_read_u32(np, "reg", ®)) { - dev_err(dev, "the reg property is not assigned in %s node\n", - np->name); - return -EINVAL; - } - - rphy->dev = dev; - phy_cfgs = match->data; - rphy->chg_state = USB_CHG_STATE_UNDEFINED; - rphy->chg_type = POWER_SUPPLY_TYPE_UNKNOWN; - platform_set_drvdata(pdev, rphy); - - ret = rockchip_usb2phy_extcon_register(rphy); - if (ret) - return ret; - - /* find out a proper config which can be matched with dt. */ - index = 0; - while (phy_cfgs[index].reg) { - if (phy_cfgs[index].reg == reg) { - rphy->phy_cfg = &phy_cfgs[index]; - break; - } - - ++index; - } - - if (!rphy->phy_cfg) { - dev_err(dev, "no phy-config can be matched with %s node\n", - np->name); - return -EINVAL; - } - - rphy->clk = of_clk_get_by_name(np, "phyclk"); - if (!IS_ERR(rphy->clk)) { - clk_prepare_enable(rphy->clk); - } else { - dev_info(&pdev->dev, "no phyclk specified\n"); - rphy->clk = NULL; - } - - ret = rockchip_usb2phy_clk480m_register(rphy); - if (ret) { - dev_err(dev, "failed to register 480m output clock\n"); - goto disable_clks; - } - - index = 0; - for_each_available_child_of_node(np, child_np) { - struct rockchip_usb2phy_port *rport = &rphy->ports[index]; - struct phy *phy; - - /* This driver aims to support both otg-port and host-port */ - if (of_node_cmp(child_np->name, "host-port") && - of_node_cmp(child_np->name, "otg-port")) - goto next_child; - - phy = devm_phy_create(dev, child_np, &rockchip_usb2phy_ops); - if (IS_ERR(phy)) { - dev_err(dev, "failed to create phy\n"); - ret = PTR_ERR(phy); - goto put_child; - } - - rport->phy = phy; - phy_set_drvdata(rport->phy, rport); - - /* initialize otg/host port separately */ - if (!of_node_cmp(child_np->name, "host-port")) { - ret = rockchip_usb2phy_host_port_init(rphy, rport, - child_np); - if (ret) - goto put_child; - } else { - ret = rockchip_usb2phy_otg_port_init(rphy, rport, - child_np); - if (ret) - goto put_child; - } - -next_child: - /* to prevent out of boundary */ - if (++index >= rphy->phy_cfg->num_ports) - break; - } - - provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); - return PTR_ERR_OR_ZERO(provider); - -put_child: - of_node_put(child_np); -disable_clks: - if (rphy->clk) { - clk_disable_unprepare(rphy->clk); - clk_put(rphy->clk); - } - return ret; -} - -static const struct rockchip_usb2phy_cfg rk3328_phy_cfgs[] = { - { - .reg = 0x100, - .num_ports = 2, - .clkout_ctl = { 0x108, 4, 4, 1, 0 }, - .port_cfgs = { - [USB2PHY_PORT_OTG] = { - .phy_sus = { 0x0100, 15, 0, 0, 0x1d1 }, - .bvalid_det_en = { 0x0110, 2, 2, 0, 1 }, - .bvalid_det_st = { 0x0114, 2, 2, 0, 1 }, - .bvalid_det_clr = { 0x0118, 2, 2, 0, 1 }, - .ls_det_en = { 0x0110, 0, 0, 0, 1 }, - .ls_det_st = { 0x0114, 0, 0, 0, 1 }, - .ls_det_clr = { 0x0118, 0, 0, 0, 1 }, - .utmi_avalid = { 0x0120, 10, 10, 0, 1 }, - .utmi_bvalid = { 0x0120, 9, 9, 0, 1 }, - .utmi_ls = { 0x0120, 5, 4, 0, 1 }, - }, - [USB2PHY_PORT_HOST] = { - .phy_sus = { 0x104, 15, 0, 0, 0x1d1 }, - .ls_det_en = { 0x110, 1, 1, 0, 1 }, - .ls_det_st = { 0x114, 1, 1, 0, 1 }, - .ls_det_clr = { 0x118, 1, 1, 0, 1 }, - .utmi_ls = { 0x120, 17, 16, 0, 1 }, - .utmi_hstdet = { 0x120, 19, 19, 0, 1 } - } - }, - .chg_det = { - .opmode = { 0x0100, 3, 0, 5, 1 }, - .cp_det = { 0x0120, 24, 24, 0, 1 }, - .dcp_det = { 0x0120, 23, 23, 0, 1 }, - .dp_det = { 0x0120, 25, 25, 0, 1 }, - .idm_sink_en = { 0x0108, 8, 8, 0, 1 }, - .idp_sink_en = { 0x0108, 7, 7, 0, 1 }, - .idp_src_en = { 0x0108, 9, 9, 0, 1 }, - .rdm_pdwn_en = { 0x0108, 10, 10, 0, 1 }, - .vdm_src_en = { 0x0108, 12, 12, 0, 1 }, - .vdp_src_en = { 0x0108, 11, 11, 0, 1 }, - }, - }, - { /* sentinel */ } -}; - -static const struct rockchip_usb2phy_cfg rk3366_phy_cfgs[] = { - { - .reg = 0x700, - .num_ports = 2, - .clkout_ctl = { 0x0724, 15, 15, 1, 0 }, - .port_cfgs = { - [USB2PHY_PORT_HOST] = { - .phy_sus = { 0x0728, 15, 0, 0, 0x1d1 }, - .ls_det_en = { 0x0680, 4, 4, 0, 1 }, - .ls_det_st = { 0x0690, 4, 4, 0, 1 }, - .ls_det_clr = { 0x06a0, 4, 4, 0, 1 }, - .utmi_ls = { 0x049c, 14, 13, 0, 1 }, - .utmi_hstdet = { 0x049c, 12, 12, 0, 1 } - } - }, - }, - { /* sentinel */ } -}; - -static const struct rockchip_usb2phy_cfg rk3399_phy_cfgs[] = { - { - .reg = 0xe450, - .num_ports = 2, - .clkout_ctl = { 0xe450, 4, 4, 1, 0 }, - .port_cfgs = { - [USB2PHY_PORT_OTG] = { - .phy_sus = { 0xe454, 1, 0, 2, 1 }, - .bvalid_det_en = { 0xe3c0, 3, 3, 0, 1 }, - .bvalid_det_st = { 0xe3e0, 3, 3, 0, 1 }, - .bvalid_det_clr = { 0xe3d0, 3, 3, 0, 1 }, - .utmi_avalid = { 0xe2ac, 7, 7, 0, 1 }, - .utmi_bvalid = { 0xe2ac, 12, 12, 0, 1 }, - }, - [USB2PHY_PORT_HOST] = { - .phy_sus = { 0xe458, 1, 0, 0x2, 0x1 }, - .ls_det_en = { 0xe3c0, 6, 6, 0, 1 }, - .ls_det_st = { 0xe3e0, 6, 6, 0, 1 }, - .ls_det_clr = { 0xe3d0, 6, 6, 0, 1 }, - .utmi_ls = { 0xe2ac, 22, 21, 0, 1 }, - .utmi_hstdet = { 0xe2ac, 23, 23, 0, 1 } - } - }, - .chg_det = { - .opmode = { 0xe454, 3, 0, 5, 1 }, - .cp_det = { 0xe2ac, 2, 2, 0, 1 }, - .dcp_det = { 0xe2ac, 1, 1, 0, 1 }, - .dp_det = { 0xe2ac, 0, 0, 0, 1 }, - .idm_sink_en = { 0xe450, 8, 8, 0, 1 }, - .idp_sink_en = { 0xe450, 7, 7, 0, 1 }, - .idp_src_en = { 0xe450, 9, 9, 0, 1 }, - .rdm_pdwn_en = { 0xe450, 10, 10, 0, 1 }, - .vdm_src_en = { 0xe450, 12, 12, 0, 1 }, - .vdp_src_en = { 0xe450, 11, 11, 0, 1 }, - }, - }, - { - .reg = 0xe460, - .num_ports = 2, - .clkout_ctl = { 0xe460, 4, 4, 1, 0 }, - .port_cfgs = { - [USB2PHY_PORT_OTG] = { - .phy_sus = { 0xe464, 1, 0, 2, 1 }, - .bvalid_det_en = { 0xe3c0, 8, 8, 0, 1 }, - .bvalid_det_st = { 0xe3e0, 8, 8, 0, 1 }, - .bvalid_det_clr = { 0xe3d0, 8, 8, 0, 1 }, - .utmi_avalid = { 0xe2ac, 10, 10, 0, 1 }, - .utmi_bvalid = { 0xe2ac, 16, 16, 0, 1 }, - }, - [USB2PHY_PORT_HOST] = { - .phy_sus = { 0xe468, 1, 0, 0x2, 0x1 }, - .ls_det_en = { 0xe3c0, 11, 11, 0, 1 }, - .ls_det_st = { 0xe3e0, 11, 11, 0, 1 }, - .ls_det_clr = { 0xe3d0, 11, 11, 0, 1 }, - .utmi_ls = { 0xe2ac, 26, 25, 0, 1 }, - .utmi_hstdet = { 0xe2ac, 27, 27, 0, 1 } - } - }, - }, - { /* sentinel */ } -}; - -static const struct of_device_id rockchip_usb2phy_dt_match[] = { - { .compatible = "rockchip,rk3328-usb2phy", .data = &rk3328_phy_cfgs }, - { .compatible = "rockchip,rk3366-usb2phy", .data = &rk3366_phy_cfgs }, - { .compatible = "rockchip,rk3399-usb2phy", .data = &rk3399_phy_cfgs }, - {} -}; -MODULE_DEVICE_TABLE(of, rockchip_usb2phy_dt_match); - -static struct platform_driver rockchip_usb2phy_driver = { - .probe = rockchip_usb2phy_probe, - .driver = { - .name = "rockchip-usb2phy", - .of_match_table = rockchip_usb2phy_dt_match, - }, -}; -module_platform_driver(rockchip_usb2phy_driver); - -MODULE_AUTHOR("Frank Wang "); -MODULE_DESCRIPTION("Rockchip USB2.0 PHY driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-rockchip-pcie.c b/drivers/phy/phy-rockchip-pcie.c deleted file mode 100644 index 6904633cad68..000000000000 --- a/drivers/phy/phy-rockchip-pcie.c +++ /dev/null @@ -1,346 +0,0 @@ -/* - * Rockchip PCIe PHY driver - * - * Copyright (C) 2016 Shawn Lin - * Copyright (C) 2016 ROCKCHIP, Inc. - * - * 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; either 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. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* - * The higher 16-bit of this register is used for write protection - * only if BIT(x + 16) set to 1 the BIT(x) can be written. - */ -#define HIWORD_UPDATE(val, mask, shift) \ - ((val) << (shift) | (mask) << ((shift) + 16)) - -#define PHY_MAX_LANE_NUM 4 -#define PHY_CFG_DATA_SHIFT 7 -#define PHY_CFG_ADDR_SHIFT 1 -#define PHY_CFG_DATA_MASK 0xf -#define PHY_CFG_ADDR_MASK 0x3f -#define PHY_CFG_RD_MASK 0x3ff -#define PHY_CFG_WR_ENABLE 1 -#define PHY_CFG_WR_DISABLE 1 -#define PHY_CFG_WR_SHIFT 0 -#define PHY_CFG_WR_MASK 1 -#define PHY_CFG_PLL_LOCK 0x10 -#define PHY_CFG_CLK_TEST 0x10 -#define PHY_CFG_CLK_SCC 0x12 -#define PHY_CFG_SEPE_RATE BIT(3) -#define PHY_CFG_PLL_100M BIT(3) -#define PHY_PLL_LOCKED BIT(9) -#define PHY_PLL_OUTPUT BIT(10) -#define PHY_LANE_A_STATUS 0x30 -#define PHY_LANE_B_STATUS 0x31 -#define PHY_LANE_C_STATUS 0x32 -#define PHY_LANE_D_STATUS 0x33 -#define PHY_LANE_RX_DET_SHIFT 11 -#define PHY_LANE_RX_DET_TH 0x1 -#define PHY_LANE_IDLE_OFF 0x1 -#define PHY_LANE_IDLE_MASK 0x1 -#define PHY_LANE_IDLE_A_SHIFT 3 -#define PHY_LANE_IDLE_B_SHIFT 4 -#define PHY_LANE_IDLE_C_SHIFT 5 -#define PHY_LANE_IDLE_D_SHIFT 6 - -struct rockchip_pcie_data { - unsigned int pcie_conf; - unsigned int pcie_status; - unsigned int pcie_laneoff; -}; - -struct rockchip_pcie_phy { - struct rockchip_pcie_data *phy_data; - struct regmap *reg_base; - struct reset_control *phy_rst; - struct clk *clk_pciephy_ref; -}; - -static inline void phy_wr_cfg(struct rockchip_pcie_phy *rk_phy, - u32 addr, u32 data) -{ - regmap_write(rk_phy->reg_base, rk_phy->phy_data->pcie_conf, - HIWORD_UPDATE(data, - PHY_CFG_DATA_MASK, - PHY_CFG_DATA_SHIFT) | - HIWORD_UPDATE(addr, - PHY_CFG_ADDR_MASK, - PHY_CFG_ADDR_SHIFT)); - udelay(1); - regmap_write(rk_phy->reg_base, rk_phy->phy_data->pcie_conf, - HIWORD_UPDATE(PHY_CFG_WR_ENABLE, - PHY_CFG_WR_MASK, - PHY_CFG_WR_SHIFT)); - udelay(1); - regmap_write(rk_phy->reg_base, rk_phy->phy_data->pcie_conf, - HIWORD_UPDATE(PHY_CFG_WR_DISABLE, - PHY_CFG_WR_MASK, - PHY_CFG_WR_SHIFT)); -} - -static inline u32 phy_rd_cfg(struct rockchip_pcie_phy *rk_phy, - u32 addr) -{ - u32 val; - - regmap_write(rk_phy->reg_base, rk_phy->phy_data->pcie_conf, - HIWORD_UPDATE(addr, - PHY_CFG_RD_MASK, - PHY_CFG_ADDR_SHIFT)); - regmap_read(rk_phy->reg_base, - rk_phy->phy_data->pcie_status, - &val); - return val; -} - -static int rockchip_pcie_phy_power_off(struct phy *phy) -{ - struct rockchip_pcie_phy *rk_phy = phy_get_drvdata(phy); - int err = 0; - - err = reset_control_assert(rk_phy->phy_rst); - if (err) { - dev_err(&phy->dev, "assert phy_rst err %d\n", err); - return err; - } - - return 0; -} - -static int rockchip_pcie_phy_power_on(struct phy *phy) -{ - struct rockchip_pcie_phy *rk_phy = phy_get_drvdata(phy); - int err = 0; - u32 status; - unsigned long timeout; - - err = reset_control_deassert(rk_phy->phy_rst); - if (err) { - dev_err(&phy->dev, "deassert phy_rst err %d\n", err); - return err; - } - - regmap_write(rk_phy->reg_base, rk_phy->phy_data->pcie_conf, - HIWORD_UPDATE(PHY_CFG_PLL_LOCK, - PHY_CFG_ADDR_MASK, - PHY_CFG_ADDR_SHIFT)); - - /* - * No documented timeout value for phy operation below, - * so we make it large enough here. And we use loop-break - * method which should not be harmful. - */ - timeout = jiffies + msecs_to_jiffies(1000); - - err = -EINVAL; - while (time_before(jiffies, timeout)) { - regmap_read(rk_phy->reg_base, - rk_phy->phy_data->pcie_status, - &status); - if (status & PHY_PLL_LOCKED) { - dev_dbg(&phy->dev, "pll locked!\n"); - err = 0; - break; - } - msleep(20); - } - - if (err) { - dev_err(&phy->dev, "pll lock timeout!\n"); - goto err_pll_lock; - } - - phy_wr_cfg(rk_phy, PHY_CFG_CLK_TEST, PHY_CFG_SEPE_RATE); - phy_wr_cfg(rk_phy, PHY_CFG_CLK_SCC, PHY_CFG_PLL_100M); - - err = -ETIMEDOUT; - while (time_before(jiffies, timeout)) { - regmap_read(rk_phy->reg_base, - rk_phy->phy_data->pcie_status, - &status); - if (!(status & PHY_PLL_OUTPUT)) { - dev_dbg(&phy->dev, "pll output enable done!\n"); - err = 0; - break; - } - msleep(20); - } - - if (err) { - dev_err(&phy->dev, "pll output enable timeout!\n"); - goto err_pll_lock; - } - - regmap_write(rk_phy->reg_base, rk_phy->phy_data->pcie_conf, - HIWORD_UPDATE(PHY_CFG_PLL_LOCK, - PHY_CFG_ADDR_MASK, - PHY_CFG_ADDR_SHIFT)); - err = -EINVAL; - while (time_before(jiffies, timeout)) { - regmap_read(rk_phy->reg_base, - rk_phy->phy_data->pcie_status, - &status); - if (status & PHY_PLL_LOCKED) { - dev_dbg(&phy->dev, "pll relocked!\n"); - err = 0; - break; - } - msleep(20); - } - - if (err) { - dev_err(&phy->dev, "pll relock timeout!\n"); - goto err_pll_lock; - } - - return 0; - -err_pll_lock: - reset_control_assert(rk_phy->phy_rst); - return err; -} - -static int rockchip_pcie_phy_init(struct phy *phy) -{ - struct rockchip_pcie_phy *rk_phy = phy_get_drvdata(phy); - int err = 0; - - err = clk_prepare_enable(rk_phy->clk_pciephy_ref); - if (err) { - dev_err(&phy->dev, "Fail to enable pcie ref clock.\n"); - goto err_refclk; - } - - err = reset_control_assert(rk_phy->phy_rst); - if (err) { - dev_err(&phy->dev, "assert phy_rst err %d\n", err); - goto err_reset; - } - - return err; - -err_reset: - clk_disable_unprepare(rk_phy->clk_pciephy_ref); -err_refclk: - return err; -} - -static int rockchip_pcie_phy_exit(struct phy *phy) -{ - struct rockchip_pcie_phy *rk_phy = phy_get_drvdata(phy); - - clk_disable_unprepare(rk_phy->clk_pciephy_ref); - - return 0; -} - -static const struct phy_ops ops = { - .init = rockchip_pcie_phy_init, - .exit = rockchip_pcie_phy_exit, - .power_on = rockchip_pcie_phy_power_on, - .power_off = rockchip_pcie_phy_power_off, - .owner = THIS_MODULE, -}; - -static const struct rockchip_pcie_data rk3399_pcie_data = { - .pcie_conf = 0xe220, - .pcie_status = 0xe2a4, - .pcie_laneoff = 0xe214, -}; - -static const struct of_device_id rockchip_pcie_phy_dt_ids[] = { - { - .compatible = "rockchip,rk3399-pcie-phy", - .data = &rk3399_pcie_data, - }, - {} -}; - -MODULE_DEVICE_TABLE(of, rockchip_pcie_phy_dt_ids); - -static int rockchip_pcie_phy_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct rockchip_pcie_phy *rk_phy; - struct phy *generic_phy; - struct phy_provider *phy_provider; - struct regmap *grf; - const struct of_device_id *of_id; - - grf = syscon_node_to_regmap(dev->parent->of_node); - if (IS_ERR(grf)) { - dev_err(dev, "Cannot find GRF syscon\n"); - return PTR_ERR(grf); - } - - rk_phy = devm_kzalloc(dev, sizeof(*rk_phy), GFP_KERNEL); - if (!rk_phy) - return -ENOMEM; - - of_id = of_match_device(rockchip_pcie_phy_dt_ids, &pdev->dev); - if (!of_id) - return -EINVAL; - - rk_phy->phy_data = (struct rockchip_pcie_data *)of_id->data; - rk_phy->reg_base = grf; - - rk_phy->phy_rst = devm_reset_control_get(dev, "phy"); - if (IS_ERR(rk_phy->phy_rst)) { - if (PTR_ERR(rk_phy->phy_rst) != -EPROBE_DEFER) - dev_err(dev, - "missing phy property for reset controller\n"); - return PTR_ERR(rk_phy->phy_rst); - } - - rk_phy->clk_pciephy_ref = devm_clk_get(dev, "refclk"); - if (IS_ERR(rk_phy->clk_pciephy_ref)) { - dev_err(dev, "refclk not found.\n"); - return PTR_ERR(rk_phy->clk_pciephy_ref); - } - - generic_phy = devm_phy_create(dev, dev->of_node, &ops); - if (IS_ERR(generic_phy)) { - dev_err(dev, "failed to create PHY\n"); - return PTR_ERR(generic_phy); - } - - phy_set_drvdata(generic_phy, rk_phy); - phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); - - return PTR_ERR_OR_ZERO(phy_provider); -} - -static struct platform_driver rockchip_pcie_driver = { - .probe = rockchip_pcie_phy_probe, - .driver = { - .name = "rockchip-pcie-phy", - .of_match_table = rockchip_pcie_phy_dt_ids, - }, -}; - -module_platform_driver(rockchip_pcie_driver); - -MODULE_AUTHOR("Shawn Lin "); -MODULE_DESCRIPTION("Rockchip PCIe PHY driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-rockchip-typec.c b/drivers/phy/phy-rockchip-typec.c deleted file mode 100644 index 7cfb0f8995de..000000000000 --- a/drivers/phy/phy-rockchip-typec.c +++ /dev/null @@ -1,1023 +0,0 @@ -/* - * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd - * Author: Chris Zhong - * Kever Yang - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * 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. - * - * The ROCKCHIP Type-C PHY has two PLL clocks. The first PLL clock - * is used for USB3, the second PLL clock is used for DP. This Type-C PHY has - * 3 working modes: USB3 only mode, DP only mode, and USB3+DP mode. - * At USB3 only mode, both PLL clocks need to be initialized, this allows the - * PHY to switch mode between USB3 and USB3+DP, without disconnecting the USB - * device. - * In The DP only mode, only the DP PLL needs to be powered on, and the 4 lanes - * are all used for DP. - * - * This driver gets extcon cable state and property, then decides which mode to - * select: - * - * 1. USB3 only mode: - * EXTCON_USB or EXTCON_USB_HOST state is true, and - * EXTCON_PROP_USB_SS property is true. - * EXTCON_DISP_DP state is false. - * - * 2. DP only mode: - * EXTCON_DISP_DP state is true, and - * EXTCON_PROP_USB_SS property is false. - * If EXTCON_USB_HOST state is true, it is DP + USB2 mode, since the USB2 phy - * is a separate phy, so this case is still DP only mode. - * - * 3. USB3+DP mode: - * EXTCON_USB_HOST and EXTCON_DISP_DP are both true, and - * EXTCON_PROP_USB_SS property is true. - * - * This Type-C PHY driver supports normal and flip orientation. The orientation - * is reported by the EXTCON_PROP_USB_TYPEC_POLARITY property: true is flip - * orientation, false is normal orientation. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#define CMN_SSM_BANDGAP (0x21 << 2) -#define CMN_SSM_BIAS (0x22 << 2) -#define CMN_PLLSM0_PLLEN (0x29 << 2) -#define CMN_PLLSM0_PLLPRE (0x2a << 2) -#define CMN_PLLSM0_PLLVREF (0x2b << 2) -#define CMN_PLLSM0_PLLLOCK (0x2c << 2) -#define CMN_PLLSM1_PLLEN (0x31 << 2) -#define CMN_PLLSM1_PLLPRE (0x32 << 2) -#define CMN_PLLSM1_PLLVREF (0x33 << 2) -#define CMN_PLLSM1_PLLLOCK (0x34 << 2) -#define CMN_PLLSM1_USER_DEF_CTRL (0x37 << 2) -#define CMN_ICAL_OVRD (0xc1 << 2) -#define CMN_PLL0_VCOCAL_OVRD (0x83 << 2) -#define CMN_PLL0_VCOCAL_INIT (0x84 << 2) -#define CMN_PLL0_VCOCAL_ITER (0x85 << 2) -#define CMN_PLL0_LOCK_REFCNT_START (0x90 << 2) -#define CMN_PLL0_LOCK_PLLCNT_START (0x92 << 2) -#define CMN_PLL0_LOCK_PLLCNT_THR (0x93 << 2) -#define CMN_PLL0_INTDIV (0x94 << 2) -#define CMN_PLL0_FRACDIV (0x95 << 2) -#define CMN_PLL0_HIGH_THR (0x96 << 2) -#define CMN_PLL0_DSM_DIAG (0x97 << 2) -#define CMN_PLL0_SS_CTRL1 (0x98 << 2) -#define CMN_PLL0_SS_CTRL2 (0x99 << 2) -#define CMN_PLL1_VCOCAL_START (0xa1 << 2) -#define CMN_PLL1_VCOCAL_OVRD (0xa3 << 2) -#define CMN_PLL1_VCOCAL_INIT (0xa4 << 2) -#define CMN_PLL1_VCOCAL_ITER (0xa5 << 2) -#define CMN_PLL1_LOCK_REFCNT_START (0xb0 << 2) -#define CMN_PLL1_LOCK_PLLCNT_START (0xb2 << 2) -#define CMN_PLL1_LOCK_PLLCNT_THR (0xb3 << 2) -#define CMN_PLL1_INTDIV (0xb4 << 2) -#define CMN_PLL1_FRACDIV (0xb5 << 2) -#define CMN_PLL1_HIGH_THR (0xb6 << 2) -#define CMN_PLL1_DSM_DIAG (0xb7 << 2) -#define CMN_PLL1_SS_CTRL1 (0xb8 << 2) -#define CMN_PLL1_SS_CTRL2 (0xb9 << 2) -#define CMN_RXCAL_OVRD (0xd1 << 2) -#define CMN_TXPUCAL_CTRL (0xe0 << 2) -#define CMN_TXPUCAL_OVRD (0xe1 << 2) -#define CMN_TXPDCAL_OVRD (0xf1 << 2) -#define CMN_DIAG_PLL0_FBH_OVRD (0x1c0 << 2) -#define CMN_DIAG_PLL0_FBL_OVRD (0x1c1 << 2) -#define CMN_DIAG_PLL0_OVRD (0x1c2 << 2) -#define CMN_DIAG_PLL0_V2I_TUNE (0x1c5 << 2) -#define CMN_DIAG_PLL0_CP_TUNE (0x1c6 << 2) -#define CMN_DIAG_PLL0_LF_PROG (0x1c7 << 2) -#define CMN_DIAG_PLL1_FBH_OVRD (0x1d0 << 2) -#define CMN_DIAG_PLL1_FBL_OVRD (0x1d1 << 2) -#define CMN_DIAG_PLL1_OVRD (0x1d2 << 2) -#define CMN_DIAG_PLL1_V2I_TUNE (0x1d5 << 2) -#define CMN_DIAG_PLL1_CP_TUNE (0x1d6 << 2) -#define CMN_DIAG_PLL1_LF_PROG (0x1d7 << 2) -#define CMN_DIAG_PLL1_PTATIS_TUNE1 (0x1d8 << 2) -#define CMN_DIAG_PLL1_PTATIS_TUNE2 (0x1d9 << 2) -#define CMN_DIAG_PLL1_INCLK_CTRL (0x1da << 2) -#define CMN_DIAG_HSCLK_SEL (0x1e0 << 2) - -#define XCVR_PSM_RCTRL(n) ((0x4001 | ((n) << 9)) << 2) -#define XCVR_PSM_CAL_TMR(n) ((0x4002 | ((n) << 9)) << 2) -#define XCVR_PSM_A0IN_TMR(n) ((0x4003 | ((n) << 9)) << 2) -#define TX_TXCC_CAL_SCLR_MULT(n) ((0x4047 | ((n) << 9)) << 2) -#define TX_TXCC_CPOST_MULT_00(n) ((0x404c | ((n) << 9)) << 2) -#define TX_TXCC_CPOST_MULT_01(n) ((0x404d | ((n) << 9)) << 2) -#define TX_TXCC_CPOST_MULT_10(n) ((0x404e | ((n) << 9)) << 2) -#define TX_TXCC_CPOST_MULT_11(n) ((0x404f | ((n) << 9)) << 2) -#define TX_TXCC_MGNFS_MULT_000(n) ((0x4050 | ((n) << 9)) << 2) -#define TX_TXCC_MGNFS_MULT_001(n) ((0x4051 | ((n) << 9)) << 2) -#define TX_TXCC_MGNFS_MULT_010(n) ((0x4052 | ((n) << 9)) << 2) -#define TX_TXCC_MGNFS_MULT_011(n) ((0x4053 | ((n) << 9)) << 2) -#define TX_TXCC_MGNFS_MULT_100(n) ((0x4054 | ((n) << 9)) << 2) -#define TX_TXCC_MGNFS_MULT_101(n) ((0x4055 | ((n) << 9)) << 2) -#define TX_TXCC_MGNFS_MULT_110(n) ((0x4056 | ((n) << 9)) << 2) -#define TX_TXCC_MGNFS_MULT_111(n) ((0x4057 | ((n) << 9)) << 2) -#define XCVR_DIAG_PLLDRC_CTRL(n) ((0x40e0 | ((n) << 9)) << 2) -#define XCVR_DIAG_BIDI_CTRL(n) ((0x40e8 | ((n) << 9)) << 2) -#define XCVR_DIAG_LANE_FCM_EN_MGN(n) ((0x40f2 | ((n) << 9)) << 2) -#define TX_PSC_A0(n) ((0x4100 | ((n) << 9)) << 2) -#define TX_PSC_A1(n) ((0x4101 | ((n) << 9)) << 2) -#define TX_PSC_A2(n) ((0x4102 | ((n) << 9)) << 2) -#define TX_PSC_A3(n) ((0x4103 | ((n) << 9)) << 2) -#define TX_RCVDET_CTRL(n) ((0x4120 | ((n) << 9)) << 2) -#define TX_RCVDET_EN_TMR(n) ((0x4122 | ((n) << 9)) << 2) -#define TX_RCVDET_ST_TMR(n) ((0x4123 | ((n) << 9)) << 2) -#define TX_DIAG_TX_DRV(n) ((0x41e1 | ((n) << 9)) << 2) -#define TX_DIAG_BGREF_PREDRV_DELAY (0x41e7 << 2) -#define TX_ANA_CTRL_REG_1 (0x5020 << 2) -#define TX_ANA_CTRL_REG_2 (0x5021 << 2) -#define TXDA_COEFF_CALC_CTRL (0x5022 << 2) -#define TX_DIG_CTRL_REG_2 (0x5024 << 2) -#define TXDA_CYA_AUXDA_CYA (0x5025 << 2) -#define TX_ANA_CTRL_REG_3 (0x5026 << 2) -#define TX_ANA_CTRL_REG_4 (0x5027 << 2) -#define TX_ANA_CTRL_REG_5 (0x5029 << 2) - -#define RX_PSC_A0(n) ((0x8000 | ((n) << 9)) << 2) -#define RX_PSC_A1(n) ((0x8001 | ((n) << 9)) << 2) -#define RX_PSC_A2(n) ((0x8002 | ((n) << 9)) << 2) -#define RX_PSC_A3(n) ((0x8003 | ((n) << 9)) << 2) -#define RX_PSC_CAL(n) ((0x8006 | ((n) << 9)) << 2) -#define RX_PSC_RDY(n) ((0x8007 | ((n) << 9)) << 2) -#define RX_IQPI_ILL_CAL_OVRD (0x8023 << 2) -#define RX_EPI_ILL_CAL_OVRD (0x8033 << 2) -#define RX_SDCAL0_OVRD (0x8041 << 2) -#define RX_SDCAL1_OVRD (0x8049 << 2) -#define RX_SLC_INIT (0x806d << 2) -#define RX_SLC_RUN (0x806e << 2) -#define RX_CDRLF_CNFG2 (0x8081 << 2) -#define RX_SIGDET_HL_FILT_TMR(n) ((0x8090 | ((n) << 9)) << 2) -#define RX_SLC_IOP0_OVRD (0x8101 << 2) -#define RX_SLC_IOP1_OVRD (0x8105 << 2) -#define RX_SLC_QOP0_OVRD (0x8109 << 2) -#define RX_SLC_QOP1_OVRD (0x810d << 2) -#define RX_SLC_EOP0_OVRD (0x8111 << 2) -#define RX_SLC_EOP1_OVRD (0x8115 << 2) -#define RX_SLC_ION0_OVRD (0x8119 << 2) -#define RX_SLC_ION1_OVRD (0x811d << 2) -#define RX_SLC_QON0_OVRD (0x8121 << 2) -#define RX_SLC_QON1_OVRD (0x8125 << 2) -#define RX_SLC_EON0_OVRD (0x8129 << 2) -#define RX_SLC_EON1_OVRD (0x812d << 2) -#define RX_SLC_IEP0_OVRD (0x8131 << 2) -#define RX_SLC_IEP1_OVRD (0x8135 << 2) -#define RX_SLC_QEP0_OVRD (0x8139 << 2) -#define RX_SLC_QEP1_OVRD (0x813d << 2) -#define RX_SLC_EEP0_OVRD (0x8141 << 2) -#define RX_SLC_EEP1_OVRD (0x8145 << 2) -#define RX_SLC_IEN0_OVRD (0x8149 << 2) -#define RX_SLC_IEN1_OVRD (0x814d << 2) -#define RX_SLC_QEN0_OVRD (0x8151 << 2) -#define RX_SLC_QEN1_OVRD (0x8155 << 2) -#define RX_SLC_EEN0_OVRD (0x8159 << 2) -#define RX_SLC_EEN1_OVRD (0x815d << 2) -#define RX_REE_CTRL_DATA_MASK(n) ((0x81bb | ((n) << 9)) << 2) -#define RX_DIAG_SIGDET_TUNE(n) ((0x81dc | ((n) << 9)) << 2) -#define RX_DIAG_SC2C_DELAY (0x81e1 << 2) - -#define PMA_LANE_CFG (0xc000 << 2) -#define PIPE_CMN_CTRL1 (0xc001 << 2) -#define PIPE_CMN_CTRL2 (0xc002 << 2) -#define PIPE_COM_LOCK_CFG1 (0xc003 << 2) -#define PIPE_COM_LOCK_CFG2 (0xc004 << 2) -#define PIPE_RCV_DET_INH (0xc005 << 2) -#define DP_MODE_CTL (0xc008 << 2) -#define DP_CLK_CTL (0xc009 << 2) -#define STS (0xc00F << 2) -#define PHY_ISO_CMN_CTRL (0xc010 << 2) -#define PHY_DP_TX_CTL (0xc408 << 2) -#define PMA_CMN_CTRL1 (0xc800 << 2) -#define PHY_PMA_ISO_CMN_CTRL (0xc810 << 2) -#define PHY_ISOLATION_CTRL (0xc81f << 2) -#define PHY_PMA_ISO_XCVR_CTRL(n) ((0xcc11 | ((n) << 6)) << 2) -#define PHY_PMA_ISO_LINK_MODE(n) ((0xcc12 | ((n) << 6)) << 2) -#define PHY_PMA_ISO_PWRST_CTRL(n) ((0xcc13 | ((n) << 6)) << 2) -#define PHY_PMA_ISO_TX_DATA_LO(n) ((0xcc14 | ((n) << 6)) << 2) -#define PHY_PMA_ISO_TX_DATA_HI(n) ((0xcc15 | ((n) << 6)) << 2) -#define PHY_PMA_ISO_RX_DATA_LO(n) ((0xcc16 | ((n) << 6)) << 2) -#define PHY_PMA_ISO_RX_DATA_HI(n) ((0xcc17 | ((n) << 6)) << 2) -#define TX_BIST_CTRL(n) ((0x4140 | ((n) << 9)) << 2) -#define TX_BIST_UDDWR(n) ((0x4141 | ((n) << 9)) << 2) - -/* - * Selects which PLL clock will be driven on the analog high speed - * clock 0: PLL 0 div 1 - * clock 1: PLL 1 div 2 - */ -#define CLK_PLL_CONFIG 0X30 -#define CLK_PLL_MASK 0x33 - -#define CMN_READY BIT(0) - -#define DP_PLL_CLOCK_ENABLE BIT(2) -#define DP_PLL_ENABLE BIT(0) -#define DP_PLL_DATA_RATE_RBR ((2 << 12) | (4 << 8)) -#define DP_PLL_DATA_RATE_HBR ((2 << 12) | (4 << 8)) -#define DP_PLL_DATA_RATE_HBR2 ((1 << 12) | (2 << 8)) - -#define DP_MODE_A0 BIT(4) -#define DP_MODE_A2 BIT(6) -#define DP_MODE_ENTER_A0 0xc101 -#define DP_MODE_ENTER_A2 0xc104 - -#define PHY_MODE_SET_TIMEOUT 100000 - -#define PIN_ASSIGN_C_E 0x51d9 -#define PIN_ASSIGN_D_F 0x5100 - -#define MODE_DISCONNECT 0 -#define MODE_UFP_USB BIT(0) -#define MODE_DFP_USB BIT(1) -#define MODE_DFP_DP BIT(2) - -struct usb3phy_reg { - u32 offset; - u32 enable_bit; - u32 write_enable; -}; - -struct rockchip_usb3phy_port_cfg { - struct usb3phy_reg typec_conn_dir; - struct usb3phy_reg usb3tousb2_en; - struct usb3phy_reg external_psm; - struct usb3phy_reg pipe_status; -}; - -struct rockchip_typec_phy { - struct device *dev; - void __iomem *base; - struct extcon_dev *extcon; - struct regmap *grf_regs; - struct clk *clk_core; - struct clk *clk_ref; - struct reset_control *uphy_rst; - struct reset_control *pipe_rst; - struct reset_control *tcphy_rst; - struct rockchip_usb3phy_port_cfg port_cfgs; - /* mutex to protect access to individual PHYs */ - struct mutex lock; - - bool flip; - u8 mode; -}; - -struct phy_reg { - u16 value; - u32 addr; -}; - -struct phy_reg usb3_pll_cfg[] = { - { 0xf0, CMN_PLL0_VCOCAL_INIT }, - { 0x18, CMN_PLL0_VCOCAL_ITER }, - { 0xd0, CMN_PLL0_INTDIV }, - { 0x4a4a, CMN_PLL0_FRACDIV }, - { 0x34, CMN_PLL0_HIGH_THR }, - { 0x1ee, CMN_PLL0_SS_CTRL1 }, - { 0x7f03, CMN_PLL0_SS_CTRL2 }, - { 0x20, CMN_PLL0_DSM_DIAG }, - { 0, CMN_DIAG_PLL0_OVRD }, - { 0, CMN_DIAG_PLL0_FBH_OVRD }, - { 0, CMN_DIAG_PLL0_FBL_OVRD }, - { 0x7, CMN_DIAG_PLL0_V2I_TUNE }, - { 0x45, CMN_DIAG_PLL0_CP_TUNE }, - { 0x8, CMN_DIAG_PLL0_LF_PROG }, -}; - -struct phy_reg dp_pll_cfg[] = { - { 0xf0, CMN_PLL1_VCOCAL_INIT }, - { 0x18, CMN_PLL1_VCOCAL_ITER }, - { 0x30b9, CMN_PLL1_VCOCAL_START }, - { 0x21c, CMN_PLL1_INTDIV }, - { 0, CMN_PLL1_FRACDIV }, - { 0x5, CMN_PLL1_HIGH_THR }, - { 0x35, CMN_PLL1_SS_CTRL1 }, - { 0x7f1e, CMN_PLL1_SS_CTRL2 }, - { 0x20, CMN_PLL1_DSM_DIAG }, - { 0, CMN_PLLSM1_USER_DEF_CTRL }, - { 0, CMN_DIAG_PLL1_OVRD }, - { 0, CMN_DIAG_PLL1_FBH_OVRD }, - { 0, CMN_DIAG_PLL1_FBL_OVRD }, - { 0x6, CMN_DIAG_PLL1_V2I_TUNE }, - { 0x45, CMN_DIAG_PLL1_CP_TUNE }, - { 0x8, CMN_DIAG_PLL1_LF_PROG }, - { 0x100, CMN_DIAG_PLL1_PTATIS_TUNE1 }, - { 0x7, CMN_DIAG_PLL1_PTATIS_TUNE2 }, - { 0x4, CMN_DIAG_PLL1_INCLK_CTRL }, -}; - -static void tcphy_cfg_24m(struct rockchip_typec_phy *tcphy) -{ - u32 i, rdata; - - /* - * cmn_ref_clk_sel = 3, select the 24Mhz for clk parent - * cmn_psm_clk_dig_div = 2, set the clk division to 2 - */ - writel(0x830, tcphy->base + PMA_CMN_CTRL1); - for (i = 0; i < 4; i++) { - /* - * The following PHY configuration assumes a 24 MHz reference - * clock. - */ - writel(0x90, tcphy->base + XCVR_DIAG_LANE_FCM_EN_MGN(i)); - writel(0x960, tcphy->base + TX_RCVDET_EN_TMR(i)); - writel(0x30, tcphy->base + TX_RCVDET_ST_TMR(i)); - } - - rdata = readl(tcphy->base + CMN_DIAG_HSCLK_SEL); - rdata &= ~CLK_PLL_MASK; - rdata |= CLK_PLL_CONFIG; - writel(rdata, tcphy->base + CMN_DIAG_HSCLK_SEL); -} - -static void tcphy_cfg_usb3_pll(struct rockchip_typec_phy *tcphy) -{ - u32 i; - - /* load the configuration of PLL0 */ - for (i = 0; i < ARRAY_SIZE(usb3_pll_cfg); i++) - writel(usb3_pll_cfg[i].value, - tcphy->base + usb3_pll_cfg[i].addr); -} - -static void tcphy_cfg_dp_pll(struct rockchip_typec_phy *tcphy) -{ - u32 i; - - /* set the default mode to RBR */ - writel(DP_PLL_CLOCK_ENABLE | DP_PLL_ENABLE | DP_PLL_DATA_RATE_RBR, - tcphy->base + DP_CLK_CTL); - - /* load the configuration of PLL1 */ - for (i = 0; i < ARRAY_SIZE(dp_pll_cfg); i++) - writel(dp_pll_cfg[i].value, tcphy->base + dp_pll_cfg[i].addr); -} - -static void tcphy_tx_usb3_cfg_lane(struct rockchip_typec_phy *tcphy, u32 lane) -{ - writel(0x7799, tcphy->base + TX_PSC_A0(lane)); - writel(0x7798, tcphy->base + TX_PSC_A1(lane)); - writel(0x5098, tcphy->base + TX_PSC_A2(lane)); - writel(0x5098, tcphy->base + TX_PSC_A3(lane)); - writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_000(lane)); - writel(0xbf, tcphy->base + XCVR_DIAG_BIDI_CTRL(lane)); -} - -static void tcphy_rx_usb3_cfg_lane(struct rockchip_typec_phy *tcphy, u32 lane) -{ - writel(0xa6fd, tcphy->base + RX_PSC_A0(lane)); - writel(0xa6fd, tcphy->base + RX_PSC_A1(lane)); - writel(0xa410, tcphy->base + RX_PSC_A2(lane)); - writel(0x2410, tcphy->base + RX_PSC_A3(lane)); - writel(0x23ff, tcphy->base + RX_PSC_CAL(lane)); - writel(0x13, tcphy->base + RX_SIGDET_HL_FILT_TMR(lane)); - writel(0x03e7, tcphy->base + RX_REE_CTRL_DATA_MASK(lane)); - writel(0x1004, tcphy->base + RX_DIAG_SIGDET_TUNE(lane)); - writel(0x2010, tcphy->base + RX_PSC_RDY(lane)); - writel(0xfb, tcphy->base + XCVR_DIAG_BIDI_CTRL(lane)); -} - -static void tcphy_dp_cfg_lane(struct rockchip_typec_phy *tcphy, u32 lane) -{ - u16 rdata; - - writel(0xbefc, tcphy->base + XCVR_PSM_RCTRL(lane)); - writel(0x6799, tcphy->base + TX_PSC_A0(lane)); - writel(0x6798, tcphy->base + TX_PSC_A1(lane)); - writel(0x98, tcphy->base + TX_PSC_A2(lane)); - writel(0x98, tcphy->base + TX_PSC_A3(lane)); - - writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_000(lane)); - writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_001(lane)); - writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_010(lane)); - writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_011(lane)); - writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_100(lane)); - writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_101(lane)); - writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_110(lane)); - writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_111(lane)); - writel(0, tcphy->base + TX_TXCC_CPOST_MULT_10(lane)); - writel(0, tcphy->base + TX_TXCC_CPOST_MULT_01(lane)); - writel(0, tcphy->base + TX_TXCC_CPOST_MULT_00(lane)); - writel(0, tcphy->base + TX_TXCC_CPOST_MULT_11(lane)); - - writel(0x128, tcphy->base + TX_TXCC_CAL_SCLR_MULT(lane)); - writel(0x400, tcphy->base + TX_DIAG_TX_DRV(lane)); - - rdata = readl(tcphy->base + XCVR_DIAG_PLLDRC_CTRL(lane)); - rdata = (rdata & 0x8fff) | 0x6000; - writel(rdata, tcphy->base + XCVR_DIAG_PLLDRC_CTRL(lane)); -} - -static inline int property_enable(struct rockchip_typec_phy *tcphy, - const struct usb3phy_reg *reg, bool en) -{ - u32 mask = 1 << reg->write_enable; - u32 val = en << reg->enable_bit; - - return regmap_write(tcphy->grf_regs, reg->offset, val | mask); -} - -static void tcphy_dp_aux_calibration(struct rockchip_typec_phy *tcphy) -{ - u16 rdata, rdata2, val; - - /* disable txda_cal_latch_en for rewrite the calibration values */ - rdata = readl(tcphy->base + TX_ANA_CTRL_REG_1); - val = rdata & 0xdfff; - writel(val, tcphy->base + TX_ANA_CTRL_REG_1); - - /* - * read a resistor calibration code from CMN_TXPUCAL_CTRL[6:0] and - * write it to TX_DIG_CTRL_REG_2[6:0], and delay 1ms to make sure it - * works. - */ - rdata = readl(tcphy->base + TX_DIG_CTRL_REG_2); - rdata = rdata & 0xffc0; - - rdata2 = readl(tcphy->base + CMN_TXPUCAL_CTRL); - rdata2 = rdata2 & 0x3f; - - val = rdata | rdata2; - writel(val, tcphy->base + TX_DIG_CTRL_REG_2); - usleep_range(1000, 1050); - - /* - * Enable signal for latch that sample and holds calibration values. - * Activate this signal for 1 clock cycle to sample new calibration - * values. - */ - rdata = readl(tcphy->base + TX_ANA_CTRL_REG_1); - val = rdata | 0x2000; - writel(val, tcphy->base + TX_ANA_CTRL_REG_1); - usleep_range(150, 200); - - /* set TX Voltage Level and TX Deemphasis to 0 */ - writel(0, tcphy->base + PHY_DP_TX_CTL); - /* re-enable decap */ - writel(0x100, tcphy->base + TX_ANA_CTRL_REG_2); - writel(0x300, tcphy->base + TX_ANA_CTRL_REG_2); - writel(0x2008, tcphy->base + TX_ANA_CTRL_REG_1); - writel(0x2018, tcphy->base + TX_ANA_CTRL_REG_1); - - writel(0, tcphy->base + TX_ANA_CTRL_REG_5); - - /* - * Programs txda_drv_ldo_prog[15:0], Sets driver LDO - * voltage 16'h1001 for DP-AUX-TX and RX - */ - writel(0x1001, tcphy->base + TX_ANA_CTRL_REG_4); - - /* re-enables Bandgap reference for LDO */ - writel(0x2098, tcphy->base + TX_ANA_CTRL_REG_1); - writel(0x2198, tcphy->base + TX_ANA_CTRL_REG_1); - - /* - * re-enables the transmitter pre-driver, driver data selection MUX, - * and receiver detect circuits. - */ - writel(0x301, tcphy->base + TX_ANA_CTRL_REG_2); - writel(0x303, tcphy->base + TX_ANA_CTRL_REG_2); - - /* - * BIT 12: Controls auxda_polarity, which selects the polarity of the - * xcvr: - * 1, Reverses the polarity (If TYPEC, Pulls ups aux_p and pull - * down aux_m) - * 0, Normal polarity (if TYPE_C, pulls up aux_m and pulls down - * aux_p) - */ - val = 0xa078; - if (!tcphy->flip) - val |= BIT(12); - writel(val, tcphy->base + TX_ANA_CTRL_REG_1); - - writel(0, tcphy->base + TX_ANA_CTRL_REG_3); - writel(0, tcphy->base + TX_ANA_CTRL_REG_4); - writel(0, tcphy->base + TX_ANA_CTRL_REG_5); - - /* - * Controls low_power_swing_en, set the voltage swing of the driver - * to 400mv. The values below are peak to peak (differential) values. - */ - writel(4, tcphy->base + TXDA_COEFF_CALC_CTRL); - writel(0, tcphy->base + TXDA_CYA_AUXDA_CYA); - - /* Controls tx_high_z_tm_en */ - val = readl(tcphy->base + TX_DIG_CTRL_REG_2); - val |= BIT(15); - writel(val, tcphy->base + TX_DIG_CTRL_REG_2); -} - -static int tcphy_phy_init(struct rockchip_typec_phy *tcphy, u8 mode) -{ - struct rockchip_usb3phy_port_cfg *cfg = &tcphy->port_cfgs; - int ret, i; - u32 val; - - ret = clk_prepare_enable(tcphy->clk_core); - if (ret) { - dev_err(tcphy->dev, "Failed to prepare_enable core clock\n"); - return ret; - } - - ret = clk_prepare_enable(tcphy->clk_ref); - if (ret) { - dev_err(tcphy->dev, "Failed to prepare_enable ref clock\n"); - goto err_clk_core; - } - - reset_control_deassert(tcphy->tcphy_rst); - - property_enable(tcphy, &cfg->typec_conn_dir, tcphy->flip); - - tcphy_cfg_24m(tcphy); - - if (mode == MODE_DFP_DP) { - tcphy_cfg_dp_pll(tcphy); - for (i = 0; i < 4; i++) - tcphy_dp_cfg_lane(tcphy, i); - - writel(PIN_ASSIGN_C_E, tcphy->base + PMA_LANE_CFG); - } else { - tcphy_cfg_usb3_pll(tcphy); - tcphy_cfg_dp_pll(tcphy); - if (tcphy->flip) { - tcphy_tx_usb3_cfg_lane(tcphy, 3); - tcphy_rx_usb3_cfg_lane(tcphy, 2); - tcphy_dp_cfg_lane(tcphy, 0); - tcphy_dp_cfg_lane(tcphy, 1); - } else { - tcphy_tx_usb3_cfg_lane(tcphy, 0); - tcphy_rx_usb3_cfg_lane(tcphy, 1); - tcphy_dp_cfg_lane(tcphy, 2); - tcphy_dp_cfg_lane(tcphy, 3); - } - - writel(PIN_ASSIGN_D_F, tcphy->base + PMA_LANE_CFG); - } - - writel(DP_MODE_ENTER_A2, tcphy->base + DP_MODE_CTL); - - reset_control_deassert(tcphy->uphy_rst); - - ret = readx_poll_timeout(readl, tcphy->base + PMA_CMN_CTRL1, - val, val & CMN_READY, 10, - PHY_MODE_SET_TIMEOUT); - if (ret < 0) { - dev_err(tcphy->dev, "wait pma ready timeout\n"); - ret = -ETIMEDOUT; - goto err_wait_pma; - } - - reset_control_deassert(tcphy->pipe_rst); - - return 0; - -err_wait_pma: - reset_control_assert(tcphy->uphy_rst); - reset_control_assert(tcphy->tcphy_rst); - clk_disable_unprepare(tcphy->clk_ref); -err_clk_core: - clk_disable_unprepare(tcphy->clk_core); - return ret; -} - -static void tcphy_phy_deinit(struct rockchip_typec_phy *tcphy) -{ - reset_control_assert(tcphy->tcphy_rst); - reset_control_assert(tcphy->uphy_rst); - reset_control_assert(tcphy->pipe_rst); - clk_disable_unprepare(tcphy->clk_core); - clk_disable_unprepare(tcphy->clk_ref); -} - -static int tcphy_get_mode(struct rockchip_typec_phy *tcphy) -{ - struct extcon_dev *edev = tcphy->extcon; - union extcon_property_value property; - unsigned int id; - bool dfp, ufp, dp; - u8 mode; - int ret; - - ufp = extcon_get_state(edev, EXTCON_USB); - dfp = extcon_get_state(edev, EXTCON_USB_HOST); - dp = extcon_get_state(edev, EXTCON_DISP_DP); - - mode = MODE_DFP_USB; - id = EXTCON_USB_HOST; - - if (ufp) { - mode = MODE_UFP_USB; - id = EXTCON_USB; - } else if (dp) { - mode = MODE_DFP_DP; - id = EXTCON_DISP_DP; - - ret = extcon_get_property(edev, id, EXTCON_PROP_USB_SS, - &property); - if (ret) { - dev_err(tcphy->dev, "get superspeed property failed\n"); - return ret; - } - - if (property.intval) - mode |= MODE_DFP_USB; - } - - ret = extcon_get_property(edev, id, EXTCON_PROP_USB_TYPEC_POLARITY, - &property); - if (ret) { - dev_err(tcphy->dev, "get polarity property failed\n"); - return ret; - } - - tcphy->flip = property.intval ? 1 : 0; - - return mode; -} - -static int rockchip_usb3_phy_power_on(struct phy *phy) -{ - struct rockchip_typec_phy *tcphy = phy_get_drvdata(phy); - struct rockchip_usb3phy_port_cfg *cfg = &tcphy->port_cfgs; - const struct usb3phy_reg *reg = &cfg->pipe_status; - int timeout, new_mode, ret = 0; - u32 val; - - mutex_lock(&tcphy->lock); - - new_mode = tcphy_get_mode(tcphy); - if (new_mode < 0) { - ret = new_mode; - goto unlock_ret; - } - - /* DP-only mode; fall back to USB2 */ - if (!(new_mode & (MODE_DFP_USB | MODE_UFP_USB))) - goto unlock_ret; - - if (tcphy->mode == new_mode) - goto unlock_ret; - - if (tcphy->mode == MODE_DISCONNECT) - tcphy_phy_init(tcphy, new_mode); - - /* wait TCPHY for pipe ready */ - for (timeout = 0; timeout < 100; timeout++) { - regmap_read(tcphy->grf_regs, reg->offset, &val); - if (!(val & BIT(reg->enable_bit))) { - tcphy->mode |= new_mode & (MODE_DFP_USB | MODE_UFP_USB); - goto unlock_ret; - } - usleep_range(10, 20); - } - - if (tcphy->mode == MODE_DISCONNECT) - tcphy_phy_deinit(tcphy); - - ret = -ETIMEDOUT; - -unlock_ret: - mutex_unlock(&tcphy->lock); - return ret; -} - -static int rockchip_usb3_phy_power_off(struct phy *phy) -{ - struct rockchip_typec_phy *tcphy = phy_get_drvdata(phy); - - mutex_lock(&tcphy->lock); - - if (tcphy->mode == MODE_DISCONNECT) - goto unlock; - - tcphy->mode &= ~(MODE_UFP_USB | MODE_DFP_USB); - if (tcphy->mode == MODE_DISCONNECT) - tcphy_phy_deinit(tcphy); - -unlock: - mutex_unlock(&tcphy->lock); - return 0; -} - -static const struct phy_ops rockchip_usb3_phy_ops = { - .power_on = rockchip_usb3_phy_power_on, - .power_off = rockchip_usb3_phy_power_off, - .owner = THIS_MODULE, -}; - -static int rockchip_dp_phy_power_on(struct phy *phy) -{ - struct rockchip_typec_phy *tcphy = phy_get_drvdata(phy); - int new_mode, ret = 0; - u32 val; - - mutex_lock(&tcphy->lock); - - new_mode = tcphy_get_mode(tcphy); - if (new_mode < 0) { - ret = new_mode; - goto unlock_ret; - } - - if (!(new_mode & MODE_DFP_DP)) { - ret = -ENODEV; - goto unlock_ret; - } - - if (tcphy->mode == new_mode) - goto unlock_ret; - - /* - * If the PHY has been power on, but the mode is not DP only mode, - * re-init the PHY for setting all of 4 lanes to DP. - */ - if (new_mode == MODE_DFP_DP && tcphy->mode != MODE_DISCONNECT) { - tcphy_phy_deinit(tcphy); - tcphy_phy_init(tcphy, new_mode); - } else if (tcphy->mode == MODE_DISCONNECT) { - tcphy_phy_init(tcphy, new_mode); - } - - ret = readx_poll_timeout(readl, tcphy->base + DP_MODE_CTL, - val, val & DP_MODE_A2, 1000, - PHY_MODE_SET_TIMEOUT); - if (ret < 0) { - dev_err(tcphy->dev, "failed to wait TCPHY enter A2\n"); - goto power_on_finish; - } - - tcphy_dp_aux_calibration(tcphy); - - writel(DP_MODE_ENTER_A0, tcphy->base + DP_MODE_CTL); - - ret = readx_poll_timeout(readl, tcphy->base + DP_MODE_CTL, - val, val & DP_MODE_A0, 1000, - PHY_MODE_SET_TIMEOUT); - if (ret < 0) { - writel(DP_MODE_ENTER_A2, tcphy->base + DP_MODE_CTL); - dev_err(tcphy->dev, "failed to wait TCPHY enter A0\n"); - goto power_on_finish; - } - - tcphy->mode |= MODE_DFP_DP; - -power_on_finish: - if (tcphy->mode == MODE_DISCONNECT) - tcphy_phy_deinit(tcphy); -unlock_ret: - mutex_unlock(&tcphy->lock); - return ret; -} - -static int rockchip_dp_phy_power_off(struct phy *phy) -{ - struct rockchip_typec_phy *tcphy = phy_get_drvdata(phy); - - mutex_lock(&tcphy->lock); - - if (tcphy->mode == MODE_DISCONNECT) - goto unlock; - - tcphy->mode &= ~MODE_DFP_DP; - - writel(DP_MODE_ENTER_A2, tcphy->base + DP_MODE_CTL); - - if (tcphy->mode == MODE_DISCONNECT) - tcphy_phy_deinit(tcphy); - -unlock: - mutex_unlock(&tcphy->lock); - return 0; -} - -static const struct phy_ops rockchip_dp_phy_ops = { - .power_on = rockchip_dp_phy_power_on, - .power_off = rockchip_dp_phy_power_off, - .owner = THIS_MODULE, -}; - -static int tcphy_get_param(struct device *dev, - struct usb3phy_reg *reg, - const char *name) -{ - u32 buffer[3]; - int ret; - - ret = of_property_read_u32_array(dev->of_node, name, buffer, 3); - if (ret) { - dev_err(dev, "Can not parse %s\n", name); - return ret; - } - - reg->offset = buffer[0]; - reg->enable_bit = buffer[1]; - reg->write_enable = buffer[2]; - return 0; -} - -static int tcphy_parse_dt(struct rockchip_typec_phy *tcphy, - struct device *dev) -{ - struct rockchip_usb3phy_port_cfg *cfg = &tcphy->port_cfgs; - int ret; - - ret = tcphy_get_param(dev, &cfg->typec_conn_dir, - "rockchip,typec-conn-dir"); - if (ret) - return ret; - - ret = tcphy_get_param(dev, &cfg->usb3tousb2_en, - "rockchip,usb3tousb2-en"); - if (ret) - return ret; - - ret = tcphy_get_param(dev, &cfg->external_psm, - "rockchip,external-psm"); - if (ret) - return ret; - - ret = tcphy_get_param(dev, &cfg->pipe_status, - "rockchip,pipe-status"); - if (ret) - return ret; - - tcphy->grf_regs = syscon_regmap_lookup_by_phandle(dev->of_node, - "rockchip,grf"); - if (IS_ERR(tcphy->grf_regs)) { - dev_err(dev, "could not find grf dt node\n"); - return PTR_ERR(tcphy->grf_regs); - } - - tcphy->clk_core = devm_clk_get(dev, "tcpdcore"); - if (IS_ERR(tcphy->clk_core)) { - dev_err(dev, "could not get uphy core clock\n"); - return PTR_ERR(tcphy->clk_core); - } - - tcphy->clk_ref = devm_clk_get(dev, "tcpdphy-ref"); - if (IS_ERR(tcphy->clk_ref)) { - dev_err(dev, "could not get uphy ref clock\n"); - return PTR_ERR(tcphy->clk_ref); - } - - tcphy->uphy_rst = devm_reset_control_get(dev, "uphy"); - if (IS_ERR(tcphy->uphy_rst)) { - dev_err(dev, "no uphy_rst reset control found\n"); - return PTR_ERR(tcphy->uphy_rst); - } - - tcphy->pipe_rst = devm_reset_control_get(dev, "uphy-pipe"); - if (IS_ERR(tcphy->pipe_rst)) { - dev_err(dev, "no pipe_rst reset control found\n"); - return PTR_ERR(tcphy->pipe_rst); - } - - tcphy->tcphy_rst = devm_reset_control_get(dev, "uphy-tcphy"); - if (IS_ERR(tcphy->tcphy_rst)) { - dev_err(dev, "no tcphy_rst reset control found\n"); - return PTR_ERR(tcphy->tcphy_rst); - } - - return 0; -} - -static void typec_phy_pre_init(struct rockchip_typec_phy *tcphy) -{ - struct rockchip_usb3phy_port_cfg *cfg = &tcphy->port_cfgs; - - reset_control_assert(tcphy->tcphy_rst); - reset_control_assert(tcphy->uphy_rst); - reset_control_assert(tcphy->pipe_rst); - - /* select external psm clock */ - property_enable(tcphy, &cfg->external_psm, 1); - property_enable(tcphy, &cfg->usb3tousb2_en, 0); - - tcphy->mode = MODE_DISCONNECT; -} - -static int rockchip_typec_phy_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct device_node *np = dev->of_node; - struct device_node *child_np; - struct rockchip_typec_phy *tcphy; - struct phy_provider *phy_provider; - struct resource *res; - int ret; - - tcphy = devm_kzalloc(dev, sizeof(*tcphy), GFP_KERNEL); - if (!tcphy) - return -ENOMEM; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - tcphy->base = devm_ioremap_resource(dev, res); - if (IS_ERR(tcphy->base)) - return PTR_ERR(tcphy->base); - - ret = tcphy_parse_dt(tcphy, dev); - if (ret) - return ret; - - tcphy->dev = dev; - platform_set_drvdata(pdev, tcphy); - mutex_init(&tcphy->lock); - - typec_phy_pre_init(tcphy); - - tcphy->extcon = extcon_get_edev_by_phandle(dev, 0); - if (IS_ERR(tcphy->extcon)) { - if (PTR_ERR(tcphy->extcon) != -EPROBE_DEFER) - dev_err(dev, "Invalid or missing extcon\n"); - return PTR_ERR(tcphy->extcon); - } - - pm_runtime_enable(dev); - - for_each_available_child_of_node(np, child_np) { - struct phy *phy; - - if (!of_node_cmp(child_np->name, "dp-port")) - phy = devm_phy_create(dev, child_np, - &rockchip_dp_phy_ops); - else if (!of_node_cmp(child_np->name, "usb3-port")) - phy = devm_phy_create(dev, child_np, - &rockchip_usb3_phy_ops); - else - continue; - - if (IS_ERR(phy)) { - dev_err(dev, "failed to create phy: %s\n", - child_np->name); - return PTR_ERR(phy); - } - - phy_set_drvdata(phy, tcphy); - } - - phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); - if (IS_ERR(phy_provider)) { - dev_err(dev, "Failed to register phy provider\n"); - return PTR_ERR(phy_provider); - } - - return 0; -} - -static int rockchip_typec_phy_remove(struct platform_device *pdev) -{ - pm_runtime_disable(&pdev->dev); - - return 0; -} - -static const struct of_device_id rockchip_typec_phy_dt_ids[] = { - { .compatible = "rockchip,rk3399-typec-phy" }, - {} -}; - -MODULE_DEVICE_TABLE(of, rockchip_typec_phy_dt_ids); - -static struct platform_driver rockchip_typec_phy_driver = { - .probe = rockchip_typec_phy_probe, - .remove = rockchip_typec_phy_remove, - .driver = { - .name = "rockchip-typec-phy", - .of_match_table = rockchip_typec_phy_dt_ids, - }, -}; - -module_platform_driver(rockchip_typec_phy_driver); - -MODULE_AUTHOR("Chris Zhong "); -MODULE_AUTHOR("Kever Yang "); -MODULE_DESCRIPTION("Rockchip USB TYPE-C PHY driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-rockchip-usb.c b/drivers/phy/phy-rockchip-usb.c deleted file mode 100644 index 3378eeb7a562..000000000000 --- a/drivers/phy/phy-rockchip-usb.c +++ /dev/null @@ -1,540 +0,0 @@ -/* - * Rockchip usb PHY driver - * - * Copyright (C) 2014 Yunzhi Li - * Copyright (C) 2014 ROCKCHIP, Inc. - * - * 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; either 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. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static int enable_usb_uart; - -#define HIWORD_UPDATE(val, mask) \ - ((val) | (mask) << 16) - -#define UOC_CON0_SIDDQ BIT(13) - -struct rockchip_usb_phys { - int reg; - const char *pll_name; -}; - -struct rockchip_usb_phy_base; -struct rockchip_usb_phy_pdata { - struct rockchip_usb_phys *phys; - int (*init_usb_uart)(struct regmap *grf); - int usb_uart_phy; -}; - -struct rockchip_usb_phy_base { - struct device *dev; - struct regmap *reg_base; - const struct rockchip_usb_phy_pdata *pdata; -}; - -struct rockchip_usb_phy { - struct rockchip_usb_phy_base *base; - struct device_node *np; - unsigned int reg_offset; - struct clk *clk; - struct clk *clk480m; - struct clk_hw clk480m_hw; - struct phy *phy; - bool uart_enabled; - struct reset_control *reset; - struct regulator *vbus; -}; - -static int rockchip_usb_phy_power(struct rockchip_usb_phy *phy, - bool siddq) -{ - u32 val = HIWORD_UPDATE(siddq ? UOC_CON0_SIDDQ : 0, UOC_CON0_SIDDQ); - - return regmap_write(phy->base->reg_base, phy->reg_offset, val); -} - -static unsigned long rockchip_usb_phy480m_recalc_rate(struct clk_hw *hw, - unsigned long parent_rate) -{ - return 480000000; -} - -static void rockchip_usb_phy480m_disable(struct clk_hw *hw) -{ - struct rockchip_usb_phy *phy = container_of(hw, - struct rockchip_usb_phy, - clk480m_hw); - - if (phy->vbus) - regulator_disable(phy->vbus); - - /* Power down usb phy analog blocks by set siddq 1 */ - rockchip_usb_phy_power(phy, 1); -} - -static int rockchip_usb_phy480m_enable(struct clk_hw *hw) -{ - struct rockchip_usb_phy *phy = container_of(hw, - struct rockchip_usb_phy, - clk480m_hw); - - /* Power up usb phy analog blocks by set siddq 0 */ - return rockchip_usb_phy_power(phy, 0); -} - -static int rockchip_usb_phy480m_is_enabled(struct clk_hw *hw) -{ - struct rockchip_usb_phy *phy = container_of(hw, - struct rockchip_usb_phy, - clk480m_hw); - int ret; - u32 val; - - ret = regmap_read(phy->base->reg_base, phy->reg_offset, &val); - if (ret < 0) - return ret; - - return (val & UOC_CON0_SIDDQ) ? 0 : 1; -} - -static const struct clk_ops rockchip_usb_phy480m_ops = { - .enable = rockchip_usb_phy480m_enable, - .disable = rockchip_usb_phy480m_disable, - .is_enabled = rockchip_usb_phy480m_is_enabled, - .recalc_rate = rockchip_usb_phy480m_recalc_rate, -}; - -static int rockchip_usb_phy_power_off(struct phy *_phy) -{ - struct rockchip_usb_phy *phy = phy_get_drvdata(_phy); - - if (phy->uart_enabled) - return -EBUSY; - - clk_disable_unprepare(phy->clk480m); - - return 0; -} - -static int rockchip_usb_phy_power_on(struct phy *_phy) -{ - struct rockchip_usb_phy *phy = phy_get_drvdata(_phy); - - if (phy->uart_enabled) - return -EBUSY; - - if (phy->vbus) { - int ret; - - ret = regulator_enable(phy->vbus); - if (ret) - return ret; - } - - return clk_prepare_enable(phy->clk480m); -} - -static int rockchip_usb_phy_reset(struct phy *_phy) -{ - struct rockchip_usb_phy *phy = phy_get_drvdata(_phy); - - if (phy->reset) { - reset_control_assert(phy->reset); - udelay(10); - reset_control_deassert(phy->reset); - } - - return 0; -} - -static const struct phy_ops ops = { - .power_on = rockchip_usb_phy_power_on, - .power_off = rockchip_usb_phy_power_off, - .reset = rockchip_usb_phy_reset, - .owner = THIS_MODULE, -}; - -static void rockchip_usb_phy_action(void *data) -{ - struct rockchip_usb_phy *rk_phy = data; - - if (!rk_phy->uart_enabled) { - of_clk_del_provider(rk_phy->np); - clk_unregister(rk_phy->clk480m); - } - - if (rk_phy->clk) - clk_put(rk_phy->clk); -} - -static int rockchip_usb_phy_init(struct rockchip_usb_phy_base *base, - struct device_node *child) -{ - struct rockchip_usb_phy *rk_phy; - unsigned int reg_offset; - const char *clk_name; - struct clk_init_data init; - int err, i; - - rk_phy = devm_kzalloc(base->dev, sizeof(*rk_phy), GFP_KERNEL); - if (!rk_phy) - return -ENOMEM; - - rk_phy->base = base; - rk_phy->np = child; - - if (of_property_read_u32(child, "reg", ®_offset)) { - dev_err(base->dev, "missing reg property in node %s\n", - child->name); - return -EINVAL; - } - - rk_phy->reset = of_reset_control_get(child, "phy-reset"); - if (IS_ERR(rk_phy->reset)) - rk_phy->reset = NULL; - - rk_phy->reg_offset = reg_offset; - - rk_phy->clk = of_clk_get_by_name(child, "phyclk"); - if (IS_ERR(rk_phy->clk)) - rk_phy->clk = NULL; - - i = 0; - init.name = NULL; - while (base->pdata->phys[i].reg) { - if (base->pdata->phys[i].reg == reg_offset) { - init.name = base->pdata->phys[i].pll_name; - break; - } - i++; - } - - if (!init.name) { - dev_err(base->dev, "phy data not found\n"); - return -EINVAL; - } - - if (enable_usb_uart && base->pdata->usb_uart_phy == i) { - dev_dbg(base->dev, "phy%d used as uart output\n", i); - rk_phy->uart_enabled = true; - } else { - if (rk_phy->clk) { - clk_name = __clk_get_name(rk_phy->clk); - init.flags = 0; - init.parent_names = &clk_name; - init.num_parents = 1; - } else { - init.flags = 0; - init.parent_names = NULL; - init.num_parents = 0; - } - - init.ops = &rockchip_usb_phy480m_ops; - rk_phy->clk480m_hw.init = &init; - - rk_phy->clk480m = clk_register(base->dev, &rk_phy->clk480m_hw); - if (IS_ERR(rk_phy->clk480m)) { - err = PTR_ERR(rk_phy->clk480m); - goto err_clk; - } - - err = of_clk_add_provider(child, of_clk_src_simple_get, - rk_phy->clk480m); - if (err < 0) - goto err_clk_prov; - } - - err = devm_add_action_or_reset(base->dev, rockchip_usb_phy_action, - rk_phy); - if (err) - return err; - - rk_phy->phy = devm_phy_create(base->dev, child, &ops); - if (IS_ERR(rk_phy->phy)) { - dev_err(base->dev, "failed to create PHY\n"); - return PTR_ERR(rk_phy->phy); - } - phy_set_drvdata(rk_phy->phy, rk_phy); - - rk_phy->vbus = devm_regulator_get_optional(&rk_phy->phy->dev, "vbus"); - if (IS_ERR(rk_phy->vbus)) { - if (PTR_ERR(rk_phy->vbus) == -EPROBE_DEFER) - return PTR_ERR(rk_phy->vbus); - rk_phy->vbus = NULL; - } - - /* - * When acting as uart-pipe, just keep clock on otherwise - * only power up usb phy when it use, so disable it when init - */ - if (rk_phy->uart_enabled) - return clk_prepare_enable(rk_phy->clk); - else - return rockchip_usb_phy_power(rk_phy, 1); - -err_clk_prov: - if (!rk_phy->uart_enabled) - clk_unregister(rk_phy->clk480m); -err_clk: - if (rk_phy->clk) - clk_put(rk_phy->clk); - return err; -} - -static const struct rockchip_usb_phy_pdata rk3066a_pdata = { - .phys = (struct rockchip_usb_phys[]){ - { .reg = 0x17c, .pll_name = "sclk_otgphy0_480m" }, - { .reg = 0x188, .pll_name = "sclk_otgphy1_480m" }, - { /* sentinel */ } - }, -}; - -static const struct rockchip_usb_phy_pdata rk3188_pdata = { - .phys = (struct rockchip_usb_phys[]){ - { .reg = 0x10c, .pll_name = "sclk_otgphy0_480m" }, - { .reg = 0x11c, .pll_name = "sclk_otgphy1_480m" }, - { /* sentinel */ } - }, -}; - -#define RK3288_UOC0_CON0 0x320 -#define RK3288_UOC0_CON0_COMMON_ON_N BIT(0) -#define RK3288_UOC0_CON0_DISABLE BIT(4) - -#define RK3288_UOC0_CON2 0x328 -#define RK3288_UOC0_CON2_SOFT_CON_SEL BIT(2) - -#define RK3288_UOC0_CON3 0x32c -#define RK3288_UOC0_CON3_UTMI_SUSPENDN BIT(0) -#define RK3288_UOC0_CON3_UTMI_OPMODE_NODRIVING (1 << 1) -#define RK3288_UOC0_CON3_UTMI_OPMODE_MASK (3 << 1) -#define RK3288_UOC0_CON3_UTMI_XCVRSEELCT_FSTRANSC (1 << 3) -#define RK3288_UOC0_CON3_UTMI_XCVRSEELCT_MASK (3 << 3) -#define RK3288_UOC0_CON3_UTMI_TERMSEL_FULLSPEED BIT(5) -#define RK3288_UOC0_CON3_BYPASSDMEN BIT(6) -#define RK3288_UOC0_CON3_BYPASSSEL BIT(7) - -/* - * Enable the bypass of uart2 data through the otg usb phy. - * Original description in the TRM. - * 1. Disable the OTG block by setting OTGDISABLE0 to 1’b1. - * 2. Disable the pull-up resistance on the D+ line by setting - * OPMODE0[1:0] to 2’b01. - * 3. To ensure that the XO, Bias, and PLL blocks are powered down in Suspend - * mode, set COMMONONN to 1’b1. - * 4. Place the USB PHY in Suspend mode by setting SUSPENDM0 to 1’b0. - * 5. Set BYPASSSEL0 to 1’b1. - * 6. To transmit data, controls BYPASSDMEN0, and BYPASSDMDATA0. - * To receive data, monitor FSVPLUS0. - * - * The actual code in the vendor kernel does some things differently. - */ -static int __init rk3288_init_usb_uart(struct regmap *grf) -{ - u32 val; - int ret; - - /* - * COMMON_ON and DISABLE settings are described in the TRM, - * but were not present in the original code. - * Also disable the analog phy components to save power. - */ - val = HIWORD_UPDATE(RK3288_UOC0_CON0_COMMON_ON_N - | RK3288_UOC0_CON0_DISABLE - | UOC_CON0_SIDDQ, - RK3288_UOC0_CON0_COMMON_ON_N - | RK3288_UOC0_CON0_DISABLE - | UOC_CON0_SIDDQ); - ret = regmap_write(grf, RK3288_UOC0_CON0, val); - if (ret) - return ret; - - val = HIWORD_UPDATE(RK3288_UOC0_CON2_SOFT_CON_SEL, - RK3288_UOC0_CON2_SOFT_CON_SEL); - ret = regmap_write(grf, RK3288_UOC0_CON2, val); - if (ret) - return ret; - - val = HIWORD_UPDATE(RK3288_UOC0_CON3_UTMI_OPMODE_NODRIVING - | RK3288_UOC0_CON3_UTMI_XCVRSEELCT_FSTRANSC - | RK3288_UOC0_CON3_UTMI_TERMSEL_FULLSPEED, - RK3288_UOC0_CON3_UTMI_SUSPENDN - | RK3288_UOC0_CON3_UTMI_OPMODE_MASK - | RK3288_UOC0_CON3_UTMI_XCVRSEELCT_MASK - | RK3288_UOC0_CON3_UTMI_TERMSEL_FULLSPEED); - ret = regmap_write(grf, RK3288_UOC0_CON3, val); - if (ret) - return ret; - - val = HIWORD_UPDATE(RK3288_UOC0_CON3_BYPASSSEL - | RK3288_UOC0_CON3_BYPASSDMEN, - RK3288_UOC0_CON3_BYPASSSEL - | RK3288_UOC0_CON3_BYPASSDMEN); - ret = regmap_write(grf, RK3288_UOC0_CON3, val); - if (ret) - return ret; - - return 0; -} - -static const struct rockchip_usb_phy_pdata rk3288_pdata = { - .phys = (struct rockchip_usb_phys[]){ - { .reg = 0x320, .pll_name = "sclk_otgphy0_480m" }, - { .reg = 0x334, .pll_name = "sclk_otgphy1_480m" }, - { .reg = 0x348, .pll_name = "sclk_otgphy2_480m" }, - { /* sentinel */ } - }, - .init_usb_uart = rk3288_init_usb_uart, - .usb_uart_phy = 0, -}; - -static int rockchip_usb_phy_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct rockchip_usb_phy_base *phy_base; - struct phy_provider *phy_provider; - const struct of_device_id *match; - struct device_node *child; - int err; - - phy_base = devm_kzalloc(dev, sizeof(*phy_base), GFP_KERNEL); - if (!phy_base) - return -ENOMEM; - - match = of_match_device(dev->driver->of_match_table, dev); - if (!match || !match->data) { - dev_err(dev, "missing phy data\n"); - return -EINVAL; - } - - phy_base->pdata = match->data; - - phy_base->dev = dev; - phy_base->reg_base = ERR_PTR(-ENODEV); - if (dev->parent && dev->parent->of_node) - phy_base->reg_base = syscon_node_to_regmap( - dev->parent->of_node); - if (IS_ERR(phy_base->reg_base)) - phy_base->reg_base = syscon_regmap_lookup_by_phandle( - dev->of_node, "rockchip,grf"); - if (IS_ERR(phy_base->reg_base)) { - dev_err(&pdev->dev, "Missing rockchip,grf property\n"); - return PTR_ERR(phy_base->reg_base); - } - - for_each_available_child_of_node(dev->of_node, child) { - err = rockchip_usb_phy_init(phy_base, child); - if (err) { - of_node_put(child); - return err; - } - } - - phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); - return PTR_ERR_OR_ZERO(phy_provider); -} - -static const struct of_device_id rockchip_usb_phy_dt_ids[] = { - { .compatible = "rockchip,rk3066a-usb-phy", .data = &rk3066a_pdata }, - { .compatible = "rockchip,rk3188-usb-phy", .data = &rk3188_pdata }, - { .compatible = "rockchip,rk3288-usb-phy", .data = &rk3288_pdata }, - {} -}; - -MODULE_DEVICE_TABLE(of, rockchip_usb_phy_dt_ids); - -static struct platform_driver rockchip_usb_driver = { - .probe = rockchip_usb_phy_probe, - .driver = { - .name = "rockchip-usb-phy", - .of_match_table = rockchip_usb_phy_dt_ids, - }, -}; - -module_platform_driver(rockchip_usb_driver); - -#ifndef MODULE -static int __init rockchip_init_usb_uart(void) -{ - const struct of_device_id *match; - const struct rockchip_usb_phy_pdata *data; - struct device_node *np; - struct regmap *grf; - int ret; - - if (!enable_usb_uart) - return 0; - - np = of_find_matching_node_and_match(NULL, rockchip_usb_phy_dt_ids, - &match); - if (!np) { - pr_err("%s: failed to find usbphy node\n", __func__); - return -ENOTSUPP; - } - - pr_debug("%s: using settings for %s\n", __func__, match->compatible); - data = match->data; - - if (!data->init_usb_uart) { - pr_err("%s: usb-uart not available on %s\n", - __func__, match->compatible); - return -ENOTSUPP; - } - - grf = ERR_PTR(-ENODEV); - if (np->parent) - grf = syscon_node_to_regmap(np->parent); - if (IS_ERR(grf)) - grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf"); - if (IS_ERR(grf)) { - pr_err("%s: Missing rockchip,grf property, %lu\n", - __func__, PTR_ERR(grf)); - return PTR_ERR(grf); - } - - ret = data->init_usb_uart(grf); - if (ret) { - pr_err("%s: could not init usb_uart, %d\n", __func__, ret); - enable_usb_uart = 0; - return ret; - } - - return 0; -} -early_initcall(rockchip_init_usb_uart); - -static int __init rockchip_usb_uart(char *buf) -{ - enable_usb_uart = true; - return 0; -} -early_param("rockchip.usb_uart", rockchip_usb_uart); -#endif - -MODULE_AUTHOR("Yunzhi Li "); -MODULE_DESCRIPTION("Rockchip USB 2.0 PHY driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-s5pv210-usb2.c b/drivers/phy/phy-s5pv210-usb2.c deleted file mode 100644 index f6f72339bbc3..000000000000 --- a/drivers/phy/phy-s5pv210-usb2.c +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Samsung SoC USB 1.1/2.0 PHY driver - S5PV210 support - * - * Copyright (C) 2013 Samsung Electronics Co., Ltd. - * Authors: Kamil Debski - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include -#include -#include -#include "phy-samsung-usb2.h" - -/* Exynos USB PHY registers */ - -/* PHY power control */ -#define S5PV210_UPHYPWR 0x0 - -#define S5PV210_UPHYPWR_PHY0_SUSPEND BIT(0) -#define S5PV210_UPHYPWR_PHY0_PWR BIT(3) -#define S5PV210_UPHYPWR_PHY0_OTG_PWR BIT(4) -#define S5PV210_UPHYPWR_PHY0 ( \ - S5PV210_UPHYPWR_PHY0_SUSPEND | \ - S5PV210_UPHYPWR_PHY0_PWR | \ - S5PV210_UPHYPWR_PHY0_OTG_PWR) - -#define S5PV210_UPHYPWR_PHY1_SUSPEND BIT(6) -#define S5PV210_UPHYPWR_PHY1_PWR BIT(7) -#define S5PV210_UPHYPWR_PHY1 ( \ - S5PV210_UPHYPWR_PHY1_SUSPEND | \ - S5PV210_UPHYPWR_PHY1_PWR) - -/* PHY clock control */ -#define S5PV210_UPHYCLK 0x4 - -#define S5PV210_UPHYCLK_PHYFSEL_MASK (0x3 << 0) -#define S5PV210_UPHYCLK_PHYFSEL_48MHZ (0x0 << 0) -#define S5PV210_UPHYCLK_PHYFSEL_24MHZ (0x3 << 0) -#define S5PV210_UPHYCLK_PHYFSEL_12MHZ (0x2 << 0) - -#define S5PV210_UPHYCLK_PHY0_ID_PULLUP BIT(2) -#define S5PV210_UPHYCLK_PHY0_COMMON_ON BIT(4) -#define S5PV210_UPHYCLK_PHY1_COMMON_ON BIT(7) - -/* PHY reset control */ -#define S5PV210_UPHYRST 0x8 - -#define S5PV210_URSTCON_PHY0 BIT(0) -#define S5PV210_URSTCON_OTG_HLINK BIT(1) -#define S5PV210_URSTCON_OTG_PHYLINK BIT(2) -#define S5PV210_URSTCON_PHY1_ALL BIT(3) -#define S5PV210_URSTCON_HOST_LINK_ALL BIT(4) - -/* Isolation, configured in the power management unit */ -#define S5PV210_USB_ISOL_OFFSET 0x680c -#define S5PV210_USB_ISOL_DEVICE BIT(0) -#define S5PV210_USB_ISOL_HOST BIT(1) - - -enum s5pv210_phy_id { - S5PV210_DEVICE, - S5PV210_HOST, - S5PV210_NUM_PHYS, -}; - -/* - * s5pv210_rate_to_clk() converts the supplied clock rate to the value that - * can be written to the phy register. - */ -static int s5pv210_rate_to_clk(unsigned long rate, u32 *reg) -{ - switch (rate) { - case 12 * MHZ: - *reg = S5PV210_UPHYCLK_PHYFSEL_12MHZ; - break; - case 24 * MHZ: - *reg = S5PV210_UPHYCLK_PHYFSEL_24MHZ; - break; - case 48 * MHZ: - *reg = S5PV210_UPHYCLK_PHYFSEL_48MHZ; - break; - default: - return -EINVAL; - } - - return 0; -} - -static void s5pv210_isol(struct samsung_usb2_phy_instance *inst, bool on) -{ - struct samsung_usb2_phy_driver *drv = inst->drv; - u32 mask; - - switch (inst->cfg->id) { - case S5PV210_DEVICE: - mask = S5PV210_USB_ISOL_DEVICE; - break; - case S5PV210_HOST: - mask = S5PV210_USB_ISOL_HOST; - break; - default: - return; - } - - regmap_update_bits(drv->reg_pmu, S5PV210_USB_ISOL_OFFSET, - mask, on ? 0 : mask); -} - -static void s5pv210_phy_pwr(struct samsung_usb2_phy_instance *inst, bool on) -{ - struct samsung_usb2_phy_driver *drv = inst->drv; - u32 rstbits = 0; - u32 phypwr = 0; - u32 rst; - u32 pwr; - - switch (inst->cfg->id) { - case S5PV210_DEVICE: - phypwr = S5PV210_UPHYPWR_PHY0; - rstbits = S5PV210_URSTCON_PHY0; - break; - case S5PV210_HOST: - phypwr = S5PV210_UPHYPWR_PHY1; - rstbits = S5PV210_URSTCON_PHY1_ALL | - S5PV210_URSTCON_HOST_LINK_ALL; - break; - } - - if (on) { - writel(drv->ref_reg_val, drv->reg_phy + S5PV210_UPHYCLK); - - pwr = readl(drv->reg_phy + S5PV210_UPHYPWR); - pwr &= ~phypwr; - writel(pwr, drv->reg_phy + S5PV210_UPHYPWR); - - rst = readl(drv->reg_phy + S5PV210_UPHYRST); - rst |= rstbits; - writel(rst, drv->reg_phy + S5PV210_UPHYRST); - udelay(10); - rst &= ~rstbits; - writel(rst, drv->reg_phy + S5PV210_UPHYRST); - } else { - pwr = readl(drv->reg_phy + S5PV210_UPHYPWR); - pwr |= phypwr; - writel(pwr, drv->reg_phy + S5PV210_UPHYPWR); - } -} - -static int s5pv210_power_on(struct samsung_usb2_phy_instance *inst) -{ - s5pv210_isol(inst, 0); - s5pv210_phy_pwr(inst, 1); - - return 0; -} - -static int s5pv210_power_off(struct samsung_usb2_phy_instance *inst) -{ - s5pv210_phy_pwr(inst, 0); - s5pv210_isol(inst, 1); - - return 0; -} - -static const struct samsung_usb2_common_phy s5pv210_phys[S5PV210_NUM_PHYS] = { - [S5PV210_DEVICE] = { - .label = "device", - .id = S5PV210_DEVICE, - .power_on = s5pv210_power_on, - .power_off = s5pv210_power_off, - }, - [S5PV210_HOST] = { - .label = "host", - .id = S5PV210_HOST, - .power_on = s5pv210_power_on, - .power_off = s5pv210_power_off, - }, -}; - -const struct samsung_usb2_phy_config s5pv210_usb2_phy_config = { - .num_phys = ARRAY_SIZE(s5pv210_phys), - .phys = s5pv210_phys, - .rate_to_clk = s5pv210_rate_to_clk, -}; diff --git a/drivers/phy/phy-samsung-usb2.c b/drivers/phy/phy-samsung-usb2.c deleted file mode 100644 index 1d22d93b552d..000000000000 --- a/drivers/phy/phy-samsung-usb2.c +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Samsung SoC USB 1.1/2.0 PHY driver - * - * Copyright (C) 2013 Samsung Electronics Co., Ltd. - * Author: Kamil Debski - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include "phy-samsung-usb2.h" - -static int samsung_usb2_phy_power_on(struct phy *phy) -{ - struct samsung_usb2_phy_instance *inst = phy_get_drvdata(phy); - struct samsung_usb2_phy_driver *drv = inst->drv; - int ret; - - dev_dbg(drv->dev, "Request to power_on \"%s\" usb phy\n", - inst->cfg->label); - - if (drv->vbus) { - ret = regulator_enable(drv->vbus); - if (ret) - goto err_regulator; - } - - ret = clk_prepare_enable(drv->clk); - if (ret) - goto err_main_clk; - ret = clk_prepare_enable(drv->ref_clk); - if (ret) - goto err_instance_clk; - if (inst->cfg->power_on) { - spin_lock(&drv->lock); - ret = inst->cfg->power_on(inst); - spin_unlock(&drv->lock); - if (ret) - goto err_power_on; - } - - return 0; - -err_power_on: - clk_disable_unprepare(drv->ref_clk); -err_instance_clk: - clk_disable_unprepare(drv->clk); -err_main_clk: - if (drv->vbus) - regulator_disable(drv->vbus); -err_regulator: - return ret; -} - -static int samsung_usb2_phy_power_off(struct phy *phy) -{ - struct samsung_usb2_phy_instance *inst = phy_get_drvdata(phy); - struct samsung_usb2_phy_driver *drv = inst->drv; - int ret = 0; - - dev_dbg(drv->dev, "Request to power_off \"%s\" usb phy\n", - inst->cfg->label); - if (inst->cfg->power_off) { - spin_lock(&drv->lock); - ret = inst->cfg->power_off(inst); - spin_unlock(&drv->lock); - if (ret) - return ret; - } - clk_disable_unprepare(drv->ref_clk); - clk_disable_unprepare(drv->clk); - if (drv->vbus) - ret = regulator_disable(drv->vbus); - - return ret; -} - -static const struct phy_ops samsung_usb2_phy_ops = { - .power_on = samsung_usb2_phy_power_on, - .power_off = samsung_usb2_phy_power_off, - .owner = THIS_MODULE, -}; - -static struct phy *samsung_usb2_phy_xlate(struct device *dev, - struct of_phandle_args *args) -{ - struct samsung_usb2_phy_driver *drv; - - drv = dev_get_drvdata(dev); - if (!drv) - return ERR_PTR(-EINVAL); - - if (WARN_ON(args->args[0] >= drv->cfg->num_phys)) - return ERR_PTR(-ENODEV); - - return drv->instances[args->args[0]].phy; -} - -static const struct of_device_id samsung_usb2_phy_of_match[] = { -#ifdef CONFIG_PHY_EXYNOS4X12_USB2 - { - .compatible = "samsung,exynos3250-usb2-phy", - .data = &exynos3250_usb2_phy_config, - }, -#endif -#ifdef CONFIG_PHY_EXYNOS4210_USB2 - { - .compatible = "samsung,exynos4210-usb2-phy", - .data = &exynos4210_usb2_phy_config, - }, -#endif -#ifdef CONFIG_PHY_EXYNOS4X12_USB2 - { - .compatible = "samsung,exynos4x12-usb2-phy", - .data = &exynos4x12_usb2_phy_config, - }, -#endif -#ifdef CONFIG_PHY_EXYNOS5250_USB2 - { - .compatible = "samsung,exynos5250-usb2-phy", - .data = &exynos5250_usb2_phy_config, - }, -#endif -#ifdef CONFIG_PHY_S5PV210_USB2 - { - .compatible = "samsung,s5pv210-usb2-phy", - .data = &s5pv210_usb2_phy_config, - }, -#endif - { }, -}; -MODULE_DEVICE_TABLE(of, samsung_usb2_phy_of_match); - -static int samsung_usb2_phy_probe(struct platform_device *pdev) -{ - const struct of_device_id *match; - const struct samsung_usb2_phy_config *cfg; - struct device *dev = &pdev->dev; - struct phy_provider *phy_provider; - struct resource *mem; - struct samsung_usb2_phy_driver *drv; - int i, ret; - - if (!pdev->dev.of_node) { - dev_err(dev, "This driver is required to be instantiated from device tree\n"); - return -EINVAL; - } - - match = of_match_node(samsung_usb2_phy_of_match, pdev->dev.of_node); - if (!match) { - dev_err(dev, "of_match_node() failed\n"); - return -EINVAL; - } - cfg = match->data; - - drv = devm_kzalloc(dev, sizeof(struct samsung_usb2_phy_driver) + - cfg->num_phys * sizeof(struct samsung_usb2_phy_instance), - GFP_KERNEL); - if (!drv) - return -ENOMEM; - - dev_set_drvdata(dev, drv); - spin_lock_init(&drv->lock); - - drv->cfg = cfg; - drv->dev = dev; - - mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); - drv->reg_phy = devm_ioremap_resource(dev, mem); - if (IS_ERR(drv->reg_phy)) { - dev_err(dev, "Failed to map register memory (phy)\n"); - return PTR_ERR(drv->reg_phy); - } - - drv->reg_pmu = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, - "samsung,pmureg-phandle"); - if (IS_ERR(drv->reg_pmu)) { - dev_err(dev, "Failed to map PMU registers (via syscon)\n"); - return PTR_ERR(drv->reg_pmu); - } - - if (drv->cfg->has_mode_switch) { - drv->reg_sys = syscon_regmap_lookup_by_phandle( - pdev->dev.of_node, "samsung,sysreg-phandle"); - if (IS_ERR(drv->reg_sys)) { - dev_err(dev, "Failed to map system registers (via syscon)\n"); - return PTR_ERR(drv->reg_sys); - } - } - - drv->clk = devm_clk_get(dev, "phy"); - if (IS_ERR(drv->clk)) { - dev_err(dev, "Failed to get clock of phy controller\n"); - return PTR_ERR(drv->clk); - } - - drv->ref_clk = devm_clk_get(dev, "ref"); - if (IS_ERR(drv->ref_clk)) { - dev_err(dev, "Failed to get reference clock for the phy controller\n"); - return PTR_ERR(drv->ref_clk); - } - - drv->ref_rate = clk_get_rate(drv->ref_clk); - if (drv->cfg->rate_to_clk) { - ret = drv->cfg->rate_to_clk(drv->ref_rate, &drv->ref_reg_val); - if (ret) - return ret; - } - - drv->vbus = devm_regulator_get(dev, "vbus"); - if (IS_ERR(drv->vbus)) { - ret = PTR_ERR(drv->vbus); - if (ret == -EPROBE_DEFER) - return ret; - drv->vbus = NULL; - } - - for (i = 0; i < drv->cfg->num_phys; i++) { - char *label = drv->cfg->phys[i].label; - struct samsung_usb2_phy_instance *p = &drv->instances[i]; - - dev_dbg(dev, "Creating phy \"%s\"\n", label); - p->phy = devm_phy_create(dev, NULL, &samsung_usb2_phy_ops); - if (IS_ERR(p->phy)) { - dev_err(drv->dev, "Failed to create usb2_phy \"%s\"\n", - label); - return PTR_ERR(p->phy); - } - - p->cfg = &drv->cfg->phys[i]; - p->drv = drv; - phy_set_bus_width(p->phy, 8); - phy_set_drvdata(p->phy, p); - } - - phy_provider = devm_of_phy_provider_register(dev, - samsung_usb2_phy_xlate); - if (IS_ERR(phy_provider)) { - dev_err(drv->dev, "Failed to register phy provider\n"); - return PTR_ERR(phy_provider); - } - - return 0; -} - -static struct platform_driver samsung_usb2_phy_driver = { - .probe = samsung_usb2_phy_probe, - .driver = { - .of_match_table = samsung_usb2_phy_of_match, - .name = "samsung-usb2-phy", - } -}; - -module_platform_driver(samsung_usb2_phy_driver); -MODULE_DESCRIPTION("Samsung S5P/EXYNOS SoC USB PHY driver"); -MODULE_AUTHOR("Kamil Debski "); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:samsung-usb2-phy"); diff --git a/drivers/phy/phy-samsung-usb2.h b/drivers/phy/phy-samsung-usb2.h deleted file mode 100644 index 6563e7ca0ac4..000000000000 --- a/drivers/phy/phy-samsung-usb2.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Samsung SoC USB 1.1/2.0 PHY driver - * - * Copyright (C) 2013 Samsung Electronics Co., Ltd. - * Author: Kamil Debski - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#ifndef _PHY_EXYNOS_USB2_H -#define _PHY_EXYNOS_USB2_H - -#include -#include -#include -#include -#include -#include - -#define KHZ 1000 -#define MHZ (KHZ * KHZ) - -struct samsung_usb2_phy_driver; -struct samsung_usb2_phy_instance; -struct samsung_usb2_phy_config; - -struct samsung_usb2_phy_instance { - const struct samsung_usb2_common_phy *cfg; - struct phy *phy; - struct samsung_usb2_phy_driver *drv; - int int_cnt; - int ext_cnt; -}; - -struct samsung_usb2_phy_driver { - const struct samsung_usb2_phy_config *cfg; - struct clk *clk; - struct clk *ref_clk; - struct regulator *vbus; - unsigned long ref_rate; - u32 ref_reg_val; - struct device *dev; - void __iomem *reg_phy; - struct regmap *reg_pmu; - struct regmap *reg_sys; - spinlock_t lock; - struct samsung_usb2_phy_instance instances[0]; -}; - -struct samsung_usb2_common_phy { - int (*power_on)(struct samsung_usb2_phy_instance *); - int (*power_off)(struct samsung_usb2_phy_instance *); - unsigned int id; - char *label; -}; - - -struct samsung_usb2_phy_config { - const struct samsung_usb2_common_phy *phys; - int (*rate_to_clk)(unsigned long, u32 *); - unsigned int num_phys; - bool has_mode_switch; - bool has_refclk_sel; -}; - -extern const struct samsung_usb2_phy_config exynos3250_usb2_phy_config; -extern const struct samsung_usb2_phy_config exynos4210_usb2_phy_config; -extern const struct samsung_usb2_phy_config exynos4x12_usb2_phy_config; -extern const struct samsung_usb2_phy_config exynos5250_usb2_phy_config; -extern const struct samsung_usb2_phy_config s5pv210_usb2_phy_config; -#endif diff --git a/drivers/phy/phy-spear1310-miphy.c b/drivers/phy/phy-spear1310-miphy.c deleted file mode 100644 index ed67e98e54ca..000000000000 --- a/drivers/phy/phy-spear1310-miphy.c +++ /dev/null @@ -1,261 +0,0 @@ -/* - * ST SPEAr1310-miphy driver - * - * Copyright (C) 2014 ST Microelectronics - * Pratyush Anand - * Mohit Kumar - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* SPEAr1310 Registers */ -#define SPEAR1310_PCIE_SATA_CFG 0x3A4 - #define SPEAR1310_PCIE_SATA2_SEL_PCIE (0 << 31) - #define SPEAR1310_PCIE_SATA1_SEL_PCIE (0 << 30) - #define SPEAR1310_PCIE_SATA0_SEL_PCIE (0 << 29) - #define SPEAR1310_PCIE_SATA2_SEL_SATA BIT(31) - #define SPEAR1310_PCIE_SATA1_SEL_SATA BIT(30) - #define SPEAR1310_PCIE_SATA0_SEL_SATA BIT(29) - #define SPEAR1310_SATA2_CFG_TX_CLK_EN BIT(27) - #define SPEAR1310_SATA2_CFG_RX_CLK_EN BIT(26) - #define SPEAR1310_SATA2_CFG_POWERUP_RESET BIT(25) - #define SPEAR1310_SATA2_CFG_PM_CLK_EN BIT(24) - #define SPEAR1310_SATA1_CFG_TX_CLK_EN BIT(23) - #define SPEAR1310_SATA1_CFG_RX_CLK_EN BIT(22) - #define SPEAR1310_SATA1_CFG_POWERUP_RESET BIT(21) - #define SPEAR1310_SATA1_CFG_PM_CLK_EN BIT(20) - #define SPEAR1310_SATA0_CFG_TX_CLK_EN BIT(19) - #define SPEAR1310_SATA0_CFG_RX_CLK_EN BIT(18) - #define SPEAR1310_SATA0_CFG_POWERUP_RESET BIT(17) - #define SPEAR1310_SATA0_CFG_PM_CLK_EN BIT(16) - #define SPEAR1310_PCIE2_CFG_DEVICE_PRESENT BIT(11) - #define SPEAR1310_PCIE2_CFG_POWERUP_RESET BIT(10) - #define SPEAR1310_PCIE2_CFG_CORE_CLK_EN BIT(9) - #define SPEAR1310_PCIE2_CFG_AUX_CLK_EN BIT(8) - #define SPEAR1310_PCIE1_CFG_DEVICE_PRESENT BIT(7) - #define SPEAR1310_PCIE1_CFG_POWERUP_RESET BIT(6) - #define SPEAR1310_PCIE1_CFG_CORE_CLK_EN BIT(5) - #define SPEAR1310_PCIE1_CFG_AUX_CLK_EN BIT(4) - #define SPEAR1310_PCIE0_CFG_DEVICE_PRESENT BIT(3) - #define SPEAR1310_PCIE0_CFG_POWERUP_RESET BIT(2) - #define SPEAR1310_PCIE0_CFG_CORE_CLK_EN BIT(1) - #define SPEAR1310_PCIE0_CFG_AUX_CLK_EN BIT(0) - - #define SPEAR1310_PCIE_CFG_MASK(x) ((0xF << (x * 4)) | BIT((x + 29))) - #define SPEAR1310_SATA_CFG_MASK(x) ((0xF << (x * 4 + 16)) | \ - BIT((x + 29))) - #define SPEAR1310_PCIE_CFG_VAL(x) \ - (SPEAR1310_PCIE_SATA##x##_SEL_PCIE | \ - SPEAR1310_PCIE##x##_CFG_AUX_CLK_EN | \ - SPEAR1310_PCIE##x##_CFG_CORE_CLK_EN | \ - SPEAR1310_PCIE##x##_CFG_POWERUP_RESET | \ - SPEAR1310_PCIE##x##_CFG_DEVICE_PRESENT) - #define SPEAR1310_SATA_CFG_VAL(x) \ - (SPEAR1310_PCIE_SATA##x##_SEL_SATA | \ - SPEAR1310_SATA##x##_CFG_PM_CLK_EN | \ - SPEAR1310_SATA##x##_CFG_POWERUP_RESET | \ - SPEAR1310_SATA##x##_CFG_RX_CLK_EN | \ - SPEAR1310_SATA##x##_CFG_TX_CLK_EN) - -#define SPEAR1310_PCIE_MIPHY_CFG_1 0x3A8 - #define SPEAR1310_MIPHY_DUAL_OSC_BYPASS_EXT BIT(31) - #define SPEAR1310_MIPHY_DUAL_CLK_REF_DIV2 BIT(28) - #define SPEAR1310_MIPHY_DUAL_PLL_RATIO_TOP(x) (x << 16) - #define SPEAR1310_MIPHY_SINGLE_OSC_BYPASS_EXT BIT(15) - #define SPEAR1310_MIPHY_SINGLE_CLK_REF_DIV2 BIT(12) - #define SPEAR1310_MIPHY_SINGLE_PLL_RATIO_TOP(x) (x << 0) - #define SPEAR1310_PCIE_SATA_MIPHY_CFG_SATA_MASK (0xFFFF) - #define SPEAR1310_PCIE_SATA_MIPHY_CFG_PCIE_MASK (0xFFFF << 16) - #define SPEAR1310_PCIE_SATA_MIPHY_CFG_SATA \ - (SPEAR1310_MIPHY_DUAL_OSC_BYPASS_EXT | \ - SPEAR1310_MIPHY_DUAL_CLK_REF_DIV2 | \ - SPEAR1310_MIPHY_DUAL_PLL_RATIO_TOP(60) | \ - SPEAR1310_MIPHY_SINGLE_OSC_BYPASS_EXT | \ - SPEAR1310_MIPHY_SINGLE_CLK_REF_DIV2 | \ - SPEAR1310_MIPHY_SINGLE_PLL_RATIO_TOP(60)) - #define SPEAR1310_PCIE_SATA_MIPHY_CFG_SATA_25M_CRYSTAL_CLK \ - (SPEAR1310_MIPHY_SINGLE_PLL_RATIO_TOP(120)) - #define SPEAR1310_PCIE_SATA_MIPHY_CFG_PCIE \ - (SPEAR1310_MIPHY_DUAL_OSC_BYPASS_EXT | \ - SPEAR1310_MIPHY_DUAL_PLL_RATIO_TOP(25) | \ - SPEAR1310_MIPHY_SINGLE_OSC_BYPASS_EXT | \ - SPEAR1310_MIPHY_SINGLE_PLL_RATIO_TOP(25)) - -#define SPEAR1310_PCIE_MIPHY_CFG_2 0x3AC - -enum spear1310_miphy_mode { - SATA, - PCIE, -}; - -struct spear1310_miphy_priv { - /* instance id of this phy */ - u32 id; - /* phy mode: 0 for SATA 1 for PCIe */ - enum spear1310_miphy_mode mode; - /* regmap for any soc specific misc registers */ - struct regmap *misc; - /* phy struct pointer */ - struct phy *phy; -}; - -static int spear1310_miphy_pcie_init(struct spear1310_miphy_priv *priv) -{ - u32 val; - - regmap_update_bits(priv->misc, SPEAR1310_PCIE_MIPHY_CFG_1, - SPEAR1310_PCIE_SATA_MIPHY_CFG_PCIE_MASK, - SPEAR1310_PCIE_SATA_MIPHY_CFG_PCIE); - - switch (priv->id) { - case 0: - val = SPEAR1310_PCIE_CFG_VAL(0); - break; - case 1: - val = SPEAR1310_PCIE_CFG_VAL(1); - break; - case 2: - val = SPEAR1310_PCIE_CFG_VAL(2); - break; - default: - return -EINVAL; - } - - regmap_update_bits(priv->misc, SPEAR1310_PCIE_SATA_CFG, - SPEAR1310_PCIE_CFG_MASK(priv->id), val); - - return 0; -} - -static int spear1310_miphy_pcie_exit(struct spear1310_miphy_priv *priv) -{ - regmap_update_bits(priv->misc, SPEAR1310_PCIE_SATA_CFG, - SPEAR1310_PCIE_CFG_MASK(priv->id), 0); - - regmap_update_bits(priv->misc, SPEAR1310_PCIE_MIPHY_CFG_1, - SPEAR1310_PCIE_SATA_MIPHY_CFG_PCIE_MASK, 0); - - return 0; -} - -static int spear1310_miphy_init(struct phy *phy) -{ - struct spear1310_miphy_priv *priv = phy_get_drvdata(phy); - int ret = 0; - - if (priv->mode == PCIE) - ret = spear1310_miphy_pcie_init(priv); - - return ret; -} - -static int spear1310_miphy_exit(struct phy *phy) -{ - struct spear1310_miphy_priv *priv = phy_get_drvdata(phy); - int ret = 0; - - if (priv->mode == PCIE) - ret = spear1310_miphy_pcie_exit(priv); - - return ret; -} - -static const struct of_device_id spear1310_miphy_of_match[] = { - { .compatible = "st,spear1310-miphy" }, - { }, -}; -MODULE_DEVICE_TABLE(of, spear1310_miphy_of_match); - -static const struct phy_ops spear1310_miphy_ops = { - .init = spear1310_miphy_init, - .exit = spear1310_miphy_exit, - .owner = THIS_MODULE, -}; - -static struct phy *spear1310_miphy_xlate(struct device *dev, - struct of_phandle_args *args) -{ - struct spear1310_miphy_priv *priv = dev_get_drvdata(dev); - - if (args->args_count < 1) { - dev_err(dev, "DT did not pass correct no of args\n"); - return ERR_PTR(-ENODEV); - } - - priv->mode = args->args[0]; - - if (priv->mode != SATA && priv->mode != PCIE) { - dev_err(dev, "DT did not pass correct phy mode\n"); - return ERR_PTR(-ENODEV); - } - - return priv->phy; -} - -static int spear1310_miphy_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct spear1310_miphy_priv *priv; - struct phy_provider *phy_provider; - - priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - priv->misc = - syscon_regmap_lookup_by_phandle(dev->of_node, "misc"); - if (IS_ERR(priv->misc)) { - dev_err(dev, "failed to find misc regmap\n"); - return PTR_ERR(priv->misc); - } - - if (of_property_read_u32(dev->of_node, "phy-id", &priv->id)) { - dev_err(dev, "failed to find phy id\n"); - return -EINVAL; - } - - priv->phy = devm_phy_create(dev, NULL, &spear1310_miphy_ops); - if (IS_ERR(priv->phy)) { - dev_err(dev, "failed to create SATA PCIe PHY\n"); - return PTR_ERR(priv->phy); - } - - dev_set_drvdata(dev, priv); - phy_set_drvdata(priv->phy, priv); - - phy_provider = - devm_of_phy_provider_register(dev, spear1310_miphy_xlate); - if (IS_ERR(phy_provider)) { - dev_err(dev, "failed to register phy provider\n"); - return PTR_ERR(phy_provider); - } - - return 0; -} - -static struct platform_driver spear1310_miphy_driver = { - .probe = spear1310_miphy_probe, - .driver = { - .name = "spear1310-miphy", - .of_match_table = of_match_ptr(spear1310_miphy_of_match), - }, -}; - -module_platform_driver(spear1310_miphy_driver); - -MODULE_DESCRIPTION("ST SPEAR1310-MIPHY driver"); -MODULE_AUTHOR("Pratyush Anand "); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-spear1340-miphy.c b/drivers/phy/phy-spear1340-miphy.c deleted file mode 100644 index 97280c0cf612..000000000000 --- a/drivers/phy/phy-spear1340-miphy.c +++ /dev/null @@ -1,294 +0,0 @@ -/* - * ST spear1340-miphy driver - * - * Copyright (C) 2014 ST Microelectronics - * Pratyush Anand - * Mohit Kumar - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* SPEAr1340 Registers */ -/* Power Management Registers */ -#define SPEAR1340_PCM_CFG 0x100 - #define SPEAR1340_PCM_CFG_SATA_POWER_EN BIT(11) -#define SPEAR1340_PCM_WKUP_CFG 0x104 -#define SPEAR1340_SWITCH_CTR 0x108 - -#define SPEAR1340_PERIP1_SW_RST 0x318 - #define SPEAR1340_PERIP1_SW_RSATA BIT(12) -#define SPEAR1340_PERIP2_SW_RST 0x31C -#define SPEAR1340_PERIP3_SW_RST 0x320 - -/* PCIE - SATA configuration registers */ -#define SPEAR1340_PCIE_SATA_CFG 0x424 - /* PCIE CFG MASks */ - #define SPEAR1340_PCIE_CFG_DEVICE_PRESENT BIT(11) - #define SPEAR1340_PCIE_CFG_POWERUP_RESET BIT(10) - #define SPEAR1340_PCIE_CFG_CORE_CLK_EN BIT(9) - #define SPEAR1340_PCIE_CFG_AUX_CLK_EN BIT(8) - #define SPEAR1340_SATA_CFG_TX_CLK_EN BIT(4) - #define SPEAR1340_SATA_CFG_RX_CLK_EN BIT(3) - #define SPEAR1340_SATA_CFG_POWERUP_RESET BIT(2) - #define SPEAR1340_SATA_CFG_PM_CLK_EN BIT(1) - #define SPEAR1340_PCIE_SATA_SEL_PCIE (0) - #define SPEAR1340_PCIE_SATA_SEL_SATA (1) - #define SPEAR1340_PCIE_SATA_CFG_MASK 0xF1F - #define SPEAR1340_PCIE_CFG_VAL (SPEAR1340_PCIE_SATA_SEL_PCIE | \ - SPEAR1340_PCIE_CFG_AUX_CLK_EN | \ - SPEAR1340_PCIE_CFG_CORE_CLK_EN | \ - SPEAR1340_PCIE_CFG_POWERUP_RESET | \ - SPEAR1340_PCIE_CFG_DEVICE_PRESENT) - #define SPEAR1340_SATA_CFG_VAL (SPEAR1340_PCIE_SATA_SEL_SATA | \ - SPEAR1340_SATA_CFG_PM_CLK_EN | \ - SPEAR1340_SATA_CFG_POWERUP_RESET | \ - SPEAR1340_SATA_CFG_RX_CLK_EN | \ - SPEAR1340_SATA_CFG_TX_CLK_EN) - -#define SPEAR1340_PCIE_MIPHY_CFG 0x428 - #define SPEAR1340_MIPHY_OSC_BYPASS_EXT BIT(31) - #define SPEAR1340_MIPHY_CLK_REF_DIV2 BIT(27) - #define SPEAR1340_MIPHY_CLK_REF_DIV4 (2 << 27) - #define SPEAR1340_MIPHY_CLK_REF_DIV8 (3 << 27) - #define SPEAR1340_MIPHY_PLL_RATIO_TOP(x) (x << 0) - #define SPEAR1340_PCIE_MIPHY_CFG_MASK 0xF80000FF - #define SPEAR1340_PCIE_SATA_MIPHY_CFG_SATA \ - (SPEAR1340_MIPHY_OSC_BYPASS_EXT | \ - SPEAR1340_MIPHY_CLK_REF_DIV2 | \ - SPEAR1340_MIPHY_PLL_RATIO_TOP(60)) - #define SPEAR1340_PCIE_SATA_MIPHY_CFG_SATA_25M_CRYSTAL_CLK \ - (SPEAR1340_MIPHY_PLL_RATIO_TOP(120)) - #define SPEAR1340_PCIE_SATA_MIPHY_CFG_PCIE \ - (SPEAR1340_MIPHY_OSC_BYPASS_EXT | \ - SPEAR1340_MIPHY_PLL_RATIO_TOP(25)) - -enum spear1340_miphy_mode { - SATA, - PCIE, -}; - -struct spear1340_miphy_priv { - /* phy mode: 0 for SATA 1 for PCIe */ - enum spear1340_miphy_mode mode; - /* regmap for any soc specific misc registers */ - struct regmap *misc; - /* phy struct pointer */ - struct phy *phy; -}; - -static int spear1340_miphy_sata_init(struct spear1340_miphy_priv *priv) -{ - regmap_update_bits(priv->misc, SPEAR1340_PCIE_SATA_CFG, - SPEAR1340_PCIE_SATA_CFG_MASK, - SPEAR1340_SATA_CFG_VAL); - regmap_update_bits(priv->misc, SPEAR1340_PCIE_MIPHY_CFG, - SPEAR1340_PCIE_MIPHY_CFG_MASK, - SPEAR1340_PCIE_SATA_MIPHY_CFG_SATA_25M_CRYSTAL_CLK); - /* Switch on sata power domain */ - regmap_update_bits(priv->misc, SPEAR1340_PCM_CFG, - SPEAR1340_PCM_CFG_SATA_POWER_EN, - SPEAR1340_PCM_CFG_SATA_POWER_EN); - /* Wait for SATA power domain on */ - msleep(20); - - /* Disable PCIE SATA Controller reset */ - regmap_update_bits(priv->misc, SPEAR1340_PERIP1_SW_RST, - SPEAR1340_PERIP1_SW_RSATA, 0); - /* Wait for SATA reset de-assert completion */ - msleep(20); - - return 0; -} - -static int spear1340_miphy_sata_exit(struct spear1340_miphy_priv *priv) -{ - regmap_update_bits(priv->misc, SPEAR1340_PCIE_SATA_CFG, - SPEAR1340_PCIE_SATA_CFG_MASK, 0); - regmap_update_bits(priv->misc, SPEAR1340_PCIE_MIPHY_CFG, - SPEAR1340_PCIE_MIPHY_CFG_MASK, 0); - - /* Enable PCIE SATA Controller reset */ - regmap_update_bits(priv->misc, SPEAR1340_PERIP1_SW_RST, - SPEAR1340_PERIP1_SW_RSATA, - SPEAR1340_PERIP1_SW_RSATA); - /* Wait for SATA power domain off */ - msleep(20); - /* Switch off sata power domain */ - regmap_update_bits(priv->misc, SPEAR1340_PCM_CFG, - SPEAR1340_PCM_CFG_SATA_POWER_EN, 0); - /* Wait for SATA reset assert completion */ - msleep(20); - - return 0; -} - -static int spear1340_miphy_pcie_init(struct spear1340_miphy_priv *priv) -{ - regmap_update_bits(priv->misc, SPEAR1340_PCIE_MIPHY_CFG, - SPEAR1340_PCIE_MIPHY_CFG_MASK, - SPEAR1340_PCIE_SATA_MIPHY_CFG_PCIE); - regmap_update_bits(priv->misc, SPEAR1340_PCIE_SATA_CFG, - SPEAR1340_PCIE_SATA_CFG_MASK, - SPEAR1340_PCIE_CFG_VAL); - - return 0; -} - -static int spear1340_miphy_pcie_exit(struct spear1340_miphy_priv *priv) -{ - regmap_update_bits(priv->misc, SPEAR1340_PCIE_MIPHY_CFG, - SPEAR1340_PCIE_MIPHY_CFG_MASK, 0); - regmap_update_bits(priv->misc, SPEAR1340_PCIE_SATA_CFG, - SPEAR1340_PCIE_SATA_CFG_MASK, 0); - - return 0; -} - -static int spear1340_miphy_init(struct phy *phy) -{ - struct spear1340_miphy_priv *priv = phy_get_drvdata(phy); - int ret = 0; - - if (priv->mode == SATA) - ret = spear1340_miphy_sata_init(priv); - else if (priv->mode == PCIE) - ret = spear1340_miphy_pcie_init(priv); - - return ret; -} - -static int spear1340_miphy_exit(struct phy *phy) -{ - struct spear1340_miphy_priv *priv = phy_get_drvdata(phy); - int ret = 0; - - if (priv->mode == SATA) - ret = spear1340_miphy_sata_exit(priv); - else if (priv->mode == PCIE) - ret = spear1340_miphy_pcie_exit(priv); - - return ret; -} - -static const struct of_device_id spear1340_miphy_of_match[] = { - { .compatible = "st,spear1340-miphy" }, - { }, -}; -MODULE_DEVICE_TABLE(of, spear1340_miphy_of_match); - -static const struct phy_ops spear1340_miphy_ops = { - .init = spear1340_miphy_init, - .exit = spear1340_miphy_exit, - .owner = THIS_MODULE, -}; - -#ifdef CONFIG_PM_SLEEP -static int spear1340_miphy_suspend(struct device *dev) -{ - struct spear1340_miphy_priv *priv = dev_get_drvdata(dev); - int ret = 0; - - if (priv->mode == SATA) - ret = spear1340_miphy_sata_exit(priv); - - return ret; -} - -static int spear1340_miphy_resume(struct device *dev) -{ - struct spear1340_miphy_priv *priv = dev_get_drvdata(dev); - int ret = 0; - - if (priv->mode == SATA) - ret = spear1340_miphy_sata_init(priv); - - return ret; -} -#endif - -static SIMPLE_DEV_PM_OPS(spear1340_miphy_pm_ops, spear1340_miphy_suspend, - spear1340_miphy_resume); - -static struct phy *spear1340_miphy_xlate(struct device *dev, - struct of_phandle_args *args) -{ - struct spear1340_miphy_priv *priv = dev_get_drvdata(dev); - - if (args->args_count < 1) { - dev_err(dev, "DT did not pass correct no of args\n"); - return ERR_PTR(-ENODEV); - } - - priv->mode = args->args[0]; - - if (priv->mode != SATA && priv->mode != PCIE) { - dev_err(dev, "DT did not pass correct phy mode\n"); - return ERR_PTR(-ENODEV); - } - - return priv->phy; -} - -static int spear1340_miphy_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct spear1340_miphy_priv *priv; - struct phy_provider *phy_provider; - - priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - priv->misc = - syscon_regmap_lookup_by_phandle(dev->of_node, "misc"); - if (IS_ERR(priv->misc)) { - dev_err(dev, "failed to find misc regmap\n"); - return PTR_ERR(priv->misc); - } - - priv->phy = devm_phy_create(dev, NULL, &spear1340_miphy_ops); - if (IS_ERR(priv->phy)) { - dev_err(dev, "failed to create SATA PCIe PHY\n"); - return PTR_ERR(priv->phy); - } - - dev_set_drvdata(dev, priv); - phy_set_drvdata(priv->phy, priv); - - phy_provider = - devm_of_phy_provider_register(dev, spear1340_miphy_xlate); - if (IS_ERR(phy_provider)) { - dev_err(dev, "failed to register phy provider\n"); - return PTR_ERR(phy_provider); - } - - return 0; -} - -static struct platform_driver spear1340_miphy_driver = { - .probe = spear1340_miphy_probe, - .driver = { - .name = "spear1340-miphy", - .pm = &spear1340_miphy_pm_ops, - .of_match_table = of_match_ptr(spear1340_miphy_of_match), - }, -}; - -module_platform_driver(spear1340_miphy_driver); - -MODULE_DESCRIPTION("ST SPEAR1340-MIPHY driver"); -MODULE_AUTHOR("Pratyush Anand "); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-stih407-usb.c b/drivers/phy/phy-stih407-usb.c deleted file mode 100644 index b1f44ab669fb..000000000000 --- a/drivers/phy/phy-stih407-usb.c +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright (C) 2014 STMicroelectronics - * - * STMicroelectronics Generic PHY driver for STiH407 USB2. - * - * Author: Giuseppe Cavallaro - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2, as - * published by the Free Software Foundation. - * - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define PHYPARAM_REG 1 -#define PHYCTRL_REG 2 - -/* Default PHY_SEL and REFCLKSEL configuration */ -#define STIH407_USB_PICOPHY_CTRL_PORT_CONF 0x6 -#define STIH407_USB_PICOPHY_CTRL_PORT_MASK 0x1f - -/* ports parameters overriding */ -#define STIH407_USB_PICOPHY_PARAM_DEF 0x39a4dc -#define STIH407_USB_PICOPHY_PARAM_MASK 0xffffffff - -struct stih407_usb2_picophy { - struct phy *phy; - struct regmap *regmap; - struct device *dev; - struct reset_control *rstc; - struct reset_control *rstport; - int ctrl; - int param; -}; - -static int stih407_usb2_pico_ctrl(struct stih407_usb2_picophy *phy_dev) -{ - reset_control_deassert(phy_dev->rstc); - - return regmap_update_bits(phy_dev->regmap, phy_dev->ctrl, - STIH407_USB_PICOPHY_CTRL_PORT_MASK, - STIH407_USB_PICOPHY_CTRL_PORT_CONF); -} - -static int stih407_usb2_init_port(struct phy *phy) -{ - int ret; - struct stih407_usb2_picophy *phy_dev = phy_get_drvdata(phy); - - stih407_usb2_pico_ctrl(phy_dev); - - ret = regmap_update_bits(phy_dev->regmap, - phy_dev->param, - STIH407_USB_PICOPHY_PARAM_MASK, - STIH407_USB_PICOPHY_PARAM_DEF); - if (ret) - return ret; - - return reset_control_deassert(phy_dev->rstport); -} - -static int stih407_usb2_exit_port(struct phy *phy) -{ - struct stih407_usb2_picophy *phy_dev = phy_get_drvdata(phy); - - /* - * Only port reset is asserted, phy global reset is kept untouched - * as other ports may still be active. When all ports are in reset - * state, assumption is made that power will be cut off on the phy, in - * case of suspend for instance. Theoretically, asserting individual - * reset (like here) or global reset should be equivalent. - */ - return reset_control_assert(phy_dev->rstport); -} - -static const struct phy_ops stih407_usb2_picophy_data = { - .init = stih407_usb2_init_port, - .exit = stih407_usb2_exit_port, - .owner = THIS_MODULE, -}; - -static int stih407_usb2_picophy_probe(struct platform_device *pdev) -{ - struct stih407_usb2_picophy *phy_dev; - struct device *dev = &pdev->dev; - struct device_node *np = dev->of_node; - struct phy_provider *phy_provider; - struct phy *phy; - int ret; - - phy_dev = devm_kzalloc(dev, sizeof(*phy_dev), GFP_KERNEL); - if (!phy_dev) - return -ENOMEM; - - phy_dev->dev = dev; - dev_set_drvdata(dev, phy_dev); - - phy_dev->rstc = devm_reset_control_get_shared(dev, "global"); - if (IS_ERR(phy_dev->rstc)) { - dev_err(dev, "failed to ctrl picoPHY reset\n"); - return PTR_ERR(phy_dev->rstc); - } - - phy_dev->rstport = devm_reset_control_get_exclusive(dev, "port"); - if (IS_ERR(phy_dev->rstport)) { - dev_err(dev, "failed to ctrl picoPHY reset\n"); - return PTR_ERR(phy_dev->rstport); - } - - /* Reset port by default: only deassert it in phy init */ - reset_control_assert(phy_dev->rstport); - - phy_dev->regmap = syscon_regmap_lookup_by_phandle(np, "st,syscfg"); - if (IS_ERR(phy_dev->regmap)) { - dev_err(dev, "No syscfg phandle specified\n"); - return PTR_ERR(phy_dev->regmap); - } - - ret = of_property_read_u32_index(np, "st,syscfg", PHYPARAM_REG, - &phy_dev->param); - if (ret) { - dev_err(dev, "can't get phyparam offset (%d)\n", ret); - return ret; - } - - ret = of_property_read_u32_index(np, "st,syscfg", PHYCTRL_REG, - &phy_dev->ctrl); - if (ret) { - dev_err(dev, "can't get phyctrl offset (%d)\n", ret); - return ret; - } - - phy = devm_phy_create(dev, NULL, &stih407_usb2_picophy_data); - if (IS_ERR(phy)) { - dev_err(dev, "failed to create Display Port PHY\n"); - return PTR_ERR(phy); - } - - phy_dev->phy = phy; - phy_set_drvdata(phy, phy_dev); - - phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); - if (IS_ERR(phy_provider)) - return PTR_ERR(phy_provider); - - dev_info(dev, "STiH407 USB Generic picoPHY driver probed!"); - - return 0; -} - -static const struct of_device_id stih407_usb2_picophy_of_match[] = { - { .compatible = "st,stih407-usb2-phy" }, - { /*sentinel */ }, -}; - -MODULE_DEVICE_TABLE(of, stih407_usb2_picophy_of_match); - -static struct platform_driver stih407_usb2_picophy_driver = { - .probe = stih407_usb2_picophy_probe, - .driver = { - .name = "stih407-usb-genphy", - .of_match_table = stih407_usb2_picophy_of_match, - } -}; - -module_platform_driver(stih407_usb2_picophy_driver); - -MODULE_AUTHOR("Giuseppe Cavallaro "); -MODULE_DESCRIPTION("STMicroelectronics Generic picoPHY driver for STiH407"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-sun4i-usb.c b/drivers/phy/phy-sun4i-usb.c deleted file mode 100644 index bbf06cfe5898..000000000000 --- a/drivers/phy/phy-sun4i-usb.c +++ /dev/null @@ -1,891 +0,0 @@ -/* - * Allwinner sun4i USB phy driver - * - * Copyright (C) 2014-2015 Hans de Goede - * - * Based on code from - * Allwinner Technology Co., Ltd. - * - * Modelled after: Samsung S5P/EXYNOS SoC series MIPI CSIS/DSIM DPHY driver - * Copyright (C) 2013 Samsung Electronics Co., Ltd. - * Author: Sylwester Nawrocki - * - * 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; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define REG_ISCR 0x00 -#define REG_PHYCTL_A10 0x04 -#define REG_PHYBIST 0x08 -#define REG_PHYTUNE 0x0c -#define REG_PHYCTL_A33 0x10 -#define REG_PHY_OTGCTL 0x20 - -#define REG_PMU_UNK1 0x10 - -#define PHYCTL_DATA BIT(7) - -#define OTGCTL_ROUTE_MUSB BIT(0) - -#define SUNXI_AHB_ICHR8_EN BIT(10) -#define SUNXI_AHB_INCR4_BURST_EN BIT(9) -#define SUNXI_AHB_INCRX_ALIGN_EN BIT(8) -#define SUNXI_ULPI_BYPASS_EN BIT(0) - -/* ISCR, Interface Status and Control bits */ -#define ISCR_ID_PULLUP_EN (1 << 17) -#define ISCR_DPDM_PULLUP_EN (1 << 16) -/* sunxi has the phy id/vbus pins not connected, so we use the force bits */ -#define ISCR_FORCE_ID_MASK (3 << 14) -#define ISCR_FORCE_ID_LOW (2 << 14) -#define ISCR_FORCE_ID_HIGH (3 << 14) -#define ISCR_FORCE_VBUS_MASK (3 << 12) -#define ISCR_FORCE_VBUS_LOW (2 << 12) -#define ISCR_FORCE_VBUS_HIGH (3 << 12) - -/* Common Control Bits for Both PHYs */ -#define PHY_PLL_BW 0x03 -#define PHY_RES45_CAL_EN 0x0c - -/* Private Control Bits for Each PHY */ -#define PHY_TX_AMPLITUDE_TUNE 0x20 -#define PHY_TX_SLEWRATE_TUNE 0x22 -#define PHY_VBUSVALID_TH_SEL 0x25 -#define PHY_PULLUP_RES_SEL 0x27 -#define PHY_OTG_FUNC_EN 0x28 -#define PHY_VBUS_DET_EN 0x29 -#define PHY_DISCON_TH_SEL 0x2a -#define PHY_SQUELCH_DETECT 0x3c - -#define MAX_PHYS 4 - -/* - * Note do not raise the debounce time, we must report Vusb high within 100ms - * otherwise we get Vbus errors - */ -#define DEBOUNCE_TIME msecs_to_jiffies(50) -#define POLL_TIME msecs_to_jiffies(250) - -enum sun4i_usb_phy_type { - sun4i_a10_phy, - sun6i_a31_phy, - sun8i_a33_phy, - sun8i_h3_phy, - sun8i_v3s_phy, - sun50i_a64_phy, -}; - -struct sun4i_usb_phy_cfg { - int num_phys; - enum sun4i_usb_phy_type type; - u32 disc_thresh; - u8 phyctl_offset; - bool dedicated_clocks; - bool enable_pmu_unk1; - bool phy0_dual_route; -}; - -struct sun4i_usb_phy_data { - void __iomem *base; - const struct sun4i_usb_phy_cfg *cfg; - enum usb_dr_mode dr_mode; - spinlock_t reg_lock; /* guard access to phyctl reg */ - struct sun4i_usb_phy { - struct phy *phy; - void __iomem *pmu; - struct regulator *vbus; - struct reset_control *reset; - struct clk *clk; - bool regulator_on; - int index; - } phys[MAX_PHYS]; - /* phy0 / otg related variables */ - struct extcon_dev *extcon; - bool phy0_init; - struct gpio_desc *id_det_gpio; - struct gpio_desc *vbus_det_gpio; - struct power_supply *vbus_power_supply; - struct notifier_block vbus_power_nb; - bool vbus_power_nb_registered; - bool force_session_end; - int id_det_irq; - int vbus_det_irq; - int id_det; - int vbus_det; - struct delayed_work detect; -}; - -#define to_sun4i_usb_phy_data(phy) \ - container_of((phy), struct sun4i_usb_phy_data, phys[(phy)->index]) - -static void sun4i_usb_phy0_update_iscr(struct phy *_phy, u32 clr, u32 set) -{ - struct sun4i_usb_phy *phy = phy_get_drvdata(_phy); - struct sun4i_usb_phy_data *data = to_sun4i_usb_phy_data(phy); - u32 iscr; - - iscr = readl(data->base + REG_ISCR); - iscr &= ~clr; - iscr |= set; - writel(iscr, data->base + REG_ISCR); -} - -static void sun4i_usb_phy0_set_id_detect(struct phy *phy, u32 val) -{ - if (val) - val = ISCR_FORCE_ID_HIGH; - else - val = ISCR_FORCE_ID_LOW; - - sun4i_usb_phy0_update_iscr(phy, ISCR_FORCE_ID_MASK, val); -} - -static void sun4i_usb_phy0_set_vbus_detect(struct phy *phy, u32 val) -{ - if (val) - val = ISCR_FORCE_VBUS_HIGH; - else - val = ISCR_FORCE_VBUS_LOW; - - sun4i_usb_phy0_update_iscr(phy, ISCR_FORCE_VBUS_MASK, val); -} - -static void sun4i_usb_phy_write(struct sun4i_usb_phy *phy, u32 addr, u32 data, - int len) -{ - struct sun4i_usb_phy_data *phy_data = to_sun4i_usb_phy_data(phy); - u32 temp, usbc_bit = BIT(phy->index * 2); - void __iomem *phyctl = phy_data->base + phy_data->cfg->phyctl_offset; - unsigned long flags; - int i; - - spin_lock_irqsave(&phy_data->reg_lock, flags); - - if (phy_data->cfg->phyctl_offset == REG_PHYCTL_A33) { - /* SoCs newer than A33 need us to set phyctl to 0 explicitly */ - writel(0, phyctl); - } - - for (i = 0; i < len; i++) { - temp = readl(phyctl); - - /* clear the address portion */ - temp &= ~(0xff << 8); - - /* set the address */ - temp |= ((addr + i) << 8); - writel(temp, phyctl); - - /* set the data bit and clear usbc bit*/ - temp = readb(phyctl); - if (data & 0x1) - temp |= PHYCTL_DATA; - else - temp &= ~PHYCTL_DATA; - temp &= ~usbc_bit; - writeb(temp, phyctl); - - /* pulse usbc_bit */ - temp = readb(phyctl); - temp |= usbc_bit; - writeb(temp, phyctl); - - temp = readb(phyctl); - temp &= ~usbc_bit; - writeb(temp, phyctl); - - data >>= 1; - } - - spin_unlock_irqrestore(&phy_data->reg_lock, flags); -} - -static void sun4i_usb_phy_passby(struct sun4i_usb_phy *phy, int enable) -{ - u32 bits, reg_value; - - if (!phy->pmu) - return; - - bits = SUNXI_AHB_ICHR8_EN | SUNXI_AHB_INCR4_BURST_EN | - SUNXI_AHB_INCRX_ALIGN_EN | SUNXI_ULPI_BYPASS_EN; - - reg_value = readl(phy->pmu); - - if (enable) - reg_value |= bits; - else - reg_value &= ~bits; - - writel(reg_value, phy->pmu); -} - -static int sun4i_usb_phy_init(struct phy *_phy) -{ - struct sun4i_usb_phy *phy = phy_get_drvdata(_phy); - struct sun4i_usb_phy_data *data = to_sun4i_usb_phy_data(phy); - int ret; - u32 val; - - ret = clk_prepare_enable(phy->clk); - if (ret) - return ret; - - ret = reset_control_deassert(phy->reset); - if (ret) { - clk_disable_unprepare(phy->clk); - return ret; - } - - if (phy->pmu && data->cfg->enable_pmu_unk1) { - val = readl(phy->pmu + REG_PMU_UNK1); - writel(val & ~2, phy->pmu + REG_PMU_UNK1); - } - - /* Enable USB 45 Ohm resistor calibration */ - if (phy->index == 0) - sun4i_usb_phy_write(phy, PHY_RES45_CAL_EN, 0x01, 1); - - /* Adjust PHY's magnitude and rate */ - sun4i_usb_phy_write(phy, PHY_TX_AMPLITUDE_TUNE, 0x14, 5); - - /* Disconnect threshold adjustment */ - sun4i_usb_phy_write(phy, PHY_DISCON_TH_SEL, - data->cfg->disc_thresh, 2); - - sun4i_usb_phy_passby(phy, 1); - - if (phy->index == 0) { - data->phy0_init = true; - - /* Enable pull-ups */ - sun4i_usb_phy0_update_iscr(_phy, 0, ISCR_DPDM_PULLUP_EN); - sun4i_usb_phy0_update_iscr(_phy, 0, ISCR_ID_PULLUP_EN); - - /* Force ISCR and cable state updates */ - data->id_det = -1; - data->vbus_det = -1; - queue_delayed_work(system_wq, &data->detect, 0); - } - - return 0; -} - -static int sun4i_usb_phy_exit(struct phy *_phy) -{ - struct sun4i_usb_phy *phy = phy_get_drvdata(_phy); - struct sun4i_usb_phy_data *data = to_sun4i_usb_phy_data(phy); - - if (phy->index == 0) { - /* Disable pull-ups */ - sun4i_usb_phy0_update_iscr(_phy, ISCR_DPDM_PULLUP_EN, 0); - sun4i_usb_phy0_update_iscr(_phy, ISCR_ID_PULLUP_EN, 0); - data->phy0_init = false; - } - - sun4i_usb_phy_passby(phy, 0); - reset_control_assert(phy->reset); - clk_disable_unprepare(phy->clk); - - return 0; -} - -static int sun4i_usb_phy0_get_id_det(struct sun4i_usb_phy_data *data) -{ - switch (data->dr_mode) { - case USB_DR_MODE_OTG: - if (data->id_det_gpio) - return gpiod_get_value_cansleep(data->id_det_gpio); - else - return 1; /* Fallback to peripheral mode */ - case USB_DR_MODE_HOST: - return 0; - case USB_DR_MODE_PERIPHERAL: - default: - return 1; - } -} - -static int sun4i_usb_phy0_get_vbus_det(struct sun4i_usb_phy_data *data) -{ - if (data->vbus_det_gpio) - return gpiod_get_value_cansleep(data->vbus_det_gpio); - - if (data->vbus_power_supply) { - union power_supply_propval val; - int r; - - r = power_supply_get_property(data->vbus_power_supply, - POWER_SUPPLY_PROP_PRESENT, &val); - if (r == 0) - return val.intval; - } - - /* Fallback: report vbus as high */ - return 1; -} - -static bool sun4i_usb_phy0_have_vbus_det(struct sun4i_usb_phy_data *data) -{ - return data->vbus_det_gpio || data->vbus_power_supply; -} - -static bool sun4i_usb_phy0_poll(struct sun4i_usb_phy_data *data) -{ - if ((data->id_det_gpio && data->id_det_irq <= 0) || - (data->vbus_det_gpio && data->vbus_det_irq <= 0)) - return true; - - /* - * The A31 companion pmic (axp221) does not generate vbus change - * interrupts when the board is driving vbus, so we must poll - * when using the pmic for vbus-det _and_ we're driving vbus. - */ - if (data->cfg->type == sun6i_a31_phy && - data->vbus_power_supply && data->phys[0].regulator_on) - return true; - - return false; -} - -static int sun4i_usb_phy_power_on(struct phy *_phy) -{ - struct sun4i_usb_phy *phy = phy_get_drvdata(_phy); - struct sun4i_usb_phy_data *data = to_sun4i_usb_phy_data(phy); - int ret; - - if (!phy->vbus || phy->regulator_on) - return 0; - - /* For phy0 only turn on Vbus if we don't have an ext. Vbus */ - if (phy->index == 0 && sun4i_usb_phy0_have_vbus_det(data) && - data->vbus_det) { - dev_warn(&_phy->dev, "External vbus detected, not enabling our own vbus\n"); - return 0; - } - - ret = regulator_enable(phy->vbus); - if (ret) - return ret; - - phy->regulator_on = true; - - /* We must report Vbus high within OTG_TIME_A_WAIT_VRISE msec. */ - if (phy->index == 0 && sun4i_usb_phy0_poll(data)) - mod_delayed_work(system_wq, &data->detect, DEBOUNCE_TIME); - - return 0; -} - -static int sun4i_usb_phy_power_off(struct phy *_phy) -{ - struct sun4i_usb_phy *phy = phy_get_drvdata(_phy); - struct sun4i_usb_phy_data *data = to_sun4i_usb_phy_data(phy); - - if (!phy->vbus || !phy->regulator_on) - return 0; - - regulator_disable(phy->vbus); - phy->regulator_on = false; - - /* - * phy0 vbus typically slowly discharges, sometimes this causes the - * Vbus gpio to not trigger an edge irq on Vbus off, so force a rescan. - */ - if (phy->index == 0 && !sun4i_usb_phy0_poll(data)) - mod_delayed_work(system_wq, &data->detect, POLL_TIME); - - return 0; -} - -static int sun4i_usb_phy_set_mode(struct phy *_phy, enum phy_mode mode) -{ - struct sun4i_usb_phy *phy = phy_get_drvdata(_phy); - struct sun4i_usb_phy_data *data = to_sun4i_usb_phy_data(phy); - int new_mode; - - if (phy->index != 0) - return -EINVAL; - - switch (mode) { - case PHY_MODE_USB_HOST: - new_mode = USB_DR_MODE_HOST; - break; - case PHY_MODE_USB_DEVICE: - new_mode = USB_DR_MODE_PERIPHERAL; - break; - case PHY_MODE_USB_OTG: - new_mode = USB_DR_MODE_OTG; - break; - default: - return -EINVAL; - } - - if (new_mode != data->dr_mode) { - dev_info(&_phy->dev, "Changing dr_mode to %d\n", new_mode); - data->dr_mode = new_mode; - } - - data->id_det = -1; /* Force reprocessing of id */ - data->force_session_end = true; - queue_delayed_work(system_wq, &data->detect, 0); - - return 0; -} - -void sun4i_usb_phy_set_squelch_detect(struct phy *_phy, bool enabled) -{ - struct sun4i_usb_phy *phy = phy_get_drvdata(_phy); - - sun4i_usb_phy_write(phy, PHY_SQUELCH_DETECT, enabled ? 0 : 2, 2); -} -EXPORT_SYMBOL_GPL(sun4i_usb_phy_set_squelch_detect); - -static const struct phy_ops sun4i_usb_phy_ops = { - .init = sun4i_usb_phy_init, - .exit = sun4i_usb_phy_exit, - .power_on = sun4i_usb_phy_power_on, - .power_off = sun4i_usb_phy_power_off, - .set_mode = sun4i_usb_phy_set_mode, - .owner = THIS_MODULE, -}; - -static void sun4i_usb_phy0_reroute(struct sun4i_usb_phy_data *data, int id_det) -{ - u32 regval; - - regval = readl(data->base + REG_PHY_OTGCTL); - if (id_det == 0) { - /* Host mode. Route phy0 to EHCI/OHCI */ - regval &= ~OTGCTL_ROUTE_MUSB; - } else { - /* Peripheral mode. Route phy0 to MUSB */ - regval |= OTGCTL_ROUTE_MUSB; - } - writel(regval, data->base + REG_PHY_OTGCTL); -} - -static void sun4i_usb_phy0_id_vbus_det_scan(struct work_struct *work) -{ - struct sun4i_usb_phy_data *data = - container_of(work, struct sun4i_usb_phy_data, detect.work); - struct phy *phy0 = data->phys[0].phy; - bool force_session_end, id_notify = false, vbus_notify = false; - int id_det, vbus_det; - - if (phy0 == NULL) - return; - - id_det = sun4i_usb_phy0_get_id_det(data); - vbus_det = sun4i_usb_phy0_get_vbus_det(data); - - mutex_lock(&phy0->mutex); - - if (!data->phy0_init) { - mutex_unlock(&phy0->mutex); - return; - } - - force_session_end = data->force_session_end; - data->force_session_end = false; - - if (id_det != data->id_det) { - /* id-change, force session end if we've no vbus detection */ - if (data->dr_mode == USB_DR_MODE_OTG && - !sun4i_usb_phy0_have_vbus_det(data)) - force_session_end = true; - - /* When entering host mode (id = 0) force end the session now */ - if (force_session_end && id_det == 0) { - sun4i_usb_phy0_set_vbus_detect(phy0, 0); - msleep(200); - sun4i_usb_phy0_set_vbus_detect(phy0, 1); - } - sun4i_usb_phy0_set_id_detect(phy0, id_det); - data->id_det = id_det; - id_notify = true; - } - - if (vbus_det != data->vbus_det) { - sun4i_usb_phy0_set_vbus_detect(phy0, vbus_det); - data->vbus_det = vbus_det; - vbus_notify = true; - } - - mutex_unlock(&phy0->mutex); - - if (id_notify) { - extcon_set_state_sync(data->extcon, EXTCON_USB_HOST, - !id_det); - /* When leaving host mode force end the session here */ - if (force_session_end && id_det == 1) { - mutex_lock(&phy0->mutex); - sun4i_usb_phy0_set_vbus_detect(phy0, 0); - msleep(1000); - sun4i_usb_phy0_set_vbus_detect(phy0, 1); - mutex_unlock(&phy0->mutex); - } - - /* Re-route PHY0 if necessary */ - if (data->cfg->phy0_dual_route) - sun4i_usb_phy0_reroute(data, id_det); - } - - if (vbus_notify) - extcon_set_state_sync(data->extcon, EXTCON_USB, vbus_det); - - if (sun4i_usb_phy0_poll(data)) - queue_delayed_work(system_wq, &data->detect, POLL_TIME); -} - -static irqreturn_t sun4i_usb_phy0_id_vbus_det_irq(int irq, void *dev_id) -{ - struct sun4i_usb_phy_data *data = dev_id; - - /* vbus or id changed, let the pins settle and then scan them */ - mod_delayed_work(system_wq, &data->detect, DEBOUNCE_TIME); - - return IRQ_HANDLED; -} - -static int sun4i_usb_phy0_vbus_notify(struct notifier_block *nb, - unsigned long val, void *v) -{ - struct sun4i_usb_phy_data *data = - container_of(nb, struct sun4i_usb_phy_data, vbus_power_nb); - struct power_supply *psy = v; - - /* Properties on the vbus_power_supply changed, scan vbus_det */ - if (val == PSY_EVENT_PROP_CHANGED && psy == data->vbus_power_supply) - mod_delayed_work(system_wq, &data->detect, DEBOUNCE_TIME); - - return NOTIFY_OK; -} - -static struct phy *sun4i_usb_phy_xlate(struct device *dev, - struct of_phandle_args *args) -{ - struct sun4i_usb_phy_data *data = dev_get_drvdata(dev); - - if (args->args[0] >= data->cfg->num_phys) - return ERR_PTR(-ENODEV); - - return data->phys[args->args[0]].phy; -} - -static int sun4i_usb_phy_remove(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct sun4i_usb_phy_data *data = dev_get_drvdata(dev); - - if (data->vbus_power_nb_registered) - power_supply_unreg_notifier(&data->vbus_power_nb); - if (data->id_det_irq > 0) - devm_free_irq(dev, data->id_det_irq, data); - if (data->vbus_det_irq > 0) - devm_free_irq(dev, data->vbus_det_irq, data); - - cancel_delayed_work_sync(&data->detect); - - return 0; -} - -static const unsigned int sun4i_usb_phy0_cable[] = { - EXTCON_USB, - EXTCON_USB_HOST, - EXTCON_NONE, -}; - -static int sun4i_usb_phy_probe(struct platform_device *pdev) -{ - struct sun4i_usb_phy_data *data; - struct device *dev = &pdev->dev; - struct device_node *np = dev->of_node; - struct phy_provider *phy_provider; - struct resource *res; - int i, ret; - - data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); - if (!data) - return -ENOMEM; - - spin_lock_init(&data->reg_lock); - INIT_DELAYED_WORK(&data->detect, sun4i_usb_phy0_id_vbus_det_scan); - dev_set_drvdata(dev, data); - data->cfg = of_device_get_match_data(dev); - if (!data->cfg) - return -EINVAL; - - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy_ctrl"); - data->base = devm_ioremap_resource(dev, res); - if (IS_ERR(data->base)) - return PTR_ERR(data->base); - - data->id_det_gpio = devm_gpiod_get_optional(dev, "usb0_id_det", - GPIOD_IN); - if (IS_ERR(data->id_det_gpio)) - return PTR_ERR(data->id_det_gpio); - - data->vbus_det_gpio = devm_gpiod_get_optional(dev, "usb0_vbus_det", - GPIOD_IN); - if (IS_ERR(data->vbus_det_gpio)) - return PTR_ERR(data->vbus_det_gpio); - - if (of_find_property(np, "usb0_vbus_power-supply", NULL)) { - data->vbus_power_supply = devm_power_supply_get_by_phandle(dev, - "usb0_vbus_power-supply"); - if (IS_ERR(data->vbus_power_supply)) - return PTR_ERR(data->vbus_power_supply); - - if (!data->vbus_power_supply) - return -EPROBE_DEFER; - } - - data->dr_mode = of_usb_get_dr_mode_by_phy(np, 0); - - data->extcon = devm_extcon_dev_allocate(dev, sun4i_usb_phy0_cable); - if (IS_ERR(data->extcon)) - return PTR_ERR(data->extcon); - - ret = devm_extcon_dev_register(dev, data->extcon); - if (ret) { - dev_err(dev, "failed to register extcon: %d\n", ret); - return ret; - } - - for (i = 0; i < data->cfg->num_phys; i++) { - struct sun4i_usb_phy *phy = data->phys + i; - char name[16]; - - snprintf(name, sizeof(name), "usb%d_vbus", i); - phy->vbus = devm_regulator_get_optional(dev, name); - if (IS_ERR(phy->vbus)) { - if (PTR_ERR(phy->vbus) == -EPROBE_DEFER) - return -EPROBE_DEFER; - phy->vbus = NULL; - } - - if (data->cfg->dedicated_clocks) - snprintf(name, sizeof(name), "usb%d_phy", i); - else - strlcpy(name, "usb_phy", sizeof(name)); - - phy->clk = devm_clk_get(dev, name); - if (IS_ERR(phy->clk)) { - dev_err(dev, "failed to get clock %s\n", name); - return PTR_ERR(phy->clk); - } - - snprintf(name, sizeof(name), "usb%d_reset", i); - phy->reset = devm_reset_control_get(dev, name); - if (IS_ERR(phy->reset)) { - dev_err(dev, "failed to get reset %s\n", name); - return PTR_ERR(phy->reset); - } - - if (i || data->cfg->phy0_dual_route) { /* No pmu for musb */ - snprintf(name, sizeof(name), "pmu%d", i); - res = platform_get_resource_byname(pdev, - IORESOURCE_MEM, name); - phy->pmu = devm_ioremap_resource(dev, res); - if (IS_ERR(phy->pmu)) - return PTR_ERR(phy->pmu); - } - - phy->phy = devm_phy_create(dev, NULL, &sun4i_usb_phy_ops); - if (IS_ERR(phy->phy)) { - dev_err(dev, "failed to create PHY %d\n", i); - return PTR_ERR(phy->phy); - } - - phy->index = i; - phy_set_drvdata(phy->phy, &data->phys[i]); - } - - data->id_det_irq = gpiod_to_irq(data->id_det_gpio); - if (data->id_det_irq > 0) { - ret = devm_request_irq(dev, data->id_det_irq, - sun4i_usb_phy0_id_vbus_det_irq, - IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, - "usb0-id-det", data); - if (ret) { - dev_err(dev, "Err requesting id-det-irq: %d\n", ret); - return ret; - } - } - - data->vbus_det_irq = gpiod_to_irq(data->vbus_det_gpio); - if (data->vbus_det_irq > 0) { - ret = devm_request_irq(dev, data->vbus_det_irq, - sun4i_usb_phy0_id_vbus_det_irq, - IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, - "usb0-vbus-det", data); - if (ret) { - dev_err(dev, "Err requesting vbus-det-irq: %d\n", ret); - data->vbus_det_irq = -1; - sun4i_usb_phy_remove(pdev); /* Stop detect work */ - return ret; - } - } - - if (data->vbus_power_supply) { - data->vbus_power_nb.notifier_call = sun4i_usb_phy0_vbus_notify; - data->vbus_power_nb.priority = 0; - ret = power_supply_reg_notifier(&data->vbus_power_nb); - if (ret) { - sun4i_usb_phy_remove(pdev); /* Stop detect work */ - return ret; - } - data->vbus_power_nb_registered = true; - } - - phy_provider = devm_of_phy_provider_register(dev, sun4i_usb_phy_xlate); - if (IS_ERR(phy_provider)) { - sun4i_usb_phy_remove(pdev); /* Stop detect work */ - return PTR_ERR(phy_provider); - } - - return 0; -} - -static const struct sun4i_usb_phy_cfg sun4i_a10_cfg = { - .num_phys = 3, - .type = sun4i_a10_phy, - .disc_thresh = 3, - .phyctl_offset = REG_PHYCTL_A10, - .dedicated_clocks = false, - .enable_pmu_unk1 = false, -}; - -static const struct sun4i_usb_phy_cfg sun5i_a13_cfg = { - .num_phys = 2, - .type = sun4i_a10_phy, - .disc_thresh = 2, - .phyctl_offset = REG_PHYCTL_A10, - .dedicated_clocks = false, - .enable_pmu_unk1 = false, -}; - -static const struct sun4i_usb_phy_cfg sun6i_a31_cfg = { - .num_phys = 3, - .type = sun6i_a31_phy, - .disc_thresh = 3, - .phyctl_offset = REG_PHYCTL_A10, - .dedicated_clocks = true, - .enable_pmu_unk1 = false, -}; - -static const struct sun4i_usb_phy_cfg sun7i_a20_cfg = { - .num_phys = 3, - .type = sun4i_a10_phy, - .disc_thresh = 2, - .phyctl_offset = REG_PHYCTL_A10, - .dedicated_clocks = false, - .enable_pmu_unk1 = false, -}; - -static const struct sun4i_usb_phy_cfg sun8i_a23_cfg = { - .num_phys = 2, - .type = sun4i_a10_phy, - .disc_thresh = 3, - .phyctl_offset = REG_PHYCTL_A10, - .dedicated_clocks = true, - .enable_pmu_unk1 = false, -}; - -static const struct sun4i_usb_phy_cfg sun8i_a33_cfg = { - .num_phys = 2, - .type = sun8i_a33_phy, - .disc_thresh = 3, - .phyctl_offset = REG_PHYCTL_A33, - .dedicated_clocks = true, - .enable_pmu_unk1 = false, -}; - -static const struct sun4i_usb_phy_cfg sun8i_h3_cfg = { - .num_phys = 4, - .type = sun8i_h3_phy, - .disc_thresh = 3, - .phyctl_offset = REG_PHYCTL_A33, - .dedicated_clocks = true, - .enable_pmu_unk1 = true, - .phy0_dual_route = true, -}; - -static const struct sun4i_usb_phy_cfg sun8i_v3s_cfg = { - .num_phys = 1, - .type = sun8i_v3s_phy, - .disc_thresh = 3, - .phyctl_offset = REG_PHYCTL_A33, - .dedicated_clocks = true, - .enable_pmu_unk1 = true, -}; - -static const struct sun4i_usb_phy_cfg sun50i_a64_cfg = { - .num_phys = 2, - .type = sun50i_a64_phy, - .disc_thresh = 3, - .phyctl_offset = REG_PHYCTL_A33, - .dedicated_clocks = true, - .enable_pmu_unk1 = true, - .phy0_dual_route = true, -}; - -static const struct of_device_id sun4i_usb_phy_of_match[] = { - { .compatible = "allwinner,sun4i-a10-usb-phy", .data = &sun4i_a10_cfg }, - { .compatible = "allwinner,sun5i-a13-usb-phy", .data = &sun5i_a13_cfg }, - { .compatible = "allwinner,sun6i-a31-usb-phy", .data = &sun6i_a31_cfg }, - { .compatible = "allwinner,sun7i-a20-usb-phy", .data = &sun7i_a20_cfg }, - { .compatible = "allwinner,sun8i-a23-usb-phy", .data = &sun8i_a23_cfg }, - { .compatible = "allwinner,sun8i-a33-usb-phy", .data = &sun8i_a33_cfg }, - { .compatible = "allwinner,sun8i-h3-usb-phy", .data = &sun8i_h3_cfg }, - { .compatible = "allwinner,sun8i-v3s-usb-phy", .data = &sun8i_v3s_cfg }, - { .compatible = "allwinner,sun50i-a64-usb-phy", - .data = &sun50i_a64_cfg}, - { }, -}; -MODULE_DEVICE_TABLE(of, sun4i_usb_phy_of_match); - -static struct platform_driver sun4i_usb_phy_driver = { - .probe = sun4i_usb_phy_probe, - .remove = sun4i_usb_phy_remove, - .driver = { - .of_match_table = sun4i_usb_phy_of_match, - .name = "sun4i-usb-phy", - } -}; -module_platform_driver(sun4i_usb_phy_driver); - -MODULE_DESCRIPTION("Allwinner sun4i USB phy driver"); -MODULE_AUTHOR("Hans de Goede "); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-sun9i-usb.c b/drivers/phy/phy-sun9i-usb.c deleted file mode 100644 index 28fce4bce638..000000000000 --- a/drivers/phy/phy-sun9i-usb.c +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Allwinner sun9i USB phy driver - * - * Copyright (C) 2014-2015 Chen-Yu Tsai - * - * Based on phy-sun4i-usb.c from - * Hans de Goede - * - * and code from - * Allwinner Technology Co., Ltd. - * - * 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; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#define SUNXI_AHB_INCR16_BURST_EN BIT(11) -#define SUNXI_AHB_INCR8_BURST_EN BIT(10) -#define SUNXI_AHB_INCR4_BURST_EN BIT(9) -#define SUNXI_AHB_INCRX_ALIGN_EN BIT(8) -#define SUNXI_ULPI_BYPASS_EN BIT(0) - -/* usb1 HSIC specific bits */ -#define SUNXI_EHCI_HS_FORCE BIT(20) -#define SUNXI_HSIC_CONNECT_DET BIT(17) -#define SUNXI_HSIC_CONNECT_INT BIT(16) -#define SUNXI_HSIC BIT(1) - -struct sun9i_usb_phy { - struct phy *phy; - void __iomem *pmu; - struct reset_control *reset; - struct clk *clk; - struct clk *hsic_clk; - enum usb_phy_interface type; -}; - -static void sun9i_usb_phy_passby(struct sun9i_usb_phy *phy, int enable) -{ - u32 bits, reg_value; - - bits = SUNXI_AHB_INCR16_BURST_EN | SUNXI_AHB_INCR8_BURST_EN | - SUNXI_AHB_INCR4_BURST_EN | SUNXI_AHB_INCRX_ALIGN_EN | - SUNXI_ULPI_BYPASS_EN; - - if (phy->type == USBPHY_INTERFACE_MODE_HSIC) - bits |= SUNXI_HSIC | SUNXI_EHCI_HS_FORCE | - SUNXI_HSIC_CONNECT_DET | SUNXI_HSIC_CONNECT_INT; - - reg_value = readl(phy->pmu); - - if (enable) - reg_value |= bits; - else - reg_value &= ~bits; - - writel(reg_value, phy->pmu); -} - -static int sun9i_usb_phy_init(struct phy *_phy) -{ - struct sun9i_usb_phy *phy = phy_get_drvdata(_phy); - int ret; - - ret = clk_prepare_enable(phy->clk); - if (ret) - goto err_clk; - - ret = clk_prepare_enable(phy->hsic_clk); - if (ret) - goto err_hsic_clk; - - ret = reset_control_deassert(phy->reset); - if (ret) - goto err_reset; - - sun9i_usb_phy_passby(phy, 1); - return 0; - -err_reset: - clk_disable_unprepare(phy->hsic_clk); - -err_hsic_clk: - clk_disable_unprepare(phy->clk); - -err_clk: - return ret; -} - -static int sun9i_usb_phy_exit(struct phy *_phy) -{ - struct sun9i_usb_phy *phy = phy_get_drvdata(_phy); - - sun9i_usb_phy_passby(phy, 0); - reset_control_assert(phy->reset); - clk_disable_unprepare(phy->hsic_clk); - clk_disable_unprepare(phy->clk); - - return 0; -} - -static const struct phy_ops sun9i_usb_phy_ops = { - .init = sun9i_usb_phy_init, - .exit = sun9i_usb_phy_exit, - .owner = THIS_MODULE, -}; - -static int sun9i_usb_phy_probe(struct platform_device *pdev) -{ - struct sun9i_usb_phy *phy; - struct device *dev = &pdev->dev; - struct device_node *np = dev->of_node; - struct phy_provider *phy_provider; - struct resource *res; - - phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); - if (!phy) - return -ENOMEM; - - phy->type = of_usb_get_phy_mode(np); - if (phy->type == USBPHY_INTERFACE_MODE_HSIC) { - phy->clk = devm_clk_get(dev, "hsic_480M"); - if (IS_ERR(phy->clk)) { - dev_err(dev, "failed to get hsic_480M clock\n"); - return PTR_ERR(phy->clk); - } - - phy->hsic_clk = devm_clk_get(dev, "hsic_12M"); - if (IS_ERR(phy->hsic_clk)) { - dev_err(dev, "failed to get hsic_12M clock\n"); - return PTR_ERR(phy->hsic_clk); - } - - phy->reset = devm_reset_control_get(dev, "hsic"); - if (IS_ERR(phy->reset)) { - dev_err(dev, "failed to get reset control\n"); - return PTR_ERR(phy->reset); - } - } else { - phy->clk = devm_clk_get(dev, "phy"); - if (IS_ERR(phy->clk)) { - dev_err(dev, "failed to get phy clock\n"); - return PTR_ERR(phy->clk); - } - - phy->reset = devm_reset_control_get(dev, "phy"); - if (IS_ERR(phy->reset)) { - dev_err(dev, "failed to get reset control\n"); - return PTR_ERR(phy->reset); - } - } - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - phy->pmu = devm_ioremap_resource(dev, res); - if (IS_ERR(phy->pmu)) - return PTR_ERR(phy->pmu); - - phy->phy = devm_phy_create(dev, NULL, &sun9i_usb_phy_ops); - if (IS_ERR(phy->phy)) { - dev_err(dev, "failed to create PHY\n"); - return PTR_ERR(phy->phy); - } - - phy_set_drvdata(phy->phy, phy); - phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); - - return PTR_ERR_OR_ZERO(phy_provider); -} - -static const struct of_device_id sun9i_usb_phy_of_match[] = { - { .compatible = "allwinner,sun9i-a80-usb-phy" }, - { }, -}; -MODULE_DEVICE_TABLE(of, sun9i_usb_phy_of_match); - -static struct platform_driver sun9i_usb_phy_driver = { - .probe = sun9i_usb_phy_probe, - .driver = { - .of_match_table = sun9i_usb_phy_of_match, - .name = "sun9i-usb-phy", - } -}; -module_platform_driver(sun9i_usb_phy_driver); - -MODULE_DESCRIPTION("Allwinner sun9i USB phy driver"); -MODULE_AUTHOR("Chen-Yu Tsai "); -MODULE_LICENSE("GPL"); diff --git a/drivers/phy/phy-ti-pipe3.c b/drivers/phy/phy-ti-pipe3.c deleted file mode 100644 index 9c84d32c6f60..000000000000 --- a/drivers/phy/phy-ti-pipe3.c +++ /dev/null @@ -1,697 +0,0 @@ -/* - * phy-ti-pipe3 - PIPE3 PHY driver. - * - * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com - * 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; either version 2 of the License, or - * (at your option) any later version. - * - * Author: Kishon Vijay Abraham I - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define PLL_STATUS 0x00000004 -#define PLL_GO 0x00000008 -#define PLL_CONFIGURATION1 0x0000000C -#define PLL_CONFIGURATION2 0x00000010 -#define PLL_CONFIGURATION3 0x00000014 -#define PLL_CONFIGURATION4 0x00000020 - -#define PLL_REGM_MASK 0x001FFE00 -#define PLL_REGM_SHIFT 0x9 -#define PLL_REGM_F_MASK 0x0003FFFF -#define PLL_REGM_F_SHIFT 0x0 -#define PLL_REGN_MASK 0x000001FE -#define PLL_REGN_SHIFT 0x1 -#define PLL_SELFREQDCO_MASK 0x0000000E -#define PLL_SELFREQDCO_SHIFT 0x1 -#define PLL_SD_MASK 0x0003FC00 -#define PLL_SD_SHIFT 10 -#define SET_PLL_GO 0x1 -#define PLL_LDOPWDN BIT(15) -#define PLL_TICOPWDN BIT(16) -#define PLL_LOCK 0x2 -#define PLL_IDLE 0x1 - -#define SATA_PLL_SOFT_RESET BIT(18) - -#define PIPE3_PHY_PWRCTL_CLK_CMD_MASK 0x003FC000 -#define PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT 14 - -#define PIPE3_PHY_PWRCTL_CLK_FREQ_MASK 0xFFC00000 -#define PIPE3_PHY_PWRCTL_CLK_FREQ_SHIFT 22 - -#define PIPE3_PHY_TX_RX_POWERON 0x3 -#define PIPE3_PHY_TX_RX_POWEROFF 0x0 - -#define PCIE_PCS_MASK 0xFF0000 -#define PCIE_PCS_DELAY_COUNT_SHIFT 0x10 - -/* - * This is an Empirical value that works, need to confirm the actual - * value required for the PIPE3PHY_PLL_CONFIGURATION2.PLL_IDLE status - * to be correctly reflected in the PIPE3PHY_PLL_STATUS register. - */ -#define PLL_IDLE_TIME 100 /* in milliseconds */ -#define PLL_LOCK_TIME 100 /* in milliseconds */ - -struct pipe3_dpll_params { - u16 m; - u8 n; - u8 freq:3; - u8 sd; - u32 mf; -}; - -struct pipe3_dpll_map { - unsigned long rate; - struct pipe3_dpll_params params; -}; - -struct ti_pipe3 { - void __iomem *pll_ctrl_base; - struct device *dev; - struct device *control_dev; - struct clk *wkupclk; - struct clk *sys_clk; - struct clk *refclk; - struct clk *div_clk; - struct pipe3_dpll_map *dpll_map; - struct regmap *phy_power_syscon; /* ctrl. reg. acces */ - struct regmap *pcs_syscon; /* ctrl. reg. acces */ - struct regmap *dpll_reset_syscon; /* ctrl. reg. acces */ - unsigned int dpll_reset_reg; /* reg. index within syscon */ - unsigned int power_reg; /* power reg. index within syscon */ - unsigned int pcie_pcs_reg; /* pcs reg. index in syscon */ - bool sata_refclk_enabled; -}; - -static struct pipe3_dpll_map dpll_map_usb[] = { - {12000000, {1250, 5, 4, 20, 0} }, /* 12 MHz */ - {16800000, {3125, 20, 4, 20, 0} }, /* 16.8 MHz */ - {19200000, {1172, 8, 4, 20, 65537} }, /* 19.2 MHz */ - {20000000, {1000, 7, 4, 10, 0} }, /* 20 MHz */ - {26000000, {1250, 12, 4, 20, 0} }, /* 26 MHz */ - {38400000, {3125, 47, 4, 20, 92843} }, /* 38.4 MHz */ - { }, /* Terminator */ -}; - -static struct pipe3_dpll_map dpll_map_sata[] = { - {12000000, {1000, 7, 4, 6, 0} }, /* 12 MHz */ - {16800000, {714, 7, 4, 6, 0} }, /* 16.8 MHz */ - {19200000, {625, 7, 4, 6, 0} }, /* 19.2 MHz */ - {20000000, {600, 7, 4, 6, 0} }, /* 20 MHz */ - {26000000, {461, 7, 4, 6, 0} }, /* 26 MHz */ - {38400000, {312, 7, 4, 6, 0} }, /* 38.4 MHz */ - { }, /* Terminator */ -}; - -static inline u32 ti_pipe3_readl(void __iomem *addr, unsigned offset) -{ - return __raw_readl(addr + offset); -} - -static inline void ti_pipe3_writel(void __iomem *addr, unsigned offset, - u32 data) -{ - __raw_writel(data, addr + offset); -} - -static struct pipe3_dpll_params *ti_pipe3_get_dpll_params(struct ti_pipe3 *phy) -{ - unsigned long rate; - struct pipe3_dpll_map *dpll_map = phy->dpll_map; - - rate = clk_get_rate(phy->sys_clk); - - for (; dpll_map->rate; dpll_map++) { - if (rate == dpll_map->rate) - return &dpll_map->params; - } - - dev_err(phy->dev, "No DPLL configuration for %lu Hz SYS CLK\n", rate); - - return NULL; -} - -static int ti_pipe3_enable_clocks(struct ti_pipe3 *phy); -static void ti_pipe3_disable_clocks(struct ti_pipe3 *phy); - -static int ti_pipe3_power_off(struct phy *x) -{ - u32 val; - int ret; - struct ti_pipe3 *phy = phy_get_drvdata(x); - - if (!phy->phy_power_syscon) { - omap_control_phy_power(phy->control_dev, 0); - return 0; - } - - val = PIPE3_PHY_TX_RX_POWEROFF << PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT; - - ret = regmap_update_bits(phy->phy_power_syscon, phy->power_reg, - PIPE3_PHY_PWRCTL_CLK_CMD_MASK, val); - return ret; -} - -static int ti_pipe3_power_on(struct phy *x) -{ - u32 val; - u32 mask; - int ret; - unsigned long rate; - struct ti_pipe3 *phy = phy_get_drvdata(x); - - if (!phy->phy_power_syscon) { - omap_control_phy_power(phy->control_dev, 1); - return 0; - } - - rate = clk_get_rate(phy->sys_clk); - if (!rate) { - dev_err(phy->dev, "Invalid clock rate\n"); - return -EINVAL; - } - rate = rate / 1000000; - mask = OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_MASK | - OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_MASK; - val = PIPE3_PHY_TX_RX_POWERON << PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT; - val |= rate << OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_SHIFT; - - ret = regmap_update_bits(phy->phy_power_syscon, phy->power_reg, - mask, val); - return ret; -} - -static int ti_pipe3_dpll_wait_lock(struct ti_pipe3 *phy) -{ - u32 val; - unsigned long timeout; - - timeout = jiffies + msecs_to_jiffies(PLL_LOCK_TIME); - do { - cpu_relax(); - val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_STATUS); - if (val & PLL_LOCK) - return 0; - } while (!time_after(jiffies, timeout)); - - dev_err(phy->dev, "DPLL failed to lock\n"); - return -EBUSY; -} - -static int ti_pipe3_dpll_program(struct ti_pipe3 *phy) -{ - u32 val; - struct pipe3_dpll_params *dpll_params; - - dpll_params = ti_pipe3_get_dpll_params(phy); - if (!dpll_params) - return -EINVAL; - - val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION1); - val &= ~PLL_REGN_MASK; - val |= dpll_params->n << PLL_REGN_SHIFT; - ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION1, val); - - val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION2); - val &= ~PLL_SELFREQDCO_MASK; - val |= dpll_params->freq << PLL_SELFREQDCO_SHIFT; - ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION2, val); - - val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION1); - val &= ~PLL_REGM_MASK; - val |= dpll_params->m << PLL_REGM_SHIFT; - ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION1, val); - - val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION4); - val &= ~PLL_REGM_F_MASK; - val |= dpll_params->mf << PLL_REGM_F_SHIFT; - ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION4, val); - - val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION3); - val &= ~PLL_SD_MASK; - val |= dpll_params->sd << PLL_SD_SHIFT; - ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION3, val); - - ti_pipe3_writel(phy->pll_ctrl_base, PLL_GO, SET_PLL_GO); - - return ti_pipe3_dpll_wait_lock(phy); -} - -static int ti_pipe3_init(struct phy *x) -{ - struct ti_pipe3 *phy = phy_get_drvdata(x); - u32 val; - int ret = 0; - - ti_pipe3_enable_clocks(phy); - /* - * Set pcie_pcs register to 0x96 for proper functioning of phy - * as recommended in AM572x TRM SPRUHZ6, section 18.5.2.2, table - * 18-1804. - */ - if (of_device_is_compatible(phy->dev->of_node, "ti,phy-pipe3-pcie")) { - if (!phy->pcs_syscon) { - omap_control_pcie_pcs(phy->control_dev, 0x96); - return 0; - } - - val = 0x96 << OMAP_CTRL_PCIE_PCS_DELAY_COUNT_SHIFT; - ret = regmap_update_bits(phy->pcs_syscon, phy->pcie_pcs_reg, - PCIE_PCS_MASK, val); - return ret; - } - - /* Bring it out of IDLE if it is IDLE */ - val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION2); - if (val & PLL_IDLE) { - val &= ~PLL_IDLE; - ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION2, val); - ret = ti_pipe3_dpll_wait_lock(phy); - } - - /* SATA has issues if re-programmed when locked */ - val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_STATUS); - if ((val & PLL_LOCK) && of_device_is_compatible(phy->dev->of_node, - "ti,phy-pipe3-sata")) - return ret; - - /* Program the DPLL */ - ret = ti_pipe3_dpll_program(phy); - if (ret) { - ti_pipe3_disable_clocks(phy); - return -EINVAL; - } - - return ret; -} - -static int ti_pipe3_exit(struct phy *x) -{ - struct ti_pipe3 *phy = phy_get_drvdata(x); - u32 val; - unsigned long timeout; - - /* If dpll_reset_syscon is not present we wont power down SATA DPLL - * due to Errata i783 - */ - if (of_device_is_compatible(phy->dev->of_node, "ti,phy-pipe3-sata") && - !phy->dpll_reset_syscon) - return 0; - - /* PCIe doesn't have internal DPLL */ - if (!of_device_is_compatible(phy->dev->of_node, "ti,phy-pipe3-pcie")) { - /* Put DPLL in IDLE mode */ - val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION2); - val |= PLL_IDLE; - ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION2, val); - - /* wait for LDO and Oscillator to power down */ - timeout = jiffies + msecs_to_jiffies(PLL_IDLE_TIME); - do { - cpu_relax(); - val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_STATUS); - if ((val & PLL_TICOPWDN) && (val & PLL_LDOPWDN)) - break; - } while (!time_after(jiffies, timeout)); - - if (!(val & PLL_TICOPWDN) || !(val & PLL_LDOPWDN)) { - dev_err(phy->dev, "Failed to power down: PLL_STATUS 0x%x\n", - val); - return -EBUSY; - } - } - - /* i783: SATA needs control bit toggle after PLL unlock */ - if (of_device_is_compatible(phy->dev->of_node, "ti,phy-pipe3-sata")) { - regmap_update_bits(phy->dpll_reset_syscon, phy->dpll_reset_reg, - SATA_PLL_SOFT_RESET, SATA_PLL_SOFT_RESET); - regmap_update_bits(phy->dpll_reset_syscon, phy->dpll_reset_reg, - SATA_PLL_SOFT_RESET, 0); - } - - ti_pipe3_disable_clocks(phy); - - return 0; -} -static const struct phy_ops ops = { - .init = ti_pipe3_init, - .exit = ti_pipe3_exit, - .power_on = ti_pipe3_power_on, - .power_off = ti_pipe3_power_off, - .owner = THIS_MODULE, -}; - -static const struct of_device_id ti_pipe3_id_table[]; - -static int ti_pipe3_get_clk(struct ti_pipe3 *phy) -{ - struct clk *clk; - struct device *dev = phy->dev; - struct device_node *node = dev->of_node; - - phy->refclk = devm_clk_get(dev, "refclk"); - if (IS_ERR(phy->refclk)) { - dev_err(dev, "unable to get refclk\n"); - /* older DTBs have missing refclk in SATA PHY - * so don't bail out in case of SATA PHY. - */ - if (!of_device_is_compatible(node, "ti,phy-pipe3-sata")) - return PTR_ERR(phy->refclk); - } - - if (!of_device_is_compatible(node, "ti,phy-pipe3-sata")) { - phy->wkupclk = devm_clk_get(dev, "wkupclk"); - if (IS_ERR(phy->wkupclk)) { - dev_err(dev, "unable to get wkupclk\n"); - return PTR_ERR(phy->wkupclk); - } - } else { - phy->wkupclk = ERR_PTR(-ENODEV); - } - - if (!of_device_is_compatible(node, "ti,phy-pipe3-pcie") || - phy->phy_power_syscon) { - phy->sys_clk = devm_clk_get(dev, "sysclk"); - if (IS_ERR(phy->sys_clk)) { - dev_err(dev, "unable to get sysclk\n"); - return -EINVAL; - } - } - - if (of_device_is_compatible(node, "ti,phy-pipe3-pcie")) { - clk = devm_clk_get(dev, "dpll_ref"); - if (IS_ERR(clk)) { - dev_err(dev, "unable to get dpll ref clk\n"); - return PTR_ERR(clk); - } - clk_set_rate(clk, 1500000000); - - clk = devm_clk_get(dev, "dpll_ref_m2"); - if (IS_ERR(clk)) { - dev_err(dev, "unable to get dpll ref m2 clk\n"); - return PTR_ERR(clk); - } - clk_set_rate(clk, 100000000); - - clk = devm_clk_get(dev, "phy-div"); - if (IS_ERR(clk)) { - dev_err(dev, "unable to get phy-div clk\n"); - return PTR_ERR(clk); - } - clk_set_rate(clk, 100000000); - - phy->div_clk = devm_clk_get(dev, "div-clk"); - if (IS_ERR(phy->div_clk)) { - dev_err(dev, "unable to get div-clk\n"); - return PTR_ERR(phy->div_clk); - } - } else { - phy->div_clk = ERR_PTR(-ENODEV); - } - - return 0; -} - -static int ti_pipe3_get_sysctrl(struct ti_pipe3 *phy) -{ - struct device *dev = phy->dev; - struct device_node *node = dev->of_node; - struct device_node *control_node; - struct platform_device *control_pdev; - - phy->phy_power_syscon = syscon_regmap_lookup_by_phandle(node, - "syscon-phy-power"); - if (IS_ERR(phy->phy_power_syscon)) { - dev_dbg(dev, - "can't get syscon-phy-power, using control device\n"); - phy->phy_power_syscon = NULL; - } else { - if (of_property_read_u32_index(node, - "syscon-phy-power", 1, - &phy->power_reg)) { - dev_err(dev, "couldn't get power reg. offset\n"); - return -EINVAL; - } - } - - if (!phy->phy_power_syscon) { - control_node = of_parse_phandle(node, "ctrl-module", 0); - if (!control_node) { - dev_err(dev, "Failed to get control device phandle\n"); - return -EINVAL; - } - - control_pdev = of_find_device_by_node(control_node); - if (!control_pdev) { - dev_err(dev, "Failed to get control device\n"); - return -EINVAL; - } - - phy->control_dev = &control_pdev->dev; - } - - if (of_device_is_compatible(node, "ti,phy-pipe3-pcie")) { - phy->pcs_syscon = syscon_regmap_lookup_by_phandle(node, - "syscon-pcs"); - if (IS_ERR(phy->pcs_syscon)) { - dev_dbg(dev, - "can't get syscon-pcs, using omap control\n"); - phy->pcs_syscon = NULL; - } else { - if (of_property_read_u32_index(node, - "syscon-pcs", 1, - &phy->pcie_pcs_reg)) { - dev_err(dev, - "couldn't get pcie pcs reg. offset\n"); - return -EINVAL; - } - } - } - - if (of_device_is_compatible(node, "ti,phy-pipe3-sata")) { - phy->dpll_reset_syscon = syscon_regmap_lookup_by_phandle(node, - "syscon-pllreset"); - if (IS_ERR(phy->dpll_reset_syscon)) { - dev_info(dev, - "can't get syscon-pllreset, sata dpll won't idle\n"); - phy->dpll_reset_syscon = NULL; - } else { - if (of_property_read_u32_index(node, - "syscon-pllreset", 1, - &phy->dpll_reset_reg)) { - dev_err(dev, - "couldn't get pllreset reg. offset\n"); - return -EINVAL; - } - } - } - - return 0; -} - -static int ti_pipe3_get_pll_base(struct ti_pipe3 *phy) -{ - struct resource *res; - const struct of_device_id *match; - struct device *dev = phy->dev; - struct device_node *node = dev->of_node; - struct platform_device *pdev = to_platform_device(dev); - - if (of_device_is_compatible(node, "ti,phy-pipe3-pcie")) - return 0; - - match = of_match_device(ti_pipe3_id_table, dev); - if (!match) - return -EINVAL; - - phy->dpll_map = (struct pipe3_dpll_map *)match->data; - if (!phy->dpll_map) { - dev_err(dev, "no DPLL data\n"); - return -EINVAL; - } - - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, - "pll_ctrl"); - phy->pll_ctrl_base = devm_ioremap_resource(dev, res); - return PTR_ERR_OR_ZERO(phy->pll_ctrl_base); -} - -static int ti_pipe3_probe(struct platform_device *pdev) -{ - struct ti_pipe3 *phy; - struct phy *generic_phy; - struct phy_provider *phy_provider; - struct device_node *node = pdev->dev.of_node; - struct device *dev = &pdev->dev; - int ret; - - phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); - if (!phy) - return -ENOMEM; - - phy->dev = dev; - - ret = ti_pipe3_get_pll_base(phy); - if (ret) - return ret; - - ret = ti_pipe3_get_sysctrl(phy); - if (ret) - return ret; - - ret = ti_pipe3_get_clk(phy); - if (ret) - return ret; - - platform_set_drvdata(pdev, phy); - pm_runtime_enable(dev); - - /* - * Prevent auto-disable of refclk for SATA PHY due to Errata i783 - */ - if (of_device_is_compatible(node, "ti,phy-pipe3-sata")) { - if (!IS_ERR(phy->refclk)) { - clk_prepare_enable(phy->refclk); - phy->sata_refclk_enabled = true; - } - } - - generic_phy = devm_phy_create(dev, NULL, &ops); - if (IS_ERR(generic_phy)) - return PTR_ERR(generic_phy); - - phy_set_drvdata(generic_phy, phy); - - ti_pipe3_power_off(generic_phy); - - phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); - return PTR_ERR_OR_ZERO(phy_provider); -} - -static int ti_pipe3_remove(struct platform_device *pdev) -{ - pm_runtime_disable(&pdev->dev); - - return 0; -} - -static int ti_pipe3_enable_clocks(struct ti_pipe3 *phy) -{ - int ret = 0; - - if (!IS_ERR(phy->refclk)) { - ret = clk_prepare_enable(phy->refclk); - if (ret) { - dev_err(phy->dev, "Failed to enable refclk %d\n", ret); - return ret; - } - } - - if (!IS_ERR(phy->wkupclk)) { - ret = clk_prepare_enable(phy->wkupclk); - if (ret) { - dev_err(phy->dev, "Failed to enable wkupclk %d\n", ret); - goto disable_refclk; - } - } - - if (!IS_ERR(phy->div_clk)) { - ret = clk_prepare_enable(phy->div_clk); - if (ret) { - dev_err(phy->dev, "Failed to enable div_clk %d\n", ret); - goto disable_wkupclk; - } - } - - return 0; - -disable_wkupclk: - if (!IS_ERR(phy->wkupclk)) - clk_disable_unprepare(phy->wkupclk); - -disable_refclk: - if (!IS_ERR(phy->refclk)) - clk_disable_unprepare(phy->refclk); - - return ret; -} - -static void ti_pipe3_disable_clocks(struct ti_pipe3 *phy) -{ - if (!IS_ERR(phy->wkupclk)) - clk_disable_unprepare(phy->wkupclk); - if (!IS_ERR(phy->refclk)) { - clk_disable_unprepare(phy->refclk); - /* - * SATA refclk needs an additional disable as we left it - * on in probe to avoid Errata i783 - */ - if (phy->sata_refclk_enabled) { - clk_disable_unprepare(phy->refclk); - phy->sata_refclk_enabled = false; - } - } - - if (!IS_ERR(phy->div_clk)) - clk_disable_unprepare(phy->div_clk); -} - -static const struct of_device_id ti_pipe3_id_table[] = { - { - .compatible = "ti,phy-usb3", - .data = dpll_map_usb, - }, - { - .compatible = "ti,omap-usb3", - .data = dpll_map_usb, - }, - { - .compatible = "ti,phy-pipe3-sata", - .data = dpll_map_sata, - }, - { - .compatible = "ti,phy-pipe3-pcie", - }, - {} -}; -MODULE_DEVICE_TABLE(of, ti_pipe3_id_table); - -static struct platform_driver ti_pipe3_driver = { - .probe = ti_pipe3_probe, - .remove = ti_pipe3_remove, - .driver = { - .name = "ti-pipe3", - .of_match_table = ti_pipe3_id_table, - }, -}; - -module_platform_driver(ti_pipe3_driver); - -MODULE_ALIAS("platform:ti_pipe3"); -MODULE_AUTHOR("Texas Instruments Inc."); -MODULE_DESCRIPTION("TI PIPE3 phy driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/phy-tusb1210.c b/drivers/phy/phy-tusb1210.c deleted file mode 100644 index bb3fb031c478..000000000000 --- a/drivers/phy/phy-tusb1210.c +++ /dev/null @@ -1,146 +0,0 @@ -/** - * tusb1210.c - TUSB1210 USB ULPI PHY driver - * - * Copyright (C) 2015 Intel Corporation - * - * Author: Heikki Krogerus - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ -#include -#include -#include -#include - -#define TUSB1210_VENDOR_SPECIFIC2 0x80 -#define TUSB1210_VENDOR_SPECIFIC2_IHSTX_SHIFT 0 -#define TUSB1210_VENDOR_SPECIFIC2_ZHSDRV_SHIFT 4 -#define TUSB1210_VENDOR_SPECIFIC2_DP_SHIFT 6 - -struct tusb1210 { - struct ulpi *ulpi; - struct phy *phy; - struct gpio_desc *gpio_reset; - struct gpio_desc *gpio_cs; - u8 vendor_specific2; -}; - -static int tusb1210_power_on(struct phy *phy) -{ - struct tusb1210 *tusb = phy_get_drvdata(phy); - - gpiod_set_value_cansleep(tusb->gpio_reset, 1); - gpiod_set_value_cansleep(tusb->gpio_cs, 1); - - /* Restore the optional eye diagram optimization value */ - if (tusb->vendor_specific2) - ulpi_write(tusb->ulpi, TUSB1210_VENDOR_SPECIFIC2, - tusb->vendor_specific2); - - return 0; -} - -static int tusb1210_power_off(struct phy *phy) -{ - struct tusb1210 *tusb = phy_get_drvdata(phy); - - gpiod_set_value_cansleep(tusb->gpio_reset, 0); - gpiod_set_value_cansleep(tusb->gpio_cs, 0); - - return 0; -} - -static const struct phy_ops phy_ops = { - .power_on = tusb1210_power_on, - .power_off = tusb1210_power_off, - .owner = THIS_MODULE, -}; - -static int tusb1210_probe(struct ulpi *ulpi) -{ - struct tusb1210 *tusb; - u8 val, reg; - - tusb = devm_kzalloc(&ulpi->dev, sizeof(*tusb), GFP_KERNEL); - if (!tusb) - return -ENOMEM; - - tusb->gpio_reset = devm_gpiod_get_optional(&ulpi->dev, "reset", - GPIOD_OUT_LOW); - if (IS_ERR(tusb->gpio_reset)) - return PTR_ERR(tusb->gpio_reset); - - gpiod_set_value_cansleep(tusb->gpio_reset, 1); - - tusb->gpio_cs = devm_gpiod_get_optional(&ulpi->dev, "cs", - GPIOD_OUT_LOW); - if (IS_ERR(tusb->gpio_cs)) - return PTR_ERR(tusb->gpio_cs); - - gpiod_set_value_cansleep(tusb->gpio_cs, 1); - - /* - * VENDOR_SPECIFIC2 register in TUSB1210 can be used for configuring eye - * diagram optimization and DP/DM swap. - */ - - /* High speed output drive strength configuration */ - device_property_read_u8(&ulpi->dev, "ihstx", &val); - reg = val << TUSB1210_VENDOR_SPECIFIC2_IHSTX_SHIFT; - - /* High speed output impedance configuration */ - device_property_read_u8(&ulpi->dev, "zhsdrv", &val); - reg |= val << TUSB1210_VENDOR_SPECIFIC2_ZHSDRV_SHIFT; - - /* DP/DM swap control */ - device_property_read_u8(&ulpi->dev, "datapolarity", &val); - reg |= val << TUSB1210_VENDOR_SPECIFIC2_DP_SHIFT; - - if (reg) { - ulpi_write(ulpi, TUSB1210_VENDOR_SPECIFIC2, reg); - tusb->vendor_specific2 = reg; - } - - tusb->phy = ulpi_phy_create(ulpi, &phy_ops); - if (IS_ERR(tusb->phy)) - return PTR_ERR(tusb->phy); - - tusb->ulpi = ulpi; - - phy_set_drvdata(tusb->phy, tusb); - ulpi_set_drvdata(ulpi, tusb); - return 0; -} - -static void tusb1210_remove(struct ulpi *ulpi) -{ - struct tusb1210 *tusb = ulpi_get_drvdata(ulpi); - - ulpi_phy_destroy(ulpi, tusb->phy); -} - -#define TI_VENDOR_ID 0x0451 - -static const struct ulpi_device_id tusb1210_ulpi_id[] = { - { TI_VENDOR_ID, 0x1507, }, - { }, -}; -MODULE_DEVICE_TABLE(ulpi, tusb1210_ulpi_id); - -static struct ulpi_driver tusb1210_driver = { - .id_table = tusb1210_ulpi_id, - .probe = tusb1210_probe, - .remove = tusb1210_remove, - .driver = { - .name = "tusb1210", - .owner = THIS_MODULE, - }, -}; - -module_ulpi_driver(tusb1210_driver); - -MODULE_AUTHOR("Intel Corporation"); -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("TUSB1210 ULPI PHY driver"); diff --git a/drivers/phy/phy-twl4030-usb.c b/drivers/phy/phy-twl4030-usb.c deleted file mode 100644 index 2990b3965460..000000000000 --- a/drivers/phy/phy-twl4030-usb.c +++ /dev/null @@ -1,839 +0,0 @@ -/* - * twl4030_usb - TWL4030 USB transceiver, talking to OMAP OTG controller - * - * Copyright (C) 2004-2007 Texas Instruments - * Copyright (C) 2008 Nokia Corporation - * Contact: Felipe Balbi - * - * 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; either version 2 of the License, or - * (at your option) any later version. - * - * 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., 675 Mass Ave, Cambridge, MA 02139, USA. - * - * Current status: - * - HS USB ULPI mode works. - * - 3-pin mode support may be added in future. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* Register defines */ - -#define MCPC_CTRL 0x30 -#define MCPC_CTRL_RTSOL (1 << 7) -#define MCPC_CTRL_EXTSWR (1 << 6) -#define MCPC_CTRL_EXTSWC (1 << 5) -#define MCPC_CTRL_VOICESW (1 << 4) -#define MCPC_CTRL_OUT64K (1 << 3) -#define MCPC_CTRL_RTSCTSSW (1 << 2) -#define MCPC_CTRL_HS_UART (1 << 0) - -#define MCPC_IO_CTRL 0x33 -#define MCPC_IO_CTRL_MICBIASEN (1 << 5) -#define MCPC_IO_CTRL_CTS_NPU (1 << 4) -#define MCPC_IO_CTRL_RXD_PU (1 << 3) -#define MCPC_IO_CTRL_TXDTYP (1 << 2) -#define MCPC_IO_CTRL_CTSTYP (1 << 1) -#define MCPC_IO_CTRL_RTSTYP (1 << 0) - -#define MCPC_CTRL2 0x36 -#define MCPC_CTRL2_MCPC_CK_EN (1 << 0) - -#define OTHER_FUNC_CTRL 0x80 -#define OTHER_FUNC_CTRL_BDIS_ACON_EN (1 << 4) -#define OTHER_FUNC_CTRL_FIVEWIRE_MODE (1 << 2) - -#define OTHER_IFC_CTRL 0x83 -#define OTHER_IFC_CTRL_OE_INT_EN (1 << 6) -#define OTHER_IFC_CTRL_CEA2011_MODE (1 << 5) -#define OTHER_IFC_CTRL_FSLSSERIALMODE_4PIN (1 << 4) -#define OTHER_IFC_CTRL_HIZ_ULPI_60MHZ_OUT (1 << 3) -#define OTHER_IFC_CTRL_HIZ_ULPI (1 << 2) -#define OTHER_IFC_CTRL_ALT_INT_REROUTE (1 << 0) - -#define OTHER_INT_EN_RISE 0x86 -#define OTHER_INT_EN_FALL 0x89 -#define OTHER_INT_STS 0x8C -#define OTHER_INT_LATCH 0x8D -#define OTHER_INT_VB_SESS_VLD (1 << 7) -#define OTHER_INT_DM_HI (1 << 6) /* not valid for "latch" reg */ -#define OTHER_INT_DP_HI (1 << 5) /* not valid for "latch" reg */ -#define OTHER_INT_BDIS_ACON (1 << 3) /* not valid for "fall" regs */ -#define OTHER_INT_MANU (1 << 1) -#define OTHER_INT_ABNORMAL_STRESS (1 << 0) - -#define ID_STATUS 0x96 -#define ID_RES_FLOAT (1 << 4) -#define ID_RES_440K (1 << 3) -#define ID_RES_200K (1 << 2) -#define ID_RES_102K (1 << 1) -#define ID_RES_GND (1 << 0) - -#define POWER_CTRL 0xAC -#define POWER_CTRL_OTG_ENAB (1 << 5) - -#define OTHER_IFC_CTRL2 0xAF -#define OTHER_IFC_CTRL2_ULPI_STP_LOW (1 << 4) -#define OTHER_IFC_CTRL2_ULPI_TXEN_POL (1 << 3) -#define OTHER_IFC_CTRL2_ULPI_4PIN_2430 (1 << 2) -#define OTHER_IFC_CTRL2_USB_INT_OUTSEL_MASK (3 << 0) /* bits 0 and 1 */ -#define OTHER_IFC_CTRL2_USB_INT_OUTSEL_INT1N (0 << 0) -#define OTHER_IFC_CTRL2_USB_INT_OUTSEL_INT2N (1 << 0) - -#define REG_CTRL_EN 0xB2 -#define REG_CTRL_ERROR 0xB5 -#define ULPI_I2C_CONFLICT_INTEN (1 << 0) - -#define OTHER_FUNC_CTRL2 0xB8 -#define OTHER_FUNC_CTRL2_VBAT_TIMER_EN (1 << 0) - -/* following registers do not have separate _clr and _set registers */ -#define VBUS_DEBOUNCE 0xC0 -#define ID_DEBOUNCE 0xC1 -#define VBAT_TIMER 0xD3 -#define PHY_PWR_CTRL 0xFD -#define PHY_PWR_PHYPWD (1 << 0) -#define PHY_CLK_CTRL 0xFE -#define PHY_CLK_CTRL_CLOCKGATING_EN (1 << 2) -#define PHY_CLK_CTRL_CLK32K_EN (1 << 1) -#define REQ_PHY_DPLL_CLK (1 << 0) -#define PHY_CLK_CTRL_STS 0xFF -#define PHY_DPLL_CLK (1 << 0) - -/* In module TWL_MODULE_PM_MASTER */ -#define STS_HW_CONDITIONS 0x0F - -/* In module TWL_MODULE_PM_RECEIVER */ -#define VUSB_DEDICATED1 0x7D -#define VUSB_DEDICATED2 0x7E -#define VUSB1V5_DEV_GRP 0x71 -#define VUSB1V5_TYPE 0x72 -#define VUSB1V5_REMAP 0x73 -#define VUSB1V8_DEV_GRP 0x74 -#define VUSB1V8_TYPE 0x75 -#define VUSB1V8_REMAP 0x76 -#define VUSB3V1_DEV_GRP 0x77 -#define VUSB3V1_TYPE 0x78 -#define VUSB3V1_REMAP 0x79 - -/* In module TWL4030_MODULE_INTBR */ -#define PMBR1 0x0D -#define GPIO_USB_4PIN_ULPI_2430C (3 << 0) - -/* - * If VBUS is valid or ID is ground, then we know a - * cable is present and we need to be runtime-enabled - */ -static inline bool cable_present(enum musb_vbus_id_status stat) -{ - return stat == MUSB_VBUS_VALID || - stat == MUSB_ID_GROUND; -} - -struct twl4030_usb { - struct usb_phy phy; - struct device *dev; - - /* TWL4030 internal USB regulator supplies */ - struct regulator *usb1v5; - struct regulator *usb1v8; - struct regulator *usb3v1; - - /* for vbus reporting with irqs disabled */ - struct mutex lock; - - /* pin configuration */ - enum twl4030_usb_mode usb_mode; - - int irq; - enum musb_vbus_id_status linkstat; - bool vbus_supplied; - bool musb_mailbox_pending; - - struct delayed_work id_workaround_work; -}; - -/* internal define on top of container_of */ -#define phy_to_twl(x) container_of((x), struct twl4030_usb, phy) - -/*-------------------------------------------------------------------------*/ - -static int twl4030_i2c_write_u8_verify(struct twl4030_usb *twl, - u8 module, u8 data, u8 address) -{ - u8 check; - - if ((twl_i2c_write_u8(module, data, address) >= 0) && - (twl_i2c_read_u8(module, &check, address) >= 0) && - (check == data)) - return 0; - dev_dbg(twl->dev, "Write%d[%d,0x%x] wrote %02x but read %02x\n", - 1, module, address, check, data); - - /* Failed once: Try again */ - if ((twl_i2c_write_u8(module, data, address) >= 0) && - (twl_i2c_read_u8(module, &check, address) >= 0) && - (check == data)) - return 0; - dev_dbg(twl->dev, "Write%d[%d,0x%x] wrote %02x but read %02x\n", - 2, module, address, check, data); - - /* Failed again: Return error */ - return -EBUSY; -} - -#define twl4030_usb_write_verify(twl, address, data) \ - twl4030_i2c_write_u8_verify(twl, TWL_MODULE_USB, (data), (address)) - -static inline int twl4030_usb_write(struct twl4030_usb *twl, - u8 address, u8 data) -{ - int ret = 0; - - ret = twl_i2c_write_u8(TWL_MODULE_USB, data, address); - if (ret < 0) - dev_dbg(twl->dev, - "TWL4030:USB:Write[0x%x] Error %d\n", address, ret); - return ret; -} - -static inline int twl4030_readb(struct twl4030_usb *twl, u8 module, u8 address) -{ - u8 data; - int ret = 0; - - ret = twl_i2c_read_u8(module, &data, address); - if (ret >= 0) - ret = data; - else - dev_dbg(twl->dev, - "TWL4030:readb[0x%x,0x%x] Error %d\n", - module, address, ret); - - return ret; -} - -static inline int twl4030_usb_read(struct twl4030_usb *twl, u8 address) -{ - return twl4030_readb(twl, TWL_MODULE_USB, address); -} - -/*-------------------------------------------------------------------------*/ - -static inline int -twl4030_usb_set_bits(struct twl4030_usb *twl, u8 reg, u8 bits) -{ - return twl4030_usb_write(twl, ULPI_SET(reg), bits); -} - -static inline int -twl4030_usb_clear_bits(struct twl4030_usb *twl, u8 reg, u8 bits) -{ - return twl4030_usb_write(twl, ULPI_CLR(reg), bits); -} - -/*-------------------------------------------------------------------------*/ - -static bool twl4030_is_driving_vbus(struct twl4030_usb *twl) -{ - int ret; - - ret = twl4030_usb_read(twl, PHY_CLK_CTRL_STS); - if (ret < 0 || !(ret & PHY_DPLL_CLK)) - /* - * if clocks are off, registers are not updated, - * but we can assume we don't drive VBUS in this case - */ - return false; - - ret = twl4030_usb_read(twl, ULPI_OTG_CTRL); - if (ret < 0) - return false; - - return (ret & (ULPI_OTG_DRVVBUS | ULPI_OTG_CHRGVBUS)) ? true : false; -} - -static enum musb_vbus_id_status - twl4030_usb_linkstat(struct twl4030_usb *twl) -{ - int status; - enum musb_vbus_id_status linkstat = MUSB_UNKNOWN; - - twl->vbus_supplied = false; - - /* - * For ID/VBUS sensing, see manual section 15.4.8 ... - * except when using only battery backup power, two - * comparators produce VBUS_PRES and ID_PRES signals, - * which don't match docs elsewhere. But ... BIT(7) - * and BIT(2) of STS_HW_CONDITIONS, respectively, do - * seem to match up. If either is true the USB_PRES - * signal is active, the OTG module is activated, and - * its interrupt may be raised (may wake the system). - */ - status = twl4030_readb(twl, TWL_MODULE_PM_MASTER, STS_HW_CONDITIONS); - if (status < 0) - dev_err(twl->dev, "USB link status err %d\n", status); - else if (status & (BIT(7) | BIT(2))) { - if (status & BIT(7)) { - if (twl4030_is_driving_vbus(twl)) - status &= ~BIT(7); - else - twl->vbus_supplied = true; - } - - if (status & BIT(2)) - linkstat = MUSB_ID_GROUND; - else if (status & BIT(7)) - linkstat = MUSB_VBUS_VALID; - else - linkstat = MUSB_VBUS_OFF; - } else { - if (twl->linkstat != MUSB_UNKNOWN) - linkstat = MUSB_VBUS_OFF; - } - - kobject_uevent(&twl->dev->kobj, linkstat == MUSB_VBUS_VALID - ? KOBJ_ONLINE : KOBJ_OFFLINE); - - dev_dbg(twl->dev, "HW_CONDITIONS 0x%02x/%d; link %d\n", - status, status, linkstat); - - /* REVISIT this assumes host and peripheral controllers - * are registered, and that both are active... - */ - - return linkstat; -} - -static void twl4030_usb_set_mode(struct twl4030_usb *twl, int mode) -{ - twl->usb_mode = mode; - - switch (mode) { - case T2_USB_MODE_ULPI: - twl4030_usb_clear_bits(twl, ULPI_IFC_CTRL, - ULPI_IFC_CTRL_CARKITMODE); - twl4030_usb_set_bits(twl, POWER_CTRL, POWER_CTRL_OTG_ENAB); - twl4030_usb_clear_bits(twl, ULPI_FUNC_CTRL, - ULPI_FUNC_CTRL_XCVRSEL_MASK | - ULPI_FUNC_CTRL_OPMODE_MASK); - break; - case -1: - /* FIXME: power on defaults */ - break; - default: - dev_err(twl->dev, "unsupported T2 transceiver mode %d\n", - mode); - break; - } -} - -static void twl4030_i2c_access(struct twl4030_usb *twl, int on) -{ - unsigned long timeout; - int val = twl4030_usb_read(twl, PHY_CLK_CTRL); - - if (val >= 0) { - if (on) { - /* enable DPLL to access PHY registers over I2C */ - val |= REQ_PHY_DPLL_CLK; - WARN_ON(twl4030_usb_write_verify(twl, PHY_CLK_CTRL, - (u8)val) < 0); - - timeout = jiffies + HZ; - while (!(twl4030_usb_read(twl, PHY_CLK_CTRL_STS) & - PHY_DPLL_CLK) - && time_before(jiffies, timeout)) - udelay(10); - if (!(twl4030_usb_read(twl, PHY_CLK_CTRL_STS) & - PHY_DPLL_CLK)) - dev_err(twl->dev, "Timeout setting T2 HSUSB " - "PHY DPLL clock\n"); - } else { - /* let ULPI control the DPLL clock */ - val &= ~REQ_PHY_DPLL_CLK; - WARN_ON(twl4030_usb_write_verify(twl, PHY_CLK_CTRL, - (u8)val) < 0); - } - } -} - -static void __twl4030_phy_power(struct twl4030_usb *twl, int on) -{ - u8 pwr = twl4030_usb_read(twl, PHY_PWR_CTRL); - - if (on) - pwr &= ~PHY_PWR_PHYPWD; - else - pwr |= PHY_PWR_PHYPWD; - - WARN_ON(twl4030_usb_write_verify(twl, PHY_PWR_CTRL, pwr) < 0); -} - -static int __maybe_unused twl4030_usb_runtime_suspend(struct device *dev) -{ - struct twl4030_usb *twl = dev_get_drvdata(dev); - - dev_dbg(twl->dev, "%s\n", __func__); - - __twl4030_phy_power(twl, 0); - regulator_disable(twl->usb1v5); - regulator_disable(twl->usb1v8); - regulator_disable(twl->usb3v1); - - return 0; -} - -static int __maybe_unused twl4030_usb_runtime_resume(struct device *dev) -{ - struct twl4030_usb *twl = dev_get_drvdata(dev); - int res; - - dev_dbg(twl->dev, "%s\n", __func__); - - res = regulator_enable(twl->usb3v1); - if (res) - dev_err(twl->dev, "Failed to enable usb3v1\n"); - - res = regulator_enable(twl->usb1v8); - if (res) - dev_err(twl->dev, "Failed to enable usb1v8\n"); - - /* - * Disabling usb3v1 regulator (= writing 0 to VUSB3V1_DEV_GRP - * in twl4030) resets the VUSB_DEDICATED2 register. This reset - * enables VUSB3V1_SLEEP bit that remaps usb3v1 ACTIVE state to - * SLEEP. We work around this by clearing the bit after usv3v1 - * is re-activated. This ensures that VUSB3V1 is really active. - */ - twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB_DEDICATED2); - - res = regulator_enable(twl->usb1v5); - if (res) - dev_err(twl->dev, "Failed to enable usb1v5\n"); - - __twl4030_phy_power(twl, 1); - twl4030_usb_write(twl, PHY_CLK_CTRL, - twl4030_usb_read(twl, PHY_CLK_CTRL) | - (PHY_CLK_CTRL_CLOCKGATING_EN | - PHY_CLK_CTRL_CLK32K_EN)); - - twl4030_i2c_access(twl, 1); - twl4030_usb_set_mode(twl, twl->usb_mode); - if (twl->usb_mode == T2_USB_MODE_ULPI) - twl4030_i2c_access(twl, 0); - /* - * According to the TPS65950 TRM, there has to be at least 50ms - * delay between setting POWER_CTRL_OTG_ENAB and enabling charging - * so wait here so that a fully enabled phy can be expected after - * resume - */ - msleep(50); - return 0; -} - -static int twl4030_phy_power_off(struct phy *phy) -{ - struct twl4030_usb *twl = phy_get_drvdata(phy); - - dev_dbg(twl->dev, "%s\n", __func__); - - return 0; -} - -static int twl4030_phy_power_on(struct phy *phy) -{ - struct twl4030_usb *twl = phy_get_drvdata(phy); - - dev_dbg(twl->dev, "%s\n", __func__); - pm_runtime_get_sync(twl->dev); - schedule_delayed_work(&twl->id_workaround_work, HZ); - pm_runtime_mark_last_busy(twl->dev); - pm_runtime_put_autosuspend(twl->dev); - - return 0; -} - -static int twl4030_usb_ldo_init(struct twl4030_usb *twl) -{ - /* Enable writing to power configuration registers */ - twl_i2c_write_u8(TWL_MODULE_PM_MASTER, TWL4030_PM_MASTER_KEY_CFG1, - TWL4030_PM_MASTER_PROTECT_KEY); - - twl_i2c_write_u8(TWL_MODULE_PM_MASTER, TWL4030_PM_MASTER_KEY_CFG2, - TWL4030_PM_MASTER_PROTECT_KEY); - - /* Keep VUSB3V1 LDO in sleep state until VBUS/ID change detected*/ - /*twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB_DEDICATED2);*/ - - /* input to VUSB3V1 LDO is from VBAT, not VBUS */ - twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0x14, VUSB_DEDICATED1); - - /* Initialize 3.1V regulator */ - twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB3V1_DEV_GRP); - - twl->usb3v1 = devm_regulator_get(twl->dev, "usb3v1"); - if (IS_ERR(twl->usb3v1)) - return -ENODEV; - - twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB3V1_TYPE); - - /* Initialize 1.5V regulator */ - twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB1V5_DEV_GRP); - - twl->usb1v5 = devm_regulator_get(twl->dev, "usb1v5"); - if (IS_ERR(twl->usb1v5)) - return -ENODEV; - - twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB1V5_TYPE); - - /* Initialize 1.8V regulator */ - twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB1V8_DEV_GRP); - - twl->usb1v8 = devm_regulator_get(twl->dev, "usb1v8"); - if (IS_ERR(twl->usb1v8)) - return -ENODEV; - - twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB1V8_TYPE); - - /* disable access to power configuration registers */ - twl_i2c_write_u8(TWL_MODULE_PM_MASTER, 0, - TWL4030_PM_MASTER_PROTECT_KEY); - - return 0; -} - -static ssize_t twl4030_usb_vbus_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct twl4030_usb *twl = dev_get_drvdata(dev); - int ret = -EINVAL; - - mutex_lock(&twl->lock); - ret = sprintf(buf, "%s\n", - twl->vbus_supplied ? "on" : "off"); - mutex_unlock(&twl->lock); - - return ret; -} -static DEVICE_ATTR(vbus, 0444, twl4030_usb_vbus_show, NULL); - -static irqreturn_t twl4030_usb_irq(int irq, void *_twl) -{ - struct twl4030_usb *twl = _twl; - enum musb_vbus_id_status status; - bool status_changed = false; - int err; - - status = twl4030_usb_linkstat(twl); - - mutex_lock(&twl->lock); - if (status >= 0 && status != twl->linkstat) { - status_changed = - cable_present(twl->linkstat) != - cable_present(status); - twl->linkstat = status; - } - mutex_unlock(&twl->lock); - - if (status_changed) { - /* FIXME add a set_power() method so that B-devices can - * configure the charger appropriately. It's not always - * correct to consume VBUS power, and how much current to - * consume is a function of the USB configuration chosen - * by the host. - * - * REVISIT usb_gadget_vbus_connect(...) as needed, ditto - * its disconnect() sibling, when changing to/from the - * USB_LINK_VBUS state. musb_hdrc won't care until it - * starts to handle softconnect right. - */ - if (cable_present(status)) { - pm_runtime_get_sync(twl->dev); - } else { - pm_runtime_mark_last_busy(twl->dev); - pm_runtime_put_autosuspend(twl->dev); - } - twl->musb_mailbox_pending = true; - } - if (twl->musb_mailbox_pending) { - err = musb_mailbox(status); - if (!err) - twl->musb_mailbox_pending = false; - } - - /* don't schedule during sleep - irq works right then */ - if (status == MUSB_ID_GROUND && pm_runtime_active(twl->dev)) { - cancel_delayed_work(&twl->id_workaround_work); - schedule_delayed_work(&twl->id_workaround_work, HZ); - } - - if (irq) - sysfs_notify(&twl->dev->kobj, NULL, "vbus"); - - return IRQ_HANDLED; -} - -static void twl4030_id_workaround_work(struct work_struct *work) -{ - struct twl4030_usb *twl = container_of(work, struct twl4030_usb, - id_workaround_work.work); - - twl4030_usb_irq(0, twl); -} - -static int twl4030_phy_init(struct phy *phy) -{ - struct twl4030_usb *twl = phy_get_drvdata(phy); - - pm_runtime_get_sync(twl->dev); - twl->linkstat = MUSB_UNKNOWN; - schedule_delayed_work(&twl->id_workaround_work, HZ); - pm_runtime_mark_last_busy(twl->dev); - pm_runtime_put_autosuspend(twl->dev); - - return 0; -} - -static int twl4030_set_peripheral(struct usb_otg *otg, - struct usb_gadget *gadget) -{ - if (!otg) - return -ENODEV; - - otg->gadget = gadget; - if (!gadget) - otg->state = OTG_STATE_UNDEFINED; - - return 0; -} - -static int twl4030_set_host(struct usb_otg *otg, struct usb_bus *host) -{ - if (!otg) - return -ENODEV; - - otg->host = host; - if (!host) - otg->state = OTG_STATE_UNDEFINED; - - return 0; -} - -static const struct phy_ops ops = { - .init = twl4030_phy_init, - .power_on = twl4030_phy_power_on, - .power_off = twl4030_phy_power_off, - .owner = THIS_MODULE, -}; - -static const struct dev_pm_ops twl4030_usb_pm_ops = { - SET_RUNTIME_PM_OPS(twl4030_usb_runtime_suspend, - twl4030_usb_runtime_resume, NULL) -}; - -static int twl4030_usb_probe(struct platform_device *pdev) -{ - struct twl4030_usb_data *pdata = dev_get_platdata(&pdev->dev); - struct twl4030_usb *twl; - struct phy *phy; - int status, err; - struct usb_otg *otg; - struct device_node *np = pdev->dev.of_node; - struct phy_provider *phy_provider; - - twl = devm_kzalloc(&pdev->dev, sizeof(*twl), GFP_KERNEL); - if (!twl) - return -ENOMEM; - - if (np) - of_property_read_u32(np, "usb_mode", - (enum twl4030_usb_mode *)&twl->usb_mode); - else if (pdata) { - twl->usb_mode = pdata->usb_mode; - } else { - dev_err(&pdev->dev, "twl4030 initialized without pdata\n"); - return -EINVAL; - } - - otg = devm_kzalloc(&pdev->dev, sizeof(*otg), GFP_KERNEL); - if (!otg) - return -ENOMEM; - - twl->dev = &pdev->dev; - twl->irq = platform_get_irq(pdev, 0); - twl->vbus_supplied = false; - twl->linkstat = MUSB_UNKNOWN; - twl->musb_mailbox_pending = false; - - twl->phy.dev = twl->dev; - twl->phy.label = "twl4030"; - twl->phy.otg = otg; - twl->phy.type = USB_PHY_TYPE_USB2; - - otg->usb_phy = &twl->phy; - otg->set_host = twl4030_set_host; - otg->set_peripheral = twl4030_set_peripheral; - - phy = devm_phy_create(twl->dev, NULL, &ops); - if (IS_ERR(phy)) { - dev_dbg(&pdev->dev, "Failed to create PHY\n"); - return PTR_ERR(phy); - } - - phy_set_drvdata(phy, twl); - - phy_provider = devm_of_phy_provider_register(twl->dev, - of_phy_simple_xlate); - if (IS_ERR(phy_provider)) - return PTR_ERR(phy_provider); - - /* init mutex for workqueue */ - mutex_init(&twl->lock); - - INIT_DELAYED_WORK(&twl->id_workaround_work, twl4030_id_workaround_work); - - err = twl4030_usb_ldo_init(twl); - if (err) { - dev_err(&pdev->dev, "ldo init failed\n"); - return err; - } - usb_add_phy_dev(&twl->phy); - - platform_set_drvdata(pdev, twl); - if (device_create_file(&pdev->dev, &dev_attr_vbus)) - dev_warn(&pdev->dev, "could not create sysfs file\n"); - - ATOMIC_INIT_NOTIFIER_HEAD(&twl->phy.notifier); - - pm_runtime_use_autosuspend(&pdev->dev); - pm_runtime_set_autosuspend_delay(&pdev->dev, 2000); - pm_runtime_enable(&pdev->dev); - pm_runtime_get_sync(&pdev->dev); - - /* Our job is to use irqs and status from the power module - * to keep the transceiver disabled when nothing's connected. - * - * FIXME we actually shouldn't start enabling it until the - * USB controller drivers have said they're ready, by calling - * set_host() and/or set_peripheral() ... OTG_capable boards - * need both handles, otherwise just one suffices. - */ - status = devm_request_threaded_irq(twl->dev, twl->irq, NULL, - twl4030_usb_irq, IRQF_TRIGGER_FALLING | - IRQF_TRIGGER_RISING | IRQF_ONESHOT, "twl4030_usb", twl); - if (status < 0) { - dev_dbg(&pdev->dev, "can't get IRQ %d, err %d\n", - twl->irq, status); - return status; - } - - if (pdata) - err = phy_create_lookup(phy, "usb", "musb-hdrc.0"); - if (err) - return err; - - pm_runtime_mark_last_busy(&pdev->dev); - pm_runtime_put_autosuspend(twl->dev); - - dev_info(&pdev->dev, "Initialized TWL4030 USB module\n"); - return 0; -} - -static int twl4030_usb_remove(struct platform_device *pdev) -{ - struct twl4030_usb *twl = platform_get_drvdata(pdev); - int val; - - usb_remove_phy(&twl->phy); - pm_runtime_get_sync(twl->dev); - cancel_delayed_work(&twl->id_workaround_work); - device_remove_file(twl->dev, &dev_attr_vbus); - - /* set transceiver mode to power on defaults */ - twl4030_usb_set_mode(twl, -1); - - /* idle ulpi before powering off */ - if (cable_present(twl->linkstat)) - pm_runtime_put_noidle(twl->dev); - pm_runtime_mark_last_busy(twl->dev); - pm_runtime_dont_use_autosuspend(&pdev->dev); - pm_runtime_put_sync(twl->dev); - pm_runtime_disable(twl->dev); - - /* autogate 60MHz ULPI clock, - * clear dpll clock request for i2c access, - * disable 32KHz - */ - val = twl4030_usb_read(twl, PHY_CLK_CTRL); - if (val >= 0) { - val |= PHY_CLK_CTRL_CLOCKGATING_EN; - val &= ~(PHY_CLK_CTRL_CLK32K_EN | REQ_PHY_DPLL_CLK); - twl4030_usb_write(twl, PHY_CLK_CTRL, (u8)val); - } - - /* disable complete OTG block */ - twl4030_usb_clear_bits(twl, POWER_CTRL, POWER_CTRL_OTG_ENAB); - - return 0; -} - -#ifdef CONFIG_OF -static const struct of_device_id twl4030_usb_id_table[] = { - { .compatible = "ti,twl4030-usb" }, - {} -}; -MODULE_DEVICE_TABLE(of, twl4030_usb_id_table); -#endif - -static struct platform_driver twl4030_usb_driver = { - .probe = twl4030_usb_probe, - .remove = twl4030_usb_remove, - .driver = { - .name = "twl4030_usb", - .pm = &twl4030_usb_pm_ops, - .of_match_table = of_match_ptr(twl4030_usb_id_table), - }, -}; - -static int __init twl4030_usb_init(void) -{ - return platform_driver_register(&twl4030_usb_driver); -} -subsys_initcall(twl4030_usb_init); - -static void __exit twl4030_usb_exit(void) -{ - platform_driver_unregister(&twl4030_usb_driver); -} -module_exit(twl4030_usb_exit); - -MODULE_ALIAS("platform:twl4030_usb"); -MODULE_AUTHOR("Texas Instruments, Inc, Nokia Corporation"); -MODULE_DESCRIPTION("TWL4030 USB transceiver driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/phy/qualcomm/Kconfig b/drivers/phy/qualcomm/Kconfig new file mode 100644 index 000000000000..7bfa64baf837 --- /dev/null +++ b/drivers/phy/qualcomm/Kconfig @@ -0,0 +1,58 @@ +# +# Phy drivers for Qualcomm platforms +# +config PHY_QCOM_APQ8064_SATA + tristate "Qualcomm APQ8064 SATA SerDes/PHY driver" + depends on ARCH_QCOM + depends on HAS_IOMEM + depends on OF + select GENERIC_PHY + +config PHY_QCOM_IPQ806X_SATA + tristate "Qualcomm IPQ806x SATA SerDes/PHY driver" + depends on ARCH_QCOM + depends on HAS_IOMEM + depends on OF + select GENERIC_PHY + +config PHY_QCOM_QMP + tristate "Qualcomm QMP PHY Driver" + depends on OF && COMMON_CLK && (ARCH_QCOM || COMPILE_TEST) + select GENERIC_PHY + help + Enable this to support the QMP PHY transceiver that is used + with controllers such as PCIe, UFS, and USB on Qualcomm chips. + +config PHY_QCOM_QUSB2 + tristate "Qualcomm QUSB2 PHY Driver" + depends on OF && (ARCH_QCOM || COMPILE_TEST) + depends on NVMEM || !NVMEM + select GENERIC_PHY + help + Enable this to support the HighSpeed QUSB2 PHY transceiver for USB + controllers on Qualcomm chips. This driver supports the high-speed + PHY which is usually paired with either the ChipIdea or Synopsys DWC3 + USB IPs on MSM SOCs. + +config PHY_QCOM_UFS + tristate "Qualcomm UFS PHY driver" + depends on OF && ARCH_QCOM + select GENERIC_PHY + help + Support for UFS PHY on QCOM chipsets. + +config PHY_QCOM_USB_HS + tristate "Qualcomm USB HS PHY module" + depends on USB_ULPI_BUS + depends on EXTCON || !EXTCON # if EXTCON=m, this cannot be built-in + select GENERIC_PHY + help + Support for the USB high-speed ULPI compliant phy on Qualcomm + chipsets. + +config PHY_QCOM_USB_HSIC + tristate "Qualcomm USB HSIC ULPI PHY module" + depends on USB_ULPI_BUS + select GENERIC_PHY + help + Support for the USB HSIC ULPI compliant PHY on QCOM chipsets. diff --git a/drivers/phy/qualcomm/Makefile b/drivers/phy/qualcomm/Makefile new file mode 100644 index 000000000000..2e183d7695fd --- /dev/null +++ b/drivers/phy/qualcomm/Makefile @@ -0,0 +1,9 @@ +obj-$(CONFIG_PHY_QCOM_APQ8064_SATA) += phy-qcom-apq8064-sata.o +obj-$(CONFIG_PHY_QCOM_IPQ806X_SATA) += phy-qcom-ipq806x-sata.o +obj-$(CONFIG_PHY_QCOM_QMP) += phy-qcom-qmp.o +obj-$(CONFIG_PHY_QCOM_QUSB2) += phy-qcom-qusb2.o +obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs.o +obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs-qmp-14nm.o +obj-$(CONFIG_PHY_QCOM_UFS) += phy-qcom-ufs-qmp-20nm.o +obj-$(CONFIG_PHY_QCOM_USB_HS) += phy-qcom-usb-hs.o +obj-$(CONFIG_PHY_QCOM_USB_HSIC) += phy-qcom-usb-hsic.o diff --git a/drivers/phy/qualcomm/phy-qcom-apq8064-sata.c b/drivers/phy/qualcomm/phy-qcom-apq8064-sata.c new file mode 100644 index 000000000000..69ce2afac015 --- /dev/null +++ b/drivers/phy/qualcomm/phy-qcom-apq8064-sata.c @@ -0,0 +1,287 @@ +/* + * Copyright (c) 2014, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* PHY registers */ +#define UNIPHY_PLL_REFCLK_CFG 0x000 +#define UNIPHY_PLL_PWRGEN_CFG 0x014 +#define UNIPHY_PLL_GLB_CFG 0x020 +#define UNIPHY_PLL_SDM_CFG0 0x038 +#define UNIPHY_PLL_SDM_CFG1 0x03C +#define UNIPHY_PLL_SDM_CFG2 0x040 +#define UNIPHY_PLL_SDM_CFG3 0x044 +#define UNIPHY_PLL_SDM_CFG4 0x048 +#define UNIPHY_PLL_SSC_CFG0 0x04C +#define UNIPHY_PLL_SSC_CFG1 0x050 +#define UNIPHY_PLL_SSC_CFG2 0x054 +#define UNIPHY_PLL_SSC_CFG3 0x058 +#define UNIPHY_PLL_LKDET_CFG0 0x05C +#define UNIPHY_PLL_LKDET_CFG1 0x060 +#define UNIPHY_PLL_LKDET_CFG2 0x064 +#define UNIPHY_PLL_CAL_CFG0 0x06C +#define UNIPHY_PLL_CAL_CFG8 0x08C +#define UNIPHY_PLL_CAL_CFG9 0x090 +#define UNIPHY_PLL_CAL_CFG10 0x094 +#define UNIPHY_PLL_CAL_CFG11 0x098 +#define UNIPHY_PLL_STATUS 0x0C0 + +#define SATA_PHY_SER_CTRL 0x100 +#define SATA_PHY_TX_DRIV_CTRL0 0x104 +#define SATA_PHY_TX_DRIV_CTRL1 0x108 +#define SATA_PHY_TX_IMCAL0 0x11C +#define SATA_PHY_TX_IMCAL2 0x124 +#define SATA_PHY_RX_IMCAL0 0x128 +#define SATA_PHY_EQUAL 0x13C +#define SATA_PHY_OOB_TERM 0x144 +#define SATA_PHY_CDR_CTRL0 0x148 +#define SATA_PHY_CDR_CTRL1 0x14C +#define SATA_PHY_CDR_CTRL2 0x150 +#define SATA_PHY_CDR_CTRL3 0x154 +#define SATA_PHY_PI_CTRL0 0x168 +#define SATA_PHY_POW_DWN_CTRL0 0x180 +#define SATA_PHY_POW_DWN_CTRL1 0x184 +#define SATA_PHY_TX_DATA_CTRL 0x188 +#define SATA_PHY_ALIGNP 0x1A4 +#define SATA_PHY_TX_IMCAL_STAT 0x1E4 +#define SATA_PHY_RX_IMCAL_STAT 0x1E8 + +#define UNIPHY_PLL_LOCK BIT(0) +#define SATA_PHY_TX_CAL BIT(0) +#define SATA_PHY_RX_CAL BIT(0) + +/* default timeout set to 1 sec */ +#define TIMEOUT_MS 10000 +#define DELAY_INTERVAL_US 100 + +struct qcom_apq8064_sata_phy { + void __iomem *mmio; + struct clk *cfg_clk; + struct device *dev; +}; + +/* Helper function to do poll and timeout */ +static int read_poll_timeout(void __iomem *addr, u32 mask) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(TIMEOUT_MS); + + do { + if (readl_relaxed(addr) & mask) + return 0; + + usleep_range(DELAY_INTERVAL_US, DELAY_INTERVAL_US + 50); + } while (!time_after(jiffies, timeout)); + + return (readl_relaxed(addr) & mask) ? 0 : -ETIMEDOUT; +} + +static int qcom_apq8064_sata_phy_init(struct phy *generic_phy) +{ + struct qcom_apq8064_sata_phy *phy = phy_get_drvdata(generic_phy); + void __iomem *base = phy->mmio; + int ret = 0; + + /* SATA phy initialization */ + writel_relaxed(0x01, base + SATA_PHY_SER_CTRL); + writel_relaxed(0xB1, base + SATA_PHY_POW_DWN_CTRL0); + /* Make sure the power down happens before power up */ + mb(); + usleep_range(10, 60); + + writel_relaxed(0x01, base + SATA_PHY_POW_DWN_CTRL0); + writel_relaxed(0x3E, base + SATA_PHY_POW_DWN_CTRL1); + writel_relaxed(0x01, base + SATA_PHY_RX_IMCAL0); + writel_relaxed(0x01, base + SATA_PHY_TX_IMCAL0); + writel_relaxed(0x02, base + SATA_PHY_TX_IMCAL2); + + /* Write UNIPHYPLL registers to configure PLL */ + writel_relaxed(0x04, base + UNIPHY_PLL_REFCLK_CFG); + writel_relaxed(0x00, base + UNIPHY_PLL_PWRGEN_CFG); + + writel_relaxed(0x0A, base + UNIPHY_PLL_CAL_CFG0); + writel_relaxed(0xF3, base + UNIPHY_PLL_CAL_CFG8); + writel_relaxed(0x01, base + UNIPHY_PLL_CAL_CFG9); + writel_relaxed(0xED, base + UNIPHY_PLL_CAL_CFG10); + writel_relaxed(0x02, base + UNIPHY_PLL_CAL_CFG11); + + writel_relaxed(0x36, base + UNIPHY_PLL_SDM_CFG0); + writel_relaxed(0x0D, base + UNIPHY_PLL_SDM_CFG1); + writel_relaxed(0xA3, base + UNIPHY_PLL_SDM_CFG2); + writel_relaxed(0xF0, base + UNIPHY_PLL_SDM_CFG3); + writel_relaxed(0x00, base + UNIPHY_PLL_SDM_CFG4); + + writel_relaxed(0x19, base + UNIPHY_PLL_SSC_CFG0); + writel_relaxed(0xE1, base + UNIPHY_PLL_SSC_CFG1); + writel_relaxed(0x00, base + UNIPHY_PLL_SSC_CFG2); + writel_relaxed(0x11, base + UNIPHY_PLL_SSC_CFG3); + + writel_relaxed(0x04, base + UNIPHY_PLL_LKDET_CFG0); + writel_relaxed(0xFF, base + UNIPHY_PLL_LKDET_CFG1); + + writel_relaxed(0x02, base + UNIPHY_PLL_GLB_CFG); + /* make sure global config LDO power down happens before power up */ + mb(); + + writel_relaxed(0x03, base + UNIPHY_PLL_GLB_CFG); + writel_relaxed(0x05, base + UNIPHY_PLL_LKDET_CFG2); + + /* PLL Lock wait */ + ret = read_poll_timeout(base + UNIPHY_PLL_STATUS, UNIPHY_PLL_LOCK); + if (ret) { + dev_err(phy->dev, "poll timeout UNIPHY_PLL_STATUS\n"); + return ret; + } + + /* TX Calibration */ + ret = read_poll_timeout(base + SATA_PHY_TX_IMCAL_STAT, SATA_PHY_TX_CAL); + if (ret) { + dev_err(phy->dev, "poll timeout SATA_PHY_TX_IMCAL_STAT\n"); + return ret; + } + + /* RX Calibration */ + ret = read_poll_timeout(base + SATA_PHY_RX_IMCAL_STAT, SATA_PHY_RX_CAL); + if (ret) { + dev_err(phy->dev, "poll timeout SATA_PHY_RX_IMCAL_STAT\n"); + return ret; + } + + /* SATA phy calibrated succesfully, power up to functional mode */ + writel_relaxed(0x3E, base + SATA_PHY_POW_DWN_CTRL1); + writel_relaxed(0x01, base + SATA_PHY_RX_IMCAL0); + writel_relaxed(0x01, base + SATA_PHY_TX_IMCAL0); + + writel_relaxed(0x00, base + SATA_PHY_POW_DWN_CTRL1); + writel_relaxed(0x59, base + SATA_PHY_CDR_CTRL0); + writel_relaxed(0x04, base + SATA_PHY_CDR_CTRL1); + writel_relaxed(0x00, base + SATA_PHY_CDR_CTRL2); + writel_relaxed(0x00, base + SATA_PHY_PI_CTRL0); + writel_relaxed(0x00, base + SATA_PHY_CDR_CTRL3); + writel_relaxed(0x01, base + SATA_PHY_POW_DWN_CTRL0); + + writel_relaxed(0x11, base + SATA_PHY_TX_DATA_CTRL); + writel_relaxed(0x43, base + SATA_PHY_ALIGNP); + writel_relaxed(0x04, base + SATA_PHY_OOB_TERM); + + writel_relaxed(0x01, base + SATA_PHY_EQUAL); + writel_relaxed(0x09, base + SATA_PHY_TX_DRIV_CTRL0); + writel_relaxed(0x09, base + SATA_PHY_TX_DRIV_CTRL1); + + return 0; +} + +static int qcom_apq8064_sata_phy_exit(struct phy *generic_phy) +{ + struct qcom_apq8064_sata_phy *phy = phy_get_drvdata(generic_phy); + void __iomem *base = phy->mmio; + + /* Power down PHY */ + writel_relaxed(0xF8, base + SATA_PHY_POW_DWN_CTRL0); + writel_relaxed(0xFE, base + SATA_PHY_POW_DWN_CTRL1); + + /* Power down PLL block */ + writel_relaxed(0x00, base + UNIPHY_PLL_GLB_CFG); + + return 0; +} + +static const struct phy_ops qcom_apq8064_sata_phy_ops = { + .init = qcom_apq8064_sata_phy_init, + .exit = qcom_apq8064_sata_phy_exit, + .owner = THIS_MODULE, +}; + +static int qcom_apq8064_sata_phy_probe(struct platform_device *pdev) +{ + struct qcom_apq8064_sata_phy *phy; + struct device *dev = &pdev->dev; + struct resource *res; + struct phy_provider *phy_provider; + struct phy *generic_phy; + int ret; + + phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); + if (!phy) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + phy->mmio = devm_ioremap_resource(dev, res); + if (IS_ERR(phy->mmio)) + return PTR_ERR(phy->mmio); + + generic_phy = devm_phy_create(dev, NULL, &qcom_apq8064_sata_phy_ops); + if (IS_ERR(generic_phy)) { + dev_err(dev, "%s: failed to create phy\n", __func__); + return PTR_ERR(generic_phy); + } + + phy->dev = dev; + phy_set_drvdata(generic_phy, phy); + platform_set_drvdata(pdev, phy); + + phy->cfg_clk = devm_clk_get(dev, "cfg"); + if (IS_ERR(phy->cfg_clk)) { + dev_err(dev, "Failed to get sata cfg clock\n"); + return PTR_ERR(phy->cfg_clk); + } + + ret = clk_prepare_enable(phy->cfg_clk); + if (ret) + return ret; + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(phy_provider)) { + clk_disable_unprepare(phy->cfg_clk); + dev_err(dev, "%s: failed to register phy\n", __func__); + return PTR_ERR(phy_provider); + } + + return 0; +} + +static int qcom_apq8064_sata_phy_remove(struct platform_device *pdev) +{ + struct qcom_apq8064_sata_phy *phy = platform_get_drvdata(pdev); + + clk_disable_unprepare(phy->cfg_clk); + + return 0; +} + +static const struct of_device_id qcom_apq8064_sata_phy_of_match[] = { + { .compatible = "qcom,apq8064-sata-phy" }, + { }, +}; +MODULE_DEVICE_TABLE(of, qcom_apq8064_sata_phy_of_match); + +static struct platform_driver qcom_apq8064_sata_phy_driver = { + .probe = qcom_apq8064_sata_phy_probe, + .remove = qcom_apq8064_sata_phy_remove, + .driver = { + .name = "qcom-apq8064-sata-phy", + .of_match_table = qcom_apq8064_sata_phy_of_match, + } +}; +module_platform_driver(qcom_apq8064_sata_phy_driver); + +MODULE_DESCRIPTION("QCOM apq8064 SATA PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/qualcomm/phy-qcom-ipq806x-sata.c b/drivers/phy/qualcomm/phy-qcom-ipq806x-sata.c new file mode 100644 index 000000000000..0ad127cc9298 --- /dev/null +++ b/drivers/phy/qualcomm/phy-qcom-ipq806x-sata.c @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2014, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct qcom_ipq806x_sata_phy { + void __iomem *mmio; + struct clk *cfg_clk; + struct device *dev; +}; + +#define __set(v, a, b) (((v) << (b)) & GENMASK(a, b)) + +#define SATA_PHY_P0_PARAM0 0x200 +#define SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN3(x) __set(x, 17, 12) +#define SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN3_MASK GENMASK(17, 12) +#define SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN2(x) __set(x, 11, 6) +#define SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN2_MASK GENMASK(11, 6) +#define SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN1(x) __set(x, 5, 0) +#define SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN1_MASK GENMASK(5, 0) + +#define SATA_PHY_P0_PARAM1 0x204 +#define SATA_PHY_P0_PARAM1_RESERVED_BITS31_21(x) __set(x, 31, 21) +#define SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN3(x) __set(x, 20, 14) +#define SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN3_MASK GENMASK(20, 14) +#define SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN2(x) __set(x, 13, 7) +#define SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN2_MASK GENMASK(13, 7) +#define SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN1(x) __set(x, 6, 0) +#define SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN1_MASK GENMASK(6, 0) + +#define SATA_PHY_P0_PARAM2 0x208 +#define SATA_PHY_P0_PARAM2_RX_EQ(x) __set(x, 20, 18) +#define SATA_PHY_P0_PARAM2_RX_EQ_MASK GENMASK(20, 18) + +#define SATA_PHY_P0_PARAM3 0x20C +#define SATA_PHY_SSC_EN 0x8 +#define SATA_PHY_P0_PARAM4 0x210 +#define SATA_PHY_REF_SSP_EN 0x2 +#define SATA_PHY_RESET 0x1 + +static int qcom_ipq806x_sata_phy_init(struct phy *generic_phy) +{ + struct qcom_ipq806x_sata_phy *phy = phy_get_drvdata(generic_phy); + u32 reg; + + /* Setting SSC_EN to 1 */ + reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM3); + reg = reg | SATA_PHY_SSC_EN; + writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM3); + + reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM0) & + ~(SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN3_MASK | + SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN2_MASK | + SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN1_MASK); + reg |= SATA_PHY_P0_PARAM0_P0_TX_PREEMPH_GEN3(0xf); + writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM0); + + reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM1) & + ~(SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN3_MASK | + SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN2_MASK | + SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN1_MASK); + reg |= SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN3(0x55) | + SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN2(0x55) | + SATA_PHY_P0_PARAM1_P0_TX_AMPLITUDE_GEN1(0x55); + writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM1); + + reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM2) & + ~SATA_PHY_P0_PARAM2_RX_EQ_MASK; + reg |= SATA_PHY_P0_PARAM2_RX_EQ(0x3); + writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM2); + + /* Setting PHY_RESET to 1 */ + reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM4); + reg = reg | SATA_PHY_RESET; + writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM4); + + /* Setting REF_SSP_EN to 1 */ + reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM4); + reg = reg | SATA_PHY_REF_SSP_EN | SATA_PHY_RESET; + writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM4); + + /* make sure all changes complete before we let the PHY out of reset */ + mb(); + + /* sleep for max. 50us more to combine processor wakeups */ + usleep_range(20, 20 + 50); + + /* Clearing PHY_RESET to 0 */ + reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM4); + reg = reg & ~SATA_PHY_RESET; + writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM4); + + return 0; +} + +static int qcom_ipq806x_sata_phy_exit(struct phy *generic_phy) +{ + struct qcom_ipq806x_sata_phy *phy = phy_get_drvdata(generic_phy); + u32 reg; + + /* Setting PHY_RESET to 1 */ + reg = readl_relaxed(phy->mmio + SATA_PHY_P0_PARAM4); + reg = reg | SATA_PHY_RESET; + writel_relaxed(reg, phy->mmio + SATA_PHY_P0_PARAM4); + + return 0; +} + +static const struct phy_ops qcom_ipq806x_sata_phy_ops = { + .init = qcom_ipq806x_sata_phy_init, + .exit = qcom_ipq806x_sata_phy_exit, + .owner = THIS_MODULE, +}; + +static int qcom_ipq806x_sata_phy_probe(struct platform_device *pdev) +{ + struct qcom_ipq806x_sata_phy *phy; + struct device *dev = &pdev->dev; + struct resource *res; + struct phy_provider *phy_provider; + struct phy *generic_phy; + int ret; + + phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); + if (!phy) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + phy->mmio = devm_ioremap_resource(dev, res); + if (IS_ERR(phy->mmio)) + return PTR_ERR(phy->mmio); + + generic_phy = devm_phy_create(dev, NULL, &qcom_ipq806x_sata_phy_ops); + if (IS_ERR(generic_phy)) { + dev_err(dev, "%s: failed to create phy\n", __func__); + return PTR_ERR(generic_phy); + } + + phy->dev = dev; + phy_set_drvdata(generic_phy, phy); + platform_set_drvdata(pdev, phy); + + phy->cfg_clk = devm_clk_get(dev, "cfg"); + if (IS_ERR(phy->cfg_clk)) { + dev_err(dev, "Failed to get sata cfg clock\n"); + return PTR_ERR(phy->cfg_clk); + } + + ret = clk_prepare_enable(phy->cfg_clk); + if (ret) + return ret; + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(phy_provider)) { + clk_disable_unprepare(phy->cfg_clk); + dev_err(dev, "%s: failed to register phy\n", __func__); + return PTR_ERR(phy_provider); + } + + return 0; +} + +static int qcom_ipq806x_sata_phy_remove(struct platform_device *pdev) +{ + struct qcom_ipq806x_sata_phy *phy = platform_get_drvdata(pdev); + + clk_disable_unprepare(phy->cfg_clk); + + return 0; +} + +static const struct of_device_id qcom_ipq806x_sata_phy_of_match[] = { + { .compatible = "qcom,ipq806x-sata-phy" }, + { }, +}; +MODULE_DEVICE_TABLE(of, qcom_ipq806x_sata_phy_of_match); + +static struct platform_driver qcom_ipq806x_sata_phy_driver = { + .probe = qcom_ipq806x_sata_phy_probe, + .remove = qcom_ipq806x_sata_phy_remove, + .driver = { + .name = "qcom-ipq806x-sata-phy", + .of_match_table = qcom_ipq806x_sata_phy_of_match, + } +}; +module_platform_driver(qcom_ipq806x_sata_phy_driver); + +MODULE_DESCRIPTION("QCOM IPQ806x SATA PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/qualcomm/phy-qcom-qmp.c b/drivers/phy/qualcomm/phy-qcom-qmp.c new file mode 100644 index 000000000000..78ca62897784 --- /dev/null +++ b/drivers/phy/qualcomm/phy-qcom-qmp.c @@ -0,0 +1,1153 @@ +/* + * Copyright (c) 2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* QMP PHY QSERDES COM registers */ +#define QSERDES_COM_BG_TIMER 0x00c +#define QSERDES_COM_SSC_EN_CENTER 0x010 +#define QSERDES_COM_SSC_ADJ_PER1 0x014 +#define QSERDES_COM_SSC_ADJ_PER2 0x018 +#define QSERDES_COM_SSC_PER1 0x01c +#define QSERDES_COM_SSC_PER2 0x020 +#define QSERDES_COM_SSC_STEP_SIZE1 0x024 +#define QSERDES_COM_SSC_STEP_SIZE2 0x028 +#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN 0x034 +#define QSERDES_COM_CLK_ENABLE1 0x038 +#define QSERDES_COM_SYS_CLK_CTRL 0x03c +#define QSERDES_COM_SYSCLK_BUF_ENABLE 0x040 +#define QSERDES_COM_PLL_IVCO 0x048 +#define QSERDES_COM_LOCK_CMP1_MODE0 0x04c +#define QSERDES_COM_LOCK_CMP2_MODE0 0x050 +#define QSERDES_COM_LOCK_CMP3_MODE0 0x054 +#define QSERDES_COM_LOCK_CMP1_MODE1 0x058 +#define QSERDES_COM_LOCK_CMP2_MODE1 0x05c +#define QSERDES_COM_LOCK_CMP3_MODE1 0x060 +#define QSERDES_COM_BG_TRIM 0x070 +#define QSERDES_COM_CLK_EP_DIV 0x074 +#define QSERDES_COM_CP_CTRL_MODE0 0x078 +#define QSERDES_COM_CP_CTRL_MODE1 0x07c +#define QSERDES_COM_PLL_RCTRL_MODE0 0x084 +#define QSERDES_COM_PLL_RCTRL_MODE1 0x088 +#define QSERDES_COM_PLL_CCTRL_MODE0 0x090 +#define QSERDES_COM_PLL_CCTRL_MODE1 0x094 +#define QSERDES_COM_SYSCLK_EN_SEL 0x0ac +#define QSERDES_COM_RESETSM_CNTRL 0x0b4 +#define QSERDES_COM_RESTRIM_CTRL 0x0bc +#define QSERDES_COM_RESCODE_DIV_NUM 0x0c4 +#define QSERDES_COM_LOCK_CMP_EN 0x0c8 +#define QSERDES_COM_LOCK_CMP_CFG 0x0cc +#define QSERDES_COM_DEC_START_MODE0 0x0d0 +#define QSERDES_COM_DEC_START_MODE1 0x0d4 +#define QSERDES_COM_DIV_FRAC_START1_MODE0 0x0dc +#define QSERDES_COM_DIV_FRAC_START2_MODE0 0x0e0 +#define QSERDES_COM_DIV_FRAC_START3_MODE0 0x0e4 +#define QSERDES_COM_DIV_FRAC_START1_MODE1 0x0e8 +#define QSERDES_COM_DIV_FRAC_START2_MODE1 0x0ec +#define QSERDES_COM_DIV_FRAC_START3_MODE1 0x0f0 +#define QSERDES_COM_INTEGLOOP_GAIN0_MODE0 0x108 +#define QSERDES_COM_INTEGLOOP_GAIN1_MODE0 0x10c +#define QSERDES_COM_INTEGLOOP_GAIN0_MODE1 0x110 +#define QSERDES_COM_INTEGLOOP_GAIN1_MODE1 0x114 +#define QSERDES_COM_VCO_TUNE_CTRL 0x124 +#define QSERDES_COM_VCO_TUNE_MAP 0x128 +#define QSERDES_COM_VCO_TUNE1_MODE0 0x12c +#define QSERDES_COM_VCO_TUNE2_MODE0 0x130 +#define QSERDES_COM_VCO_TUNE1_MODE1 0x134 +#define QSERDES_COM_VCO_TUNE2_MODE1 0x138 +#define QSERDES_COM_VCO_TUNE_TIMER1 0x144 +#define QSERDES_COM_VCO_TUNE_TIMER2 0x148 +#define QSERDES_COM_BG_CTRL 0x170 +#define QSERDES_COM_CLK_SELECT 0x174 +#define QSERDES_COM_HSCLK_SEL 0x178 +#define QSERDES_COM_CORECLK_DIV 0x184 +#define QSERDES_COM_CORE_CLK_EN 0x18c +#define QSERDES_COM_C_READY_STATUS 0x190 +#define QSERDES_COM_CMN_CONFIG 0x194 +#define QSERDES_COM_SVS_MODE_CLK_SEL 0x19c +#define QSERDES_COM_DEBUG_BUS0 0x1a0 +#define QSERDES_COM_DEBUG_BUS1 0x1a4 +#define QSERDES_COM_DEBUG_BUS2 0x1a8 +#define QSERDES_COM_DEBUG_BUS3 0x1ac +#define QSERDES_COM_DEBUG_BUS_SEL 0x1b0 +#define QSERDES_COM_CORECLK_DIV_MODE1 0x1bc + +/* QMP PHY TX registers */ +#define QSERDES_TX_RES_CODE_LANE_OFFSET 0x054 +#define QSERDES_TX_DEBUG_BUS_SEL 0x064 +#define QSERDES_TX_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN 0x068 +#define QSERDES_TX_LANE_MODE 0x094 +#define QSERDES_TX_RCV_DETECT_LVL_2 0x0ac + +/* QMP PHY RX registers */ +#define QSERDES_RX_UCDR_SO_GAIN_HALF 0x010 +#define QSERDES_RX_UCDR_SO_GAIN 0x01c +#define QSERDES_RX_UCDR_FASTLOCK_FO_GAIN 0x040 +#define QSERDES_RX_UCDR_SO_SATURATION_AND_ENABLE 0x048 +#define QSERDES_RX_RX_TERM_BW 0x090 +#define QSERDES_RX_RX_EQ_GAIN1_LSB 0x0c4 +#define QSERDES_RX_RX_EQ_GAIN1_MSB 0x0c8 +#define QSERDES_RX_RX_EQ_GAIN2_LSB 0x0cc +#define QSERDES_RX_RX_EQ_GAIN2_MSB 0x0d0 +#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2 0x0d8 +#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL3 0x0dc +#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL4 0x0e0 +#define QSERDES_RX_RX_EQ_OFFSET_ADAPTOR_CNTRL1 0x108 +#define QSERDES_RX_RX_OFFSET_ADAPTOR_CNTRL2 0x10c +#define QSERDES_RX_SIGDET_ENABLES 0x110 +#define QSERDES_RX_SIGDET_CNTRL 0x114 +#define QSERDES_RX_SIGDET_LVL 0x118 +#define QSERDES_RX_SIGDET_DEGLITCH_CNTRL 0x11c +#define QSERDES_RX_RX_BAND 0x120 +#define QSERDES_RX_RX_INTERFACE_MODE 0x12c + +/* QMP PHY PCS registers */ +#define QPHY_POWER_DOWN_CONTROL 0x04 +#define QPHY_TXDEEMPH_M6DB_V0 0x24 +#define QPHY_TXDEEMPH_M3P5DB_V0 0x28 +#define QPHY_ENDPOINT_REFCLK_DRIVE 0x54 +#define QPHY_RX_IDLE_DTCT_CNTRL 0x58 +#define QPHY_POWER_STATE_CONFIG1 0x60 +#define QPHY_POWER_STATE_CONFIG2 0x64 +#define QPHY_POWER_STATE_CONFIG4 0x6c +#define QPHY_LOCK_DETECT_CONFIG1 0x80 +#define QPHY_LOCK_DETECT_CONFIG2 0x84 +#define QPHY_LOCK_DETECT_CONFIG3 0x88 +#define QPHY_PWRUP_RESET_DLY_TIME_AUXCLK 0xa0 +#define QPHY_LP_WAKEUP_DLY_TIME_AUXCLK 0xa4 + +/* QPHY_SW_RESET bit */ +#define SW_RESET BIT(0) +/* QPHY_POWER_DOWN_CONTROL */ +#define SW_PWRDN BIT(0) +#define REFCLK_DRV_DSBL BIT(1) +/* QPHY_START_CONTROL bits */ +#define SERDES_START BIT(0) +#define PCS_START BIT(1) +#define PLL_READY_GATE_EN BIT(3) +/* QPHY_PCS_STATUS bit */ +#define PHYSTATUS BIT(6) +/* QPHY_COM_PCS_READY_STATUS bit */ +#define PCS_READY BIT(0) + +#define PHY_INIT_COMPLETE_TIMEOUT 1000 +#define POWER_DOWN_DELAY_US_MIN 10 +#define POWER_DOWN_DELAY_US_MAX 11 + +#define MAX_PROP_NAME 32 + +struct qmp_phy_init_tbl { + unsigned int offset; + unsigned int val; + /* + * register part of layout ? + * if yes, then offset gives index in the reg-layout + */ + int in_layout; +}; + +#define QMP_PHY_INIT_CFG(o, v) \ + { \ + .offset = o, \ + .val = v, \ + } + +#define QMP_PHY_INIT_CFG_L(o, v) \ + { \ + .offset = o, \ + .val = v, \ + .in_layout = 1, \ + } + +/* set of registers with offsets different per-PHY */ +enum qphy_reg_layout { + /* Common block control registers */ + QPHY_COM_SW_RESET, + QPHY_COM_POWER_DOWN_CONTROL, + QPHY_COM_START_CONTROL, + QPHY_COM_PCS_READY_STATUS, + /* PCS registers */ + QPHY_PLL_LOCK_CHK_DLY_TIME, + QPHY_FLL_CNTRL1, + QPHY_FLL_CNTRL2, + QPHY_FLL_CNT_VAL_L, + QPHY_FLL_CNT_VAL_H_TOL, + QPHY_FLL_MAN_CODE, + QPHY_SW_RESET, + QPHY_START_CTRL, + QPHY_PCS_READY_STATUS, +}; + +static const unsigned int pciephy_regs_layout[] = { + [QPHY_COM_SW_RESET] = 0x400, + [QPHY_COM_POWER_DOWN_CONTROL] = 0x404, + [QPHY_COM_START_CONTROL] = 0x408, + [QPHY_COM_PCS_READY_STATUS] = 0x448, + [QPHY_PLL_LOCK_CHK_DLY_TIME] = 0xa8, + [QPHY_FLL_CNTRL1] = 0xc4, + [QPHY_FLL_CNTRL2] = 0xc8, + [QPHY_FLL_CNT_VAL_L] = 0xcc, + [QPHY_FLL_CNT_VAL_H_TOL] = 0xd0, + [QPHY_FLL_MAN_CODE] = 0xd4, + [QPHY_SW_RESET] = 0x00, + [QPHY_START_CTRL] = 0x08, + [QPHY_PCS_READY_STATUS] = 0x174, +}; + +static const unsigned int usb3phy_regs_layout[] = { + [QPHY_FLL_CNTRL1] = 0xc0, + [QPHY_FLL_CNTRL2] = 0xc4, + [QPHY_FLL_CNT_VAL_L] = 0xc8, + [QPHY_FLL_CNT_VAL_H_TOL] = 0xcc, + [QPHY_FLL_MAN_CODE] = 0xd0, + [QPHY_SW_RESET] = 0x00, + [QPHY_START_CTRL] = 0x08, + [QPHY_PCS_READY_STATUS] = 0x17c, +}; + +static const struct qmp_phy_init_tbl msm8996_pcie_serdes_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x1c), + QMP_PHY_INIT_CFG(QSERDES_COM_CLK_ENABLE1, 0x10), + QMP_PHY_INIT_CFG(QSERDES_COM_CLK_SELECT, 0x33), + QMP_PHY_INIT_CFG(QSERDES_COM_CMN_CONFIG, 0x06), + QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP_EN, 0x42), + QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_MAP, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_TIMER1, 0xff), + QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_TIMER2, 0x1f), + QMP_PHY_INIT_CFG(QSERDES_COM_HSCLK_SEL, 0x01), + QMP_PHY_INIT_CFG(QSERDES_COM_SVS_MODE_CLK_SEL, 0x01), + QMP_PHY_INIT_CFG(QSERDES_COM_CORE_CLK_EN, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_CORECLK_DIV, 0x0a), + QMP_PHY_INIT_CFG(QSERDES_COM_BG_TIMER, 0x09), + QMP_PHY_INIT_CFG(QSERDES_COM_DEC_START_MODE0, 0x82), + QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START3_MODE0, 0x03), + QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START2_MODE0, 0x55), + QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START1_MODE0, 0x55), + QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP3_MODE0, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP2_MODE0, 0x1a), + QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP1_MODE0, 0x0a), + QMP_PHY_INIT_CFG(QSERDES_COM_CLK_SELECT, 0x33), + QMP_PHY_INIT_CFG(QSERDES_COM_SYS_CLK_CTRL, 0x02), + QMP_PHY_INIT_CFG(QSERDES_COM_SYSCLK_BUF_ENABLE, 0x1f), + QMP_PHY_INIT_CFG(QSERDES_COM_SYSCLK_EN_SEL, 0x04), + QMP_PHY_INIT_CFG(QSERDES_COM_CP_CTRL_MODE0, 0x0b), + QMP_PHY_INIT_CFG(QSERDES_COM_PLL_RCTRL_MODE0, 0x16), + QMP_PHY_INIT_CFG(QSERDES_COM_PLL_CCTRL_MODE0, 0x28), + QMP_PHY_INIT_CFG(QSERDES_COM_INTEGLOOP_GAIN1_MODE0, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_INTEGLOOP_GAIN0_MODE0, 0x80), + QMP_PHY_INIT_CFG(QSERDES_COM_SSC_EN_CENTER, 0x01), + QMP_PHY_INIT_CFG(QSERDES_COM_SSC_PER1, 0x31), + QMP_PHY_INIT_CFG(QSERDES_COM_SSC_PER2, 0x01), + QMP_PHY_INIT_CFG(QSERDES_COM_SSC_ADJ_PER1, 0x02), + QMP_PHY_INIT_CFG(QSERDES_COM_SSC_ADJ_PER2, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_SSC_STEP_SIZE1, 0x2f), + QMP_PHY_INIT_CFG(QSERDES_COM_SSC_STEP_SIZE2, 0x19), + QMP_PHY_INIT_CFG(QSERDES_COM_RESCODE_DIV_NUM, 0x15), + QMP_PHY_INIT_CFG(QSERDES_COM_BG_TRIM, 0x0f), + QMP_PHY_INIT_CFG(QSERDES_COM_PLL_IVCO, 0x0f), + QMP_PHY_INIT_CFG(QSERDES_COM_CLK_EP_DIV, 0x19), + QMP_PHY_INIT_CFG(QSERDES_COM_CLK_ENABLE1, 0x10), + QMP_PHY_INIT_CFG(QSERDES_COM_HSCLK_SEL, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_RESCODE_DIV_NUM, 0x40), +}; + +static const struct qmp_phy_init_tbl msm8996_pcie_tx_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_TX_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN, 0x45), + QMP_PHY_INIT_CFG(QSERDES_TX_LANE_MODE, 0x06), +}; + +static const struct qmp_phy_init_tbl msm8996_pcie_rx_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_ENABLES, 0x1c), + QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2, 0x01), + QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL3, 0x00), + QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL4, 0xdb), + QMP_PHY_INIT_CFG(QSERDES_RX_RX_BAND, 0x18), + QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_SO_GAIN, 0x04), + QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_SO_GAIN_HALF, 0x04), + QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_SO_SATURATION_AND_ENABLE, 0x4b), + QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_DEGLITCH_CNTRL, 0x14), + QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_LVL, 0x19), +}; + +static const struct qmp_phy_init_tbl msm8996_pcie_pcs_tbl[] = { + QMP_PHY_INIT_CFG(QPHY_RX_IDLE_DTCT_CNTRL, 0x4c), + QMP_PHY_INIT_CFG(QPHY_PWRUP_RESET_DLY_TIME_AUXCLK, 0x00), + QMP_PHY_INIT_CFG(QPHY_LP_WAKEUP_DLY_TIME_AUXCLK, 0x01), + + QMP_PHY_INIT_CFG_L(QPHY_PLL_LOCK_CHK_DLY_TIME, 0x05), + + QMP_PHY_INIT_CFG(QPHY_ENDPOINT_REFCLK_DRIVE, 0x05), + QMP_PHY_INIT_CFG(QPHY_POWER_DOWN_CONTROL, 0x02), + QMP_PHY_INIT_CFG(QPHY_POWER_STATE_CONFIG4, 0x00), + QMP_PHY_INIT_CFG(QPHY_POWER_STATE_CONFIG1, 0xa3), + QMP_PHY_INIT_CFG(QPHY_TXDEEMPH_M3P5DB_V0, 0x0e), +}; + +static const struct qmp_phy_init_tbl msm8996_usb3_serdes_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_COM_SYSCLK_EN_SEL, 0x14), + QMP_PHY_INIT_CFG(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x08), + QMP_PHY_INIT_CFG(QSERDES_COM_CLK_SELECT, 0x30), + QMP_PHY_INIT_CFG(QSERDES_COM_CMN_CONFIG, 0x06), + QMP_PHY_INIT_CFG(QSERDES_COM_SVS_MODE_CLK_SEL, 0x01), + QMP_PHY_INIT_CFG(QSERDES_COM_HSCLK_SEL, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_BG_TRIM, 0x0f), + QMP_PHY_INIT_CFG(QSERDES_COM_PLL_IVCO, 0x0f), + QMP_PHY_INIT_CFG(QSERDES_COM_SYS_CLK_CTRL, 0x04), + /* PLL and Loop filter settings */ + QMP_PHY_INIT_CFG(QSERDES_COM_DEC_START_MODE0, 0x82), + QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START1_MODE0, 0x55), + QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START2_MODE0, 0x55), + QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START3_MODE0, 0x03), + QMP_PHY_INIT_CFG(QSERDES_COM_CP_CTRL_MODE0, 0x0b), + QMP_PHY_INIT_CFG(QSERDES_COM_PLL_RCTRL_MODE0, 0x16), + QMP_PHY_INIT_CFG(QSERDES_COM_PLL_CCTRL_MODE0, 0x28), + QMP_PHY_INIT_CFG(QSERDES_COM_INTEGLOOP_GAIN0_MODE0, 0x80), + QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_CTRL, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP1_MODE0, 0x15), + QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP2_MODE0, 0x34), + QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP3_MODE0, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_CORE_CLK_EN, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP_CFG, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_MAP, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_BG_TIMER, 0x0a), + /* SSC settings */ + QMP_PHY_INIT_CFG(QSERDES_COM_SSC_EN_CENTER, 0x01), + QMP_PHY_INIT_CFG(QSERDES_COM_SSC_PER1, 0x31), + QMP_PHY_INIT_CFG(QSERDES_COM_SSC_PER2, 0x01), + QMP_PHY_INIT_CFG(QSERDES_COM_SSC_ADJ_PER1, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_SSC_ADJ_PER2, 0x00), + QMP_PHY_INIT_CFG(QSERDES_COM_SSC_STEP_SIZE1, 0xde), + QMP_PHY_INIT_CFG(QSERDES_COM_SSC_STEP_SIZE2, 0x07), +}; + +static const struct qmp_phy_init_tbl msm8996_usb3_tx_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_TX_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN, 0x45), + QMP_PHY_INIT_CFG(QSERDES_TX_RCV_DETECT_LVL_2, 0x12), + QMP_PHY_INIT_CFG(QSERDES_TX_LANE_MODE, 0x06), +}; + +static const struct qmp_phy_init_tbl msm8996_usb3_rx_tbl[] = { + QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_FASTLOCK_FO_GAIN, 0x0b), + QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_SO_GAIN, 0x04), + QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2, 0x02), + QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL3, 0x4c), + QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL4, 0xbb), + QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQ_OFFSET_ADAPTOR_CNTRL1, 0x77), + QMP_PHY_INIT_CFG(QSERDES_RX_RX_OFFSET_ADAPTOR_CNTRL2, 0x80), + QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_CNTRL, 0x03), + QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_LVL, 0x18), + QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_DEGLITCH_CNTRL, 0x16), +}; + +static const struct qmp_phy_init_tbl msm8996_usb3_pcs_tbl[] = { + /* FLL settings */ + QMP_PHY_INIT_CFG_L(QPHY_FLL_CNTRL2, 0x03), + QMP_PHY_INIT_CFG_L(QPHY_FLL_CNTRL1, 0x02), + QMP_PHY_INIT_CFG_L(QPHY_FLL_CNT_VAL_L, 0x09), + QMP_PHY_INIT_CFG_L(QPHY_FLL_CNT_VAL_H_TOL, 0x42), + QMP_PHY_INIT_CFG_L(QPHY_FLL_MAN_CODE, 0x85), + + /* Lock Det settings */ + QMP_PHY_INIT_CFG(QPHY_LOCK_DETECT_CONFIG1, 0xd1), + QMP_PHY_INIT_CFG(QPHY_LOCK_DETECT_CONFIG2, 0x1f), + QMP_PHY_INIT_CFG(QPHY_LOCK_DETECT_CONFIG3, 0x47), + QMP_PHY_INIT_CFG(QPHY_POWER_STATE_CONFIG2, 0x08), +}; + +/* struct qmp_phy_cfg - per-PHY initialization config */ +struct qmp_phy_cfg { + /* phy-type - PCIE/UFS/USB */ + unsigned int type; + /* number of lanes provided by phy */ + int nlanes; + + /* Init sequence for PHY blocks - serdes, tx, rx, pcs */ + const struct qmp_phy_init_tbl *serdes_tbl; + int serdes_tbl_num; + const struct qmp_phy_init_tbl *tx_tbl; + int tx_tbl_num; + const struct qmp_phy_init_tbl *rx_tbl; + int rx_tbl_num; + const struct qmp_phy_init_tbl *pcs_tbl; + int pcs_tbl_num; + + /* clock ids to be requested */ + const char * const *clk_list; + int num_clks; + /* resets to be requested */ + const char * const *reset_list; + int num_resets; + /* regulators to be requested */ + const char * const *vreg_list; + int num_vregs; + + /* array of registers with different offsets */ + const unsigned int *regs; + + unsigned int start_ctrl; + unsigned int pwrdn_ctrl; + unsigned int mask_pcs_ready; + unsigned int mask_com_pcs_ready; + + /* true, if PHY has a separate PHY_COM control block */ + bool has_phy_com_ctrl; + /* true, if PHY has a reset for individual lanes */ + bool has_lane_rst; + /* true, if PHY needs delay after POWER_DOWN */ + bool has_pwrdn_delay; + /* power_down delay in usec */ + int pwrdn_delay_min; + int pwrdn_delay_max; +}; + +/** + * struct qmp_phy - per-lane phy descriptor + * + * @phy: generic phy + * @tx: iomapped memory space for lane's tx + * @rx: iomapped memory space for lane's rx + * @pcs: iomapped memory space for lane's pcs + * @pipe_clk: pipe lock + * @index: lane index + * @qmp: QMP phy to which this lane belongs + * @lane_rst: lane's reset controller + */ +struct qmp_phy { + struct phy *phy; + void __iomem *tx; + void __iomem *rx; + void __iomem *pcs; + struct clk *pipe_clk; + unsigned int index; + struct qcom_qmp *qmp; + struct reset_control *lane_rst; +}; + +/** + * struct qcom_qmp - structure holding QMP phy block attributes + * + * @dev: device + * @serdes: iomapped memory space for phy's serdes + * + * @clks: array of clocks required by phy + * @resets: array of resets required by phy + * @vregs: regulator supplies bulk data + * + * @cfg: phy specific configuration + * @phys: array of per-lane phy descriptors + * @phy_mutex: mutex lock for PHY common block initialization + * @init_count: phy common block initialization count + */ +struct qcom_qmp { + struct device *dev; + void __iomem *serdes; + + struct clk **clks; + struct reset_control **resets; + struct regulator_bulk_data *vregs; + + const struct qmp_phy_cfg *cfg; + struct qmp_phy **phys; + + struct mutex phy_mutex; + int init_count; +}; + +static inline void qphy_setbits(void __iomem *base, u32 offset, u32 val) +{ + u32 reg; + + reg = readl(base + offset); + reg |= val; + writel(reg, base + offset); + + /* ensure that above write is through */ + readl(base + offset); +} + +static inline void qphy_clrbits(void __iomem *base, u32 offset, u32 val) +{ + u32 reg; + + reg = readl(base + offset); + reg &= ~val; + writel(reg, base + offset); + + /* ensure that above write is through */ + readl(base + offset); +} + +/* list of clocks required by phy */ +static const char * const msm8996_phy_clk_l[] = { + "aux", "cfg_ahb", "ref", +}; + +/* list of resets */ +static const char * const msm8996_pciephy_reset_l[] = { + "phy", "common", "cfg", +}; + +static const char * const msm8996_usb3phy_reset_l[] = { + "phy", "common", +}; + +/* list of regulators */ +static const char * const msm8996_phy_vreg_l[] = { + "vdda-phy", "vdda-pll", +}; + +static const struct qmp_phy_cfg msm8996_pciephy_cfg = { + .type = PHY_TYPE_PCIE, + .nlanes = 3, + + .serdes_tbl = msm8996_pcie_serdes_tbl, + .serdes_tbl_num = ARRAY_SIZE(msm8996_pcie_serdes_tbl), + .tx_tbl = msm8996_pcie_tx_tbl, + .tx_tbl_num = ARRAY_SIZE(msm8996_pcie_tx_tbl), + .rx_tbl = msm8996_pcie_rx_tbl, + .rx_tbl_num = ARRAY_SIZE(msm8996_pcie_rx_tbl), + .pcs_tbl = msm8996_pcie_pcs_tbl, + .pcs_tbl_num = ARRAY_SIZE(msm8996_pcie_pcs_tbl), + .clk_list = msm8996_phy_clk_l, + .num_clks = ARRAY_SIZE(msm8996_phy_clk_l), + .reset_list = msm8996_pciephy_reset_l, + .num_resets = ARRAY_SIZE(msm8996_pciephy_reset_l), + .vreg_list = msm8996_phy_vreg_l, + .num_vregs = ARRAY_SIZE(msm8996_phy_vreg_l), + .regs = pciephy_regs_layout, + + .start_ctrl = PCS_START | PLL_READY_GATE_EN, + .pwrdn_ctrl = SW_PWRDN | REFCLK_DRV_DSBL, + .mask_com_pcs_ready = PCS_READY, + + .has_phy_com_ctrl = true, + .has_lane_rst = true, + .has_pwrdn_delay = true, + .pwrdn_delay_min = POWER_DOWN_DELAY_US_MIN, + .pwrdn_delay_max = POWER_DOWN_DELAY_US_MAX, +}; + +static const struct qmp_phy_cfg msm8996_usb3phy_cfg = { + .type = PHY_TYPE_USB3, + .nlanes = 1, + + .serdes_tbl = msm8996_usb3_serdes_tbl, + .serdes_tbl_num = ARRAY_SIZE(msm8996_usb3_serdes_tbl), + .tx_tbl = msm8996_usb3_tx_tbl, + .tx_tbl_num = ARRAY_SIZE(msm8996_usb3_tx_tbl), + .rx_tbl = msm8996_usb3_rx_tbl, + .rx_tbl_num = ARRAY_SIZE(msm8996_usb3_rx_tbl), + .pcs_tbl = msm8996_usb3_pcs_tbl, + .pcs_tbl_num = ARRAY_SIZE(msm8996_usb3_pcs_tbl), + .clk_list = msm8996_phy_clk_l, + .num_clks = ARRAY_SIZE(msm8996_phy_clk_l), + .reset_list = msm8996_usb3phy_reset_l, + .num_resets = ARRAY_SIZE(msm8996_usb3phy_reset_l), + .vreg_list = msm8996_phy_vreg_l, + .num_vregs = ARRAY_SIZE(msm8996_phy_vreg_l), + .regs = usb3phy_regs_layout, + + .start_ctrl = SERDES_START | PCS_START, + .pwrdn_ctrl = SW_PWRDN, + .mask_pcs_ready = PHYSTATUS, +}; + +static void qcom_qmp_phy_configure(void __iomem *base, + const unsigned int *regs, + const struct qmp_phy_init_tbl tbl[], + int num) +{ + int i; + const struct qmp_phy_init_tbl *t = tbl; + + if (!t) + return; + + for (i = 0; i < num; i++, t++) { + if (t->in_layout) + writel(t->val, base + regs[t->offset]); + else + writel(t->val, base + t->offset); + } +} + +static int qcom_qmp_phy_poweron(struct phy *phy) +{ + struct qmp_phy *qphy = phy_get_drvdata(phy); + struct qcom_qmp *qmp = qphy->qmp; + int num = qmp->cfg->num_vregs; + int ret; + + dev_vdbg(&phy->dev, "Powering on QMP phy\n"); + + /* turn on regulator supplies */ + ret = regulator_bulk_enable(num, qmp->vregs); + if (ret) { + dev_err(qmp->dev, "failed to enable regulators, err=%d\n", ret); + return ret; + } + + ret = clk_prepare_enable(qphy->pipe_clk); + if (ret) { + dev_err(qmp->dev, "pipe_clk enable failed, err=%d\n", ret); + regulator_bulk_disable(num, qmp->vregs); + return ret; + } + + return 0; +} + +static int qcom_qmp_phy_poweroff(struct phy *phy) +{ + struct qmp_phy *qphy = phy_get_drvdata(phy); + struct qcom_qmp *qmp = qphy->qmp; + + clk_disable_unprepare(qphy->pipe_clk); + + regulator_bulk_disable(qmp->cfg->num_vregs, qmp->vregs); + + return 0; +} + +static int qcom_qmp_phy_com_init(struct qcom_qmp *qmp) +{ + const struct qmp_phy_cfg *cfg = qmp->cfg; + void __iomem *serdes = qmp->serdes; + int ret, i; + + mutex_lock(&qmp->phy_mutex); + if (qmp->init_count++) { + mutex_unlock(&qmp->phy_mutex); + return 0; + } + + for (i = 0; i < cfg->num_resets; i++) { + ret = reset_control_deassert(qmp->resets[i]); + if (ret) { + dev_err(qmp->dev, "%s reset deassert failed\n", + qmp->cfg->reset_list[i]); + while (--i >= 0) + reset_control_assert(qmp->resets[i]); + goto err_rst; + } + } + + if (cfg->has_phy_com_ctrl) + qphy_setbits(serdes, cfg->regs[QPHY_COM_POWER_DOWN_CONTROL], + SW_PWRDN); + + /* Serdes configuration */ + qcom_qmp_phy_configure(serdes, cfg->regs, cfg->serdes_tbl, + cfg->serdes_tbl_num); + + if (cfg->has_phy_com_ctrl) { + void __iomem *status; + unsigned int mask, val; + + qphy_clrbits(serdes, cfg->regs[QPHY_COM_SW_RESET], SW_RESET); + qphy_setbits(serdes, cfg->regs[QPHY_COM_START_CONTROL], + SERDES_START | PCS_START); + + status = serdes + cfg->regs[QPHY_COM_PCS_READY_STATUS]; + mask = cfg->mask_com_pcs_ready; + + ret = readl_poll_timeout(status, val, (val & mask), 10, + PHY_INIT_COMPLETE_TIMEOUT); + if (ret) { + dev_err(qmp->dev, + "phy common block init timed-out\n"); + goto err_com_init; + } + } + + mutex_unlock(&qmp->phy_mutex); + + return 0; + +err_com_init: + while (--i >= 0) + reset_control_assert(qmp->resets[i]); +err_rst: + mutex_unlock(&qmp->phy_mutex); + return ret; +} + +static int qcom_qmp_phy_com_exit(struct qcom_qmp *qmp) +{ + const struct qmp_phy_cfg *cfg = qmp->cfg; + void __iomem *serdes = qmp->serdes; + int i = cfg->num_resets; + + mutex_lock(&qmp->phy_mutex); + if (--qmp->init_count) { + mutex_unlock(&qmp->phy_mutex); + return 0; + } + + if (cfg->has_phy_com_ctrl) { + qphy_setbits(serdes, cfg->regs[QPHY_COM_START_CONTROL], + SERDES_START | PCS_START); + qphy_clrbits(serdes, cfg->regs[QPHY_COM_SW_RESET], + SW_RESET); + qphy_setbits(serdes, cfg->regs[QPHY_COM_POWER_DOWN_CONTROL], + SW_PWRDN); + } + + while (--i >= 0) + reset_control_assert(qmp->resets[i]); + + mutex_unlock(&qmp->phy_mutex); + + return 0; +} + +/* PHY Initialization */ +static int qcom_qmp_phy_init(struct phy *phy) +{ + struct qmp_phy *qphy = phy_get_drvdata(phy); + struct qcom_qmp *qmp = qphy->qmp; + const struct qmp_phy_cfg *cfg = qmp->cfg; + void __iomem *tx = qphy->tx; + void __iomem *rx = qphy->rx; + void __iomem *pcs = qphy->pcs; + void __iomem *status; + unsigned int mask, val; + int ret, i; + + dev_vdbg(qmp->dev, "Initializing QMP phy\n"); + + for (i = 0; i < qmp->cfg->num_clks; i++) { + ret = clk_prepare_enable(qmp->clks[i]); + if (ret) { + dev_err(qmp->dev, "failed to enable %s clk, err=%d\n", + qmp->cfg->clk_list[i], ret); + while (--i >= 0) + clk_disable_unprepare(qmp->clks[i]); + } + } + + ret = qcom_qmp_phy_com_init(qmp); + if (ret) + goto err_com_init; + + if (cfg->has_lane_rst) { + ret = reset_control_deassert(qphy->lane_rst); + if (ret) { + dev_err(qmp->dev, "lane%d reset deassert failed\n", + qphy->index); + goto err_lane_rst; + } + } + + /* Tx, Rx, and PCS configurations */ + qcom_qmp_phy_configure(tx, cfg->regs, cfg->tx_tbl, cfg->tx_tbl_num); + qcom_qmp_phy_configure(rx, cfg->regs, cfg->rx_tbl, cfg->rx_tbl_num); + qcom_qmp_phy_configure(pcs, cfg->regs, cfg->pcs_tbl, cfg->pcs_tbl_num); + + /* + * Pull out PHY from POWER DOWN state. + * This is active low enable signal to power-down PHY. + */ + qphy_setbits(pcs, QPHY_POWER_DOWN_CONTROL, cfg->pwrdn_ctrl); + + if (cfg->has_pwrdn_delay) + usleep_range(cfg->pwrdn_delay_min, cfg->pwrdn_delay_max); + + /* start SerDes and Phy-Coding-Sublayer */ + qphy_setbits(pcs, cfg->regs[QPHY_START_CTRL], cfg->start_ctrl); + + /* Pull PHY out of reset state */ + qphy_clrbits(pcs, cfg->regs[QPHY_SW_RESET], SW_RESET); + + status = pcs + cfg->regs[QPHY_PCS_READY_STATUS]; + mask = cfg->mask_pcs_ready; + + ret = readl_poll_timeout(status, val, !(val & mask), 1, + PHY_INIT_COMPLETE_TIMEOUT); + if (ret) { + dev_err(qmp->dev, "phy initialization timed-out\n"); + goto err_pcs_ready; + } + + return ret; + +err_pcs_ready: + if (cfg->has_lane_rst) + reset_control_assert(qphy->lane_rst); +err_lane_rst: + qcom_qmp_phy_com_exit(qmp); +err_com_init: + while (--i >= 0) + clk_disable_unprepare(qmp->clks[i]); + + return ret; +} + +static int qcom_qmp_phy_exit(struct phy *phy) +{ + struct qmp_phy *qphy = phy_get_drvdata(phy); + struct qcom_qmp *qmp = qphy->qmp; + const struct qmp_phy_cfg *cfg = qmp->cfg; + int i = cfg->num_clks; + + /* PHY reset */ + qphy_setbits(qphy->pcs, cfg->regs[QPHY_SW_RESET], SW_RESET); + + /* stop SerDes and Phy-Coding-Sublayer */ + qphy_clrbits(qphy->pcs, cfg->regs[QPHY_START_CTRL], cfg->start_ctrl); + + /* Put PHY into POWER DOWN state: active low */ + qphy_clrbits(qphy->pcs, QPHY_POWER_DOWN_CONTROL, cfg->pwrdn_ctrl); + + if (cfg->has_lane_rst) + reset_control_assert(qphy->lane_rst); + + qcom_qmp_phy_com_exit(qmp); + + while (--i >= 0) + clk_disable_unprepare(qmp->clks[i]); + + return 0; +} + +static int qcom_qmp_phy_vreg_init(struct device *dev) +{ + struct qcom_qmp *qmp = dev_get_drvdata(dev); + int num = qmp->cfg->num_vregs; + int i; + + qmp->vregs = devm_kcalloc(dev, num, sizeof(*qmp->vregs), GFP_KERNEL); + if (!qmp->vregs) + return -ENOMEM; + + for (i = 0; i < num; i++) + qmp->vregs[i].supply = qmp->cfg->vreg_list[i]; + + return devm_regulator_bulk_get(dev, num, qmp->vregs); +} + +static int qcom_qmp_phy_reset_init(struct device *dev) +{ + struct qcom_qmp *qmp = dev_get_drvdata(dev); + int i; + + qmp->resets = devm_kcalloc(dev, qmp->cfg->num_resets, + sizeof(*qmp->resets), GFP_KERNEL); + if (!qmp->resets) + return -ENOMEM; + + for (i = 0; i < qmp->cfg->num_resets; i++) { + struct reset_control *rst; + const char *name = qmp->cfg->reset_list[i]; + + rst = devm_reset_control_get(dev, name); + if (IS_ERR(rst)) { + dev_err(dev, "failed to get %s reset\n", name); + return PTR_ERR(rst); + } + qmp->resets[i] = rst; + } + + return 0; +} + +static int qcom_qmp_phy_clk_init(struct device *dev) +{ + struct qcom_qmp *qmp = dev_get_drvdata(dev); + int ret, i; + + qmp->clks = devm_kcalloc(dev, qmp->cfg->num_clks, + sizeof(*qmp->clks), GFP_KERNEL); + if (!qmp->clks) + return -ENOMEM; + + for (i = 0; i < qmp->cfg->num_clks; i++) { + struct clk *_clk; + const char *name = qmp->cfg->clk_list[i]; + + _clk = devm_clk_get(dev, name); + if (IS_ERR(_clk)) { + ret = PTR_ERR(_clk); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to get %s clk, %d\n", + name, ret); + return ret; + } + qmp->clks[i] = _clk; + } + + return 0; +} + +/* + * Register a fixed rate pipe clock. + * + * The _pipe_clksrc generated by PHY goes to the GCC that gate + * controls it. The _pipe_clk coming out of the GCC is requested + * by the PHY driver for its operations. + * We register the _pipe_clksrc here. The gcc driver takes care + * of assigning this _pipe_clksrc as parent to _pipe_clk. + * Below picture shows this relationship. + * + * +---------------+ + * | PHY block |<<---------------------------------------+ + * | | | + * | +-------+ | +-----+ | + * I/P---^-->| PLL |---^--->pipe_clksrc--->| GCC |--->pipe_clk---+ + * clk | +-------+ | +-----+ + * +---------------+ + */ +static int phy_pipe_clk_register(struct qcom_qmp *qmp, int id) +{ + char name[24]; + struct clk_fixed_rate *fixed; + struct clk_init_data init = { }; + + switch (qmp->cfg->type) { + case PHY_TYPE_USB3: + snprintf(name, sizeof(name), "usb3_phy_pipe_clk_src"); + break; + case PHY_TYPE_PCIE: + snprintf(name, sizeof(name), "pcie_%d_pipe_clk_src", id); + break; + default: + /* not all phys register pipe clocks, so return success */ + return 0; + } + + fixed = devm_kzalloc(qmp->dev, sizeof(*fixed), GFP_KERNEL); + if (!fixed) + return -ENOMEM; + + init.name = name; + init.ops = &clk_fixed_rate_ops; + + /* controllers using QMP phys use 125MHz pipe clock interface */ + fixed->fixed_rate = 125000000; + fixed->hw.init = &init; + + return devm_clk_hw_register(qmp->dev, &fixed->hw); +} + +static const struct phy_ops qcom_qmp_phy_gen_ops = { + .init = qcom_qmp_phy_init, + .exit = qcom_qmp_phy_exit, + .power_on = qcom_qmp_phy_poweron, + .power_off = qcom_qmp_phy_poweroff, + .owner = THIS_MODULE, +}; + +static +int qcom_qmp_phy_create(struct device *dev, struct device_node *np, int id) +{ + struct qcom_qmp *qmp = dev_get_drvdata(dev); + struct phy *generic_phy; + struct qmp_phy *qphy; + char prop_name[MAX_PROP_NAME]; + int ret; + + qphy = devm_kzalloc(dev, sizeof(*qphy), GFP_KERNEL); + if (!qphy) + return -ENOMEM; + + /* + * Get memory resources for each phy lane: + * Resources are indexed as: tx -> 0; rx -> 1; pcs -> 2. + */ + qphy->tx = of_iomap(np, 0); + if (!qphy->tx) + return -ENOMEM; + + qphy->rx = of_iomap(np, 1); + if (!qphy->rx) + return -ENOMEM; + + qphy->pcs = of_iomap(np, 2); + if (!qphy->pcs) + return -ENOMEM; + + /* + * Get PHY's Pipe clock, if any. USB3 and PCIe are PIPE3 + * based phys, so they essentially have pipe clock. So, + * we return error in case phy is USB3 or PIPE type. + * Otherwise, we initialize pipe clock to NULL for + * all phys that don't need this. + */ + snprintf(prop_name, sizeof(prop_name), "pipe%d", id); + qphy->pipe_clk = of_clk_get_by_name(np, prop_name); + if (IS_ERR(qphy->pipe_clk)) { + if (qmp->cfg->type == PHY_TYPE_PCIE || + qmp->cfg->type == PHY_TYPE_USB3) { + ret = PTR_ERR(qphy->pipe_clk); + if (ret != -EPROBE_DEFER) + dev_err(dev, + "failed to get lane%d pipe_clk, %d\n", + id, ret); + return ret; + } + qphy->pipe_clk = NULL; + } + + /* Get lane reset, if any */ + if (qmp->cfg->has_lane_rst) { + snprintf(prop_name, sizeof(prop_name), "lane%d", id); + qphy->lane_rst = of_reset_control_get(np, prop_name); + if (IS_ERR(qphy->lane_rst)) { + dev_err(dev, "failed to get lane%d reset\n", id); + return PTR_ERR(qphy->lane_rst); + } + } + + generic_phy = devm_phy_create(dev, np, &qcom_qmp_phy_gen_ops); + if (IS_ERR(generic_phy)) { + ret = PTR_ERR(generic_phy); + dev_err(dev, "failed to create qphy %d\n", ret); + return ret; + } + + qphy->phy = generic_phy; + qphy->index = id; + qphy->qmp = qmp; + qmp->phys[id] = qphy; + phy_set_drvdata(generic_phy, qphy); + + return 0; +} + +static const struct of_device_id qcom_qmp_phy_of_match_table[] = { + { + .compatible = "qcom,msm8996-qmp-pcie-phy", + .data = &msm8996_pciephy_cfg, + }, { + .compatible = "qcom,msm8996-qmp-usb3-phy", + .data = &msm8996_usb3phy_cfg, + }, + { }, +}; +MODULE_DEVICE_TABLE(of, qcom_qmp_phy_of_match_table); + +static int qcom_qmp_phy_probe(struct platform_device *pdev) +{ + struct qcom_qmp *qmp; + struct device *dev = &pdev->dev; + struct resource *res; + struct device_node *child; + struct phy_provider *phy_provider; + void __iomem *base; + int num, id; + int ret; + + qmp = devm_kzalloc(dev, sizeof(*qmp), GFP_KERNEL); + if (!qmp) + return -ENOMEM; + + qmp->dev = dev; + dev_set_drvdata(dev, qmp); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + /* per PHY serdes; usually located at base address */ + qmp->serdes = base; + + mutex_init(&qmp->phy_mutex); + + /* Get the specific init parameters of QMP phy */ + qmp->cfg = of_device_get_match_data(dev); + + ret = qcom_qmp_phy_clk_init(dev); + if (ret) + return ret; + + ret = qcom_qmp_phy_reset_init(dev); + if (ret) + return ret; + + ret = qcom_qmp_phy_vreg_init(dev); + if (ret) { + dev_err(dev, "failed to get regulator supplies\n"); + return ret; + } + + num = of_get_available_child_count(dev->of_node); + /* do we have a rogue child node ? */ + if (num > qmp->cfg->nlanes) + return -EINVAL; + + qmp->phys = devm_kcalloc(dev, num, sizeof(*qmp->phys), GFP_KERNEL); + if (!qmp->phys) + return -ENOMEM; + + id = 0; + for_each_available_child_of_node(dev->of_node, child) { + /* Create per-lane phy */ + ret = qcom_qmp_phy_create(dev, child, id); + if (ret) { + dev_err(dev, "failed to create lane%d phy, %d\n", + id, ret); + return ret; + } + + /* + * Register the pipe clock provided by phy. + * See function description to see details of this pipe clock. + */ + ret = phy_pipe_clk_register(qmp, id); + if (ret) { + dev_err(qmp->dev, + "failed to register pipe clock source\n"); + return ret; + } + id++; + } + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (!IS_ERR(phy_provider)) + dev_info(dev, "Registered Qcom-QMP phy\n"); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static struct platform_driver qcom_qmp_phy_driver = { + .probe = qcom_qmp_phy_probe, + .driver = { + .name = "qcom-qmp-phy", + .of_match_table = qcom_qmp_phy_of_match_table, + }, +}; + +module_platform_driver(qcom_qmp_phy_driver); + +MODULE_AUTHOR("Vivek Gautam "); +MODULE_DESCRIPTION("Qualcomm QMP PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/qualcomm/phy-qcom-qusb2.c b/drivers/phy/qualcomm/phy-qcom-qusb2.c new file mode 100644 index 000000000000..6c575244c0fb --- /dev/null +++ b/drivers/phy/qualcomm/phy-qcom-qusb2.c @@ -0,0 +1,493 @@ +/* + * Copyright (c) 2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define QUSB2PHY_PLL_TEST 0x04 +#define CLK_REF_SEL BIT(7) + +#define QUSB2PHY_PLL_TUNE 0x08 +#define QUSB2PHY_PLL_USER_CTL1 0x0c +#define QUSB2PHY_PLL_USER_CTL2 0x10 +#define QUSB2PHY_PLL_AUTOPGM_CTL1 0x1c +#define QUSB2PHY_PLL_PWR_CTRL 0x18 + +#define QUSB2PHY_PLL_STATUS 0x38 +#define PLL_LOCKED BIT(5) + +#define QUSB2PHY_PORT_TUNE1 0x80 +#define QUSB2PHY_PORT_TUNE2 0x84 +#define QUSB2PHY_PORT_TUNE3 0x88 +#define QUSB2PHY_PORT_TUNE4 0x8c +#define QUSB2PHY_PORT_TUNE5 0x90 +#define QUSB2PHY_PORT_TEST2 0x9c + +#define QUSB2PHY_PORT_POWERDOWN 0xb4 +#define CLAMP_N_EN BIT(5) +#define FREEZIO_N BIT(1) +#define POWER_DOWN BIT(0) + +#define QUSB2PHY_REFCLK_ENABLE BIT(0) + +#define PHY_CLK_SCHEME_SEL BIT(0) + +struct qusb2_phy_init_tbl { + unsigned int offset; + unsigned int val; +}; + +#define QUSB2_PHY_INIT_CFG(o, v) \ + { \ + .offset = o, \ + .val = v, \ + } + +static const struct qusb2_phy_init_tbl msm8996_init_tbl[] = { + QUSB2_PHY_INIT_CFG(QUSB2PHY_PORT_TUNE1, 0xf8), + QUSB2_PHY_INIT_CFG(QUSB2PHY_PORT_TUNE2, 0xb3), + QUSB2_PHY_INIT_CFG(QUSB2PHY_PORT_TUNE3, 0x83), + QUSB2_PHY_INIT_CFG(QUSB2PHY_PORT_TUNE4, 0xc0), + QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_TUNE, 0x30), + QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_USER_CTL1, 0x79), + QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_USER_CTL2, 0x21), + QUSB2_PHY_INIT_CFG(QUSB2PHY_PORT_TEST2, 0x14), + QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_AUTOPGM_CTL1, 0x9f), + QUSB2_PHY_INIT_CFG(QUSB2PHY_PLL_PWR_CTRL, 0x00), +}; + +struct qusb2_phy_cfg { + const struct qusb2_phy_init_tbl *tbl; + /* number of entries in the table */ + unsigned int tbl_num; + /* offset to PHY_CLK_SCHEME register in TCSR map */ + unsigned int clk_scheme_offset; +}; + +static const struct qusb2_phy_cfg msm8996_phy_cfg = { + .tbl = msm8996_init_tbl, + .tbl_num = ARRAY_SIZE(msm8996_init_tbl), +}; + +static const char * const qusb2_phy_vreg_names[] = { + "vdda-pll", "vdda-phy-dpdm", +}; + +#define QUSB2_NUM_VREGS ARRAY_SIZE(qusb2_phy_vreg_names) + +/** + * struct qusb2_phy - structure holding qusb2 phy attributes + * + * @phy: generic phy + * @base: iomapped memory space for qubs2 phy + * + * @cfg_ahb_clk: AHB2PHY interface clock + * @ref_clk: phy reference clock + * @iface_clk: phy interface clock + * @phy_reset: phy reset control + * @vregs: regulator supplies bulk data + * + * @tcsr: TCSR syscon register map + * @cell: nvmem cell containing phy tuning value + * + * @cfg: phy config data + * @has_se_clk_scheme: indicate if PHY has single-ended ref clock scheme + */ +struct qusb2_phy { + struct phy *phy; + void __iomem *base; + + struct clk *cfg_ahb_clk; + struct clk *ref_clk; + struct clk *iface_clk; + struct reset_control *phy_reset; + struct regulator_bulk_data vregs[QUSB2_NUM_VREGS]; + + struct regmap *tcsr; + struct nvmem_cell *cell; + + const struct qusb2_phy_cfg *cfg; + bool has_se_clk_scheme; +}; + +static inline void qusb2_setbits(void __iomem *base, u32 offset, u32 val) +{ + u32 reg; + + reg = readl(base + offset); + reg |= val; + writel(reg, base + offset); + + /* Ensure above write is completed */ + readl(base + offset); +} + +static inline void qusb2_clrbits(void __iomem *base, u32 offset, u32 val) +{ + u32 reg; + + reg = readl(base + offset); + reg &= ~val; + writel(reg, base + offset); + + /* Ensure above write is completed */ + readl(base + offset); +} + +static inline +void qcom_qusb2_phy_configure(void __iomem *base, + const struct qusb2_phy_init_tbl tbl[], int num) +{ + int i; + + for (i = 0; i < num; i++) + writel(tbl[i].val, base + tbl[i].offset); +} + +/* + * Fetches HS Tx tuning value from nvmem and sets the + * QUSB2PHY_PORT_TUNE2 register. + * For error case, skip setting the value and use the default value. + */ +static void qusb2_phy_set_tune2_param(struct qusb2_phy *qphy) +{ + struct device *dev = &qphy->phy->dev; + u8 *val; + + /* + * Read efuse register having TUNE2 parameter's high nibble. + * If efuse register shows value as 0x0, or if we fail to find + * a valid efuse register settings, then use default value + * as 0xB for high nibble that we have already set while + * configuring phy. + */ + val = nvmem_cell_read(qphy->cell, NULL); + if (IS_ERR(val) || !val[0]) { + dev_dbg(dev, "failed to read a valid hs-tx trim value\n"); + return; + } + + /* Fused TUNE2 value is the higher nibble only */ + qusb2_setbits(qphy->base, QUSB2PHY_PORT_TUNE2, val[0] << 0x4); +} + +static int qusb2_phy_poweron(struct phy *phy) +{ + struct qusb2_phy *qphy = phy_get_drvdata(phy); + int num = ARRAY_SIZE(qphy->vregs); + int ret; + + dev_vdbg(&phy->dev, "%s(): Powering-on QUSB2 phy\n", __func__); + + /* turn on regulator supplies */ + ret = regulator_bulk_enable(num, qphy->vregs); + if (ret) + return ret; + + ret = clk_prepare_enable(qphy->iface_clk); + if (ret) { + dev_err(&phy->dev, "failed to enable iface_clk, %d\n", ret); + regulator_bulk_disable(num, qphy->vregs); + return ret; + } + + return 0; +} + +static int qusb2_phy_poweroff(struct phy *phy) +{ + struct qusb2_phy *qphy = phy_get_drvdata(phy); + + clk_disable_unprepare(qphy->iface_clk); + + regulator_bulk_disable(ARRAY_SIZE(qphy->vregs), qphy->vregs); + + return 0; +} + +static int qusb2_phy_init(struct phy *phy) +{ + struct qusb2_phy *qphy = phy_get_drvdata(phy); + unsigned int val; + unsigned int clk_scheme; + int ret; + + dev_vdbg(&phy->dev, "%s(): Initializing QUSB2 phy\n", __func__); + + /* enable ahb interface clock to program phy */ + ret = clk_prepare_enable(qphy->cfg_ahb_clk); + if (ret) { + dev_err(&phy->dev, "failed to enable cfg ahb clock, %d\n", ret); + return ret; + } + + /* Perform phy reset */ + ret = reset_control_assert(qphy->phy_reset); + if (ret) { + dev_err(&phy->dev, "failed to assert phy_reset, %d\n", ret); + goto disable_ahb_clk; + } + + /* 100 us delay to keep PHY in reset mode */ + usleep_range(100, 150); + + ret = reset_control_deassert(qphy->phy_reset); + if (ret) { + dev_err(&phy->dev, "failed to de-assert phy_reset, %d\n", ret); + goto disable_ahb_clk; + } + + /* Disable the PHY */ + qusb2_setbits(qphy->base, QUSB2PHY_PORT_POWERDOWN, + CLAMP_N_EN | FREEZIO_N | POWER_DOWN); + + /* save reset value to override reference clock scheme later */ + val = readl(qphy->base + QUSB2PHY_PLL_TEST); + + qcom_qusb2_phy_configure(qphy->base, qphy->cfg->tbl, + qphy->cfg->tbl_num); + + /* Set efuse value for tuning the PHY */ + qusb2_phy_set_tune2_param(qphy); + + /* Enable the PHY */ + qusb2_clrbits(qphy->base, QUSB2PHY_PORT_POWERDOWN, POWER_DOWN); + + /* Required to get phy pll lock successfully */ + usleep_range(150, 160); + + /* Default is single-ended clock on msm8996 */ + qphy->has_se_clk_scheme = true; + /* + * read TCSR_PHY_CLK_SCHEME register to check if single-ended + * clock scheme is selected. If yes, then disable differential + * ref_clk and use single-ended clock, otherwise use differential + * ref_clk only. + */ + if (qphy->tcsr) { + ret = regmap_read(qphy->tcsr, qphy->cfg->clk_scheme_offset, + &clk_scheme); + if (ret) { + dev_err(&phy->dev, "failed to read clk scheme reg\n"); + goto assert_phy_reset; + } + + /* is it a differential clock scheme ? */ + if (!(clk_scheme & PHY_CLK_SCHEME_SEL)) { + dev_vdbg(&phy->dev, "%s(): select differential clk\n", + __func__); + qphy->has_se_clk_scheme = false; + } else { + dev_vdbg(&phy->dev, "%s(): select single-ended clk\n", + __func__); + } + } + + if (!qphy->has_se_clk_scheme) { + val &= ~CLK_REF_SEL; + ret = clk_prepare_enable(qphy->ref_clk); + if (ret) { + dev_err(&phy->dev, "failed to enable ref clk, %d\n", + ret); + goto assert_phy_reset; + } + } else { + val |= CLK_REF_SEL; + } + + writel(val, qphy->base + QUSB2PHY_PLL_TEST); + + /* ensure above write is through */ + readl(qphy->base + QUSB2PHY_PLL_TEST); + + /* Required to get phy pll lock successfully */ + usleep_range(100, 110); + + val = readb(qphy->base + QUSB2PHY_PLL_STATUS); + if (!(val & PLL_LOCKED)) { + dev_err(&phy->dev, + "QUSB2PHY pll lock failed: status reg = %x\n", val); + ret = -EBUSY; + goto disable_ref_clk; + } + + return 0; + +disable_ref_clk: + if (!qphy->has_se_clk_scheme) + clk_disable_unprepare(qphy->ref_clk); +assert_phy_reset: + reset_control_assert(qphy->phy_reset); +disable_ahb_clk: + clk_disable_unprepare(qphy->cfg_ahb_clk); + return ret; +} + +static int qusb2_phy_exit(struct phy *phy) +{ + struct qusb2_phy *qphy = phy_get_drvdata(phy); + + /* Disable the PHY */ + qusb2_setbits(qphy->base, QUSB2PHY_PORT_POWERDOWN, + CLAMP_N_EN | FREEZIO_N | POWER_DOWN); + + if (!qphy->has_se_clk_scheme) + clk_disable_unprepare(qphy->ref_clk); + + reset_control_assert(qphy->phy_reset); + + clk_disable_unprepare(qphy->cfg_ahb_clk); + + return 0; +} + +static const struct phy_ops qusb2_phy_gen_ops = { + .init = qusb2_phy_init, + .exit = qusb2_phy_exit, + .power_on = qusb2_phy_poweron, + .power_off = qusb2_phy_poweroff, + .owner = THIS_MODULE, +}; + +static const struct of_device_id qusb2_phy_of_match_table[] = { + { + .compatible = "qcom,msm8996-qusb2-phy", + .data = &msm8996_phy_cfg, + }, + { }, +}; +MODULE_DEVICE_TABLE(of, qusb2_phy_of_match_table); + +static int qusb2_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct qusb2_phy *qphy; + struct phy_provider *phy_provider; + struct phy *generic_phy; + struct resource *res; + int ret, i; + int num; + + qphy = devm_kzalloc(dev, sizeof(*qphy), GFP_KERNEL); + if (!qphy) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + qphy->base = devm_ioremap_resource(dev, res); + if (IS_ERR(qphy->base)) + return PTR_ERR(qphy->base); + + qphy->cfg_ahb_clk = devm_clk_get(dev, "cfg_ahb"); + if (IS_ERR(qphy->cfg_ahb_clk)) { + ret = PTR_ERR(qphy->cfg_ahb_clk); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to get cfg ahb clk, %d\n", ret); + return ret; + } + + qphy->ref_clk = devm_clk_get(dev, "ref"); + if (IS_ERR(qphy->ref_clk)) { + ret = PTR_ERR(qphy->ref_clk); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to get ref clk, %d\n", ret); + return ret; + } + + qphy->iface_clk = devm_clk_get(dev, "iface"); + if (IS_ERR(qphy->iface_clk)) { + ret = PTR_ERR(qphy->iface_clk); + if (ret == -EPROBE_DEFER) + return ret; + qphy->iface_clk = NULL; + dev_dbg(dev, "failed to get iface clk, %d\n", ret); + } + + qphy->phy_reset = devm_reset_control_get_by_index(&pdev->dev, 0); + if (IS_ERR(qphy->phy_reset)) { + dev_err(dev, "failed to get phy core reset\n"); + return PTR_ERR(qphy->phy_reset); + } + + num = ARRAY_SIZE(qphy->vregs); + for (i = 0; i < num; i++) + qphy->vregs[i].supply = qusb2_phy_vreg_names[i]; + + ret = devm_regulator_bulk_get(dev, num, qphy->vregs); + if (ret) { + dev_err(dev, "failed to get regulator supplies\n"); + return ret; + } + + /* Get the specific init parameters of QMP phy */ + qphy->cfg = of_device_get_match_data(dev); + + qphy->tcsr = syscon_regmap_lookup_by_phandle(dev->of_node, + "qcom,tcsr-syscon"); + if (IS_ERR(qphy->tcsr)) { + dev_dbg(dev, "failed to lookup TCSR regmap\n"); + qphy->tcsr = NULL; + } + + qphy->cell = devm_nvmem_cell_get(dev, NULL); + if (IS_ERR(qphy->cell)) { + if (PTR_ERR(qphy->cell) == -EPROBE_DEFER) + return -EPROBE_DEFER; + qphy->cell = NULL; + dev_dbg(dev, "failed to lookup tune2 hstx trim value\n"); + } + + generic_phy = devm_phy_create(dev, NULL, &qusb2_phy_gen_ops); + if (IS_ERR(generic_phy)) { + ret = PTR_ERR(generic_phy); + dev_err(dev, "failed to create phy, %d\n", ret); + return ret; + } + qphy->phy = generic_phy; + + dev_set_drvdata(dev, qphy); + phy_set_drvdata(generic_phy, qphy); + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (!IS_ERR(phy_provider)) + dev_info(dev, "Registered Qcom-QUSB2 phy\n"); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static struct platform_driver qusb2_phy_driver = { + .probe = qusb2_phy_probe, + .driver = { + .name = "qcom-qusb2-phy", + .of_match_table = qusb2_phy_of_match_table, + }, +}; + +module_platform_driver(qusb2_phy_driver); + +MODULE_AUTHOR("Vivek Gautam "); +MODULE_DESCRIPTION("Qualcomm QUSB2 PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/qualcomm/phy-qcom-ufs-i.h b/drivers/phy/qualcomm/phy-qcom-ufs-i.h new file mode 100644 index 000000000000..13b02b7de30b --- /dev/null +++ b/drivers/phy/qualcomm/phy-qcom-ufs-i.h @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2013-2015, Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef UFS_QCOM_PHY_I_H_ +#define UFS_QCOM_PHY_I_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define readl_poll_timeout(addr, val, cond, sleep_us, timeout_us) \ +({ \ + ktime_t timeout = ktime_add_us(ktime_get(), timeout_us); \ + might_sleep_if(timeout_us); \ + for (;;) { \ + (val) = readl(addr); \ + if (cond) \ + break; \ + if (timeout_us && ktime_compare(ktime_get(), timeout) > 0) { \ + (val) = readl(addr); \ + break; \ + } \ + if (sleep_us) \ + usleep_range(DIV_ROUND_UP(sleep_us, 4), sleep_us); \ + } \ + (cond) ? 0 : -ETIMEDOUT; \ +}) + +#define UFS_QCOM_PHY_CAL_ENTRY(reg, val) \ + { \ + .reg_offset = reg, \ + .cfg_value = val, \ + } + +#define UFS_QCOM_PHY_NAME_LEN 30 + +enum { + MASK_SERDES_START = 0x1, + MASK_PCS_READY = 0x1, +}; + +enum { + OFFSET_SERDES_START = 0x0, +}; + +struct ufs_qcom_phy_stored_attributes { + u32 att; + u32 value; +}; + + +struct ufs_qcom_phy_calibration { + u32 reg_offset; + u32 cfg_value; +}; + +struct ufs_qcom_phy_vreg { + const char *name; + struct regulator *reg; + int max_uA; + int min_uV; + int max_uV; + bool enabled; +}; + +struct ufs_qcom_phy { + struct list_head list; + struct device *dev; + void __iomem *mmio; + void __iomem *dev_ref_clk_ctrl_mmio; + struct clk *tx_iface_clk; + struct clk *rx_iface_clk; + bool is_iface_clk_enabled; + struct clk *ref_clk_src; + struct clk *ref_clk_parent; + struct clk *ref_clk; + bool is_ref_clk_enabled; + bool is_dev_ref_clk_enabled; + struct ufs_qcom_phy_vreg vdda_pll; + struct ufs_qcom_phy_vreg vdda_phy; + struct ufs_qcom_phy_vreg vddp_ref_clk; + unsigned int quirks; + + /* + * If UFS link is put into Hibern8 and if UFS PHY analog hardware is + * power collapsed (by clearing UFS_PHY_POWER_DOWN_CONTROL), Hibern8 + * exit might fail even after powering on UFS PHY analog hardware. + * Enabling this quirk will help to solve above issue by doing + * custom PHY settings just before PHY analog power collapse. + */ + #define UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE BIT(0) + + u8 host_ctrl_rev_major; + u16 host_ctrl_rev_minor; + u16 host_ctrl_rev_step; + + char name[UFS_QCOM_PHY_NAME_LEN]; + struct ufs_qcom_phy_calibration *cached_regs; + int cached_regs_table_size; + bool is_powered_on; + struct ufs_qcom_phy_specific_ops *phy_spec_ops; +}; + +/** + * struct ufs_qcom_phy_specific_ops - set of pointers to functions which have a + * specific implementation per phy. Each UFS phy, should implement + * those functions according to its spec and requirements + * @calibrate_phy: pointer to a function that calibrate the phy + * @start_serdes: pointer to a function that starts the serdes + * @is_physical_coding_sublayer_ready: pointer to a function that + * checks pcs readiness. returns 0 for success and non-zero for error. + * @set_tx_lane_enable: pointer to a function that enable tx lanes + * @power_control: pointer to a function that controls analog rail of phy + * and writes to QSERDES_RX_SIGDET_CNTRL attribute + */ +struct ufs_qcom_phy_specific_ops { + int (*calibrate_phy)(struct ufs_qcom_phy *phy, bool is_rate_B); + void (*start_serdes)(struct ufs_qcom_phy *phy); + int (*is_physical_coding_sublayer_ready)(struct ufs_qcom_phy *phy); + void (*set_tx_lane_enable)(struct ufs_qcom_phy *phy, u32 val); + void (*power_control)(struct ufs_qcom_phy *phy, bool val); +}; + +struct ufs_qcom_phy *get_ufs_qcom_phy(struct phy *generic_phy); +int ufs_qcom_phy_power_on(struct phy *generic_phy); +int ufs_qcom_phy_power_off(struct phy *generic_phy); +int ufs_qcom_phy_init_clks(struct ufs_qcom_phy *phy_common); +int ufs_qcom_phy_init_vregulators(struct ufs_qcom_phy *phy_common); +int ufs_qcom_phy_remove(struct phy *generic_phy, + struct ufs_qcom_phy *ufs_qcom_phy); +struct phy *ufs_qcom_phy_generic_probe(struct platform_device *pdev, + struct ufs_qcom_phy *common_cfg, + const struct phy_ops *ufs_qcom_phy_gen_ops, + struct ufs_qcom_phy_specific_ops *phy_spec_ops); +int ufs_qcom_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy, + struct ufs_qcom_phy_calibration *tbl_A, int tbl_size_A, + struct ufs_qcom_phy_calibration *tbl_B, int tbl_size_B, + bool is_rate_B); +#endif diff --git a/drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.c b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.c new file mode 100644 index 000000000000..12a1b498dc4b --- /dev/null +++ b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.c @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2013-2015, Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "phy-qcom-ufs-qmp-14nm.h" + +#define UFS_PHY_NAME "ufs_phy_qmp_14nm" +#define UFS_PHY_VDDA_PHY_UV (925000) + +static +int ufs_qcom_phy_qmp_14nm_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy, + bool is_rate_B) +{ + int tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A); + int tbl_size_B = ARRAY_SIZE(phy_cal_table_rate_B); + int err; + + err = ufs_qcom_phy_calibrate(ufs_qcom_phy, phy_cal_table_rate_A, + tbl_size_A, phy_cal_table_rate_B, tbl_size_B, is_rate_B); + + if (err) + dev_err(ufs_qcom_phy->dev, + "%s: ufs_qcom_phy_calibrate() failed %d\n", + __func__, err); + return err; +} + +static +void ufs_qcom_phy_qmp_14nm_advertise_quirks(struct ufs_qcom_phy *phy_common) +{ + phy_common->quirks = + UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE; +} + +static int ufs_qcom_phy_qmp_14nm_init(struct phy *generic_phy) +{ + return 0; +} + +static int ufs_qcom_phy_qmp_14nm_exit(struct phy *generic_phy) +{ + return 0; +} + +static +void ufs_qcom_phy_qmp_14nm_power_control(struct ufs_qcom_phy *phy, bool val) +{ + writel_relaxed(val ? 0x1 : 0x0, phy->mmio + UFS_PHY_POWER_DOWN_CONTROL); + /* + * Before any transactions involving PHY, ensure PHY knows + * that it's analog rail is powered ON (or OFF). + */ + mb(); +} + +static inline +void ufs_qcom_phy_qmp_14nm_set_tx_lane_enable(struct ufs_qcom_phy *phy, u32 val) +{ + /* + * 14nm PHY does not have TX_LANE_ENABLE register. + * Implement this function so as not to propagate error to caller. + */ +} + +static inline void ufs_qcom_phy_qmp_14nm_start_serdes(struct ufs_qcom_phy *phy) +{ + u32 tmp; + + tmp = readl_relaxed(phy->mmio + UFS_PHY_PHY_START); + tmp &= ~MASK_SERDES_START; + tmp |= (1 << OFFSET_SERDES_START); + writel_relaxed(tmp, phy->mmio + UFS_PHY_PHY_START); + /* Ensure register value is committed */ + mb(); +} + +static int ufs_qcom_phy_qmp_14nm_is_pcs_ready(struct ufs_qcom_phy *phy_common) +{ + int err = 0; + u32 val; + + err = readl_poll_timeout(phy_common->mmio + UFS_PHY_PCS_READY_STATUS, + val, (val & MASK_PCS_READY), 10, 1000000); + if (err) + dev_err(phy_common->dev, "%s: poll for pcs failed err = %d\n", + __func__, err); + return err; +} + +static const struct phy_ops ufs_qcom_phy_qmp_14nm_phy_ops = { + .init = ufs_qcom_phy_qmp_14nm_init, + .exit = ufs_qcom_phy_qmp_14nm_exit, + .power_on = ufs_qcom_phy_power_on, + .power_off = ufs_qcom_phy_power_off, + .owner = THIS_MODULE, +}; + +static struct ufs_qcom_phy_specific_ops phy_14nm_ops = { + .calibrate_phy = ufs_qcom_phy_qmp_14nm_phy_calibrate, + .start_serdes = ufs_qcom_phy_qmp_14nm_start_serdes, + .is_physical_coding_sublayer_ready = ufs_qcom_phy_qmp_14nm_is_pcs_ready, + .set_tx_lane_enable = ufs_qcom_phy_qmp_14nm_set_tx_lane_enable, + .power_control = ufs_qcom_phy_qmp_14nm_power_control, +}; + +static int ufs_qcom_phy_qmp_14nm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct phy *generic_phy; + struct ufs_qcom_phy_qmp_14nm *phy; + struct ufs_qcom_phy *phy_common; + int err = 0; + + phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); + if (!phy) { + err = -ENOMEM; + goto out; + } + phy_common = &phy->common_cfg; + + generic_phy = ufs_qcom_phy_generic_probe(pdev, phy_common, + &ufs_qcom_phy_qmp_14nm_phy_ops, &phy_14nm_ops); + + if (!generic_phy) { + err = -EIO; + goto out; + } + + err = ufs_qcom_phy_init_clks(phy_common); + if (err) + goto out; + + err = ufs_qcom_phy_init_vregulators(phy_common); + if (err) + goto out; + + phy_common->vdda_phy.max_uV = UFS_PHY_VDDA_PHY_UV; + phy_common->vdda_phy.min_uV = UFS_PHY_VDDA_PHY_UV; + + ufs_qcom_phy_qmp_14nm_advertise_quirks(phy_common); + + phy_set_drvdata(generic_phy, phy); + + strlcpy(phy_common->name, UFS_PHY_NAME, sizeof(phy_common->name)); + +out: + return err; +} + +static const struct of_device_id ufs_qcom_phy_qmp_14nm_of_match[] = { + {.compatible = "qcom,ufs-phy-qmp-14nm"}, + {.compatible = "qcom,msm8996-ufs-phy-qmp-14nm"}, + {}, +}; +MODULE_DEVICE_TABLE(of, ufs_qcom_phy_qmp_14nm_of_match); + +static struct platform_driver ufs_qcom_phy_qmp_14nm_driver = { + .probe = ufs_qcom_phy_qmp_14nm_probe, + .driver = { + .of_match_table = ufs_qcom_phy_qmp_14nm_of_match, + .name = "ufs_qcom_phy_qmp_14nm", + }, +}; + +module_platform_driver(ufs_qcom_phy_qmp_14nm_driver); + +MODULE_DESCRIPTION("Universal Flash Storage (UFS) QCOM PHY QMP 14nm"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.h b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.h new file mode 100644 index 000000000000..3aefdbacbcd0 --- /dev/null +++ b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-14nm.h @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2013-2015, Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef UFS_QCOM_PHY_QMP_14NM_H_ +#define UFS_QCOM_PHY_QMP_14NM_H_ + +#include "phy-qcom-ufs-i.h" + +/* QCOM UFS PHY control registers */ +#define COM_OFF(x) (0x000 + x) +#define PHY_OFF(x) (0xC00 + x) +#define TX_OFF(n, x) (0x400 + (0x400 * n) + x) +#define RX_OFF(n, x) (0x600 + (0x400 * n) + x) + +/* UFS PHY QSERDES COM registers */ +#define QSERDES_COM_BG_TIMER COM_OFF(0x0C) +#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN COM_OFF(0x34) +#define QSERDES_COM_SYS_CLK_CTRL COM_OFF(0x3C) +#define QSERDES_COM_LOCK_CMP1_MODE0 COM_OFF(0x4C) +#define QSERDES_COM_LOCK_CMP2_MODE0 COM_OFF(0x50) +#define QSERDES_COM_LOCK_CMP3_MODE0 COM_OFF(0x54) +#define QSERDES_COM_LOCK_CMP1_MODE1 COM_OFF(0x58) +#define QSERDES_COM_LOCK_CMP2_MODE1 COM_OFF(0x5C) +#define QSERDES_COM_LOCK_CMP3_MODE1 COM_OFF(0x60) +#define QSERDES_COM_CP_CTRL_MODE0 COM_OFF(0x78) +#define QSERDES_COM_CP_CTRL_MODE1 COM_OFF(0x7C) +#define QSERDES_COM_PLL_RCTRL_MODE0 COM_OFF(0x84) +#define QSERDES_COM_PLL_RCTRL_MODE1 COM_OFF(0x88) +#define QSERDES_COM_PLL_CCTRL_MODE0 COM_OFF(0x90) +#define QSERDES_COM_PLL_CCTRL_MODE1 COM_OFF(0x94) +#define QSERDES_COM_SYSCLK_EN_SEL COM_OFF(0xAC) +#define QSERDES_COM_RESETSM_CNTRL COM_OFF(0xB4) +#define QSERDES_COM_LOCK_CMP_EN COM_OFF(0xC8) +#define QSERDES_COM_LOCK_CMP_CFG COM_OFF(0xCC) +#define QSERDES_COM_DEC_START_MODE0 COM_OFF(0xD0) +#define QSERDES_COM_DEC_START_MODE1 COM_OFF(0xD4) +#define QSERDES_COM_DIV_FRAC_START1_MODE0 COM_OFF(0xDC) +#define QSERDES_COM_DIV_FRAC_START2_MODE0 COM_OFF(0xE0) +#define QSERDES_COM_DIV_FRAC_START3_MODE0 COM_OFF(0xE4) +#define QSERDES_COM_DIV_FRAC_START1_MODE1 COM_OFF(0xE8) +#define QSERDES_COM_DIV_FRAC_START2_MODE1 COM_OFF(0xEC) +#define QSERDES_COM_DIV_FRAC_START3_MODE1 COM_OFF(0xF0) +#define QSERDES_COM_INTEGLOOP_GAIN0_MODE0 COM_OFF(0x108) +#define QSERDES_COM_INTEGLOOP_GAIN1_MODE0 COM_OFF(0x10C) +#define QSERDES_COM_INTEGLOOP_GAIN0_MODE1 COM_OFF(0x110) +#define QSERDES_COM_INTEGLOOP_GAIN1_MODE1 COM_OFF(0x114) +#define QSERDES_COM_VCO_TUNE_CTRL COM_OFF(0x124) +#define QSERDES_COM_VCO_TUNE_MAP COM_OFF(0x128) +#define QSERDES_COM_VCO_TUNE1_MODE0 COM_OFF(0x12C) +#define QSERDES_COM_VCO_TUNE2_MODE0 COM_OFF(0x130) +#define QSERDES_COM_VCO_TUNE1_MODE1 COM_OFF(0x134) +#define QSERDES_COM_VCO_TUNE2_MODE1 COM_OFF(0x138) +#define QSERDES_COM_VCO_TUNE_TIMER1 COM_OFF(0x144) +#define QSERDES_COM_VCO_TUNE_TIMER2 COM_OFF(0x148) +#define QSERDES_COM_CLK_SELECT COM_OFF(0x174) +#define QSERDES_COM_HSCLK_SEL COM_OFF(0x178) +#define QSERDES_COM_CORECLK_DIV COM_OFF(0x184) +#define QSERDES_COM_CORE_CLK_EN COM_OFF(0x18C) +#define QSERDES_COM_CMN_CONFIG COM_OFF(0x194) +#define QSERDES_COM_SVS_MODE_CLK_SEL COM_OFF(0x19C) +#define QSERDES_COM_CORECLK_DIV_MODE1 COM_OFF(0x1BC) + +/* UFS PHY registers */ +#define UFS_PHY_PHY_START PHY_OFF(0x00) +#define UFS_PHY_POWER_DOWN_CONTROL PHY_OFF(0x04) +#define UFS_PHY_PCS_READY_STATUS PHY_OFF(0x168) + +/* UFS PHY TX registers */ +#define QSERDES_TX_HIGHZ_TRANSCEIVER_BIAS_DRVR_EN TX_OFF(0, 0x68) +#define QSERDES_TX_LANE_MODE TX_OFF(0, 0x94) + +/* UFS PHY RX registers */ +#define QSERDES_RX_UCDR_FASTLOCK_FO_GAIN RX_OFF(0, 0x40) +#define QSERDES_RX_RX_TERM_BW RX_OFF(0, 0x90) +#define QSERDES_RX_RX_EQ_GAIN1_LSB RX_OFF(0, 0xC4) +#define QSERDES_RX_RX_EQ_GAIN1_MSB RX_OFF(0, 0xC8) +#define QSERDES_RX_RX_EQ_GAIN2_LSB RX_OFF(0, 0xCC) +#define QSERDES_RX_RX_EQ_GAIN2_MSB RX_OFF(0, 0xD0) +#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2 RX_OFF(0, 0xD8) +#define QSERDES_RX_SIGDET_CNTRL RX_OFF(0, 0x114) +#define QSERDES_RX_SIGDET_LVL RX_OFF(0, 0x118) +#define QSERDES_RX_SIGDET_DEGLITCH_CNTRL RX_OFF(0, 0x11C) +#define QSERDES_RX_RX_INTERFACE_MODE RX_OFF(0, 0x12C) + +/* + * This structure represents the 14nm specific phy. + * common_cfg MUST remain the first field in this structure + * in case extra fields are added. This way, when calling + * get_ufs_qcom_phy() of generic phy, we can extract the + * common phy structure (struct ufs_qcom_phy) out of it + * regardless of the relevant specific phy. + */ +struct ufs_qcom_phy_qmp_14nm { + struct ufs_qcom_phy common_cfg; +}; + +static struct ufs_qcom_phy_calibration phy_cal_table_rate_A[] = { + UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_POWER_DOWN_CONTROL, 0x01), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CMN_CONFIG, 0x0e), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYSCLK_EN_SEL, 0xd7), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CLK_SELECT, 0x30), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYS_CLK_CTRL, 0x06), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x08), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BG_TIMER, 0x0a), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_HSCLK_SEL, 0x05), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CORECLK_DIV, 0x0a), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CORECLK_DIV_MODE1, 0x0a), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP_EN, 0x01), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_CTRL, 0x10), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL, 0x20), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CORE_CLK_EN, 0x00), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP_CFG, 0x00), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_TIMER1, 0xff), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_TIMER2, 0x3f), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_MAP, 0x14), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SVS_MODE_CLK_SEL, 0x05), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START_MODE0, 0x82), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1_MODE0, 0x00), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2_MODE0, 0x00), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3_MODE0, 0x00), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CP_CTRL_MODE0, 0x0b), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RCTRL_MODE0, 0x16), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CCTRL_MODE0, 0x28), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN0_MODE0, 0x80), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN1_MODE0, 0x00), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE1_MODE0, 0x28), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE2_MODE0, 0x02), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP1_MODE0, 0xff), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP2_MODE0, 0x0c), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP3_MODE0, 0x00), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START_MODE1, 0x98), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1_MODE1, 0x00), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2_MODE1, 0x00), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3_MODE1, 0x00), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_CP_CTRL_MODE1, 0x0b), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RCTRL_MODE1, 0x16), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CCTRL_MODE1, 0x28), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN0_MODE1, 0x80), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_INTEGLOOP_GAIN1_MODE1, 0x00), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE1_MODE1, 0xd6), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE2_MODE1, 0x00), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP1_MODE1, 0x32), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP2_MODE1, 0x0f), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_LOCK_CMP3_MODE1, 0x00), + + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_HIGHZ_TRANSCEIVER_BIAS_DRVR_EN, 0x45), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE, 0x02), + + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_SIGDET_LVL, 0x24), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_SIGDET_CNTRL, 0x02), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_INTERFACE_MODE, 0x00), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_SIGDET_DEGLITCH_CNTRL, 0x18), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_UCDR_FASTLOCK_FO_GAIN, 0x0B), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_TERM_BW, 0x5B), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB, 0xFF), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB, 0x3F), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB, 0xFF), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB, 0x0F), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2, 0x0E), +}; + +static struct ufs_qcom_phy_calibration phy_cal_table_rate_B[] = { + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_VCO_TUNE_MAP, 0x54), +}; + +#endif diff --git a/drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.c b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.c new file mode 100644 index 000000000000..4f68acb58b73 --- /dev/null +++ b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.c @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2013-2015, Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "phy-qcom-ufs-qmp-20nm.h" + +#define UFS_PHY_NAME "ufs_phy_qmp_20nm" + +static +int ufs_qcom_phy_qmp_20nm_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy, + bool is_rate_B) +{ + struct ufs_qcom_phy_calibration *tbl_A, *tbl_B; + int tbl_size_A, tbl_size_B; + u8 major = ufs_qcom_phy->host_ctrl_rev_major; + u16 minor = ufs_qcom_phy->host_ctrl_rev_minor; + u16 step = ufs_qcom_phy->host_ctrl_rev_step; + int err; + + if ((major == 0x1) && (minor == 0x002) && (step == 0x0000)) { + tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A_1_2_0); + tbl_A = phy_cal_table_rate_A_1_2_0; + } else if ((major == 0x1) && (minor == 0x003) && (step == 0x0000)) { + tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A_1_3_0); + tbl_A = phy_cal_table_rate_A_1_3_0; + } else { + dev_err(ufs_qcom_phy->dev, "%s: Unknown UFS-PHY version, no calibration values\n", + __func__); + err = -ENODEV; + goto out; + } + + tbl_size_B = ARRAY_SIZE(phy_cal_table_rate_B); + tbl_B = phy_cal_table_rate_B; + + err = ufs_qcom_phy_calibrate(ufs_qcom_phy, tbl_A, tbl_size_A, + tbl_B, tbl_size_B, is_rate_B); + + if (err) + dev_err(ufs_qcom_phy->dev, "%s: ufs_qcom_phy_calibrate() failed %d\n", + __func__, err); + +out: + return err; +} + +static +void ufs_qcom_phy_qmp_20nm_advertise_quirks(struct ufs_qcom_phy *phy_common) +{ + phy_common->quirks = + UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE; +} + +static int ufs_qcom_phy_qmp_20nm_init(struct phy *generic_phy) +{ + return 0; +} + +static int ufs_qcom_phy_qmp_20nm_exit(struct phy *generic_phy) +{ + return 0; +} + +static +void ufs_qcom_phy_qmp_20nm_power_control(struct ufs_qcom_phy *phy, bool val) +{ + bool hibern8_exit_after_pwr_collapse = phy->quirks & + UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE; + + if (val) { + writel_relaxed(0x1, phy->mmio + UFS_PHY_POWER_DOWN_CONTROL); + /* + * Before any transactions involving PHY, ensure PHY knows + * that it's analog rail is powered ON. + */ + mb(); + + if (hibern8_exit_after_pwr_collapse) { + /* + * Give atleast 1us delay after restoring PHY analog + * power. + */ + usleep_range(1, 2); + writel_relaxed(0x0A, phy->mmio + + QSERDES_COM_SYSCLK_EN_SEL_TXBAND); + writel_relaxed(0x08, phy->mmio + + QSERDES_COM_SYSCLK_EN_SEL_TXBAND); + /* + * Make sure workaround is deactivated before proceeding + * with normal PHY operations. + */ + mb(); + } + } else { + if (hibern8_exit_after_pwr_collapse) { + writel_relaxed(0x0A, phy->mmio + + QSERDES_COM_SYSCLK_EN_SEL_TXBAND); + writel_relaxed(0x02, phy->mmio + + QSERDES_COM_SYSCLK_EN_SEL_TXBAND); + /* + * Make sure that above workaround is activated before + * PHY analog power collapse. + */ + mb(); + } + + writel_relaxed(0x0, phy->mmio + UFS_PHY_POWER_DOWN_CONTROL); + /* + * ensure that PHY knows its PHY analog rail is going + * to be powered down + */ + mb(); + } +} + +static +void ufs_qcom_phy_qmp_20nm_set_tx_lane_enable(struct ufs_qcom_phy *phy, u32 val) +{ + writel_relaxed(val & UFS_PHY_TX_LANE_ENABLE_MASK, + phy->mmio + UFS_PHY_TX_LANE_ENABLE); + mb(); +} + +static inline void ufs_qcom_phy_qmp_20nm_start_serdes(struct ufs_qcom_phy *phy) +{ + u32 tmp; + + tmp = readl_relaxed(phy->mmio + UFS_PHY_PHY_START); + tmp &= ~MASK_SERDES_START; + tmp |= (1 << OFFSET_SERDES_START); + writel_relaxed(tmp, phy->mmio + UFS_PHY_PHY_START); + mb(); +} + +static int ufs_qcom_phy_qmp_20nm_is_pcs_ready(struct ufs_qcom_phy *phy_common) +{ + int err = 0; + u32 val; + + err = readl_poll_timeout(phy_common->mmio + UFS_PHY_PCS_READY_STATUS, + val, (val & MASK_PCS_READY), 10, 1000000); + if (err) + dev_err(phy_common->dev, "%s: poll for pcs failed err = %d\n", + __func__, err); + return err; +} + +static const struct phy_ops ufs_qcom_phy_qmp_20nm_phy_ops = { + .init = ufs_qcom_phy_qmp_20nm_init, + .exit = ufs_qcom_phy_qmp_20nm_exit, + .power_on = ufs_qcom_phy_power_on, + .power_off = ufs_qcom_phy_power_off, + .owner = THIS_MODULE, +}; + +static struct ufs_qcom_phy_specific_ops phy_20nm_ops = { + .calibrate_phy = ufs_qcom_phy_qmp_20nm_phy_calibrate, + .start_serdes = ufs_qcom_phy_qmp_20nm_start_serdes, + .is_physical_coding_sublayer_ready = ufs_qcom_phy_qmp_20nm_is_pcs_ready, + .set_tx_lane_enable = ufs_qcom_phy_qmp_20nm_set_tx_lane_enable, + .power_control = ufs_qcom_phy_qmp_20nm_power_control, +}; + +static int ufs_qcom_phy_qmp_20nm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct phy *generic_phy; + struct ufs_qcom_phy_qmp_20nm *phy; + struct ufs_qcom_phy *phy_common; + int err = 0; + + phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); + if (!phy) { + err = -ENOMEM; + goto out; + } + phy_common = &phy->common_cfg; + + generic_phy = ufs_qcom_phy_generic_probe(pdev, phy_common, + &ufs_qcom_phy_qmp_20nm_phy_ops, &phy_20nm_ops); + + if (!generic_phy) { + err = -EIO; + goto out; + } + + err = ufs_qcom_phy_init_clks(phy_common); + if (err) + goto out; + + err = ufs_qcom_phy_init_vregulators(phy_common); + if (err) + goto out; + + ufs_qcom_phy_qmp_20nm_advertise_quirks(phy_common); + + phy_set_drvdata(generic_phy, phy); + + strlcpy(phy_common->name, UFS_PHY_NAME, sizeof(phy_common->name)); + +out: + return err; +} + +static const struct of_device_id ufs_qcom_phy_qmp_20nm_of_match[] = { + {.compatible = "qcom,ufs-phy-qmp-20nm"}, + {}, +}; +MODULE_DEVICE_TABLE(of, ufs_qcom_phy_qmp_20nm_of_match); + +static struct platform_driver ufs_qcom_phy_qmp_20nm_driver = { + .probe = ufs_qcom_phy_qmp_20nm_probe, + .driver = { + .of_match_table = ufs_qcom_phy_qmp_20nm_of_match, + .name = "ufs_qcom_phy_qmp_20nm", + }, +}; + +module_platform_driver(ufs_qcom_phy_qmp_20nm_driver); + +MODULE_DESCRIPTION("Universal Flash Storage (UFS) QCOM PHY QMP 20nm"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.h b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.h new file mode 100644 index 000000000000..4f3076bb3d71 --- /dev/null +++ b/drivers/phy/qualcomm/phy-qcom-ufs-qmp-20nm.h @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2013-2015, Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef UFS_QCOM_PHY_QMP_20NM_H_ +#define UFS_QCOM_PHY_QMP_20NM_H_ + +#include "phy-qcom-ufs-i.h" + +/* QCOM UFS PHY control registers */ + +#define COM_OFF(x) (0x000 + x) +#define PHY_OFF(x) (0xC00 + x) +#define TX_OFF(n, x) (0x400 + (0x400 * n) + x) +#define RX_OFF(n, x) (0x600 + (0x400 * n) + x) + +/* UFS PHY PLL block registers */ +#define QSERDES_COM_SYS_CLK_CTRL COM_OFF(0x0) +#define QSERDES_COM_PLL_VCOTAIL_EN COM_OFF(0x04) +#define QSERDES_COM_PLL_CNTRL COM_OFF(0x14) +#define QSERDES_COM_PLL_IP_SETI COM_OFF(0x24) +#define QSERDES_COM_CORE_CLK_IN_SYNC_SEL COM_OFF(0x28) +#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN COM_OFF(0x30) +#define QSERDES_COM_PLL_CP_SETI COM_OFF(0x34) +#define QSERDES_COM_PLL_IP_SETP COM_OFF(0x38) +#define QSERDES_COM_PLL_CP_SETP COM_OFF(0x3C) +#define QSERDES_COM_SYSCLK_EN_SEL_TXBAND COM_OFF(0x48) +#define QSERDES_COM_RESETSM_CNTRL COM_OFF(0x4C) +#define QSERDES_COM_RESETSM_CNTRL2 COM_OFF(0x50) +#define QSERDES_COM_PLLLOCK_CMP1 COM_OFF(0x90) +#define QSERDES_COM_PLLLOCK_CMP2 COM_OFF(0x94) +#define QSERDES_COM_PLLLOCK_CMP3 COM_OFF(0x98) +#define QSERDES_COM_PLLLOCK_CMP_EN COM_OFF(0x9C) +#define QSERDES_COM_BGTC COM_OFF(0xA0) +#define QSERDES_COM_DEC_START1 COM_OFF(0xAC) +#define QSERDES_COM_PLL_AMP_OS COM_OFF(0xB0) +#define QSERDES_COM_RES_CODE_UP_OFFSET COM_OFF(0xD8) +#define QSERDES_COM_RES_CODE_DN_OFFSET COM_OFF(0xDC) +#define QSERDES_COM_DIV_FRAC_START1 COM_OFF(0x100) +#define QSERDES_COM_DIV_FRAC_START2 COM_OFF(0x104) +#define QSERDES_COM_DIV_FRAC_START3 COM_OFF(0x108) +#define QSERDES_COM_DEC_START2 COM_OFF(0x10C) +#define QSERDES_COM_PLL_RXTXEPCLK_EN COM_OFF(0x110) +#define QSERDES_COM_PLL_CRCTRL COM_OFF(0x114) +#define QSERDES_COM_PLL_CLKEPDIV COM_OFF(0x118) + +/* TX LANE n (0, 1) registers */ +#define QSERDES_TX_EMP_POST1_LVL(n) TX_OFF(n, 0x08) +#define QSERDES_TX_DRV_LVL(n) TX_OFF(n, 0x0C) +#define QSERDES_TX_LANE_MODE(n) TX_OFF(n, 0x54) + +/* RX LANE n (0, 1) registers */ +#define QSERDES_RX_CDR_CONTROL1(n) RX_OFF(n, 0x0) +#define QSERDES_RX_CDR_CONTROL_HALF(n) RX_OFF(n, 0x8) +#define QSERDES_RX_RX_EQ_GAIN1_LSB(n) RX_OFF(n, 0xA8) +#define QSERDES_RX_RX_EQ_GAIN1_MSB(n) RX_OFF(n, 0xAC) +#define QSERDES_RX_RX_EQ_GAIN2_LSB(n) RX_OFF(n, 0xB0) +#define QSERDES_RX_RX_EQ_GAIN2_MSB(n) RX_OFF(n, 0xB4) +#define QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(n) RX_OFF(n, 0xBC) +#define QSERDES_RX_CDR_CONTROL_QUARTER(n) RX_OFF(n, 0xC) +#define QSERDES_RX_SIGDET_CNTRL(n) RX_OFF(n, 0x100) + +/* UFS PHY registers */ +#define UFS_PHY_PHY_START PHY_OFF(0x00) +#define UFS_PHY_POWER_DOWN_CONTROL PHY_OFF(0x4) +#define UFS_PHY_TX_LANE_ENABLE PHY_OFF(0x44) +#define UFS_PHY_PWM_G1_CLK_DIVIDER PHY_OFF(0x08) +#define UFS_PHY_PWM_G2_CLK_DIVIDER PHY_OFF(0x0C) +#define UFS_PHY_PWM_G3_CLK_DIVIDER PHY_OFF(0x10) +#define UFS_PHY_PWM_G4_CLK_DIVIDER PHY_OFF(0x14) +#define UFS_PHY_CORECLK_PWM_G1_CLK_DIVIDER PHY_OFF(0x34) +#define UFS_PHY_CORECLK_PWM_G2_CLK_DIVIDER PHY_OFF(0x38) +#define UFS_PHY_CORECLK_PWM_G3_CLK_DIVIDER PHY_OFF(0x3C) +#define UFS_PHY_CORECLK_PWM_G4_CLK_DIVIDER PHY_OFF(0x40) +#define UFS_PHY_OMC_STATUS_RDVAL PHY_OFF(0x68) +#define UFS_PHY_LINE_RESET_TIME PHY_OFF(0x28) +#define UFS_PHY_LINE_RESET_GRANULARITY PHY_OFF(0x2C) +#define UFS_PHY_TSYNC_RSYNC_CNTL PHY_OFF(0x48) +#define UFS_PHY_PLL_CNTL PHY_OFF(0x50) +#define UFS_PHY_TX_LARGE_AMP_DRV_LVL PHY_OFF(0x54) +#define UFS_PHY_TX_SMALL_AMP_DRV_LVL PHY_OFF(0x5C) +#define UFS_PHY_TX_LARGE_AMP_POST_EMP_LVL PHY_OFF(0x58) +#define UFS_PHY_TX_SMALL_AMP_POST_EMP_LVL PHY_OFF(0x60) +#define UFS_PHY_CFG_CHANGE_CNT_VAL PHY_OFF(0x64) +#define UFS_PHY_RX_SYNC_WAIT_TIME PHY_OFF(0x6C) +#define UFS_PHY_TX_MIN_SLEEP_NOCONFIG_TIME_CAPABILITY PHY_OFF(0xB4) +#define UFS_PHY_RX_MIN_SLEEP_NOCONFIG_TIME_CAPABILITY PHY_OFF(0xE0) +#define UFS_PHY_TX_MIN_STALL_NOCONFIG_TIME_CAPABILITY PHY_OFF(0xB8) +#define UFS_PHY_RX_MIN_STALL_NOCONFIG_TIME_CAPABILITY PHY_OFF(0xE4) +#define UFS_PHY_TX_MIN_SAVE_CONFIG_TIME_CAPABILITY PHY_OFF(0xBC) +#define UFS_PHY_RX_MIN_SAVE_CONFIG_TIME_CAPABILITY PHY_OFF(0xE8) +#define UFS_PHY_RX_PWM_BURST_CLOSURE_LENGTH_CAPABILITY PHY_OFF(0xFC) +#define UFS_PHY_RX_MIN_ACTIVATETIME_CAPABILITY PHY_OFF(0x100) +#define UFS_PHY_RX_SIGDET_CTRL3 PHY_OFF(0x14c) +#define UFS_PHY_RMMI_ATTR_CTRL PHY_OFF(0x160) +#define UFS_PHY_RMMI_RX_CFGUPDT_L1 (1 << 7) +#define UFS_PHY_RMMI_TX_CFGUPDT_L1 (1 << 6) +#define UFS_PHY_RMMI_CFGWR_L1 (1 << 5) +#define UFS_PHY_RMMI_CFGRD_L1 (1 << 4) +#define UFS_PHY_RMMI_RX_CFGUPDT_L0 (1 << 3) +#define UFS_PHY_RMMI_TX_CFGUPDT_L0 (1 << 2) +#define UFS_PHY_RMMI_CFGWR_L0 (1 << 1) +#define UFS_PHY_RMMI_CFGRD_L0 (1 << 0) +#define UFS_PHY_RMMI_ATTRID PHY_OFF(0x164) +#define UFS_PHY_RMMI_ATTRWRVAL PHY_OFF(0x168) +#define UFS_PHY_RMMI_ATTRRDVAL_L0_STATUS PHY_OFF(0x16C) +#define UFS_PHY_RMMI_ATTRRDVAL_L1_STATUS PHY_OFF(0x170) +#define UFS_PHY_PCS_READY_STATUS PHY_OFF(0x174) + +#define UFS_PHY_TX_LANE_ENABLE_MASK 0x3 + +/* + * This structure represents the 20nm specific phy. + * common_cfg MUST remain the first field in this structure + * in case extra fields are added. This way, when calling + * get_ufs_qcom_phy() of generic phy, we can extract the + * common phy structure (struct ufs_qcom_phy) out of it + * regardless of the relevant specific phy. + */ +struct ufs_qcom_phy_qmp_20nm { + struct ufs_qcom_phy common_cfg; +}; + +static struct ufs_qcom_phy_calibration phy_cal_table_rate_A_1_2_0[] = { + UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_POWER_DOWN_CONTROL, 0x01), + UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_RX_SIGDET_CTRL3, 0x0D), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_VCOTAIL_EN, 0xe1), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CRCTRL, 0xcc), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYSCLK_EN_SEL_TXBAND, 0x08), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CLKEPDIV, 0x03), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RXTXEPCLK_EN, 0x10), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START1, 0x82), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START2, 0x03), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1, 0x80), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2, 0x80), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3, 0x40), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP1, 0xff), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP2, 0x19), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP3, 0x00), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP_EN, 0x03), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL, 0x90), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL2, 0x03), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(0), 0xf2), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(0), 0x0c), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(0), 0x12), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(1), 0xf2), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(1), 0x0c), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(1), 0x12), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(0), 0xff), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(0), 0xff), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(0), 0xff), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(0), 0x00), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(1), 0xff), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(1), 0xff), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(1), 0xff), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(1), 0x00), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETI, 0x3f), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETP, 0x1b), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETP, 0x0f), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETI, 0x01), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_EMP_POST1_LVL(0), 0x2F), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_DRV_LVL(0), 0x20), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_EMP_POST1_LVL(1), 0x2F), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_DRV_LVL(1), 0x20), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(0), 0x68), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(1), 0x68), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(1), 0xdc), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(0), 0xdc), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x3), +}; + +static struct ufs_qcom_phy_calibration phy_cal_table_rate_A_1_3_0[] = { + UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_POWER_DOWN_CONTROL, 0x01), + UFS_QCOM_PHY_CAL_ENTRY(UFS_PHY_RX_SIGDET_CTRL3, 0x0D), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_VCOTAIL_EN, 0xe1), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CRCTRL, 0xcc), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_SYSCLK_EN_SEL_TXBAND, 0x08), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CLKEPDIV, 0x03), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_RXTXEPCLK_EN, 0x10), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START1, 0x82), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START2, 0x03), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START1, 0x80), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START2, 0x80), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DIV_FRAC_START3, 0x40), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP1, 0xff), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP2, 0x19), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP3, 0x00), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP_EN, 0x03), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL, 0x90), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RESETSM_CNTRL2, 0x03), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(0), 0xf2), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(0), 0x0c), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(0), 0x12), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL1(1), 0xf2), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_HALF(1), 0x0c), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_CDR_CONTROL_QUARTER(1), 0x12), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(0), 0xff), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(0), 0xff), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(0), 0xff), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(0), 0x00), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_LSB(1), 0xff), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN1_MSB(1), 0xff), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_LSB(1), 0xff), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQ_GAIN2_MSB(1), 0x00), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETI, 0x2b), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETP, 0x38), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CP_SETP, 0x3c), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RES_CODE_UP_OFFSET, 0x02), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_RES_CODE_DN_OFFSET, 0x02), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_IP_SETI, 0x01), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLL_CNTRL, 0x40), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(0), 0x68), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_TX_LANE_MODE(1), 0x68), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(1), 0xdc), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2(0), 0xdc), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x3), +}; + +static struct ufs_qcom_phy_calibration phy_cal_table_rate_B[] = { + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_DEC_START1, 0x98), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP1, 0x65), + UFS_QCOM_PHY_CAL_ENTRY(QSERDES_COM_PLLLOCK_CMP2, 0x1e), +}; + +#endif diff --git a/drivers/phy/qualcomm/phy-qcom-ufs.c b/drivers/phy/qualcomm/phy-qcom-ufs.c new file mode 100644 index 000000000000..43865ef340e2 --- /dev/null +++ b/drivers/phy/qualcomm/phy-qcom-ufs.c @@ -0,0 +1,691 @@ +/* + * Copyright (c) 2013-2015, Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "phy-qcom-ufs-i.h" + +#define MAX_PROP_NAME 32 +#define VDDA_PHY_MIN_UV 1000000 +#define VDDA_PHY_MAX_UV 1000000 +#define VDDA_PLL_MIN_UV 1800000 +#define VDDA_PLL_MAX_UV 1800000 +#define VDDP_REF_CLK_MIN_UV 1200000 +#define VDDP_REF_CLK_MAX_UV 1200000 + +int ufs_qcom_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy, + struct ufs_qcom_phy_calibration *tbl_A, + int tbl_size_A, + struct ufs_qcom_phy_calibration *tbl_B, + int tbl_size_B, bool is_rate_B) +{ + int i; + int ret = 0; + + if (!tbl_A) { + dev_err(ufs_qcom_phy->dev, "%s: tbl_A is NULL", __func__); + ret = EINVAL; + goto out; + } + + for (i = 0; i < tbl_size_A; i++) + writel_relaxed(tbl_A[i].cfg_value, + ufs_qcom_phy->mmio + tbl_A[i].reg_offset); + + /* + * In case we would like to work in rate B, we need + * to override a registers that were configured in rate A table + * with registers of rate B table. + * table. + */ + if (is_rate_B) { + if (!tbl_B) { + dev_err(ufs_qcom_phy->dev, "%s: tbl_B is NULL", + __func__); + ret = EINVAL; + goto out; + } + + for (i = 0; i < tbl_size_B; i++) + writel_relaxed(tbl_B[i].cfg_value, + ufs_qcom_phy->mmio + tbl_B[i].reg_offset); + } + + /* flush buffered writes */ + mb(); + +out: + return ret; +} +EXPORT_SYMBOL_GPL(ufs_qcom_phy_calibrate); + +/* + * This assumes the embedded phy structure inside generic_phy is of type + * struct ufs_qcom_phy. In order to function properly it's crucial + * to keep the embedded struct "struct ufs_qcom_phy common_cfg" + * as the first inside generic_phy. + */ +struct ufs_qcom_phy *get_ufs_qcom_phy(struct phy *generic_phy) +{ + return (struct ufs_qcom_phy *)phy_get_drvdata(generic_phy); +} +EXPORT_SYMBOL_GPL(get_ufs_qcom_phy); + +static +int ufs_qcom_phy_base_init(struct platform_device *pdev, + struct ufs_qcom_phy *phy_common) +{ + struct device *dev = &pdev->dev; + struct resource *res; + int err = 0; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy_mem"); + phy_common->mmio = devm_ioremap_resource(dev, res); + if (IS_ERR((void const *)phy_common->mmio)) { + err = PTR_ERR((void const *)phy_common->mmio); + phy_common->mmio = NULL; + dev_err(dev, "%s: ioremap for phy_mem resource failed %d\n", + __func__, err); + return err; + } + + /* "dev_ref_clk_ctrl_mem" is optional resource */ + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "dev_ref_clk_ctrl_mem"); + phy_common->dev_ref_clk_ctrl_mmio = devm_ioremap_resource(dev, res); + if (IS_ERR((void const *)phy_common->dev_ref_clk_ctrl_mmio)) + phy_common->dev_ref_clk_ctrl_mmio = NULL; + + return 0; +} + +struct phy *ufs_qcom_phy_generic_probe(struct platform_device *pdev, + struct ufs_qcom_phy *common_cfg, + const struct phy_ops *ufs_qcom_phy_gen_ops, + struct ufs_qcom_phy_specific_ops *phy_spec_ops) +{ + int err; + struct device *dev = &pdev->dev; + struct phy *generic_phy = NULL; + struct phy_provider *phy_provider; + + err = ufs_qcom_phy_base_init(pdev, common_cfg); + if (err) { + dev_err(dev, "%s: phy base init failed %d\n", __func__, err); + goto out; + } + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(phy_provider)) { + err = PTR_ERR(phy_provider); + dev_err(dev, "%s: failed to register phy %d\n", __func__, err); + goto out; + } + + generic_phy = devm_phy_create(dev, NULL, ufs_qcom_phy_gen_ops); + if (IS_ERR(generic_phy)) { + err = PTR_ERR(generic_phy); + dev_err(dev, "%s: failed to create phy %d\n", __func__, err); + generic_phy = NULL; + goto out; + } + + common_cfg->phy_spec_ops = phy_spec_ops; + common_cfg->dev = dev; + +out: + return generic_phy; +} +EXPORT_SYMBOL_GPL(ufs_qcom_phy_generic_probe); + +static int __ufs_qcom_phy_clk_get(struct device *dev, + const char *name, struct clk **clk_out, bool err_print) +{ + struct clk *clk; + int err = 0; + + clk = devm_clk_get(dev, name); + if (IS_ERR(clk)) { + err = PTR_ERR(clk); + if (err_print) + dev_err(dev, "failed to get %s err %d", name, err); + } else { + *clk_out = clk; + } + + return err; +} + +static int ufs_qcom_phy_clk_get(struct device *dev, + const char *name, struct clk **clk_out) +{ + return __ufs_qcom_phy_clk_get(dev, name, clk_out, true); +} + +int ufs_qcom_phy_init_clks(struct ufs_qcom_phy *phy_common) +{ + int err; + + if (of_device_is_compatible(phy_common->dev->of_node, + "qcom,msm8996-ufs-phy-qmp-14nm")) + goto skip_txrx_clk; + + err = ufs_qcom_phy_clk_get(phy_common->dev, "tx_iface_clk", + &phy_common->tx_iface_clk); + if (err) + goto out; + + err = ufs_qcom_phy_clk_get(phy_common->dev, "rx_iface_clk", + &phy_common->rx_iface_clk); + if (err) + goto out; + +skip_txrx_clk: + err = ufs_qcom_phy_clk_get(phy_common->dev, "ref_clk_src", + &phy_common->ref_clk_src); + if (err) + goto out; + + /* + * "ref_clk_parent" is optional hence don't abort init if it's not + * found. + */ + __ufs_qcom_phy_clk_get(phy_common->dev, "ref_clk_parent", + &phy_common->ref_clk_parent, false); + + err = ufs_qcom_phy_clk_get(phy_common->dev, "ref_clk", + &phy_common->ref_clk); + +out: + return err; +} +EXPORT_SYMBOL_GPL(ufs_qcom_phy_init_clks); + +static int ufs_qcom_phy_init_vreg(struct device *dev, + struct ufs_qcom_phy_vreg *vreg, + const char *name) +{ + int err = 0; + + char prop_name[MAX_PROP_NAME]; + + vreg->name = name; + vreg->reg = devm_regulator_get(dev, name); + if (IS_ERR(vreg->reg)) { + err = PTR_ERR(vreg->reg); + dev_err(dev, "failed to get %s, %d\n", name, err); + goto out; + } + + if (dev->of_node) { + snprintf(prop_name, MAX_PROP_NAME, "%s-max-microamp", name); + err = of_property_read_u32(dev->of_node, + prop_name, &vreg->max_uA); + if (err && err != -EINVAL) { + dev_err(dev, "%s: failed to read %s\n", + __func__, prop_name); + goto out; + } else if (err == -EINVAL || !vreg->max_uA) { + if (regulator_count_voltages(vreg->reg) > 0) { + dev_err(dev, "%s: %s is mandatory\n", + __func__, prop_name); + goto out; + } + err = 0; + } + } + + if (!strcmp(name, "vdda-pll")) { + vreg->max_uV = VDDA_PLL_MAX_UV; + vreg->min_uV = VDDA_PLL_MIN_UV; + } else if (!strcmp(name, "vdda-phy")) { + vreg->max_uV = VDDA_PHY_MAX_UV; + vreg->min_uV = VDDA_PHY_MIN_UV; + } else if (!strcmp(name, "vddp-ref-clk")) { + vreg->max_uV = VDDP_REF_CLK_MAX_UV; + vreg->min_uV = VDDP_REF_CLK_MIN_UV; + } + +out: + return err; +} + +int ufs_qcom_phy_init_vregulators(struct ufs_qcom_phy *phy_common) +{ + int err; + + err = ufs_qcom_phy_init_vreg(phy_common->dev, &phy_common->vdda_pll, + "vdda-pll"); + if (err) + goto out; + + err = ufs_qcom_phy_init_vreg(phy_common->dev, &phy_common->vdda_phy, + "vdda-phy"); + + if (err) + goto out; + + err = ufs_qcom_phy_init_vreg(phy_common->dev, &phy_common->vddp_ref_clk, + "vddp-ref-clk"); + +out: + return err; +} +EXPORT_SYMBOL_GPL(ufs_qcom_phy_init_vregulators); + +static int ufs_qcom_phy_cfg_vreg(struct device *dev, + struct ufs_qcom_phy_vreg *vreg, bool on) +{ + int ret = 0; + struct regulator *reg = vreg->reg; + const char *name = vreg->name; + int min_uV; + int uA_load; + + if (regulator_count_voltages(reg) > 0) { + min_uV = on ? vreg->min_uV : 0; + ret = regulator_set_voltage(reg, min_uV, vreg->max_uV); + if (ret) { + dev_err(dev, "%s: %s set voltage failed, err=%d\n", + __func__, name, ret); + goto out; + } + uA_load = on ? vreg->max_uA : 0; + ret = regulator_set_load(reg, uA_load); + if (ret >= 0) { + /* + * regulator_set_load() returns new regulator + * mode upon success. + */ + ret = 0; + } else { + dev_err(dev, "%s: %s set optimum mode(uA_load=%d) failed, err=%d\n", + __func__, name, uA_load, ret); + goto out; + } + } +out: + return ret; +} + +static int ufs_qcom_phy_enable_vreg(struct device *dev, + struct ufs_qcom_phy_vreg *vreg) +{ + int ret = 0; + + if (!vreg || vreg->enabled) + goto out; + + ret = ufs_qcom_phy_cfg_vreg(dev, vreg, true); + if (ret) { + dev_err(dev, "%s: ufs_qcom_phy_cfg_vreg() failed, err=%d\n", + __func__, ret); + goto out; + } + + ret = regulator_enable(vreg->reg); + if (ret) { + dev_err(dev, "%s: enable failed, err=%d\n", + __func__, ret); + goto out; + } + + vreg->enabled = true; +out: + return ret; +} + +static int ufs_qcom_phy_enable_ref_clk(struct ufs_qcom_phy *phy) +{ + int ret = 0; + + if (phy->is_ref_clk_enabled) + goto out; + + /* + * reference clock is propagated in a daisy-chained manner from + * source to phy, so ungate them at each stage. + */ + ret = clk_prepare_enable(phy->ref_clk_src); + if (ret) { + dev_err(phy->dev, "%s: ref_clk_src enable failed %d\n", + __func__, ret); + goto out; + } + + /* + * "ref_clk_parent" is optional clock hence make sure that clk reference + * is available before trying to enable the clock. + */ + if (phy->ref_clk_parent) { + ret = clk_prepare_enable(phy->ref_clk_parent); + if (ret) { + dev_err(phy->dev, "%s: ref_clk_parent enable failed %d\n", + __func__, ret); + goto out_disable_src; + } + } + + ret = clk_prepare_enable(phy->ref_clk); + if (ret) { + dev_err(phy->dev, "%s: ref_clk enable failed %d\n", + __func__, ret); + goto out_disable_parent; + } + + phy->is_ref_clk_enabled = true; + goto out; + +out_disable_parent: + if (phy->ref_clk_parent) + clk_disable_unprepare(phy->ref_clk_parent); +out_disable_src: + clk_disable_unprepare(phy->ref_clk_src); +out: + return ret; +} + +static int ufs_qcom_phy_disable_vreg(struct device *dev, + struct ufs_qcom_phy_vreg *vreg) +{ + int ret = 0; + + if (!vreg || !vreg->enabled) + goto out; + + ret = regulator_disable(vreg->reg); + + if (!ret) { + /* ignore errors on applying disable config */ + ufs_qcom_phy_cfg_vreg(dev, vreg, false); + vreg->enabled = false; + } else { + dev_err(dev, "%s: %s disable failed, err=%d\n", + __func__, vreg->name, ret); + } +out: + return ret; +} + +static void ufs_qcom_phy_disable_ref_clk(struct ufs_qcom_phy *phy) +{ + if (phy->is_ref_clk_enabled) { + clk_disable_unprepare(phy->ref_clk); + /* + * "ref_clk_parent" is optional clock hence make sure that clk + * reference is available before trying to disable the clock. + */ + if (phy->ref_clk_parent) + clk_disable_unprepare(phy->ref_clk_parent); + clk_disable_unprepare(phy->ref_clk_src); + phy->is_ref_clk_enabled = false; + } +} + +#define UFS_REF_CLK_EN (1 << 5) + +static void ufs_qcom_phy_dev_ref_clk_ctrl(struct phy *generic_phy, bool enable) +{ + struct ufs_qcom_phy *phy = get_ufs_qcom_phy(generic_phy); + + if (phy->dev_ref_clk_ctrl_mmio && + (enable ^ phy->is_dev_ref_clk_enabled)) { + u32 temp = readl_relaxed(phy->dev_ref_clk_ctrl_mmio); + + if (enable) + temp |= UFS_REF_CLK_EN; + else + temp &= ~UFS_REF_CLK_EN; + + /* + * If we are here to disable this clock immediately after + * entering into hibern8, we need to make sure that device + * ref_clk is active atleast 1us after the hibern8 enter. + */ + if (!enable) + udelay(1); + + writel_relaxed(temp, phy->dev_ref_clk_ctrl_mmio); + /* ensure that ref_clk is enabled/disabled before we return */ + wmb(); + /* + * If we call hibern8 exit after this, we need to make sure that + * device ref_clk is stable for atleast 1us before the hibern8 + * exit command. + */ + if (enable) + udelay(1); + + phy->is_dev_ref_clk_enabled = enable; + } +} + +void ufs_qcom_phy_enable_dev_ref_clk(struct phy *generic_phy) +{ + ufs_qcom_phy_dev_ref_clk_ctrl(generic_phy, true); +} +EXPORT_SYMBOL_GPL(ufs_qcom_phy_enable_dev_ref_clk); + +void ufs_qcom_phy_disable_dev_ref_clk(struct phy *generic_phy) +{ + ufs_qcom_phy_dev_ref_clk_ctrl(generic_phy, false); +} +EXPORT_SYMBOL_GPL(ufs_qcom_phy_disable_dev_ref_clk); + +/* Turn ON M-PHY RMMI interface clocks */ +static int ufs_qcom_phy_enable_iface_clk(struct ufs_qcom_phy *phy) +{ + int ret = 0; + + if (phy->is_iface_clk_enabled) + goto out; + + ret = clk_prepare_enable(phy->tx_iface_clk); + if (ret) { + dev_err(phy->dev, "%s: tx_iface_clk enable failed %d\n", + __func__, ret); + goto out; + } + ret = clk_prepare_enable(phy->rx_iface_clk); + if (ret) { + clk_disable_unprepare(phy->tx_iface_clk); + dev_err(phy->dev, "%s: rx_iface_clk enable failed %d. disabling also tx_iface_clk\n", + __func__, ret); + goto out; + } + phy->is_iface_clk_enabled = true; + +out: + return ret; +} + +/* Turn OFF M-PHY RMMI interface clocks */ +void ufs_qcom_phy_disable_iface_clk(struct ufs_qcom_phy *phy) +{ + if (phy->is_iface_clk_enabled) { + clk_disable_unprepare(phy->tx_iface_clk); + clk_disable_unprepare(phy->rx_iface_clk); + phy->is_iface_clk_enabled = false; + } +} + +int ufs_qcom_phy_start_serdes(struct phy *generic_phy) +{ + struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy); + int ret = 0; + + if (!ufs_qcom_phy->phy_spec_ops->start_serdes) { + dev_err(ufs_qcom_phy->dev, "%s: start_serdes() callback is not supported\n", + __func__); + ret = -ENOTSUPP; + } else { + ufs_qcom_phy->phy_spec_ops->start_serdes(ufs_qcom_phy); + } + + return ret; +} +EXPORT_SYMBOL_GPL(ufs_qcom_phy_start_serdes); + +int ufs_qcom_phy_set_tx_lane_enable(struct phy *generic_phy, u32 tx_lanes) +{ + struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy); + int ret = 0; + + if (!ufs_qcom_phy->phy_spec_ops->set_tx_lane_enable) { + dev_err(ufs_qcom_phy->dev, "%s: set_tx_lane_enable() callback is not supported\n", + __func__); + ret = -ENOTSUPP; + } else { + ufs_qcom_phy->phy_spec_ops->set_tx_lane_enable(ufs_qcom_phy, + tx_lanes); + } + + return ret; +} +EXPORT_SYMBOL_GPL(ufs_qcom_phy_set_tx_lane_enable); + +void ufs_qcom_phy_save_controller_version(struct phy *generic_phy, + u8 major, u16 minor, u16 step) +{ + struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy); + + ufs_qcom_phy->host_ctrl_rev_major = major; + ufs_qcom_phy->host_ctrl_rev_minor = minor; + ufs_qcom_phy->host_ctrl_rev_step = step; +} +EXPORT_SYMBOL_GPL(ufs_qcom_phy_save_controller_version); + +int ufs_qcom_phy_calibrate_phy(struct phy *generic_phy, bool is_rate_B) +{ + struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy); + int ret = 0; + + if (!ufs_qcom_phy->phy_spec_ops->calibrate_phy) { + dev_err(ufs_qcom_phy->dev, "%s: calibrate_phy() callback is not supported\n", + __func__); + ret = -ENOTSUPP; + } else { + ret = ufs_qcom_phy->phy_spec_ops-> + calibrate_phy(ufs_qcom_phy, is_rate_B); + if (ret) + dev_err(ufs_qcom_phy->dev, "%s: calibrate_phy() failed %d\n", + __func__, ret); + } + + return ret; +} +EXPORT_SYMBOL_GPL(ufs_qcom_phy_calibrate_phy); + +int ufs_qcom_phy_is_pcs_ready(struct phy *generic_phy) +{ + struct ufs_qcom_phy *ufs_qcom_phy = get_ufs_qcom_phy(generic_phy); + + if (!ufs_qcom_phy->phy_spec_ops->is_physical_coding_sublayer_ready) { + dev_err(ufs_qcom_phy->dev, "%s: is_physical_coding_sublayer_ready() callback is not supported\n", + __func__); + return -ENOTSUPP; + } + + return ufs_qcom_phy->phy_spec_ops-> + is_physical_coding_sublayer_ready(ufs_qcom_phy); +} +EXPORT_SYMBOL_GPL(ufs_qcom_phy_is_pcs_ready); + +int ufs_qcom_phy_power_on(struct phy *generic_phy) +{ + struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy); + struct device *dev = phy_common->dev; + int err; + + if (phy_common->is_powered_on) + return 0; + + err = ufs_qcom_phy_enable_vreg(dev, &phy_common->vdda_phy); + if (err) { + dev_err(dev, "%s enable vdda_phy failed, err=%d\n", + __func__, err); + goto out; + } + + phy_common->phy_spec_ops->power_control(phy_common, true); + + /* vdda_pll also enables ref clock LDOs so enable it first */ + err = ufs_qcom_phy_enable_vreg(dev, &phy_common->vdda_pll); + if (err) { + dev_err(dev, "%s enable vdda_pll failed, err=%d\n", + __func__, err); + goto out_disable_phy; + } + + err = ufs_qcom_phy_enable_iface_clk(phy_common); + if (err) { + dev_err(dev, "%s enable phy iface clock failed, err=%d\n", + __func__, err); + goto out_disable_pll; + } + + err = ufs_qcom_phy_enable_ref_clk(phy_common); + if (err) { + dev_err(dev, "%s enable phy ref clock failed, err=%d\n", + __func__, err); + goto out_disable_iface_clk; + } + + /* enable device PHY ref_clk pad rail */ + if (phy_common->vddp_ref_clk.reg) { + err = ufs_qcom_phy_enable_vreg(dev, + &phy_common->vddp_ref_clk); + if (err) { + dev_err(dev, "%s enable vddp_ref_clk failed, err=%d\n", + __func__, err); + goto out_disable_ref_clk; + } + } + + phy_common->is_powered_on = true; + goto out; + +out_disable_ref_clk: + ufs_qcom_phy_disable_ref_clk(phy_common); +out_disable_iface_clk: + ufs_qcom_phy_disable_iface_clk(phy_common); +out_disable_pll: + ufs_qcom_phy_disable_vreg(dev, &phy_common->vdda_pll); +out_disable_phy: + ufs_qcom_phy_disable_vreg(dev, &phy_common->vdda_phy); +out: + return err; +} +EXPORT_SYMBOL_GPL(ufs_qcom_phy_power_on); + +int ufs_qcom_phy_power_off(struct phy *generic_phy) +{ + struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy); + + if (!phy_common->is_powered_on) + return 0; + + phy_common->phy_spec_ops->power_control(phy_common, false); + + if (phy_common->vddp_ref_clk.reg) + ufs_qcom_phy_disable_vreg(phy_common->dev, + &phy_common->vddp_ref_clk); + ufs_qcom_phy_disable_ref_clk(phy_common); + ufs_qcom_phy_disable_iface_clk(phy_common); + + ufs_qcom_phy_disable_vreg(phy_common->dev, &phy_common->vdda_pll); + ufs_qcom_phy_disable_vreg(phy_common->dev, &phy_common->vdda_phy); + phy_common->is_powered_on = false; + + return 0; +} +EXPORT_SYMBOL_GPL(ufs_qcom_phy_power_off); diff --git a/drivers/phy/qualcomm/phy-qcom-usb-hs.c b/drivers/phy/qualcomm/phy-qcom-usb-hs.c new file mode 100644 index 000000000000..4b20abc3ae2f --- /dev/null +++ b/drivers/phy/qualcomm/phy-qcom-usb-hs.c @@ -0,0 +1,295 @@ +/** + * Copyright (C) 2016 Linaro Ltd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ULPI_PWR_CLK_MNG_REG 0x88 +# define ULPI_PWR_OTG_COMP_DISABLE BIT(0) + +#define ULPI_MISC_A 0x96 +# define ULPI_MISC_A_VBUSVLDEXTSEL BIT(1) +# define ULPI_MISC_A_VBUSVLDEXT BIT(0) + + +struct ulpi_seq { + u8 addr; + u8 val; +}; + +struct qcom_usb_hs_phy { + struct ulpi *ulpi; + struct phy *phy; + struct clk *ref_clk; + struct clk *sleep_clk; + struct regulator *v1p8; + struct regulator *v3p3; + struct reset_control *reset; + struct ulpi_seq *init_seq; + struct extcon_dev *vbus_edev; + struct notifier_block vbus_notify; +}; + +static int qcom_usb_hs_phy_set_mode(struct phy *phy, enum phy_mode mode) +{ + struct qcom_usb_hs_phy *uphy = phy_get_drvdata(phy); + u8 addr; + int ret; + + if (!uphy->vbus_edev) { + u8 val = 0; + + switch (mode) { + case PHY_MODE_USB_OTG: + case PHY_MODE_USB_HOST: + val |= ULPI_INT_IDGRD; + case PHY_MODE_USB_DEVICE: + val |= ULPI_INT_SESS_VALID; + default: + break; + } + + ret = ulpi_write(uphy->ulpi, ULPI_USB_INT_EN_RISE, val); + if (ret) + return ret; + ret = ulpi_write(uphy->ulpi, ULPI_USB_INT_EN_FALL, val); + } else { + switch (mode) { + case PHY_MODE_USB_OTG: + case PHY_MODE_USB_DEVICE: + addr = ULPI_SET(ULPI_MISC_A); + break; + case PHY_MODE_USB_HOST: + addr = ULPI_CLR(ULPI_MISC_A); + break; + default: + return -EINVAL; + } + + ret = ulpi_write(uphy->ulpi, ULPI_SET(ULPI_PWR_CLK_MNG_REG), + ULPI_PWR_OTG_COMP_DISABLE); + if (ret) + return ret; + ret = ulpi_write(uphy->ulpi, addr, ULPI_MISC_A_VBUSVLDEXTSEL); + } + + return ret; +} + +static int +qcom_usb_hs_phy_vbus_notifier(struct notifier_block *nb, unsigned long event, + void *ptr) +{ + struct qcom_usb_hs_phy *uphy; + u8 addr; + + uphy = container_of(nb, struct qcom_usb_hs_phy, vbus_notify); + + if (event) + addr = ULPI_SET(ULPI_MISC_A); + else + addr = ULPI_CLR(ULPI_MISC_A); + + return ulpi_write(uphy->ulpi, addr, ULPI_MISC_A_VBUSVLDEXT); +} + +static int qcom_usb_hs_phy_power_on(struct phy *phy) +{ + struct qcom_usb_hs_phy *uphy = phy_get_drvdata(phy); + struct ulpi *ulpi = uphy->ulpi; + const struct ulpi_seq *seq; + int ret, state; + + ret = clk_prepare_enable(uphy->ref_clk); + if (ret) + return ret; + + ret = clk_prepare_enable(uphy->sleep_clk); + if (ret) + goto err_sleep; + + ret = regulator_set_load(uphy->v1p8, 50000); + if (ret < 0) + goto err_1p8; + + ret = regulator_enable(uphy->v1p8); + if (ret) + goto err_1p8; + + ret = regulator_set_voltage_triplet(uphy->v3p3, 3050000, 3300000, + 3300000); + if (ret) + goto err_3p3; + + ret = regulator_set_load(uphy->v3p3, 50000); + if (ret < 0) + goto err_3p3; + + ret = regulator_enable(uphy->v3p3); + if (ret) + goto err_3p3; + + for (seq = uphy->init_seq; seq->addr; seq++) { + ret = ulpi_write(ulpi, ULPI_EXT_VENDOR_SPECIFIC + seq->addr, + seq->val); + if (ret) + goto err_ulpi; + } + + if (uphy->reset) { + ret = reset_control_reset(uphy->reset); + if (ret) + goto err_ulpi; + } + + if (uphy->vbus_edev) { + state = extcon_get_cable_state_(uphy->vbus_edev, EXTCON_USB); + /* setup initial state */ + qcom_usb_hs_phy_vbus_notifier(&uphy->vbus_notify, state, + uphy->vbus_edev); + ret = extcon_register_notifier(uphy->vbus_edev, EXTCON_USB, + &uphy->vbus_notify); + if (ret) + goto err_ulpi; + } + + return 0; +err_ulpi: + regulator_disable(uphy->v3p3); +err_3p3: + regulator_disable(uphy->v1p8); +err_1p8: + clk_disable_unprepare(uphy->sleep_clk); +err_sleep: + clk_disable_unprepare(uphy->ref_clk); + return ret; +} + +static int qcom_usb_hs_phy_power_off(struct phy *phy) +{ + int ret; + struct qcom_usb_hs_phy *uphy = phy_get_drvdata(phy); + + if (uphy->vbus_edev) { + ret = extcon_unregister_notifier(uphy->vbus_edev, EXTCON_USB, + &uphy->vbus_notify); + if (ret) + return ret; + } + + regulator_disable(uphy->v3p3); + regulator_disable(uphy->v1p8); + clk_disable_unprepare(uphy->sleep_clk); + clk_disable_unprepare(uphy->ref_clk); + + return 0; +} + +static const struct phy_ops qcom_usb_hs_phy_ops = { + .power_on = qcom_usb_hs_phy_power_on, + .power_off = qcom_usb_hs_phy_power_off, + .set_mode = qcom_usb_hs_phy_set_mode, + .owner = THIS_MODULE, +}; + +static int qcom_usb_hs_phy_probe(struct ulpi *ulpi) +{ + struct qcom_usb_hs_phy *uphy; + struct phy_provider *p; + struct clk *clk; + struct regulator *reg; + struct reset_control *reset; + int size; + int ret; + + uphy = devm_kzalloc(&ulpi->dev, sizeof(*uphy), GFP_KERNEL); + if (!uphy) + return -ENOMEM; + ulpi_set_drvdata(ulpi, uphy); + uphy->ulpi = ulpi; + + size = of_property_count_u8_elems(ulpi->dev.of_node, "qcom,init-seq"); + if (size < 0) + size = 0; + uphy->init_seq = devm_kmalloc_array(&ulpi->dev, (size / 2) + 1, + sizeof(*uphy->init_seq), GFP_KERNEL); + if (!uphy->init_seq) + return -ENOMEM; + ret = of_property_read_u8_array(ulpi->dev.of_node, "qcom,init-seq", + (u8 *)uphy->init_seq, size); + if (ret && size) + return ret; + /* NUL terminate */ + uphy->init_seq[size / 2].addr = uphy->init_seq[size / 2].val = 0; + + uphy->ref_clk = clk = devm_clk_get(&ulpi->dev, "ref"); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + uphy->sleep_clk = clk = devm_clk_get(&ulpi->dev, "sleep"); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + uphy->v1p8 = reg = devm_regulator_get(&ulpi->dev, "v1p8"); + if (IS_ERR(reg)) + return PTR_ERR(reg); + + uphy->v3p3 = reg = devm_regulator_get(&ulpi->dev, "v3p3"); + if (IS_ERR(reg)) + return PTR_ERR(reg); + + uphy->reset = reset = devm_reset_control_get(&ulpi->dev, "por"); + if (IS_ERR(reset)) { + if (PTR_ERR(reset) == -EPROBE_DEFER) + return PTR_ERR(reset); + uphy->reset = NULL; + } + + uphy->phy = devm_phy_create(&ulpi->dev, ulpi->dev.of_node, + &qcom_usb_hs_phy_ops); + if (IS_ERR(uphy->phy)) + return PTR_ERR(uphy->phy); + + uphy->vbus_edev = extcon_get_edev_by_phandle(&ulpi->dev, 0); + if (IS_ERR(uphy->vbus_edev)) { + if (PTR_ERR(uphy->vbus_edev) != -ENODEV) + return PTR_ERR(uphy->vbus_edev); + uphy->vbus_edev = NULL; + } + + uphy->vbus_notify.notifier_call = qcom_usb_hs_phy_vbus_notifier; + phy_set_drvdata(uphy->phy, uphy); + + p = devm_of_phy_provider_register(&ulpi->dev, of_phy_simple_xlate); + return PTR_ERR_OR_ZERO(p); +} + +static const struct of_device_id qcom_usb_hs_phy_match[] = { + { .compatible = "qcom,usb-hs-phy", }, + { } +}; +MODULE_DEVICE_TABLE(of, qcom_usb_hs_phy_match); + +static struct ulpi_driver qcom_usb_hs_phy_driver = { + .probe = qcom_usb_hs_phy_probe, + .driver = { + .name = "qcom_usb_hs_phy", + .of_match_table = qcom_usb_hs_phy_match, + }, +}; +module_ulpi_driver(qcom_usb_hs_phy_driver); + +MODULE_DESCRIPTION("Qualcomm USB HS phy"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/qualcomm/phy-qcom-usb-hsic.c b/drivers/phy/qualcomm/phy-qcom-usb-hsic.c new file mode 100644 index 000000000000..c110563a73cb --- /dev/null +++ b/drivers/phy/qualcomm/phy-qcom-usb-hsic.c @@ -0,0 +1,159 @@ +/** + * Copyright (C) 2016 Linaro Ltd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#define ULPI_HSIC_CFG 0x30 +#define ULPI_HSIC_IO_CAL 0x33 + +struct qcom_usb_hsic_phy { + struct ulpi *ulpi; + struct phy *phy; + struct pinctrl *pctl; + struct clk *phy_clk; + struct clk *cal_clk; + struct clk *cal_sleep_clk; +}; + +static int qcom_usb_hsic_phy_power_on(struct phy *phy) +{ + struct qcom_usb_hsic_phy *uphy = phy_get_drvdata(phy); + struct ulpi *ulpi = uphy->ulpi; + struct pinctrl_state *pins_default; + int ret; + + ret = clk_prepare_enable(uphy->phy_clk); + if (ret) + return ret; + + ret = clk_prepare_enable(uphy->cal_clk); + if (ret) + goto err_cal; + + ret = clk_prepare_enable(uphy->cal_sleep_clk); + if (ret) + goto err_sleep; + + /* Set periodic calibration interval to ~2.048sec in HSIC_IO_CAL_REG */ + ret = ulpi_write(ulpi, ULPI_HSIC_IO_CAL, 0xff); + if (ret) + goto err_ulpi; + + /* Enable periodic IO calibration in HSIC_CFG register */ + ret = ulpi_write(ulpi, ULPI_HSIC_CFG, 0xa8); + if (ret) + goto err_ulpi; + + /* Configure pins for HSIC functionality */ + pins_default = pinctrl_lookup_state(uphy->pctl, PINCTRL_STATE_DEFAULT); + if (IS_ERR(pins_default)) + return PTR_ERR(pins_default); + + ret = pinctrl_select_state(uphy->pctl, pins_default); + if (ret) + goto err_ulpi; + + /* Enable HSIC mode in HSIC_CFG register */ + ret = ulpi_write(ulpi, ULPI_SET(ULPI_HSIC_CFG), 0x01); + if (ret) + goto err_ulpi; + + /* Disable auto-resume */ + ret = ulpi_write(ulpi, ULPI_CLR(ULPI_IFC_CTRL), + ULPI_IFC_CTRL_AUTORESUME); + if (ret) + goto err_ulpi; + + return ret; +err_ulpi: + clk_disable_unprepare(uphy->cal_sleep_clk); +err_sleep: + clk_disable_unprepare(uphy->cal_clk); +err_cal: + clk_disable_unprepare(uphy->phy_clk); + return ret; +} + +static int qcom_usb_hsic_phy_power_off(struct phy *phy) +{ + struct qcom_usb_hsic_phy *uphy = phy_get_drvdata(phy); + + clk_disable_unprepare(uphy->cal_sleep_clk); + clk_disable_unprepare(uphy->cal_clk); + clk_disable_unprepare(uphy->phy_clk); + + return 0; +} + +static const struct phy_ops qcom_usb_hsic_phy_ops = { + .power_on = qcom_usb_hsic_phy_power_on, + .power_off = qcom_usb_hsic_phy_power_off, + .owner = THIS_MODULE, +}; + +static int qcom_usb_hsic_phy_probe(struct ulpi *ulpi) +{ + struct qcom_usb_hsic_phy *uphy; + struct phy_provider *p; + struct clk *clk; + + uphy = devm_kzalloc(&ulpi->dev, sizeof(*uphy), GFP_KERNEL); + if (!uphy) + return -ENOMEM; + ulpi_set_drvdata(ulpi, uphy); + + uphy->ulpi = ulpi; + uphy->pctl = devm_pinctrl_get(&ulpi->dev); + if (IS_ERR(uphy->pctl)) + return PTR_ERR(uphy->pctl); + + uphy->phy_clk = clk = devm_clk_get(&ulpi->dev, "phy"); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + uphy->cal_clk = clk = devm_clk_get(&ulpi->dev, "cal"); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + uphy->cal_sleep_clk = clk = devm_clk_get(&ulpi->dev, "cal_sleep"); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + uphy->phy = devm_phy_create(&ulpi->dev, ulpi->dev.of_node, + &qcom_usb_hsic_phy_ops); + if (IS_ERR(uphy->phy)) + return PTR_ERR(uphy->phy); + phy_set_drvdata(uphy->phy, uphy); + + p = devm_of_phy_provider_register(&ulpi->dev, of_phy_simple_xlate); + return PTR_ERR_OR_ZERO(p); +} + +static const struct of_device_id qcom_usb_hsic_phy_match[] = { + { .compatible = "qcom,usb-hsic-phy", }, + { } +}; +MODULE_DEVICE_TABLE(of, qcom_usb_hsic_phy_match); + +static struct ulpi_driver qcom_usb_hsic_phy_driver = { + .probe = qcom_usb_hsic_phy_probe, + .driver = { + .name = "qcom_usb_hsic_phy", + .of_match_table = qcom_usb_hsic_phy_match, + }, +}; +module_ulpi_driver(qcom_usb_hsic_phy_driver); + +MODULE_DESCRIPTION("Qualcomm USB HSIC phy"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/renesas/Kconfig b/drivers/phy/renesas/Kconfig new file mode 100644 index 000000000000..432e2715e9c4 --- /dev/null +++ b/drivers/phy/renesas/Kconfig @@ -0,0 +1,17 @@ +# +# Phy drivers for Renesas platforms +# +config PHY_RCAR_GEN2 + tristate "Renesas R-Car generation 2 USB PHY driver" + depends on ARCH_RENESAS + depends on GENERIC_PHY + help + Support for USB PHY found on Renesas R-Car generation 2 SoCs. + +config PHY_RCAR_GEN3_USB2 + tristate "Renesas R-Car generation 3 USB 2.0 PHY driver" + depends on ARCH_RENESAS + depends on EXTCON + select GENERIC_PHY + help + Support for USB 2.0 PHY found on Renesas R-Car generation 3 SoCs. diff --git a/drivers/phy/renesas/Makefile b/drivers/phy/renesas/Makefile new file mode 100644 index 000000000000..695241aebf69 --- /dev/null +++ b/drivers/phy/renesas/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_PHY_RCAR_GEN2) += phy-rcar-gen2.o +obj-$(CONFIG_PHY_RCAR_GEN3_USB2) += phy-rcar-gen3-usb2.o diff --git a/drivers/phy/renesas/phy-rcar-gen2.c b/drivers/phy/renesas/phy-rcar-gen2.c new file mode 100644 index 000000000000..97d4dd6ea924 --- /dev/null +++ b/drivers/phy/renesas/phy-rcar-gen2.c @@ -0,0 +1,337 @@ +/* + * Renesas R-Car Gen2 PHY driver + * + * Copyright (C) 2014 Renesas Solutions Corp. + * Copyright (C) 2014 Cogent Embedded, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define USBHS_LPSTS 0x02 +#define USBHS_UGCTRL 0x80 +#define USBHS_UGCTRL2 0x84 +#define USBHS_UGSTS 0x88 /* From technical update */ + +/* Low Power Status register (LPSTS) */ +#define USBHS_LPSTS_SUSPM 0x4000 + +/* USB General control register (UGCTRL) */ +#define USBHS_UGCTRL_CONNECT 0x00000004 +#define USBHS_UGCTRL_PLLRESET 0x00000001 + +/* USB General control register 2 (UGCTRL2) */ +#define USBHS_UGCTRL2_USB2SEL 0x80000000 +#define USBHS_UGCTRL2_USB2SEL_PCI 0x00000000 +#define USBHS_UGCTRL2_USB2SEL_USB30 0x80000000 +#define USBHS_UGCTRL2_USB0SEL 0x00000030 +#define USBHS_UGCTRL2_USB0SEL_PCI 0x00000010 +#define USBHS_UGCTRL2_USB0SEL_HS_USB 0x00000030 + +/* USB General status register (UGSTS) */ +#define USBHS_UGSTS_LOCK 0x00000100 /* From technical update */ + +#define PHYS_PER_CHANNEL 2 + +struct rcar_gen2_phy { + struct phy *phy; + struct rcar_gen2_channel *channel; + int number; + u32 select_value; +}; + +struct rcar_gen2_channel { + struct device_node *of_node; + struct rcar_gen2_phy_driver *drv; + struct rcar_gen2_phy phys[PHYS_PER_CHANNEL]; + int selected_phy; + u32 select_mask; +}; + +struct rcar_gen2_phy_driver { + void __iomem *base; + struct clk *clk; + spinlock_t lock; + int num_channels; + struct rcar_gen2_channel *channels; +}; + +static int rcar_gen2_phy_init(struct phy *p) +{ + struct rcar_gen2_phy *phy = phy_get_drvdata(p); + struct rcar_gen2_channel *channel = phy->channel; + struct rcar_gen2_phy_driver *drv = channel->drv; + unsigned long flags; + u32 ugctrl2; + + /* + * Try to acquire exclusive access to PHY. The first driver calling + * phy_init() on a given channel wins, and all attempts to use another + * PHY on this channel will fail until phy_exit() is called by the first + * driver. Achieving this with cmpxcgh() should be SMP-safe. + */ + if (cmpxchg(&channel->selected_phy, -1, phy->number) != -1) + return -EBUSY; + + clk_prepare_enable(drv->clk); + + spin_lock_irqsave(&drv->lock, flags); + ugctrl2 = readl(drv->base + USBHS_UGCTRL2); + ugctrl2 &= ~channel->select_mask; + ugctrl2 |= phy->select_value; + writel(ugctrl2, drv->base + USBHS_UGCTRL2); + spin_unlock_irqrestore(&drv->lock, flags); + return 0; +} + +static int rcar_gen2_phy_exit(struct phy *p) +{ + struct rcar_gen2_phy *phy = phy_get_drvdata(p); + struct rcar_gen2_channel *channel = phy->channel; + + clk_disable_unprepare(channel->drv->clk); + + channel->selected_phy = -1; + + return 0; +} + +static int rcar_gen2_phy_power_on(struct phy *p) +{ + struct rcar_gen2_phy *phy = phy_get_drvdata(p); + struct rcar_gen2_phy_driver *drv = phy->channel->drv; + void __iomem *base = drv->base; + unsigned long flags; + u32 value; + int err = 0, i; + + /* Skip if it's not USBHS */ + if (phy->select_value != USBHS_UGCTRL2_USB0SEL_HS_USB) + return 0; + + spin_lock_irqsave(&drv->lock, flags); + + /* Power on USBHS PHY */ + value = readl(base + USBHS_UGCTRL); + value &= ~USBHS_UGCTRL_PLLRESET; + writel(value, base + USBHS_UGCTRL); + + value = readw(base + USBHS_LPSTS); + value |= USBHS_LPSTS_SUSPM; + writew(value, base + USBHS_LPSTS); + + for (i = 0; i < 20; i++) { + value = readl(base + USBHS_UGSTS); + if ((value & USBHS_UGSTS_LOCK) == USBHS_UGSTS_LOCK) { + value = readl(base + USBHS_UGCTRL); + value |= USBHS_UGCTRL_CONNECT; + writel(value, base + USBHS_UGCTRL); + goto out; + } + udelay(1); + } + + /* Timed out waiting for the PLL lock */ + err = -ETIMEDOUT; + +out: + spin_unlock_irqrestore(&drv->lock, flags); + + return err; +} + +static int rcar_gen2_phy_power_off(struct phy *p) +{ + struct rcar_gen2_phy *phy = phy_get_drvdata(p); + struct rcar_gen2_phy_driver *drv = phy->channel->drv; + void __iomem *base = drv->base; + unsigned long flags; + u32 value; + + /* Skip if it's not USBHS */ + if (phy->select_value != USBHS_UGCTRL2_USB0SEL_HS_USB) + return 0; + + spin_lock_irqsave(&drv->lock, flags); + + /* Power off USBHS PHY */ + value = readl(base + USBHS_UGCTRL); + value &= ~USBHS_UGCTRL_CONNECT; + writel(value, base + USBHS_UGCTRL); + + value = readw(base + USBHS_LPSTS); + value &= ~USBHS_LPSTS_SUSPM; + writew(value, base + USBHS_LPSTS); + + value = readl(base + USBHS_UGCTRL); + value |= USBHS_UGCTRL_PLLRESET; + writel(value, base + USBHS_UGCTRL); + + spin_unlock_irqrestore(&drv->lock, flags); + + return 0; +} + +static const struct phy_ops rcar_gen2_phy_ops = { + .init = rcar_gen2_phy_init, + .exit = rcar_gen2_phy_exit, + .power_on = rcar_gen2_phy_power_on, + .power_off = rcar_gen2_phy_power_off, + .owner = THIS_MODULE, +}; + +static const struct of_device_id rcar_gen2_phy_match_table[] = { + { .compatible = "renesas,usb-phy-r8a7790" }, + { .compatible = "renesas,usb-phy-r8a7791" }, + { .compatible = "renesas,usb-phy-r8a7794" }, + { .compatible = "renesas,rcar-gen2-usb-phy" }, + { } +}; +MODULE_DEVICE_TABLE(of, rcar_gen2_phy_match_table); + +static struct phy *rcar_gen2_phy_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct rcar_gen2_phy_driver *drv; + struct device_node *np = args->np; + int i; + + drv = dev_get_drvdata(dev); + if (!drv) + return ERR_PTR(-EINVAL); + + for (i = 0; i < drv->num_channels; i++) { + if (np == drv->channels[i].of_node) + break; + } + + if (i >= drv->num_channels || args->args[0] >= 2) + return ERR_PTR(-ENODEV); + + return drv->channels[i].phys[args->args[0]].phy; +} + +static const u32 select_mask[] = { + [0] = USBHS_UGCTRL2_USB0SEL, + [2] = USBHS_UGCTRL2_USB2SEL, +}; + +static const u32 select_value[][PHYS_PER_CHANNEL] = { + [0] = { USBHS_UGCTRL2_USB0SEL_PCI, USBHS_UGCTRL2_USB0SEL_HS_USB }, + [2] = { USBHS_UGCTRL2_USB2SEL_PCI, USBHS_UGCTRL2_USB2SEL_USB30 }, +}; + +static int rcar_gen2_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rcar_gen2_phy_driver *drv; + struct phy_provider *provider; + struct device_node *np; + struct resource *res; + void __iomem *base; + struct clk *clk; + int i = 0; + + if (!dev->of_node) { + dev_err(dev, + "This driver is required to be instantiated from device tree\n"); + return -EINVAL; + } + + clk = devm_clk_get(dev, "usbhs"); + if (IS_ERR(clk)) { + dev_err(dev, "Can't get USBHS clock\n"); + return PTR_ERR(clk); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL); + if (!drv) + return -ENOMEM; + + spin_lock_init(&drv->lock); + + drv->clk = clk; + drv->base = base; + + drv->num_channels = of_get_child_count(dev->of_node); + drv->channels = devm_kcalloc(dev, drv->num_channels, + sizeof(struct rcar_gen2_channel), + GFP_KERNEL); + if (!drv->channels) + return -ENOMEM; + + for_each_child_of_node(dev->of_node, np) { + struct rcar_gen2_channel *channel = drv->channels + i; + u32 channel_num; + int error, n; + + channel->of_node = np; + channel->drv = drv; + channel->selected_phy = -1; + + error = of_property_read_u32(np, "reg", &channel_num); + if (error || channel_num > 2) { + dev_err(dev, "Invalid \"reg\" property\n"); + return error; + } + channel->select_mask = select_mask[channel_num]; + + for (n = 0; n < PHYS_PER_CHANNEL; n++) { + struct rcar_gen2_phy *phy = &channel->phys[n]; + + phy->channel = channel; + phy->number = n; + phy->select_value = select_value[channel_num][n]; + + phy->phy = devm_phy_create(dev, NULL, + &rcar_gen2_phy_ops); + if (IS_ERR(phy->phy)) { + dev_err(dev, "Failed to create PHY\n"); + return PTR_ERR(phy->phy); + } + phy_set_drvdata(phy->phy, phy); + } + + i++; + } + + provider = devm_of_phy_provider_register(dev, rcar_gen2_phy_xlate); + if (IS_ERR(provider)) { + dev_err(dev, "Failed to register PHY provider\n"); + return PTR_ERR(provider); + } + + dev_set_drvdata(dev, drv); + + return 0; +} + +static struct platform_driver rcar_gen2_phy_driver = { + .driver = { + .name = "phy_rcar_gen2", + .of_match_table = rcar_gen2_phy_match_table, + }, + .probe = rcar_gen2_phy_probe, +}; + +module_platform_driver(rcar_gen2_phy_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Renesas R-Car Gen2 PHY"); +MODULE_AUTHOR("Sergei Shtylyov "); diff --git a/drivers/phy/renesas/phy-rcar-gen3-usb2.c b/drivers/phy/renesas/phy-rcar-gen3-usb2.c new file mode 100644 index 000000000000..54c34298a000 --- /dev/null +++ b/drivers/phy/renesas/phy-rcar-gen3-usb2.c @@ -0,0 +1,507 @@ +/* + * Renesas R-Car Gen3 for USB2.0 PHY driver + * + * Copyright (C) 2015 Renesas Electronics Corporation + * + * This is based on the phy-rcar-gen2 driver: + * Copyright (C) 2014 Renesas Solutions Corp. + * Copyright (C) 2014 Cogent Embedded, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/******* USB2.0 Host registers (original offset is +0x200) *******/ +#define USB2_INT_ENABLE 0x000 +#define USB2_USBCTR 0x00c +#define USB2_SPD_RSM_TIMSET 0x10c +#define USB2_OC_TIMSET 0x110 +#define USB2_COMMCTRL 0x600 +#define USB2_OBINTSTA 0x604 +#define USB2_OBINTEN 0x608 +#define USB2_VBCTRL 0x60c +#define USB2_LINECTRL1 0x610 +#define USB2_ADPCTRL 0x630 + +/* INT_ENABLE */ +#define USB2_INT_ENABLE_UCOM_INTEN BIT(3) +#define USB2_INT_ENABLE_USBH_INTB_EN BIT(2) +#define USB2_INT_ENABLE_USBH_INTA_EN BIT(1) +#define USB2_INT_ENABLE_INIT (USB2_INT_ENABLE_UCOM_INTEN | \ + USB2_INT_ENABLE_USBH_INTB_EN | \ + USB2_INT_ENABLE_USBH_INTA_EN) + +/* USBCTR */ +#define USB2_USBCTR_DIRPD BIT(2) +#define USB2_USBCTR_PLL_RST BIT(1) + +/* SPD_RSM_TIMSET */ +#define USB2_SPD_RSM_TIMSET_INIT 0x014e029b + +/* OC_TIMSET */ +#define USB2_OC_TIMSET_INIT 0x000209ab + +/* COMMCTRL */ +#define USB2_COMMCTRL_OTG_PERI BIT(31) /* 1 = Peripheral mode */ + +/* OBINTSTA and OBINTEN */ +#define USB2_OBINT_SESSVLDCHG BIT(12) +#define USB2_OBINT_IDDIGCHG BIT(11) +#define USB2_OBINT_BITS (USB2_OBINT_SESSVLDCHG | \ + USB2_OBINT_IDDIGCHG) + +/* VBCTRL */ +#define USB2_VBCTRL_DRVVBUSSEL BIT(8) + +/* LINECTRL1 */ +#define USB2_LINECTRL1_DPRPD_EN BIT(19) +#define USB2_LINECTRL1_DP_RPD BIT(18) +#define USB2_LINECTRL1_DMRPD_EN BIT(17) +#define USB2_LINECTRL1_DM_RPD BIT(16) +#define USB2_LINECTRL1_OPMODE_NODRV BIT(6) + +/* ADPCTRL */ +#define USB2_ADPCTRL_OTGSESSVLD BIT(20) +#define USB2_ADPCTRL_IDDIG BIT(19) +#define USB2_ADPCTRL_IDPULLUP BIT(5) /* 1 = ID sampling is enabled */ +#define USB2_ADPCTRL_DRVVBUS BIT(4) + +struct rcar_gen3_chan { + void __iomem *base; + struct extcon_dev *extcon; + struct phy *phy; + struct regulator *vbus; + struct work_struct work; + bool extcon_host; + bool has_otg; +}; + +static void rcar_gen3_phy_usb2_work(struct work_struct *work) +{ + struct rcar_gen3_chan *ch = container_of(work, struct rcar_gen3_chan, + work); + + if (ch->extcon_host) { + extcon_set_state_sync(ch->extcon, EXTCON_USB_HOST, true); + extcon_set_state_sync(ch->extcon, EXTCON_USB, false); + } else { + extcon_set_state_sync(ch->extcon, EXTCON_USB_HOST, false); + extcon_set_state_sync(ch->extcon, EXTCON_USB, true); + } +} + +static void rcar_gen3_set_host_mode(struct rcar_gen3_chan *ch, int host) +{ + void __iomem *usb2_base = ch->base; + u32 val = readl(usb2_base + USB2_COMMCTRL); + + dev_vdbg(&ch->phy->dev, "%s: %08x, %d\n", __func__, val, host); + if (host) + val &= ~USB2_COMMCTRL_OTG_PERI; + else + val |= USB2_COMMCTRL_OTG_PERI; + writel(val, usb2_base + USB2_COMMCTRL); +} + +static void rcar_gen3_set_linectrl(struct rcar_gen3_chan *ch, int dp, int dm) +{ + void __iomem *usb2_base = ch->base; + u32 val = readl(usb2_base + USB2_LINECTRL1); + + dev_vdbg(&ch->phy->dev, "%s: %08x, %d, %d\n", __func__, val, dp, dm); + val &= ~(USB2_LINECTRL1_DP_RPD | USB2_LINECTRL1_DM_RPD); + if (dp) + val |= USB2_LINECTRL1_DP_RPD; + if (dm) + val |= USB2_LINECTRL1_DM_RPD; + writel(val, usb2_base + USB2_LINECTRL1); +} + +static void rcar_gen3_enable_vbus_ctrl(struct rcar_gen3_chan *ch, int vbus) +{ + void __iomem *usb2_base = ch->base; + u32 val = readl(usb2_base + USB2_ADPCTRL); + + dev_vdbg(&ch->phy->dev, "%s: %08x, %d\n", __func__, val, vbus); + if (vbus) + val |= USB2_ADPCTRL_DRVVBUS; + else + val &= ~USB2_ADPCTRL_DRVVBUS; + writel(val, usb2_base + USB2_ADPCTRL); +} + +static void rcar_gen3_init_for_host(struct rcar_gen3_chan *ch) +{ + rcar_gen3_set_linectrl(ch, 1, 1); + rcar_gen3_set_host_mode(ch, 1); + rcar_gen3_enable_vbus_ctrl(ch, 1); + + ch->extcon_host = true; + schedule_work(&ch->work); +} + +static void rcar_gen3_init_for_peri(struct rcar_gen3_chan *ch) +{ + rcar_gen3_set_linectrl(ch, 0, 1); + rcar_gen3_set_host_mode(ch, 0); + rcar_gen3_enable_vbus_ctrl(ch, 0); + + ch->extcon_host = false; + schedule_work(&ch->work); +} + +static void rcar_gen3_init_for_b_host(struct rcar_gen3_chan *ch) +{ + void __iomem *usb2_base = ch->base; + u32 val; + + val = readl(usb2_base + USB2_LINECTRL1); + writel(val | USB2_LINECTRL1_OPMODE_NODRV, usb2_base + USB2_LINECTRL1); + + rcar_gen3_set_linectrl(ch, 1, 1); + rcar_gen3_set_host_mode(ch, 1); + rcar_gen3_enable_vbus_ctrl(ch, 0); + + val = readl(usb2_base + USB2_LINECTRL1); + writel(val & ~USB2_LINECTRL1_OPMODE_NODRV, usb2_base + USB2_LINECTRL1); +} + +static void rcar_gen3_init_for_a_peri(struct rcar_gen3_chan *ch) +{ + rcar_gen3_set_linectrl(ch, 0, 1); + rcar_gen3_set_host_mode(ch, 0); + rcar_gen3_enable_vbus_ctrl(ch, 1); +} + +static void rcar_gen3_init_from_a_peri_to_a_host(struct rcar_gen3_chan *ch) +{ + void __iomem *usb2_base = ch->base; + u32 val; + + val = readl(usb2_base + USB2_OBINTEN); + writel(val & ~USB2_OBINT_BITS, usb2_base + USB2_OBINTEN); + + rcar_gen3_enable_vbus_ctrl(ch, 0); + rcar_gen3_init_for_host(ch); + + writel(val | USB2_OBINT_BITS, usb2_base + USB2_OBINTEN); +} + +static bool rcar_gen3_check_id(struct rcar_gen3_chan *ch) +{ + return !!(readl(ch->base + USB2_ADPCTRL) & USB2_ADPCTRL_IDDIG); +} + +static void rcar_gen3_device_recognition(struct rcar_gen3_chan *ch) +{ + if (!rcar_gen3_check_id(ch)) + rcar_gen3_init_for_host(ch); + else + rcar_gen3_init_for_peri(ch); +} + +static bool rcar_gen3_is_host(struct rcar_gen3_chan *ch) +{ + return !(readl(ch->base + USB2_COMMCTRL) & USB2_COMMCTRL_OTG_PERI); +} + +static ssize_t role_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct rcar_gen3_chan *ch = dev_get_drvdata(dev); + bool is_b_device, is_host, new_mode_is_host; + + if (!ch->has_otg || !ch->phy->init_count) + return -EIO; + + /* + * is_b_device: true is B-Device. false is A-Device. + * If {new_mode_}is_host: true is Host mode. false is Peripheral mode. + */ + is_b_device = rcar_gen3_check_id(ch); + is_host = rcar_gen3_is_host(ch); + if (!strncmp(buf, "host", strlen("host"))) + new_mode_is_host = true; + else if (!strncmp(buf, "peripheral", strlen("peripheral"))) + new_mode_is_host = false; + else + return -EINVAL; + + /* If current and new mode is the same, this returns the error */ + if (is_host == new_mode_is_host) + return -EINVAL; + + if (new_mode_is_host) { /* And is_host must be false */ + if (!is_b_device) /* A-Peripheral */ + rcar_gen3_init_from_a_peri_to_a_host(ch); + else /* B-Peripheral */ + rcar_gen3_init_for_b_host(ch); + } else { /* And is_host must be true */ + if (!is_b_device) /* A-Host */ + rcar_gen3_init_for_a_peri(ch); + else /* B-Host */ + rcar_gen3_init_for_peri(ch); + } + + return count; +} + +static ssize_t role_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct rcar_gen3_chan *ch = dev_get_drvdata(dev); + + if (!ch->has_otg || !ch->phy->init_count) + return -EIO; + + return sprintf(buf, "%s\n", rcar_gen3_is_host(ch) ? "host" : + "peripheral"); +} +static DEVICE_ATTR_RW(role); + +static void rcar_gen3_init_otg(struct rcar_gen3_chan *ch) +{ + void __iomem *usb2_base = ch->base; + u32 val; + + val = readl(usb2_base + USB2_VBCTRL); + writel(val | USB2_VBCTRL_DRVVBUSSEL, usb2_base + USB2_VBCTRL); + writel(USB2_OBINT_BITS, usb2_base + USB2_OBINTSTA); + val = readl(usb2_base + USB2_OBINTEN); + writel(val | USB2_OBINT_BITS, usb2_base + USB2_OBINTEN); + val = readl(usb2_base + USB2_ADPCTRL); + writel(val | USB2_ADPCTRL_IDPULLUP, usb2_base + USB2_ADPCTRL); + val = readl(usb2_base + USB2_LINECTRL1); + rcar_gen3_set_linectrl(ch, 0, 0); + writel(val | USB2_LINECTRL1_DPRPD_EN | USB2_LINECTRL1_DMRPD_EN, + usb2_base + USB2_LINECTRL1); + + rcar_gen3_device_recognition(ch); +} + +static int rcar_gen3_phy_usb2_init(struct phy *p) +{ + struct rcar_gen3_chan *channel = phy_get_drvdata(p); + void __iomem *usb2_base = channel->base; + + /* Initialize USB2 part */ + writel(USB2_INT_ENABLE_INIT, usb2_base + USB2_INT_ENABLE); + writel(USB2_SPD_RSM_TIMSET_INIT, usb2_base + USB2_SPD_RSM_TIMSET); + writel(USB2_OC_TIMSET_INIT, usb2_base + USB2_OC_TIMSET); + + /* Initialize otg part */ + if (channel->has_otg) + rcar_gen3_init_otg(channel); + + return 0; +} + +static int rcar_gen3_phy_usb2_exit(struct phy *p) +{ + struct rcar_gen3_chan *channel = phy_get_drvdata(p); + + writel(0, channel->base + USB2_INT_ENABLE); + + return 0; +} + +static int rcar_gen3_phy_usb2_power_on(struct phy *p) +{ + struct rcar_gen3_chan *channel = phy_get_drvdata(p); + void __iomem *usb2_base = channel->base; + u32 val; + int ret; + + if (channel->vbus) { + ret = regulator_enable(channel->vbus); + if (ret) + return ret; + } + + val = readl(usb2_base + USB2_USBCTR); + val |= USB2_USBCTR_PLL_RST; + writel(val, usb2_base + USB2_USBCTR); + val &= ~USB2_USBCTR_PLL_RST; + writel(val, usb2_base + USB2_USBCTR); + + return 0; +} + +static int rcar_gen3_phy_usb2_power_off(struct phy *p) +{ + struct rcar_gen3_chan *channel = phy_get_drvdata(p); + int ret = 0; + + if (channel->vbus) + ret = regulator_disable(channel->vbus); + + return ret; +} + +static const struct phy_ops rcar_gen3_phy_usb2_ops = { + .init = rcar_gen3_phy_usb2_init, + .exit = rcar_gen3_phy_usb2_exit, + .power_on = rcar_gen3_phy_usb2_power_on, + .power_off = rcar_gen3_phy_usb2_power_off, + .owner = THIS_MODULE, +}; + +static irqreturn_t rcar_gen3_phy_usb2_irq(int irq, void *_ch) +{ + struct rcar_gen3_chan *ch = _ch; + void __iomem *usb2_base = ch->base; + u32 status = readl(usb2_base + USB2_OBINTSTA); + irqreturn_t ret = IRQ_NONE; + + if (status & USB2_OBINT_BITS) { + dev_vdbg(&ch->phy->dev, "%s: %08x\n", __func__, status); + writel(USB2_OBINT_BITS, usb2_base + USB2_OBINTSTA); + rcar_gen3_device_recognition(ch); + ret = IRQ_HANDLED; + } + + return ret; +} + +static const struct of_device_id rcar_gen3_phy_usb2_match_table[] = { + { .compatible = "renesas,usb2-phy-r8a7795" }, + { .compatible = "renesas,usb2-phy-r8a7796" }, + { .compatible = "renesas,rcar-gen3-usb2-phy" }, + { } +}; +MODULE_DEVICE_TABLE(of, rcar_gen3_phy_usb2_match_table); + +static const unsigned int rcar_gen3_phy_cable[] = { + EXTCON_USB, + EXTCON_USB_HOST, + EXTCON_NONE, +}; + +static int rcar_gen3_phy_usb2_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rcar_gen3_chan *channel; + struct phy_provider *provider; + struct resource *res; + int irq, ret = 0; + + if (!dev->of_node) { + dev_err(dev, "This driver needs device tree\n"); + return -EINVAL; + } + + channel = devm_kzalloc(dev, sizeof(*channel), GFP_KERNEL); + if (!channel) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + channel->base = devm_ioremap_resource(dev, res); + if (IS_ERR(channel->base)) + return PTR_ERR(channel->base); + + /* call request_irq for OTG */ + irq = platform_get_irq(pdev, 0); + if (irq >= 0) { + int ret; + + INIT_WORK(&channel->work, rcar_gen3_phy_usb2_work); + irq = devm_request_irq(dev, irq, rcar_gen3_phy_usb2_irq, + IRQF_SHARED, dev_name(dev), channel); + if (irq < 0) + dev_err(dev, "No irq handler (%d)\n", irq); + channel->has_otg = true; + channel->extcon = devm_extcon_dev_allocate(dev, + rcar_gen3_phy_cable); + if (IS_ERR(channel->extcon)) + return PTR_ERR(channel->extcon); + + ret = devm_extcon_dev_register(dev, channel->extcon); + if (ret < 0) { + dev_err(dev, "Failed to register extcon\n"); + return ret; + } + } + + /* + * devm_phy_create() will call pm_runtime_enable(&phy->dev); + * And then, phy-core will manage runtime pm for this device. + */ + pm_runtime_enable(dev); + channel->phy = devm_phy_create(dev, NULL, &rcar_gen3_phy_usb2_ops); + if (IS_ERR(channel->phy)) { + dev_err(dev, "Failed to create USB2 PHY\n"); + ret = PTR_ERR(channel->phy); + goto error; + } + + channel->vbus = devm_regulator_get_optional(dev, "vbus"); + if (IS_ERR(channel->vbus)) { + if (PTR_ERR(channel->vbus) == -EPROBE_DEFER) { + ret = PTR_ERR(channel->vbus); + goto error; + } + channel->vbus = NULL; + } + + platform_set_drvdata(pdev, channel); + phy_set_drvdata(channel->phy, channel); + + provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(provider)) { + dev_err(dev, "Failed to register PHY provider\n"); + ret = PTR_ERR(provider); + goto error; + } else if (channel->has_otg) { + int ret; + + ret = device_create_file(dev, &dev_attr_role); + if (ret < 0) + goto error; + } + + return 0; + +error: + pm_runtime_disable(dev); + + return ret; +} + +static int rcar_gen3_phy_usb2_remove(struct platform_device *pdev) +{ + struct rcar_gen3_chan *channel = platform_get_drvdata(pdev); + + if (channel->has_otg) + device_remove_file(&pdev->dev, &dev_attr_role); + + pm_runtime_disable(&pdev->dev); + + return 0; +}; + +static struct platform_driver rcar_gen3_phy_usb2_driver = { + .driver = { + .name = "phy_rcar_gen3_usb2", + .of_match_table = rcar_gen3_phy_usb2_match_table, + }, + .probe = rcar_gen3_phy_usb2_probe, + .remove = rcar_gen3_phy_usb2_remove, +}; +module_platform_driver(rcar_gen3_phy_usb2_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Renesas R-Car Gen3 USB 2.0 PHY"); +MODULE_AUTHOR("Yoshihiro Shimoda "); diff --git a/drivers/phy/rockchip/Kconfig b/drivers/phy/rockchip/Kconfig new file mode 100644 index 000000000000..f5325b2b679e --- /dev/null +++ b/drivers/phy/rockchip/Kconfig @@ -0,0 +1,51 @@ +# +# Phy drivers for Rockchip platforms +# +config PHY_ROCKCHIP_DP + tristate "Rockchip Display Port PHY Driver" + depends on ARCH_ROCKCHIP && OF + select GENERIC_PHY + help + Enable this to support the Rockchip Display Port PHY. + +config PHY_ROCKCHIP_EMMC + tristate "Rockchip EMMC PHY Driver" + depends on ARCH_ROCKCHIP && OF + select GENERIC_PHY + help + Enable this to support the Rockchip EMMC PHY. + +config PHY_ROCKCHIP_INNO_USB2 + tristate "Rockchip INNO USB2PHY Driver" + depends on (ARCH_ROCKCHIP || COMPILE_TEST) && OF + depends on COMMON_CLK + depends on EXTCON + depends on USB_SUPPORT + select GENERIC_PHY + select USB_COMMON + help + Support for Rockchip USB2.0 PHY with Innosilicon IP block. + +config PHY_ROCKCHIP_PCIE + tristate "Rockchip PCIe PHY Driver" + depends on (ARCH_ROCKCHIP && OF) || COMPILE_TEST + select GENERIC_PHY + select MFD_SYSCON + help + Enable this to support the Rockchip PCIe PHY. + +config PHY_ROCKCHIP_TYPEC + tristate "Rockchip TYPEC PHY Driver" + depends on OF && (ARCH_ROCKCHIP || COMPILE_TEST) + select EXTCON + select GENERIC_PHY + select RESET_CONTROLLER + help + Enable this to support the Rockchip USB TYPEC PHY. + +config PHY_ROCKCHIP_USB + tristate "Rockchip USB2 PHY Driver" + depends on ARCH_ROCKCHIP && OF + select GENERIC_PHY + help + Enable this to support the Rockchip USB 2.0 PHY. diff --git a/drivers/phy/rockchip/Makefile b/drivers/phy/rockchip/Makefile new file mode 100644 index 000000000000..bd0acdf38e0f --- /dev/null +++ b/drivers/phy/rockchip/Makefile @@ -0,0 +1,6 @@ +obj-$(CONFIG_PHY_ROCKCHIP_DP) += phy-rockchip-dp.o +obj-$(CONFIG_PHY_ROCKCHIP_EMMC) += phy-rockchip-emmc.o +obj-$(CONFIG_PHY_ROCKCHIP_INNO_USB2) += phy-rockchip-inno-usb2.o +obj-$(CONFIG_PHY_ROCKCHIP_PCIE) += phy-rockchip-pcie.o +obj-$(CONFIG_PHY_ROCKCHIP_TYPEC) += phy-rockchip-typec.o +obj-$(CONFIG_PHY_ROCKCHIP_USB) += phy-rockchip-usb.o diff --git a/drivers/phy/rockchip/phy-rockchip-dp.c b/drivers/phy/rockchip/phy-rockchip-dp.c new file mode 100644 index 000000000000..8b267a746576 --- /dev/null +++ b/drivers/phy/rockchip/phy-rockchip-dp.c @@ -0,0 +1,154 @@ +/* + * Rockchip DP PHY driver + * + * Copyright (C) 2016 FuZhou Rockchip Co., Ltd. + * Author: Yakir Yang + * + * 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; either version 2 of the License. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define GRF_SOC_CON12 0x0274 + +#define GRF_EDP_REF_CLK_SEL_INTER_HIWORD_MASK BIT(20) +#define GRF_EDP_REF_CLK_SEL_INTER BIT(4) + +#define GRF_EDP_PHY_SIDDQ_HIWORD_MASK BIT(21) +#define GRF_EDP_PHY_SIDDQ_ON 0 +#define GRF_EDP_PHY_SIDDQ_OFF BIT(5) + +struct rockchip_dp_phy { + struct device *dev; + struct regmap *grf; + struct clk *phy_24m; +}; + +static int rockchip_set_phy_state(struct phy *phy, bool enable) +{ + struct rockchip_dp_phy *dp = phy_get_drvdata(phy); + int ret; + + if (enable) { + ret = regmap_write(dp->grf, GRF_SOC_CON12, + GRF_EDP_PHY_SIDDQ_HIWORD_MASK | + GRF_EDP_PHY_SIDDQ_ON); + if (ret < 0) { + dev_err(dp->dev, "Can't enable PHY power %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(dp->phy_24m); + } else { + clk_disable_unprepare(dp->phy_24m); + + ret = regmap_write(dp->grf, GRF_SOC_CON12, + GRF_EDP_PHY_SIDDQ_HIWORD_MASK | + GRF_EDP_PHY_SIDDQ_OFF); + } + + return ret; +} + +static int rockchip_dp_phy_power_on(struct phy *phy) +{ + return rockchip_set_phy_state(phy, true); +} + +static int rockchip_dp_phy_power_off(struct phy *phy) +{ + return rockchip_set_phy_state(phy, false); +} + +static const struct phy_ops rockchip_dp_phy_ops = { + .power_on = rockchip_dp_phy_power_on, + .power_off = rockchip_dp_phy_power_off, + .owner = THIS_MODULE, +}; + +static int rockchip_dp_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct phy_provider *phy_provider; + struct rockchip_dp_phy *dp; + struct phy *phy; + int ret; + + if (!np) + return -ENODEV; + + if (!dev->parent || !dev->parent->of_node) + return -ENODEV; + + dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL); + if (!dp) + return -ENOMEM; + + dp->dev = dev; + + dp->phy_24m = devm_clk_get(dev, "24m"); + if (IS_ERR(dp->phy_24m)) { + dev_err(dev, "cannot get clock 24m\n"); + return PTR_ERR(dp->phy_24m); + } + + ret = clk_set_rate(dp->phy_24m, 24000000); + if (ret < 0) { + dev_err(dp->dev, "cannot set clock phy_24m %d\n", ret); + return ret; + } + + dp->grf = syscon_node_to_regmap(dev->parent->of_node); + if (IS_ERR(dp->grf)) { + dev_err(dev, "rk3288-dp needs the General Register Files syscon\n"); + return PTR_ERR(dp->grf); + } + + ret = regmap_write(dp->grf, GRF_SOC_CON12, GRF_EDP_REF_CLK_SEL_INTER | + GRF_EDP_REF_CLK_SEL_INTER_HIWORD_MASK); + if (ret != 0) { + dev_err(dp->dev, "Could not config GRF edp ref clk: %d\n", ret); + return ret; + } + + phy = devm_phy_create(dev, np, &rockchip_dp_phy_ops); + if (IS_ERR(phy)) { + dev_err(dev, "failed to create phy\n"); + return PTR_ERR(phy); + } + phy_set_drvdata(phy, dp); + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id rockchip_dp_phy_dt_ids[] = { + { .compatible = "rockchip,rk3288-dp-phy" }, + {} +}; + +MODULE_DEVICE_TABLE(of, rockchip_dp_phy_dt_ids); + +static struct platform_driver rockchip_dp_phy_driver = { + .probe = rockchip_dp_phy_probe, + .driver = { + .name = "rockchip-dp-phy", + .of_match_table = rockchip_dp_phy_dt_ids, + }, +}; + +module_platform_driver(rockchip_dp_phy_driver); + +MODULE_AUTHOR("Yakir Yang "); +MODULE_DESCRIPTION("Rockchip DP PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/rockchip/phy-rockchip-emmc.c b/drivers/phy/rockchip/phy-rockchip-emmc.c new file mode 100644 index 000000000000..f1b24f18e9b2 --- /dev/null +++ b/drivers/phy/rockchip/phy-rockchip-emmc.c @@ -0,0 +1,383 @@ +/* + * Rockchip emmc PHY driver + * + * Copyright (C) 2016 Shawn Lin + * Copyright (C) 2016 ROCKCHIP, Inc. + * + * 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; either 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * The higher 16-bit of this register is used for write protection + * only if BIT(x + 16) set to 1 the BIT(x) can be written. + */ +#define HIWORD_UPDATE(val, mask, shift) \ + ((val) << (shift) | (mask) << ((shift) + 16)) + +/* Register definition */ +#define GRF_EMMCPHY_CON0 0x0 +#define GRF_EMMCPHY_CON1 0x4 +#define GRF_EMMCPHY_CON2 0x8 +#define GRF_EMMCPHY_CON3 0xc +#define GRF_EMMCPHY_CON4 0x10 +#define GRF_EMMCPHY_CON5 0x14 +#define GRF_EMMCPHY_CON6 0x18 +#define GRF_EMMCPHY_STATUS 0x20 + +#define PHYCTRL_PDB_MASK 0x1 +#define PHYCTRL_PDB_SHIFT 0x0 +#define PHYCTRL_PDB_PWR_ON 0x1 +#define PHYCTRL_PDB_PWR_OFF 0x0 +#define PHYCTRL_ENDLL_MASK 0x1 +#define PHYCTRL_ENDLL_SHIFT 0x1 +#define PHYCTRL_ENDLL_ENABLE 0x1 +#define PHYCTRL_ENDLL_DISABLE 0x0 +#define PHYCTRL_CALDONE_MASK 0x1 +#define PHYCTRL_CALDONE_SHIFT 0x6 +#define PHYCTRL_CALDONE_DONE 0x1 +#define PHYCTRL_CALDONE_GOING 0x0 +#define PHYCTRL_DLLRDY_MASK 0x1 +#define PHYCTRL_DLLRDY_SHIFT 0x5 +#define PHYCTRL_DLLRDY_DONE 0x1 +#define PHYCTRL_DLLRDY_GOING 0x0 +#define PHYCTRL_FREQSEL_200M 0x0 +#define PHYCTRL_FREQSEL_50M 0x1 +#define PHYCTRL_FREQSEL_100M 0x2 +#define PHYCTRL_FREQSEL_150M 0x3 +#define PHYCTRL_FREQSEL_MASK 0x3 +#define PHYCTRL_FREQSEL_SHIFT 0xc +#define PHYCTRL_DR_MASK 0x7 +#define PHYCTRL_DR_SHIFT 0x4 +#define PHYCTRL_DR_50OHM 0x0 +#define PHYCTRL_DR_33OHM 0x1 +#define PHYCTRL_DR_66OHM 0x2 +#define PHYCTRL_DR_100OHM 0x3 +#define PHYCTRL_DR_40OHM 0x4 +#define PHYCTRL_OTAPDLYENA 0x1 +#define PHYCTRL_OTAPDLYENA_MASK 0x1 +#define PHYCTRL_OTAPDLYENA_SHIFT 0xb +#define PHYCTRL_OTAPDLYSEL_MASK 0xf +#define PHYCTRL_OTAPDLYSEL_SHIFT 0x7 + +struct rockchip_emmc_phy { + unsigned int reg_offset; + struct regmap *reg_base; + struct clk *emmcclk; +}; + +static int rockchip_emmc_phy_power(struct phy *phy, bool on_off) +{ + struct rockchip_emmc_phy *rk_phy = phy_get_drvdata(phy); + unsigned int caldone; + unsigned int dllrdy; + unsigned int freqsel = PHYCTRL_FREQSEL_200M; + unsigned long rate; + unsigned long timeout; + + /* + * Keep phyctrl_pdb and phyctrl_endll low to allow + * initialization of CALIO state M/C DFFs + */ + regmap_write(rk_phy->reg_base, + rk_phy->reg_offset + GRF_EMMCPHY_CON6, + HIWORD_UPDATE(PHYCTRL_PDB_PWR_OFF, + PHYCTRL_PDB_MASK, + PHYCTRL_PDB_SHIFT)); + regmap_write(rk_phy->reg_base, + rk_phy->reg_offset + GRF_EMMCPHY_CON6, + HIWORD_UPDATE(PHYCTRL_ENDLL_DISABLE, + PHYCTRL_ENDLL_MASK, + PHYCTRL_ENDLL_SHIFT)); + + /* Already finish power_off above */ + if (on_off == PHYCTRL_PDB_PWR_OFF) + return 0; + + rate = clk_get_rate(rk_phy->emmcclk); + + if (rate != 0) { + unsigned long ideal_rate; + unsigned long diff; + + switch (rate) { + case 1 ... 74999999: + ideal_rate = 50000000; + freqsel = PHYCTRL_FREQSEL_50M; + break; + case 75000000 ... 124999999: + ideal_rate = 100000000; + freqsel = PHYCTRL_FREQSEL_100M; + break; + case 125000000 ... 174999999: + ideal_rate = 150000000; + freqsel = PHYCTRL_FREQSEL_150M; + break; + default: + ideal_rate = 200000000; + break; + } + + diff = (rate > ideal_rate) ? + rate - ideal_rate : ideal_rate - rate; + + /* + * In order for tuning delays to be accurate we need to be + * pretty spot on for the DLL range, so warn if we're too + * far off. Also warn if we're above the 200 MHz max. Don't + * warn for really slow rates since we won't be tuning then. + */ + if ((rate > 50000000 && diff > 15000000) || (rate > 200000000)) + dev_warn(&phy->dev, "Unsupported rate: %lu\n", rate); + } + + /* + * According to the user manual, calpad calibration + * cycle takes more than 2us without the minimal recommended + * value, so we may need a little margin here + */ + udelay(3); + regmap_write(rk_phy->reg_base, + rk_phy->reg_offset + GRF_EMMCPHY_CON6, + HIWORD_UPDATE(PHYCTRL_PDB_PWR_ON, + PHYCTRL_PDB_MASK, + PHYCTRL_PDB_SHIFT)); + + /* + * According to the user manual, it asks driver to + * wait 5us for calpad busy trimming + */ + udelay(5); + regmap_read(rk_phy->reg_base, + rk_phy->reg_offset + GRF_EMMCPHY_STATUS, + &caldone); + caldone = (caldone >> PHYCTRL_CALDONE_SHIFT) & PHYCTRL_CALDONE_MASK; + if (caldone != PHYCTRL_CALDONE_DONE) { + pr_err("rockchip_emmc_phy_power: caldone timeout.\n"); + return -ETIMEDOUT; + } + + /* Set the frequency of the DLL operation */ + regmap_write(rk_phy->reg_base, + rk_phy->reg_offset + GRF_EMMCPHY_CON0, + HIWORD_UPDATE(freqsel, PHYCTRL_FREQSEL_MASK, + PHYCTRL_FREQSEL_SHIFT)); + + /* Turn on the DLL */ + regmap_write(rk_phy->reg_base, + rk_phy->reg_offset + GRF_EMMCPHY_CON6, + HIWORD_UPDATE(PHYCTRL_ENDLL_ENABLE, + PHYCTRL_ENDLL_MASK, + PHYCTRL_ENDLL_SHIFT)); + + /* + * We turned on the DLL even though the rate was 0 because we the + * clock might be turned on later. ...but we can't wait for the DLL + * to lock when the rate is 0 because it will never lock with no + * input clock. + * + * Technically we should be checking the lock later when the clock + * is turned on, but for now we won't. + */ + if (rate == 0) + return 0; + + /* + * After enabling analog DLL circuits docs say that we need 10.2 us if + * our source clock is at 50 MHz and that lock time scales linearly + * with clock speed. If we are powering on the PHY and the card clock + * is super slow (like 100 kHZ) this could take as long as 5.1 ms as + * per the math: 10.2 us * (50000000 Hz / 100000 Hz) => 5.1 ms + * Hopefully we won't be running at 100 kHz, but we should still make + * sure we wait long enough. + * + * NOTE: There appear to be corner cases where the DLL seems to take + * extra long to lock for reasons that aren't understood. In some + * extreme cases we've seen it take up to over 10ms (!). We'll be + * generous and give it 50ms. We still busy wait here because: + * - In most cases it should be super fast. + * - This is not called lots during normal operation so it shouldn't + * be a power or performance problem to busy wait. We expect it + * only at boot / resume. In both cases, eMMC is probably on the + * critical path so busy waiting a little extra time should be OK. + */ + timeout = jiffies + msecs_to_jiffies(50); + do { + udelay(1); + + regmap_read(rk_phy->reg_base, + rk_phy->reg_offset + GRF_EMMCPHY_STATUS, + &dllrdy); + dllrdy = (dllrdy >> PHYCTRL_DLLRDY_SHIFT) & PHYCTRL_DLLRDY_MASK; + if (dllrdy == PHYCTRL_DLLRDY_DONE) + break; + } while (!time_after(jiffies, timeout)); + + if (dllrdy != PHYCTRL_DLLRDY_DONE) { + pr_err("rockchip_emmc_phy_power: dllrdy timeout.\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static int rockchip_emmc_phy_init(struct phy *phy) +{ + struct rockchip_emmc_phy *rk_phy = phy_get_drvdata(phy); + int ret = 0; + + /* + * We purposely get the clock here and not in probe to avoid the + * circular dependency problem. We expect: + * - PHY driver to probe + * - SDHCI driver to start probe + * - SDHCI driver to register it's clock + * - SDHCI driver to get the PHY + * - SDHCI driver to init the PHY + * + * The clock is optional, so upon any error we just set to NULL. + * + * NOTE: we don't do anything special for EPROBE_DEFER here. Given the + * above expected use case, EPROBE_DEFER isn't sensible to expect, so + * it's just like any other error. + */ + rk_phy->emmcclk = clk_get(&phy->dev, "emmcclk"); + if (IS_ERR(rk_phy->emmcclk)) { + dev_dbg(&phy->dev, "Error getting emmcclk: %d\n", ret); + rk_phy->emmcclk = NULL; + } + + return ret; +} + +static int rockchip_emmc_phy_exit(struct phy *phy) +{ + struct rockchip_emmc_phy *rk_phy = phy_get_drvdata(phy); + + clk_put(rk_phy->emmcclk); + + return 0; +} + +static int rockchip_emmc_phy_power_off(struct phy *phy) +{ + /* Power down emmc phy analog blocks */ + return rockchip_emmc_phy_power(phy, PHYCTRL_PDB_PWR_OFF); +} + +static int rockchip_emmc_phy_power_on(struct phy *phy) +{ + struct rockchip_emmc_phy *rk_phy = phy_get_drvdata(phy); + + /* Drive impedance: 50 Ohm */ + regmap_write(rk_phy->reg_base, + rk_phy->reg_offset + GRF_EMMCPHY_CON6, + HIWORD_UPDATE(PHYCTRL_DR_50OHM, + PHYCTRL_DR_MASK, + PHYCTRL_DR_SHIFT)); + + /* Output tap delay: enable */ + regmap_write(rk_phy->reg_base, + rk_phy->reg_offset + GRF_EMMCPHY_CON0, + HIWORD_UPDATE(PHYCTRL_OTAPDLYENA, + PHYCTRL_OTAPDLYENA_MASK, + PHYCTRL_OTAPDLYENA_SHIFT)); + + /* Output tap delay */ + regmap_write(rk_phy->reg_base, + rk_phy->reg_offset + GRF_EMMCPHY_CON0, + HIWORD_UPDATE(4, + PHYCTRL_OTAPDLYSEL_MASK, + PHYCTRL_OTAPDLYSEL_SHIFT)); + + /* Power up emmc phy analog blocks */ + return rockchip_emmc_phy_power(phy, PHYCTRL_PDB_PWR_ON); +} + +static const struct phy_ops ops = { + .init = rockchip_emmc_phy_init, + .exit = rockchip_emmc_phy_exit, + .power_on = rockchip_emmc_phy_power_on, + .power_off = rockchip_emmc_phy_power_off, + .owner = THIS_MODULE, +}; + +static int rockchip_emmc_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rockchip_emmc_phy *rk_phy; + struct phy *generic_phy; + struct phy_provider *phy_provider; + struct regmap *grf; + unsigned int reg_offset; + + if (!dev->parent || !dev->parent->of_node) + return -ENODEV; + + grf = syscon_node_to_regmap(dev->parent->of_node); + if (IS_ERR(grf)) { + dev_err(dev, "Missing rockchip,grf property\n"); + return PTR_ERR(grf); + } + + rk_phy = devm_kzalloc(dev, sizeof(*rk_phy), GFP_KERNEL); + if (!rk_phy) + return -ENOMEM; + + if (of_property_read_u32(dev->of_node, "reg", ®_offset)) { + dev_err(dev, "missing reg property in node %s\n", + dev->of_node->name); + return -EINVAL; + } + + rk_phy->reg_offset = reg_offset; + rk_phy->reg_base = grf; + + generic_phy = devm_phy_create(dev, dev->of_node, &ops); + if (IS_ERR(generic_phy)) { + dev_err(dev, "failed to create PHY\n"); + return PTR_ERR(generic_phy); + } + + phy_set_drvdata(generic_phy, rk_phy); + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id rockchip_emmc_phy_dt_ids[] = { + { .compatible = "rockchip,rk3399-emmc-phy" }, + {} +}; + +MODULE_DEVICE_TABLE(of, rockchip_emmc_phy_dt_ids); + +static struct platform_driver rockchip_emmc_driver = { + .probe = rockchip_emmc_phy_probe, + .driver = { + .name = "rockchip-emmc-phy", + .of_match_table = rockchip_emmc_phy_dt_ids, + }, +}; + +module_platform_driver(rockchip_emmc_driver); + +MODULE_AUTHOR("Shawn Lin "); +MODULE_DESCRIPTION("Rockchip EMMC PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c new file mode 100644 index 000000000000..8efe78a49916 --- /dev/null +++ b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c @@ -0,0 +1,1284 @@ +/* + * Rockchip USB2.0 PHY with Innosilicon IP block driver + * + * Copyright (C) 2016 Fuzhou Rockchip Electronics Co., Ltd + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BIT_WRITEABLE_SHIFT 16 +#define SCHEDULE_DELAY (60 * HZ) +#define OTG_SCHEDULE_DELAY (2 * HZ) + +enum rockchip_usb2phy_port_id { + USB2PHY_PORT_OTG, + USB2PHY_PORT_HOST, + USB2PHY_NUM_PORTS, +}; + +enum rockchip_usb2phy_host_state { + PHY_STATE_HS_ONLINE = 0, + PHY_STATE_DISCONNECT = 1, + PHY_STATE_CONNECT = 2, + PHY_STATE_FS_LS_ONLINE = 4, +}; + +/** + * Different states involved in USB charger detection. + * USB_CHG_STATE_UNDEFINED USB charger is not connected or detection + * process is not yet started. + * USB_CHG_STATE_WAIT_FOR_DCD Waiting for Data pins contact. + * USB_CHG_STATE_DCD_DONE Data pin contact is detected. + * USB_CHG_STATE_PRIMARY_DONE Primary detection is completed (Detects + * between SDP and DCP/CDP). + * USB_CHG_STATE_SECONDARY_DONE Secondary detection is completed (Detects + * between DCP and CDP). + * USB_CHG_STATE_DETECTED USB charger type is determined. + */ +enum usb_chg_state { + USB_CHG_STATE_UNDEFINED = 0, + USB_CHG_STATE_WAIT_FOR_DCD, + USB_CHG_STATE_DCD_DONE, + USB_CHG_STATE_PRIMARY_DONE, + USB_CHG_STATE_SECONDARY_DONE, + USB_CHG_STATE_DETECTED, +}; + +static const unsigned int rockchip_usb2phy_extcon_cable[] = { + EXTCON_USB, + EXTCON_USB_HOST, + EXTCON_CHG_USB_SDP, + EXTCON_CHG_USB_CDP, + EXTCON_CHG_USB_DCP, + EXTCON_CHG_USB_SLOW, + EXTCON_NONE, +}; + +struct usb2phy_reg { + unsigned int offset; + unsigned int bitend; + unsigned int bitstart; + unsigned int disable; + unsigned int enable; +}; + +/** + * struct rockchip_chg_det_reg: usb charger detect registers + * @cp_det: charging port detected successfully. + * @dcp_det: dedicated charging port detected successfully. + * @dp_det: assert data pin connect successfully. + * @idm_sink_en: open dm sink curren. + * @idp_sink_en: open dp sink current. + * @idp_src_en: open dm source current. + * @rdm_pdwn_en: open dm pull down resistor. + * @vdm_src_en: open dm voltage source. + * @vdp_src_en: open dp voltage source. + * @opmode: utmi operational mode. + */ +struct rockchip_chg_det_reg { + struct usb2phy_reg cp_det; + struct usb2phy_reg dcp_det; + struct usb2phy_reg dp_det; + struct usb2phy_reg idm_sink_en; + struct usb2phy_reg idp_sink_en; + struct usb2phy_reg idp_src_en; + struct usb2phy_reg rdm_pdwn_en; + struct usb2phy_reg vdm_src_en; + struct usb2phy_reg vdp_src_en; + struct usb2phy_reg opmode; +}; + +/** + * struct rockchip_usb2phy_port_cfg: usb-phy port configuration. + * @phy_sus: phy suspend register. + * @bvalid_det_en: vbus valid rise detection enable register. + * @bvalid_det_st: vbus valid rise detection status register. + * @bvalid_det_clr: vbus valid rise detection clear register. + * @ls_det_en: linestate detection enable register. + * @ls_det_st: linestate detection state register. + * @ls_det_clr: linestate detection clear register. + * @utmi_avalid: utmi vbus avalid status register. + * @utmi_bvalid: utmi vbus bvalid status register. + * @utmi_ls: utmi linestate state register. + * @utmi_hstdet: utmi host disconnect register. + */ +struct rockchip_usb2phy_port_cfg { + struct usb2phy_reg phy_sus; + struct usb2phy_reg bvalid_det_en; + struct usb2phy_reg bvalid_det_st; + struct usb2phy_reg bvalid_det_clr; + struct usb2phy_reg ls_det_en; + struct usb2phy_reg ls_det_st; + struct usb2phy_reg ls_det_clr; + struct usb2phy_reg utmi_avalid; + struct usb2phy_reg utmi_bvalid; + struct usb2phy_reg utmi_ls; + struct usb2phy_reg utmi_hstdet; +}; + +/** + * struct rockchip_usb2phy_cfg: usb-phy configuration. + * @reg: the address offset of grf for usb-phy config. + * @num_ports: specify how many ports that the phy has. + * @clkout_ctl: keep on/turn off output clk of phy. + * @chg_det: charger detection registers. + */ +struct rockchip_usb2phy_cfg { + unsigned int reg; + unsigned int num_ports; + struct usb2phy_reg clkout_ctl; + const struct rockchip_usb2phy_port_cfg port_cfgs[USB2PHY_NUM_PORTS]; + const struct rockchip_chg_det_reg chg_det; +}; + +/** + * struct rockchip_usb2phy_port: usb-phy port data. + * @port_id: flag for otg port or host port. + * @suspended: phy suspended flag. + * @utmi_avalid: utmi avalid status usage flag. + * true - use avalid to get vbus status + * flase - use bvalid to get vbus status + * @vbus_attached: otg device vbus status. + * @bvalid_irq: IRQ number assigned for vbus valid rise detection. + * @ls_irq: IRQ number assigned for linestate detection. + * @mutex: for register updating in sm_work. + * @chg_work: charge detect work. + * @otg_sm_work: OTG state machine work. + * @sm_work: HOST state machine work. + * @phy_cfg: port register configuration, assigned by driver data. + * @event_nb: hold event notification callback. + * @state: define OTG enumeration states before device reset. + * @mode: the dr_mode of the controller. + */ +struct rockchip_usb2phy_port { + struct phy *phy; + unsigned int port_id; + bool suspended; + bool utmi_avalid; + bool vbus_attached; + int bvalid_irq; + int ls_irq; + struct mutex mutex; + struct delayed_work chg_work; + struct delayed_work otg_sm_work; + struct delayed_work sm_work; + const struct rockchip_usb2phy_port_cfg *port_cfg; + struct notifier_block event_nb; + enum usb_otg_state state; + enum usb_dr_mode mode; +}; + +/** + * struct rockchip_usb2phy: usb2.0 phy driver data. + * @grf: General Register Files regmap. + * @clk: clock struct of phy input clk. + * @clk480m: clock struct of phy output clk. + * @clk_hw: clock struct of phy output clk management. + * @chg_state: states involved in USB charger detection. + * @chg_type: USB charger types. + * @dcd_retries: The retry count used to track Data contact + * detection process. + * @edev: extcon device for notification registration + * @phy_cfg: phy register configuration, assigned by driver data. + * @ports: phy port instance. + */ +struct rockchip_usb2phy { + struct device *dev; + struct regmap *grf; + struct clk *clk; + struct clk *clk480m; + struct clk_hw clk480m_hw; + enum usb_chg_state chg_state; + enum power_supply_type chg_type; + u8 dcd_retries; + struct extcon_dev *edev; + const struct rockchip_usb2phy_cfg *phy_cfg; + struct rockchip_usb2phy_port ports[USB2PHY_NUM_PORTS]; +}; + +static inline int property_enable(struct rockchip_usb2phy *rphy, + const struct usb2phy_reg *reg, bool en) +{ + unsigned int val, mask, tmp; + + tmp = en ? reg->enable : reg->disable; + mask = GENMASK(reg->bitend, reg->bitstart); + val = (tmp << reg->bitstart) | (mask << BIT_WRITEABLE_SHIFT); + + return regmap_write(rphy->grf, reg->offset, val); +} + +static inline bool property_enabled(struct rockchip_usb2phy *rphy, + const struct usb2phy_reg *reg) +{ + int ret; + unsigned int tmp, orig; + unsigned int mask = GENMASK(reg->bitend, reg->bitstart); + + ret = regmap_read(rphy->grf, reg->offset, &orig); + if (ret) + return false; + + tmp = (orig & mask) >> reg->bitstart; + return tmp == reg->enable; +} + +static int rockchip_usb2phy_clk480m_prepare(struct clk_hw *hw) +{ + struct rockchip_usb2phy *rphy = + container_of(hw, struct rockchip_usb2phy, clk480m_hw); + int ret; + + /* turn on 480m clk output if it is off */ + if (!property_enabled(rphy, &rphy->phy_cfg->clkout_ctl)) { + ret = property_enable(rphy, &rphy->phy_cfg->clkout_ctl, true); + if (ret) + return ret; + + /* waiting for the clk become stable */ + usleep_range(1200, 1300); + } + + return 0; +} + +static void rockchip_usb2phy_clk480m_unprepare(struct clk_hw *hw) +{ + struct rockchip_usb2phy *rphy = + container_of(hw, struct rockchip_usb2phy, clk480m_hw); + + /* turn off 480m clk output */ + property_enable(rphy, &rphy->phy_cfg->clkout_ctl, false); +} + +static int rockchip_usb2phy_clk480m_prepared(struct clk_hw *hw) +{ + struct rockchip_usb2phy *rphy = + container_of(hw, struct rockchip_usb2phy, clk480m_hw); + + return property_enabled(rphy, &rphy->phy_cfg->clkout_ctl); +} + +static unsigned long +rockchip_usb2phy_clk480m_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + return 480000000; +} + +static const struct clk_ops rockchip_usb2phy_clkout_ops = { + .prepare = rockchip_usb2phy_clk480m_prepare, + .unprepare = rockchip_usb2phy_clk480m_unprepare, + .is_prepared = rockchip_usb2phy_clk480m_prepared, + .recalc_rate = rockchip_usb2phy_clk480m_recalc_rate, +}; + +static void rockchip_usb2phy_clk480m_unregister(void *data) +{ + struct rockchip_usb2phy *rphy = data; + + of_clk_del_provider(rphy->dev->of_node); + clk_unregister(rphy->clk480m); +} + +static int +rockchip_usb2phy_clk480m_register(struct rockchip_usb2phy *rphy) +{ + struct device_node *node = rphy->dev->of_node; + struct clk_init_data init; + const char *clk_name; + int ret; + + init.flags = 0; + init.name = "clk_usbphy_480m"; + init.ops = &rockchip_usb2phy_clkout_ops; + + /* optional override of the clockname */ + of_property_read_string(node, "clock-output-names", &init.name); + + if (rphy->clk) { + clk_name = __clk_get_name(rphy->clk); + init.parent_names = &clk_name; + init.num_parents = 1; + } else { + init.parent_names = NULL; + init.num_parents = 0; + } + + rphy->clk480m_hw.init = &init; + + /* register the clock */ + rphy->clk480m = clk_register(rphy->dev, &rphy->clk480m_hw); + if (IS_ERR(rphy->clk480m)) { + ret = PTR_ERR(rphy->clk480m); + goto err_ret; + } + + ret = of_clk_add_provider(node, of_clk_src_simple_get, rphy->clk480m); + if (ret < 0) + goto err_clk_provider; + + ret = devm_add_action(rphy->dev, rockchip_usb2phy_clk480m_unregister, + rphy); + if (ret < 0) + goto err_unreg_action; + + return 0; + +err_unreg_action: + of_clk_del_provider(node); +err_clk_provider: + clk_unregister(rphy->clk480m); +err_ret: + return ret; +} + +static int rockchip_usb2phy_extcon_register(struct rockchip_usb2phy *rphy) +{ + int ret; + struct device_node *node = rphy->dev->of_node; + struct extcon_dev *edev; + + if (of_property_read_bool(node, "extcon")) { + edev = extcon_get_edev_by_phandle(rphy->dev, 0); + if (IS_ERR(edev)) { + if (PTR_ERR(edev) != -EPROBE_DEFER) + dev_err(rphy->dev, "Invalid or missing extcon\n"); + return PTR_ERR(edev); + } + } else { + /* Initialize extcon device */ + edev = devm_extcon_dev_allocate(rphy->dev, + rockchip_usb2phy_extcon_cable); + + if (IS_ERR(edev)) + return -ENOMEM; + + ret = devm_extcon_dev_register(rphy->dev, edev); + if (ret) { + dev_err(rphy->dev, "failed to register extcon device\n"); + return ret; + } + } + + rphy->edev = edev; + + return 0; +} + +static int rockchip_usb2phy_init(struct phy *phy) +{ + struct rockchip_usb2phy_port *rport = phy_get_drvdata(phy); + struct rockchip_usb2phy *rphy = dev_get_drvdata(phy->dev.parent); + int ret = 0; + + mutex_lock(&rport->mutex); + + if (rport->port_id == USB2PHY_PORT_OTG) { + if (rport->mode != USB_DR_MODE_HOST) { + /* clear bvalid status and enable bvalid detect irq */ + ret = property_enable(rphy, + &rport->port_cfg->bvalid_det_clr, + true); + if (ret) + goto out; + + ret = property_enable(rphy, + &rport->port_cfg->bvalid_det_en, + true); + if (ret) + goto out; + + schedule_delayed_work(&rport->otg_sm_work, + OTG_SCHEDULE_DELAY); + } else { + /* If OTG works in host only mode, do nothing. */ + dev_dbg(&rport->phy->dev, "mode %d\n", rport->mode); + } + } else if (rport->port_id == USB2PHY_PORT_HOST) { + /* clear linestate and enable linestate detect irq */ + ret = property_enable(rphy, &rport->port_cfg->ls_det_clr, true); + if (ret) + goto out; + + ret = property_enable(rphy, &rport->port_cfg->ls_det_en, true); + if (ret) + goto out; + + schedule_delayed_work(&rport->sm_work, SCHEDULE_DELAY); + } + +out: + mutex_unlock(&rport->mutex); + return ret; +} + +static int rockchip_usb2phy_power_on(struct phy *phy) +{ + struct rockchip_usb2phy_port *rport = phy_get_drvdata(phy); + struct rockchip_usb2phy *rphy = dev_get_drvdata(phy->dev.parent); + int ret; + + dev_dbg(&rport->phy->dev, "port power on\n"); + + if (!rport->suspended) + return 0; + + ret = clk_prepare_enable(rphy->clk480m); + if (ret) + return ret; + + ret = property_enable(rphy, &rport->port_cfg->phy_sus, false); + if (ret) + return ret; + + rport->suspended = false; + return 0; +} + +static int rockchip_usb2phy_power_off(struct phy *phy) +{ + struct rockchip_usb2phy_port *rport = phy_get_drvdata(phy); + struct rockchip_usb2phy *rphy = dev_get_drvdata(phy->dev.parent); + int ret; + + dev_dbg(&rport->phy->dev, "port power off\n"); + + if (rport->suspended) + return 0; + + ret = property_enable(rphy, &rport->port_cfg->phy_sus, true); + if (ret) + return ret; + + rport->suspended = true; + clk_disable_unprepare(rphy->clk480m); + + return 0; +} + +static int rockchip_usb2phy_exit(struct phy *phy) +{ + struct rockchip_usb2phy_port *rport = phy_get_drvdata(phy); + + if (rport->port_id == USB2PHY_PORT_OTG && + rport->mode != USB_DR_MODE_HOST) { + cancel_delayed_work_sync(&rport->otg_sm_work); + cancel_delayed_work_sync(&rport->chg_work); + } else if (rport->port_id == USB2PHY_PORT_HOST) + cancel_delayed_work_sync(&rport->sm_work); + + return 0; +} + +static const struct phy_ops rockchip_usb2phy_ops = { + .init = rockchip_usb2phy_init, + .exit = rockchip_usb2phy_exit, + .power_on = rockchip_usb2phy_power_on, + .power_off = rockchip_usb2phy_power_off, + .owner = THIS_MODULE, +}; + +static void rockchip_usb2phy_otg_sm_work(struct work_struct *work) +{ + struct rockchip_usb2phy_port *rport = + container_of(work, struct rockchip_usb2phy_port, + otg_sm_work.work); + struct rockchip_usb2phy *rphy = dev_get_drvdata(rport->phy->dev.parent); + static unsigned int cable; + unsigned long delay; + bool vbus_attach, sch_work, notify_charger; + + if (rport->utmi_avalid) + vbus_attach = + property_enabled(rphy, &rport->port_cfg->utmi_avalid); + else + vbus_attach = + property_enabled(rphy, &rport->port_cfg->utmi_bvalid); + + sch_work = false; + notify_charger = false; + delay = OTG_SCHEDULE_DELAY; + dev_dbg(&rport->phy->dev, "%s otg sm work\n", + usb_otg_state_string(rport->state)); + + switch (rport->state) { + case OTG_STATE_UNDEFINED: + rport->state = OTG_STATE_B_IDLE; + if (!vbus_attach) + rockchip_usb2phy_power_off(rport->phy); + /* fall through */ + case OTG_STATE_B_IDLE: + if (extcon_get_cable_state_(rphy->edev, EXTCON_USB_HOST) > 0) { + dev_dbg(&rport->phy->dev, "usb otg host connect\n"); + rport->state = OTG_STATE_A_HOST; + rockchip_usb2phy_power_on(rport->phy); + return; + } else if (vbus_attach) { + dev_dbg(&rport->phy->dev, "vbus_attach\n"); + switch (rphy->chg_state) { + case USB_CHG_STATE_UNDEFINED: + schedule_delayed_work(&rport->chg_work, 0); + return; + case USB_CHG_STATE_DETECTED: + switch (rphy->chg_type) { + case POWER_SUPPLY_TYPE_USB: + dev_dbg(&rport->phy->dev, "sdp cable is connected\n"); + rockchip_usb2phy_power_on(rport->phy); + rport->state = OTG_STATE_B_PERIPHERAL; + notify_charger = true; + sch_work = true; + cable = EXTCON_CHG_USB_SDP; + break; + case POWER_SUPPLY_TYPE_USB_DCP: + dev_dbg(&rport->phy->dev, "dcp cable is connected\n"); + rockchip_usb2phy_power_off(rport->phy); + notify_charger = true; + sch_work = true; + cable = EXTCON_CHG_USB_DCP; + break; + case POWER_SUPPLY_TYPE_USB_CDP: + dev_dbg(&rport->phy->dev, "cdp cable is connected\n"); + rockchip_usb2phy_power_on(rport->phy); + rport->state = OTG_STATE_B_PERIPHERAL; + notify_charger = true; + sch_work = true; + cable = EXTCON_CHG_USB_CDP; + break; + default: + break; + } + break; + default: + break; + } + } else { + notify_charger = true; + rphy->chg_state = USB_CHG_STATE_UNDEFINED; + rphy->chg_type = POWER_SUPPLY_TYPE_UNKNOWN; + } + + if (rport->vbus_attached != vbus_attach) { + rport->vbus_attached = vbus_attach; + + if (notify_charger && rphy->edev) { + extcon_set_cable_state_(rphy->edev, + cable, vbus_attach); + if (cable == EXTCON_CHG_USB_SDP) + extcon_set_state_sync(rphy->edev, + EXTCON_USB, + vbus_attach); + } + } + break; + case OTG_STATE_B_PERIPHERAL: + if (!vbus_attach) { + dev_dbg(&rport->phy->dev, "usb disconnect\n"); + rphy->chg_state = USB_CHG_STATE_UNDEFINED; + rphy->chg_type = POWER_SUPPLY_TYPE_UNKNOWN; + rport->state = OTG_STATE_B_IDLE; + delay = 0; + rockchip_usb2phy_power_off(rport->phy); + } + sch_work = true; + break; + case OTG_STATE_A_HOST: + if (extcon_get_cable_state_(rphy->edev, EXTCON_USB_HOST) == 0) { + dev_dbg(&rport->phy->dev, "usb otg host disconnect\n"); + rport->state = OTG_STATE_B_IDLE; + rockchip_usb2phy_power_off(rport->phy); + } + break; + default: + break; + } + + if (sch_work) + schedule_delayed_work(&rport->otg_sm_work, delay); +} + +static const char *chg_to_string(enum power_supply_type chg_type) +{ + switch (chg_type) { + case POWER_SUPPLY_TYPE_USB: + return "USB_SDP_CHARGER"; + case POWER_SUPPLY_TYPE_USB_DCP: + return "USB_DCP_CHARGER"; + case POWER_SUPPLY_TYPE_USB_CDP: + return "USB_CDP_CHARGER"; + default: + return "INVALID_CHARGER"; + } +} + +static void rockchip_chg_enable_dcd(struct rockchip_usb2phy *rphy, + bool en) +{ + property_enable(rphy, &rphy->phy_cfg->chg_det.rdm_pdwn_en, en); + property_enable(rphy, &rphy->phy_cfg->chg_det.idp_src_en, en); +} + +static void rockchip_chg_enable_primary_det(struct rockchip_usb2phy *rphy, + bool en) +{ + property_enable(rphy, &rphy->phy_cfg->chg_det.vdp_src_en, en); + property_enable(rphy, &rphy->phy_cfg->chg_det.idm_sink_en, en); +} + +static void rockchip_chg_enable_secondary_det(struct rockchip_usb2phy *rphy, + bool en) +{ + property_enable(rphy, &rphy->phy_cfg->chg_det.vdm_src_en, en); + property_enable(rphy, &rphy->phy_cfg->chg_det.idp_sink_en, en); +} + +#define CHG_DCD_POLL_TIME (100 * HZ / 1000) +#define CHG_DCD_MAX_RETRIES 6 +#define CHG_PRIMARY_DET_TIME (40 * HZ / 1000) +#define CHG_SECONDARY_DET_TIME (40 * HZ / 1000) +static void rockchip_chg_detect_work(struct work_struct *work) +{ + struct rockchip_usb2phy_port *rport = + container_of(work, struct rockchip_usb2phy_port, chg_work.work); + struct rockchip_usb2phy *rphy = dev_get_drvdata(rport->phy->dev.parent); + bool is_dcd, tmout, vout; + unsigned long delay; + + dev_dbg(&rport->phy->dev, "chg detection work state = %d\n", + rphy->chg_state); + switch (rphy->chg_state) { + case USB_CHG_STATE_UNDEFINED: + if (!rport->suspended) + rockchip_usb2phy_power_off(rport->phy); + /* put the controller in non-driving mode */ + property_enable(rphy, &rphy->phy_cfg->chg_det.opmode, false); + /* Start DCD processing stage 1 */ + rockchip_chg_enable_dcd(rphy, true); + rphy->chg_state = USB_CHG_STATE_WAIT_FOR_DCD; + rphy->dcd_retries = 0; + delay = CHG_DCD_POLL_TIME; + break; + case USB_CHG_STATE_WAIT_FOR_DCD: + /* get data contact detection status */ + is_dcd = property_enabled(rphy, &rphy->phy_cfg->chg_det.dp_det); + tmout = ++rphy->dcd_retries == CHG_DCD_MAX_RETRIES; + /* stage 2 */ + if (is_dcd || tmout) { + /* stage 4 */ + /* Turn off DCD circuitry */ + rockchip_chg_enable_dcd(rphy, false); + /* Voltage Source on DP, Probe on DM */ + rockchip_chg_enable_primary_det(rphy, true); + delay = CHG_PRIMARY_DET_TIME; + rphy->chg_state = USB_CHG_STATE_DCD_DONE; + } else { + /* stage 3 */ + delay = CHG_DCD_POLL_TIME; + } + break; + case USB_CHG_STATE_DCD_DONE: + vout = property_enabled(rphy, &rphy->phy_cfg->chg_det.cp_det); + rockchip_chg_enable_primary_det(rphy, false); + if (vout) { + /* Voltage Source on DM, Probe on DP */ + rockchip_chg_enable_secondary_det(rphy, true); + delay = CHG_SECONDARY_DET_TIME; + rphy->chg_state = USB_CHG_STATE_PRIMARY_DONE; + } else { + if (rphy->dcd_retries == CHG_DCD_MAX_RETRIES) { + /* floating charger found */ + rphy->chg_type = POWER_SUPPLY_TYPE_USB_DCP; + rphy->chg_state = USB_CHG_STATE_DETECTED; + delay = 0; + } else { + rphy->chg_type = POWER_SUPPLY_TYPE_USB; + rphy->chg_state = USB_CHG_STATE_DETECTED; + delay = 0; + } + } + break; + case USB_CHG_STATE_PRIMARY_DONE: + vout = property_enabled(rphy, &rphy->phy_cfg->chg_det.dcp_det); + /* Turn off voltage source */ + rockchip_chg_enable_secondary_det(rphy, false); + if (vout) + rphy->chg_type = POWER_SUPPLY_TYPE_USB_DCP; + else + rphy->chg_type = POWER_SUPPLY_TYPE_USB_CDP; + /* fall through */ + case USB_CHG_STATE_SECONDARY_DONE: + rphy->chg_state = USB_CHG_STATE_DETECTED; + delay = 0; + /* fall through */ + case USB_CHG_STATE_DETECTED: + /* put the controller in normal mode */ + property_enable(rphy, &rphy->phy_cfg->chg_det.opmode, true); + rockchip_usb2phy_otg_sm_work(&rport->otg_sm_work.work); + dev_info(&rport->phy->dev, "charger = %s\n", + chg_to_string(rphy->chg_type)); + return; + default: + return; + } + + schedule_delayed_work(&rport->chg_work, delay); +} + +/* + * The function manage host-phy port state and suspend/resume phy port + * to save power. + * + * we rely on utmi_linestate and utmi_hostdisconnect to identify whether + * devices is disconnect or not. Besides, we do not need care it is FS/LS + * disconnected or HS disconnected, actually, we just only need get the + * device is disconnected at last through rearm the delayed work, + * to suspend the phy port in _PHY_STATE_DISCONNECT_ case. + * + * NOTE: It may invoke *phy_powr_off or *phy_power_on which will invoke + * some clk related APIs, so do not invoke it from interrupt context directly. + */ +static void rockchip_usb2phy_sm_work(struct work_struct *work) +{ + struct rockchip_usb2phy_port *rport = + container_of(work, struct rockchip_usb2phy_port, sm_work.work); + struct rockchip_usb2phy *rphy = dev_get_drvdata(rport->phy->dev.parent); + unsigned int sh = rport->port_cfg->utmi_hstdet.bitend - + rport->port_cfg->utmi_hstdet.bitstart + 1; + unsigned int ul, uhd, state; + unsigned int ul_mask, uhd_mask; + int ret; + + mutex_lock(&rport->mutex); + + ret = regmap_read(rphy->grf, rport->port_cfg->utmi_ls.offset, &ul); + if (ret < 0) + goto next_schedule; + + ret = regmap_read(rphy->grf, rport->port_cfg->utmi_hstdet.offset, + &uhd); + if (ret < 0) + goto next_schedule; + + uhd_mask = GENMASK(rport->port_cfg->utmi_hstdet.bitend, + rport->port_cfg->utmi_hstdet.bitstart); + ul_mask = GENMASK(rport->port_cfg->utmi_ls.bitend, + rport->port_cfg->utmi_ls.bitstart); + + /* stitch on utmi_ls and utmi_hstdet as phy state */ + state = ((uhd & uhd_mask) >> rport->port_cfg->utmi_hstdet.bitstart) | + (((ul & ul_mask) >> rport->port_cfg->utmi_ls.bitstart) << sh); + + switch (state) { + case PHY_STATE_HS_ONLINE: + dev_dbg(&rport->phy->dev, "HS online\n"); + break; + case PHY_STATE_FS_LS_ONLINE: + /* + * For FS/LS device, the online state share with connect state + * from utmi_ls and utmi_hstdet register, so we distinguish + * them via suspended flag. + * + * Plus, there are two cases, one is D- Line pull-up, and D+ + * line pull-down, the state is 4; another is D+ line pull-up, + * and D- line pull-down, the state is 2. + */ + if (!rport->suspended) { + /* D- line pull-up, D+ line pull-down */ + dev_dbg(&rport->phy->dev, "FS/LS online\n"); + break; + } + /* fall through */ + case PHY_STATE_CONNECT: + if (rport->suspended) { + dev_dbg(&rport->phy->dev, "Connected\n"); + rockchip_usb2phy_power_on(rport->phy); + rport->suspended = false; + } else { + /* D+ line pull-up, D- line pull-down */ + dev_dbg(&rport->phy->dev, "FS/LS online\n"); + } + break; + case PHY_STATE_DISCONNECT: + if (!rport->suspended) { + dev_dbg(&rport->phy->dev, "Disconnected\n"); + rockchip_usb2phy_power_off(rport->phy); + rport->suspended = true; + } + + /* + * activate the linestate detection to get the next device + * plug-in irq. + */ + property_enable(rphy, &rport->port_cfg->ls_det_clr, true); + property_enable(rphy, &rport->port_cfg->ls_det_en, true); + + /* + * we don't need to rearm the delayed work when the phy port + * is suspended. + */ + mutex_unlock(&rport->mutex); + return; + default: + dev_dbg(&rport->phy->dev, "unknown phy state\n"); + break; + } + +next_schedule: + mutex_unlock(&rport->mutex); + schedule_delayed_work(&rport->sm_work, SCHEDULE_DELAY); +} + +static irqreturn_t rockchip_usb2phy_linestate_irq(int irq, void *data) +{ + struct rockchip_usb2phy_port *rport = data; + struct rockchip_usb2phy *rphy = dev_get_drvdata(rport->phy->dev.parent); + + if (!property_enabled(rphy, &rport->port_cfg->ls_det_st)) + return IRQ_NONE; + + mutex_lock(&rport->mutex); + + /* disable linestate detect irq and clear its status */ + property_enable(rphy, &rport->port_cfg->ls_det_en, false); + property_enable(rphy, &rport->port_cfg->ls_det_clr, true); + + mutex_unlock(&rport->mutex); + + /* + * In this case for host phy port, a new device is plugged in, + * meanwhile, if the phy port is suspended, we need rearm the work to + * resume it and mange its states; otherwise, we do nothing about that. + */ + if (rport->suspended && rport->port_id == USB2PHY_PORT_HOST) + rockchip_usb2phy_sm_work(&rport->sm_work.work); + + return IRQ_HANDLED; +} + +static irqreturn_t rockchip_usb2phy_bvalid_irq(int irq, void *data) +{ + struct rockchip_usb2phy_port *rport = data; + struct rockchip_usb2phy *rphy = dev_get_drvdata(rport->phy->dev.parent); + + if (!property_enabled(rphy, &rport->port_cfg->bvalid_det_st)) + return IRQ_NONE; + + mutex_lock(&rport->mutex); + + /* clear bvalid detect irq pending status */ + property_enable(rphy, &rport->port_cfg->bvalid_det_clr, true); + + mutex_unlock(&rport->mutex); + + rockchip_usb2phy_otg_sm_work(&rport->otg_sm_work.work); + + return IRQ_HANDLED; +} + +static int rockchip_usb2phy_host_port_init(struct rockchip_usb2phy *rphy, + struct rockchip_usb2phy_port *rport, + struct device_node *child_np) +{ + int ret; + + rport->port_id = USB2PHY_PORT_HOST; + rport->port_cfg = &rphy->phy_cfg->port_cfgs[USB2PHY_PORT_HOST]; + rport->suspended = true; + + mutex_init(&rport->mutex); + INIT_DELAYED_WORK(&rport->sm_work, rockchip_usb2phy_sm_work); + + rport->ls_irq = of_irq_get_byname(child_np, "linestate"); + if (rport->ls_irq < 0) { + dev_err(rphy->dev, "no linestate irq provided\n"); + return rport->ls_irq; + } + + ret = devm_request_threaded_irq(rphy->dev, rport->ls_irq, NULL, + rockchip_usb2phy_linestate_irq, + IRQF_ONESHOT, + "rockchip_usb2phy", rport); + if (ret) { + dev_err(rphy->dev, "failed to request linestate irq handle\n"); + return ret; + } + + return 0; +} + +static int rockchip_otg_event(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct rockchip_usb2phy_port *rport = + container_of(nb, struct rockchip_usb2phy_port, event_nb); + + schedule_delayed_work(&rport->otg_sm_work, OTG_SCHEDULE_DELAY); + + return NOTIFY_DONE; +} + +static int rockchip_usb2phy_otg_port_init(struct rockchip_usb2phy *rphy, + struct rockchip_usb2phy_port *rport, + struct device_node *child_np) +{ + int ret; + + rport->port_id = USB2PHY_PORT_OTG; + rport->port_cfg = &rphy->phy_cfg->port_cfgs[USB2PHY_PORT_OTG]; + rport->state = OTG_STATE_UNDEFINED; + + /* + * set suspended flag to true, but actually don't + * put phy in suspend mode, it aims to enable usb + * phy and clock in power_on() called by usb controller + * driver during probe. + */ + rport->suspended = true; + rport->vbus_attached = false; + + mutex_init(&rport->mutex); + + rport->mode = of_usb_get_dr_mode_by_phy(child_np, -1); + if (rport->mode == USB_DR_MODE_HOST) { + ret = 0; + goto out; + } + + INIT_DELAYED_WORK(&rport->chg_work, rockchip_chg_detect_work); + INIT_DELAYED_WORK(&rport->otg_sm_work, rockchip_usb2phy_otg_sm_work); + + rport->utmi_avalid = + of_property_read_bool(child_np, "rockchip,utmi-avalid"); + + rport->bvalid_irq = of_irq_get_byname(child_np, "otg-bvalid"); + if (rport->bvalid_irq < 0) { + dev_err(rphy->dev, "no vbus valid irq provided\n"); + ret = rport->bvalid_irq; + goto out; + } + + ret = devm_request_threaded_irq(rphy->dev, rport->bvalid_irq, NULL, + rockchip_usb2phy_bvalid_irq, + IRQF_ONESHOT, + "rockchip_usb2phy_bvalid", rport); + if (ret) { + dev_err(rphy->dev, "failed to request otg-bvalid irq handle\n"); + goto out; + } + + if (!IS_ERR(rphy->edev)) { + rport->event_nb.notifier_call = rockchip_otg_event; + + ret = extcon_register_notifier(rphy->edev, EXTCON_USB_HOST, + &rport->event_nb); + if (ret) + dev_err(rphy->dev, "register USB HOST notifier failed\n"); + } + +out: + return ret; +} + +static int rockchip_usb2phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct device_node *child_np; + struct phy_provider *provider; + struct rockchip_usb2phy *rphy; + const struct rockchip_usb2phy_cfg *phy_cfgs; + const struct of_device_id *match; + unsigned int reg; + int index, ret; + + rphy = devm_kzalloc(dev, sizeof(*rphy), GFP_KERNEL); + if (!rphy) + return -ENOMEM; + + match = of_match_device(dev->driver->of_match_table, dev); + if (!match || !match->data) { + dev_err(dev, "phy configs are not assigned!\n"); + return -EINVAL; + } + + if (!dev->parent || !dev->parent->of_node) + return -EINVAL; + + rphy->grf = syscon_node_to_regmap(dev->parent->of_node); + if (IS_ERR(rphy->grf)) + return PTR_ERR(rphy->grf); + + if (of_property_read_u32(np, "reg", ®)) { + dev_err(dev, "the reg property is not assigned in %s node\n", + np->name); + return -EINVAL; + } + + rphy->dev = dev; + phy_cfgs = match->data; + rphy->chg_state = USB_CHG_STATE_UNDEFINED; + rphy->chg_type = POWER_SUPPLY_TYPE_UNKNOWN; + platform_set_drvdata(pdev, rphy); + + ret = rockchip_usb2phy_extcon_register(rphy); + if (ret) + return ret; + + /* find out a proper config which can be matched with dt. */ + index = 0; + while (phy_cfgs[index].reg) { + if (phy_cfgs[index].reg == reg) { + rphy->phy_cfg = &phy_cfgs[index]; + break; + } + + ++index; + } + + if (!rphy->phy_cfg) { + dev_err(dev, "no phy-config can be matched with %s node\n", + np->name); + return -EINVAL; + } + + rphy->clk = of_clk_get_by_name(np, "phyclk"); + if (!IS_ERR(rphy->clk)) { + clk_prepare_enable(rphy->clk); + } else { + dev_info(&pdev->dev, "no phyclk specified\n"); + rphy->clk = NULL; + } + + ret = rockchip_usb2phy_clk480m_register(rphy); + if (ret) { + dev_err(dev, "failed to register 480m output clock\n"); + goto disable_clks; + } + + index = 0; + for_each_available_child_of_node(np, child_np) { + struct rockchip_usb2phy_port *rport = &rphy->ports[index]; + struct phy *phy; + + /* This driver aims to support both otg-port and host-port */ + if (of_node_cmp(child_np->name, "host-port") && + of_node_cmp(child_np->name, "otg-port")) + goto next_child; + + phy = devm_phy_create(dev, child_np, &rockchip_usb2phy_ops); + if (IS_ERR(phy)) { + dev_err(dev, "failed to create phy\n"); + ret = PTR_ERR(phy); + goto put_child; + } + + rport->phy = phy; + phy_set_drvdata(rport->phy, rport); + + /* initialize otg/host port separately */ + if (!of_node_cmp(child_np->name, "host-port")) { + ret = rockchip_usb2phy_host_port_init(rphy, rport, + child_np); + if (ret) + goto put_child; + } else { + ret = rockchip_usb2phy_otg_port_init(rphy, rport, + child_np); + if (ret) + goto put_child; + } + +next_child: + /* to prevent out of boundary */ + if (++index >= rphy->phy_cfg->num_ports) + break; + } + + provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + return PTR_ERR_OR_ZERO(provider); + +put_child: + of_node_put(child_np); +disable_clks: + if (rphy->clk) { + clk_disable_unprepare(rphy->clk); + clk_put(rphy->clk); + } + return ret; +} + +static const struct rockchip_usb2phy_cfg rk3328_phy_cfgs[] = { + { + .reg = 0x100, + .num_ports = 2, + .clkout_ctl = { 0x108, 4, 4, 1, 0 }, + .port_cfgs = { + [USB2PHY_PORT_OTG] = { + .phy_sus = { 0x0100, 15, 0, 0, 0x1d1 }, + .bvalid_det_en = { 0x0110, 2, 2, 0, 1 }, + .bvalid_det_st = { 0x0114, 2, 2, 0, 1 }, + .bvalid_det_clr = { 0x0118, 2, 2, 0, 1 }, + .ls_det_en = { 0x0110, 0, 0, 0, 1 }, + .ls_det_st = { 0x0114, 0, 0, 0, 1 }, + .ls_det_clr = { 0x0118, 0, 0, 0, 1 }, + .utmi_avalid = { 0x0120, 10, 10, 0, 1 }, + .utmi_bvalid = { 0x0120, 9, 9, 0, 1 }, + .utmi_ls = { 0x0120, 5, 4, 0, 1 }, + }, + [USB2PHY_PORT_HOST] = { + .phy_sus = { 0x104, 15, 0, 0, 0x1d1 }, + .ls_det_en = { 0x110, 1, 1, 0, 1 }, + .ls_det_st = { 0x114, 1, 1, 0, 1 }, + .ls_det_clr = { 0x118, 1, 1, 0, 1 }, + .utmi_ls = { 0x120, 17, 16, 0, 1 }, + .utmi_hstdet = { 0x120, 19, 19, 0, 1 } + } + }, + .chg_det = { + .opmode = { 0x0100, 3, 0, 5, 1 }, + .cp_det = { 0x0120, 24, 24, 0, 1 }, + .dcp_det = { 0x0120, 23, 23, 0, 1 }, + .dp_det = { 0x0120, 25, 25, 0, 1 }, + .idm_sink_en = { 0x0108, 8, 8, 0, 1 }, + .idp_sink_en = { 0x0108, 7, 7, 0, 1 }, + .idp_src_en = { 0x0108, 9, 9, 0, 1 }, + .rdm_pdwn_en = { 0x0108, 10, 10, 0, 1 }, + .vdm_src_en = { 0x0108, 12, 12, 0, 1 }, + .vdp_src_en = { 0x0108, 11, 11, 0, 1 }, + }, + }, + { /* sentinel */ } +}; + +static const struct rockchip_usb2phy_cfg rk3366_phy_cfgs[] = { + { + .reg = 0x700, + .num_ports = 2, + .clkout_ctl = { 0x0724, 15, 15, 1, 0 }, + .port_cfgs = { + [USB2PHY_PORT_HOST] = { + .phy_sus = { 0x0728, 15, 0, 0, 0x1d1 }, + .ls_det_en = { 0x0680, 4, 4, 0, 1 }, + .ls_det_st = { 0x0690, 4, 4, 0, 1 }, + .ls_det_clr = { 0x06a0, 4, 4, 0, 1 }, + .utmi_ls = { 0x049c, 14, 13, 0, 1 }, + .utmi_hstdet = { 0x049c, 12, 12, 0, 1 } + } + }, + }, + { /* sentinel */ } +}; + +static const struct rockchip_usb2phy_cfg rk3399_phy_cfgs[] = { + { + .reg = 0xe450, + .num_ports = 2, + .clkout_ctl = { 0xe450, 4, 4, 1, 0 }, + .port_cfgs = { + [USB2PHY_PORT_OTG] = { + .phy_sus = { 0xe454, 1, 0, 2, 1 }, + .bvalid_det_en = { 0xe3c0, 3, 3, 0, 1 }, + .bvalid_det_st = { 0xe3e0, 3, 3, 0, 1 }, + .bvalid_det_clr = { 0xe3d0, 3, 3, 0, 1 }, + .utmi_avalid = { 0xe2ac, 7, 7, 0, 1 }, + .utmi_bvalid = { 0xe2ac, 12, 12, 0, 1 }, + }, + [USB2PHY_PORT_HOST] = { + .phy_sus = { 0xe458, 1, 0, 0x2, 0x1 }, + .ls_det_en = { 0xe3c0, 6, 6, 0, 1 }, + .ls_det_st = { 0xe3e0, 6, 6, 0, 1 }, + .ls_det_clr = { 0xe3d0, 6, 6, 0, 1 }, + .utmi_ls = { 0xe2ac, 22, 21, 0, 1 }, + .utmi_hstdet = { 0xe2ac, 23, 23, 0, 1 } + } + }, + .chg_det = { + .opmode = { 0xe454, 3, 0, 5, 1 }, + .cp_det = { 0xe2ac, 2, 2, 0, 1 }, + .dcp_det = { 0xe2ac, 1, 1, 0, 1 }, + .dp_det = { 0xe2ac, 0, 0, 0, 1 }, + .idm_sink_en = { 0xe450, 8, 8, 0, 1 }, + .idp_sink_en = { 0xe450, 7, 7, 0, 1 }, + .idp_src_en = { 0xe450, 9, 9, 0, 1 }, + .rdm_pdwn_en = { 0xe450, 10, 10, 0, 1 }, + .vdm_src_en = { 0xe450, 12, 12, 0, 1 }, + .vdp_src_en = { 0xe450, 11, 11, 0, 1 }, + }, + }, + { + .reg = 0xe460, + .num_ports = 2, + .clkout_ctl = { 0xe460, 4, 4, 1, 0 }, + .port_cfgs = { + [USB2PHY_PORT_OTG] = { + .phy_sus = { 0xe464, 1, 0, 2, 1 }, + .bvalid_det_en = { 0xe3c0, 8, 8, 0, 1 }, + .bvalid_det_st = { 0xe3e0, 8, 8, 0, 1 }, + .bvalid_det_clr = { 0xe3d0, 8, 8, 0, 1 }, + .utmi_avalid = { 0xe2ac, 10, 10, 0, 1 }, + .utmi_bvalid = { 0xe2ac, 16, 16, 0, 1 }, + }, + [USB2PHY_PORT_HOST] = { + .phy_sus = { 0xe468, 1, 0, 0x2, 0x1 }, + .ls_det_en = { 0xe3c0, 11, 11, 0, 1 }, + .ls_det_st = { 0xe3e0, 11, 11, 0, 1 }, + .ls_det_clr = { 0xe3d0, 11, 11, 0, 1 }, + .utmi_ls = { 0xe2ac, 26, 25, 0, 1 }, + .utmi_hstdet = { 0xe2ac, 27, 27, 0, 1 } + } + }, + }, + { /* sentinel */ } +}; + +static const struct of_device_id rockchip_usb2phy_dt_match[] = { + { .compatible = "rockchip,rk3328-usb2phy", .data = &rk3328_phy_cfgs }, + { .compatible = "rockchip,rk3366-usb2phy", .data = &rk3366_phy_cfgs }, + { .compatible = "rockchip,rk3399-usb2phy", .data = &rk3399_phy_cfgs }, + {} +}; +MODULE_DEVICE_TABLE(of, rockchip_usb2phy_dt_match); + +static struct platform_driver rockchip_usb2phy_driver = { + .probe = rockchip_usb2phy_probe, + .driver = { + .name = "rockchip-usb2phy", + .of_match_table = rockchip_usb2phy_dt_match, + }, +}; +module_platform_driver(rockchip_usb2phy_driver); + +MODULE_AUTHOR("Frank Wang "); +MODULE_DESCRIPTION("Rockchip USB2.0 PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/rockchip/phy-rockchip-pcie.c b/drivers/phy/rockchip/phy-rockchip-pcie.c new file mode 100644 index 000000000000..6904633cad68 --- /dev/null +++ b/drivers/phy/rockchip/phy-rockchip-pcie.c @@ -0,0 +1,346 @@ +/* + * Rockchip PCIe PHY driver + * + * Copyright (C) 2016 Shawn Lin + * Copyright (C) 2016 ROCKCHIP, Inc. + * + * 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; either 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * The higher 16-bit of this register is used for write protection + * only if BIT(x + 16) set to 1 the BIT(x) can be written. + */ +#define HIWORD_UPDATE(val, mask, shift) \ + ((val) << (shift) | (mask) << ((shift) + 16)) + +#define PHY_MAX_LANE_NUM 4 +#define PHY_CFG_DATA_SHIFT 7 +#define PHY_CFG_ADDR_SHIFT 1 +#define PHY_CFG_DATA_MASK 0xf +#define PHY_CFG_ADDR_MASK 0x3f +#define PHY_CFG_RD_MASK 0x3ff +#define PHY_CFG_WR_ENABLE 1 +#define PHY_CFG_WR_DISABLE 1 +#define PHY_CFG_WR_SHIFT 0 +#define PHY_CFG_WR_MASK 1 +#define PHY_CFG_PLL_LOCK 0x10 +#define PHY_CFG_CLK_TEST 0x10 +#define PHY_CFG_CLK_SCC 0x12 +#define PHY_CFG_SEPE_RATE BIT(3) +#define PHY_CFG_PLL_100M BIT(3) +#define PHY_PLL_LOCKED BIT(9) +#define PHY_PLL_OUTPUT BIT(10) +#define PHY_LANE_A_STATUS 0x30 +#define PHY_LANE_B_STATUS 0x31 +#define PHY_LANE_C_STATUS 0x32 +#define PHY_LANE_D_STATUS 0x33 +#define PHY_LANE_RX_DET_SHIFT 11 +#define PHY_LANE_RX_DET_TH 0x1 +#define PHY_LANE_IDLE_OFF 0x1 +#define PHY_LANE_IDLE_MASK 0x1 +#define PHY_LANE_IDLE_A_SHIFT 3 +#define PHY_LANE_IDLE_B_SHIFT 4 +#define PHY_LANE_IDLE_C_SHIFT 5 +#define PHY_LANE_IDLE_D_SHIFT 6 + +struct rockchip_pcie_data { + unsigned int pcie_conf; + unsigned int pcie_status; + unsigned int pcie_laneoff; +}; + +struct rockchip_pcie_phy { + struct rockchip_pcie_data *phy_data; + struct regmap *reg_base; + struct reset_control *phy_rst; + struct clk *clk_pciephy_ref; +}; + +static inline void phy_wr_cfg(struct rockchip_pcie_phy *rk_phy, + u32 addr, u32 data) +{ + regmap_write(rk_phy->reg_base, rk_phy->phy_data->pcie_conf, + HIWORD_UPDATE(data, + PHY_CFG_DATA_MASK, + PHY_CFG_DATA_SHIFT) | + HIWORD_UPDATE(addr, + PHY_CFG_ADDR_MASK, + PHY_CFG_ADDR_SHIFT)); + udelay(1); + regmap_write(rk_phy->reg_base, rk_phy->phy_data->pcie_conf, + HIWORD_UPDATE(PHY_CFG_WR_ENABLE, + PHY_CFG_WR_MASK, + PHY_CFG_WR_SHIFT)); + udelay(1); + regmap_write(rk_phy->reg_base, rk_phy->phy_data->pcie_conf, + HIWORD_UPDATE(PHY_CFG_WR_DISABLE, + PHY_CFG_WR_MASK, + PHY_CFG_WR_SHIFT)); +} + +static inline u32 phy_rd_cfg(struct rockchip_pcie_phy *rk_phy, + u32 addr) +{ + u32 val; + + regmap_write(rk_phy->reg_base, rk_phy->phy_data->pcie_conf, + HIWORD_UPDATE(addr, + PHY_CFG_RD_MASK, + PHY_CFG_ADDR_SHIFT)); + regmap_read(rk_phy->reg_base, + rk_phy->phy_data->pcie_status, + &val); + return val; +} + +static int rockchip_pcie_phy_power_off(struct phy *phy) +{ + struct rockchip_pcie_phy *rk_phy = phy_get_drvdata(phy); + int err = 0; + + err = reset_control_assert(rk_phy->phy_rst); + if (err) { + dev_err(&phy->dev, "assert phy_rst err %d\n", err); + return err; + } + + return 0; +} + +static int rockchip_pcie_phy_power_on(struct phy *phy) +{ + struct rockchip_pcie_phy *rk_phy = phy_get_drvdata(phy); + int err = 0; + u32 status; + unsigned long timeout; + + err = reset_control_deassert(rk_phy->phy_rst); + if (err) { + dev_err(&phy->dev, "deassert phy_rst err %d\n", err); + return err; + } + + regmap_write(rk_phy->reg_base, rk_phy->phy_data->pcie_conf, + HIWORD_UPDATE(PHY_CFG_PLL_LOCK, + PHY_CFG_ADDR_MASK, + PHY_CFG_ADDR_SHIFT)); + + /* + * No documented timeout value for phy operation below, + * so we make it large enough here. And we use loop-break + * method which should not be harmful. + */ + timeout = jiffies + msecs_to_jiffies(1000); + + err = -EINVAL; + while (time_before(jiffies, timeout)) { + regmap_read(rk_phy->reg_base, + rk_phy->phy_data->pcie_status, + &status); + if (status & PHY_PLL_LOCKED) { + dev_dbg(&phy->dev, "pll locked!\n"); + err = 0; + break; + } + msleep(20); + } + + if (err) { + dev_err(&phy->dev, "pll lock timeout!\n"); + goto err_pll_lock; + } + + phy_wr_cfg(rk_phy, PHY_CFG_CLK_TEST, PHY_CFG_SEPE_RATE); + phy_wr_cfg(rk_phy, PHY_CFG_CLK_SCC, PHY_CFG_PLL_100M); + + err = -ETIMEDOUT; + while (time_before(jiffies, timeout)) { + regmap_read(rk_phy->reg_base, + rk_phy->phy_data->pcie_status, + &status); + if (!(status & PHY_PLL_OUTPUT)) { + dev_dbg(&phy->dev, "pll output enable done!\n"); + err = 0; + break; + } + msleep(20); + } + + if (err) { + dev_err(&phy->dev, "pll output enable timeout!\n"); + goto err_pll_lock; + } + + regmap_write(rk_phy->reg_base, rk_phy->phy_data->pcie_conf, + HIWORD_UPDATE(PHY_CFG_PLL_LOCK, + PHY_CFG_ADDR_MASK, + PHY_CFG_ADDR_SHIFT)); + err = -EINVAL; + while (time_before(jiffies, timeout)) { + regmap_read(rk_phy->reg_base, + rk_phy->phy_data->pcie_status, + &status); + if (status & PHY_PLL_LOCKED) { + dev_dbg(&phy->dev, "pll relocked!\n"); + err = 0; + break; + } + msleep(20); + } + + if (err) { + dev_err(&phy->dev, "pll relock timeout!\n"); + goto err_pll_lock; + } + + return 0; + +err_pll_lock: + reset_control_assert(rk_phy->phy_rst); + return err; +} + +static int rockchip_pcie_phy_init(struct phy *phy) +{ + struct rockchip_pcie_phy *rk_phy = phy_get_drvdata(phy); + int err = 0; + + err = clk_prepare_enable(rk_phy->clk_pciephy_ref); + if (err) { + dev_err(&phy->dev, "Fail to enable pcie ref clock.\n"); + goto err_refclk; + } + + err = reset_control_assert(rk_phy->phy_rst); + if (err) { + dev_err(&phy->dev, "assert phy_rst err %d\n", err); + goto err_reset; + } + + return err; + +err_reset: + clk_disable_unprepare(rk_phy->clk_pciephy_ref); +err_refclk: + return err; +} + +static int rockchip_pcie_phy_exit(struct phy *phy) +{ + struct rockchip_pcie_phy *rk_phy = phy_get_drvdata(phy); + + clk_disable_unprepare(rk_phy->clk_pciephy_ref); + + return 0; +} + +static const struct phy_ops ops = { + .init = rockchip_pcie_phy_init, + .exit = rockchip_pcie_phy_exit, + .power_on = rockchip_pcie_phy_power_on, + .power_off = rockchip_pcie_phy_power_off, + .owner = THIS_MODULE, +}; + +static const struct rockchip_pcie_data rk3399_pcie_data = { + .pcie_conf = 0xe220, + .pcie_status = 0xe2a4, + .pcie_laneoff = 0xe214, +}; + +static const struct of_device_id rockchip_pcie_phy_dt_ids[] = { + { + .compatible = "rockchip,rk3399-pcie-phy", + .data = &rk3399_pcie_data, + }, + {} +}; + +MODULE_DEVICE_TABLE(of, rockchip_pcie_phy_dt_ids); + +static int rockchip_pcie_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rockchip_pcie_phy *rk_phy; + struct phy *generic_phy; + struct phy_provider *phy_provider; + struct regmap *grf; + const struct of_device_id *of_id; + + grf = syscon_node_to_regmap(dev->parent->of_node); + if (IS_ERR(grf)) { + dev_err(dev, "Cannot find GRF syscon\n"); + return PTR_ERR(grf); + } + + rk_phy = devm_kzalloc(dev, sizeof(*rk_phy), GFP_KERNEL); + if (!rk_phy) + return -ENOMEM; + + of_id = of_match_device(rockchip_pcie_phy_dt_ids, &pdev->dev); + if (!of_id) + return -EINVAL; + + rk_phy->phy_data = (struct rockchip_pcie_data *)of_id->data; + rk_phy->reg_base = grf; + + rk_phy->phy_rst = devm_reset_control_get(dev, "phy"); + if (IS_ERR(rk_phy->phy_rst)) { + if (PTR_ERR(rk_phy->phy_rst) != -EPROBE_DEFER) + dev_err(dev, + "missing phy property for reset controller\n"); + return PTR_ERR(rk_phy->phy_rst); + } + + rk_phy->clk_pciephy_ref = devm_clk_get(dev, "refclk"); + if (IS_ERR(rk_phy->clk_pciephy_ref)) { + dev_err(dev, "refclk not found.\n"); + return PTR_ERR(rk_phy->clk_pciephy_ref); + } + + generic_phy = devm_phy_create(dev, dev->of_node, &ops); + if (IS_ERR(generic_phy)) { + dev_err(dev, "failed to create PHY\n"); + return PTR_ERR(generic_phy); + } + + phy_set_drvdata(generic_phy, rk_phy); + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static struct platform_driver rockchip_pcie_driver = { + .probe = rockchip_pcie_phy_probe, + .driver = { + .name = "rockchip-pcie-phy", + .of_match_table = rockchip_pcie_phy_dt_ids, + }, +}; + +module_platform_driver(rockchip_pcie_driver); + +MODULE_AUTHOR("Shawn Lin "); +MODULE_DESCRIPTION("Rockchip PCIe PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/rockchip/phy-rockchip-typec.c b/drivers/phy/rockchip/phy-rockchip-typec.c new file mode 100644 index 000000000000..7cfb0f8995de --- /dev/null +++ b/drivers/phy/rockchip/phy-rockchip-typec.c @@ -0,0 +1,1023 @@ +/* + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd + * Author: Chris Zhong + * Kever Yang + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + * The ROCKCHIP Type-C PHY has two PLL clocks. The first PLL clock + * is used for USB3, the second PLL clock is used for DP. This Type-C PHY has + * 3 working modes: USB3 only mode, DP only mode, and USB3+DP mode. + * At USB3 only mode, both PLL clocks need to be initialized, this allows the + * PHY to switch mode between USB3 and USB3+DP, without disconnecting the USB + * device. + * In The DP only mode, only the DP PLL needs to be powered on, and the 4 lanes + * are all used for DP. + * + * This driver gets extcon cable state and property, then decides which mode to + * select: + * + * 1. USB3 only mode: + * EXTCON_USB or EXTCON_USB_HOST state is true, and + * EXTCON_PROP_USB_SS property is true. + * EXTCON_DISP_DP state is false. + * + * 2. DP only mode: + * EXTCON_DISP_DP state is true, and + * EXTCON_PROP_USB_SS property is false. + * If EXTCON_USB_HOST state is true, it is DP + USB2 mode, since the USB2 phy + * is a separate phy, so this case is still DP only mode. + * + * 3. USB3+DP mode: + * EXTCON_USB_HOST and EXTCON_DISP_DP are both true, and + * EXTCON_PROP_USB_SS property is true. + * + * This Type-C PHY driver supports normal and flip orientation. The orientation + * is reported by the EXTCON_PROP_USB_TYPEC_POLARITY property: true is flip + * orientation, false is normal orientation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define CMN_SSM_BANDGAP (0x21 << 2) +#define CMN_SSM_BIAS (0x22 << 2) +#define CMN_PLLSM0_PLLEN (0x29 << 2) +#define CMN_PLLSM0_PLLPRE (0x2a << 2) +#define CMN_PLLSM0_PLLVREF (0x2b << 2) +#define CMN_PLLSM0_PLLLOCK (0x2c << 2) +#define CMN_PLLSM1_PLLEN (0x31 << 2) +#define CMN_PLLSM1_PLLPRE (0x32 << 2) +#define CMN_PLLSM1_PLLVREF (0x33 << 2) +#define CMN_PLLSM1_PLLLOCK (0x34 << 2) +#define CMN_PLLSM1_USER_DEF_CTRL (0x37 << 2) +#define CMN_ICAL_OVRD (0xc1 << 2) +#define CMN_PLL0_VCOCAL_OVRD (0x83 << 2) +#define CMN_PLL0_VCOCAL_INIT (0x84 << 2) +#define CMN_PLL0_VCOCAL_ITER (0x85 << 2) +#define CMN_PLL0_LOCK_REFCNT_START (0x90 << 2) +#define CMN_PLL0_LOCK_PLLCNT_START (0x92 << 2) +#define CMN_PLL0_LOCK_PLLCNT_THR (0x93 << 2) +#define CMN_PLL0_INTDIV (0x94 << 2) +#define CMN_PLL0_FRACDIV (0x95 << 2) +#define CMN_PLL0_HIGH_THR (0x96 << 2) +#define CMN_PLL0_DSM_DIAG (0x97 << 2) +#define CMN_PLL0_SS_CTRL1 (0x98 << 2) +#define CMN_PLL0_SS_CTRL2 (0x99 << 2) +#define CMN_PLL1_VCOCAL_START (0xa1 << 2) +#define CMN_PLL1_VCOCAL_OVRD (0xa3 << 2) +#define CMN_PLL1_VCOCAL_INIT (0xa4 << 2) +#define CMN_PLL1_VCOCAL_ITER (0xa5 << 2) +#define CMN_PLL1_LOCK_REFCNT_START (0xb0 << 2) +#define CMN_PLL1_LOCK_PLLCNT_START (0xb2 << 2) +#define CMN_PLL1_LOCK_PLLCNT_THR (0xb3 << 2) +#define CMN_PLL1_INTDIV (0xb4 << 2) +#define CMN_PLL1_FRACDIV (0xb5 << 2) +#define CMN_PLL1_HIGH_THR (0xb6 << 2) +#define CMN_PLL1_DSM_DIAG (0xb7 << 2) +#define CMN_PLL1_SS_CTRL1 (0xb8 << 2) +#define CMN_PLL1_SS_CTRL2 (0xb9 << 2) +#define CMN_RXCAL_OVRD (0xd1 << 2) +#define CMN_TXPUCAL_CTRL (0xe0 << 2) +#define CMN_TXPUCAL_OVRD (0xe1 << 2) +#define CMN_TXPDCAL_OVRD (0xf1 << 2) +#define CMN_DIAG_PLL0_FBH_OVRD (0x1c0 << 2) +#define CMN_DIAG_PLL0_FBL_OVRD (0x1c1 << 2) +#define CMN_DIAG_PLL0_OVRD (0x1c2 << 2) +#define CMN_DIAG_PLL0_V2I_TUNE (0x1c5 << 2) +#define CMN_DIAG_PLL0_CP_TUNE (0x1c6 << 2) +#define CMN_DIAG_PLL0_LF_PROG (0x1c7 << 2) +#define CMN_DIAG_PLL1_FBH_OVRD (0x1d0 << 2) +#define CMN_DIAG_PLL1_FBL_OVRD (0x1d1 << 2) +#define CMN_DIAG_PLL1_OVRD (0x1d2 << 2) +#define CMN_DIAG_PLL1_V2I_TUNE (0x1d5 << 2) +#define CMN_DIAG_PLL1_CP_TUNE (0x1d6 << 2) +#define CMN_DIAG_PLL1_LF_PROG (0x1d7 << 2) +#define CMN_DIAG_PLL1_PTATIS_TUNE1 (0x1d8 << 2) +#define CMN_DIAG_PLL1_PTATIS_TUNE2 (0x1d9 << 2) +#define CMN_DIAG_PLL1_INCLK_CTRL (0x1da << 2) +#define CMN_DIAG_HSCLK_SEL (0x1e0 << 2) + +#define XCVR_PSM_RCTRL(n) ((0x4001 | ((n) << 9)) << 2) +#define XCVR_PSM_CAL_TMR(n) ((0x4002 | ((n) << 9)) << 2) +#define XCVR_PSM_A0IN_TMR(n) ((0x4003 | ((n) << 9)) << 2) +#define TX_TXCC_CAL_SCLR_MULT(n) ((0x4047 | ((n) << 9)) << 2) +#define TX_TXCC_CPOST_MULT_00(n) ((0x404c | ((n) << 9)) << 2) +#define TX_TXCC_CPOST_MULT_01(n) ((0x404d | ((n) << 9)) << 2) +#define TX_TXCC_CPOST_MULT_10(n) ((0x404e | ((n) << 9)) << 2) +#define TX_TXCC_CPOST_MULT_11(n) ((0x404f | ((n) << 9)) << 2) +#define TX_TXCC_MGNFS_MULT_000(n) ((0x4050 | ((n) << 9)) << 2) +#define TX_TXCC_MGNFS_MULT_001(n) ((0x4051 | ((n) << 9)) << 2) +#define TX_TXCC_MGNFS_MULT_010(n) ((0x4052 | ((n) << 9)) << 2) +#define TX_TXCC_MGNFS_MULT_011(n) ((0x4053 | ((n) << 9)) << 2) +#define TX_TXCC_MGNFS_MULT_100(n) ((0x4054 | ((n) << 9)) << 2) +#define TX_TXCC_MGNFS_MULT_101(n) ((0x4055 | ((n) << 9)) << 2) +#define TX_TXCC_MGNFS_MULT_110(n) ((0x4056 | ((n) << 9)) << 2) +#define TX_TXCC_MGNFS_MULT_111(n) ((0x4057 | ((n) << 9)) << 2) +#define XCVR_DIAG_PLLDRC_CTRL(n) ((0x40e0 | ((n) << 9)) << 2) +#define XCVR_DIAG_BIDI_CTRL(n) ((0x40e8 | ((n) << 9)) << 2) +#define XCVR_DIAG_LANE_FCM_EN_MGN(n) ((0x40f2 | ((n) << 9)) << 2) +#define TX_PSC_A0(n) ((0x4100 | ((n) << 9)) << 2) +#define TX_PSC_A1(n) ((0x4101 | ((n) << 9)) << 2) +#define TX_PSC_A2(n) ((0x4102 | ((n) << 9)) << 2) +#define TX_PSC_A3(n) ((0x4103 | ((n) << 9)) << 2) +#define TX_RCVDET_CTRL(n) ((0x4120 | ((n) << 9)) << 2) +#define TX_RCVDET_EN_TMR(n) ((0x4122 | ((n) << 9)) << 2) +#define TX_RCVDET_ST_TMR(n) ((0x4123 | ((n) << 9)) << 2) +#define TX_DIAG_TX_DRV(n) ((0x41e1 | ((n) << 9)) << 2) +#define TX_DIAG_BGREF_PREDRV_DELAY (0x41e7 << 2) +#define TX_ANA_CTRL_REG_1 (0x5020 << 2) +#define TX_ANA_CTRL_REG_2 (0x5021 << 2) +#define TXDA_COEFF_CALC_CTRL (0x5022 << 2) +#define TX_DIG_CTRL_REG_2 (0x5024 << 2) +#define TXDA_CYA_AUXDA_CYA (0x5025 << 2) +#define TX_ANA_CTRL_REG_3 (0x5026 << 2) +#define TX_ANA_CTRL_REG_4 (0x5027 << 2) +#define TX_ANA_CTRL_REG_5 (0x5029 << 2) + +#define RX_PSC_A0(n) ((0x8000 | ((n) << 9)) << 2) +#define RX_PSC_A1(n) ((0x8001 | ((n) << 9)) << 2) +#define RX_PSC_A2(n) ((0x8002 | ((n) << 9)) << 2) +#define RX_PSC_A3(n) ((0x8003 | ((n) << 9)) << 2) +#define RX_PSC_CAL(n) ((0x8006 | ((n) << 9)) << 2) +#define RX_PSC_RDY(n) ((0x8007 | ((n) << 9)) << 2) +#define RX_IQPI_ILL_CAL_OVRD (0x8023 << 2) +#define RX_EPI_ILL_CAL_OVRD (0x8033 << 2) +#define RX_SDCAL0_OVRD (0x8041 << 2) +#define RX_SDCAL1_OVRD (0x8049 << 2) +#define RX_SLC_INIT (0x806d << 2) +#define RX_SLC_RUN (0x806e << 2) +#define RX_CDRLF_CNFG2 (0x8081 << 2) +#define RX_SIGDET_HL_FILT_TMR(n) ((0x8090 | ((n) << 9)) << 2) +#define RX_SLC_IOP0_OVRD (0x8101 << 2) +#define RX_SLC_IOP1_OVRD (0x8105 << 2) +#define RX_SLC_QOP0_OVRD (0x8109 << 2) +#define RX_SLC_QOP1_OVRD (0x810d << 2) +#define RX_SLC_EOP0_OVRD (0x8111 << 2) +#define RX_SLC_EOP1_OVRD (0x8115 << 2) +#define RX_SLC_ION0_OVRD (0x8119 << 2) +#define RX_SLC_ION1_OVRD (0x811d << 2) +#define RX_SLC_QON0_OVRD (0x8121 << 2) +#define RX_SLC_QON1_OVRD (0x8125 << 2) +#define RX_SLC_EON0_OVRD (0x8129 << 2) +#define RX_SLC_EON1_OVRD (0x812d << 2) +#define RX_SLC_IEP0_OVRD (0x8131 << 2) +#define RX_SLC_IEP1_OVRD (0x8135 << 2) +#define RX_SLC_QEP0_OVRD (0x8139 << 2) +#define RX_SLC_QEP1_OVRD (0x813d << 2) +#define RX_SLC_EEP0_OVRD (0x8141 << 2) +#define RX_SLC_EEP1_OVRD (0x8145 << 2) +#define RX_SLC_IEN0_OVRD (0x8149 << 2) +#define RX_SLC_IEN1_OVRD (0x814d << 2) +#define RX_SLC_QEN0_OVRD (0x8151 << 2) +#define RX_SLC_QEN1_OVRD (0x8155 << 2) +#define RX_SLC_EEN0_OVRD (0x8159 << 2) +#define RX_SLC_EEN1_OVRD (0x815d << 2) +#define RX_REE_CTRL_DATA_MASK(n) ((0x81bb | ((n) << 9)) << 2) +#define RX_DIAG_SIGDET_TUNE(n) ((0x81dc | ((n) << 9)) << 2) +#define RX_DIAG_SC2C_DELAY (0x81e1 << 2) + +#define PMA_LANE_CFG (0xc000 << 2) +#define PIPE_CMN_CTRL1 (0xc001 << 2) +#define PIPE_CMN_CTRL2 (0xc002 << 2) +#define PIPE_COM_LOCK_CFG1 (0xc003 << 2) +#define PIPE_COM_LOCK_CFG2 (0xc004 << 2) +#define PIPE_RCV_DET_INH (0xc005 << 2) +#define DP_MODE_CTL (0xc008 << 2) +#define DP_CLK_CTL (0xc009 << 2) +#define STS (0xc00F << 2) +#define PHY_ISO_CMN_CTRL (0xc010 << 2) +#define PHY_DP_TX_CTL (0xc408 << 2) +#define PMA_CMN_CTRL1 (0xc800 << 2) +#define PHY_PMA_ISO_CMN_CTRL (0xc810 << 2) +#define PHY_ISOLATION_CTRL (0xc81f << 2) +#define PHY_PMA_ISO_XCVR_CTRL(n) ((0xcc11 | ((n) << 6)) << 2) +#define PHY_PMA_ISO_LINK_MODE(n) ((0xcc12 | ((n) << 6)) << 2) +#define PHY_PMA_ISO_PWRST_CTRL(n) ((0xcc13 | ((n) << 6)) << 2) +#define PHY_PMA_ISO_TX_DATA_LO(n) ((0xcc14 | ((n) << 6)) << 2) +#define PHY_PMA_ISO_TX_DATA_HI(n) ((0xcc15 | ((n) << 6)) << 2) +#define PHY_PMA_ISO_RX_DATA_LO(n) ((0xcc16 | ((n) << 6)) << 2) +#define PHY_PMA_ISO_RX_DATA_HI(n) ((0xcc17 | ((n) << 6)) << 2) +#define TX_BIST_CTRL(n) ((0x4140 | ((n) << 9)) << 2) +#define TX_BIST_UDDWR(n) ((0x4141 | ((n) << 9)) << 2) + +/* + * Selects which PLL clock will be driven on the analog high speed + * clock 0: PLL 0 div 1 + * clock 1: PLL 1 div 2 + */ +#define CLK_PLL_CONFIG 0X30 +#define CLK_PLL_MASK 0x33 + +#define CMN_READY BIT(0) + +#define DP_PLL_CLOCK_ENABLE BIT(2) +#define DP_PLL_ENABLE BIT(0) +#define DP_PLL_DATA_RATE_RBR ((2 << 12) | (4 << 8)) +#define DP_PLL_DATA_RATE_HBR ((2 << 12) | (4 << 8)) +#define DP_PLL_DATA_RATE_HBR2 ((1 << 12) | (2 << 8)) + +#define DP_MODE_A0 BIT(4) +#define DP_MODE_A2 BIT(6) +#define DP_MODE_ENTER_A0 0xc101 +#define DP_MODE_ENTER_A2 0xc104 + +#define PHY_MODE_SET_TIMEOUT 100000 + +#define PIN_ASSIGN_C_E 0x51d9 +#define PIN_ASSIGN_D_F 0x5100 + +#define MODE_DISCONNECT 0 +#define MODE_UFP_USB BIT(0) +#define MODE_DFP_USB BIT(1) +#define MODE_DFP_DP BIT(2) + +struct usb3phy_reg { + u32 offset; + u32 enable_bit; + u32 write_enable; +}; + +struct rockchip_usb3phy_port_cfg { + struct usb3phy_reg typec_conn_dir; + struct usb3phy_reg usb3tousb2_en; + struct usb3phy_reg external_psm; + struct usb3phy_reg pipe_status; +}; + +struct rockchip_typec_phy { + struct device *dev; + void __iomem *base; + struct extcon_dev *extcon; + struct regmap *grf_regs; + struct clk *clk_core; + struct clk *clk_ref; + struct reset_control *uphy_rst; + struct reset_control *pipe_rst; + struct reset_control *tcphy_rst; + struct rockchip_usb3phy_port_cfg port_cfgs; + /* mutex to protect access to individual PHYs */ + struct mutex lock; + + bool flip; + u8 mode; +}; + +struct phy_reg { + u16 value; + u32 addr; +}; + +struct phy_reg usb3_pll_cfg[] = { + { 0xf0, CMN_PLL0_VCOCAL_INIT }, + { 0x18, CMN_PLL0_VCOCAL_ITER }, + { 0xd0, CMN_PLL0_INTDIV }, + { 0x4a4a, CMN_PLL0_FRACDIV }, + { 0x34, CMN_PLL0_HIGH_THR }, + { 0x1ee, CMN_PLL0_SS_CTRL1 }, + { 0x7f03, CMN_PLL0_SS_CTRL2 }, + { 0x20, CMN_PLL0_DSM_DIAG }, + { 0, CMN_DIAG_PLL0_OVRD }, + { 0, CMN_DIAG_PLL0_FBH_OVRD }, + { 0, CMN_DIAG_PLL0_FBL_OVRD }, + { 0x7, CMN_DIAG_PLL0_V2I_TUNE }, + { 0x45, CMN_DIAG_PLL0_CP_TUNE }, + { 0x8, CMN_DIAG_PLL0_LF_PROG }, +}; + +struct phy_reg dp_pll_cfg[] = { + { 0xf0, CMN_PLL1_VCOCAL_INIT }, + { 0x18, CMN_PLL1_VCOCAL_ITER }, + { 0x30b9, CMN_PLL1_VCOCAL_START }, + { 0x21c, CMN_PLL1_INTDIV }, + { 0, CMN_PLL1_FRACDIV }, + { 0x5, CMN_PLL1_HIGH_THR }, + { 0x35, CMN_PLL1_SS_CTRL1 }, + { 0x7f1e, CMN_PLL1_SS_CTRL2 }, + { 0x20, CMN_PLL1_DSM_DIAG }, + { 0, CMN_PLLSM1_USER_DEF_CTRL }, + { 0, CMN_DIAG_PLL1_OVRD }, + { 0, CMN_DIAG_PLL1_FBH_OVRD }, + { 0, CMN_DIAG_PLL1_FBL_OVRD }, + { 0x6, CMN_DIAG_PLL1_V2I_TUNE }, + { 0x45, CMN_DIAG_PLL1_CP_TUNE }, + { 0x8, CMN_DIAG_PLL1_LF_PROG }, + { 0x100, CMN_DIAG_PLL1_PTATIS_TUNE1 }, + { 0x7, CMN_DIAG_PLL1_PTATIS_TUNE2 }, + { 0x4, CMN_DIAG_PLL1_INCLK_CTRL }, +}; + +static void tcphy_cfg_24m(struct rockchip_typec_phy *tcphy) +{ + u32 i, rdata; + + /* + * cmn_ref_clk_sel = 3, select the 24Mhz for clk parent + * cmn_psm_clk_dig_div = 2, set the clk division to 2 + */ + writel(0x830, tcphy->base + PMA_CMN_CTRL1); + for (i = 0; i < 4; i++) { + /* + * The following PHY configuration assumes a 24 MHz reference + * clock. + */ + writel(0x90, tcphy->base + XCVR_DIAG_LANE_FCM_EN_MGN(i)); + writel(0x960, tcphy->base + TX_RCVDET_EN_TMR(i)); + writel(0x30, tcphy->base + TX_RCVDET_ST_TMR(i)); + } + + rdata = readl(tcphy->base + CMN_DIAG_HSCLK_SEL); + rdata &= ~CLK_PLL_MASK; + rdata |= CLK_PLL_CONFIG; + writel(rdata, tcphy->base + CMN_DIAG_HSCLK_SEL); +} + +static void tcphy_cfg_usb3_pll(struct rockchip_typec_phy *tcphy) +{ + u32 i; + + /* load the configuration of PLL0 */ + for (i = 0; i < ARRAY_SIZE(usb3_pll_cfg); i++) + writel(usb3_pll_cfg[i].value, + tcphy->base + usb3_pll_cfg[i].addr); +} + +static void tcphy_cfg_dp_pll(struct rockchip_typec_phy *tcphy) +{ + u32 i; + + /* set the default mode to RBR */ + writel(DP_PLL_CLOCK_ENABLE | DP_PLL_ENABLE | DP_PLL_DATA_RATE_RBR, + tcphy->base + DP_CLK_CTL); + + /* load the configuration of PLL1 */ + for (i = 0; i < ARRAY_SIZE(dp_pll_cfg); i++) + writel(dp_pll_cfg[i].value, tcphy->base + dp_pll_cfg[i].addr); +} + +static void tcphy_tx_usb3_cfg_lane(struct rockchip_typec_phy *tcphy, u32 lane) +{ + writel(0x7799, tcphy->base + TX_PSC_A0(lane)); + writel(0x7798, tcphy->base + TX_PSC_A1(lane)); + writel(0x5098, tcphy->base + TX_PSC_A2(lane)); + writel(0x5098, tcphy->base + TX_PSC_A3(lane)); + writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_000(lane)); + writel(0xbf, tcphy->base + XCVR_DIAG_BIDI_CTRL(lane)); +} + +static void tcphy_rx_usb3_cfg_lane(struct rockchip_typec_phy *tcphy, u32 lane) +{ + writel(0xa6fd, tcphy->base + RX_PSC_A0(lane)); + writel(0xa6fd, tcphy->base + RX_PSC_A1(lane)); + writel(0xa410, tcphy->base + RX_PSC_A2(lane)); + writel(0x2410, tcphy->base + RX_PSC_A3(lane)); + writel(0x23ff, tcphy->base + RX_PSC_CAL(lane)); + writel(0x13, tcphy->base + RX_SIGDET_HL_FILT_TMR(lane)); + writel(0x03e7, tcphy->base + RX_REE_CTRL_DATA_MASK(lane)); + writel(0x1004, tcphy->base + RX_DIAG_SIGDET_TUNE(lane)); + writel(0x2010, tcphy->base + RX_PSC_RDY(lane)); + writel(0xfb, tcphy->base + XCVR_DIAG_BIDI_CTRL(lane)); +} + +static void tcphy_dp_cfg_lane(struct rockchip_typec_phy *tcphy, u32 lane) +{ + u16 rdata; + + writel(0xbefc, tcphy->base + XCVR_PSM_RCTRL(lane)); + writel(0x6799, tcphy->base + TX_PSC_A0(lane)); + writel(0x6798, tcphy->base + TX_PSC_A1(lane)); + writel(0x98, tcphy->base + TX_PSC_A2(lane)); + writel(0x98, tcphy->base + TX_PSC_A3(lane)); + + writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_000(lane)); + writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_001(lane)); + writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_010(lane)); + writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_011(lane)); + writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_100(lane)); + writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_101(lane)); + writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_110(lane)); + writel(0, tcphy->base + TX_TXCC_MGNFS_MULT_111(lane)); + writel(0, tcphy->base + TX_TXCC_CPOST_MULT_10(lane)); + writel(0, tcphy->base + TX_TXCC_CPOST_MULT_01(lane)); + writel(0, tcphy->base + TX_TXCC_CPOST_MULT_00(lane)); + writel(0, tcphy->base + TX_TXCC_CPOST_MULT_11(lane)); + + writel(0x128, tcphy->base + TX_TXCC_CAL_SCLR_MULT(lane)); + writel(0x400, tcphy->base + TX_DIAG_TX_DRV(lane)); + + rdata = readl(tcphy->base + XCVR_DIAG_PLLDRC_CTRL(lane)); + rdata = (rdata & 0x8fff) | 0x6000; + writel(rdata, tcphy->base + XCVR_DIAG_PLLDRC_CTRL(lane)); +} + +static inline int property_enable(struct rockchip_typec_phy *tcphy, + const struct usb3phy_reg *reg, bool en) +{ + u32 mask = 1 << reg->write_enable; + u32 val = en << reg->enable_bit; + + return regmap_write(tcphy->grf_regs, reg->offset, val | mask); +} + +static void tcphy_dp_aux_calibration(struct rockchip_typec_phy *tcphy) +{ + u16 rdata, rdata2, val; + + /* disable txda_cal_latch_en for rewrite the calibration values */ + rdata = readl(tcphy->base + TX_ANA_CTRL_REG_1); + val = rdata & 0xdfff; + writel(val, tcphy->base + TX_ANA_CTRL_REG_1); + + /* + * read a resistor calibration code from CMN_TXPUCAL_CTRL[6:0] and + * write it to TX_DIG_CTRL_REG_2[6:0], and delay 1ms to make sure it + * works. + */ + rdata = readl(tcphy->base + TX_DIG_CTRL_REG_2); + rdata = rdata & 0xffc0; + + rdata2 = readl(tcphy->base + CMN_TXPUCAL_CTRL); + rdata2 = rdata2 & 0x3f; + + val = rdata | rdata2; + writel(val, tcphy->base + TX_DIG_CTRL_REG_2); + usleep_range(1000, 1050); + + /* + * Enable signal for latch that sample and holds calibration values. + * Activate this signal for 1 clock cycle to sample new calibration + * values. + */ + rdata = readl(tcphy->base + TX_ANA_CTRL_REG_1); + val = rdata | 0x2000; + writel(val, tcphy->base + TX_ANA_CTRL_REG_1); + usleep_range(150, 200); + + /* set TX Voltage Level and TX Deemphasis to 0 */ + writel(0, tcphy->base + PHY_DP_TX_CTL); + /* re-enable decap */ + writel(0x100, tcphy->base + TX_ANA_CTRL_REG_2); + writel(0x300, tcphy->base + TX_ANA_CTRL_REG_2); + writel(0x2008, tcphy->base + TX_ANA_CTRL_REG_1); + writel(0x2018, tcphy->base + TX_ANA_CTRL_REG_1); + + writel(0, tcphy->base + TX_ANA_CTRL_REG_5); + + /* + * Programs txda_drv_ldo_prog[15:0], Sets driver LDO + * voltage 16'h1001 for DP-AUX-TX and RX + */ + writel(0x1001, tcphy->base + TX_ANA_CTRL_REG_4); + + /* re-enables Bandgap reference for LDO */ + writel(0x2098, tcphy->base + TX_ANA_CTRL_REG_1); + writel(0x2198, tcphy->base + TX_ANA_CTRL_REG_1); + + /* + * re-enables the transmitter pre-driver, driver data selection MUX, + * and receiver detect circuits. + */ + writel(0x301, tcphy->base + TX_ANA_CTRL_REG_2); + writel(0x303, tcphy->base + TX_ANA_CTRL_REG_2); + + /* + * BIT 12: Controls auxda_polarity, which selects the polarity of the + * xcvr: + * 1, Reverses the polarity (If TYPEC, Pulls ups aux_p and pull + * down aux_m) + * 0, Normal polarity (if TYPE_C, pulls up aux_m and pulls down + * aux_p) + */ + val = 0xa078; + if (!tcphy->flip) + val |= BIT(12); + writel(val, tcphy->base + TX_ANA_CTRL_REG_1); + + writel(0, tcphy->base + TX_ANA_CTRL_REG_3); + writel(0, tcphy->base + TX_ANA_CTRL_REG_4); + writel(0, tcphy->base + TX_ANA_CTRL_REG_5); + + /* + * Controls low_power_swing_en, set the voltage swing of the driver + * to 400mv. The values below are peak to peak (differential) values. + */ + writel(4, tcphy->base + TXDA_COEFF_CALC_CTRL); + writel(0, tcphy->base + TXDA_CYA_AUXDA_CYA); + + /* Controls tx_high_z_tm_en */ + val = readl(tcphy->base + TX_DIG_CTRL_REG_2); + val |= BIT(15); + writel(val, tcphy->base + TX_DIG_CTRL_REG_2); +} + +static int tcphy_phy_init(struct rockchip_typec_phy *tcphy, u8 mode) +{ + struct rockchip_usb3phy_port_cfg *cfg = &tcphy->port_cfgs; + int ret, i; + u32 val; + + ret = clk_prepare_enable(tcphy->clk_core); + if (ret) { + dev_err(tcphy->dev, "Failed to prepare_enable core clock\n"); + return ret; + } + + ret = clk_prepare_enable(tcphy->clk_ref); + if (ret) { + dev_err(tcphy->dev, "Failed to prepare_enable ref clock\n"); + goto err_clk_core; + } + + reset_control_deassert(tcphy->tcphy_rst); + + property_enable(tcphy, &cfg->typec_conn_dir, tcphy->flip); + + tcphy_cfg_24m(tcphy); + + if (mode == MODE_DFP_DP) { + tcphy_cfg_dp_pll(tcphy); + for (i = 0; i < 4; i++) + tcphy_dp_cfg_lane(tcphy, i); + + writel(PIN_ASSIGN_C_E, tcphy->base + PMA_LANE_CFG); + } else { + tcphy_cfg_usb3_pll(tcphy); + tcphy_cfg_dp_pll(tcphy); + if (tcphy->flip) { + tcphy_tx_usb3_cfg_lane(tcphy, 3); + tcphy_rx_usb3_cfg_lane(tcphy, 2); + tcphy_dp_cfg_lane(tcphy, 0); + tcphy_dp_cfg_lane(tcphy, 1); + } else { + tcphy_tx_usb3_cfg_lane(tcphy, 0); + tcphy_rx_usb3_cfg_lane(tcphy, 1); + tcphy_dp_cfg_lane(tcphy, 2); + tcphy_dp_cfg_lane(tcphy, 3); + } + + writel(PIN_ASSIGN_D_F, tcphy->base + PMA_LANE_CFG); + } + + writel(DP_MODE_ENTER_A2, tcphy->base + DP_MODE_CTL); + + reset_control_deassert(tcphy->uphy_rst); + + ret = readx_poll_timeout(readl, tcphy->base + PMA_CMN_CTRL1, + val, val & CMN_READY, 10, + PHY_MODE_SET_TIMEOUT); + if (ret < 0) { + dev_err(tcphy->dev, "wait pma ready timeout\n"); + ret = -ETIMEDOUT; + goto err_wait_pma; + } + + reset_control_deassert(tcphy->pipe_rst); + + return 0; + +err_wait_pma: + reset_control_assert(tcphy->uphy_rst); + reset_control_assert(tcphy->tcphy_rst); + clk_disable_unprepare(tcphy->clk_ref); +err_clk_core: + clk_disable_unprepare(tcphy->clk_core); + return ret; +} + +static void tcphy_phy_deinit(struct rockchip_typec_phy *tcphy) +{ + reset_control_assert(tcphy->tcphy_rst); + reset_control_assert(tcphy->uphy_rst); + reset_control_assert(tcphy->pipe_rst); + clk_disable_unprepare(tcphy->clk_core); + clk_disable_unprepare(tcphy->clk_ref); +} + +static int tcphy_get_mode(struct rockchip_typec_phy *tcphy) +{ + struct extcon_dev *edev = tcphy->extcon; + union extcon_property_value property; + unsigned int id; + bool dfp, ufp, dp; + u8 mode; + int ret; + + ufp = extcon_get_state(edev, EXTCON_USB); + dfp = extcon_get_state(edev, EXTCON_USB_HOST); + dp = extcon_get_state(edev, EXTCON_DISP_DP); + + mode = MODE_DFP_USB; + id = EXTCON_USB_HOST; + + if (ufp) { + mode = MODE_UFP_USB; + id = EXTCON_USB; + } else if (dp) { + mode = MODE_DFP_DP; + id = EXTCON_DISP_DP; + + ret = extcon_get_property(edev, id, EXTCON_PROP_USB_SS, + &property); + if (ret) { + dev_err(tcphy->dev, "get superspeed property failed\n"); + return ret; + } + + if (property.intval) + mode |= MODE_DFP_USB; + } + + ret = extcon_get_property(edev, id, EXTCON_PROP_USB_TYPEC_POLARITY, + &property); + if (ret) { + dev_err(tcphy->dev, "get polarity property failed\n"); + return ret; + } + + tcphy->flip = property.intval ? 1 : 0; + + return mode; +} + +static int rockchip_usb3_phy_power_on(struct phy *phy) +{ + struct rockchip_typec_phy *tcphy = phy_get_drvdata(phy); + struct rockchip_usb3phy_port_cfg *cfg = &tcphy->port_cfgs; + const struct usb3phy_reg *reg = &cfg->pipe_status; + int timeout, new_mode, ret = 0; + u32 val; + + mutex_lock(&tcphy->lock); + + new_mode = tcphy_get_mode(tcphy); + if (new_mode < 0) { + ret = new_mode; + goto unlock_ret; + } + + /* DP-only mode; fall back to USB2 */ + if (!(new_mode & (MODE_DFP_USB | MODE_UFP_USB))) + goto unlock_ret; + + if (tcphy->mode == new_mode) + goto unlock_ret; + + if (tcphy->mode == MODE_DISCONNECT) + tcphy_phy_init(tcphy, new_mode); + + /* wait TCPHY for pipe ready */ + for (timeout = 0; timeout < 100; timeout++) { + regmap_read(tcphy->grf_regs, reg->offset, &val); + if (!(val & BIT(reg->enable_bit))) { + tcphy->mode |= new_mode & (MODE_DFP_USB | MODE_UFP_USB); + goto unlock_ret; + } + usleep_range(10, 20); + } + + if (tcphy->mode == MODE_DISCONNECT) + tcphy_phy_deinit(tcphy); + + ret = -ETIMEDOUT; + +unlock_ret: + mutex_unlock(&tcphy->lock); + return ret; +} + +static int rockchip_usb3_phy_power_off(struct phy *phy) +{ + struct rockchip_typec_phy *tcphy = phy_get_drvdata(phy); + + mutex_lock(&tcphy->lock); + + if (tcphy->mode == MODE_DISCONNECT) + goto unlock; + + tcphy->mode &= ~(MODE_UFP_USB | MODE_DFP_USB); + if (tcphy->mode == MODE_DISCONNECT) + tcphy_phy_deinit(tcphy); + +unlock: + mutex_unlock(&tcphy->lock); + return 0; +} + +static const struct phy_ops rockchip_usb3_phy_ops = { + .power_on = rockchip_usb3_phy_power_on, + .power_off = rockchip_usb3_phy_power_off, + .owner = THIS_MODULE, +}; + +static int rockchip_dp_phy_power_on(struct phy *phy) +{ + struct rockchip_typec_phy *tcphy = phy_get_drvdata(phy); + int new_mode, ret = 0; + u32 val; + + mutex_lock(&tcphy->lock); + + new_mode = tcphy_get_mode(tcphy); + if (new_mode < 0) { + ret = new_mode; + goto unlock_ret; + } + + if (!(new_mode & MODE_DFP_DP)) { + ret = -ENODEV; + goto unlock_ret; + } + + if (tcphy->mode == new_mode) + goto unlock_ret; + + /* + * If the PHY has been power on, but the mode is not DP only mode, + * re-init the PHY for setting all of 4 lanes to DP. + */ + if (new_mode == MODE_DFP_DP && tcphy->mode != MODE_DISCONNECT) { + tcphy_phy_deinit(tcphy); + tcphy_phy_init(tcphy, new_mode); + } else if (tcphy->mode == MODE_DISCONNECT) { + tcphy_phy_init(tcphy, new_mode); + } + + ret = readx_poll_timeout(readl, tcphy->base + DP_MODE_CTL, + val, val & DP_MODE_A2, 1000, + PHY_MODE_SET_TIMEOUT); + if (ret < 0) { + dev_err(tcphy->dev, "failed to wait TCPHY enter A2\n"); + goto power_on_finish; + } + + tcphy_dp_aux_calibration(tcphy); + + writel(DP_MODE_ENTER_A0, tcphy->base + DP_MODE_CTL); + + ret = readx_poll_timeout(readl, tcphy->base + DP_MODE_CTL, + val, val & DP_MODE_A0, 1000, + PHY_MODE_SET_TIMEOUT); + if (ret < 0) { + writel(DP_MODE_ENTER_A2, tcphy->base + DP_MODE_CTL); + dev_err(tcphy->dev, "failed to wait TCPHY enter A0\n"); + goto power_on_finish; + } + + tcphy->mode |= MODE_DFP_DP; + +power_on_finish: + if (tcphy->mode == MODE_DISCONNECT) + tcphy_phy_deinit(tcphy); +unlock_ret: + mutex_unlock(&tcphy->lock); + return ret; +} + +static int rockchip_dp_phy_power_off(struct phy *phy) +{ + struct rockchip_typec_phy *tcphy = phy_get_drvdata(phy); + + mutex_lock(&tcphy->lock); + + if (tcphy->mode == MODE_DISCONNECT) + goto unlock; + + tcphy->mode &= ~MODE_DFP_DP; + + writel(DP_MODE_ENTER_A2, tcphy->base + DP_MODE_CTL); + + if (tcphy->mode == MODE_DISCONNECT) + tcphy_phy_deinit(tcphy); + +unlock: + mutex_unlock(&tcphy->lock); + return 0; +} + +static const struct phy_ops rockchip_dp_phy_ops = { + .power_on = rockchip_dp_phy_power_on, + .power_off = rockchip_dp_phy_power_off, + .owner = THIS_MODULE, +}; + +static int tcphy_get_param(struct device *dev, + struct usb3phy_reg *reg, + const char *name) +{ + u32 buffer[3]; + int ret; + + ret = of_property_read_u32_array(dev->of_node, name, buffer, 3); + if (ret) { + dev_err(dev, "Can not parse %s\n", name); + return ret; + } + + reg->offset = buffer[0]; + reg->enable_bit = buffer[1]; + reg->write_enable = buffer[2]; + return 0; +} + +static int tcphy_parse_dt(struct rockchip_typec_phy *tcphy, + struct device *dev) +{ + struct rockchip_usb3phy_port_cfg *cfg = &tcphy->port_cfgs; + int ret; + + ret = tcphy_get_param(dev, &cfg->typec_conn_dir, + "rockchip,typec-conn-dir"); + if (ret) + return ret; + + ret = tcphy_get_param(dev, &cfg->usb3tousb2_en, + "rockchip,usb3tousb2-en"); + if (ret) + return ret; + + ret = tcphy_get_param(dev, &cfg->external_psm, + "rockchip,external-psm"); + if (ret) + return ret; + + ret = tcphy_get_param(dev, &cfg->pipe_status, + "rockchip,pipe-status"); + if (ret) + return ret; + + tcphy->grf_regs = syscon_regmap_lookup_by_phandle(dev->of_node, + "rockchip,grf"); + if (IS_ERR(tcphy->grf_regs)) { + dev_err(dev, "could not find grf dt node\n"); + return PTR_ERR(tcphy->grf_regs); + } + + tcphy->clk_core = devm_clk_get(dev, "tcpdcore"); + if (IS_ERR(tcphy->clk_core)) { + dev_err(dev, "could not get uphy core clock\n"); + return PTR_ERR(tcphy->clk_core); + } + + tcphy->clk_ref = devm_clk_get(dev, "tcpdphy-ref"); + if (IS_ERR(tcphy->clk_ref)) { + dev_err(dev, "could not get uphy ref clock\n"); + return PTR_ERR(tcphy->clk_ref); + } + + tcphy->uphy_rst = devm_reset_control_get(dev, "uphy"); + if (IS_ERR(tcphy->uphy_rst)) { + dev_err(dev, "no uphy_rst reset control found\n"); + return PTR_ERR(tcphy->uphy_rst); + } + + tcphy->pipe_rst = devm_reset_control_get(dev, "uphy-pipe"); + if (IS_ERR(tcphy->pipe_rst)) { + dev_err(dev, "no pipe_rst reset control found\n"); + return PTR_ERR(tcphy->pipe_rst); + } + + tcphy->tcphy_rst = devm_reset_control_get(dev, "uphy-tcphy"); + if (IS_ERR(tcphy->tcphy_rst)) { + dev_err(dev, "no tcphy_rst reset control found\n"); + return PTR_ERR(tcphy->tcphy_rst); + } + + return 0; +} + +static void typec_phy_pre_init(struct rockchip_typec_phy *tcphy) +{ + struct rockchip_usb3phy_port_cfg *cfg = &tcphy->port_cfgs; + + reset_control_assert(tcphy->tcphy_rst); + reset_control_assert(tcphy->uphy_rst); + reset_control_assert(tcphy->pipe_rst); + + /* select external psm clock */ + property_enable(tcphy, &cfg->external_psm, 1); + property_enable(tcphy, &cfg->usb3tousb2_en, 0); + + tcphy->mode = MODE_DISCONNECT; +} + +static int rockchip_typec_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct device_node *child_np; + struct rockchip_typec_phy *tcphy; + struct phy_provider *phy_provider; + struct resource *res; + int ret; + + tcphy = devm_kzalloc(dev, sizeof(*tcphy), GFP_KERNEL); + if (!tcphy) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + tcphy->base = devm_ioremap_resource(dev, res); + if (IS_ERR(tcphy->base)) + return PTR_ERR(tcphy->base); + + ret = tcphy_parse_dt(tcphy, dev); + if (ret) + return ret; + + tcphy->dev = dev; + platform_set_drvdata(pdev, tcphy); + mutex_init(&tcphy->lock); + + typec_phy_pre_init(tcphy); + + tcphy->extcon = extcon_get_edev_by_phandle(dev, 0); + if (IS_ERR(tcphy->extcon)) { + if (PTR_ERR(tcphy->extcon) != -EPROBE_DEFER) + dev_err(dev, "Invalid or missing extcon\n"); + return PTR_ERR(tcphy->extcon); + } + + pm_runtime_enable(dev); + + for_each_available_child_of_node(np, child_np) { + struct phy *phy; + + if (!of_node_cmp(child_np->name, "dp-port")) + phy = devm_phy_create(dev, child_np, + &rockchip_dp_phy_ops); + else if (!of_node_cmp(child_np->name, "usb3-port")) + phy = devm_phy_create(dev, child_np, + &rockchip_usb3_phy_ops); + else + continue; + + if (IS_ERR(phy)) { + dev_err(dev, "failed to create phy: %s\n", + child_np->name); + return PTR_ERR(phy); + } + + phy_set_drvdata(phy, tcphy); + } + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(phy_provider)) { + dev_err(dev, "Failed to register phy provider\n"); + return PTR_ERR(phy_provider); + } + + return 0; +} + +static int rockchip_typec_phy_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static const struct of_device_id rockchip_typec_phy_dt_ids[] = { + { .compatible = "rockchip,rk3399-typec-phy" }, + {} +}; + +MODULE_DEVICE_TABLE(of, rockchip_typec_phy_dt_ids); + +static struct platform_driver rockchip_typec_phy_driver = { + .probe = rockchip_typec_phy_probe, + .remove = rockchip_typec_phy_remove, + .driver = { + .name = "rockchip-typec-phy", + .of_match_table = rockchip_typec_phy_dt_ids, + }, +}; + +module_platform_driver(rockchip_typec_phy_driver); + +MODULE_AUTHOR("Chris Zhong "); +MODULE_AUTHOR("Kever Yang "); +MODULE_DESCRIPTION("Rockchip USB TYPE-C PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/rockchip/phy-rockchip-usb.c b/drivers/phy/rockchip/phy-rockchip-usb.c new file mode 100644 index 000000000000..3378eeb7a562 --- /dev/null +++ b/drivers/phy/rockchip/phy-rockchip-usb.c @@ -0,0 +1,540 @@ +/* + * Rockchip usb PHY driver + * + * Copyright (C) 2014 Yunzhi Li + * Copyright (C) 2014 ROCKCHIP, Inc. + * + * 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; either 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int enable_usb_uart; + +#define HIWORD_UPDATE(val, mask) \ + ((val) | (mask) << 16) + +#define UOC_CON0_SIDDQ BIT(13) + +struct rockchip_usb_phys { + int reg; + const char *pll_name; +}; + +struct rockchip_usb_phy_base; +struct rockchip_usb_phy_pdata { + struct rockchip_usb_phys *phys; + int (*init_usb_uart)(struct regmap *grf); + int usb_uart_phy; +}; + +struct rockchip_usb_phy_base { + struct device *dev; + struct regmap *reg_base; + const struct rockchip_usb_phy_pdata *pdata; +}; + +struct rockchip_usb_phy { + struct rockchip_usb_phy_base *base; + struct device_node *np; + unsigned int reg_offset; + struct clk *clk; + struct clk *clk480m; + struct clk_hw clk480m_hw; + struct phy *phy; + bool uart_enabled; + struct reset_control *reset; + struct regulator *vbus; +}; + +static int rockchip_usb_phy_power(struct rockchip_usb_phy *phy, + bool siddq) +{ + u32 val = HIWORD_UPDATE(siddq ? UOC_CON0_SIDDQ : 0, UOC_CON0_SIDDQ); + + return regmap_write(phy->base->reg_base, phy->reg_offset, val); +} + +static unsigned long rockchip_usb_phy480m_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + return 480000000; +} + +static void rockchip_usb_phy480m_disable(struct clk_hw *hw) +{ + struct rockchip_usb_phy *phy = container_of(hw, + struct rockchip_usb_phy, + clk480m_hw); + + if (phy->vbus) + regulator_disable(phy->vbus); + + /* Power down usb phy analog blocks by set siddq 1 */ + rockchip_usb_phy_power(phy, 1); +} + +static int rockchip_usb_phy480m_enable(struct clk_hw *hw) +{ + struct rockchip_usb_phy *phy = container_of(hw, + struct rockchip_usb_phy, + clk480m_hw); + + /* Power up usb phy analog blocks by set siddq 0 */ + return rockchip_usb_phy_power(phy, 0); +} + +static int rockchip_usb_phy480m_is_enabled(struct clk_hw *hw) +{ + struct rockchip_usb_phy *phy = container_of(hw, + struct rockchip_usb_phy, + clk480m_hw); + int ret; + u32 val; + + ret = regmap_read(phy->base->reg_base, phy->reg_offset, &val); + if (ret < 0) + return ret; + + return (val & UOC_CON0_SIDDQ) ? 0 : 1; +} + +static const struct clk_ops rockchip_usb_phy480m_ops = { + .enable = rockchip_usb_phy480m_enable, + .disable = rockchip_usb_phy480m_disable, + .is_enabled = rockchip_usb_phy480m_is_enabled, + .recalc_rate = rockchip_usb_phy480m_recalc_rate, +}; + +static int rockchip_usb_phy_power_off(struct phy *_phy) +{ + struct rockchip_usb_phy *phy = phy_get_drvdata(_phy); + + if (phy->uart_enabled) + return -EBUSY; + + clk_disable_unprepare(phy->clk480m); + + return 0; +} + +static int rockchip_usb_phy_power_on(struct phy *_phy) +{ + struct rockchip_usb_phy *phy = phy_get_drvdata(_phy); + + if (phy->uart_enabled) + return -EBUSY; + + if (phy->vbus) { + int ret; + + ret = regulator_enable(phy->vbus); + if (ret) + return ret; + } + + return clk_prepare_enable(phy->clk480m); +} + +static int rockchip_usb_phy_reset(struct phy *_phy) +{ + struct rockchip_usb_phy *phy = phy_get_drvdata(_phy); + + if (phy->reset) { + reset_control_assert(phy->reset); + udelay(10); + reset_control_deassert(phy->reset); + } + + return 0; +} + +static const struct phy_ops ops = { + .power_on = rockchip_usb_phy_power_on, + .power_off = rockchip_usb_phy_power_off, + .reset = rockchip_usb_phy_reset, + .owner = THIS_MODULE, +}; + +static void rockchip_usb_phy_action(void *data) +{ + struct rockchip_usb_phy *rk_phy = data; + + if (!rk_phy->uart_enabled) { + of_clk_del_provider(rk_phy->np); + clk_unregister(rk_phy->clk480m); + } + + if (rk_phy->clk) + clk_put(rk_phy->clk); +} + +static int rockchip_usb_phy_init(struct rockchip_usb_phy_base *base, + struct device_node *child) +{ + struct rockchip_usb_phy *rk_phy; + unsigned int reg_offset; + const char *clk_name; + struct clk_init_data init; + int err, i; + + rk_phy = devm_kzalloc(base->dev, sizeof(*rk_phy), GFP_KERNEL); + if (!rk_phy) + return -ENOMEM; + + rk_phy->base = base; + rk_phy->np = child; + + if (of_property_read_u32(child, "reg", ®_offset)) { + dev_err(base->dev, "missing reg property in node %s\n", + child->name); + return -EINVAL; + } + + rk_phy->reset = of_reset_control_get(child, "phy-reset"); + if (IS_ERR(rk_phy->reset)) + rk_phy->reset = NULL; + + rk_phy->reg_offset = reg_offset; + + rk_phy->clk = of_clk_get_by_name(child, "phyclk"); + if (IS_ERR(rk_phy->clk)) + rk_phy->clk = NULL; + + i = 0; + init.name = NULL; + while (base->pdata->phys[i].reg) { + if (base->pdata->phys[i].reg == reg_offset) { + init.name = base->pdata->phys[i].pll_name; + break; + } + i++; + } + + if (!init.name) { + dev_err(base->dev, "phy data not found\n"); + return -EINVAL; + } + + if (enable_usb_uart && base->pdata->usb_uart_phy == i) { + dev_dbg(base->dev, "phy%d used as uart output\n", i); + rk_phy->uart_enabled = true; + } else { + if (rk_phy->clk) { + clk_name = __clk_get_name(rk_phy->clk); + init.flags = 0; + init.parent_names = &clk_name; + init.num_parents = 1; + } else { + init.flags = 0; + init.parent_names = NULL; + init.num_parents = 0; + } + + init.ops = &rockchip_usb_phy480m_ops; + rk_phy->clk480m_hw.init = &init; + + rk_phy->clk480m = clk_register(base->dev, &rk_phy->clk480m_hw); + if (IS_ERR(rk_phy->clk480m)) { + err = PTR_ERR(rk_phy->clk480m); + goto err_clk; + } + + err = of_clk_add_provider(child, of_clk_src_simple_get, + rk_phy->clk480m); + if (err < 0) + goto err_clk_prov; + } + + err = devm_add_action_or_reset(base->dev, rockchip_usb_phy_action, + rk_phy); + if (err) + return err; + + rk_phy->phy = devm_phy_create(base->dev, child, &ops); + if (IS_ERR(rk_phy->phy)) { + dev_err(base->dev, "failed to create PHY\n"); + return PTR_ERR(rk_phy->phy); + } + phy_set_drvdata(rk_phy->phy, rk_phy); + + rk_phy->vbus = devm_regulator_get_optional(&rk_phy->phy->dev, "vbus"); + if (IS_ERR(rk_phy->vbus)) { + if (PTR_ERR(rk_phy->vbus) == -EPROBE_DEFER) + return PTR_ERR(rk_phy->vbus); + rk_phy->vbus = NULL; + } + + /* + * When acting as uart-pipe, just keep clock on otherwise + * only power up usb phy when it use, so disable it when init + */ + if (rk_phy->uart_enabled) + return clk_prepare_enable(rk_phy->clk); + else + return rockchip_usb_phy_power(rk_phy, 1); + +err_clk_prov: + if (!rk_phy->uart_enabled) + clk_unregister(rk_phy->clk480m); +err_clk: + if (rk_phy->clk) + clk_put(rk_phy->clk); + return err; +} + +static const struct rockchip_usb_phy_pdata rk3066a_pdata = { + .phys = (struct rockchip_usb_phys[]){ + { .reg = 0x17c, .pll_name = "sclk_otgphy0_480m" }, + { .reg = 0x188, .pll_name = "sclk_otgphy1_480m" }, + { /* sentinel */ } + }, +}; + +static const struct rockchip_usb_phy_pdata rk3188_pdata = { + .phys = (struct rockchip_usb_phys[]){ + { .reg = 0x10c, .pll_name = "sclk_otgphy0_480m" }, + { .reg = 0x11c, .pll_name = "sclk_otgphy1_480m" }, + { /* sentinel */ } + }, +}; + +#define RK3288_UOC0_CON0 0x320 +#define RK3288_UOC0_CON0_COMMON_ON_N BIT(0) +#define RK3288_UOC0_CON0_DISABLE BIT(4) + +#define RK3288_UOC0_CON2 0x328 +#define RK3288_UOC0_CON2_SOFT_CON_SEL BIT(2) + +#define RK3288_UOC0_CON3 0x32c +#define RK3288_UOC0_CON3_UTMI_SUSPENDN BIT(0) +#define RK3288_UOC0_CON3_UTMI_OPMODE_NODRIVING (1 << 1) +#define RK3288_UOC0_CON3_UTMI_OPMODE_MASK (3 << 1) +#define RK3288_UOC0_CON3_UTMI_XCVRSEELCT_FSTRANSC (1 << 3) +#define RK3288_UOC0_CON3_UTMI_XCVRSEELCT_MASK (3 << 3) +#define RK3288_UOC0_CON3_UTMI_TERMSEL_FULLSPEED BIT(5) +#define RK3288_UOC0_CON3_BYPASSDMEN BIT(6) +#define RK3288_UOC0_CON3_BYPASSSEL BIT(7) + +/* + * Enable the bypass of uart2 data through the otg usb phy. + * Original description in the TRM. + * 1. Disable the OTG block by setting OTGDISABLE0 to 1’b1. + * 2. Disable the pull-up resistance on the D+ line by setting + * OPMODE0[1:0] to 2’b01. + * 3. To ensure that the XO, Bias, and PLL blocks are powered down in Suspend + * mode, set COMMONONN to 1’b1. + * 4. Place the USB PHY in Suspend mode by setting SUSPENDM0 to 1’b0. + * 5. Set BYPASSSEL0 to 1’b1. + * 6. To transmit data, controls BYPASSDMEN0, and BYPASSDMDATA0. + * To receive data, monitor FSVPLUS0. + * + * The actual code in the vendor kernel does some things differently. + */ +static int __init rk3288_init_usb_uart(struct regmap *grf) +{ + u32 val; + int ret; + + /* + * COMMON_ON and DISABLE settings are described in the TRM, + * but were not present in the original code. + * Also disable the analog phy components to save power. + */ + val = HIWORD_UPDATE(RK3288_UOC0_CON0_COMMON_ON_N + | RK3288_UOC0_CON0_DISABLE + | UOC_CON0_SIDDQ, + RK3288_UOC0_CON0_COMMON_ON_N + | RK3288_UOC0_CON0_DISABLE + | UOC_CON0_SIDDQ); + ret = regmap_write(grf, RK3288_UOC0_CON0, val); + if (ret) + return ret; + + val = HIWORD_UPDATE(RK3288_UOC0_CON2_SOFT_CON_SEL, + RK3288_UOC0_CON2_SOFT_CON_SEL); + ret = regmap_write(grf, RK3288_UOC0_CON2, val); + if (ret) + return ret; + + val = HIWORD_UPDATE(RK3288_UOC0_CON3_UTMI_OPMODE_NODRIVING + | RK3288_UOC0_CON3_UTMI_XCVRSEELCT_FSTRANSC + | RK3288_UOC0_CON3_UTMI_TERMSEL_FULLSPEED, + RK3288_UOC0_CON3_UTMI_SUSPENDN + | RK3288_UOC0_CON3_UTMI_OPMODE_MASK + | RK3288_UOC0_CON3_UTMI_XCVRSEELCT_MASK + | RK3288_UOC0_CON3_UTMI_TERMSEL_FULLSPEED); + ret = regmap_write(grf, RK3288_UOC0_CON3, val); + if (ret) + return ret; + + val = HIWORD_UPDATE(RK3288_UOC0_CON3_BYPASSSEL + | RK3288_UOC0_CON3_BYPASSDMEN, + RK3288_UOC0_CON3_BYPASSSEL + | RK3288_UOC0_CON3_BYPASSDMEN); + ret = regmap_write(grf, RK3288_UOC0_CON3, val); + if (ret) + return ret; + + return 0; +} + +static const struct rockchip_usb_phy_pdata rk3288_pdata = { + .phys = (struct rockchip_usb_phys[]){ + { .reg = 0x320, .pll_name = "sclk_otgphy0_480m" }, + { .reg = 0x334, .pll_name = "sclk_otgphy1_480m" }, + { .reg = 0x348, .pll_name = "sclk_otgphy2_480m" }, + { /* sentinel */ } + }, + .init_usb_uart = rk3288_init_usb_uart, + .usb_uart_phy = 0, +}; + +static int rockchip_usb_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rockchip_usb_phy_base *phy_base; + struct phy_provider *phy_provider; + const struct of_device_id *match; + struct device_node *child; + int err; + + phy_base = devm_kzalloc(dev, sizeof(*phy_base), GFP_KERNEL); + if (!phy_base) + return -ENOMEM; + + match = of_match_device(dev->driver->of_match_table, dev); + if (!match || !match->data) { + dev_err(dev, "missing phy data\n"); + return -EINVAL; + } + + phy_base->pdata = match->data; + + phy_base->dev = dev; + phy_base->reg_base = ERR_PTR(-ENODEV); + if (dev->parent && dev->parent->of_node) + phy_base->reg_base = syscon_node_to_regmap( + dev->parent->of_node); + if (IS_ERR(phy_base->reg_base)) + phy_base->reg_base = syscon_regmap_lookup_by_phandle( + dev->of_node, "rockchip,grf"); + if (IS_ERR(phy_base->reg_base)) { + dev_err(&pdev->dev, "Missing rockchip,grf property\n"); + return PTR_ERR(phy_base->reg_base); + } + + for_each_available_child_of_node(dev->of_node, child) { + err = rockchip_usb_phy_init(phy_base, child); + if (err) { + of_node_put(child); + return err; + } + } + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id rockchip_usb_phy_dt_ids[] = { + { .compatible = "rockchip,rk3066a-usb-phy", .data = &rk3066a_pdata }, + { .compatible = "rockchip,rk3188-usb-phy", .data = &rk3188_pdata }, + { .compatible = "rockchip,rk3288-usb-phy", .data = &rk3288_pdata }, + {} +}; + +MODULE_DEVICE_TABLE(of, rockchip_usb_phy_dt_ids); + +static struct platform_driver rockchip_usb_driver = { + .probe = rockchip_usb_phy_probe, + .driver = { + .name = "rockchip-usb-phy", + .of_match_table = rockchip_usb_phy_dt_ids, + }, +}; + +module_platform_driver(rockchip_usb_driver); + +#ifndef MODULE +static int __init rockchip_init_usb_uart(void) +{ + const struct of_device_id *match; + const struct rockchip_usb_phy_pdata *data; + struct device_node *np; + struct regmap *grf; + int ret; + + if (!enable_usb_uart) + return 0; + + np = of_find_matching_node_and_match(NULL, rockchip_usb_phy_dt_ids, + &match); + if (!np) { + pr_err("%s: failed to find usbphy node\n", __func__); + return -ENOTSUPP; + } + + pr_debug("%s: using settings for %s\n", __func__, match->compatible); + data = match->data; + + if (!data->init_usb_uart) { + pr_err("%s: usb-uart not available on %s\n", + __func__, match->compatible); + return -ENOTSUPP; + } + + grf = ERR_PTR(-ENODEV); + if (np->parent) + grf = syscon_node_to_regmap(np->parent); + if (IS_ERR(grf)) + grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf"); + if (IS_ERR(grf)) { + pr_err("%s: Missing rockchip,grf property, %lu\n", + __func__, PTR_ERR(grf)); + return PTR_ERR(grf); + } + + ret = data->init_usb_uart(grf); + if (ret) { + pr_err("%s: could not init usb_uart, %d\n", __func__, ret); + enable_usb_uart = 0; + return ret; + } + + return 0; +} +early_initcall(rockchip_init_usb_uart); + +static int __init rockchip_usb_uart(char *buf) +{ + enable_usb_uart = true; + return 0; +} +early_param("rockchip.usb_uart", rockchip_usb_uart); +#endif + +MODULE_AUTHOR("Yunzhi Li "); +MODULE_DESCRIPTION("Rockchip USB 2.0 PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/samsung/Kconfig b/drivers/phy/samsung/Kconfig new file mode 100644 index 000000000000..b7e0645a7bd9 --- /dev/null +++ b/drivers/phy/samsung/Kconfig @@ -0,0 +1,95 @@ +# +# Phy drivers for Samsung platforms +# +config PHY_EXYNOS_DP_VIDEO + tristate "EXYNOS SoC series Display Port PHY driver" + depends on OF + depends on ARCH_EXYNOS || COMPILE_TEST + default ARCH_EXYNOS + select GENERIC_PHY + help + Support for Display Port PHY found on Samsung EXYNOS SoCs. + +config PHY_EXYNOS_MIPI_VIDEO + tristate "S5P/EXYNOS SoC series MIPI CSI-2/DSI PHY driver" + depends on HAS_IOMEM + depends on ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST + select GENERIC_PHY + default y if ARCH_S5PV210 || ARCH_EXYNOS + help + Support for MIPI CSI-2 and MIPI DSI DPHY found on Samsung S5P + and EXYNOS SoCs. + +config PHY_EXYNOS_PCIE + bool "Exynos PCIe PHY driver" + depends on OF && (ARCH_EXYNOS || COMPILE_TEST) + select GENERIC_PHY + help + Enable PCIe PHY support for Exynos SoC series. + This driver provides PHY interface for Exynos PCIe controller. + +config PHY_SAMSUNG_USB2 + tristate "Samsung USB 2.0 PHY driver" + depends on HAS_IOMEM + depends on USB_EHCI_EXYNOS || USB_OHCI_EXYNOS || USB_DWC2 + select GENERIC_PHY + select MFD_SYSCON + default ARCH_EXYNOS + help + Enable this to support the Samsung USB 2.0 PHY driver for Samsung + SoCs. This driver provides the interface for USB 2.0 PHY. Support + for particular PHYs will be enabled based on the SoC type in addition + to this driver. + +config PHY_EXYNOS4210_USB2 + bool + depends on PHY_SAMSUNG_USB2 + default CPU_EXYNOS4210 + +config PHY_EXYNOS4X12_USB2 + bool + depends on PHY_SAMSUNG_USB2 + default SOC_EXYNOS3250 || SOC_EXYNOS4212 || SOC_EXYNOS4412 + +config PHY_EXYNOS5250_USB2 + bool + depends on PHY_SAMSUNG_USB2 + default SOC_EXYNOS5250 || SOC_EXYNOS5420 + +config PHY_S5PV210_USB2 + bool "Support for S5PV210" + depends on PHY_SAMSUNG_USB2 + depends on ARCH_S5PV210 + help + Enable USB PHY support for S5PV210. This option requires that Samsung + USB 2.0 PHY driver is enabled and means that support for this + particular SoC is compiled in the driver. In case of S5PV210 two phys + are available - device and host. + +config PHY_EXYNOS5_USBDRD + tristate "Exynos5 SoC series USB DRD PHY driver" + depends on ARCH_EXYNOS && OF + depends on HAS_IOMEM + depends on USB_DWC3_EXYNOS + select GENERIC_PHY + select MFD_SYSCON + default y + help + Enable USB DRD PHY support for Exynos 5 SoC series. + This driver provides PHY interface for USB 3.0 DRD controller + present on Exynos5 SoC series. + +config PHY_EXYNOS5250_SATA + tristate "Exynos5250 Sata SerDes/PHY driver" + depends on SOC_EXYNOS5250 + depends on HAS_IOMEM + depends on OF + select GENERIC_PHY + select I2C + select I2C_S3C2410 + select MFD_SYSCON + help + Enable this to support SATA SerDes/Phy found on Samsung's + Exynos5250 based SoCs.This SerDes/Phy supports SATA 1.5 Gb/s, + SATA 3.0 Gb/s, SATA 6.0 Gb/s speeds. It supports one SATA host + port to accept one SATA device. diff --git a/drivers/phy/samsung/Makefile b/drivers/phy/samsung/Makefile new file mode 100644 index 000000000000..20d7f2424772 --- /dev/null +++ b/drivers/phy/samsung/Makefile @@ -0,0 +1,11 @@ +obj-$(CONFIG_PHY_EXYNOS_DP_VIDEO) += phy-exynos-dp-video.o +obj-$(CONFIG_PHY_EXYNOS_MIPI_VIDEO) += phy-exynos-mipi-video.o +obj-$(CONFIG_PHY_EXYNOS_PCIE) += phy-exynos-pcie.o +obj-$(CONFIG_PHY_SAMSUNG_USB2) += phy-exynos-usb2.o +phy-exynos-usb2-y += phy-samsung-usb2.o +phy-exynos-usb2-$(CONFIG_PHY_EXYNOS4210_USB2) += phy-exynos4210-usb2.o +phy-exynos-usb2-$(CONFIG_PHY_EXYNOS4X12_USB2) += phy-exynos4x12-usb2.o +phy-exynos-usb2-$(CONFIG_PHY_EXYNOS5250_USB2) += phy-exynos5250-usb2.o +phy-exynos-usb2-$(CONFIG_PHY_S5PV210_USB2) += phy-s5pv210-usb2.o +obj-$(CONFIG_PHY_EXYNOS5_USBDRD) += phy-exynos5-usbdrd.o +obj-$(CONFIG_PHY_EXYNOS5250_SATA) += phy-exynos5250-sata.o diff --git a/drivers/phy/samsung/phy-exynos-dp-video.c b/drivers/phy/samsung/phy-exynos-dp-video.c new file mode 100644 index 000000000000..bb3279dbf88c --- /dev/null +++ b/drivers/phy/samsung/phy-exynos-dp-video.c @@ -0,0 +1,122 @@ +/* + * Samsung EXYNOS SoC series Display Port PHY driver + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Author: Jingoo Han + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct exynos_dp_video_phy_drvdata { + u32 phy_ctrl_offset; +}; + +struct exynos_dp_video_phy { + struct regmap *regs; + const struct exynos_dp_video_phy_drvdata *drvdata; +}; + +static int exynos_dp_video_phy_power_on(struct phy *phy) +{ + struct exynos_dp_video_phy *state = phy_get_drvdata(phy); + + /* Disable power isolation on DP-PHY */ + return regmap_update_bits(state->regs, state->drvdata->phy_ctrl_offset, + EXYNOS4_PHY_ENABLE, EXYNOS4_PHY_ENABLE); +} + +static int exynos_dp_video_phy_power_off(struct phy *phy) +{ + struct exynos_dp_video_phy *state = phy_get_drvdata(phy); + + /* Enable power isolation on DP-PHY */ + return regmap_update_bits(state->regs, state->drvdata->phy_ctrl_offset, + EXYNOS4_PHY_ENABLE, 0); +} + +static const struct phy_ops exynos_dp_video_phy_ops = { + .power_on = exynos_dp_video_phy_power_on, + .power_off = exynos_dp_video_phy_power_off, + .owner = THIS_MODULE, +}; + +static const struct exynos_dp_video_phy_drvdata exynos5250_dp_video_phy = { + .phy_ctrl_offset = EXYNOS5_DPTX_PHY_CONTROL, +}; + +static const struct exynos_dp_video_phy_drvdata exynos5420_dp_video_phy = { + .phy_ctrl_offset = EXYNOS5420_DPTX_PHY_CONTROL, +}; + +static const struct of_device_id exynos_dp_video_phy_of_match[] = { + { + .compatible = "samsung,exynos5250-dp-video-phy", + .data = &exynos5250_dp_video_phy, + }, { + .compatible = "samsung,exynos5420-dp-video-phy", + .data = &exynos5420_dp_video_phy, + }, + { }, +}; +MODULE_DEVICE_TABLE(of, exynos_dp_video_phy_of_match); + +static int exynos_dp_video_phy_probe(struct platform_device *pdev) +{ + struct exynos_dp_video_phy *state; + struct device *dev = &pdev->dev; + const struct of_device_id *match; + struct phy_provider *phy_provider; + struct phy *phy; + + state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + state->regs = syscon_regmap_lookup_by_phandle(dev->of_node, + "samsung,pmu-syscon"); + if (IS_ERR(state->regs)) { + dev_err(dev, "Failed to lookup PMU regmap\n"); + return PTR_ERR(state->regs); + } + + match = of_match_node(exynos_dp_video_phy_of_match, dev->of_node); + state->drvdata = match->data; + + phy = devm_phy_create(dev, NULL, &exynos_dp_video_phy_ops); + if (IS_ERR(phy)) { + dev_err(dev, "failed to create Display Port PHY\n"); + return PTR_ERR(phy); + } + phy_set_drvdata(phy, state); + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static struct platform_driver exynos_dp_video_phy_driver = { + .probe = exynos_dp_video_phy_probe, + .driver = { + .name = "exynos-dp-video-phy", + .of_match_table = exynos_dp_video_phy_of_match, + } +}; +module_platform_driver(exynos_dp_video_phy_driver); + +MODULE_AUTHOR("Jingoo Han "); +MODULE_DESCRIPTION("Samsung EXYNOS SoC DP PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/samsung/phy-exynos-mipi-video.c b/drivers/phy/samsung/phy-exynos-mipi-video.c new file mode 100644 index 000000000000..c198886f80a3 --- /dev/null +++ b/drivers/phy/samsung/phy-exynos-mipi-video.c @@ -0,0 +1,377 @@ +/* + * Samsung S5P/EXYNOS SoC series MIPI CSIS/DSIM DPHY driver + * + * Copyright (C) 2013,2016 Samsung Electronics Co., Ltd. + * Author: Sylwester Nawrocki + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum exynos_mipi_phy_id { + EXYNOS_MIPI_PHY_ID_NONE = -1, + EXYNOS_MIPI_PHY_ID_CSIS0, + EXYNOS_MIPI_PHY_ID_DSIM0, + EXYNOS_MIPI_PHY_ID_CSIS1, + EXYNOS_MIPI_PHY_ID_DSIM1, + EXYNOS_MIPI_PHY_ID_CSIS2, + EXYNOS_MIPI_PHYS_NUM +}; + +enum exynos_mipi_phy_regmap_id { + EXYNOS_MIPI_REGMAP_PMU, + EXYNOS_MIPI_REGMAP_DISP, + EXYNOS_MIPI_REGMAP_CAM0, + EXYNOS_MIPI_REGMAP_CAM1, + EXYNOS_MIPI_REGMAPS_NUM +}; + +struct mipi_phy_device_desc { + int num_phys; + int num_regmaps; + const char *regmap_names[EXYNOS_MIPI_REGMAPS_NUM]; + struct exynos_mipi_phy_desc { + enum exynos_mipi_phy_id coupled_phy_id; + u32 enable_val; + unsigned int enable_reg; + enum exynos_mipi_phy_regmap_id enable_map; + u32 resetn_val; + unsigned int resetn_reg; + enum exynos_mipi_phy_regmap_id resetn_map; + } phys[EXYNOS_MIPI_PHYS_NUM]; +}; + +static const struct mipi_phy_device_desc s5pv210_mipi_phy = { + .num_regmaps = 1, + .regmap_names = {"syscon"}, + .num_phys = 4, + .phys = { + { + /* EXYNOS_MIPI_PHY_ID_CSIS0 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM0, + .enable_val = EXYNOS4_PHY_ENABLE, + .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(0), + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = EXYNOS4_MIPI_PHY_SRESETN, + .resetn_reg = EXYNOS4_MIPI_PHY_CONTROL(0), + .resetn_map = EXYNOS_MIPI_REGMAP_PMU, + }, { + /* EXYNOS_MIPI_PHY_ID_DSIM0 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS0, + .enable_val = EXYNOS4_PHY_ENABLE, + .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(0), + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = EXYNOS4_MIPI_PHY_MRESETN, + .resetn_reg = EXYNOS4_MIPI_PHY_CONTROL(0), + .resetn_map = EXYNOS_MIPI_REGMAP_PMU, + }, { + /* EXYNOS_MIPI_PHY_ID_CSIS1 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM1, + .enable_val = EXYNOS4_PHY_ENABLE, + .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(1), + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = EXYNOS4_MIPI_PHY_SRESETN, + .resetn_reg = EXYNOS4_MIPI_PHY_CONTROL(1), + .resetn_map = EXYNOS_MIPI_REGMAP_PMU, + }, { + /* EXYNOS_MIPI_PHY_ID_DSIM1 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS1, + .enable_val = EXYNOS4_PHY_ENABLE, + .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(1), + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = EXYNOS4_MIPI_PHY_MRESETN, + .resetn_reg = EXYNOS4_MIPI_PHY_CONTROL(1), + .resetn_map = EXYNOS_MIPI_REGMAP_PMU, + }, + }, +}; + +static const struct mipi_phy_device_desc exynos5420_mipi_phy = { + .num_regmaps = 1, + .regmap_names = {"syscon"}, + .num_phys = 5, + .phys = { + { + /* EXYNOS_MIPI_PHY_ID_CSIS0 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM0, + .enable_val = EXYNOS4_PHY_ENABLE, + .enable_reg = EXYNOS5420_MIPI_PHY_CONTROL(0), + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = EXYNOS4_MIPI_PHY_SRESETN, + .resetn_reg = EXYNOS5420_MIPI_PHY_CONTROL(0), + .resetn_map = EXYNOS_MIPI_REGMAP_PMU, + }, { + /* EXYNOS_MIPI_PHY_ID_DSIM0 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS0, + .enable_val = EXYNOS4_PHY_ENABLE, + .enable_reg = EXYNOS5420_MIPI_PHY_CONTROL(0), + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = EXYNOS4_MIPI_PHY_MRESETN, + .resetn_reg = EXYNOS5420_MIPI_PHY_CONTROL(0), + .resetn_map = EXYNOS_MIPI_REGMAP_PMU, + }, { + /* EXYNOS_MIPI_PHY_ID_CSIS1 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM1, + .enable_val = EXYNOS4_PHY_ENABLE, + .enable_reg = EXYNOS5420_MIPI_PHY_CONTROL(1), + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = EXYNOS4_MIPI_PHY_SRESETN, + .resetn_reg = EXYNOS5420_MIPI_PHY_CONTROL(1), + .resetn_map = EXYNOS_MIPI_REGMAP_PMU, + }, { + /* EXYNOS_MIPI_PHY_ID_DSIM1 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS1, + .enable_val = EXYNOS4_PHY_ENABLE, + .enable_reg = EXYNOS5420_MIPI_PHY_CONTROL(1), + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = EXYNOS4_MIPI_PHY_MRESETN, + .resetn_reg = EXYNOS5420_MIPI_PHY_CONTROL(1), + .resetn_map = EXYNOS_MIPI_REGMAP_PMU, + }, { + /* EXYNOS_MIPI_PHY_ID_CSIS2 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_NONE, + .enable_val = EXYNOS4_PHY_ENABLE, + .enable_reg = EXYNOS5420_MIPI_PHY_CONTROL(2), + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = EXYNOS4_MIPI_PHY_SRESETN, + .resetn_reg = EXYNOS5420_MIPI_PHY_CONTROL(2), + .resetn_map = EXYNOS_MIPI_REGMAP_PMU, + }, + }, +}; + +#define EXYNOS5433_SYSREG_DISP_MIPI_PHY 0x100C +#define EXYNOS5433_SYSREG_CAM0_MIPI_DPHY_CON 0x1014 +#define EXYNOS5433_SYSREG_CAM1_MIPI_DPHY_CON 0x1020 + +static const struct mipi_phy_device_desc exynos5433_mipi_phy = { + .num_regmaps = 4, + .regmap_names = { + "samsung,pmu-syscon", + "samsung,disp-sysreg", + "samsung,cam0-sysreg", + "samsung,cam1-sysreg" + }, + .num_phys = 5, + .phys = { + { + /* EXYNOS_MIPI_PHY_ID_CSIS0 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_DSIM0, + .enable_val = EXYNOS4_PHY_ENABLE, + .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(0), + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = BIT(0), + .resetn_reg = EXYNOS5433_SYSREG_CAM0_MIPI_DPHY_CON, + .resetn_map = EXYNOS_MIPI_REGMAP_CAM0, + }, { + /* EXYNOS_MIPI_PHY_ID_DSIM0 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_CSIS0, + .enable_val = EXYNOS4_PHY_ENABLE, + .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(0), + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = BIT(0), + .resetn_reg = EXYNOS5433_SYSREG_DISP_MIPI_PHY, + .resetn_map = EXYNOS_MIPI_REGMAP_DISP, + }, { + /* EXYNOS_MIPI_PHY_ID_CSIS1 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_NONE, + .enable_val = EXYNOS4_PHY_ENABLE, + .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(1), + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = BIT(1), + .resetn_reg = EXYNOS5433_SYSREG_CAM0_MIPI_DPHY_CON, + .resetn_map = EXYNOS_MIPI_REGMAP_CAM0, + }, { + /* EXYNOS_MIPI_PHY_ID_DSIM1 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_NONE, + .enable_val = EXYNOS4_PHY_ENABLE, + .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(1), + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = BIT(1), + .resetn_reg = EXYNOS5433_SYSREG_DISP_MIPI_PHY, + .resetn_map = EXYNOS_MIPI_REGMAP_DISP, + }, { + /* EXYNOS_MIPI_PHY_ID_CSIS2 */ + .coupled_phy_id = EXYNOS_MIPI_PHY_ID_NONE, + .enable_val = EXYNOS4_PHY_ENABLE, + .enable_reg = EXYNOS4_MIPI_PHY_CONTROL(2), + .enable_map = EXYNOS_MIPI_REGMAP_PMU, + .resetn_val = BIT(0), + .resetn_reg = EXYNOS5433_SYSREG_CAM1_MIPI_DPHY_CON, + .resetn_map = EXYNOS_MIPI_REGMAP_CAM1, + }, + }, +}; + +struct exynos_mipi_video_phy { + struct regmap *regmaps[EXYNOS_MIPI_REGMAPS_NUM]; + int num_phys; + struct video_phy_desc { + struct phy *phy; + unsigned int index; + const struct exynos_mipi_phy_desc *data; + } phys[EXYNOS_MIPI_PHYS_NUM]; + spinlock_t slock; +}; + +static int __set_phy_state(const struct exynos_mipi_phy_desc *data, + struct exynos_mipi_video_phy *state, unsigned int on) +{ + u32 val; + + spin_lock(&state->slock); + + /* disable in PMU sysreg */ + if (!on && data->coupled_phy_id >= 0 && + state->phys[data->coupled_phy_id].phy->power_count == 0) { + regmap_read(state->regmaps[data->enable_map], data->enable_reg, + &val); + val &= ~data->enable_val; + regmap_write(state->regmaps[data->enable_map], data->enable_reg, + val); + } + + /* PHY reset */ + regmap_read(state->regmaps[data->resetn_map], data->resetn_reg, &val); + val = on ? (val | data->resetn_val) : (val & ~data->resetn_val); + regmap_write(state->regmaps[data->resetn_map], data->resetn_reg, val); + + /* enable in PMU sysreg */ + if (on) { + regmap_read(state->regmaps[data->enable_map], data->enable_reg, + &val); + val |= data->enable_val; + regmap_write(state->regmaps[data->enable_map], data->enable_reg, + val); + } + + spin_unlock(&state->slock); + + return 0; +} + +#define to_mipi_video_phy(desc) \ + container_of((desc), struct exynos_mipi_video_phy, phys[(desc)->index]) + +static int exynos_mipi_video_phy_power_on(struct phy *phy) +{ + struct video_phy_desc *phy_desc = phy_get_drvdata(phy); + struct exynos_mipi_video_phy *state = to_mipi_video_phy(phy_desc); + + return __set_phy_state(phy_desc->data, state, 1); +} + +static int exynos_mipi_video_phy_power_off(struct phy *phy) +{ + struct video_phy_desc *phy_desc = phy_get_drvdata(phy); + struct exynos_mipi_video_phy *state = to_mipi_video_phy(phy_desc); + + return __set_phy_state(phy_desc->data, state, 0); +} + +static struct phy *exynos_mipi_video_phy_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct exynos_mipi_video_phy *state = dev_get_drvdata(dev); + + if (WARN_ON(args->args[0] >= state->num_phys)) + return ERR_PTR(-ENODEV); + + return state->phys[args->args[0]].phy; +} + +static const struct phy_ops exynos_mipi_video_phy_ops = { + .power_on = exynos_mipi_video_phy_power_on, + .power_off = exynos_mipi_video_phy_power_off, + .owner = THIS_MODULE, +}; + +static int exynos_mipi_video_phy_probe(struct platform_device *pdev) +{ + const struct mipi_phy_device_desc *phy_dev; + struct exynos_mipi_video_phy *state; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct phy_provider *phy_provider; + unsigned int i; + + phy_dev = of_device_get_match_data(dev); + if (!phy_dev) + return -ENODEV; + + state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + for (i = 0; i < phy_dev->num_regmaps; i++) { + state->regmaps[i] = syscon_regmap_lookup_by_phandle(np, + phy_dev->regmap_names[i]); + if (IS_ERR(state->regmaps[i])) + return PTR_ERR(state->regmaps[i]); + } + state->num_phys = phy_dev->num_phys; + spin_lock_init(&state->slock); + + dev_set_drvdata(dev, state); + + for (i = 0; i < state->num_phys; i++) { + struct phy *phy = devm_phy_create(dev, NULL, + &exynos_mipi_video_phy_ops); + if (IS_ERR(phy)) { + dev_err(dev, "failed to create PHY %d\n", i); + return PTR_ERR(phy); + } + + state->phys[i].phy = phy; + state->phys[i].index = i; + state->phys[i].data = &phy_dev->phys[i]; + phy_set_drvdata(phy, &state->phys[i]); + } + + phy_provider = devm_of_phy_provider_register(dev, + exynos_mipi_video_phy_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id exynos_mipi_video_phy_of_match[] = { + { + .compatible = "samsung,s5pv210-mipi-video-phy", + .data = &s5pv210_mipi_phy, + }, { + .compatible = "samsung,exynos5420-mipi-video-phy", + .data = &exynos5420_mipi_phy, + }, { + .compatible = "samsung,exynos5433-mipi-video-phy", + .data = &exynos5433_mipi_phy, + }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, exynos_mipi_video_phy_of_match); + +static struct platform_driver exynos_mipi_video_phy_driver = { + .probe = exynos_mipi_video_phy_probe, + .driver = { + .of_match_table = exynos_mipi_video_phy_of_match, + .name = "exynos-mipi-video-phy", + } +}; +module_platform_driver(exynos_mipi_video_phy_driver); + +MODULE_DESCRIPTION("Samsung S5P/EXYNOS SoC MIPI CSI-2/DSI PHY driver"); +MODULE_AUTHOR("Sylwester Nawrocki "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/samsung/phy-exynos-pcie.c b/drivers/phy/samsung/phy-exynos-pcie.c new file mode 100644 index 000000000000..a89c12faff39 --- /dev/null +++ b/drivers/phy/samsung/phy-exynos-pcie.c @@ -0,0 +1,281 @@ +/* + * Samsung EXYNOS SoC series PCIe PHY driver + * + * Phy provider for PCIe controller on Exynos SoC series + * + * Copyright (C) 2017 Samsung Electronics Co., Ltd. + * Jaehoon Chung + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* PCIe Purple registers */ +#define PCIE_PHY_GLOBAL_RESET 0x000 +#define PCIE_PHY_COMMON_RESET 0x004 +#define PCIE_PHY_CMN_REG 0x008 +#define PCIE_PHY_MAC_RESET 0x00c +#define PCIE_PHY_PLL_LOCKED 0x010 +#define PCIE_PHY_TRSVREG_RESET 0x020 +#define PCIE_PHY_TRSV_RESET 0x024 + +/* PCIe PHY registers */ +#define PCIE_PHY_IMPEDANCE 0x004 +#define PCIE_PHY_PLL_DIV_0 0x008 +#define PCIE_PHY_PLL_BIAS 0x00c +#define PCIE_PHY_DCC_FEEDBACK 0x014 +#define PCIE_PHY_PLL_DIV_1 0x05c +#define PCIE_PHY_COMMON_POWER 0x064 +#define PCIE_PHY_COMMON_PD_CMN BIT(3) +#define PCIE_PHY_TRSV0_EMP_LVL 0x084 +#define PCIE_PHY_TRSV0_DRV_LVL 0x088 +#define PCIE_PHY_TRSV0_RXCDR 0x0ac +#define PCIE_PHY_TRSV0_POWER 0x0c4 +#define PCIE_PHY_TRSV0_PD_TSV BIT(7) +#define PCIE_PHY_TRSV0_LVCC 0x0dc +#define PCIE_PHY_TRSV1_EMP_LVL 0x144 +#define PCIE_PHY_TRSV1_RXCDR 0x16c +#define PCIE_PHY_TRSV1_POWER 0x184 +#define PCIE_PHY_TRSV1_PD_TSV BIT(7) +#define PCIE_PHY_TRSV1_LVCC 0x19c +#define PCIE_PHY_TRSV2_EMP_LVL 0x204 +#define PCIE_PHY_TRSV2_RXCDR 0x22c +#define PCIE_PHY_TRSV2_POWER 0x244 +#define PCIE_PHY_TRSV2_PD_TSV BIT(7) +#define PCIE_PHY_TRSV2_LVCC 0x25c +#define PCIE_PHY_TRSV3_EMP_LVL 0x2c4 +#define PCIE_PHY_TRSV3_RXCDR 0x2ec +#define PCIE_PHY_TRSV3_POWER 0x304 +#define PCIE_PHY_TRSV3_PD_TSV BIT(7) +#define PCIE_PHY_TRSV3_LVCC 0x31c + +struct exynos_pcie_phy_data { + const struct phy_ops *ops; +}; + +/* For Exynos pcie phy */ +struct exynos_pcie_phy { + const struct exynos_pcie_phy_data *drv_data; + void __iomem *phy_base; + void __iomem *blk_base; /* For exynos5440 */ +}; + +static void exynos_pcie_phy_writel(void __iomem *base, u32 val, u32 offset) +{ + writel(val, base + offset); +} + +static u32 exynos_pcie_phy_readl(void __iomem *base, u32 offset) +{ + return readl(base + offset); +} + +/* For Exynos5440 specific functions */ +static int exynos5440_pcie_phy_init(struct phy *phy) +{ + struct exynos_pcie_phy *ep = phy_get_drvdata(phy); + + /* DCC feedback control off */ + exynos_pcie_phy_writel(ep->phy_base, 0x29, PCIE_PHY_DCC_FEEDBACK); + + /* set TX/RX impedance */ + exynos_pcie_phy_writel(ep->phy_base, 0xd5, PCIE_PHY_IMPEDANCE); + + /* set 50Mhz PHY clock */ + exynos_pcie_phy_writel(ep->phy_base, 0x14, PCIE_PHY_PLL_DIV_0); + exynos_pcie_phy_writel(ep->phy_base, 0x12, PCIE_PHY_PLL_DIV_1); + + /* set TX Differential output for lane 0 */ + exynos_pcie_phy_writel(ep->phy_base, 0x7f, PCIE_PHY_TRSV0_DRV_LVL); + + /* set TX Pre-emphasis Level Control for lane 0 to minimum */ + exynos_pcie_phy_writel(ep->phy_base, 0x0, PCIE_PHY_TRSV0_EMP_LVL); + + /* set RX clock and data recovery bandwidth */ + exynos_pcie_phy_writel(ep->phy_base, 0xe7, PCIE_PHY_PLL_BIAS); + exynos_pcie_phy_writel(ep->phy_base, 0x82, PCIE_PHY_TRSV0_RXCDR); + exynos_pcie_phy_writel(ep->phy_base, 0x82, PCIE_PHY_TRSV1_RXCDR); + exynos_pcie_phy_writel(ep->phy_base, 0x82, PCIE_PHY_TRSV2_RXCDR); + exynos_pcie_phy_writel(ep->phy_base, 0x82, PCIE_PHY_TRSV3_RXCDR); + + /* change TX Pre-emphasis Level Control for lanes */ + exynos_pcie_phy_writel(ep->phy_base, 0x39, PCIE_PHY_TRSV0_EMP_LVL); + exynos_pcie_phy_writel(ep->phy_base, 0x39, PCIE_PHY_TRSV1_EMP_LVL); + exynos_pcie_phy_writel(ep->phy_base, 0x39, PCIE_PHY_TRSV2_EMP_LVL); + exynos_pcie_phy_writel(ep->phy_base, 0x39, PCIE_PHY_TRSV3_EMP_LVL); + + /* set LVCC */ + exynos_pcie_phy_writel(ep->phy_base, 0x20, PCIE_PHY_TRSV0_LVCC); + exynos_pcie_phy_writel(ep->phy_base, 0xa0, PCIE_PHY_TRSV1_LVCC); + exynos_pcie_phy_writel(ep->phy_base, 0xa0, PCIE_PHY_TRSV2_LVCC); + exynos_pcie_phy_writel(ep->phy_base, 0xa0, PCIE_PHY_TRSV3_LVCC); + + /* pulse for common reset */ + exynos_pcie_phy_writel(ep->blk_base, 1, PCIE_PHY_COMMON_RESET); + udelay(500); + exynos_pcie_phy_writel(ep->blk_base, 0, PCIE_PHY_COMMON_RESET); + + return 0; +} + +static int exynos5440_pcie_phy_power_on(struct phy *phy) +{ + struct exynos_pcie_phy *ep = phy_get_drvdata(phy); + u32 val; + + exynos_pcie_phy_writel(ep->blk_base, 0, PCIE_PHY_COMMON_RESET); + exynos_pcie_phy_writel(ep->blk_base, 0, PCIE_PHY_CMN_REG); + exynos_pcie_phy_writel(ep->blk_base, 0, PCIE_PHY_TRSVREG_RESET); + exynos_pcie_phy_writel(ep->blk_base, 0, PCIE_PHY_TRSV_RESET); + + val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_COMMON_POWER); + val &= ~PCIE_PHY_COMMON_PD_CMN; + exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_COMMON_POWER); + + val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_TRSV0_POWER); + val &= ~PCIE_PHY_TRSV0_PD_TSV; + exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_TRSV0_POWER); + + val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_TRSV1_POWER); + val &= ~PCIE_PHY_TRSV1_PD_TSV; + exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_TRSV1_POWER); + + val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_TRSV2_POWER); + val &= ~PCIE_PHY_TRSV2_PD_TSV; + exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_TRSV2_POWER); + + val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_TRSV3_POWER); + val &= ~PCIE_PHY_TRSV3_PD_TSV; + exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_TRSV3_POWER); + + return 0; +} + +static int exynos5440_pcie_phy_power_off(struct phy *phy) +{ + struct exynos_pcie_phy *ep = phy_get_drvdata(phy); + u32 val; + + if (readl_poll_timeout(ep->phy_base + PCIE_PHY_PLL_LOCKED, val, + (val != 0), 1, 500)) { + dev_err(&phy->dev, "PLL Locked: 0x%x\n", val); + return -ETIMEDOUT; + } + + val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_COMMON_POWER); + val |= PCIE_PHY_COMMON_PD_CMN; + exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_COMMON_POWER); + + val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_TRSV0_POWER); + val |= PCIE_PHY_TRSV0_PD_TSV; + exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_TRSV0_POWER); + + val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_TRSV1_POWER); + val |= PCIE_PHY_TRSV1_PD_TSV; + exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_TRSV1_POWER); + + val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_TRSV2_POWER); + val |= PCIE_PHY_TRSV2_PD_TSV; + exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_TRSV2_POWER); + + val = exynos_pcie_phy_readl(ep->phy_base, PCIE_PHY_TRSV3_POWER); + val |= PCIE_PHY_TRSV3_PD_TSV; + exynos_pcie_phy_writel(ep->phy_base, val, PCIE_PHY_TRSV3_POWER); + + return 0; +} + +static int exynos5440_pcie_phy_reset(struct phy *phy) +{ + struct exynos_pcie_phy *ep = phy_get_drvdata(phy); + + exynos_pcie_phy_writel(ep->blk_base, 0, PCIE_PHY_MAC_RESET); + exynos_pcie_phy_writel(ep->blk_base, 1, PCIE_PHY_GLOBAL_RESET); + exynos_pcie_phy_writel(ep->blk_base, 0, PCIE_PHY_GLOBAL_RESET); + + return 0; +} + +static const struct phy_ops exynos5440_phy_ops = { + .init = exynos5440_pcie_phy_init, + .power_on = exynos5440_pcie_phy_power_on, + .power_off = exynos5440_pcie_phy_power_off, + .reset = exynos5440_pcie_phy_reset, + .owner = THIS_MODULE, +}; + +static const struct exynos_pcie_phy_data exynos5440_pcie_phy_data = { + .ops = &exynos5440_phy_ops, +}; + +static const struct of_device_id exynos_pcie_phy_match[] = { + { + .compatible = "samsung,exynos5440-pcie-phy", + .data = &exynos5440_pcie_phy_data, + }, + {}, +}; + +static int exynos_pcie_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct exynos_pcie_phy *exynos_phy; + struct phy *generic_phy; + struct phy_provider *phy_provider; + struct resource *res; + const struct exynos_pcie_phy_data *drv_data; + + drv_data = of_device_get_match_data(dev); + if (!drv_data) + return -ENODEV; + + exynos_phy = devm_kzalloc(dev, sizeof(*exynos_phy), GFP_KERNEL); + if (!exynos_phy) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + exynos_phy->phy_base = devm_ioremap_resource(dev, res); + if (IS_ERR(exynos_phy->phy_base)) + return PTR_ERR(exynos_phy->phy_base); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + exynos_phy->blk_base = devm_ioremap_resource(dev, res); + if (IS_ERR(exynos_phy->blk_base)) + return PTR_ERR(exynos_phy->blk_base); + + exynos_phy->drv_data = drv_data; + + generic_phy = devm_phy_create(dev, dev->of_node, drv_data->ops); + if (IS_ERR(generic_phy)) { + dev_err(dev, "failed to create PHY\n"); + return PTR_ERR(generic_phy); + } + + phy_set_drvdata(generic_phy, exynos_phy); + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static struct platform_driver exynos_pcie_phy_driver = { + .probe = exynos_pcie_phy_probe, + .driver = { + .of_match_table = exynos_pcie_phy_match, + .name = "exynos_pcie_phy", + } +}; + +builtin_platform_driver(exynos_pcie_phy_driver); diff --git a/drivers/phy/samsung/phy-exynos4210-usb2.c b/drivers/phy/samsung/phy-exynos4210-usb2.c new file mode 100644 index 000000000000..1f50e1004828 --- /dev/null +++ b/drivers/phy/samsung/phy-exynos4210-usb2.c @@ -0,0 +1,260 @@ +/* + * Samsung SoC USB 1.1/2.0 PHY driver - Exynos 4210 support + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Author: Kamil Debski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include "phy-samsung-usb2.h" + +/* Exynos USB PHY registers */ + +/* PHY power control */ +#define EXYNOS_4210_UPHYPWR 0x0 + +#define EXYNOS_4210_UPHYPWR_PHY0_SUSPEND BIT(0) +#define EXYNOS_4210_UPHYPWR_PHY0_PWR BIT(3) +#define EXYNOS_4210_UPHYPWR_PHY0_OTG_PWR BIT(4) +#define EXYNOS_4210_UPHYPWR_PHY0_SLEEP BIT(5) +#define EXYNOS_4210_UPHYPWR_PHY0 ( \ + EXYNOS_4210_UPHYPWR_PHY0_SUSPEND | \ + EXYNOS_4210_UPHYPWR_PHY0_PWR | \ + EXYNOS_4210_UPHYPWR_PHY0_OTG_PWR | \ + EXYNOS_4210_UPHYPWR_PHY0_SLEEP) + +#define EXYNOS_4210_UPHYPWR_PHY1_SUSPEND BIT(6) +#define EXYNOS_4210_UPHYPWR_PHY1_PWR BIT(7) +#define EXYNOS_4210_UPHYPWR_PHY1_SLEEP BIT(8) +#define EXYNOS_4210_UPHYPWR_PHY1 ( \ + EXYNOS_4210_UPHYPWR_PHY1_SUSPEND | \ + EXYNOS_4210_UPHYPWR_PHY1_PWR | \ + EXYNOS_4210_UPHYPWR_PHY1_SLEEP) + +#define EXYNOS_4210_UPHYPWR_HSIC0_SUSPEND BIT(9) +#define EXYNOS_4210_UPHYPWR_HSIC0_SLEEP BIT(10) +#define EXYNOS_4210_UPHYPWR_HSIC0 ( \ + EXYNOS_4210_UPHYPWR_HSIC0_SUSPEND | \ + EXYNOS_4210_UPHYPWR_HSIC0_SLEEP) + +#define EXYNOS_4210_UPHYPWR_HSIC1_SUSPEND BIT(11) +#define EXYNOS_4210_UPHYPWR_HSIC1_SLEEP BIT(12) +#define EXYNOS_4210_UPHYPWR_HSIC1 ( \ + EXYNOS_4210_UPHYPWR_HSIC1_SUSPEND | \ + EXYNOS_4210_UPHYPWR_HSIC1_SLEEP) + +/* PHY clock control */ +#define EXYNOS_4210_UPHYCLK 0x4 + +#define EXYNOS_4210_UPHYCLK_PHYFSEL_MASK (0x3 << 0) +#define EXYNOS_4210_UPHYCLK_PHYFSEL_OFFSET 0 +#define EXYNOS_4210_UPHYCLK_PHYFSEL_48MHZ (0x0 << 0) +#define EXYNOS_4210_UPHYCLK_PHYFSEL_24MHZ (0x3 << 0) +#define EXYNOS_4210_UPHYCLK_PHYFSEL_12MHZ (0x2 << 0) + +#define EXYNOS_4210_UPHYCLK_PHY0_ID_PULLUP BIT(2) +#define EXYNOS_4210_UPHYCLK_PHY0_COMMON_ON BIT(4) +#define EXYNOS_4210_UPHYCLK_PHY1_COMMON_ON BIT(7) + +/* PHY reset control */ +#define EXYNOS_4210_UPHYRST 0x8 + +#define EXYNOS_4210_URSTCON_PHY0 BIT(0) +#define EXYNOS_4210_URSTCON_OTG_HLINK BIT(1) +#define EXYNOS_4210_URSTCON_OTG_PHYLINK BIT(2) +#define EXYNOS_4210_URSTCON_PHY1_ALL BIT(3) +#define EXYNOS_4210_URSTCON_PHY1_P0 BIT(4) +#define EXYNOS_4210_URSTCON_PHY1_P1P2 BIT(5) +#define EXYNOS_4210_URSTCON_HOST_LINK_ALL BIT(6) +#define EXYNOS_4210_URSTCON_HOST_LINK_P0 BIT(7) +#define EXYNOS_4210_URSTCON_HOST_LINK_P1 BIT(8) +#define EXYNOS_4210_URSTCON_HOST_LINK_P2 BIT(9) + +/* Isolation, configured in the power management unit */ +#define EXYNOS_4210_USB_ISOL_DEVICE_OFFSET 0x704 +#define EXYNOS_4210_USB_ISOL_DEVICE BIT(0) +#define EXYNOS_4210_USB_ISOL_HOST_OFFSET 0x708 +#define EXYNOS_4210_USB_ISOL_HOST BIT(0) + +/* USBYPHY1 Floating prevention */ +#define EXYNOS_4210_UPHY1CON 0x34 +#define EXYNOS_4210_UPHY1CON_FLOAT_PREVENTION 0x1 + +/* Mode switching SUB Device <-> Host */ +#define EXYNOS_4210_MODE_SWITCH_OFFSET 0x21c +#define EXYNOS_4210_MODE_SWITCH_MASK 1 +#define EXYNOS_4210_MODE_SWITCH_DEVICE 0 +#define EXYNOS_4210_MODE_SWITCH_HOST 1 + +enum exynos4210_phy_id { + EXYNOS4210_DEVICE, + EXYNOS4210_HOST, + EXYNOS4210_HSIC0, + EXYNOS4210_HSIC1, + EXYNOS4210_NUM_PHYS, +}; + +/* + * exynos4210_rate_to_clk() converts the supplied clock rate to the value that + * can be written to the phy register. + */ +static int exynos4210_rate_to_clk(unsigned long rate, u32 *reg) +{ + switch (rate) { + case 12 * MHZ: + *reg = EXYNOS_4210_UPHYCLK_PHYFSEL_12MHZ; + break; + case 24 * MHZ: + *reg = EXYNOS_4210_UPHYCLK_PHYFSEL_24MHZ; + break; + case 48 * MHZ: + *reg = EXYNOS_4210_UPHYCLK_PHYFSEL_48MHZ; + break; + default: + return -EINVAL; + } + + return 0; +} + +static void exynos4210_isol(struct samsung_usb2_phy_instance *inst, bool on) +{ + struct samsung_usb2_phy_driver *drv = inst->drv; + u32 offset; + u32 mask; + + switch (inst->cfg->id) { + case EXYNOS4210_DEVICE: + offset = EXYNOS_4210_USB_ISOL_DEVICE_OFFSET; + mask = EXYNOS_4210_USB_ISOL_DEVICE; + break; + case EXYNOS4210_HOST: + offset = EXYNOS_4210_USB_ISOL_HOST_OFFSET; + mask = EXYNOS_4210_USB_ISOL_HOST; + break; + default: + return; + } + + regmap_update_bits(drv->reg_pmu, offset, mask, on ? 0 : mask); +} + +static void exynos4210_phy_pwr(struct samsung_usb2_phy_instance *inst, bool on) +{ + struct samsung_usb2_phy_driver *drv = inst->drv; + u32 rstbits = 0; + u32 phypwr = 0; + u32 rst; + u32 pwr; + u32 clk; + + switch (inst->cfg->id) { + case EXYNOS4210_DEVICE: + phypwr = EXYNOS_4210_UPHYPWR_PHY0; + rstbits = EXYNOS_4210_URSTCON_PHY0; + break; + case EXYNOS4210_HOST: + phypwr = EXYNOS_4210_UPHYPWR_PHY1; + rstbits = EXYNOS_4210_URSTCON_PHY1_ALL | + EXYNOS_4210_URSTCON_PHY1_P0 | + EXYNOS_4210_URSTCON_PHY1_P1P2 | + EXYNOS_4210_URSTCON_HOST_LINK_ALL | + EXYNOS_4210_URSTCON_HOST_LINK_P0; + writel(on, drv->reg_phy + EXYNOS_4210_UPHY1CON); + break; + case EXYNOS4210_HSIC0: + phypwr = EXYNOS_4210_UPHYPWR_HSIC0; + rstbits = EXYNOS_4210_URSTCON_PHY1_P1P2 | + EXYNOS_4210_URSTCON_HOST_LINK_P1; + break; + case EXYNOS4210_HSIC1: + phypwr = EXYNOS_4210_UPHYPWR_HSIC1; + rstbits = EXYNOS_4210_URSTCON_PHY1_P1P2 | + EXYNOS_4210_URSTCON_HOST_LINK_P2; + break; + } + + if (on) { + clk = readl(drv->reg_phy + EXYNOS_4210_UPHYCLK); + clk &= ~EXYNOS_4210_UPHYCLK_PHYFSEL_MASK; + clk |= drv->ref_reg_val << EXYNOS_4210_UPHYCLK_PHYFSEL_OFFSET; + writel(clk, drv->reg_phy + EXYNOS_4210_UPHYCLK); + + pwr = readl(drv->reg_phy + EXYNOS_4210_UPHYPWR); + pwr &= ~phypwr; + writel(pwr, drv->reg_phy + EXYNOS_4210_UPHYPWR); + + rst = readl(drv->reg_phy + EXYNOS_4210_UPHYRST); + rst |= rstbits; + writel(rst, drv->reg_phy + EXYNOS_4210_UPHYRST); + udelay(10); + rst &= ~rstbits; + writel(rst, drv->reg_phy + EXYNOS_4210_UPHYRST); + /* The following delay is necessary for the reset sequence to be + * completed */ + udelay(80); + } else { + pwr = readl(drv->reg_phy + EXYNOS_4210_UPHYPWR); + pwr |= phypwr; + writel(pwr, drv->reg_phy + EXYNOS_4210_UPHYPWR); + } +} + +static int exynos4210_power_on(struct samsung_usb2_phy_instance *inst) +{ + /* Order of initialisation is important - first power then isolation */ + exynos4210_phy_pwr(inst, 1); + exynos4210_isol(inst, 0); + + return 0; +} + +static int exynos4210_power_off(struct samsung_usb2_phy_instance *inst) +{ + exynos4210_isol(inst, 1); + exynos4210_phy_pwr(inst, 0); + + return 0; +} + + +static const struct samsung_usb2_common_phy exynos4210_phys[] = { + { + .label = "device", + .id = EXYNOS4210_DEVICE, + .power_on = exynos4210_power_on, + .power_off = exynos4210_power_off, + }, + { + .label = "host", + .id = EXYNOS4210_HOST, + .power_on = exynos4210_power_on, + .power_off = exynos4210_power_off, + }, + { + .label = "hsic0", + .id = EXYNOS4210_HSIC0, + .power_on = exynos4210_power_on, + .power_off = exynos4210_power_off, + }, + { + .label = "hsic1", + .id = EXYNOS4210_HSIC1, + .power_on = exynos4210_power_on, + .power_off = exynos4210_power_off, + }, +}; + +const struct samsung_usb2_phy_config exynos4210_usb2_phy_config = { + .has_mode_switch = 0, + .num_phys = EXYNOS4210_NUM_PHYS, + .phys = exynos4210_phys, + .rate_to_clk = exynos4210_rate_to_clk, +}; diff --git a/drivers/phy/samsung/phy-exynos4x12-usb2.c b/drivers/phy/samsung/phy-exynos4x12-usb2.c new file mode 100644 index 000000000000..7f27a91acf87 --- /dev/null +++ b/drivers/phy/samsung/phy-exynos4x12-usb2.c @@ -0,0 +1,378 @@ +/* + * Samsung SoC USB 1.1/2.0 PHY driver - Exynos 4x12 support + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Author: Kamil Debski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include "phy-samsung-usb2.h" + +/* Exynos USB PHY registers */ + +/* PHY power control */ +#define EXYNOS_4x12_UPHYPWR 0x0 + +#define EXYNOS_4x12_UPHYPWR_PHY0_SUSPEND BIT(0) +#define EXYNOS_4x12_UPHYPWR_PHY0_PWR BIT(3) +#define EXYNOS_4x12_UPHYPWR_PHY0_OTG_PWR BIT(4) +#define EXYNOS_4x12_UPHYPWR_PHY0_SLEEP BIT(5) +#define EXYNOS_4x12_UPHYPWR_PHY0 ( \ + EXYNOS_4x12_UPHYPWR_PHY0_SUSPEND | \ + EXYNOS_4x12_UPHYPWR_PHY0_PWR | \ + EXYNOS_4x12_UPHYPWR_PHY0_OTG_PWR | \ + EXYNOS_4x12_UPHYPWR_PHY0_SLEEP) + +#define EXYNOS_4x12_UPHYPWR_PHY1_SUSPEND BIT(6) +#define EXYNOS_4x12_UPHYPWR_PHY1_PWR BIT(7) +#define EXYNOS_4x12_UPHYPWR_PHY1_SLEEP BIT(8) +#define EXYNOS_4x12_UPHYPWR_PHY1 ( \ + EXYNOS_4x12_UPHYPWR_PHY1_SUSPEND | \ + EXYNOS_4x12_UPHYPWR_PHY1_PWR | \ + EXYNOS_4x12_UPHYPWR_PHY1_SLEEP) + +#define EXYNOS_4x12_UPHYPWR_HSIC0_SUSPEND BIT(9) +#define EXYNOS_4x12_UPHYPWR_HSIC0_PWR BIT(10) +#define EXYNOS_4x12_UPHYPWR_HSIC0_SLEEP BIT(11) +#define EXYNOS_4x12_UPHYPWR_HSIC0 ( \ + EXYNOS_4x12_UPHYPWR_HSIC0_SUSPEND | \ + EXYNOS_4x12_UPHYPWR_HSIC0_PWR | \ + EXYNOS_4x12_UPHYPWR_HSIC0_SLEEP) + +#define EXYNOS_4x12_UPHYPWR_HSIC1_SUSPEND BIT(12) +#define EXYNOS_4x12_UPHYPWR_HSIC1_PWR BIT(13) +#define EXYNOS_4x12_UPHYPWR_HSIC1_SLEEP BIT(14) +#define EXYNOS_4x12_UPHYPWR_HSIC1 ( \ + EXYNOS_4x12_UPHYPWR_HSIC1_SUSPEND | \ + EXYNOS_4x12_UPHYPWR_HSIC1_PWR | \ + EXYNOS_4x12_UPHYPWR_HSIC1_SLEEP) + +/* PHY clock control */ +#define EXYNOS_4x12_UPHYCLK 0x4 + +#define EXYNOS_4x12_UPHYCLK_PHYFSEL_MASK (0x7 << 0) +#define EXYNOS_4x12_UPHYCLK_PHYFSEL_OFFSET 0 +#define EXYNOS_4x12_UPHYCLK_PHYFSEL_9MHZ6 (0x0 << 0) +#define EXYNOS_4x12_UPHYCLK_PHYFSEL_10MHZ (0x1 << 0) +#define EXYNOS_4x12_UPHYCLK_PHYFSEL_12MHZ (0x2 << 0) +#define EXYNOS_4x12_UPHYCLK_PHYFSEL_19MHZ2 (0x3 << 0) +#define EXYNOS_4x12_UPHYCLK_PHYFSEL_20MHZ (0x4 << 0) +#define EXYNOS_4x12_UPHYCLK_PHYFSEL_24MHZ (0x5 << 0) +#define EXYNOS_4x12_UPHYCLK_PHYFSEL_50MHZ (0x7 << 0) + +#define EXYNOS_3250_UPHYCLK_REFCLKSEL (0x2 << 8) + +#define EXYNOS_4x12_UPHYCLK_PHY0_ID_PULLUP BIT(3) +#define EXYNOS_4x12_UPHYCLK_PHY0_COMMON_ON BIT(4) +#define EXYNOS_4x12_UPHYCLK_PHY1_COMMON_ON BIT(7) + +#define EXYNOS_4x12_UPHYCLK_HSIC_REFCLK_MASK (0x7f << 10) +#define EXYNOS_4x12_UPHYCLK_HSIC_REFCLK_OFFSET 10 +#define EXYNOS_4x12_UPHYCLK_HSIC_REFCLK_12MHZ (0x24 << 10) +#define EXYNOS_4x12_UPHYCLK_HSIC_REFCLK_15MHZ (0x1c << 10) +#define EXYNOS_4x12_UPHYCLK_HSIC_REFCLK_16MHZ (0x1a << 10) +#define EXYNOS_4x12_UPHYCLK_HSIC_REFCLK_19MHZ2 (0x15 << 10) +#define EXYNOS_4x12_UPHYCLK_HSIC_REFCLK_20MHZ (0x14 << 10) + +/* PHY reset control */ +#define EXYNOS_4x12_UPHYRST 0x8 + +#define EXYNOS_4x12_URSTCON_PHY0 BIT(0) +#define EXYNOS_4x12_URSTCON_OTG_HLINK BIT(1) +#define EXYNOS_4x12_URSTCON_OTG_PHYLINK BIT(2) +#define EXYNOS_4x12_URSTCON_HOST_PHY BIT(3) +/* The following bit defines are presented in the + * order taken from the Exynos4412 reference manual. + * + * During experiments with the hardware and debugging + * it was determined that the hardware behaves contrary + * to the manual. + * + * The following bit values were chaned accordingly to the + * results of real hardware experiments. + */ +#define EXYNOS_4x12_URSTCON_PHY1 BIT(4) +#define EXYNOS_4x12_URSTCON_HSIC0 BIT(6) +#define EXYNOS_4x12_URSTCON_HSIC1 BIT(5) +#define EXYNOS_4x12_URSTCON_HOST_LINK_ALL BIT(7) +#define EXYNOS_4x12_URSTCON_HOST_LINK_P0 BIT(10) +#define EXYNOS_4x12_URSTCON_HOST_LINK_P1 BIT(9) +#define EXYNOS_4x12_URSTCON_HOST_LINK_P2 BIT(8) + +/* Isolation, configured in the power management unit */ +#define EXYNOS_4x12_USB_ISOL_OFFSET 0x704 +#define EXYNOS_4x12_USB_ISOL_OTG BIT(0) +#define EXYNOS_4x12_USB_ISOL_HSIC0_OFFSET 0x708 +#define EXYNOS_4x12_USB_ISOL_HSIC0 BIT(0) +#define EXYNOS_4x12_USB_ISOL_HSIC1_OFFSET 0x70c +#define EXYNOS_4x12_USB_ISOL_HSIC1 BIT(0) + +/* Mode switching SUB Device <-> Host */ +#define EXYNOS_4x12_MODE_SWITCH_OFFSET 0x21c +#define EXYNOS_4x12_MODE_SWITCH_MASK 1 +#define EXYNOS_4x12_MODE_SWITCH_DEVICE 0 +#define EXYNOS_4x12_MODE_SWITCH_HOST 1 + +enum exynos4x12_phy_id { + EXYNOS4x12_DEVICE, + EXYNOS4x12_HOST, + EXYNOS4x12_HSIC0, + EXYNOS4x12_HSIC1, + EXYNOS4x12_NUM_PHYS, +}; + +/* + * exynos4x12_rate_to_clk() converts the supplied clock rate to the value that + * can be written to the phy register. + */ +static int exynos4x12_rate_to_clk(unsigned long rate, u32 *reg) +{ + /* EXYNOS_4x12_UPHYCLK_PHYFSEL_MASK */ + + switch (rate) { + case 9600 * KHZ: + *reg = EXYNOS_4x12_UPHYCLK_PHYFSEL_9MHZ6; + break; + case 10 * MHZ: + *reg = EXYNOS_4x12_UPHYCLK_PHYFSEL_10MHZ; + break; + case 12 * MHZ: + *reg = EXYNOS_4x12_UPHYCLK_PHYFSEL_12MHZ; + break; + case 19200 * KHZ: + *reg = EXYNOS_4x12_UPHYCLK_PHYFSEL_19MHZ2; + break; + case 20 * MHZ: + *reg = EXYNOS_4x12_UPHYCLK_PHYFSEL_20MHZ; + break; + case 24 * MHZ: + *reg = EXYNOS_4x12_UPHYCLK_PHYFSEL_24MHZ; + break; + case 50 * MHZ: + *reg = EXYNOS_4x12_UPHYCLK_PHYFSEL_50MHZ; + break; + default: + return -EINVAL; + } + + return 0; +} + +static void exynos4x12_isol(struct samsung_usb2_phy_instance *inst, bool on) +{ + struct samsung_usb2_phy_driver *drv = inst->drv; + u32 offset; + u32 mask; + + switch (inst->cfg->id) { + case EXYNOS4x12_DEVICE: + case EXYNOS4x12_HOST: + offset = EXYNOS_4x12_USB_ISOL_OFFSET; + mask = EXYNOS_4x12_USB_ISOL_OTG; + break; + case EXYNOS4x12_HSIC0: + offset = EXYNOS_4x12_USB_ISOL_HSIC0_OFFSET; + mask = EXYNOS_4x12_USB_ISOL_HSIC0; + break; + case EXYNOS4x12_HSIC1: + offset = EXYNOS_4x12_USB_ISOL_HSIC1_OFFSET; + mask = EXYNOS_4x12_USB_ISOL_HSIC1; + break; + default: + return; + } + + regmap_update_bits(drv->reg_pmu, offset, mask, on ? 0 : mask); +} + +static void exynos4x12_setup_clk(struct samsung_usb2_phy_instance *inst) +{ + struct samsung_usb2_phy_driver *drv = inst->drv; + u32 clk; + + clk = readl(drv->reg_phy + EXYNOS_4x12_UPHYCLK); + clk &= ~EXYNOS_4x12_UPHYCLK_PHYFSEL_MASK; + + if (drv->cfg->has_refclk_sel) + clk = EXYNOS_3250_UPHYCLK_REFCLKSEL; + + clk |= drv->ref_reg_val << EXYNOS_4x12_UPHYCLK_PHYFSEL_OFFSET; + clk |= EXYNOS_4x12_UPHYCLK_PHY1_COMMON_ON; + writel(clk, drv->reg_phy + EXYNOS_4x12_UPHYCLK); +} + +static void exynos4x12_phy_pwr(struct samsung_usb2_phy_instance *inst, bool on) +{ + struct samsung_usb2_phy_driver *drv = inst->drv; + u32 rstbits = 0; + u32 phypwr = 0; + u32 rst; + u32 pwr; + + switch (inst->cfg->id) { + case EXYNOS4x12_DEVICE: + phypwr = EXYNOS_4x12_UPHYPWR_PHY0; + rstbits = EXYNOS_4x12_URSTCON_PHY0; + break; + case EXYNOS4x12_HOST: + phypwr = EXYNOS_4x12_UPHYPWR_PHY1; + rstbits = EXYNOS_4x12_URSTCON_HOST_PHY | + EXYNOS_4x12_URSTCON_PHY1 | + EXYNOS_4x12_URSTCON_HOST_LINK_P0; + break; + case EXYNOS4x12_HSIC0: + phypwr = EXYNOS_4x12_UPHYPWR_HSIC0; + rstbits = EXYNOS_4x12_URSTCON_HSIC0 | + EXYNOS_4x12_URSTCON_HOST_LINK_P1; + break; + case EXYNOS4x12_HSIC1: + phypwr = EXYNOS_4x12_UPHYPWR_HSIC1; + rstbits = EXYNOS_4x12_URSTCON_HSIC1 | + EXYNOS_4x12_URSTCON_HOST_LINK_P1; + break; + } + + if (on) { + pwr = readl(drv->reg_phy + EXYNOS_4x12_UPHYPWR); + pwr &= ~phypwr; + writel(pwr, drv->reg_phy + EXYNOS_4x12_UPHYPWR); + + rst = readl(drv->reg_phy + EXYNOS_4x12_UPHYRST); + rst |= rstbits; + writel(rst, drv->reg_phy + EXYNOS_4x12_UPHYRST); + udelay(10); + rst &= ~rstbits; + writel(rst, drv->reg_phy + EXYNOS_4x12_UPHYRST); + /* The following delay is necessary for the reset sequence to be + * completed */ + udelay(80); + } else { + pwr = readl(drv->reg_phy + EXYNOS_4x12_UPHYPWR); + pwr |= phypwr; + writel(pwr, drv->reg_phy + EXYNOS_4x12_UPHYPWR); + } +} + +static void exynos4x12_power_on_int(struct samsung_usb2_phy_instance *inst) +{ + if (inst->int_cnt++ > 0) + return; + + exynos4x12_setup_clk(inst); + exynos4x12_isol(inst, 0); + exynos4x12_phy_pwr(inst, 1); +} + +static int exynos4x12_power_on(struct samsung_usb2_phy_instance *inst) +{ + struct samsung_usb2_phy_driver *drv = inst->drv; + + if (inst->ext_cnt++ > 0) + return 0; + + if (inst->cfg->id == EXYNOS4x12_HOST) { + regmap_update_bits(drv->reg_sys, EXYNOS_4x12_MODE_SWITCH_OFFSET, + EXYNOS_4x12_MODE_SWITCH_MASK, + EXYNOS_4x12_MODE_SWITCH_HOST); + exynos4x12_power_on_int(&drv->instances[EXYNOS4x12_DEVICE]); + } + + if (inst->cfg->id == EXYNOS4x12_DEVICE && drv->cfg->has_mode_switch) + regmap_update_bits(drv->reg_sys, EXYNOS_4x12_MODE_SWITCH_OFFSET, + EXYNOS_4x12_MODE_SWITCH_MASK, + EXYNOS_4x12_MODE_SWITCH_DEVICE); + + if (inst->cfg->id == EXYNOS4x12_HSIC0 || + inst->cfg->id == EXYNOS4x12_HSIC1) { + exynos4x12_power_on_int(&drv->instances[EXYNOS4x12_DEVICE]); + exynos4x12_power_on_int(&drv->instances[EXYNOS4x12_HOST]); + } + + exynos4x12_power_on_int(inst); + + return 0; +} + +static void exynos4x12_power_off_int(struct samsung_usb2_phy_instance *inst) +{ + if (inst->int_cnt-- > 1) + return; + + exynos4x12_isol(inst, 1); + exynos4x12_phy_pwr(inst, 0); +} + +static int exynos4x12_power_off(struct samsung_usb2_phy_instance *inst) +{ + struct samsung_usb2_phy_driver *drv = inst->drv; + + if (inst->ext_cnt-- > 1) + return 0; + + if (inst->cfg->id == EXYNOS4x12_DEVICE && drv->cfg->has_mode_switch) + regmap_update_bits(drv->reg_sys, EXYNOS_4x12_MODE_SWITCH_OFFSET, + EXYNOS_4x12_MODE_SWITCH_MASK, + EXYNOS_4x12_MODE_SWITCH_HOST); + + if (inst->cfg->id == EXYNOS4x12_HOST) + exynos4x12_power_off_int(&drv->instances[EXYNOS4x12_DEVICE]); + + if (inst->cfg->id == EXYNOS4x12_HSIC0 || + inst->cfg->id == EXYNOS4x12_HSIC1) { + exynos4x12_power_off_int(&drv->instances[EXYNOS4x12_DEVICE]); + exynos4x12_power_off_int(&drv->instances[EXYNOS4x12_HOST]); + } + + exynos4x12_power_off_int(inst); + + return 0; +} + + +static const struct samsung_usb2_common_phy exynos4x12_phys[] = { + { + .label = "device", + .id = EXYNOS4x12_DEVICE, + .power_on = exynos4x12_power_on, + .power_off = exynos4x12_power_off, + }, + { + .label = "host", + .id = EXYNOS4x12_HOST, + .power_on = exynos4x12_power_on, + .power_off = exynos4x12_power_off, + }, + { + .label = "hsic0", + .id = EXYNOS4x12_HSIC0, + .power_on = exynos4x12_power_on, + .power_off = exynos4x12_power_off, + }, + { + .label = "hsic1", + .id = EXYNOS4x12_HSIC1, + .power_on = exynos4x12_power_on, + .power_off = exynos4x12_power_off, + }, +}; + +const struct samsung_usb2_phy_config exynos3250_usb2_phy_config = { + .has_refclk_sel = 1, + .num_phys = 1, + .phys = exynos4x12_phys, + .rate_to_clk = exynos4x12_rate_to_clk, +}; + +const struct samsung_usb2_phy_config exynos4x12_usb2_phy_config = { + .has_mode_switch = 1, + .num_phys = EXYNOS4x12_NUM_PHYS, + .phys = exynos4x12_phys, + .rate_to_clk = exynos4x12_rate_to_clk, +}; diff --git a/drivers/phy/samsung/phy-exynos5-usbdrd.c b/drivers/phy/samsung/phy-exynos5-usbdrd.c new file mode 100644 index 000000000000..7c41daa2c625 --- /dev/null +++ b/drivers/phy/samsung/phy-exynos5-usbdrd.c @@ -0,0 +1,782 @@ +/* + * Samsung EXYNOS5 SoC series USB DRD PHY driver + * + * Phy provider for USB 3.0 DRD controller on Exynos5 SoC series + * + * Copyright (C) 2014 Samsung Electronics Co., Ltd. + * Author: Vivek Gautam + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Exynos USB PHY registers */ +#define EXYNOS5_FSEL_9MHZ6 0x0 +#define EXYNOS5_FSEL_10MHZ 0x1 +#define EXYNOS5_FSEL_12MHZ 0x2 +#define EXYNOS5_FSEL_19MHZ2 0x3 +#define EXYNOS5_FSEL_20MHZ 0x4 +#define EXYNOS5_FSEL_24MHZ 0x5 +#define EXYNOS5_FSEL_50MHZ 0x7 + +/* EXYNOS5: USB 3.0 DRD PHY registers */ +#define EXYNOS5_DRD_LINKSYSTEM 0x04 + +#define LINKSYSTEM_FLADJ_MASK (0x3f << 1) +#define LINKSYSTEM_FLADJ(_x) ((_x) << 1) +#define LINKSYSTEM_XHCI_VERSION_CONTROL BIT(27) + +#define EXYNOS5_DRD_PHYUTMI 0x08 + +#define PHYUTMI_OTGDISABLE BIT(6) +#define PHYUTMI_FORCESUSPEND BIT(1) +#define PHYUTMI_FORCESLEEP BIT(0) + +#define EXYNOS5_DRD_PHYPIPE 0x0c + +#define EXYNOS5_DRD_PHYCLKRST 0x10 + +#define PHYCLKRST_EN_UTMISUSPEND BIT(31) + +#define PHYCLKRST_SSC_REFCLKSEL_MASK (0xff << 23) +#define PHYCLKRST_SSC_REFCLKSEL(_x) ((_x) << 23) + +#define PHYCLKRST_SSC_RANGE_MASK (0x03 << 21) +#define PHYCLKRST_SSC_RANGE(_x) ((_x) << 21) + +#define PHYCLKRST_SSC_EN BIT(20) +#define PHYCLKRST_REF_SSP_EN BIT(19) +#define PHYCLKRST_REF_CLKDIV2 BIT(18) + +#define PHYCLKRST_MPLL_MULTIPLIER_MASK (0x7f << 11) +#define PHYCLKRST_MPLL_MULTIPLIER_100MHZ_REF (0x19 << 11) +#define PHYCLKRST_MPLL_MULTIPLIER_50M_REF (0x32 << 11) +#define PHYCLKRST_MPLL_MULTIPLIER_24MHZ_REF (0x68 << 11) +#define PHYCLKRST_MPLL_MULTIPLIER_20MHZ_REF (0x7d << 11) +#define PHYCLKRST_MPLL_MULTIPLIER_19200KHZ_REF (0x02 << 11) + +#define PHYCLKRST_FSEL_UTMI_MASK (0x7 << 5) +#define PHYCLKRST_FSEL_PIPE_MASK (0x7 << 8) +#define PHYCLKRST_FSEL(_x) ((_x) << 5) +#define PHYCLKRST_FSEL_PAD_100MHZ (0x27 << 5) +#define PHYCLKRST_FSEL_PAD_24MHZ (0x2a << 5) +#define PHYCLKRST_FSEL_PAD_20MHZ (0x31 << 5) +#define PHYCLKRST_FSEL_PAD_19_2MHZ (0x38 << 5) + +#define PHYCLKRST_RETENABLEN BIT(4) + +#define PHYCLKRST_REFCLKSEL_MASK (0x03 << 2) +#define PHYCLKRST_REFCLKSEL_PAD_REFCLK (0x2 << 2) +#define PHYCLKRST_REFCLKSEL_EXT_REFCLK (0x3 << 2) + +#define PHYCLKRST_PORTRESET BIT(1) +#define PHYCLKRST_COMMONONN BIT(0) + +#define EXYNOS5_DRD_PHYREG0 0x14 +#define EXYNOS5_DRD_PHYREG1 0x18 + +#define EXYNOS5_DRD_PHYPARAM0 0x1c + +#define PHYPARAM0_REF_USE_PAD BIT(31) +#define PHYPARAM0_REF_LOSLEVEL_MASK (0x1f << 26) +#define PHYPARAM0_REF_LOSLEVEL (0x9 << 26) + +#define EXYNOS5_DRD_PHYPARAM1 0x20 + +#define PHYPARAM1_PCS_TXDEEMPH_MASK (0x1f << 0) +#define PHYPARAM1_PCS_TXDEEMPH (0x1c) + +#define EXYNOS5_DRD_PHYTERM 0x24 + +#define EXYNOS5_DRD_PHYTEST 0x28 + +#define PHYTEST_POWERDOWN_SSP BIT(3) +#define PHYTEST_POWERDOWN_HSP BIT(2) + +#define EXYNOS5_DRD_PHYADP 0x2c + +#define EXYNOS5_DRD_PHYUTMICLKSEL 0x30 + +#define PHYUTMICLKSEL_UTMI_CLKSEL BIT(2) + +#define EXYNOS5_DRD_PHYRESUME 0x34 +#define EXYNOS5_DRD_LINKPORT 0x44 + +#define KHZ 1000 +#define MHZ (KHZ * KHZ) + +enum exynos5_usbdrd_phy_id { + EXYNOS5_DRDPHY_UTMI, + EXYNOS5_DRDPHY_PIPE3, + EXYNOS5_DRDPHYS_NUM, +}; + +struct phy_usb_instance; +struct exynos5_usbdrd_phy; + +struct exynos5_usbdrd_phy_config { + u32 id; + void (*phy_isol)(struct phy_usb_instance *inst, u32 on); + void (*phy_init)(struct exynos5_usbdrd_phy *phy_drd); + unsigned int (*set_refclk)(struct phy_usb_instance *inst); +}; + +struct exynos5_usbdrd_phy_drvdata { + const struct exynos5_usbdrd_phy_config *phy_cfg; + u32 pmu_offset_usbdrd0_phy; + u32 pmu_offset_usbdrd1_phy; + bool has_common_clk_gate; +}; + +/** + * struct exynos5_usbdrd_phy - driver data for USB 3.0 PHY + * @dev: pointer to device instance of this platform device + * @reg_phy: usb phy controller register memory base + * @clk: phy clock for register access + * @pipeclk: clock for pipe3 phy + * @utmiclk: clock for utmi+ phy + * @itpclk: clock for ITP generation + * @drv_data: pointer to SoC level driver data structure + * @phys[]: array for 'EXYNOS5_DRDPHYS_NUM' number of PHY + * instances each with its 'phy' and 'phy_cfg'. + * @extrefclk: frequency select settings when using 'separate + * reference clocks' for SS and HS operations + * @ref_clk: reference clock to PHY block from which PHY's + * operational clocks are derived + * vbus: VBUS regulator for phy + * vbus_boost: Boost regulator for VBUS present on few Exynos boards + */ +struct exynos5_usbdrd_phy { + struct device *dev; + void __iomem *reg_phy; + struct clk *clk; + struct clk *pipeclk; + struct clk *utmiclk; + struct clk *itpclk; + const struct exynos5_usbdrd_phy_drvdata *drv_data; + struct phy_usb_instance { + struct phy *phy; + u32 index; + struct regmap *reg_pmu; + u32 pmu_offset; + const struct exynos5_usbdrd_phy_config *phy_cfg; + } phys[EXYNOS5_DRDPHYS_NUM]; + u32 extrefclk; + struct clk *ref_clk; + struct regulator *vbus; + struct regulator *vbus_boost; +}; + +static inline +struct exynos5_usbdrd_phy *to_usbdrd_phy(struct phy_usb_instance *inst) +{ + return container_of((inst), struct exynos5_usbdrd_phy, + phys[(inst)->index]); +} + +/* + * exynos5_rate_to_clk() converts the supplied clock rate to the value that + * can be written to the phy register. + */ +static unsigned int exynos5_rate_to_clk(unsigned long rate, u32 *reg) +{ + /* EXYNOS5_FSEL_MASK */ + + switch (rate) { + case 9600 * KHZ: + *reg = EXYNOS5_FSEL_9MHZ6; + break; + case 10 * MHZ: + *reg = EXYNOS5_FSEL_10MHZ; + break; + case 12 * MHZ: + *reg = EXYNOS5_FSEL_12MHZ; + break; + case 19200 * KHZ: + *reg = EXYNOS5_FSEL_19MHZ2; + break; + case 20 * MHZ: + *reg = EXYNOS5_FSEL_20MHZ; + break; + case 24 * MHZ: + *reg = EXYNOS5_FSEL_24MHZ; + break; + case 50 * MHZ: + *reg = EXYNOS5_FSEL_50MHZ; + break; + default: + return -EINVAL; + } + + return 0; +} + +static void exynos5_usbdrd_phy_isol(struct phy_usb_instance *inst, + unsigned int on) +{ + unsigned int val; + + if (!inst->reg_pmu) + return; + + val = on ? 0 : EXYNOS4_PHY_ENABLE; + + regmap_update_bits(inst->reg_pmu, inst->pmu_offset, + EXYNOS4_PHY_ENABLE, val); +} + +/* + * Sets the pipe3 phy's clk as EXTREFCLK (XXTI) which is internal clock + * from clock core. Further sets multiplier values and spread spectrum + * clock settings for SuperSpeed operations. + */ +static unsigned int +exynos5_usbdrd_pipe3_set_refclk(struct phy_usb_instance *inst) +{ + u32 reg; + struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst); + + /* restore any previous reference clock settings */ + reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYCLKRST); + + /* Use EXTREFCLK as ref clock */ + reg &= ~PHYCLKRST_REFCLKSEL_MASK; + reg |= PHYCLKRST_REFCLKSEL_EXT_REFCLK; + + /* FSEL settings corresponding to reference clock */ + reg &= ~PHYCLKRST_FSEL_PIPE_MASK | + PHYCLKRST_MPLL_MULTIPLIER_MASK | + PHYCLKRST_SSC_REFCLKSEL_MASK; + switch (phy_drd->extrefclk) { + case EXYNOS5_FSEL_50MHZ: + reg |= (PHYCLKRST_MPLL_MULTIPLIER_50M_REF | + PHYCLKRST_SSC_REFCLKSEL(0x00)); + break; + case EXYNOS5_FSEL_24MHZ: + reg |= (PHYCLKRST_MPLL_MULTIPLIER_24MHZ_REF | + PHYCLKRST_SSC_REFCLKSEL(0x88)); + break; + case EXYNOS5_FSEL_20MHZ: + reg |= (PHYCLKRST_MPLL_MULTIPLIER_20MHZ_REF | + PHYCLKRST_SSC_REFCLKSEL(0x00)); + break; + case EXYNOS5_FSEL_19MHZ2: + reg |= (PHYCLKRST_MPLL_MULTIPLIER_19200KHZ_REF | + PHYCLKRST_SSC_REFCLKSEL(0x88)); + break; + default: + dev_dbg(phy_drd->dev, "unsupported ref clk\n"); + break; + } + + return reg; +} + +/* + * Sets the utmi phy's clk as EXTREFCLK (XXTI) which is internal clock + * from clock core. Further sets the FSEL values for HighSpeed operations. + */ +static unsigned int +exynos5_usbdrd_utmi_set_refclk(struct phy_usb_instance *inst) +{ + u32 reg; + struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst); + + /* restore any previous reference clock settings */ + reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYCLKRST); + + reg &= ~PHYCLKRST_REFCLKSEL_MASK; + reg |= PHYCLKRST_REFCLKSEL_EXT_REFCLK; + + reg &= ~PHYCLKRST_FSEL_UTMI_MASK | + PHYCLKRST_MPLL_MULTIPLIER_MASK | + PHYCLKRST_SSC_REFCLKSEL_MASK; + reg |= PHYCLKRST_FSEL(phy_drd->extrefclk); + + return reg; +} + +static void exynos5_usbdrd_pipe3_init(struct exynos5_usbdrd_phy *phy_drd) +{ + u32 reg; + + reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM1); + /* Set Tx De-Emphasis level */ + reg &= ~PHYPARAM1_PCS_TXDEEMPH_MASK; + reg |= PHYPARAM1_PCS_TXDEEMPH; + writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM1); + + reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYTEST); + reg &= ~PHYTEST_POWERDOWN_SSP; + writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYTEST); +} + +static void exynos5_usbdrd_utmi_init(struct exynos5_usbdrd_phy *phy_drd) +{ + u32 reg; + + reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM0); + /* Set Loss-of-Signal Detector sensitivity */ + reg &= ~PHYPARAM0_REF_LOSLEVEL_MASK; + reg |= PHYPARAM0_REF_LOSLEVEL; + writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM0); + + reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM1); + /* Set Tx De-Emphasis level */ + reg &= ~PHYPARAM1_PCS_TXDEEMPH_MASK; + reg |= PHYPARAM1_PCS_TXDEEMPH; + writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM1); + + /* UTMI Power Control */ + writel(PHYUTMI_OTGDISABLE, phy_drd->reg_phy + EXYNOS5_DRD_PHYUTMI); + + reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYTEST); + reg &= ~PHYTEST_POWERDOWN_HSP; + writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYTEST); +} + +static int exynos5_usbdrd_phy_init(struct phy *phy) +{ + int ret; + u32 reg; + struct phy_usb_instance *inst = phy_get_drvdata(phy); + struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst); + + ret = clk_prepare_enable(phy_drd->clk); + if (ret) + return ret; + + /* Reset USB 3.0 PHY */ + writel(0x0, phy_drd->reg_phy + EXYNOS5_DRD_PHYREG0); + writel(0x0, phy_drd->reg_phy + EXYNOS5_DRD_PHYRESUME); + + /* + * Setting the Frame length Adj value[6:1] to default 0x20 + * See xHCI 1.0 spec, 5.2.4 + */ + reg = LINKSYSTEM_XHCI_VERSION_CONTROL | + LINKSYSTEM_FLADJ(0x20); + writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_LINKSYSTEM); + + reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM0); + /* Select PHY CLK source */ + reg &= ~PHYPARAM0_REF_USE_PAD; + writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYPARAM0); + + /* This bit must be set for both HS and SS operations */ + reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYUTMICLKSEL); + reg |= PHYUTMICLKSEL_UTMI_CLKSEL; + writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYUTMICLKSEL); + + /* UTMI or PIPE3 specific init */ + inst->phy_cfg->phy_init(phy_drd); + + /* reference clock settings */ + reg = inst->phy_cfg->set_refclk(inst); + + /* Digital power supply in normal operating mode */ + reg |= PHYCLKRST_RETENABLEN | + /* Enable ref clock for SS function */ + PHYCLKRST_REF_SSP_EN | + /* Enable spread spectrum */ + PHYCLKRST_SSC_EN | + /* Power down HS Bias and PLL blocks in suspend mode */ + PHYCLKRST_COMMONONN | + /* Reset the port */ + PHYCLKRST_PORTRESET; + + writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYCLKRST); + + udelay(10); + + reg &= ~PHYCLKRST_PORTRESET; + writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYCLKRST); + + clk_disable_unprepare(phy_drd->clk); + + return 0; +} + +static int exynos5_usbdrd_phy_exit(struct phy *phy) +{ + int ret; + u32 reg; + struct phy_usb_instance *inst = phy_get_drvdata(phy); + struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst); + + ret = clk_prepare_enable(phy_drd->clk); + if (ret) + return ret; + + reg = PHYUTMI_OTGDISABLE | + PHYUTMI_FORCESUSPEND | + PHYUTMI_FORCESLEEP; + writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYUTMI); + + /* Resetting the PHYCLKRST enable bits to reduce leakage current */ + reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYCLKRST); + reg &= ~(PHYCLKRST_REF_SSP_EN | + PHYCLKRST_SSC_EN | + PHYCLKRST_COMMONONN); + writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYCLKRST); + + /* Control PHYTEST to remove leakage current */ + reg = readl(phy_drd->reg_phy + EXYNOS5_DRD_PHYTEST); + reg |= PHYTEST_POWERDOWN_SSP | + PHYTEST_POWERDOWN_HSP; + writel(reg, phy_drd->reg_phy + EXYNOS5_DRD_PHYTEST); + + clk_disable_unprepare(phy_drd->clk); + + return 0; +} + +static int exynos5_usbdrd_phy_power_on(struct phy *phy) +{ + int ret; + struct phy_usb_instance *inst = phy_get_drvdata(phy); + struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst); + + dev_dbg(phy_drd->dev, "Request to power_on usbdrd_phy phy\n"); + + clk_prepare_enable(phy_drd->ref_clk); + if (!phy_drd->drv_data->has_common_clk_gate) { + clk_prepare_enable(phy_drd->pipeclk); + clk_prepare_enable(phy_drd->utmiclk); + clk_prepare_enable(phy_drd->itpclk); + } + + /* Enable VBUS supply */ + if (phy_drd->vbus_boost) { + ret = regulator_enable(phy_drd->vbus_boost); + if (ret) { + dev_err(phy_drd->dev, + "Failed to enable VBUS boost supply\n"); + goto fail_vbus; + } + } + + if (phy_drd->vbus) { + ret = regulator_enable(phy_drd->vbus); + if (ret) { + dev_err(phy_drd->dev, "Failed to enable VBUS supply\n"); + goto fail_vbus_boost; + } + } + + /* Power-on PHY*/ + inst->phy_cfg->phy_isol(inst, 0); + + return 0; + +fail_vbus_boost: + if (phy_drd->vbus_boost) + regulator_disable(phy_drd->vbus_boost); + +fail_vbus: + clk_disable_unprepare(phy_drd->ref_clk); + if (!phy_drd->drv_data->has_common_clk_gate) { + clk_disable_unprepare(phy_drd->itpclk); + clk_disable_unprepare(phy_drd->utmiclk); + clk_disable_unprepare(phy_drd->pipeclk); + } + + return ret; +} + +static int exynos5_usbdrd_phy_power_off(struct phy *phy) +{ + struct phy_usb_instance *inst = phy_get_drvdata(phy); + struct exynos5_usbdrd_phy *phy_drd = to_usbdrd_phy(inst); + + dev_dbg(phy_drd->dev, "Request to power_off usbdrd_phy phy\n"); + + /* Power-off the PHY */ + inst->phy_cfg->phy_isol(inst, 1); + + /* Disable VBUS supply */ + if (phy_drd->vbus) + regulator_disable(phy_drd->vbus); + if (phy_drd->vbus_boost) + regulator_disable(phy_drd->vbus_boost); + + clk_disable_unprepare(phy_drd->ref_clk); + if (!phy_drd->drv_data->has_common_clk_gate) { + clk_disable_unprepare(phy_drd->itpclk); + clk_disable_unprepare(phy_drd->pipeclk); + clk_disable_unprepare(phy_drd->utmiclk); + } + + return 0; +} + +static struct phy *exynos5_usbdrd_phy_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct exynos5_usbdrd_phy *phy_drd = dev_get_drvdata(dev); + + if (WARN_ON(args->args[0] >= EXYNOS5_DRDPHYS_NUM)) + return ERR_PTR(-ENODEV); + + return phy_drd->phys[args->args[0]].phy; +} + +static const struct phy_ops exynos5_usbdrd_phy_ops = { + .init = exynos5_usbdrd_phy_init, + .exit = exynos5_usbdrd_phy_exit, + .power_on = exynos5_usbdrd_phy_power_on, + .power_off = exynos5_usbdrd_phy_power_off, + .owner = THIS_MODULE, +}; + +static int exynos5_usbdrd_phy_clk_handle(struct exynos5_usbdrd_phy *phy_drd) +{ + unsigned long ref_rate; + int ret; + + phy_drd->clk = devm_clk_get(phy_drd->dev, "phy"); + if (IS_ERR(phy_drd->clk)) { + dev_err(phy_drd->dev, "Failed to get phy clock\n"); + return PTR_ERR(phy_drd->clk); + } + + phy_drd->ref_clk = devm_clk_get(phy_drd->dev, "ref"); + if (IS_ERR(phy_drd->ref_clk)) { + dev_err(phy_drd->dev, "Failed to get phy reference clock\n"); + return PTR_ERR(phy_drd->ref_clk); + } + ref_rate = clk_get_rate(phy_drd->ref_clk); + + ret = exynos5_rate_to_clk(ref_rate, &phy_drd->extrefclk); + if (ret) { + dev_err(phy_drd->dev, "Clock rate (%ld) not supported\n", + ref_rate); + return ret; + } + + if (!phy_drd->drv_data->has_common_clk_gate) { + phy_drd->pipeclk = devm_clk_get(phy_drd->dev, "phy_pipe"); + if (IS_ERR(phy_drd->pipeclk)) { + dev_info(phy_drd->dev, + "PIPE3 phy operational clock not specified\n"); + phy_drd->pipeclk = NULL; + } + + phy_drd->utmiclk = devm_clk_get(phy_drd->dev, "phy_utmi"); + if (IS_ERR(phy_drd->utmiclk)) { + dev_info(phy_drd->dev, + "UTMI phy operational clock not specified\n"); + phy_drd->utmiclk = NULL; + } + + phy_drd->itpclk = devm_clk_get(phy_drd->dev, "itp"); + if (IS_ERR(phy_drd->itpclk)) { + dev_info(phy_drd->dev, + "ITP clock from main OSC not specified\n"); + phy_drd->itpclk = NULL; + } + } + + return 0; +} + +static const struct exynos5_usbdrd_phy_config phy_cfg_exynos5[] = { + { + .id = EXYNOS5_DRDPHY_UTMI, + .phy_isol = exynos5_usbdrd_phy_isol, + .phy_init = exynos5_usbdrd_utmi_init, + .set_refclk = exynos5_usbdrd_utmi_set_refclk, + }, + { + .id = EXYNOS5_DRDPHY_PIPE3, + .phy_isol = exynos5_usbdrd_phy_isol, + .phy_init = exynos5_usbdrd_pipe3_init, + .set_refclk = exynos5_usbdrd_pipe3_set_refclk, + }, +}; + +static const struct exynos5_usbdrd_phy_drvdata exynos5420_usbdrd_phy = { + .phy_cfg = phy_cfg_exynos5, + .pmu_offset_usbdrd0_phy = EXYNOS5_USBDRD_PHY_CONTROL, + .pmu_offset_usbdrd1_phy = EXYNOS5420_USBDRD1_PHY_CONTROL, + .has_common_clk_gate = true, +}; + +static const struct exynos5_usbdrd_phy_drvdata exynos5250_usbdrd_phy = { + .phy_cfg = phy_cfg_exynos5, + .pmu_offset_usbdrd0_phy = EXYNOS5_USBDRD_PHY_CONTROL, + .has_common_clk_gate = true, +}; + +static const struct exynos5_usbdrd_phy_drvdata exynos5433_usbdrd_phy = { + .phy_cfg = phy_cfg_exynos5, + .pmu_offset_usbdrd0_phy = EXYNOS5_USBDRD_PHY_CONTROL, + .pmu_offset_usbdrd1_phy = EXYNOS5433_USBHOST30_PHY_CONTROL, + .has_common_clk_gate = false, +}; + +static const struct exynos5_usbdrd_phy_drvdata exynos7_usbdrd_phy = { + .phy_cfg = phy_cfg_exynos5, + .pmu_offset_usbdrd0_phy = EXYNOS5_USBDRD_PHY_CONTROL, + .has_common_clk_gate = false, +}; + +static const struct of_device_id exynos5_usbdrd_phy_of_match[] = { + { + .compatible = "samsung,exynos5250-usbdrd-phy", + .data = &exynos5250_usbdrd_phy + }, { + .compatible = "samsung,exynos5420-usbdrd-phy", + .data = &exynos5420_usbdrd_phy + }, { + .compatible = "samsung,exynos5433-usbdrd-phy", + .data = &exynos5433_usbdrd_phy + }, { + .compatible = "samsung,exynos7-usbdrd-phy", + .data = &exynos7_usbdrd_phy + }, + { }, +}; +MODULE_DEVICE_TABLE(of, exynos5_usbdrd_phy_of_match); + +static int exynos5_usbdrd_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct exynos5_usbdrd_phy *phy_drd; + struct phy_provider *phy_provider; + struct resource *res; + const struct of_device_id *match; + const struct exynos5_usbdrd_phy_drvdata *drv_data; + struct regmap *reg_pmu; + u32 pmu_offset; + int i, ret; + int channel; + + phy_drd = devm_kzalloc(dev, sizeof(*phy_drd), GFP_KERNEL); + if (!phy_drd) + return -ENOMEM; + + dev_set_drvdata(dev, phy_drd); + phy_drd->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + phy_drd->reg_phy = devm_ioremap_resource(dev, res); + if (IS_ERR(phy_drd->reg_phy)) + return PTR_ERR(phy_drd->reg_phy); + + match = of_match_node(exynos5_usbdrd_phy_of_match, pdev->dev.of_node); + + drv_data = match->data; + phy_drd->drv_data = drv_data; + + ret = exynos5_usbdrd_phy_clk_handle(phy_drd); + if (ret) { + dev_err(dev, "Failed to initialize clocks\n"); + return ret; + } + + reg_pmu = syscon_regmap_lookup_by_phandle(dev->of_node, + "samsung,pmu-syscon"); + if (IS_ERR(reg_pmu)) { + dev_err(dev, "Failed to lookup PMU regmap\n"); + return PTR_ERR(reg_pmu); + } + + /* + * Exynos5420 SoC has multiple channels for USB 3.0 PHY, with + * each having separate power control registers. + * 'channel' facilitates to set such registers. + */ + channel = of_alias_get_id(node, "usbdrdphy"); + if (channel < 0) + dev_dbg(dev, "Not a multi-controller usbdrd phy\n"); + + switch (channel) { + case 1: + pmu_offset = phy_drd->drv_data->pmu_offset_usbdrd1_phy; + break; + case 0: + default: + pmu_offset = phy_drd->drv_data->pmu_offset_usbdrd0_phy; + break; + } + + /* Get Vbus regulators */ + phy_drd->vbus = devm_regulator_get(dev, "vbus"); + if (IS_ERR(phy_drd->vbus)) { + ret = PTR_ERR(phy_drd->vbus); + if (ret == -EPROBE_DEFER) + return ret; + + dev_warn(dev, "Failed to get VBUS supply regulator\n"); + phy_drd->vbus = NULL; + } + + phy_drd->vbus_boost = devm_regulator_get(dev, "vbus-boost"); + if (IS_ERR(phy_drd->vbus_boost)) { + ret = PTR_ERR(phy_drd->vbus_boost); + if (ret == -EPROBE_DEFER) + return ret; + + dev_warn(dev, "Failed to get VBUS boost supply regulator\n"); + phy_drd->vbus_boost = NULL; + } + + dev_vdbg(dev, "Creating usbdrd_phy phy\n"); + + for (i = 0; i < EXYNOS5_DRDPHYS_NUM; i++) { + struct phy *phy = devm_phy_create(dev, NULL, + &exynos5_usbdrd_phy_ops); + if (IS_ERR(phy)) { + dev_err(dev, "Failed to create usbdrd_phy phy\n"); + return PTR_ERR(phy); + } + + phy_drd->phys[i].phy = phy; + phy_drd->phys[i].index = i; + phy_drd->phys[i].reg_pmu = reg_pmu; + phy_drd->phys[i].pmu_offset = pmu_offset; + phy_drd->phys[i].phy_cfg = &drv_data->phy_cfg[i]; + phy_set_drvdata(phy, &phy_drd->phys[i]); + } + + phy_provider = devm_of_phy_provider_register(dev, + exynos5_usbdrd_phy_xlate); + if (IS_ERR(phy_provider)) { + dev_err(phy_drd->dev, "Failed to register phy provider\n"); + return PTR_ERR(phy_provider); + } + + return 0; +} + +static struct platform_driver exynos5_usb3drd_phy = { + .probe = exynos5_usbdrd_phy_probe, + .driver = { + .of_match_table = exynos5_usbdrd_phy_of_match, + .name = "exynos5_usb3drd_phy", + } +}; + +module_platform_driver(exynos5_usb3drd_phy); +MODULE_DESCRIPTION("Samsung EXYNOS5 SoCs USB 3.0 DRD controller PHY driver"); +MODULE_AUTHOR("Vivek Gautam "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:exynos5_usb3drd_phy"); diff --git a/drivers/phy/samsung/phy-exynos5250-sata.c b/drivers/phy/samsung/phy-exynos5250-sata.c new file mode 100644 index 000000000000..60e13afcd9b8 --- /dev/null +++ b/drivers/phy/samsung/phy-exynos5250-sata.c @@ -0,0 +1,250 @@ +/* + * Samsung SATA SerDes(PHY) driver + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Authors: Girish K S + * Yuvaraj Kumar C D + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SATAPHY_CONTROL_OFFSET 0x0724 +#define EXYNOS5_SATAPHY_PMU_ENABLE BIT(0) +#define EXYNOS5_SATA_RESET 0x4 +#define RESET_GLOBAL_RST_N BIT(0) +#define RESET_CMN_RST_N BIT(1) +#define RESET_CMN_BLOCK_RST_N BIT(2) +#define RESET_CMN_I2C_RST_N BIT(3) +#define RESET_TX_RX_PIPE_RST_N BIT(4) +#define RESET_TX_RX_BLOCK_RST_N BIT(5) +#define RESET_TX_RX_I2C_RST_N (BIT(6) | BIT(7)) +#define LINK_RESET 0xf0000 +#define EXYNOS5_SATA_MODE0 0x10 +#define SATA_SPD_GEN3 BIT(1) +#define EXYNOS5_SATA_CTRL0 0x14 +#define CTRL0_P0_PHY_CALIBRATED_SEL BIT(9) +#define CTRL0_P0_PHY_CALIBRATED BIT(8) +#define EXYNOS5_SATA_PHSATA_CTRLM 0xe0 +#define PHCTRLM_REF_RATE BIT(1) +#define PHCTRLM_HIGH_SPEED BIT(0) +#define EXYNOS5_SATA_PHSATA_STATM 0xf0 +#define PHSTATM_PLL_LOCKED BIT(0) + +#define PHY_PLL_TIMEOUT (usecs_to_jiffies(1000)) + +struct exynos_sata_phy { + struct phy *phy; + struct clk *phyclk; + void __iomem *regs; + struct regmap *pmureg; + struct i2c_client *client; +}; + +static int wait_for_reg_status(void __iomem *base, u32 reg, u32 checkbit, + u32 status) +{ + unsigned long timeout = jiffies + PHY_PLL_TIMEOUT; + + while (time_before(jiffies, timeout)) { + if ((readl(base + reg) & checkbit) == status) + return 0; + } + + return -EFAULT; +} + +static int exynos_sata_phy_power_on(struct phy *phy) +{ + struct exynos_sata_phy *sata_phy = phy_get_drvdata(phy); + + return regmap_update_bits(sata_phy->pmureg, SATAPHY_CONTROL_OFFSET, + EXYNOS5_SATAPHY_PMU_ENABLE, true); + +} + +static int exynos_sata_phy_power_off(struct phy *phy) +{ + struct exynos_sata_phy *sata_phy = phy_get_drvdata(phy); + + return regmap_update_bits(sata_phy->pmureg, SATAPHY_CONTROL_OFFSET, + EXYNOS5_SATAPHY_PMU_ENABLE, false); + +} + +static int exynos_sata_phy_init(struct phy *phy) +{ + u32 val = 0; + int ret = 0; + u8 buf[] = { 0x3a, 0x0b }; + struct exynos_sata_phy *sata_phy = phy_get_drvdata(phy); + + ret = regmap_update_bits(sata_phy->pmureg, SATAPHY_CONTROL_OFFSET, + EXYNOS5_SATAPHY_PMU_ENABLE, true); + if (ret != 0) + dev_err(&sata_phy->phy->dev, "phy init failed\n"); + + writel(val, sata_phy->regs + EXYNOS5_SATA_RESET); + + val = readl(sata_phy->regs + EXYNOS5_SATA_RESET); + val |= RESET_GLOBAL_RST_N | RESET_CMN_RST_N | RESET_CMN_BLOCK_RST_N + | RESET_CMN_I2C_RST_N | RESET_TX_RX_PIPE_RST_N + | RESET_TX_RX_BLOCK_RST_N | RESET_TX_RX_I2C_RST_N; + writel(val, sata_phy->regs + EXYNOS5_SATA_RESET); + + val = readl(sata_phy->regs + EXYNOS5_SATA_RESET); + val |= LINK_RESET; + writel(val, sata_phy->regs + EXYNOS5_SATA_RESET); + + val = readl(sata_phy->regs + EXYNOS5_SATA_RESET); + val |= RESET_CMN_RST_N; + writel(val, sata_phy->regs + EXYNOS5_SATA_RESET); + + val = readl(sata_phy->regs + EXYNOS5_SATA_PHSATA_CTRLM); + val &= ~PHCTRLM_REF_RATE; + writel(val, sata_phy->regs + EXYNOS5_SATA_PHSATA_CTRLM); + + /* High speed enable for Gen3 */ + val = readl(sata_phy->regs + EXYNOS5_SATA_PHSATA_CTRLM); + val |= PHCTRLM_HIGH_SPEED; + writel(val, sata_phy->regs + EXYNOS5_SATA_PHSATA_CTRLM); + + val = readl(sata_phy->regs + EXYNOS5_SATA_CTRL0); + val |= CTRL0_P0_PHY_CALIBRATED_SEL | CTRL0_P0_PHY_CALIBRATED; + writel(val, sata_phy->regs + EXYNOS5_SATA_CTRL0); + + val = readl(sata_phy->regs + EXYNOS5_SATA_MODE0); + val |= SATA_SPD_GEN3; + writel(val, sata_phy->regs + EXYNOS5_SATA_MODE0); + + ret = i2c_master_send(sata_phy->client, buf, sizeof(buf)); + if (ret < 0) + return ret; + + /* release cmu reset */ + val = readl(sata_phy->regs + EXYNOS5_SATA_RESET); + val &= ~RESET_CMN_RST_N; + writel(val, sata_phy->regs + EXYNOS5_SATA_RESET); + + val = readl(sata_phy->regs + EXYNOS5_SATA_RESET); + val |= RESET_CMN_RST_N; + writel(val, sata_phy->regs + EXYNOS5_SATA_RESET); + + ret = wait_for_reg_status(sata_phy->regs, + EXYNOS5_SATA_PHSATA_STATM, + PHSTATM_PLL_LOCKED, 1); + if (ret < 0) + dev_err(&sata_phy->phy->dev, + "PHY PLL locking failed\n"); + return ret; +} + +static const struct phy_ops exynos_sata_phy_ops = { + .init = exynos_sata_phy_init, + .power_on = exynos_sata_phy_power_on, + .power_off = exynos_sata_phy_power_off, + .owner = THIS_MODULE, +}; + +static int exynos_sata_phy_probe(struct platform_device *pdev) +{ + struct exynos_sata_phy *sata_phy; + struct device *dev = &pdev->dev; + struct resource *res; + struct phy_provider *phy_provider; + struct device_node *node; + int ret = 0; + + sata_phy = devm_kzalloc(dev, sizeof(*sata_phy), GFP_KERNEL); + if (!sata_phy) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + sata_phy->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(sata_phy->regs)) + return PTR_ERR(sata_phy->regs); + + sata_phy->pmureg = syscon_regmap_lookup_by_phandle(dev->of_node, + "samsung,syscon-phandle"); + if (IS_ERR(sata_phy->pmureg)) { + dev_err(dev, "syscon regmap lookup failed.\n"); + return PTR_ERR(sata_phy->pmureg); + } + + node = of_parse_phandle(dev->of_node, + "samsung,exynos-sataphy-i2c-phandle", 0); + if (!node) + return -EINVAL; + + sata_phy->client = of_find_i2c_device_by_node(node); + if (!sata_phy->client) + return -EPROBE_DEFER; + + dev_set_drvdata(dev, sata_phy); + + sata_phy->phyclk = devm_clk_get(dev, "sata_phyctrl"); + if (IS_ERR(sata_phy->phyclk)) { + dev_err(dev, "failed to get clk for PHY\n"); + return PTR_ERR(sata_phy->phyclk); + } + + ret = clk_prepare_enable(sata_phy->phyclk); + if (ret < 0) { + dev_err(dev, "failed to enable source clk\n"); + return ret; + } + + sata_phy->phy = devm_phy_create(dev, NULL, &exynos_sata_phy_ops); + if (IS_ERR(sata_phy->phy)) { + clk_disable_unprepare(sata_phy->phyclk); + dev_err(dev, "failed to create PHY\n"); + return PTR_ERR(sata_phy->phy); + } + + phy_set_drvdata(sata_phy->phy, sata_phy); + + phy_provider = devm_of_phy_provider_register(dev, + of_phy_simple_xlate); + if (IS_ERR(phy_provider)) { + clk_disable_unprepare(sata_phy->phyclk); + return PTR_ERR(phy_provider); + } + + return 0; +} + +static const struct of_device_id exynos_sata_phy_of_match[] = { + { .compatible = "samsung,exynos5250-sata-phy" }, + { }, +}; +MODULE_DEVICE_TABLE(of, exynos_sata_phy_of_match); + +static struct platform_driver exynos_sata_phy_driver = { + .probe = exynos_sata_phy_probe, + .driver = { + .of_match_table = exynos_sata_phy_of_match, + .name = "samsung,sata-phy", + } +}; +module_platform_driver(exynos_sata_phy_driver); + +MODULE_DESCRIPTION("Samsung SerDes PHY driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Girish K S "); +MODULE_AUTHOR("Yuvaraj C D "); diff --git a/drivers/phy/samsung/phy-exynos5250-usb2.c b/drivers/phy/samsung/phy-exynos5250-usb2.c new file mode 100644 index 000000000000..aad806272305 --- /dev/null +++ b/drivers/phy/samsung/phy-exynos5250-usb2.c @@ -0,0 +1,401 @@ +/* + * Samsung SoC USB 1.1/2.0 PHY driver - Exynos 5250 support + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Author: Kamil Debski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include "phy-samsung-usb2.h" + +/* Exynos USB PHY registers */ +#define EXYNOS_5250_REFCLKSEL_CRYSTAL 0x0 +#define EXYNOS_5250_REFCLKSEL_XO 0x1 +#define EXYNOS_5250_REFCLKSEL_CLKCORE 0x2 + +#define EXYNOS_5250_FSEL_9MHZ6 0x0 +#define EXYNOS_5250_FSEL_10MHZ 0x1 +#define EXYNOS_5250_FSEL_12MHZ 0x2 +#define EXYNOS_5250_FSEL_19MHZ2 0x3 +#define EXYNOS_5250_FSEL_20MHZ 0x4 +#define EXYNOS_5250_FSEL_24MHZ 0x5 +#define EXYNOS_5250_FSEL_50MHZ 0x7 + +/* Normal host */ +#define EXYNOS_5250_HOSTPHYCTRL0 0x0 + +#define EXYNOS_5250_HOSTPHYCTRL0_PHYSWRSTALL BIT(31) +#define EXYNOS_5250_HOSTPHYCTRL0_REFCLKSEL_SHIFT 19 +#define EXYNOS_5250_HOSTPHYCTRL0_REFCLKSEL_MASK \ + (0x3 << EXYNOS_5250_HOSTPHYCTRL0_REFCLKSEL_SHIFT) +#define EXYNOS_5250_HOSTPHYCTRL0_FSEL_SHIFT 16 +#define EXYNOS_5250_HOSTPHYCTRL0_FSEL_MASK \ + (0x7 << EXYNOS_5250_HOSTPHYCTRL0_FSEL_SHIFT) +#define EXYNOS_5250_HOSTPHYCTRL0_TESTBURNIN BIT(11) +#define EXYNOS_5250_HOSTPHYCTRL0_RETENABLE BIT(10) +#define EXYNOS_5250_HOSTPHYCTRL0_COMMON_ON_N BIT(9) +#define EXYNOS_5250_HOSTPHYCTRL0_VATESTENB_MASK (0x3 << 7) +#define EXYNOS_5250_HOSTPHYCTRL0_VATESTENB_DUAL (0x0 << 7) +#define EXYNOS_5250_HOSTPHYCTRL0_VATESTENB_ID0 (0x1 << 7) +#define EXYNOS_5250_HOSTPHYCTRL0_VATESTENB_ANALOGTEST (0x2 << 7) +#define EXYNOS_5250_HOSTPHYCTRL0_SIDDQ BIT(6) +#define EXYNOS_5250_HOSTPHYCTRL0_FORCESLEEP BIT(5) +#define EXYNOS_5250_HOSTPHYCTRL0_FORCESUSPEND BIT(4) +#define EXYNOS_5250_HOSTPHYCTRL0_WORDINTERFACE BIT(3) +#define EXYNOS_5250_HOSTPHYCTRL0_UTMISWRST BIT(2) +#define EXYNOS_5250_HOSTPHYCTRL0_LINKSWRST BIT(1) +#define EXYNOS_5250_HOSTPHYCTRL0_PHYSWRST BIT(0) + +/* HSIC0 & HSIC1 */ +#define EXYNOS_5250_HSICPHYCTRL1 0x10 +#define EXYNOS_5250_HSICPHYCTRL2 0x20 + +#define EXYNOS_5250_HSICPHYCTRLX_REFCLKSEL_MASK (0x3 << 23) +#define EXYNOS_5250_HSICPHYCTRLX_REFCLKSEL_DEFAULT (0x2 << 23) +#define EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_MASK (0x7f << 16) +#define EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_12 (0x24 << 16) +#define EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_15 (0x1c << 16) +#define EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_16 (0x1a << 16) +#define EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_19_2 (0x15 << 16) +#define EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_20 (0x14 << 16) +#define EXYNOS_5250_HSICPHYCTRLX_SIDDQ BIT(6) +#define EXYNOS_5250_HSICPHYCTRLX_FORCESLEEP BIT(5) +#define EXYNOS_5250_HSICPHYCTRLX_FORCESUSPEND BIT(4) +#define EXYNOS_5250_HSICPHYCTRLX_WORDINTERFACE BIT(3) +#define EXYNOS_5250_HSICPHYCTRLX_UTMISWRST BIT(2) +#define EXYNOS_5250_HSICPHYCTRLX_PHYSWRST BIT(0) + +/* EHCI control */ +#define EXYNOS_5250_HOSTEHCICTRL 0x30 +#define EXYNOS_5250_HOSTEHCICTRL_ENAINCRXALIGN BIT(29) +#define EXYNOS_5250_HOSTEHCICTRL_ENAINCR4 BIT(28) +#define EXYNOS_5250_HOSTEHCICTRL_ENAINCR8 BIT(27) +#define EXYNOS_5250_HOSTEHCICTRL_ENAINCR16 BIT(26) +#define EXYNOS_5250_HOSTEHCICTRL_AUTOPPDONOVRCUREN BIT(25) +#define EXYNOS_5250_HOSTEHCICTRL_FLADJVAL0_SHIFT 19 +#define EXYNOS_5250_HOSTEHCICTRL_FLADJVAL0_MASK \ + (0x3f << EXYNOS_5250_HOSTEHCICTRL_FLADJVAL0_SHIFT) +#define EXYNOS_5250_HOSTEHCICTRL_FLADJVAL1_SHIFT 13 +#define EXYNOS_5250_HOSTEHCICTRL_FLADJVAL1_MASK \ + (0x3f << EXYNOS_5250_HOSTEHCICTRL_FLADJVAL1_SHIFT) +#define EXYNOS_5250_HOSTEHCICTRL_FLADJVAL2_SHIFT 7 +#define EXYNOS_5250_HOSTEHCICTRL_FLADJVAL0_MASK \ + (0x3f << EXYNOS_5250_HOSTEHCICTRL_FLADJVAL0_SHIFT) +#define EXYNOS_5250_HOSTEHCICTRL_FLADJVALHOST_SHIFT 1 +#define EXYNOS_5250_HOSTEHCICTRL_FLADJVALHOST_MASK \ + (0x1 << EXYNOS_5250_HOSTEHCICTRL_FLADJVALHOST_SHIFT) +#define EXYNOS_5250_HOSTEHCICTRL_SIMULATIONMODE BIT(0) + +/* OHCI control */ +#define EXYNOS_5250_HOSTOHCICTRL 0x34 +#define EXYNOS_5250_HOSTOHCICTRL_FRAMELENVAL_SHIFT 1 +#define EXYNOS_5250_HOSTOHCICTRL_FRAMELENVAL_MASK \ + (0x3ff << EXYNOS_5250_HOSTOHCICTRL_FRAMELENVAL_SHIFT) +#define EXYNOS_5250_HOSTOHCICTRL_FRAMELENVALEN BIT(0) + +/* USBOTG */ +#define EXYNOS_5250_USBOTGSYS 0x38 +#define EXYNOS_5250_USBOTGSYS_PHYLINK_SW_RESET BIT(14) +#define EXYNOS_5250_USBOTGSYS_LINK_SW_RST_UOTG BIT(13) +#define EXYNOS_5250_USBOTGSYS_PHY_SW_RST BIT(12) +#define EXYNOS_5250_USBOTGSYS_REFCLKSEL_SHIFT 9 +#define EXYNOS_5250_USBOTGSYS_REFCLKSEL_MASK \ + (0x3 << EXYNOS_5250_USBOTGSYS_REFCLKSEL_SHIFT) +#define EXYNOS_5250_USBOTGSYS_ID_PULLUP BIT(8) +#define EXYNOS_5250_USBOTGSYS_COMMON_ON BIT(7) +#define EXYNOS_5250_USBOTGSYS_FSEL_SHIFT 4 +#define EXYNOS_5250_USBOTGSYS_FSEL_MASK \ + (0x3 << EXYNOS_5250_USBOTGSYS_FSEL_SHIFT) +#define EXYNOS_5250_USBOTGSYS_FORCE_SLEEP BIT(3) +#define EXYNOS_5250_USBOTGSYS_OTGDISABLE BIT(2) +#define EXYNOS_5250_USBOTGSYS_SIDDQ_UOTG BIT(1) +#define EXYNOS_5250_USBOTGSYS_FORCE_SUSPEND BIT(0) + +/* Isolation, configured in the power management unit */ +#define EXYNOS_5250_USB_ISOL_OTG_OFFSET 0x704 +#define EXYNOS_5250_USB_ISOL_OTG BIT(0) +#define EXYNOS_5250_USB_ISOL_HOST_OFFSET 0x708 +#define EXYNOS_5250_USB_ISOL_HOST BIT(0) + +/* Mode swtich register */ +#define EXYNOS_5250_MODE_SWITCH_OFFSET 0x230 +#define EXYNOS_5250_MODE_SWITCH_MASK 1 +#define EXYNOS_5250_MODE_SWITCH_DEVICE 0 +#define EXYNOS_5250_MODE_SWITCH_HOST 1 + +enum exynos4x12_phy_id { + EXYNOS5250_DEVICE, + EXYNOS5250_HOST, + EXYNOS5250_HSIC0, + EXYNOS5250_HSIC1, + EXYNOS5250_NUM_PHYS, +}; + +/* + * exynos5250_rate_to_clk() converts the supplied clock rate to the value that + * can be written to the phy register. + */ +static int exynos5250_rate_to_clk(unsigned long rate, u32 *reg) +{ + /* EXYNOS_5250_FSEL_MASK */ + + switch (rate) { + case 9600 * KHZ: + *reg = EXYNOS_5250_FSEL_9MHZ6; + break; + case 10 * MHZ: + *reg = EXYNOS_5250_FSEL_10MHZ; + break; + case 12 * MHZ: + *reg = EXYNOS_5250_FSEL_12MHZ; + break; + case 19200 * KHZ: + *reg = EXYNOS_5250_FSEL_19MHZ2; + break; + case 20 * MHZ: + *reg = EXYNOS_5250_FSEL_20MHZ; + break; + case 24 * MHZ: + *reg = EXYNOS_5250_FSEL_24MHZ; + break; + case 50 * MHZ: + *reg = EXYNOS_5250_FSEL_50MHZ; + break; + default: + return -EINVAL; + } + + return 0; +} + +static void exynos5250_isol(struct samsung_usb2_phy_instance *inst, bool on) +{ + struct samsung_usb2_phy_driver *drv = inst->drv; + u32 offset; + u32 mask; + + switch (inst->cfg->id) { + case EXYNOS5250_DEVICE: + offset = EXYNOS_5250_USB_ISOL_OTG_OFFSET; + mask = EXYNOS_5250_USB_ISOL_OTG; + break; + case EXYNOS5250_HOST: + offset = EXYNOS_5250_USB_ISOL_HOST_OFFSET; + mask = EXYNOS_5250_USB_ISOL_HOST; + break; + default: + return; + } + + regmap_update_bits(drv->reg_pmu, offset, mask, on ? 0 : mask); +} + +static int exynos5250_power_on(struct samsung_usb2_phy_instance *inst) +{ + struct samsung_usb2_phy_driver *drv = inst->drv; + u32 ctrl0; + u32 otg; + u32 ehci; + u32 ohci; + u32 hsic; + + switch (inst->cfg->id) { + case EXYNOS5250_DEVICE: + regmap_update_bits(drv->reg_sys, + EXYNOS_5250_MODE_SWITCH_OFFSET, + EXYNOS_5250_MODE_SWITCH_MASK, + EXYNOS_5250_MODE_SWITCH_DEVICE); + + /* OTG configuration */ + otg = readl(drv->reg_phy + EXYNOS_5250_USBOTGSYS); + /* The clock */ + otg &= ~EXYNOS_5250_USBOTGSYS_FSEL_MASK; + otg |= drv->ref_reg_val << EXYNOS_5250_USBOTGSYS_FSEL_SHIFT; + /* Reset */ + otg &= ~(EXYNOS_5250_USBOTGSYS_FORCE_SUSPEND | + EXYNOS_5250_USBOTGSYS_FORCE_SLEEP | + EXYNOS_5250_USBOTGSYS_SIDDQ_UOTG); + otg |= EXYNOS_5250_USBOTGSYS_PHY_SW_RST | + EXYNOS_5250_USBOTGSYS_PHYLINK_SW_RESET | + EXYNOS_5250_USBOTGSYS_LINK_SW_RST_UOTG | + EXYNOS_5250_USBOTGSYS_OTGDISABLE; + /* Ref clock */ + otg &= ~EXYNOS_5250_USBOTGSYS_REFCLKSEL_MASK; + otg |= EXYNOS_5250_REFCLKSEL_CLKCORE << + EXYNOS_5250_USBOTGSYS_REFCLKSEL_SHIFT; + writel(otg, drv->reg_phy + EXYNOS_5250_USBOTGSYS); + udelay(100); + otg &= ~(EXYNOS_5250_USBOTGSYS_PHY_SW_RST | + EXYNOS_5250_USBOTGSYS_LINK_SW_RST_UOTG | + EXYNOS_5250_USBOTGSYS_PHYLINK_SW_RESET | + EXYNOS_5250_USBOTGSYS_OTGDISABLE); + writel(otg, drv->reg_phy + EXYNOS_5250_USBOTGSYS); + + + break; + case EXYNOS5250_HOST: + case EXYNOS5250_HSIC0: + case EXYNOS5250_HSIC1: + /* Host registers configuration */ + ctrl0 = readl(drv->reg_phy + EXYNOS_5250_HOSTPHYCTRL0); + /* The clock */ + ctrl0 &= ~EXYNOS_5250_HOSTPHYCTRL0_FSEL_MASK; + ctrl0 |= drv->ref_reg_val << + EXYNOS_5250_HOSTPHYCTRL0_FSEL_SHIFT; + + /* Reset */ + ctrl0 &= ~(EXYNOS_5250_HOSTPHYCTRL0_PHYSWRST | + EXYNOS_5250_HOSTPHYCTRL0_PHYSWRSTALL | + EXYNOS_5250_HOSTPHYCTRL0_SIDDQ | + EXYNOS_5250_HOSTPHYCTRL0_FORCESUSPEND | + EXYNOS_5250_HOSTPHYCTRL0_FORCESLEEP); + ctrl0 |= EXYNOS_5250_HOSTPHYCTRL0_LINKSWRST | + EXYNOS_5250_HOSTPHYCTRL0_UTMISWRST | + EXYNOS_5250_HOSTPHYCTRL0_COMMON_ON_N; + writel(ctrl0, drv->reg_phy + EXYNOS_5250_HOSTPHYCTRL0); + udelay(10); + ctrl0 &= ~(EXYNOS_5250_HOSTPHYCTRL0_LINKSWRST | + EXYNOS_5250_HOSTPHYCTRL0_UTMISWRST); + writel(ctrl0, drv->reg_phy + EXYNOS_5250_HOSTPHYCTRL0); + + /* OTG configuration */ + otg = readl(drv->reg_phy + EXYNOS_5250_USBOTGSYS); + /* The clock */ + otg &= ~EXYNOS_5250_USBOTGSYS_FSEL_MASK; + otg |= drv->ref_reg_val << EXYNOS_5250_USBOTGSYS_FSEL_SHIFT; + /* Reset */ + otg &= ~(EXYNOS_5250_USBOTGSYS_FORCE_SUSPEND | + EXYNOS_5250_USBOTGSYS_FORCE_SLEEP | + EXYNOS_5250_USBOTGSYS_SIDDQ_UOTG); + otg |= EXYNOS_5250_USBOTGSYS_PHY_SW_RST | + EXYNOS_5250_USBOTGSYS_PHYLINK_SW_RESET | + EXYNOS_5250_USBOTGSYS_LINK_SW_RST_UOTG | + EXYNOS_5250_USBOTGSYS_OTGDISABLE; + /* Ref clock */ + otg &= ~EXYNOS_5250_USBOTGSYS_REFCLKSEL_MASK; + otg |= EXYNOS_5250_REFCLKSEL_CLKCORE << + EXYNOS_5250_USBOTGSYS_REFCLKSEL_SHIFT; + writel(otg, drv->reg_phy + EXYNOS_5250_USBOTGSYS); + udelay(10); + otg &= ~(EXYNOS_5250_USBOTGSYS_PHY_SW_RST | + EXYNOS_5250_USBOTGSYS_LINK_SW_RST_UOTG | + EXYNOS_5250_USBOTGSYS_PHYLINK_SW_RESET); + + /* HSIC phy configuration */ + hsic = (EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_12 | + EXYNOS_5250_HSICPHYCTRLX_REFCLKSEL_DEFAULT | + EXYNOS_5250_HSICPHYCTRLX_PHYSWRST); + writel(hsic, drv->reg_phy + EXYNOS_5250_HSICPHYCTRL1); + writel(hsic, drv->reg_phy + EXYNOS_5250_HSICPHYCTRL2); + udelay(10); + hsic &= ~EXYNOS_5250_HSICPHYCTRLX_PHYSWRST; + writel(hsic, drv->reg_phy + EXYNOS_5250_HSICPHYCTRL1); + writel(hsic, drv->reg_phy + EXYNOS_5250_HSICPHYCTRL2); + /* The following delay is necessary for the reset sequence to be + * completed */ + udelay(80); + + /* Enable EHCI DMA burst */ + ehci = readl(drv->reg_phy + EXYNOS_5250_HOSTEHCICTRL); + ehci |= EXYNOS_5250_HOSTEHCICTRL_ENAINCRXALIGN | + EXYNOS_5250_HOSTEHCICTRL_ENAINCR4 | + EXYNOS_5250_HOSTEHCICTRL_ENAINCR8 | + EXYNOS_5250_HOSTEHCICTRL_ENAINCR16; + writel(ehci, drv->reg_phy + EXYNOS_5250_HOSTEHCICTRL); + + /* OHCI settings */ + ohci = readl(drv->reg_phy + EXYNOS_5250_HOSTOHCICTRL); + /* Following code is based on the old driver */ + ohci |= 0x1 << 3; + writel(ohci, drv->reg_phy + EXYNOS_5250_HOSTOHCICTRL); + + break; + } + exynos5250_isol(inst, 0); + + return 0; +} + +static int exynos5250_power_off(struct samsung_usb2_phy_instance *inst) +{ + struct samsung_usb2_phy_driver *drv = inst->drv; + u32 ctrl0; + u32 otg; + u32 hsic; + + exynos5250_isol(inst, 1); + + switch (inst->cfg->id) { + case EXYNOS5250_DEVICE: + otg = readl(drv->reg_phy + EXYNOS_5250_USBOTGSYS); + otg |= (EXYNOS_5250_USBOTGSYS_FORCE_SUSPEND | + EXYNOS_5250_USBOTGSYS_SIDDQ_UOTG | + EXYNOS_5250_USBOTGSYS_FORCE_SLEEP); + writel(otg, drv->reg_phy + EXYNOS_5250_USBOTGSYS); + break; + case EXYNOS5250_HOST: + ctrl0 = readl(drv->reg_phy + EXYNOS_5250_HOSTPHYCTRL0); + ctrl0 |= (EXYNOS_5250_HOSTPHYCTRL0_SIDDQ | + EXYNOS_5250_HOSTPHYCTRL0_FORCESUSPEND | + EXYNOS_5250_HOSTPHYCTRL0_FORCESLEEP | + EXYNOS_5250_HOSTPHYCTRL0_PHYSWRST | + EXYNOS_5250_HOSTPHYCTRL0_PHYSWRSTALL); + writel(ctrl0, drv->reg_phy + EXYNOS_5250_HOSTPHYCTRL0); + break; + case EXYNOS5250_HSIC0: + case EXYNOS5250_HSIC1: + hsic = (EXYNOS_5250_HSICPHYCTRLX_REFCLKDIV_12 | + EXYNOS_5250_HSICPHYCTRLX_REFCLKSEL_DEFAULT | + EXYNOS_5250_HSICPHYCTRLX_SIDDQ | + EXYNOS_5250_HSICPHYCTRLX_FORCESLEEP | + EXYNOS_5250_HSICPHYCTRLX_FORCESUSPEND + ); + writel(hsic, drv->reg_phy + EXYNOS_5250_HSICPHYCTRL1); + writel(hsic, drv->reg_phy + EXYNOS_5250_HSICPHYCTRL2); + break; + } + + return 0; +} + + +static const struct samsung_usb2_common_phy exynos5250_phys[] = { + { + .label = "device", + .id = EXYNOS5250_DEVICE, + .power_on = exynos5250_power_on, + .power_off = exynos5250_power_off, + }, + { + .label = "host", + .id = EXYNOS5250_HOST, + .power_on = exynos5250_power_on, + .power_off = exynos5250_power_off, + }, + { + .label = "hsic0", + .id = EXYNOS5250_HSIC0, + .power_on = exynos5250_power_on, + .power_off = exynos5250_power_off, + }, + { + .label = "hsic1", + .id = EXYNOS5250_HSIC1, + .power_on = exynos5250_power_on, + .power_off = exynos5250_power_off, + }, +}; + +const struct samsung_usb2_phy_config exynos5250_usb2_phy_config = { + .has_mode_switch = 1, + .num_phys = EXYNOS5250_NUM_PHYS, + .phys = exynos5250_phys, + .rate_to_clk = exynos5250_rate_to_clk, +}; diff --git a/drivers/phy/samsung/phy-s5pv210-usb2.c b/drivers/phy/samsung/phy-s5pv210-usb2.c new file mode 100644 index 000000000000..f6f72339bbc3 --- /dev/null +++ b/drivers/phy/samsung/phy-s5pv210-usb2.c @@ -0,0 +1,187 @@ +/* + * Samsung SoC USB 1.1/2.0 PHY driver - S5PV210 support + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Authors: Kamil Debski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include "phy-samsung-usb2.h" + +/* Exynos USB PHY registers */ + +/* PHY power control */ +#define S5PV210_UPHYPWR 0x0 + +#define S5PV210_UPHYPWR_PHY0_SUSPEND BIT(0) +#define S5PV210_UPHYPWR_PHY0_PWR BIT(3) +#define S5PV210_UPHYPWR_PHY0_OTG_PWR BIT(4) +#define S5PV210_UPHYPWR_PHY0 ( \ + S5PV210_UPHYPWR_PHY0_SUSPEND | \ + S5PV210_UPHYPWR_PHY0_PWR | \ + S5PV210_UPHYPWR_PHY0_OTG_PWR) + +#define S5PV210_UPHYPWR_PHY1_SUSPEND BIT(6) +#define S5PV210_UPHYPWR_PHY1_PWR BIT(7) +#define S5PV210_UPHYPWR_PHY1 ( \ + S5PV210_UPHYPWR_PHY1_SUSPEND | \ + S5PV210_UPHYPWR_PHY1_PWR) + +/* PHY clock control */ +#define S5PV210_UPHYCLK 0x4 + +#define S5PV210_UPHYCLK_PHYFSEL_MASK (0x3 << 0) +#define S5PV210_UPHYCLK_PHYFSEL_48MHZ (0x0 << 0) +#define S5PV210_UPHYCLK_PHYFSEL_24MHZ (0x3 << 0) +#define S5PV210_UPHYCLK_PHYFSEL_12MHZ (0x2 << 0) + +#define S5PV210_UPHYCLK_PHY0_ID_PULLUP BIT(2) +#define S5PV210_UPHYCLK_PHY0_COMMON_ON BIT(4) +#define S5PV210_UPHYCLK_PHY1_COMMON_ON BIT(7) + +/* PHY reset control */ +#define S5PV210_UPHYRST 0x8 + +#define S5PV210_URSTCON_PHY0 BIT(0) +#define S5PV210_URSTCON_OTG_HLINK BIT(1) +#define S5PV210_URSTCON_OTG_PHYLINK BIT(2) +#define S5PV210_URSTCON_PHY1_ALL BIT(3) +#define S5PV210_URSTCON_HOST_LINK_ALL BIT(4) + +/* Isolation, configured in the power management unit */ +#define S5PV210_USB_ISOL_OFFSET 0x680c +#define S5PV210_USB_ISOL_DEVICE BIT(0) +#define S5PV210_USB_ISOL_HOST BIT(1) + + +enum s5pv210_phy_id { + S5PV210_DEVICE, + S5PV210_HOST, + S5PV210_NUM_PHYS, +}; + +/* + * s5pv210_rate_to_clk() converts the supplied clock rate to the value that + * can be written to the phy register. + */ +static int s5pv210_rate_to_clk(unsigned long rate, u32 *reg) +{ + switch (rate) { + case 12 * MHZ: + *reg = S5PV210_UPHYCLK_PHYFSEL_12MHZ; + break; + case 24 * MHZ: + *reg = S5PV210_UPHYCLK_PHYFSEL_24MHZ; + break; + case 48 * MHZ: + *reg = S5PV210_UPHYCLK_PHYFSEL_48MHZ; + break; + default: + return -EINVAL; + } + + return 0; +} + +static void s5pv210_isol(struct samsung_usb2_phy_instance *inst, bool on) +{ + struct samsung_usb2_phy_driver *drv = inst->drv; + u32 mask; + + switch (inst->cfg->id) { + case S5PV210_DEVICE: + mask = S5PV210_USB_ISOL_DEVICE; + break; + case S5PV210_HOST: + mask = S5PV210_USB_ISOL_HOST; + break; + default: + return; + } + + regmap_update_bits(drv->reg_pmu, S5PV210_USB_ISOL_OFFSET, + mask, on ? 0 : mask); +} + +static void s5pv210_phy_pwr(struct samsung_usb2_phy_instance *inst, bool on) +{ + struct samsung_usb2_phy_driver *drv = inst->drv; + u32 rstbits = 0; + u32 phypwr = 0; + u32 rst; + u32 pwr; + + switch (inst->cfg->id) { + case S5PV210_DEVICE: + phypwr = S5PV210_UPHYPWR_PHY0; + rstbits = S5PV210_URSTCON_PHY0; + break; + case S5PV210_HOST: + phypwr = S5PV210_UPHYPWR_PHY1; + rstbits = S5PV210_URSTCON_PHY1_ALL | + S5PV210_URSTCON_HOST_LINK_ALL; + break; + } + + if (on) { + writel(drv->ref_reg_val, drv->reg_phy + S5PV210_UPHYCLK); + + pwr = readl(drv->reg_phy + S5PV210_UPHYPWR); + pwr &= ~phypwr; + writel(pwr, drv->reg_phy + S5PV210_UPHYPWR); + + rst = readl(drv->reg_phy + S5PV210_UPHYRST); + rst |= rstbits; + writel(rst, drv->reg_phy + S5PV210_UPHYRST); + udelay(10); + rst &= ~rstbits; + writel(rst, drv->reg_phy + S5PV210_UPHYRST); + } else { + pwr = readl(drv->reg_phy + S5PV210_UPHYPWR); + pwr |= phypwr; + writel(pwr, drv->reg_phy + S5PV210_UPHYPWR); + } +} + +static int s5pv210_power_on(struct samsung_usb2_phy_instance *inst) +{ + s5pv210_isol(inst, 0); + s5pv210_phy_pwr(inst, 1); + + return 0; +} + +static int s5pv210_power_off(struct samsung_usb2_phy_instance *inst) +{ + s5pv210_phy_pwr(inst, 0); + s5pv210_isol(inst, 1); + + return 0; +} + +static const struct samsung_usb2_common_phy s5pv210_phys[S5PV210_NUM_PHYS] = { + [S5PV210_DEVICE] = { + .label = "device", + .id = S5PV210_DEVICE, + .power_on = s5pv210_power_on, + .power_off = s5pv210_power_off, + }, + [S5PV210_HOST] = { + .label = "host", + .id = S5PV210_HOST, + .power_on = s5pv210_power_on, + .power_off = s5pv210_power_off, + }, +}; + +const struct samsung_usb2_phy_config s5pv210_usb2_phy_config = { + .num_phys = ARRAY_SIZE(s5pv210_phys), + .phys = s5pv210_phys, + .rate_to_clk = s5pv210_rate_to_clk, +}; diff --git a/drivers/phy/samsung/phy-samsung-usb2.c b/drivers/phy/samsung/phy-samsung-usb2.c new file mode 100644 index 000000000000..1d22d93b552d --- /dev/null +++ b/drivers/phy/samsung/phy-samsung-usb2.c @@ -0,0 +1,267 @@ +/* + * Samsung SoC USB 1.1/2.0 PHY driver + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Author: Kamil Debski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "phy-samsung-usb2.h" + +static int samsung_usb2_phy_power_on(struct phy *phy) +{ + struct samsung_usb2_phy_instance *inst = phy_get_drvdata(phy); + struct samsung_usb2_phy_driver *drv = inst->drv; + int ret; + + dev_dbg(drv->dev, "Request to power_on \"%s\" usb phy\n", + inst->cfg->label); + + if (drv->vbus) { + ret = regulator_enable(drv->vbus); + if (ret) + goto err_regulator; + } + + ret = clk_prepare_enable(drv->clk); + if (ret) + goto err_main_clk; + ret = clk_prepare_enable(drv->ref_clk); + if (ret) + goto err_instance_clk; + if (inst->cfg->power_on) { + spin_lock(&drv->lock); + ret = inst->cfg->power_on(inst); + spin_unlock(&drv->lock); + if (ret) + goto err_power_on; + } + + return 0; + +err_power_on: + clk_disable_unprepare(drv->ref_clk); +err_instance_clk: + clk_disable_unprepare(drv->clk); +err_main_clk: + if (drv->vbus) + regulator_disable(drv->vbus); +err_regulator: + return ret; +} + +static int samsung_usb2_phy_power_off(struct phy *phy) +{ + struct samsung_usb2_phy_instance *inst = phy_get_drvdata(phy); + struct samsung_usb2_phy_driver *drv = inst->drv; + int ret = 0; + + dev_dbg(drv->dev, "Request to power_off \"%s\" usb phy\n", + inst->cfg->label); + if (inst->cfg->power_off) { + spin_lock(&drv->lock); + ret = inst->cfg->power_off(inst); + spin_unlock(&drv->lock); + if (ret) + return ret; + } + clk_disable_unprepare(drv->ref_clk); + clk_disable_unprepare(drv->clk); + if (drv->vbus) + ret = regulator_disable(drv->vbus); + + return ret; +} + +static const struct phy_ops samsung_usb2_phy_ops = { + .power_on = samsung_usb2_phy_power_on, + .power_off = samsung_usb2_phy_power_off, + .owner = THIS_MODULE, +}; + +static struct phy *samsung_usb2_phy_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct samsung_usb2_phy_driver *drv; + + drv = dev_get_drvdata(dev); + if (!drv) + return ERR_PTR(-EINVAL); + + if (WARN_ON(args->args[0] >= drv->cfg->num_phys)) + return ERR_PTR(-ENODEV); + + return drv->instances[args->args[0]].phy; +} + +static const struct of_device_id samsung_usb2_phy_of_match[] = { +#ifdef CONFIG_PHY_EXYNOS4X12_USB2 + { + .compatible = "samsung,exynos3250-usb2-phy", + .data = &exynos3250_usb2_phy_config, + }, +#endif +#ifdef CONFIG_PHY_EXYNOS4210_USB2 + { + .compatible = "samsung,exynos4210-usb2-phy", + .data = &exynos4210_usb2_phy_config, + }, +#endif +#ifdef CONFIG_PHY_EXYNOS4X12_USB2 + { + .compatible = "samsung,exynos4x12-usb2-phy", + .data = &exynos4x12_usb2_phy_config, + }, +#endif +#ifdef CONFIG_PHY_EXYNOS5250_USB2 + { + .compatible = "samsung,exynos5250-usb2-phy", + .data = &exynos5250_usb2_phy_config, + }, +#endif +#ifdef CONFIG_PHY_S5PV210_USB2 + { + .compatible = "samsung,s5pv210-usb2-phy", + .data = &s5pv210_usb2_phy_config, + }, +#endif + { }, +}; +MODULE_DEVICE_TABLE(of, samsung_usb2_phy_of_match); + +static int samsung_usb2_phy_probe(struct platform_device *pdev) +{ + const struct of_device_id *match; + const struct samsung_usb2_phy_config *cfg; + struct device *dev = &pdev->dev; + struct phy_provider *phy_provider; + struct resource *mem; + struct samsung_usb2_phy_driver *drv; + int i, ret; + + if (!pdev->dev.of_node) { + dev_err(dev, "This driver is required to be instantiated from device tree\n"); + return -EINVAL; + } + + match = of_match_node(samsung_usb2_phy_of_match, pdev->dev.of_node); + if (!match) { + dev_err(dev, "of_match_node() failed\n"); + return -EINVAL; + } + cfg = match->data; + + drv = devm_kzalloc(dev, sizeof(struct samsung_usb2_phy_driver) + + cfg->num_phys * sizeof(struct samsung_usb2_phy_instance), + GFP_KERNEL); + if (!drv) + return -ENOMEM; + + dev_set_drvdata(dev, drv); + spin_lock_init(&drv->lock); + + drv->cfg = cfg; + drv->dev = dev; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + drv->reg_phy = devm_ioremap_resource(dev, mem); + if (IS_ERR(drv->reg_phy)) { + dev_err(dev, "Failed to map register memory (phy)\n"); + return PTR_ERR(drv->reg_phy); + } + + drv->reg_pmu = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, + "samsung,pmureg-phandle"); + if (IS_ERR(drv->reg_pmu)) { + dev_err(dev, "Failed to map PMU registers (via syscon)\n"); + return PTR_ERR(drv->reg_pmu); + } + + if (drv->cfg->has_mode_switch) { + drv->reg_sys = syscon_regmap_lookup_by_phandle( + pdev->dev.of_node, "samsung,sysreg-phandle"); + if (IS_ERR(drv->reg_sys)) { + dev_err(dev, "Failed to map system registers (via syscon)\n"); + return PTR_ERR(drv->reg_sys); + } + } + + drv->clk = devm_clk_get(dev, "phy"); + if (IS_ERR(drv->clk)) { + dev_err(dev, "Failed to get clock of phy controller\n"); + return PTR_ERR(drv->clk); + } + + drv->ref_clk = devm_clk_get(dev, "ref"); + if (IS_ERR(drv->ref_clk)) { + dev_err(dev, "Failed to get reference clock for the phy controller\n"); + return PTR_ERR(drv->ref_clk); + } + + drv->ref_rate = clk_get_rate(drv->ref_clk); + if (drv->cfg->rate_to_clk) { + ret = drv->cfg->rate_to_clk(drv->ref_rate, &drv->ref_reg_val); + if (ret) + return ret; + } + + drv->vbus = devm_regulator_get(dev, "vbus"); + if (IS_ERR(drv->vbus)) { + ret = PTR_ERR(drv->vbus); + if (ret == -EPROBE_DEFER) + return ret; + drv->vbus = NULL; + } + + for (i = 0; i < drv->cfg->num_phys; i++) { + char *label = drv->cfg->phys[i].label; + struct samsung_usb2_phy_instance *p = &drv->instances[i]; + + dev_dbg(dev, "Creating phy \"%s\"\n", label); + p->phy = devm_phy_create(dev, NULL, &samsung_usb2_phy_ops); + if (IS_ERR(p->phy)) { + dev_err(drv->dev, "Failed to create usb2_phy \"%s\"\n", + label); + return PTR_ERR(p->phy); + } + + p->cfg = &drv->cfg->phys[i]; + p->drv = drv; + phy_set_bus_width(p->phy, 8); + phy_set_drvdata(p->phy, p); + } + + phy_provider = devm_of_phy_provider_register(dev, + samsung_usb2_phy_xlate); + if (IS_ERR(phy_provider)) { + dev_err(drv->dev, "Failed to register phy provider\n"); + return PTR_ERR(phy_provider); + } + + return 0; +} + +static struct platform_driver samsung_usb2_phy_driver = { + .probe = samsung_usb2_phy_probe, + .driver = { + .of_match_table = samsung_usb2_phy_of_match, + .name = "samsung-usb2-phy", + } +}; + +module_platform_driver(samsung_usb2_phy_driver); +MODULE_DESCRIPTION("Samsung S5P/EXYNOS SoC USB PHY driver"); +MODULE_AUTHOR("Kamil Debski "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:samsung-usb2-phy"); diff --git a/drivers/phy/samsung/phy-samsung-usb2.h b/drivers/phy/samsung/phy-samsung-usb2.h new file mode 100644 index 000000000000..6563e7ca0ac4 --- /dev/null +++ b/drivers/phy/samsung/phy-samsung-usb2.h @@ -0,0 +1,73 @@ +/* + * Samsung SoC USB 1.1/2.0 PHY driver + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Author: Kamil Debski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _PHY_EXYNOS_USB2_H +#define _PHY_EXYNOS_USB2_H + +#include +#include +#include +#include +#include +#include + +#define KHZ 1000 +#define MHZ (KHZ * KHZ) + +struct samsung_usb2_phy_driver; +struct samsung_usb2_phy_instance; +struct samsung_usb2_phy_config; + +struct samsung_usb2_phy_instance { + const struct samsung_usb2_common_phy *cfg; + struct phy *phy; + struct samsung_usb2_phy_driver *drv; + int int_cnt; + int ext_cnt; +}; + +struct samsung_usb2_phy_driver { + const struct samsung_usb2_phy_config *cfg; + struct clk *clk; + struct clk *ref_clk; + struct regulator *vbus; + unsigned long ref_rate; + u32 ref_reg_val; + struct device *dev; + void __iomem *reg_phy; + struct regmap *reg_pmu; + struct regmap *reg_sys; + spinlock_t lock; + struct samsung_usb2_phy_instance instances[0]; +}; + +struct samsung_usb2_common_phy { + int (*power_on)(struct samsung_usb2_phy_instance *); + int (*power_off)(struct samsung_usb2_phy_instance *); + unsigned int id; + char *label; +}; + + +struct samsung_usb2_phy_config { + const struct samsung_usb2_common_phy *phys; + int (*rate_to_clk)(unsigned long, u32 *); + unsigned int num_phys; + bool has_mode_switch; + bool has_refclk_sel; +}; + +extern const struct samsung_usb2_phy_config exynos3250_usb2_phy_config; +extern const struct samsung_usb2_phy_config exynos4210_usb2_phy_config; +extern const struct samsung_usb2_phy_config exynos4x12_usb2_phy_config; +extern const struct samsung_usb2_phy_config exynos5250_usb2_phy_config; +extern const struct samsung_usb2_phy_config s5pv210_usb2_phy_config; +#endif diff --git a/drivers/phy/st/Kconfig b/drivers/phy/st/Kconfig new file mode 100644 index 000000000000..0814d3f87ec6 --- /dev/null +++ b/drivers/phy/st/Kconfig @@ -0,0 +1,33 @@ +# +# Phy drivers for STMicro platforms +# +config PHY_MIPHY28LP + tristate "STMicroelectronics MIPHY28LP PHY driver for STiH407" + depends on ARCH_STI + select GENERIC_PHY + help + Enable this to support the miphy transceiver (for SATA/PCIE/USB3) + that is part of STMicroelectronics STiH407 SoC. + +config PHY_ST_SPEAR1310_MIPHY + tristate "ST SPEAR1310-MIPHY driver" + select GENERIC_PHY + depends on MACH_SPEAR1310 || COMPILE_TEST + help + Support for ST SPEAr1310 MIPHY which can be used for PCIe and SATA. + +config PHY_ST_SPEAR1340_MIPHY + tristate "ST SPEAR1340-MIPHY driver" + select GENERIC_PHY + depends on MACH_SPEAR1340 || COMPILE_TEST + help + Support for ST SPEAr1340 MIPHY which can be used for PCIe and SATA. + +config PHY_STIH407_USB + tristate "STMicroelectronics USB2 picoPHY driver for STiH407 family" + depends on RESET_CONTROLLER + depends on ARCH_STI || COMPILE_TEST + select GENERIC_PHY + help + Enable this support to enable the picoPHY device used by USB2 + and USB3 controllers on STMicroelectronics STiH407 SoC families. diff --git a/drivers/phy/st/Makefile b/drivers/phy/st/Makefile new file mode 100644 index 000000000000..e2adfe2166d2 --- /dev/null +++ b/drivers/phy/st/Makefile @@ -0,0 +1,4 @@ +obj-$(CONFIG_PHY_MIPHY28LP) += phy-miphy28lp.o +obj-$(CONFIG_PHY_ST_SPEAR1310_MIPHY) += phy-spear1310-miphy.o +obj-$(CONFIG_PHY_ST_SPEAR1340_MIPHY) += phy-spear1340-miphy.o +obj-$(CONFIG_PHY_STIH407_USB) += phy-stih407-usb.o diff --git a/drivers/phy/st/phy-miphy28lp.c b/drivers/phy/st/phy-miphy28lp.c new file mode 100644 index 000000000000..213e2e15339c --- /dev/null +++ b/drivers/phy/st/phy-miphy28lp.c @@ -0,0 +1,1286 @@ +/* + * Copyright (C) 2014 STMicroelectronics + * + * STMicroelectronics PHY driver MiPHY28lp (for SoC STiH407). + * + * Author: Alexandre Torgue + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* MiPHY registers */ +#define MIPHY_CONF_RESET 0x00 +#define RST_APPLI_SW BIT(0) +#define RST_CONF_SW BIT(1) +#define RST_MACRO_SW BIT(2) + +#define MIPHY_RESET 0x01 +#define RST_PLL_SW BIT(0) +#define RST_COMP_SW BIT(2) + +#define MIPHY_STATUS_1 0x02 +#define PHY_RDY BIT(0) +#define HFC_RDY BIT(1) +#define HFC_PLL BIT(2) + +#define MIPHY_CONTROL 0x04 +#define TERM_EN_SW BIT(2) +#define DIS_LINK_RST BIT(3) +#define AUTO_RST_RX BIT(4) +#define PX_RX_POL BIT(5) + +#define MIPHY_BOUNDARY_SEL 0x0a +#define TX_SEL BIT(6) +#define SSC_SEL BIT(4) +#define GENSEL_SEL BIT(0) + +#define MIPHY_BOUNDARY_1 0x0b +#define MIPHY_BOUNDARY_2 0x0c +#define SSC_EN_SW BIT(2) + +#define MIPHY_PLL_CLKREF_FREQ 0x0d +#define MIPHY_SPEED 0x0e +#define TX_SPDSEL_80DEC 0 +#define TX_SPDSEL_40DEC 1 +#define TX_SPDSEL_20DEC 2 +#define RX_SPDSEL_80DEC 0 +#define RX_SPDSEL_40DEC (1 << 2) +#define RX_SPDSEL_20DEC (2 << 2) + +#define MIPHY_CONF 0x0f +#define MIPHY_CTRL_TEST_SEL 0x20 +#define MIPHY_CTRL_TEST_1 0x21 +#define MIPHY_CTRL_TEST_2 0x22 +#define MIPHY_CTRL_TEST_3 0x23 +#define MIPHY_CTRL_TEST_4 0x24 +#define MIPHY_FEEDBACK_TEST 0x25 +#define MIPHY_DEBUG_BUS 0x26 +#define MIPHY_DEBUG_STATUS_MSB 0x27 +#define MIPHY_DEBUG_STATUS_LSB 0x28 +#define MIPHY_PWR_RAIL_1 0x29 +#define MIPHY_PWR_RAIL_2 0x2a +#define MIPHY_SYNCHAR_CONTROL 0x30 + +#define MIPHY_COMP_FSM_1 0x3a +#define COMP_START BIT(6) + +#define MIPHY_COMP_FSM_6 0x3f +#define COMP_DONE BIT(7) + +#define MIPHY_COMP_POSTP 0x42 +#define MIPHY_TX_CTRL_1 0x49 +#define TX_REG_STEP_0V 0 +#define TX_REG_STEP_P_25MV 1 +#define TX_REG_STEP_P_50MV 2 +#define TX_REG_STEP_N_25MV 7 +#define TX_REG_STEP_N_50MV 6 +#define TX_REG_STEP_N_75MV 5 + +#define MIPHY_TX_CTRL_2 0x4a +#define TX_SLEW_SW_40_PS 0 +#define TX_SLEW_SW_80_PS 1 +#define TX_SLEW_SW_120_PS 2 + +#define MIPHY_TX_CTRL_3 0x4b +#define MIPHY_TX_CAL_MAN 0x4e +#define TX_SLEW_CAL_MAN_EN BIT(0) + +#define MIPHY_TST_BIAS_BOOST_2 0x62 +#define MIPHY_BIAS_BOOST_1 0x63 +#define MIPHY_BIAS_BOOST_2 0x64 +#define MIPHY_RX_DESBUFF_FDB_2 0x67 +#define MIPHY_RX_DESBUFF_FDB_3 0x68 +#define MIPHY_SIGDET_COMPENS1 0x69 +#define MIPHY_SIGDET_COMPENS2 0x6a +#define MIPHY_JITTER_PERIOD 0x6b +#define MIPHY_JITTER_AMPLITUDE_1 0x6c +#define MIPHY_JITTER_AMPLITUDE_2 0x6d +#define MIPHY_JITTER_AMPLITUDE_3 0x6e +#define MIPHY_RX_K_GAIN 0x78 +#define MIPHY_RX_BUFFER_CTRL 0x7a +#define VGA_GAIN BIT(0) +#define EQ_DC_GAIN BIT(2) +#define EQ_BOOST_GAIN BIT(3) + +#define MIPHY_RX_VGA_GAIN 0x7b +#define MIPHY_RX_EQU_GAIN_1 0x7f +#define MIPHY_RX_EQU_GAIN_2 0x80 +#define MIPHY_RX_EQU_GAIN_3 0x81 +#define MIPHY_RX_CAL_CTRL_1 0x97 +#define MIPHY_RX_CAL_CTRL_2 0x98 + +#define MIPHY_RX_CAL_OFFSET_CTRL 0x99 +#define CAL_OFFSET_VGA_64 (0x03 << 0) +#define CAL_OFFSET_THRESHOLD_64 (0x03 << 2) +#define VGA_OFFSET_POLARITY BIT(4) +#define OFFSET_COMPENSATION_EN BIT(6) + +#define MIPHY_RX_CAL_VGA_STEP 0x9a +#define MIPHY_RX_CAL_EYE_MIN 0x9d +#define MIPHY_RX_CAL_OPT_LENGTH 0x9f +#define MIPHY_RX_LOCK_CTRL_1 0xc1 +#define MIPHY_RX_LOCK_SETTINGS_OPT 0xc2 +#define MIPHY_RX_LOCK_STEP 0xc4 + +#define MIPHY_RX_SIGDET_SLEEP_OA 0xc9 +#define MIPHY_RX_SIGDET_SLEEP_SEL 0xca +#define MIPHY_RX_SIGDET_WAIT_SEL 0xcb +#define MIPHY_RX_SIGDET_DATA_SEL 0xcc +#define EN_ULTRA_LOW_POWER BIT(0) +#define EN_FIRST_HALF BIT(1) +#define EN_SECOND_HALF BIT(2) +#define EN_DIGIT_SIGNAL_CHECK BIT(3) + +#define MIPHY_RX_POWER_CTRL_1 0xcd +#define MIPHY_RX_POWER_CTRL_2 0xce +#define MIPHY_PLL_CALSET_CTRL 0xd3 +#define MIPHY_PLL_CALSET_1 0xd4 +#define MIPHY_PLL_CALSET_2 0xd5 +#define MIPHY_PLL_CALSET_3 0xd6 +#define MIPHY_PLL_CALSET_4 0xd7 +#define MIPHY_PLL_SBR_1 0xe3 +#define SET_NEW_CHANGE BIT(1) + +#define MIPHY_PLL_SBR_2 0xe4 +#define MIPHY_PLL_SBR_3 0xe5 +#define MIPHY_PLL_SBR_4 0xe6 +#define MIPHY_PLL_COMMON_MISC_2 0xe9 +#define START_ACT_FILT BIT(6) + +#define MIPHY_PLL_SPAREIN 0xeb + +/* + * On STiH407 the glue logic can be different among MiPHY devices; for example: + * MiPHY0: OSC_FORCE_EXT means: + * 0: 30MHz crystal clk - 1: 100MHz ext clk routed through MiPHY1 + * MiPHY1: OSC_FORCE_EXT means: + * 1: 30MHz crystal clk - 0: 100MHz ext clk routed through MiPHY1 + * Some devices have not the possibility to check if the osc is ready. + */ +#define MIPHY_OSC_FORCE_EXT BIT(3) +#define MIPHY_OSC_RDY BIT(5) + +#define MIPHY_CTRL_MASK 0x0f +#define MIPHY_CTRL_DEFAULT 0 +#define MIPHY_CTRL_SYNC_D_EN BIT(2) + +/* SATA / PCIe defines */ +#define SATA_CTRL_MASK 0x07 +#define PCIE_CTRL_MASK 0xff +#define SATA_CTRL_SELECT_SATA 1 +#define SATA_CTRL_SELECT_PCIE 0 +#define SYSCFG_PCIE_PCIE_VAL 0x80 +#define SATA_SPDMODE 1 + +#define MIPHY_SATA_BANK_NB 3 +#define MIPHY_PCIE_BANK_NB 2 + +enum { + SYSCFG_CTRL, + SYSCFG_STATUS, + SYSCFG_PCI, + SYSCFG_SATA, + SYSCFG_REG_MAX, +}; + +struct miphy28lp_phy { + struct phy *phy; + struct miphy28lp_dev *phydev; + void __iomem *base; + void __iomem *pipebase; + + bool osc_force_ext; + bool osc_rdy; + bool px_rx_pol_inv; + bool ssc; + bool tx_impedance; + + struct reset_control *miphy_rst; + + u32 sata_gen; + + /* Sysconfig registers offsets needed to configure the device */ + u32 syscfg_reg[SYSCFG_REG_MAX]; + u8 type; +}; + +struct miphy28lp_dev { + struct device *dev; + struct regmap *regmap; + struct mutex miphy_mutex; + struct miphy28lp_phy **phys; + int nphys; +}; + +struct miphy_initval { + u16 reg; + u16 val; +}; + +enum miphy_sata_gen { SATA_GEN1, SATA_GEN2, SATA_GEN3 }; + +static char *PHY_TYPE_name[] = { "sata-up", "pcie-up", "", "usb3-up" }; + +struct pll_ratio { + int clk_ref; + int calset_1; + int calset_2; + int calset_3; + int calset_4; + int cal_ctrl; +}; + +static struct pll_ratio sata_pll_ratio = { + .clk_ref = 0x1e, + .calset_1 = 0xc8, + .calset_2 = 0x00, + .calset_3 = 0x00, + .calset_4 = 0x00, + .cal_ctrl = 0x00, +}; + +static struct pll_ratio pcie_pll_ratio = { + .clk_ref = 0x1e, + .calset_1 = 0xa6, + .calset_2 = 0xaa, + .calset_3 = 0xaa, + .calset_4 = 0x00, + .cal_ctrl = 0x00, +}; + +static struct pll_ratio usb3_pll_ratio = { + .clk_ref = 0x1e, + .calset_1 = 0xa6, + .calset_2 = 0xaa, + .calset_3 = 0xaa, + .calset_4 = 0x04, + .cal_ctrl = 0x00, +}; + +struct miphy28lp_pll_gen { + int bank; + int speed; + int bias_boost_1; + int bias_boost_2; + int tx_ctrl_1; + int tx_ctrl_2; + int tx_ctrl_3; + int rx_k_gain; + int rx_vga_gain; + int rx_equ_gain_1; + int rx_equ_gain_2; + int rx_equ_gain_3; + int rx_buff_ctrl; +}; + +static struct miphy28lp_pll_gen sata_pll_gen[] = { + { + .bank = 0x00, + .speed = TX_SPDSEL_80DEC | RX_SPDSEL_80DEC, + .bias_boost_1 = 0x00, + .bias_boost_2 = 0xae, + .tx_ctrl_2 = 0x53, + .tx_ctrl_3 = 0x00, + .rx_buff_ctrl = EQ_BOOST_GAIN | EQ_DC_GAIN | VGA_GAIN, + .rx_vga_gain = 0x00, + .rx_equ_gain_1 = 0x7d, + .rx_equ_gain_2 = 0x56, + .rx_equ_gain_3 = 0x00, + }, + { + .bank = 0x01, + .speed = TX_SPDSEL_40DEC | RX_SPDSEL_40DEC, + .bias_boost_1 = 0x00, + .bias_boost_2 = 0xae, + .tx_ctrl_2 = 0x72, + .tx_ctrl_3 = 0x20, + .rx_buff_ctrl = EQ_BOOST_GAIN | EQ_DC_GAIN | VGA_GAIN, + .rx_vga_gain = 0x00, + .rx_equ_gain_1 = 0x7d, + .rx_equ_gain_2 = 0x56, + .rx_equ_gain_3 = 0x00, + }, + { + .bank = 0x02, + .speed = TX_SPDSEL_20DEC | RX_SPDSEL_20DEC, + .bias_boost_1 = 0x00, + .bias_boost_2 = 0xae, + .tx_ctrl_2 = 0xc0, + .tx_ctrl_3 = 0x20, + .rx_buff_ctrl = EQ_BOOST_GAIN | EQ_DC_GAIN | VGA_GAIN, + .rx_vga_gain = 0x00, + .rx_equ_gain_1 = 0x7d, + .rx_equ_gain_2 = 0x56, + .rx_equ_gain_3 = 0x00, + }, +}; + +static struct miphy28lp_pll_gen pcie_pll_gen[] = { + { + .bank = 0x00, + .speed = TX_SPDSEL_40DEC | RX_SPDSEL_40DEC, + .bias_boost_1 = 0x00, + .bias_boost_2 = 0xa5, + .tx_ctrl_1 = TX_REG_STEP_N_25MV, + .tx_ctrl_2 = 0x71, + .tx_ctrl_3 = 0x60, + .rx_k_gain = 0x98, + .rx_buff_ctrl = EQ_BOOST_GAIN | EQ_DC_GAIN | VGA_GAIN, + .rx_vga_gain = 0x00, + .rx_equ_gain_1 = 0x79, + .rx_equ_gain_2 = 0x56, + }, + { + .bank = 0x01, + .speed = TX_SPDSEL_20DEC | RX_SPDSEL_20DEC, + .bias_boost_1 = 0x00, + .bias_boost_2 = 0xa5, + .tx_ctrl_1 = TX_REG_STEP_N_25MV, + .tx_ctrl_2 = 0x70, + .tx_ctrl_3 = 0x60, + .rx_k_gain = 0xcc, + .rx_buff_ctrl = EQ_BOOST_GAIN | EQ_DC_GAIN | VGA_GAIN, + .rx_vga_gain = 0x00, + .rx_equ_gain_1 = 0x78, + .rx_equ_gain_2 = 0x07, + }, +}; + +static inline void miphy28lp_set_reset(struct miphy28lp_phy *miphy_phy) +{ + void __iomem *base = miphy_phy->base; + u8 val; + + /* Putting Macro in reset */ + writeb_relaxed(RST_APPLI_SW, base + MIPHY_CONF_RESET); + + val = RST_APPLI_SW | RST_CONF_SW; + writeb_relaxed(val, base + MIPHY_CONF_RESET); + + writeb_relaxed(RST_APPLI_SW, base + MIPHY_CONF_RESET); + + /* Bringing the MIPHY-CPU registers out of reset */ + if (miphy_phy->type == PHY_TYPE_PCIE) { + val = AUTO_RST_RX | TERM_EN_SW; + writeb_relaxed(val, base + MIPHY_CONTROL); + } else { + val = AUTO_RST_RX | TERM_EN_SW | DIS_LINK_RST; + writeb_relaxed(val, base + MIPHY_CONTROL); + } +} + +static inline void miphy28lp_pll_calibration(struct miphy28lp_phy *miphy_phy, + struct pll_ratio *pll_ratio) +{ + void __iomem *base = miphy_phy->base; + u8 val; + + /* Applying PLL Settings */ + writeb_relaxed(0x1d, base + MIPHY_PLL_SPAREIN); + writeb_relaxed(pll_ratio->clk_ref, base + MIPHY_PLL_CLKREF_FREQ); + + /* PLL Ratio */ + writeb_relaxed(pll_ratio->calset_1, base + MIPHY_PLL_CALSET_1); + writeb_relaxed(pll_ratio->calset_2, base + MIPHY_PLL_CALSET_2); + writeb_relaxed(pll_ratio->calset_3, base + MIPHY_PLL_CALSET_3); + writeb_relaxed(pll_ratio->calset_4, base + MIPHY_PLL_CALSET_4); + writeb_relaxed(pll_ratio->cal_ctrl, base + MIPHY_PLL_CALSET_CTRL); + + writeb_relaxed(TX_SEL, base + MIPHY_BOUNDARY_SEL); + + val = (0x68 << 1) | TX_SLEW_CAL_MAN_EN; + writeb_relaxed(val, base + MIPHY_TX_CAL_MAN); + + val = VGA_OFFSET_POLARITY | CAL_OFFSET_THRESHOLD_64 | CAL_OFFSET_VGA_64; + + if (miphy_phy->type != PHY_TYPE_SATA) + val |= OFFSET_COMPENSATION_EN; + + writeb_relaxed(val, base + MIPHY_RX_CAL_OFFSET_CTRL); + + if (miphy_phy->type == PHY_TYPE_USB3) { + writeb_relaxed(0x00, base + MIPHY_CONF); + writeb_relaxed(0x70, base + MIPHY_RX_LOCK_STEP); + writeb_relaxed(EN_FIRST_HALF, base + MIPHY_RX_SIGDET_SLEEP_OA); + writeb_relaxed(EN_FIRST_HALF, base + MIPHY_RX_SIGDET_SLEEP_SEL); + writeb_relaxed(EN_FIRST_HALF, base + MIPHY_RX_SIGDET_WAIT_SEL); + + val = EN_DIGIT_SIGNAL_CHECK | EN_FIRST_HALF; + writeb_relaxed(val, base + MIPHY_RX_SIGDET_DATA_SEL); + } + +} + +static inline void miphy28lp_sata_config_gen(struct miphy28lp_phy *miphy_phy) +{ + void __iomem *base = miphy_phy->base; + int i; + + for (i = 0; i < ARRAY_SIZE(sata_pll_gen); i++) { + struct miphy28lp_pll_gen *gen = &sata_pll_gen[i]; + + /* Banked settings */ + writeb_relaxed(gen->bank, base + MIPHY_CONF); + writeb_relaxed(gen->speed, base + MIPHY_SPEED); + writeb_relaxed(gen->bias_boost_1, base + MIPHY_BIAS_BOOST_1); + writeb_relaxed(gen->bias_boost_2, base + MIPHY_BIAS_BOOST_2); + + /* TX buffer Settings */ + writeb_relaxed(gen->tx_ctrl_2, base + MIPHY_TX_CTRL_2); + writeb_relaxed(gen->tx_ctrl_3, base + MIPHY_TX_CTRL_3); + + /* RX Buffer Settings */ + writeb_relaxed(gen->rx_buff_ctrl, base + MIPHY_RX_BUFFER_CTRL); + writeb_relaxed(gen->rx_vga_gain, base + MIPHY_RX_VGA_GAIN); + writeb_relaxed(gen->rx_equ_gain_1, base + MIPHY_RX_EQU_GAIN_1); + writeb_relaxed(gen->rx_equ_gain_2, base + MIPHY_RX_EQU_GAIN_2); + writeb_relaxed(gen->rx_equ_gain_3, base + MIPHY_RX_EQU_GAIN_3); + } +} + +static inline void miphy28lp_pcie_config_gen(struct miphy28lp_phy *miphy_phy) +{ + void __iomem *base = miphy_phy->base; + int i; + + for (i = 0; i < ARRAY_SIZE(pcie_pll_gen); i++) { + struct miphy28lp_pll_gen *gen = &pcie_pll_gen[i]; + + /* Banked settings */ + writeb_relaxed(gen->bank, base + MIPHY_CONF); + writeb_relaxed(gen->speed, base + MIPHY_SPEED); + writeb_relaxed(gen->bias_boost_1, base + MIPHY_BIAS_BOOST_1); + writeb_relaxed(gen->bias_boost_2, base + MIPHY_BIAS_BOOST_2); + + /* TX buffer Settings */ + writeb_relaxed(gen->tx_ctrl_1, base + MIPHY_TX_CTRL_1); + writeb_relaxed(gen->tx_ctrl_2, base + MIPHY_TX_CTRL_2); + writeb_relaxed(gen->tx_ctrl_3, base + MIPHY_TX_CTRL_3); + + writeb_relaxed(gen->rx_k_gain, base + MIPHY_RX_K_GAIN); + + /* RX Buffer Settings */ + writeb_relaxed(gen->rx_buff_ctrl, base + MIPHY_RX_BUFFER_CTRL); + writeb_relaxed(gen->rx_vga_gain, base + MIPHY_RX_VGA_GAIN); + writeb_relaxed(gen->rx_equ_gain_1, base + MIPHY_RX_EQU_GAIN_1); + writeb_relaxed(gen->rx_equ_gain_2, base + MIPHY_RX_EQU_GAIN_2); + } +} + +static inline int miphy28lp_wait_compensation(struct miphy28lp_phy *miphy_phy) +{ + unsigned long finish = jiffies + 5 * HZ; + u8 val; + + /* Waiting for Compensation to complete */ + do { + val = readb_relaxed(miphy_phy->base + MIPHY_COMP_FSM_6); + + if (time_after_eq(jiffies, finish)) + return -EBUSY; + cpu_relax(); + } while (!(val & COMP_DONE)); + + return 0; +} + + +static inline int miphy28lp_compensation(struct miphy28lp_phy *miphy_phy, + struct pll_ratio *pll_ratio) +{ + void __iomem *base = miphy_phy->base; + + /* Poll for HFC ready after reset release */ + /* Compensation measurement */ + writeb_relaxed(RST_PLL_SW | RST_COMP_SW, base + MIPHY_RESET); + + writeb_relaxed(0x00, base + MIPHY_PLL_COMMON_MISC_2); + writeb_relaxed(pll_ratio->clk_ref, base + MIPHY_PLL_CLKREF_FREQ); + writeb_relaxed(COMP_START, base + MIPHY_COMP_FSM_1); + + if (miphy_phy->type == PHY_TYPE_PCIE) + writeb_relaxed(RST_PLL_SW, base + MIPHY_RESET); + + writeb_relaxed(0x00, base + MIPHY_RESET); + writeb_relaxed(START_ACT_FILT, base + MIPHY_PLL_COMMON_MISC_2); + writeb_relaxed(SET_NEW_CHANGE, base + MIPHY_PLL_SBR_1); + + /* TX compensation offset to re-center TX impedance */ + writeb_relaxed(0x00, base + MIPHY_COMP_POSTP); + + if (miphy_phy->type == PHY_TYPE_PCIE) + return miphy28lp_wait_compensation(miphy_phy); + + return 0; +} + +static inline void miphy28_usb3_miphy_reset(struct miphy28lp_phy *miphy_phy) +{ + void __iomem *base = miphy_phy->base; + u8 val; + + /* MIPHY Reset */ + writeb_relaxed(RST_APPLI_SW, base + MIPHY_CONF_RESET); + writeb_relaxed(0x00, base + MIPHY_CONF_RESET); + writeb_relaxed(RST_COMP_SW, base + MIPHY_RESET); + + val = RST_COMP_SW | RST_PLL_SW; + writeb_relaxed(val, base + MIPHY_RESET); + + writeb_relaxed(0x00, base + MIPHY_PLL_COMMON_MISC_2); + writeb_relaxed(0x1e, base + MIPHY_PLL_CLKREF_FREQ); + writeb_relaxed(COMP_START, base + MIPHY_COMP_FSM_1); + writeb_relaxed(RST_PLL_SW, base + MIPHY_RESET); + writeb_relaxed(0x00, base + MIPHY_RESET); + writeb_relaxed(START_ACT_FILT, base + MIPHY_PLL_COMMON_MISC_2); + writeb_relaxed(0x00, base + MIPHY_CONF); + writeb_relaxed(0x00, base + MIPHY_BOUNDARY_1); + writeb_relaxed(0x00, base + MIPHY_TST_BIAS_BOOST_2); + writeb_relaxed(0x00, base + MIPHY_CONF); + writeb_relaxed(SET_NEW_CHANGE, base + MIPHY_PLL_SBR_1); + writeb_relaxed(0xa5, base + MIPHY_DEBUG_BUS); + writeb_relaxed(0x00, base + MIPHY_CONF); +} + +static void miphy_sata_tune_ssc(struct miphy28lp_phy *miphy_phy) +{ + void __iomem *base = miphy_phy->base; + u8 val; + + /* Compensate Tx impedance to avoid out of range values */ + /* + * Enable the SSC on PLL for all banks + * SSC Modulation @ 31 KHz and 4000 ppm modulation amp + */ + val = readb_relaxed(base + MIPHY_BOUNDARY_2); + val |= SSC_EN_SW; + writeb_relaxed(val, base + MIPHY_BOUNDARY_2); + + val = readb_relaxed(base + MIPHY_BOUNDARY_SEL); + val |= SSC_SEL; + writeb_relaxed(val, base + MIPHY_BOUNDARY_SEL); + + for (val = 0; val < MIPHY_SATA_BANK_NB; val++) { + writeb_relaxed(val, base + MIPHY_CONF); + + /* Add value to each reference clock cycle */ + /* and define the period length of the SSC */ + writeb_relaxed(0x3c, base + MIPHY_PLL_SBR_2); + writeb_relaxed(0x6c, base + MIPHY_PLL_SBR_3); + writeb_relaxed(0x81, base + MIPHY_PLL_SBR_4); + + /* Clear any previous request */ + writeb_relaxed(0x00, base + MIPHY_PLL_SBR_1); + + /* requests the PLL to take in account new parameters */ + writeb_relaxed(SET_NEW_CHANGE, base + MIPHY_PLL_SBR_1); + + /* To be sure there is no other pending requests */ + writeb_relaxed(0x00, base + MIPHY_PLL_SBR_1); + } +} + +static void miphy_pcie_tune_ssc(struct miphy28lp_phy *miphy_phy) +{ + void __iomem *base = miphy_phy->base; + u8 val; + + /* Compensate Tx impedance to avoid out of range values */ + /* + * Enable the SSC on PLL for all banks + * SSC Modulation @ 31 KHz and 4000 ppm modulation amp + */ + val = readb_relaxed(base + MIPHY_BOUNDARY_2); + val |= SSC_EN_SW; + writeb_relaxed(val, base + MIPHY_BOUNDARY_2); + + val = readb_relaxed(base + MIPHY_BOUNDARY_SEL); + val |= SSC_SEL; + writeb_relaxed(val, base + MIPHY_BOUNDARY_SEL); + + for (val = 0; val < MIPHY_PCIE_BANK_NB; val++) { + writeb_relaxed(val, base + MIPHY_CONF); + + /* Validate Step component */ + writeb_relaxed(0x69, base + MIPHY_PLL_SBR_3); + writeb_relaxed(0x21, base + MIPHY_PLL_SBR_4); + + /* Validate Period component */ + writeb_relaxed(0x3c, base + MIPHY_PLL_SBR_2); + writeb_relaxed(0x21, base + MIPHY_PLL_SBR_4); + + /* Clear any previous request */ + writeb_relaxed(0x00, base + MIPHY_PLL_SBR_1); + + /* requests the PLL to take in account new parameters */ + writeb_relaxed(SET_NEW_CHANGE, base + MIPHY_PLL_SBR_1); + + /* To be sure there is no other pending requests */ + writeb_relaxed(0x00, base + MIPHY_PLL_SBR_1); + } +} + +static inline void miphy_tune_tx_impedance(struct miphy28lp_phy *miphy_phy) +{ + /* Compensate Tx impedance to avoid out of range values */ + writeb_relaxed(0x02, miphy_phy->base + MIPHY_COMP_POSTP); +} + +static inline int miphy28lp_configure_sata(struct miphy28lp_phy *miphy_phy) +{ + void __iomem *base = miphy_phy->base; + int err; + u8 val; + + /* Putting Macro in reset */ + miphy28lp_set_reset(miphy_phy); + + /* PLL calibration */ + miphy28lp_pll_calibration(miphy_phy, &sata_pll_ratio); + + /* Banked settings Gen1/Gen2/Gen3 */ + miphy28lp_sata_config_gen(miphy_phy); + + /* Power control */ + /* Input bridge enable, manual input bridge control */ + writeb_relaxed(0x21, base + MIPHY_RX_POWER_CTRL_1); + + /* Macro out of reset */ + writeb_relaxed(0x00, base + MIPHY_CONF_RESET); + + /* Poll for HFC ready after reset release */ + /* Compensation measurement */ + err = miphy28lp_compensation(miphy_phy, &sata_pll_ratio); + if (err) + return err; + + if (miphy_phy->px_rx_pol_inv) { + /* Invert Rx polarity */ + val = readb_relaxed(miphy_phy->base + MIPHY_CONTROL); + val |= PX_RX_POL; + writeb_relaxed(val, miphy_phy->base + MIPHY_CONTROL); + } + + if (miphy_phy->ssc) + miphy_sata_tune_ssc(miphy_phy); + + if (miphy_phy->tx_impedance) + miphy_tune_tx_impedance(miphy_phy); + + return 0; +} + +static inline int miphy28lp_configure_pcie(struct miphy28lp_phy *miphy_phy) +{ + void __iomem *base = miphy_phy->base; + int err; + + /* Putting Macro in reset */ + miphy28lp_set_reset(miphy_phy); + + /* PLL calibration */ + miphy28lp_pll_calibration(miphy_phy, &pcie_pll_ratio); + + /* Banked settings Gen1/Gen2 */ + miphy28lp_pcie_config_gen(miphy_phy); + + /* Power control */ + /* Input bridge enable, manual input bridge control */ + writeb_relaxed(0x21, base + MIPHY_RX_POWER_CTRL_1); + + /* Macro out of reset */ + writeb_relaxed(0x00, base + MIPHY_CONF_RESET); + + /* Poll for HFC ready after reset release */ + /* Compensation measurement */ + err = miphy28lp_compensation(miphy_phy, &pcie_pll_ratio); + if (err) + return err; + + if (miphy_phy->ssc) + miphy_pcie_tune_ssc(miphy_phy); + + if (miphy_phy->tx_impedance) + miphy_tune_tx_impedance(miphy_phy); + + return 0; +} + + +static inline void miphy28lp_configure_usb3(struct miphy28lp_phy *miphy_phy) +{ + void __iomem *base = miphy_phy->base; + u8 val; + + /* Putting Macro in reset */ + miphy28lp_set_reset(miphy_phy); + + /* PLL calibration */ + miphy28lp_pll_calibration(miphy_phy, &usb3_pll_ratio); + + /* Writing The Speed Rate */ + writeb_relaxed(0x00, base + MIPHY_CONF); + + val = RX_SPDSEL_20DEC | TX_SPDSEL_20DEC; + writeb_relaxed(val, base + MIPHY_SPEED); + + /* RX Channel compensation and calibration */ + writeb_relaxed(0x1c, base + MIPHY_RX_LOCK_SETTINGS_OPT); + writeb_relaxed(0x51, base + MIPHY_RX_CAL_CTRL_1); + writeb_relaxed(0x70, base + MIPHY_RX_CAL_CTRL_2); + + val = OFFSET_COMPENSATION_EN | VGA_OFFSET_POLARITY | + CAL_OFFSET_THRESHOLD_64 | CAL_OFFSET_VGA_64; + writeb_relaxed(val, base + MIPHY_RX_CAL_OFFSET_CTRL); + writeb_relaxed(0x22, base + MIPHY_RX_CAL_VGA_STEP); + writeb_relaxed(0x0e, base + MIPHY_RX_CAL_OPT_LENGTH); + + val = EQ_DC_GAIN | VGA_GAIN; + writeb_relaxed(val, base + MIPHY_RX_BUFFER_CTRL); + writeb_relaxed(0x78, base + MIPHY_RX_EQU_GAIN_1); + writeb_relaxed(0x1b, base + MIPHY_SYNCHAR_CONTROL); + + /* TX compensation offset to re-center TX impedance */ + writeb_relaxed(0x02, base + MIPHY_COMP_POSTP); + + /* Enable GENSEL_SEL and SSC */ + /* TX_SEL=0 swing preemp forced by pipe registres */ + val = SSC_SEL | GENSEL_SEL; + writeb_relaxed(val, base + MIPHY_BOUNDARY_SEL); + + /* MIPHY Bias boost */ + writeb_relaxed(0x00, base + MIPHY_BIAS_BOOST_1); + writeb_relaxed(0xa7, base + MIPHY_BIAS_BOOST_2); + + /* SSC modulation */ + writeb_relaxed(SSC_EN_SW, base + MIPHY_BOUNDARY_2); + + /* MIPHY TX control */ + writeb_relaxed(0x00, base + MIPHY_CONF); + + /* Validate Step component */ + writeb_relaxed(0x5a, base + MIPHY_PLL_SBR_3); + writeb_relaxed(0xa0, base + MIPHY_PLL_SBR_4); + + /* Validate Period component */ + writeb_relaxed(0x3c, base + MIPHY_PLL_SBR_2); + writeb_relaxed(0xa1, base + MIPHY_PLL_SBR_4); + + /* Clear any previous request */ + writeb_relaxed(0x00, base + MIPHY_PLL_SBR_1); + + /* requests the PLL to take in account new parameters */ + writeb_relaxed(0x02, base + MIPHY_PLL_SBR_1); + + /* To be sure there is no other pending requests */ + writeb_relaxed(0x00, base + MIPHY_PLL_SBR_1); + + /* Rx PI controller settings */ + writeb_relaxed(0xca, base + MIPHY_RX_K_GAIN); + + /* MIPHY RX input bridge control */ + /* INPUT_BRIDGE_EN_SW=1, manual input bridge control[0]=1 */ + writeb_relaxed(0x21, base + MIPHY_RX_POWER_CTRL_1); + writeb_relaxed(0x29, base + MIPHY_RX_POWER_CTRL_1); + writeb_relaxed(0x1a, base + MIPHY_RX_POWER_CTRL_2); + + /* MIPHY Reset for usb3 */ + miphy28_usb3_miphy_reset(miphy_phy); +} + +static inline int miphy_is_ready(struct miphy28lp_phy *miphy_phy) +{ + unsigned long finish = jiffies + 5 * HZ; + u8 mask = HFC_PLL | HFC_RDY; + u8 val; + + /* + * For PCIe and USB3 check only that PLL and HFC are ready + * For SATA check also that phy is ready! + */ + if (miphy_phy->type == PHY_TYPE_SATA) + mask |= PHY_RDY; + + do { + val = readb_relaxed(miphy_phy->base + MIPHY_STATUS_1); + if ((val & mask) != mask) + cpu_relax(); + else + return 0; + } while (!time_after_eq(jiffies, finish)); + + return -EBUSY; +} + +static int miphy_osc_is_ready(struct miphy28lp_phy *miphy_phy) +{ + struct miphy28lp_dev *miphy_dev = miphy_phy->phydev; + unsigned long finish = jiffies + 5 * HZ; + u32 val; + + if (!miphy_phy->osc_rdy) + return 0; + + if (!miphy_phy->syscfg_reg[SYSCFG_STATUS]) + return -EINVAL; + + do { + regmap_read(miphy_dev->regmap, + miphy_phy->syscfg_reg[SYSCFG_STATUS], &val); + + if ((val & MIPHY_OSC_RDY) != MIPHY_OSC_RDY) + cpu_relax(); + else + return 0; + } while (!time_after_eq(jiffies, finish)); + + return -EBUSY; +} + +static int miphy28lp_get_resource_byname(struct device_node *child, + char *rname, struct resource *res) +{ + int index; + + index = of_property_match_string(child, "reg-names", rname); + if (index < 0) + return -ENODEV; + + return of_address_to_resource(child, index, res); +} + +static int miphy28lp_get_one_addr(struct device *dev, + struct device_node *child, char *rname, + void __iomem **base) +{ + struct resource res; + int ret; + + ret = miphy28lp_get_resource_byname(child, rname, &res); + if (!ret) { + *base = devm_ioremap(dev, res.start, resource_size(&res)); + if (!*base) { + dev_err(dev, "failed to ioremap %s address region\n" + , rname); + return -ENOENT; + } + } + + return 0; +} + +/* MiPHY reset and sysconf setup */ +static int miphy28lp_setup(struct miphy28lp_phy *miphy_phy, u32 miphy_val) +{ + int err; + struct miphy28lp_dev *miphy_dev = miphy_phy->phydev; + + if (!miphy_phy->syscfg_reg[SYSCFG_CTRL]) + return -EINVAL; + + err = reset_control_assert(miphy_phy->miphy_rst); + if (err) { + dev_err(miphy_dev->dev, "unable to bring out of miphy reset\n"); + return err; + } + + if (miphy_phy->osc_force_ext) + miphy_val |= MIPHY_OSC_FORCE_EXT; + + regmap_update_bits(miphy_dev->regmap, + miphy_phy->syscfg_reg[SYSCFG_CTRL], + MIPHY_CTRL_MASK, miphy_val); + + err = reset_control_deassert(miphy_phy->miphy_rst); + if (err) { + dev_err(miphy_dev->dev, "unable to bring out of miphy reset\n"); + return err; + } + + return miphy_osc_is_ready(miphy_phy); +} + +static int miphy28lp_init_sata(struct miphy28lp_phy *miphy_phy) +{ + struct miphy28lp_dev *miphy_dev = miphy_phy->phydev; + int err, sata_conf = SATA_CTRL_SELECT_SATA; + + if ((!miphy_phy->syscfg_reg[SYSCFG_SATA]) || + (!miphy_phy->syscfg_reg[SYSCFG_PCI]) || + (!miphy_phy->base)) + return -EINVAL; + + dev_info(miphy_dev->dev, "sata-up mode, addr 0x%p\n", miphy_phy->base); + + /* Configure the glue-logic */ + sata_conf |= ((miphy_phy->sata_gen - SATA_GEN1) << SATA_SPDMODE); + + regmap_update_bits(miphy_dev->regmap, + miphy_phy->syscfg_reg[SYSCFG_SATA], + SATA_CTRL_MASK, sata_conf); + + regmap_update_bits(miphy_dev->regmap, miphy_phy->syscfg_reg[SYSCFG_PCI], + PCIE_CTRL_MASK, SATA_CTRL_SELECT_PCIE); + + /* MiPHY path and clocking init */ + err = miphy28lp_setup(miphy_phy, MIPHY_CTRL_DEFAULT); + + if (err) { + dev_err(miphy_dev->dev, "SATA phy setup failed\n"); + return err; + } + + /* initialize miphy */ + miphy28lp_configure_sata(miphy_phy); + + return miphy_is_ready(miphy_phy); +} + +static int miphy28lp_init_pcie(struct miphy28lp_phy *miphy_phy) +{ + struct miphy28lp_dev *miphy_dev = miphy_phy->phydev; + int err; + + if ((!miphy_phy->syscfg_reg[SYSCFG_SATA]) || + (!miphy_phy->syscfg_reg[SYSCFG_PCI]) + || (!miphy_phy->base) || (!miphy_phy->pipebase)) + return -EINVAL; + + dev_info(miphy_dev->dev, "pcie-up mode, addr 0x%p\n", miphy_phy->base); + + /* Configure the glue-logic */ + regmap_update_bits(miphy_dev->regmap, + miphy_phy->syscfg_reg[SYSCFG_SATA], + SATA_CTRL_MASK, SATA_CTRL_SELECT_PCIE); + + regmap_update_bits(miphy_dev->regmap, miphy_phy->syscfg_reg[SYSCFG_PCI], + PCIE_CTRL_MASK, SYSCFG_PCIE_PCIE_VAL); + + /* MiPHY path and clocking init */ + err = miphy28lp_setup(miphy_phy, MIPHY_CTRL_DEFAULT); + + if (err) { + dev_err(miphy_dev->dev, "PCIe phy setup failed\n"); + return err; + } + + /* initialize miphy */ + err = miphy28lp_configure_pcie(miphy_phy); + if (err) + return err; + + /* PIPE Wrapper Configuration */ + writeb_relaxed(0x68, miphy_phy->pipebase + 0x104); /* Rise_0 */ + writeb_relaxed(0x61, miphy_phy->pipebase + 0x105); /* Rise_1 */ + writeb_relaxed(0x68, miphy_phy->pipebase + 0x108); /* Fall_0 */ + writeb_relaxed(0x61, miphy_phy->pipebase + 0x109); /* Fall-1 */ + writeb_relaxed(0x68, miphy_phy->pipebase + 0x10c); /* Threshold_0 */ + writeb_relaxed(0x60, miphy_phy->pipebase + 0x10d); /* Threshold_1 */ + + /* Wait for phy_ready */ + return miphy_is_ready(miphy_phy); +} + +static int miphy28lp_init_usb3(struct miphy28lp_phy *miphy_phy) +{ + struct miphy28lp_dev *miphy_dev = miphy_phy->phydev; + int err; + + if ((!miphy_phy->base) || (!miphy_phy->pipebase)) + return -EINVAL; + + dev_info(miphy_dev->dev, "usb3-up mode, addr 0x%p\n", miphy_phy->base); + + /* MiPHY path and clocking init */ + err = miphy28lp_setup(miphy_phy, MIPHY_CTRL_SYNC_D_EN); + if (err) { + dev_err(miphy_dev->dev, "USB3 phy setup failed\n"); + return err; + } + + /* initialize miphy */ + miphy28lp_configure_usb3(miphy_phy); + + /* PIPE Wrapper Configuration */ + writeb_relaxed(0x68, miphy_phy->pipebase + 0x23); + writeb_relaxed(0x61, miphy_phy->pipebase + 0x24); + writeb_relaxed(0x68, miphy_phy->pipebase + 0x26); + writeb_relaxed(0x61, miphy_phy->pipebase + 0x27); + writeb_relaxed(0x18, miphy_phy->pipebase + 0x29); + writeb_relaxed(0x61, miphy_phy->pipebase + 0x2a); + + /* pipe Wrapper usb3 TX swing de-emph margin PREEMPH[7:4], SWING[3:0] */ + writeb_relaxed(0X67, miphy_phy->pipebase + 0x68); + writeb_relaxed(0x0d, miphy_phy->pipebase + 0x69); + writeb_relaxed(0X67, miphy_phy->pipebase + 0x6a); + writeb_relaxed(0X0d, miphy_phy->pipebase + 0x6b); + writeb_relaxed(0X67, miphy_phy->pipebase + 0x6c); + writeb_relaxed(0X0d, miphy_phy->pipebase + 0x6d); + writeb_relaxed(0X67, miphy_phy->pipebase + 0x6e); + writeb_relaxed(0X0d, miphy_phy->pipebase + 0x6f); + + return miphy_is_ready(miphy_phy); +} + +static int miphy28lp_init(struct phy *phy) +{ + struct miphy28lp_phy *miphy_phy = phy_get_drvdata(phy); + struct miphy28lp_dev *miphy_dev = miphy_phy->phydev; + int ret; + + mutex_lock(&miphy_dev->miphy_mutex); + + switch (miphy_phy->type) { + + case PHY_TYPE_SATA: + ret = miphy28lp_init_sata(miphy_phy); + break; + case PHY_TYPE_PCIE: + ret = miphy28lp_init_pcie(miphy_phy); + break; + case PHY_TYPE_USB3: + ret = miphy28lp_init_usb3(miphy_phy); + break; + default: + ret = -EINVAL; + break; + } + + mutex_unlock(&miphy_dev->miphy_mutex); + + return ret; +} + +static int miphy28lp_get_addr(struct miphy28lp_phy *miphy_phy) +{ + struct miphy28lp_dev *miphy_dev = miphy_phy->phydev; + struct device_node *phynode = miphy_phy->phy->dev.of_node; + int err; + + if ((miphy_phy->type != PHY_TYPE_SATA) && + (miphy_phy->type != PHY_TYPE_PCIE) && + (miphy_phy->type != PHY_TYPE_USB3)) { + return -EINVAL; + } + + err = miphy28lp_get_one_addr(miphy_dev->dev, phynode, + PHY_TYPE_name[miphy_phy->type - PHY_TYPE_SATA], + &miphy_phy->base); + if (err) + return err; + + if ((miphy_phy->type == PHY_TYPE_PCIE) || + (miphy_phy->type == PHY_TYPE_USB3)) { + err = miphy28lp_get_one_addr(miphy_dev->dev, phynode, "pipew", + &miphy_phy->pipebase); + if (err) + return err; + } + + return 0; +} + +static struct phy *miphy28lp_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct miphy28lp_dev *miphy_dev = dev_get_drvdata(dev); + struct miphy28lp_phy *miphy_phy = NULL; + struct device_node *phynode = args->np; + int ret, index = 0; + + if (args->args_count != 1) { + dev_err(dev, "Invalid number of cells in 'phy' property\n"); + return ERR_PTR(-EINVAL); + } + + for (index = 0; index < miphy_dev->nphys; index++) + if (phynode == miphy_dev->phys[index]->phy->dev.of_node) { + miphy_phy = miphy_dev->phys[index]; + break; + } + + if (!miphy_phy) { + dev_err(dev, "Failed to find appropriate phy\n"); + return ERR_PTR(-EINVAL); + } + + miphy_phy->type = args->args[0]; + + ret = miphy28lp_get_addr(miphy_phy); + if (ret < 0) + return ERR_PTR(ret); + + return miphy_phy->phy; +} + +static const struct phy_ops miphy28lp_ops = { + .init = miphy28lp_init, + .owner = THIS_MODULE, +}; + +static int miphy28lp_probe_resets(struct device_node *node, + struct miphy28lp_phy *miphy_phy) +{ + struct miphy28lp_dev *miphy_dev = miphy_phy->phydev; + int err; + + miphy_phy->miphy_rst = + of_reset_control_get_shared(node, "miphy-sw-rst"); + + if (IS_ERR(miphy_phy->miphy_rst)) { + dev_err(miphy_dev->dev, + "miphy soft reset control not defined\n"); + return PTR_ERR(miphy_phy->miphy_rst); + } + + err = reset_control_deassert(miphy_phy->miphy_rst); + if (err) { + dev_err(miphy_dev->dev, "unable to bring out of miphy reset\n"); + return err; + } + + return 0; +} + +static int miphy28lp_of_probe(struct device_node *np, + struct miphy28lp_phy *miphy_phy) +{ + int i; + u32 ctrlreg; + + miphy_phy->osc_force_ext = + of_property_read_bool(np, "st,osc-force-ext"); + + miphy_phy->osc_rdy = of_property_read_bool(np, "st,osc-rdy"); + + miphy_phy->px_rx_pol_inv = + of_property_read_bool(np, "st,px_rx_pol_inv"); + + miphy_phy->ssc = of_property_read_bool(np, "st,ssc-on"); + + miphy_phy->tx_impedance = + of_property_read_bool(np, "st,tx-impedance-comp"); + + of_property_read_u32(np, "st,sata-gen", &miphy_phy->sata_gen); + if (!miphy_phy->sata_gen) + miphy_phy->sata_gen = SATA_GEN1; + + for (i = 0; i < SYSCFG_REG_MAX; i++) { + if (!of_property_read_u32_index(np, "st,syscfg", i, &ctrlreg)) + miphy_phy->syscfg_reg[i] = ctrlreg; + } + + return 0; +} + +static int miphy28lp_probe(struct platform_device *pdev) +{ + struct device_node *child, *np = pdev->dev.of_node; + struct miphy28lp_dev *miphy_dev; + struct phy_provider *provider; + struct phy *phy; + int ret, port = 0; + + miphy_dev = devm_kzalloc(&pdev->dev, sizeof(*miphy_dev), GFP_KERNEL); + if (!miphy_dev) + return -ENOMEM; + + miphy_dev->nphys = of_get_child_count(np); + miphy_dev->phys = devm_kcalloc(&pdev->dev, miphy_dev->nphys, + sizeof(*miphy_dev->phys), GFP_KERNEL); + if (!miphy_dev->phys) + return -ENOMEM; + + miphy_dev->regmap = syscon_regmap_lookup_by_phandle(np, "st,syscfg"); + if (IS_ERR(miphy_dev->regmap)) { + dev_err(miphy_dev->dev, "No syscfg phandle specified\n"); + return PTR_ERR(miphy_dev->regmap); + } + + miphy_dev->dev = &pdev->dev; + + dev_set_drvdata(&pdev->dev, miphy_dev); + + mutex_init(&miphy_dev->miphy_mutex); + + for_each_child_of_node(np, child) { + struct miphy28lp_phy *miphy_phy; + + miphy_phy = devm_kzalloc(&pdev->dev, sizeof(*miphy_phy), + GFP_KERNEL); + if (!miphy_phy) { + ret = -ENOMEM; + goto put_child; + } + + miphy_dev->phys[port] = miphy_phy; + + phy = devm_phy_create(&pdev->dev, child, &miphy28lp_ops); + if (IS_ERR(phy)) { + dev_err(&pdev->dev, "failed to create PHY\n"); + ret = PTR_ERR(phy); + goto put_child; + } + + miphy_dev->phys[port]->phy = phy; + miphy_dev->phys[port]->phydev = miphy_dev; + + ret = miphy28lp_of_probe(child, miphy_phy); + if (ret) + goto put_child; + + ret = miphy28lp_probe_resets(child, miphy_dev->phys[port]); + if (ret) + goto put_child; + + phy_set_drvdata(phy, miphy_dev->phys[port]); + port++; + + } + + provider = devm_of_phy_provider_register(&pdev->dev, miphy28lp_xlate); + return PTR_ERR_OR_ZERO(provider); +put_child: + of_node_put(child); + return ret; +} + +static const struct of_device_id miphy28lp_of_match[] = { + {.compatible = "st,miphy28lp-phy", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, miphy28lp_of_match); + +static struct platform_driver miphy28lp_driver = { + .probe = miphy28lp_probe, + .driver = { + .name = "miphy28lp-phy", + .of_match_table = miphy28lp_of_match, + } +}; + +module_platform_driver(miphy28lp_driver); + +MODULE_AUTHOR("Alexandre Torgue "); +MODULE_DESCRIPTION("STMicroelectronics miphy28lp driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/st/phy-spear1310-miphy.c b/drivers/phy/st/phy-spear1310-miphy.c new file mode 100644 index 000000000000..ed67e98e54ca --- /dev/null +++ b/drivers/phy/st/phy-spear1310-miphy.c @@ -0,0 +1,261 @@ +/* + * ST SPEAr1310-miphy driver + * + * Copyright (C) 2014 ST Microelectronics + * Pratyush Anand + * Mohit Kumar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* SPEAr1310 Registers */ +#define SPEAR1310_PCIE_SATA_CFG 0x3A4 + #define SPEAR1310_PCIE_SATA2_SEL_PCIE (0 << 31) + #define SPEAR1310_PCIE_SATA1_SEL_PCIE (0 << 30) + #define SPEAR1310_PCIE_SATA0_SEL_PCIE (0 << 29) + #define SPEAR1310_PCIE_SATA2_SEL_SATA BIT(31) + #define SPEAR1310_PCIE_SATA1_SEL_SATA BIT(30) + #define SPEAR1310_PCIE_SATA0_SEL_SATA BIT(29) + #define SPEAR1310_SATA2_CFG_TX_CLK_EN BIT(27) + #define SPEAR1310_SATA2_CFG_RX_CLK_EN BIT(26) + #define SPEAR1310_SATA2_CFG_POWERUP_RESET BIT(25) + #define SPEAR1310_SATA2_CFG_PM_CLK_EN BIT(24) + #define SPEAR1310_SATA1_CFG_TX_CLK_EN BIT(23) + #define SPEAR1310_SATA1_CFG_RX_CLK_EN BIT(22) + #define SPEAR1310_SATA1_CFG_POWERUP_RESET BIT(21) + #define SPEAR1310_SATA1_CFG_PM_CLK_EN BIT(20) + #define SPEAR1310_SATA0_CFG_TX_CLK_EN BIT(19) + #define SPEAR1310_SATA0_CFG_RX_CLK_EN BIT(18) + #define SPEAR1310_SATA0_CFG_POWERUP_RESET BIT(17) + #define SPEAR1310_SATA0_CFG_PM_CLK_EN BIT(16) + #define SPEAR1310_PCIE2_CFG_DEVICE_PRESENT BIT(11) + #define SPEAR1310_PCIE2_CFG_POWERUP_RESET BIT(10) + #define SPEAR1310_PCIE2_CFG_CORE_CLK_EN BIT(9) + #define SPEAR1310_PCIE2_CFG_AUX_CLK_EN BIT(8) + #define SPEAR1310_PCIE1_CFG_DEVICE_PRESENT BIT(7) + #define SPEAR1310_PCIE1_CFG_POWERUP_RESET BIT(6) + #define SPEAR1310_PCIE1_CFG_CORE_CLK_EN BIT(5) + #define SPEAR1310_PCIE1_CFG_AUX_CLK_EN BIT(4) + #define SPEAR1310_PCIE0_CFG_DEVICE_PRESENT BIT(3) + #define SPEAR1310_PCIE0_CFG_POWERUP_RESET BIT(2) + #define SPEAR1310_PCIE0_CFG_CORE_CLK_EN BIT(1) + #define SPEAR1310_PCIE0_CFG_AUX_CLK_EN BIT(0) + + #define SPEAR1310_PCIE_CFG_MASK(x) ((0xF << (x * 4)) | BIT((x + 29))) + #define SPEAR1310_SATA_CFG_MASK(x) ((0xF << (x * 4 + 16)) | \ + BIT((x + 29))) + #define SPEAR1310_PCIE_CFG_VAL(x) \ + (SPEAR1310_PCIE_SATA##x##_SEL_PCIE | \ + SPEAR1310_PCIE##x##_CFG_AUX_CLK_EN | \ + SPEAR1310_PCIE##x##_CFG_CORE_CLK_EN | \ + SPEAR1310_PCIE##x##_CFG_POWERUP_RESET | \ + SPEAR1310_PCIE##x##_CFG_DEVICE_PRESENT) + #define SPEAR1310_SATA_CFG_VAL(x) \ + (SPEAR1310_PCIE_SATA##x##_SEL_SATA | \ + SPEAR1310_SATA##x##_CFG_PM_CLK_EN | \ + SPEAR1310_SATA##x##_CFG_POWERUP_RESET | \ + SPEAR1310_SATA##x##_CFG_RX_CLK_EN | \ + SPEAR1310_SATA##x##_CFG_TX_CLK_EN) + +#define SPEAR1310_PCIE_MIPHY_CFG_1 0x3A8 + #define SPEAR1310_MIPHY_DUAL_OSC_BYPASS_EXT BIT(31) + #define SPEAR1310_MIPHY_DUAL_CLK_REF_DIV2 BIT(28) + #define SPEAR1310_MIPHY_DUAL_PLL_RATIO_TOP(x) (x << 16) + #define SPEAR1310_MIPHY_SINGLE_OSC_BYPASS_EXT BIT(15) + #define SPEAR1310_MIPHY_SINGLE_CLK_REF_DIV2 BIT(12) + #define SPEAR1310_MIPHY_SINGLE_PLL_RATIO_TOP(x) (x << 0) + #define SPEAR1310_PCIE_SATA_MIPHY_CFG_SATA_MASK (0xFFFF) + #define SPEAR1310_PCIE_SATA_MIPHY_CFG_PCIE_MASK (0xFFFF << 16) + #define SPEAR1310_PCIE_SATA_MIPHY_CFG_SATA \ + (SPEAR1310_MIPHY_DUAL_OSC_BYPASS_EXT | \ + SPEAR1310_MIPHY_DUAL_CLK_REF_DIV2 | \ + SPEAR1310_MIPHY_DUAL_PLL_RATIO_TOP(60) | \ + SPEAR1310_MIPHY_SINGLE_OSC_BYPASS_EXT | \ + SPEAR1310_MIPHY_SINGLE_CLK_REF_DIV2 | \ + SPEAR1310_MIPHY_SINGLE_PLL_RATIO_TOP(60)) + #define SPEAR1310_PCIE_SATA_MIPHY_CFG_SATA_25M_CRYSTAL_CLK \ + (SPEAR1310_MIPHY_SINGLE_PLL_RATIO_TOP(120)) + #define SPEAR1310_PCIE_SATA_MIPHY_CFG_PCIE \ + (SPEAR1310_MIPHY_DUAL_OSC_BYPASS_EXT | \ + SPEAR1310_MIPHY_DUAL_PLL_RATIO_TOP(25) | \ + SPEAR1310_MIPHY_SINGLE_OSC_BYPASS_EXT | \ + SPEAR1310_MIPHY_SINGLE_PLL_RATIO_TOP(25)) + +#define SPEAR1310_PCIE_MIPHY_CFG_2 0x3AC + +enum spear1310_miphy_mode { + SATA, + PCIE, +}; + +struct spear1310_miphy_priv { + /* instance id of this phy */ + u32 id; + /* phy mode: 0 for SATA 1 for PCIe */ + enum spear1310_miphy_mode mode; + /* regmap for any soc specific misc registers */ + struct regmap *misc; + /* phy struct pointer */ + struct phy *phy; +}; + +static int spear1310_miphy_pcie_init(struct spear1310_miphy_priv *priv) +{ + u32 val; + + regmap_update_bits(priv->misc, SPEAR1310_PCIE_MIPHY_CFG_1, + SPEAR1310_PCIE_SATA_MIPHY_CFG_PCIE_MASK, + SPEAR1310_PCIE_SATA_MIPHY_CFG_PCIE); + + switch (priv->id) { + case 0: + val = SPEAR1310_PCIE_CFG_VAL(0); + break; + case 1: + val = SPEAR1310_PCIE_CFG_VAL(1); + break; + case 2: + val = SPEAR1310_PCIE_CFG_VAL(2); + break; + default: + return -EINVAL; + } + + regmap_update_bits(priv->misc, SPEAR1310_PCIE_SATA_CFG, + SPEAR1310_PCIE_CFG_MASK(priv->id), val); + + return 0; +} + +static int spear1310_miphy_pcie_exit(struct spear1310_miphy_priv *priv) +{ + regmap_update_bits(priv->misc, SPEAR1310_PCIE_SATA_CFG, + SPEAR1310_PCIE_CFG_MASK(priv->id), 0); + + regmap_update_bits(priv->misc, SPEAR1310_PCIE_MIPHY_CFG_1, + SPEAR1310_PCIE_SATA_MIPHY_CFG_PCIE_MASK, 0); + + return 0; +} + +static int spear1310_miphy_init(struct phy *phy) +{ + struct spear1310_miphy_priv *priv = phy_get_drvdata(phy); + int ret = 0; + + if (priv->mode == PCIE) + ret = spear1310_miphy_pcie_init(priv); + + return ret; +} + +static int spear1310_miphy_exit(struct phy *phy) +{ + struct spear1310_miphy_priv *priv = phy_get_drvdata(phy); + int ret = 0; + + if (priv->mode == PCIE) + ret = spear1310_miphy_pcie_exit(priv); + + return ret; +} + +static const struct of_device_id spear1310_miphy_of_match[] = { + { .compatible = "st,spear1310-miphy" }, + { }, +}; +MODULE_DEVICE_TABLE(of, spear1310_miphy_of_match); + +static const struct phy_ops spear1310_miphy_ops = { + .init = spear1310_miphy_init, + .exit = spear1310_miphy_exit, + .owner = THIS_MODULE, +}; + +static struct phy *spear1310_miphy_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct spear1310_miphy_priv *priv = dev_get_drvdata(dev); + + if (args->args_count < 1) { + dev_err(dev, "DT did not pass correct no of args\n"); + return ERR_PTR(-ENODEV); + } + + priv->mode = args->args[0]; + + if (priv->mode != SATA && priv->mode != PCIE) { + dev_err(dev, "DT did not pass correct phy mode\n"); + return ERR_PTR(-ENODEV); + } + + return priv->phy; +} + +static int spear1310_miphy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct spear1310_miphy_priv *priv; + struct phy_provider *phy_provider; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->misc = + syscon_regmap_lookup_by_phandle(dev->of_node, "misc"); + if (IS_ERR(priv->misc)) { + dev_err(dev, "failed to find misc regmap\n"); + return PTR_ERR(priv->misc); + } + + if (of_property_read_u32(dev->of_node, "phy-id", &priv->id)) { + dev_err(dev, "failed to find phy id\n"); + return -EINVAL; + } + + priv->phy = devm_phy_create(dev, NULL, &spear1310_miphy_ops); + if (IS_ERR(priv->phy)) { + dev_err(dev, "failed to create SATA PCIe PHY\n"); + return PTR_ERR(priv->phy); + } + + dev_set_drvdata(dev, priv); + phy_set_drvdata(priv->phy, priv); + + phy_provider = + devm_of_phy_provider_register(dev, spear1310_miphy_xlate); + if (IS_ERR(phy_provider)) { + dev_err(dev, "failed to register phy provider\n"); + return PTR_ERR(phy_provider); + } + + return 0; +} + +static struct platform_driver spear1310_miphy_driver = { + .probe = spear1310_miphy_probe, + .driver = { + .name = "spear1310-miphy", + .of_match_table = of_match_ptr(spear1310_miphy_of_match), + }, +}; + +module_platform_driver(spear1310_miphy_driver); + +MODULE_DESCRIPTION("ST SPEAR1310-MIPHY driver"); +MODULE_AUTHOR("Pratyush Anand "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/st/phy-spear1340-miphy.c b/drivers/phy/st/phy-spear1340-miphy.c new file mode 100644 index 000000000000..97280c0cf612 --- /dev/null +++ b/drivers/phy/st/phy-spear1340-miphy.c @@ -0,0 +1,294 @@ +/* + * ST spear1340-miphy driver + * + * Copyright (C) 2014 ST Microelectronics + * Pratyush Anand + * Mohit Kumar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* SPEAr1340 Registers */ +/* Power Management Registers */ +#define SPEAR1340_PCM_CFG 0x100 + #define SPEAR1340_PCM_CFG_SATA_POWER_EN BIT(11) +#define SPEAR1340_PCM_WKUP_CFG 0x104 +#define SPEAR1340_SWITCH_CTR 0x108 + +#define SPEAR1340_PERIP1_SW_RST 0x318 + #define SPEAR1340_PERIP1_SW_RSATA BIT(12) +#define SPEAR1340_PERIP2_SW_RST 0x31C +#define SPEAR1340_PERIP3_SW_RST 0x320 + +/* PCIE - SATA configuration registers */ +#define SPEAR1340_PCIE_SATA_CFG 0x424 + /* PCIE CFG MASks */ + #define SPEAR1340_PCIE_CFG_DEVICE_PRESENT BIT(11) + #define SPEAR1340_PCIE_CFG_POWERUP_RESET BIT(10) + #define SPEAR1340_PCIE_CFG_CORE_CLK_EN BIT(9) + #define SPEAR1340_PCIE_CFG_AUX_CLK_EN BIT(8) + #define SPEAR1340_SATA_CFG_TX_CLK_EN BIT(4) + #define SPEAR1340_SATA_CFG_RX_CLK_EN BIT(3) + #define SPEAR1340_SATA_CFG_POWERUP_RESET BIT(2) + #define SPEAR1340_SATA_CFG_PM_CLK_EN BIT(1) + #define SPEAR1340_PCIE_SATA_SEL_PCIE (0) + #define SPEAR1340_PCIE_SATA_SEL_SATA (1) + #define SPEAR1340_PCIE_SATA_CFG_MASK 0xF1F + #define SPEAR1340_PCIE_CFG_VAL (SPEAR1340_PCIE_SATA_SEL_PCIE | \ + SPEAR1340_PCIE_CFG_AUX_CLK_EN | \ + SPEAR1340_PCIE_CFG_CORE_CLK_EN | \ + SPEAR1340_PCIE_CFG_POWERUP_RESET | \ + SPEAR1340_PCIE_CFG_DEVICE_PRESENT) + #define SPEAR1340_SATA_CFG_VAL (SPEAR1340_PCIE_SATA_SEL_SATA | \ + SPEAR1340_SATA_CFG_PM_CLK_EN | \ + SPEAR1340_SATA_CFG_POWERUP_RESET | \ + SPEAR1340_SATA_CFG_RX_CLK_EN | \ + SPEAR1340_SATA_CFG_TX_CLK_EN) + +#define SPEAR1340_PCIE_MIPHY_CFG 0x428 + #define SPEAR1340_MIPHY_OSC_BYPASS_EXT BIT(31) + #define SPEAR1340_MIPHY_CLK_REF_DIV2 BIT(27) + #define SPEAR1340_MIPHY_CLK_REF_DIV4 (2 << 27) + #define SPEAR1340_MIPHY_CLK_REF_DIV8 (3 << 27) + #define SPEAR1340_MIPHY_PLL_RATIO_TOP(x) (x << 0) + #define SPEAR1340_PCIE_MIPHY_CFG_MASK 0xF80000FF + #define SPEAR1340_PCIE_SATA_MIPHY_CFG_SATA \ + (SPEAR1340_MIPHY_OSC_BYPASS_EXT | \ + SPEAR1340_MIPHY_CLK_REF_DIV2 | \ + SPEAR1340_MIPHY_PLL_RATIO_TOP(60)) + #define SPEAR1340_PCIE_SATA_MIPHY_CFG_SATA_25M_CRYSTAL_CLK \ + (SPEAR1340_MIPHY_PLL_RATIO_TOP(120)) + #define SPEAR1340_PCIE_SATA_MIPHY_CFG_PCIE \ + (SPEAR1340_MIPHY_OSC_BYPASS_EXT | \ + SPEAR1340_MIPHY_PLL_RATIO_TOP(25)) + +enum spear1340_miphy_mode { + SATA, + PCIE, +}; + +struct spear1340_miphy_priv { + /* phy mode: 0 for SATA 1 for PCIe */ + enum spear1340_miphy_mode mode; + /* regmap for any soc specific misc registers */ + struct regmap *misc; + /* phy struct pointer */ + struct phy *phy; +}; + +static int spear1340_miphy_sata_init(struct spear1340_miphy_priv *priv) +{ + regmap_update_bits(priv->misc, SPEAR1340_PCIE_SATA_CFG, + SPEAR1340_PCIE_SATA_CFG_MASK, + SPEAR1340_SATA_CFG_VAL); + regmap_update_bits(priv->misc, SPEAR1340_PCIE_MIPHY_CFG, + SPEAR1340_PCIE_MIPHY_CFG_MASK, + SPEAR1340_PCIE_SATA_MIPHY_CFG_SATA_25M_CRYSTAL_CLK); + /* Switch on sata power domain */ + regmap_update_bits(priv->misc, SPEAR1340_PCM_CFG, + SPEAR1340_PCM_CFG_SATA_POWER_EN, + SPEAR1340_PCM_CFG_SATA_POWER_EN); + /* Wait for SATA power domain on */ + msleep(20); + + /* Disable PCIE SATA Controller reset */ + regmap_update_bits(priv->misc, SPEAR1340_PERIP1_SW_RST, + SPEAR1340_PERIP1_SW_RSATA, 0); + /* Wait for SATA reset de-assert completion */ + msleep(20); + + return 0; +} + +static int spear1340_miphy_sata_exit(struct spear1340_miphy_priv *priv) +{ + regmap_update_bits(priv->misc, SPEAR1340_PCIE_SATA_CFG, + SPEAR1340_PCIE_SATA_CFG_MASK, 0); + regmap_update_bits(priv->misc, SPEAR1340_PCIE_MIPHY_CFG, + SPEAR1340_PCIE_MIPHY_CFG_MASK, 0); + + /* Enable PCIE SATA Controller reset */ + regmap_update_bits(priv->misc, SPEAR1340_PERIP1_SW_RST, + SPEAR1340_PERIP1_SW_RSATA, + SPEAR1340_PERIP1_SW_RSATA); + /* Wait for SATA power domain off */ + msleep(20); + /* Switch off sata power domain */ + regmap_update_bits(priv->misc, SPEAR1340_PCM_CFG, + SPEAR1340_PCM_CFG_SATA_POWER_EN, 0); + /* Wait for SATA reset assert completion */ + msleep(20); + + return 0; +} + +static int spear1340_miphy_pcie_init(struct spear1340_miphy_priv *priv) +{ + regmap_update_bits(priv->misc, SPEAR1340_PCIE_MIPHY_CFG, + SPEAR1340_PCIE_MIPHY_CFG_MASK, + SPEAR1340_PCIE_SATA_MIPHY_CFG_PCIE); + regmap_update_bits(priv->misc, SPEAR1340_PCIE_SATA_CFG, + SPEAR1340_PCIE_SATA_CFG_MASK, + SPEAR1340_PCIE_CFG_VAL); + + return 0; +} + +static int spear1340_miphy_pcie_exit(struct spear1340_miphy_priv *priv) +{ + regmap_update_bits(priv->misc, SPEAR1340_PCIE_MIPHY_CFG, + SPEAR1340_PCIE_MIPHY_CFG_MASK, 0); + regmap_update_bits(priv->misc, SPEAR1340_PCIE_SATA_CFG, + SPEAR1340_PCIE_SATA_CFG_MASK, 0); + + return 0; +} + +static int spear1340_miphy_init(struct phy *phy) +{ + struct spear1340_miphy_priv *priv = phy_get_drvdata(phy); + int ret = 0; + + if (priv->mode == SATA) + ret = spear1340_miphy_sata_init(priv); + else if (priv->mode == PCIE) + ret = spear1340_miphy_pcie_init(priv); + + return ret; +} + +static int spear1340_miphy_exit(struct phy *phy) +{ + struct spear1340_miphy_priv *priv = phy_get_drvdata(phy); + int ret = 0; + + if (priv->mode == SATA) + ret = spear1340_miphy_sata_exit(priv); + else if (priv->mode == PCIE) + ret = spear1340_miphy_pcie_exit(priv); + + return ret; +} + +static const struct of_device_id spear1340_miphy_of_match[] = { + { .compatible = "st,spear1340-miphy" }, + { }, +}; +MODULE_DEVICE_TABLE(of, spear1340_miphy_of_match); + +static const struct phy_ops spear1340_miphy_ops = { + .init = spear1340_miphy_init, + .exit = spear1340_miphy_exit, + .owner = THIS_MODULE, +}; + +#ifdef CONFIG_PM_SLEEP +static int spear1340_miphy_suspend(struct device *dev) +{ + struct spear1340_miphy_priv *priv = dev_get_drvdata(dev); + int ret = 0; + + if (priv->mode == SATA) + ret = spear1340_miphy_sata_exit(priv); + + return ret; +} + +static int spear1340_miphy_resume(struct device *dev) +{ + struct spear1340_miphy_priv *priv = dev_get_drvdata(dev); + int ret = 0; + + if (priv->mode == SATA) + ret = spear1340_miphy_sata_init(priv); + + return ret; +} +#endif + +static SIMPLE_DEV_PM_OPS(spear1340_miphy_pm_ops, spear1340_miphy_suspend, + spear1340_miphy_resume); + +static struct phy *spear1340_miphy_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct spear1340_miphy_priv *priv = dev_get_drvdata(dev); + + if (args->args_count < 1) { + dev_err(dev, "DT did not pass correct no of args\n"); + return ERR_PTR(-ENODEV); + } + + priv->mode = args->args[0]; + + if (priv->mode != SATA && priv->mode != PCIE) { + dev_err(dev, "DT did not pass correct phy mode\n"); + return ERR_PTR(-ENODEV); + } + + return priv->phy; +} + +static int spear1340_miphy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct spear1340_miphy_priv *priv; + struct phy_provider *phy_provider; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->misc = + syscon_regmap_lookup_by_phandle(dev->of_node, "misc"); + if (IS_ERR(priv->misc)) { + dev_err(dev, "failed to find misc regmap\n"); + return PTR_ERR(priv->misc); + } + + priv->phy = devm_phy_create(dev, NULL, &spear1340_miphy_ops); + if (IS_ERR(priv->phy)) { + dev_err(dev, "failed to create SATA PCIe PHY\n"); + return PTR_ERR(priv->phy); + } + + dev_set_drvdata(dev, priv); + phy_set_drvdata(priv->phy, priv); + + phy_provider = + devm_of_phy_provider_register(dev, spear1340_miphy_xlate); + if (IS_ERR(phy_provider)) { + dev_err(dev, "failed to register phy provider\n"); + return PTR_ERR(phy_provider); + } + + return 0; +} + +static struct platform_driver spear1340_miphy_driver = { + .probe = spear1340_miphy_probe, + .driver = { + .name = "spear1340-miphy", + .pm = &spear1340_miphy_pm_ops, + .of_match_table = of_match_ptr(spear1340_miphy_of_match), + }, +}; + +module_platform_driver(spear1340_miphy_driver); + +MODULE_DESCRIPTION("ST SPEAR1340-MIPHY driver"); +MODULE_AUTHOR("Pratyush Anand "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/st/phy-stih407-usb.c b/drivers/phy/st/phy-stih407-usb.c new file mode 100644 index 000000000000..b1f44ab669fb --- /dev/null +++ b/drivers/phy/st/phy-stih407-usb.c @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2014 STMicroelectronics + * + * STMicroelectronics Generic PHY driver for STiH407 USB2. + * + * Author: Giuseppe Cavallaro + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PHYPARAM_REG 1 +#define PHYCTRL_REG 2 + +/* Default PHY_SEL and REFCLKSEL configuration */ +#define STIH407_USB_PICOPHY_CTRL_PORT_CONF 0x6 +#define STIH407_USB_PICOPHY_CTRL_PORT_MASK 0x1f + +/* ports parameters overriding */ +#define STIH407_USB_PICOPHY_PARAM_DEF 0x39a4dc +#define STIH407_USB_PICOPHY_PARAM_MASK 0xffffffff + +struct stih407_usb2_picophy { + struct phy *phy; + struct regmap *regmap; + struct device *dev; + struct reset_control *rstc; + struct reset_control *rstport; + int ctrl; + int param; +}; + +static int stih407_usb2_pico_ctrl(struct stih407_usb2_picophy *phy_dev) +{ + reset_control_deassert(phy_dev->rstc); + + return regmap_update_bits(phy_dev->regmap, phy_dev->ctrl, + STIH407_USB_PICOPHY_CTRL_PORT_MASK, + STIH407_USB_PICOPHY_CTRL_PORT_CONF); +} + +static int stih407_usb2_init_port(struct phy *phy) +{ + int ret; + struct stih407_usb2_picophy *phy_dev = phy_get_drvdata(phy); + + stih407_usb2_pico_ctrl(phy_dev); + + ret = regmap_update_bits(phy_dev->regmap, + phy_dev->param, + STIH407_USB_PICOPHY_PARAM_MASK, + STIH407_USB_PICOPHY_PARAM_DEF); + if (ret) + return ret; + + return reset_control_deassert(phy_dev->rstport); +} + +static int stih407_usb2_exit_port(struct phy *phy) +{ + struct stih407_usb2_picophy *phy_dev = phy_get_drvdata(phy); + + /* + * Only port reset is asserted, phy global reset is kept untouched + * as other ports may still be active. When all ports are in reset + * state, assumption is made that power will be cut off on the phy, in + * case of suspend for instance. Theoretically, asserting individual + * reset (like here) or global reset should be equivalent. + */ + return reset_control_assert(phy_dev->rstport); +} + +static const struct phy_ops stih407_usb2_picophy_data = { + .init = stih407_usb2_init_port, + .exit = stih407_usb2_exit_port, + .owner = THIS_MODULE, +}; + +static int stih407_usb2_picophy_probe(struct platform_device *pdev) +{ + struct stih407_usb2_picophy *phy_dev; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct phy_provider *phy_provider; + struct phy *phy; + int ret; + + phy_dev = devm_kzalloc(dev, sizeof(*phy_dev), GFP_KERNEL); + if (!phy_dev) + return -ENOMEM; + + phy_dev->dev = dev; + dev_set_drvdata(dev, phy_dev); + + phy_dev->rstc = devm_reset_control_get_shared(dev, "global"); + if (IS_ERR(phy_dev->rstc)) { + dev_err(dev, "failed to ctrl picoPHY reset\n"); + return PTR_ERR(phy_dev->rstc); + } + + phy_dev->rstport = devm_reset_control_get_exclusive(dev, "port"); + if (IS_ERR(phy_dev->rstport)) { + dev_err(dev, "failed to ctrl picoPHY reset\n"); + return PTR_ERR(phy_dev->rstport); + } + + /* Reset port by default: only deassert it in phy init */ + reset_control_assert(phy_dev->rstport); + + phy_dev->regmap = syscon_regmap_lookup_by_phandle(np, "st,syscfg"); + if (IS_ERR(phy_dev->regmap)) { + dev_err(dev, "No syscfg phandle specified\n"); + return PTR_ERR(phy_dev->regmap); + } + + ret = of_property_read_u32_index(np, "st,syscfg", PHYPARAM_REG, + &phy_dev->param); + if (ret) { + dev_err(dev, "can't get phyparam offset (%d)\n", ret); + return ret; + } + + ret = of_property_read_u32_index(np, "st,syscfg", PHYCTRL_REG, + &phy_dev->ctrl); + if (ret) { + dev_err(dev, "can't get phyctrl offset (%d)\n", ret); + return ret; + } + + phy = devm_phy_create(dev, NULL, &stih407_usb2_picophy_data); + if (IS_ERR(phy)) { + dev_err(dev, "failed to create Display Port PHY\n"); + return PTR_ERR(phy); + } + + phy_dev->phy = phy; + phy_set_drvdata(phy, phy_dev); + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(phy_provider)) + return PTR_ERR(phy_provider); + + dev_info(dev, "STiH407 USB Generic picoPHY driver probed!"); + + return 0; +} + +static const struct of_device_id stih407_usb2_picophy_of_match[] = { + { .compatible = "st,stih407-usb2-phy" }, + { /*sentinel */ }, +}; + +MODULE_DEVICE_TABLE(of, stih407_usb2_picophy_of_match); + +static struct platform_driver stih407_usb2_picophy_driver = { + .probe = stih407_usb2_picophy_probe, + .driver = { + .name = "stih407-usb-genphy", + .of_match_table = stih407_usb2_picophy_of_match, + } +}; + +module_platform_driver(stih407_usb2_picophy_driver); + +MODULE_AUTHOR("Giuseppe Cavallaro "); +MODULE_DESCRIPTION("STMicroelectronics Generic picoPHY driver for STiH407"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/ti/Kconfig b/drivers/phy/ti/Kconfig new file mode 100644 index 000000000000..20503562666c --- /dev/null +++ b/drivers/phy/ti/Kconfig @@ -0,0 +1,78 @@ +# +# Phy drivers for TI platforms +# +config PHY_DA8XX_USB + tristate "TI DA8xx USB PHY Driver" + depends on ARCH_DAVINCI_DA8XX + select GENERIC_PHY + select MFD_SYSCON + help + Enable this to support the USB PHY on DA8xx SoCs. + + This driver controls both the USB 1.1 PHY and the USB 2.0 PHY. + +config PHY_DM816X_USB + tristate "TI dm816x USB PHY driver" + depends on ARCH_OMAP2PLUS + depends on USB_SUPPORT + select GENERIC_PHY + select USB_PHY + help + Enable this for dm816x USB to work. + +config OMAP_CONTROL_PHY + tristate "OMAP CONTROL PHY Driver" + depends on ARCH_OMAP2PLUS || COMPILE_TEST + help + Enable this to add support for the PHY part present in the control + module. This driver has API to power on the USB2 PHY and to write to + the mailbox. The mailbox is present only in omap4 and the register to + power on the USB2 PHY is present in OMAP4 and OMAP5. OMAP5 has an + additional register to power on USB3 PHY/SATA PHY/PCIE PHY + (PIPE3 PHY). + +config OMAP_USB2 + tristate "OMAP USB2 PHY Driver" + depends on ARCH_OMAP2PLUS + depends on USB_SUPPORT + select GENERIC_PHY + select USB_PHY + select OMAP_CONTROL_PHY + depends on OMAP_OCP2SCP + help + Enable this to support the transceiver that is part of SOC. This + driver takes care of all the PHY functionality apart from comparator. + The USB OTG controller communicates with the comparator using this + driver. + +config TI_PIPE3 + tristate "TI PIPE3 PHY Driver" + depends on ARCH_OMAP2PLUS || COMPILE_TEST + select GENERIC_PHY + select OMAP_CONTROL_PHY + depends on OMAP_OCP2SCP + help + Enable this to support the PIPE3 PHY that is part of TI SOCs. This + driver takes care of all the PHY functionality apart from comparator. + This driver interacts with the "OMAP Control PHY Driver" to power + on/off the PHY. + +config PHY_TUSB1210 + tristate "TI TUSB1210 ULPI PHY module" + depends on USB_ULPI_BUS + select GENERIC_PHY + help + Support for TI TUSB1210 USB ULPI PHY. + +config TWL4030_USB + tristate "TWL4030 USB Transceiver Driver" + depends on TWL4030_CORE && REGULATOR_TWL4030 && USB_MUSB_OMAP2PLUS + depends on USB_SUPPORT + depends on USB_GADGET || !USB_GADGET # if USB_GADGET=m, this can't 'y' + select GENERIC_PHY + select USB_PHY + help + Enable this to support the USB OTG transceiver on TWL4030 + family chips (including the TWL5030 and TPS659x0 devices). + This transceiver supports high and full speed devices plus, + in host mode, low speed. diff --git a/drivers/phy/ti/Makefile b/drivers/phy/ti/Makefile new file mode 100644 index 000000000000..0cc3a1a557a3 --- /dev/null +++ b/drivers/phy/ti/Makefile @@ -0,0 +1,7 @@ +obj-$(CONFIG_PHY_DA8XX_USB) += phy-da8xx-usb.o +obj-$(CONFIG_PHY_DM816X_USB) += phy-dm816x-usb.o +obj-$(CONFIG_OMAP_CONTROL_PHY) += phy-omap-control.o +obj-$(CONFIG_OMAP_USB2) += phy-omap-usb2.o +obj-$(CONFIG_TI_PIPE3) += phy-ti-pipe3.o +obj-$(CONFIG_PHY_TUSB1210) += phy-tusb1210.o +obj-$(CONFIG_TWL4030_USB) += phy-twl4030-usb.o diff --git a/drivers/phy/ti/phy-da8xx-usb.c b/drivers/phy/ti/phy-da8xx-usb.c new file mode 100644 index 000000000000..1b82bff6330f --- /dev/null +++ b/drivers/phy/ti/phy-da8xx-usb.c @@ -0,0 +1,251 @@ +/* + * phy-da8xx-usb - TI DaVinci DA8xx USB PHY driver + * + * Copyright (C) 2016 David Lechner + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PHY_INIT_BITS (CFGCHIP2_SESENDEN | CFGCHIP2_VBDTCTEN) + +struct da8xx_usb_phy { + struct phy_provider *phy_provider; + struct phy *usb11_phy; + struct phy *usb20_phy; + struct clk *usb11_clk; + struct clk *usb20_clk; + struct regmap *regmap; +}; + +static int da8xx_usb11_phy_power_on(struct phy *phy) +{ + struct da8xx_usb_phy *d_phy = phy_get_drvdata(phy); + int ret; + + ret = clk_prepare_enable(d_phy->usb11_clk); + if (ret) + return ret; + + regmap_write_bits(d_phy->regmap, CFGCHIP(2), CFGCHIP2_USB1SUSPENDM, + CFGCHIP2_USB1SUSPENDM); + + return 0; +} + +static int da8xx_usb11_phy_power_off(struct phy *phy) +{ + struct da8xx_usb_phy *d_phy = phy_get_drvdata(phy); + + regmap_write_bits(d_phy->regmap, CFGCHIP(2), CFGCHIP2_USB1SUSPENDM, 0); + + clk_disable_unprepare(d_phy->usb11_clk); + + return 0; +} + +static const struct phy_ops da8xx_usb11_phy_ops = { + .power_on = da8xx_usb11_phy_power_on, + .power_off = da8xx_usb11_phy_power_off, + .owner = THIS_MODULE, +}; + +static int da8xx_usb20_phy_power_on(struct phy *phy) +{ + struct da8xx_usb_phy *d_phy = phy_get_drvdata(phy); + int ret; + + ret = clk_prepare_enable(d_phy->usb20_clk); + if (ret) + return ret; + + regmap_write_bits(d_phy->regmap, CFGCHIP(2), CFGCHIP2_OTGPWRDN, 0); + + return 0; +} + +static int da8xx_usb20_phy_power_off(struct phy *phy) +{ + struct da8xx_usb_phy *d_phy = phy_get_drvdata(phy); + + regmap_write_bits(d_phy->regmap, CFGCHIP(2), CFGCHIP2_OTGPWRDN, + CFGCHIP2_OTGPWRDN); + + clk_disable_unprepare(d_phy->usb20_clk); + + return 0; +} + +static int da8xx_usb20_phy_set_mode(struct phy *phy, enum phy_mode mode) +{ + struct da8xx_usb_phy *d_phy = phy_get_drvdata(phy); + u32 val; + + switch (mode) { + case PHY_MODE_USB_HOST: /* Force VBUS valid, ID = 0 */ + val = CFGCHIP2_OTGMODE_FORCE_HOST; + break; + case PHY_MODE_USB_DEVICE: /* Force VBUS valid, ID = 1 */ + val = CFGCHIP2_OTGMODE_FORCE_DEVICE; + break; + case PHY_MODE_USB_OTG: /* Don't override the VBUS/ID comparators */ + val = CFGCHIP2_OTGMODE_NO_OVERRIDE; + break; + default: + return -EINVAL; + } + + regmap_write_bits(d_phy->regmap, CFGCHIP(2), CFGCHIP2_OTGMODE_MASK, + val); + + return 0; +} + +static const struct phy_ops da8xx_usb20_phy_ops = { + .power_on = da8xx_usb20_phy_power_on, + .power_off = da8xx_usb20_phy_power_off, + .set_mode = da8xx_usb20_phy_set_mode, + .owner = THIS_MODULE, +}; + +static struct phy *da8xx_usb_phy_of_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct da8xx_usb_phy *d_phy = dev_get_drvdata(dev); + + if (!d_phy) + return ERR_PTR(-ENODEV); + + switch (args->args[0]) { + case 0: + return d_phy->usb20_phy; + case 1: + return d_phy->usb11_phy; + default: + return ERR_PTR(-EINVAL); + } +} + +static int da8xx_usb_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct da8xx_usb_phy *d_phy; + + d_phy = devm_kzalloc(dev, sizeof(*d_phy), GFP_KERNEL); + if (!d_phy) + return -ENOMEM; + + if (node) + d_phy->regmap = syscon_regmap_lookup_by_compatible( + "ti,da830-cfgchip"); + else + d_phy->regmap = syscon_regmap_lookup_by_pdevname("syscon"); + if (IS_ERR(d_phy->regmap)) { + dev_err(dev, "Failed to get syscon\n"); + return PTR_ERR(d_phy->regmap); + } + + d_phy->usb11_clk = devm_clk_get(dev, "usb11_phy"); + if (IS_ERR(d_phy->usb11_clk)) { + dev_err(dev, "Failed to get usb11_phy clock\n"); + return PTR_ERR(d_phy->usb11_clk); + } + + d_phy->usb20_clk = devm_clk_get(dev, "usb20_phy"); + if (IS_ERR(d_phy->usb20_clk)) { + dev_err(dev, "Failed to get usb20_phy clock\n"); + return PTR_ERR(d_phy->usb20_clk); + } + + d_phy->usb11_phy = devm_phy_create(dev, node, &da8xx_usb11_phy_ops); + if (IS_ERR(d_phy->usb11_phy)) { + dev_err(dev, "Failed to create usb11 phy\n"); + return PTR_ERR(d_phy->usb11_phy); + } + + d_phy->usb20_phy = devm_phy_create(dev, node, &da8xx_usb20_phy_ops); + if (IS_ERR(d_phy->usb20_phy)) { + dev_err(dev, "Failed to create usb20 phy\n"); + return PTR_ERR(d_phy->usb20_phy); + } + + platform_set_drvdata(pdev, d_phy); + phy_set_drvdata(d_phy->usb11_phy, d_phy); + phy_set_drvdata(d_phy->usb20_phy, d_phy); + + if (node) { + d_phy->phy_provider = devm_of_phy_provider_register(dev, + da8xx_usb_phy_of_xlate); + if (IS_ERR(d_phy->phy_provider)) { + dev_err(dev, "Failed to create phy provider\n"); + return PTR_ERR(d_phy->phy_provider); + } + } else { + int ret; + + ret = phy_create_lookup(d_phy->usb11_phy, "usb-phy", + "ohci-da8xx"); + if (ret) + dev_warn(dev, "Failed to create usb11 phy lookup\n"); + ret = phy_create_lookup(d_phy->usb20_phy, "usb-phy", + "musb-da8xx"); + if (ret) + dev_warn(dev, "Failed to create usb20 phy lookup\n"); + } + + regmap_write_bits(d_phy->regmap, CFGCHIP(2), + PHY_INIT_BITS, PHY_INIT_BITS); + + return 0; +} + +static int da8xx_usb_phy_remove(struct platform_device *pdev) +{ + struct da8xx_usb_phy *d_phy = platform_get_drvdata(pdev); + + if (!pdev->dev.of_node) { + phy_remove_lookup(d_phy->usb20_phy, "usb-phy", "musb-da8xx"); + phy_remove_lookup(d_phy->usb11_phy, "usb-phy", "ohci-da8xx"); + } + + return 0; +} + +static const struct of_device_id da8xx_usb_phy_ids[] = { + { .compatible = "ti,da830-usb-phy" }, + { } +}; +MODULE_DEVICE_TABLE(of, da8xx_usb_phy_ids); + +static struct platform_driver da8xx_usb_phy_driver = { + .probe = da8xx_usb_phy_probe, + .remove = da8xx_usb_phy_remove, + .driver = { + .name = "da8xx-usb-phy", + .of_match_table = da8xx_usb_phy_ids, + }, +}; + +module_platform_driver(da8xx_usb_phy_driver); + +MODULE_ALIAS("platform:da8xx-usb-phy"); +MODULE_AUTHOR("David Lechner "); +MODULE_DESCRIPTION("TI DA8xx USB PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/ti/phy-dm816x-usb.c b/drivers/phy/ti/phy-dm816x-usb.c new file mode 100644 index 000000000000..cbcce7cf0028 --- /dev/null +++ b/drivers/phy/ti/phy-dm816x-usb.c @@ -0,0 +1,290 @@ +/* + * 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. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* + * TRM has two sets of USB_CTRL registers.. The correct register bits + * are in TRM section 24.9.8.2 USB_CTRL Register. The TRM documents the + * phy as being SR70LX Synopsys USB 2.0 OTG nanoPHY. It also seems at + * least dm816x rev c ignores writes to USB_CTRL register, but the TI + * kernel is writing to those so it's possible that later revisions + * have worknig USB_CTRL register. + * + * Also note that At least USB_CTRL register seems to be dm816x specific + * according to the TRM. It's possible that USBPHY_CTRL is more generic, + * but that would have to be checked against the SR70LX documentation + * which does not seem to be publicly available. + * + * Finally, the phy on dm814x and am335x is different from dm816x. + */ +#define DM816X_USB_CTRL_PHYCLKSRC BIT(8) /* 1 = PLL ref clock */ +#define DM816X_USB_CTRL_PHYSLEEP1 BIT(1) /* Enable the first phy */ +#define DM816X_USB_CTRL_PHYSLEEP0 BIT(0) /* Enable the second phy */ + +#define DM816X_USBPHY_CTRL_TXRISETUNE 1 +#define DM816X_USBPHY_CTRL_TXVREFTUNE 0xc +#define DM816X_USBPHY_CTRL_TXPREEMTUNE 0x2 + +struct dm816x_usb_phy { + struct regmap *syscon; + struct device *dev; + unsigned int instance; + struct clk *refclk; + struct usb_phy phy; + unsigned int usb_ctrl; /* Shared between phy0 and phy1 */ + unsigned int usbphy_ctrl; +}; + +static int dm816x_usb_phy_set_host(struct usb_otg *otg, struct usb_bus *host) +{ + otg->host = host; + if (!host) + otg->state = OTG_STATE_UNDEFINED; + + return 0; +} + +static int dm816x_usb_phy_set_peripheral(struct usb_otg *otg, + struct usb_gadget *gadget) +{ + otg->gadget = gadget; + if (!gadget) + otg->state = OTG_STATE_UNDEFINED; + + return 0; +} + +static int dm816x_usb_phy_init(struct phy *x) +{ + struct dm816x_usb_phy *phy = phy_get_drvdata(x); + unsigned int val; + int error; + + if (clk_get_rate(phy->refclk) != 24000000) + dev_warn(phy->dev, "nonstandard phy refclk\n"); + + /* Set PLL ref clock and put phys to sleep */ + error = regmap_update_bits(phy->syscon, phy->usb_ctrl, + DM816X_USB_CTRL_PHYCLKSRC | + DM816X_USB_CTRL_PHYSLEEP1 | + DM816X_USB_CTRL_PHYSLEEP0, + 0); + regmap_read(phy->syscon, phy->usb_ctrl, &val); + if ((val & 3) != 0) + dev_info(phy->dev, + "Working dm816x USB_CTRL! (0x%08x)\n", + val); + + /* + * TI kernel sets these values for "symmetrical eye diagram and + * better signal quality" so let's assume somebody checked the + * values with a scope and set them here too. + */ + regmap_read(phy->syscon, phy->usbphy_ctrl, &val); + val |= DM816X_USBPHY_CTRL_TXRISETUNE | + DM816X_USBPHY_CTRL_TXVREFTUNE | + DM816X_USBPHY_CTRL_TXPREEMTUNE; + regmap_write(phy->syscon, phy->usbphy_ctrl, val); + + return 0; +} + +static const struct phy_ops ops = { + .init = dm816x_usb_phy_init, + .owner = THIS_MODULE, +}; + +static int __maybe_unused dm816x_usb_phy_runtime_suspend(struct device *dev) +{ + struct dm816x_usb_phy *phy = dev_get_drvdata(dev); + unsigned int mask, val; + int error = 0; + + mask = BIT(phy->instance); + val = ~BIT(phy->instance); + error = regmap_update_bits(phy->syscon, phy->usb_ctrl, + mask, val); + if (error) + dev_err(phy->dev, "phy%i failed to power off\n", + phy->instance); + clk_disable(phy->refclk); + + return 0; +} + +static int __maybe_unused dm816x_usb_phy_runtime_resume(struct device *dev) +{ + struct dm816x_usb_phy *phy = dev_get_drvdata(dev); + unsigned int mask, val; + int error; + + error = clk_enable(phy->refclk); + if (error) + return error; + + /* + * Note that at least dm816x rev c does not seem to do + * anything with the USB_CTRL register. But let's follow + * what the TI tree is doing in case later revisions use + * USB_CTRL. + */ + mask = BIT(phy->instance); + val = BIT(phy->instance); + error = regmap_update_bits(phy->syscon, phy->usb_ctrl, + mask, val); + if (error) { + dev_err(phy->dev, "phy%i failed to power on\n", + phy->instance); + clk_disable(phy->refclk); + return error; + } + + return 0; +} + +static UNIVERSAL_DEV_PM_OPS(dm816x_usb_phy_pm_ops, + dm816x_usb_phy_runtime_suspend, + dm816x_usb_phy_runtime_resume, + NULL); + +#ifdef CONFIG_OF +static const struct of_device_id dm816x_usb_phy_id_table[] = { + { + .compatible = "ti,dm8168-usb-phy", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, dm816x_usb_phy_id_table); +#endif + +static int dm816x_usb_phy_probe(struct platform_device *pdev) +{ + struct dm816x_usb_phy *phy; + struct resource *res; + struct phy *generic_phy; + struct phy_provider *phy_provider; + struct usb_otg *otg; + const struct of_device_id *of_id; + const struct usb_phy_data *phy_data; + int error; + + of_id = of_match_device(of_match_ptr(dm816x_usb_phy_id_table), + &pdev->dev); + if (!of_id) + return -EINVAL; + + phy = devm_kzalloc(&pdev->dev, sizeof(*phy), GFP_KERNEL); + if (!phy) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENOENT; + + phy->syscon = syscon_regmap_lookup_by_phandle(pdev->dev.of_node, + "syscon"); + if (IS_ERR(phy->syscon)) + return PTR_ERR(phy->syscon); + + /* + * According to sprs614e.pdf, the first usb_ctrl is shared and + * the second instance for usb_ctrl is reserved.. Also the + * register bits are different from earlier TRMs. + */ + phy->usb_ctrl = 0x20; + phy->usbphy_ctrl = (res->start & 0xff) + 4; + if (phy->usbphy_ctrl == 0x2c) + phy->instance = 1; + + phy_data = of_id->data; + + otg = devm_kzalloc(&pdev->dev, sizeof(*otg), GFP_KERNEL); + if (!otg) + return -ENOMEM; + + phy->dev = &pdev->dev; + phy->phy.dev = phy->dev; + phy->phy.label = "dm8168_usb_phy"; + phy->phy.otg = otg; + phy->phy.type = USB_PHY_TYPE_USB2; + otg->set_host = dm816x_usb_phy_set_host; + otg->set_peripheral = dm816x_usb_phy_set_peripheral; + otg->usb_phy = &phy->phy; + + platform_set_drvdata(pdev, phy); + + phy->refclk = devm_clk_get(phy->dev, "refclk"); + if (IS_ERR(phy->refclk)) + return PTR_ERR(phy->refclk); + error = clk_prepare(phy->refclk); + if (error) + return error; + + pm_runtime_enable(phy->dev); + generic_phy = devm_phy_create(phy->dev, NULL, &ops); + if (IS_ERR(generic_phy)) + return PTR_ERR(generic_phy); + + phy_set_drvdata(generic_phy, phy); + + phy_provider = devm_of_phy_provider_register(phy->dev, + of_phy_simple_xlate); + if (IS_ERR(phy_provider)) + return PTR_ERR(phy_provider); + + usb_add_phy_dev(&phy->phy); + + return 0; +} + +static int dm816x_usb_phy_remove(struct platform_device *pdev) +{ + struct dm816x_usb_phy *phy = platform_get_drvdata(pdev); + + usb_remove_phy(&phy->phy); + pm_runtime_disable(phy->dev); + clk_unprepare(phy->refclk); + + return 0; +} + +static struct platform_driver dm816x_usb_phy_driver = { + .probe = dm816x_usb_phy_probe, + .remove = dm816x_usb_phy_remove, + .driver = { + .name = "dm816x-usb-phy", + .pm = &dm816x_usb_phy_pm_ops, + .of_match_table = of_match_ptr(dm816x_usb_phy_id_table), + }, +}; + +module_platform_driver(dm816x_usb_phy_driver); + +MODULE_ALIAS("platform:dm816x_usb"); +MODULE_AUTHOR("Tony Lindgren "); +MODULE_DESCRIPTION("dm816x usb phy driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/ti/phy-omap-control.c b/drivers/phy/ti/phy-omap-control.c new file mode 100644 index 000000000000..e9c41b3fa0ee --- /dev/null +++ b/drivers/phy/ti/phy-omap-control.c @@ -0,0 +1,360 @@ +/* + * omap-control-phy.c - The PHY part of control module. + * + * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * Author: Kishon Vijay Abraham I + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * omap_control_pcie_pcs - set the PCS delay count + * @dev: the control module device + * @delay: 8 bit delay value + */ +void omap_control_pcie_pcs(struct device *dev, u8 delay) +{ + u32 val; + struct omap_control_phy *control_phy; + + if (IS_ERR(dev) || !dev) { + pr_err("%s: invalid device\n", __func__); + return; + } + + control_phy = dev_get_drvdata(dev); + if (!control_phy) { + dev_err(dev, "%s: invalid control phy device\n", __func__); + return; + } + + if (control_phy->type != OMAP_CTRL_TYPE_PCIE) { + dev_err(dev, "%s: unsupported operation\n", __func__); + return; + } + + val = readl(control_phy->pcie_pcs); + val &= ~(OMAP_CTRL_PCIE_PCS_MASK << + OMAP_CTRL_PCIE_PCS_DELAY_COUNT_SHIFT); + val |= (delay << OMAP_CTRL_PCIE_PCS_DELAY_COUNT_SHIFT); + writel(val, control_phy->pcie_pcs); +} +EXPORT_SYMBOL_GPL(omap_control_pcie_pcs); + +/** + * omap_control_phy_power - power on/off the phy using control module reg + * @dev: the control module device + * @on: 0 or 1, based on powering on or off the PHY + */ +void omap_control_phy_power(struct device *dev, int on) +{ + u32 val; + unsigned long rate; + struct omap_control_phy *control_phy; + + if (IS_ERR(dev) || !dev) { + pr_err("%s: invalid device\n", __func__); + return; + } + + control_phy = dev_get_drvdata(dev); + if (!control_phy) { + dev_err(dev, "%s: invalid control phy device\n", __func__); + return; + } + + if (control_phy->type == OMAP_CTRL_TYPE_OTGHS) + return; + + val = readl(control_phy->power); + + switch (control_phy->type) { + case OMAP_CTRL_TYPE_USB2: + if (on) + val &= ~OMAP_CTRL_DEV_PHY_PD; + else + val |= OMAP_CTRL_DEV_PHY_PD; + break; + + case OMAP_CTRL_TYPE_PCIE: + case OMAP_CTRL_TYPE_PIPE3: + rate = clk_get_rate(control_phy->sys_clk); + rate = rate/1000000; + + if (on) { + val &= ~(OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_MASK | + OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_MASK); + val |= OMAP_CTRL_PIPE3_PHY_TX_RX_POWERON << + OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT; + val |= rate << + OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_SHIFT; + } else { + val &= ~OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_MASK; + val |= OMAP_CTRL_PIPE3_PHY_TX_RX_POWEROFF << + OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT; + } + break; + + case OMAP_CTRL_TYPE_DRA7USB2: + if (on) + val &= ~OMAP_CTRL_USB2_PHY_PD; + else + val |= OMAP_CTRL_USB2_PHY_PD; + break; + + case OMAP_CTRL_TYPE_AM437USB2: + if (on) { + val &= ~(AM437X_CTRL_USB2_PHY_PD | + AM437X_CTRL_USB2_OTG_PD); + val |= (AM437X_CTRL_USB2_OTGVDET_EN | + AM437X_CTRL_USB2_OTGSESSEND_EN); + } else { + val &= ~(AM437X_CTRL_USB2_OTGVDET_EN | + AM437X_CTRL_USB2_OTGSESSEND_EN); + val |= (AM437X_CTRL_USB2_PHY_PD | + AM437X_CTRL_USB2_OTG_PD); + } + break; + default: + dev_err(dev, "%s: type %d not recognized\n", + __func__, control_phy->type); + break; + } + + writel(val, control_phy->power); +} +EXPORT_SYMBOL_GPL(omap_control_phy_power); + +/** + * omap_control_usb_host_mode - set AVALID, VBUSVALID and ID pin in grounded + * @ctrl_phy: struct omap_control_phy * + * + * Writes to the mailbox register to notify the usb core that a usb + * device has been connected. + */ +static void omap_control_usb_host_mode(struct omap_control_phy *ctrl_phy) +{ + u32 val; + + val = readl(ctrl_phy->otghs_control); + val &= ~(OMAP_CTRL_DEV_IDDIG | OMAP_CTRL_DEV_SESSEND); + val |= OMAP_CTRL_DEV_AVALID | OMAP_CTRL_DEV_VBUSVALID; + writel(val, ctrl_phy->otghs_control); +} + +/** + * omap_control_usb_device_mode - set AVALID, VBUSVALID and ID pin in high + * impedance + * @ctrl_phy: struct omap_control_phy * + * + * Writes to the mailbox register to notify the usb core that it has been + * connected to a usb host. + */ +static void omap_control_usb_device_mode(struct omap_control_phy *ctrl_phy) +{ + u32 val; + + val = readl(ctrl_phy->otghs_control); + val &= ~OMAP_CTRL_DEV_SESSEND; + val |= OMAP_CTRL_DEV_IDDIG | OMAP_CTRL_DEV_AVALID | + OMAP_CTRL_DEV_VBUSVALID; + writel(val, ctrl_phy->otghs_control); +} + +/** + * omap_control_usb_set_sessionend - Enable SESSIONEND and IDIG to high + * impedance + * @ctrl_phy: struct omap_control_phy * + * + * Writes to the mailbox register to notify the usb core it's now in + * disconnected state. + */ +static void omap_control_usb_set_sessionend(struct omap_control_phy *ctrl_phy) +{ + u32 val; + + val = readl(ctrl_phy->otghs_control); + val &= ~(OMAP_CTRL_DEV_AVALID | OMAP_CTRL_DEV_VBUSVALID); + val |= OMAP_CTRL_DEV_IDDIG | OMAP_CTRL_DEV_SESSEND; + writel(val, ctrl_phy->otghs_control); +} + +/** + * omap_control_usb_set_mode - Calls to functions to set USB in one of host mode + * or device mode or to denote disconnected state + * @dev: the control module device + * @mode: The mode to which usb should be configured + * + * This is an API to write to the mailbox register to notify the usb core that + * a usb device has been connected. + */ +void omap_control_usb_set_mode(struct device *dev, + enum omap_control_usb_mode mode) +{ + struct omap_control_phy *ctrl_phy; + + if (IS_ERR(dev) || !dev) + return; + + ctrl_phy = dev_get_drvdata(dev); + if (!ctrl_phy) { + dev_err(dev, "Invalid control phy device\n"); + return; + } + + if (ctrl_phy->type != OMAP_CTRL_TYPE_OTGHS) + return; + + switch (mode) { + case USB_MODE_HOST: + omap_control_usb_host_mode(ctrl_phy); + break; + case USB_MODE_DEVICE: + omap_control_usb_device_mode(ctrl_phy); + break; + case USB_MODE_DISCONNECT: + omap_control_usb_set_sessionend(ctrl_phy); + break; + default: + dev_vdbg(dev, "invalid omap control usb mode\n"); + } +} +EXPORT_SYMBOL_GPL(omap_control_usb_set_mode); + +static const enum omap_control_phy_type otghs_data = OMAP_CTRL_TYPE_OTGHS; +static const enum omap_control_phy_type usb2_data = OMAP_CTRL_TYPE_USB2; +static const enum omap_control_phy_type pipe3_data = OMAP_CTRL_TYPE_PIPE3; +static const enum omap_control_phy_type pcie_data = OMAP_CTRL_TYPE_PCIE; +static const enum omap_control_phy_type dra7usb2_data = OMAP_CTRL_TYPE_DRA7USB2; +static const enum omap_control_phy_type am437usb2_data = OMAP_CTRL_TYPE_AM437USB2; + +static const struct of_device_id omap_control_phy_id_table[] = { + { + .compatible = "ti,control-phy-otghs", + .data = &otghs_data, + }, + { + .compatible = "ti,control-phy-usb2", + .data = &usb2_data, + }, + { + .compatible = "ti,control-phy-pipe3", + .data = &pipe3_data, + }, + { + .compatible = "ti,control-phy-pcie", + .data = &pcie_data, + }, + { + .compatible = "ti,control-phy-usb2-dra7", + .data = &dra7usb2_data, + }, + { + .compatible = "ti,control-phy-usb2-am437", + .data = &am437usb2_data, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, omap_control_phy_id_table); + +static int omap_control_phy_probe(struct platform_device *pdev) +{ + struct resource *res; + const struct of_device_id *of_id; + struct omap_control_phy *control_phy; + + of_id = of_match_device(omap_control_phy_id_table, &pdev->dev); + if (!of_id) + return -EINVAL; + + control_phy = devm_kzalloc(&pdev->dev, sizeof(*control_phy), + GFP_KERNEL); + if (!control_phy) + return -ENOMEM; + + control_phy->dev = &pdev->dev; + control_phy->type = *(enum omap_control_phy_type *)of_id->data; + + if (control_phy->type == OMAP_CTRL_TYPE_OTGHS) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "otghs_control"); + control_phy->otghs_control = devm_ioremap_resource( + &pdev->dev, res); + if (IS_ERR(control_phy->otghs_control)) + return PTR_ERR(control_phy->otghs_control); + } else { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "power"); + control_phy->power = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(control_phy->power)) { + dev_err(&pdev->dev, "Couldn't get power register\n"); + return PTR_ERR(control_phy->power); + } + } + + if (control_phy->type == OMAP_CTRL_TYPE_PIPE3 || + control_phy->type == OMAP_CTRL_TYPE_PCIE) { + control_phy->sys_clk = devm_clk_get(control_phy->dev, + "sys_clkin"); + if (IS_ERR(control_phy->sys_clk)) { + pr_err("%s: unable to get sys_clkin\n", __func__); + return -EINVAL; + } + } + + if (control_phy->type == OMAP_CTRL_TYPE_PCIE) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "pcie_pcs"); + control_phy->pcie_pcs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(control_phy->pcie_pcs)) + return PTR_ERR(control_phy->pcie_pcs); + } + + dev_set_drvdata(control_phy->dev, control_phy); + + return 0; +} + +static struct platform_driver omap_control_phy_driver = { + .probe = omap_control_phy_probe, + .driver = { + .name = "omap-control-phy", + .of_match_table = omap_control_phy_id_table, + }, +}; + +static int __init omap_control_phy_init(void) +{ + return platform_driver_register(&omap_control_phy_driver); +} +subsys_initcall(omap_control_phy_init); + +static void __exit omap_control_phy_exit(void) +{ + platform_driver_unregister(&omap_control_phy_driver); +} +module_exit(omap_control_phy_exit); + +MODULE_ALIAS("platform:omap_control_phy"); +MODULE_AUTHOR("Texas Instruments Inc."); +MODULE_DESCRIPTION("OMAP Control Module PHY Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/ti/phy-omap-usb2.c b/drivers/phy/ti/phy-omap-usb2.c new file mode 100644 index 000000000000..fe909fd8144f --- /dev/null +++ b/drivers/phy/ti/phy-omap-usb2.c @@ -0,0 +1,439 @@ +/* + * omap-usb2.c - USB PHY, talking to musb controller in OMAP. + * + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * Author: Kishon Vijay Abraham I + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define USB2PHY_DISCON_BYP_LATCH (1 << 31) +#define USB2PHY_ANA_CONFIG1 0x4c + +/** + * omap_usb2_set_comparator - links the comparator present in the sytem with + * this phy + * @comparator - the companion phy(comparator) for this phy + * + * The phy companion driver should call this API passing the phy_companion + * filled with set_vbus and start_srp to be used by usb phy. + * + * For use by phy companion driver + */ +int omap_usb2_set_comparator(struct phy_companion *comparator) +{ + struct omap_usb *phy; + struct usb_phy *x = usb_get_phy(USB_PHY_TYPE_USB2); + + if (IS_ERR(x)) + return -ENODEV; + + phy = phy_to_omapusb(x); + phy->comparator = comparator; + return 0; +} +EXPORT_SYMBOL_GPL(omap_usb2_set_comparator); + +static int omap_usb_set_vbus(struct usb_otg *otg, bool enabled) +{ + struct omap_usb *phy = phy_to_omapusb(otg->usb_phy); + + if (!phy->comparator) + return -ENODEV; + + return phy->comparator->set_vbus(phy->comparator, enabled); +} + +static int omap_usb_start_srp(struct usb_otg *otg) +{ + struct omap_usb *phy = phy_to_omapusb(otg->usb_phy); + + if (!phy->comparator) + return -ENODEV; + + return phy->comparator->start_srp(phy->comparator); +} + +static int omap_usb_set_host(struct usb_otg *otg, struct usb_bus *host) +{ + otg->host = host; + if (!host) + otg->state = OTG_STATE_UNDEFINED; + + return 0; +} + +static int omap_usb_set_peripheral(struct usb_otg *otg, + struct usb_gadget *gadget) +{ + otg->gadget = gadget; + if (!gadget) + otg->state = OTG_STATE_UNDEFINED; + + return 0; +} + +static int omap_usb_phy_power(struct omap_usb *phy, int on) +{ + u32 val; + int ret; + + if (!phy->syscon_phy_power) { + omap_control_phy_power(phy->control_dev, on); + return 0; + } + + if (on) + val = phy->power_on; + else + val = phy->power_off; + + ret = regmap_update_bits(phy->syscon_phy_power, phy->power_reg, + phy->mask, val); + return ret; +} + +static int omap_usb_power_off(struct phy *x) +{ + struct omap_usb *phy = phy_get_drvdata(x); + + return omap_usb_phy_power(phy, false); +} + +static int omap_usb_power_on(struct phy *x) +{ + struct omap_usb *phy = phy_get_drvdata(x); + + return omap_usb_phy_power(phy, true); +} + +static int omap_usb2_disable_clocks(struct omap_usb *phy) +{ + clk_disable(phy->wkupclk); + if (!IS_ERR(phy->optclk)) + clk_disable(phy->optclk); + + return 0; +} + +static int omap_usb2_enable_clocks(struct omap_usb *phy) +{ + int ret; + + ret = clk_enable(phy->wkupclk); + if (ret < 0) { + dev_err(phy->dev, "Failed to enable wkupclk %d\n", ret); + goto err0; + } + + if (!IS_ERR(phy->optclk)) { + ret = clk_enable(phy->optclk); + if (ret < 0) { + dev_err(phy->dev, "Failed to enable optclk %d\n", ret); + goto err1; + } + } + + return 0; + +err1: + clk_disable(phy->wkupclk); + +err0: + return ret; +} + +static int omap_usb_init(struct phy *x) +{ + struct omap_usb *phy = phy_get_drvdata(x); + u32 val; + + omap_usb2_enable_clocks(phy); + + if (phy->flags & OMAP_USB2_CALIBRATE_FALSE_DISCONNECT) { + /* + * + * Reduce the sensitivity of internal PHY by enabling the + * DISCON_BYP_LATCH of the USB2PHY_ANA_CONFIG1 register. This + * resolves issues with certain devices which can otherwise + * be prone to false disconnects. + * + */ + val = omap_usb_readl(phy->phy_base, USB2PHY_ANA_CONFIG1); + val |= USB2PHY_DISCON_BYP_LATCH; + omap_usb_writel(phy->phy_base, USB2PHY_ANA_CONFIG1, val); + } + + return 0; +} + +static int omap_usb_exit(struct phy *x) +{ + struct omap_usb *phy = phy_get_drvdata(x); + + return omap_usb2_disable_clocks(phy); +} + +static const struct phy_ops ops = { + .init = omap_usb_init, + .exit = omap_usb_exit, + .power_on = omap_usb_power_on, + .power_off = omap_usb_power_off, + .owner = THIS_MODULE, +}; + +static const struct usb_phy_data omap_usb2_data = { + .label = "omap_usb2", + .flags = OMAP_USB2_HAS_START_SRP | OMAP_USB2_HAS_SET_VBUS, + .mask = OMAP_DEV_PHY_PD, + .power_off = OMAP_DEV_PHY_PD, +}; + +static const struct usb_phy_data omap5_usb2_data = { + .label = "omap5_usb2", + .flags = 0, + .mask = OMAP_DEV_PHY_PD, + .power_off = OMAP_DEV_PHY_PD, +}; + +static const struct usb_phy_data dra7x_usb2_data = { + .label = "dra7x_usb2", + .flags = OMAP_USB2_CALIBRATE_FALSE_DISCONNECT, + .mask = OMAP_DEV_PHY_PD, + .power_off = OMAP_DEV_PHY_PD, +}; + +static const struct usb_phy_data dra7x_usb2_phy2_data = { + .label = "dra7x_usb2_phy2", + .flags = OMAP_USB2_CALIBRATE_FALSE_DISCONNECT, + .mask = OMAP_USB2_PHY_PD, + .power_off = OMAP_USB2_PHY_PD, +}; + +static const struct usb_phy_data am437x_usb2_data = { + .label = "am437x_usb2", + .flags = 0, + .mask = AM437X_USB2_PHY_PD | AM437X_USB2_OTG_PD | + AM437X_USB2_OTGVDET_EN | AM437X_USB2_OTGSESSEND_EN, + .power_on = AM437X_USB2_OTGVDET_EN | AM437X_USB2_OTGSESSEND_EN, + .power_off = AM437X_USB2_PHY_PD | AM437X_USB2_OTG_PD, +}; + +static const struct of_device_id omap_usb2_id_table[] = { + { + .compatible = "ti,omap-usb2", + .data = &omap_usb2_data, + }, + { + .compatible = "ti,omap5-usb2", + .data = &omap5_usb2_data, + }, + { + .compatible = "ti,dra7x-usb2", + .data = &dra7x_usb2_data, + }, + { + .compatible = "ti,dra7x-usb2-phy2", + .data = &dra7x_usb2_phy2_data, + }, + { + .compatible = "ti,am437x-usb2", + .data = &am437x_usb2_data, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, omap_usb2_id_table); + +static int omap_usb2_probe(struct platform_device *pdev) +{ + struct omap_usb *phy; + struct phy *generic_phy; + struct resource *res; + struct phy_provider *phy_provider; + struct usb_otg *otg; + struct device_node *node = pdev->dev.of_node; + struct device_node *control_node; + struct platform_device *control_pdev; + const struct of_device_id *of_id; + struct usb_phy_data *phy_data; + + of_id = of_match_device(omap_usb2_id_table, &pdev->dev); + + if (!of_id) + return -EINVAL; + + phy_data = (struct usb_phy_data *)of_id->data; + + phy = devm_kzalloc(&pdev->dev, sizeof(*phy), GFP_KERNEL); + if (!phy) + return -ENOMEM; + + otg = devm_kzalloc(&pdev->dev, sizeof(*otg), GFP_KERNEL); + if (!otg) + return -ENOMEM; + + phy->dev = &pdev->dev; + + phy->phy.dev = phy->dev; + phy->phy.label = phy_data->label; + phy->phy.otg = otg; + phy->phy.type = USB_PHY_TYPE_USB2; + phy->mask = phy_data->mask; + phy->power_on = phy_data->power_on; + phy->power_off = phy_data->power_off; + + if (phy_data->flags & OMAP_USB2_CALIBRATE_FALSE_DISCONNECT) { + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + phy->phy_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(phy->phy_base)) + return PTR_ERR(phy->phy_base); + phy->flags |= OMAP_USB2_CALIBRATE_FALSE_DISCONNECT; + } + + phy->syscon_phy_power = syscon_regmap_lookup_by_phandle(node, + "syscon-phy-power"); + if (IS_ERR(phy->syscon_phy_power)) { + dev_dbg(&pdev->dev, + "can't get syscon-phy-power, using control device\n"); + phy->syscon_phy_power = NULL; + + control_node = of_parse_phandle(node, "ctrl-module", 0); + if (!control_node) { + dev_err(&pdev->dev, + "Failed to get control device phandle\n"); + return -EINVAL; + } + + control_pdev = of_find_device_by_node(control_node); + if (!control_pdev) { + dev_err(&pdev->dev, "Failed to get control device\n"); + return -EINVAL; + } + phy->control_dev = &control_pdev->dev; + } else { + if (of_property_read_u32_index(node, + "syscon-phy-power", 1, + &phy->power_reg)) { + dev_err(&pdev->dev, + "couldn't get power reg. offset\n"); + return -EINVAL; + } + } + + otg->set_host = omap_usb_set_host; + otg->set_peripheral = omap_usb_set_peripheral; + if (phy_data->flags & OMAP_USB2_HAS_SET_VBUS) + otg->set_vbus = omap_usb_set_vbus; + if (phy_data->flags & OMAP_USB2_HAS_START_SRP) + otg->start_srp = omap_usb_start_srp; + otg->usb_phy = &phy->phy; + + platform_set_drvdata(pdev, phy); + pm_runtime_enable(phy->dev); + + generic_phy = devm_phy_create(phy->dev, NULL, &ops); + if (IS_ERR(generic_phy)) { + pm_runtime_disable(phy->dev); + return PTR_ERR(generic_phy); + } + + phy_set_drvdata(generic_phy, phy); + omap_usb_power_off(generic_phy); + + phy_provider = devm_of_phy_provider_register(phy->dev, + of_phy_simple_xlate); + if (IS_ERR(phy_provider)) { + pm_runtime_disable(phy->dev); + return PTR_ERR(phy_provider); + } + + phy->wkupclk = devm_clk_get(phy->dev, "wkupclk"); + if (IS_ERR(phy->wkupclk)) { + dev_warn(&pdev->dev, "unable to get wkupclk, trying old name\n"); + phy->wkupclk = devm_clk_get(phy->dev, "usb_phy_cm_clk32k"); + if (IS_ERR(phy->wkupclk)) { + dev_err(&pdev->dev, "unable to get usb_phy_cm_clk32k\n"); + pm_runtime_disable(phy->dev); + return PTR_ERR(phy->wkupclk); + } else { + dev_warn(&pdev->dev, + "found usb_phy_cm_clk32k, please fix DTS\n"); + } + } + clk_prepare(phy->wkupclk); + + phy->optclk = devm_clk_get(phy->dev, "refclk"); + if (IS_ERR(phy->optclk)) { + dev_dbg(&pdev->dev, "unable to get refclk, trying old name\n"); + phy->optclk = devm_clk_get(phy->dev, "usb_otg_ss_refclk960m"); + if (IS_ERR(phy->optclk)) { + dev_dbg(&pdev->dev, + "unable to get usb_otg_ss_refclk960m\n"); + } else { + dev_warn(&pdev->dev, + "found usb_otg_ss_refclk960m, please fix DTS\n"); + } + } + + if (!IS_ERR(phy->optclk)) + clk_prepare(phy->optclk); + + usb_add_phy_dev(&phy->phy); + + return 0; +} + +static int omap_usb2_remove(struct platform_device *pdev) +{ + struct omap_usb *phy = platform_get_drvdata(pdev); + + clk_unprepare(phy->wkupclk); + if (!IS_ERR(phy->optclk)) + clk_unprepare(phy->optclk); + usb_remove_phy(&phy->phy); + pm_runtime_disable(phy->dev); + + return 0; +} + +static struct platform_driver omap_usb2_driver = { + .probe = omap_usb2_probe, + .remove = omap_usb2_remove, + .driver = { + .name = "omap-usb2", + .of_match_table = omap_usb2_id_table, + }, +}; + +module_platform_driver(omap_usb2_driver); + +MODULE_ALIAS("platform:omap_usb2"); +MODULE_AUTHOR("Texas Instruments Inc."); +MODULE_DESCRIPTION("OMAP USB2 phy driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/ti/phy-ti-pipe3.c b/drivers/phy/ti/phy-ti-pipe3.c new file mode 100644 index 000000000000..9c84d32c6f60 --- /dev/null +++ b/drivers/phy/ti/phy-ti-pipe3.c @@ -0,0 +1,697 @@ +/* + * phy-ti-pipe3 - PIPE3 PHY driver. + * + * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * Author: Kishon Vijay Abraham I + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PLL_STATUS 0x00000004 +#define PLL_GO 0x00000008 +#define PLL_CONFIGURATION1 0x0000000C +#define PLL_CONFIGURATION2 0x00000010 +#define PLL_CONFIGURATION3 0x00000014 +#define PLL_CONFIGURATION4 0x00000020 + +#define PLL_REGM_MASK 0x001FFE00 +#define PLL_REGM_SHIFT 0x9 +#define PLL_REGM_F_MASK 0x0003FFFF +#define PLL_REGM_F_SHIFT 0x0 +#define PLL_REGN_MASK 0x000001FE +#define PLL_REGN_SHIFT 0x1 +#define PLL_SELFREQDCO_MASK 0x0000000E +#define PLL_SELFREQDCO_SHIFT 0x1 +#define PLL_SD_MASK 0x0003FC00 +#define PLL_SD_SHIFT 10 +#define SET_PLL_GO 0x1 +#define PLL_LDOPWDN BIT(15) +#define PLL_TICOPWDN BIT(16) +#define PLL_LOCK 0x2 +#define PLL_IDLE 0x1 + +#define SATA_PLL_SOFT_RESET BIT(18) + +#define PIPE3_PHY_PWRCTL_CLK_CMD_MASK 0x003FC000 +#define PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT 14 + +#define PIPE3_PHY_PWRCTL_CLK_FREQ_MASK 0xFFC00000 +#define PIPE3_PHY_PWRCTL_CLK_FREQ_SHIFT 22 + +#define PIPE3_PHY_TX_RX_POWERON 0x3 +#define PIPE3_PHY_TX_RX_POWEROFF 0x0 + +#define PCIE_PCS_MASK 0xFF0000 +#define PCIE_PCS_DELAY_COUNT_SHIFT 0x10 + +/* + * This is an Empirical value that works, need to confirm the actual + * value required for the PIPE3PHY_PLL_CONFIGURATION2.PLL_IDLE status + * to be correctly reflected in the PIPE3PHY_PLL_STATUS register. + */ +#define PLL_IDLE_TIME 100 /* in milliseconds */ +#define PLL_LOCK_TIME 100 /* in milliseconds */ + +struct pipe3_dpll_params { + u16 m; + u8 n; + u8 freq:3; + u8 sd; + u32 mf; +}; + +struct pipe3_dpll_map { + unsigned long rate; + struct pipe3_dpll_params params; +}; + +struct ti_pipe3 { + void __iomem *pll_ctrl_base; + struct device *dev; + struct device *control_dev; + struct clk *wkupclk; + struct clk *sys_clk; + struct clk *refclk; + struct clk *div_clk; + struct pipe3_dpll_map *dpll_map; + struct regmap *phy_power_syscon; /* ctrl. reg. acces */ + struct regmap *pcs_syscon; /* ctrl. reg. acces */ + struct regmap *dpll_reset_syscon; /* ctrl. reg. acces */ + unsigned int dpll_reset_reg; /* reg. index within syscon */ + unsigned int power_reg; /* power reg. index within syscon */ + unsigned int pcie_pcs_reg; /* pcs reg. index in syscon */ + bool sata_refclk_enabled; +}; + +static struct pipe3_dpll_map dpll_map_usb[] = { + {12000000, {1250, 5, 4, 20, 0} }, /* 12 MHz */ + {16800000, {3125, 20, 4, 20, 0} }, /* 16.8 MHz */ + {19200000, {1172, 8, 4, 20, 65537} }, /* 19.2 MHz */ + {20000000, {1000, 7, 4, 10, 0} }, /* 20 MHz */ + {26000000, {1250, 12, 4, 20, 0} }, /* 26 MHz */ + {38400000, {3125, 47, 4, 20, 92843} }, /* 38.4 MHz */ + { }, /* Terminator */ +}; + +static struct pipe3_dpll_map dpll_map_sata[] = { + {12000000, {1000, 7, 4, 6, 0} }, /* 12 MHz */ + {16800000, {714, 7, 4, 6, 0} }, /* 16.8 MHz */ + {19200000, {625, 7, 4, 6, 0} }, /* 19.2 MHz */ + {20000000, {600, 7, 4, 6, 0} }, /* 20 MHz */ + {26000000, {461, 7, 4, 6, 0} }, /* 26 MHz */ + {38400000, {312, 7, 4, 6, 0} }, /* 38.4 MHz */ + { }, /* Terminator */ +}; + +static inline u32 ti_pipe3_readl(void __iomem *addr, unsigned offset) +{ + return __raw_readl(addr + offset); +} + +static inline void ti_pipe3_writel(void __iomem *addr, unsigned offset, + u32 data) +{ + __raw_writel(data, addr + offset); +} + +static struct pipe3_dpll_params *ti_pipe3_get_dpll_params(struct ti_pipe3 *phy) +{ + unsigned long rate; + struct pipe3_dpll_map *dpll_map = phy->dpll_map; + + rate = clk_get_rate(phy->sys_clk); + + for (; dpll_map->rate; dpll_map++) { + if (rate == dpll_map->rate) + return &dpll_map->params; + } + + dev_err(phy->dev, "No DPLL configuration for %lu Hz SYS CLK\n", rate); + + return NULL; +} + +static int ti_pipe3_enable_clocks(struct ti_pipe3 *phy); +static void ti_pipe3_disable_clocks(struct ti_pipe3 *phy); + +static int ti_pipe3_power_off(struct phy *x) +{ + u32 val; + int ret; + struct ti_pipe3 *phy = phy_get_drvdata(x); + + if (!phy->phy_power_syscon) { + omap_control_phy_power(phy->control_dev, 0); + return 0; + } + + val = PIPE3_PHY_TX_RX_POWEROFF << PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT; + + ret = regmap_update_bits(phy->phy_power_syscon, phy->power_reg, + PIPE3_PHY_PWRCTL_CLK_CMD_MASK, val); + return ret; +} + +static int ti_pipe3_power_on(struct phy *x) +{ + u32 val; + u32 mask; + int ret; + unsigned long rate; + struct ti_pipe3 *phy = phy_get_drvdata(x); + + if (!phy->phy_power_syscon) { + omap_control_phy_power(phy->control_dev, 1); + return 0; + } + + rate = clk_get_rate(phy->sys_clk); + if (!rate) { + dev_err(phy->dev, "Invalid clock rate\n"); + return -EINVAL; + } + rate = rate / 1000000; + mask = OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_CMD_MASK | + OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_MASK; + val = PIPE3_PHY_TX_RX_POWERON << PIPE3_PHY_PWRCTL_CLK_CMD_SHIFT; + val |= rate << OMAP_CTRL_PIPE3_PHY_PWRCTL_CLK_FREQ_SHIFT; + + ret = regmap_update_bits(phy->phy_power_syscon, phy->power_reg, + mask, val); + return ret; +} + +static int ti_pipe3_dpll_wait_lock(struct ti_pipe3 *phy) +{ + u32 val; + unsigned long timeout; + + timeout = jiffies + msecs_to_jiffies(PLL_LOCK_TIME); + do { + cpu_relax(); + val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_STATUS); + if (val & PLL_LOCK) + return 0; + } while (!time_after(jiffies, timeout)); + + dev_err(phy->dev, "DPLL failed to lock\n"); + return -EBUSY; +} + +static int ti_pipe3_dpll_program(struct ti_pipe3 *phy) +{ + u32 val; + struct pipe3_dpll_params *dpll_params; + + dpll_params = ti_pipe3_get_dpll_params(phy); + if (!dpll_params) + return -EINVAL; + + val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION1); + val &= ~PLL_REGN_MASK; + val |= dpll_params->n << PLL_REGN_SHIFT; + ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION1, val); + + val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION2); + val &= ~PLL_SELFREQDCO_MASK; + val |= dpll_params->freq << PLL_SELFREQDCO_SHIFT; + ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION2, val); + + val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION1); + val &= ~PLL_REGM_MASK; + val |= dpll_params->m << PLL_REGM_SHIFT; + ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION1, val); + + val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION4); + val &= ~PLL_REGM_F_MASK; + val |= dpll_params->mf << PLL_REGM_F_SHIFT; + ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION4, val); + + val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION3); + val &= ~PLL_SD_MASK; + val |= dpll_params->sd << PLL_SD_SHIFT; + ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION3, val); + + ti_pipe3_writel(phy->pll_ctrl_base, PLL_GO, SET_PLL_GO); + + return ti_pipe3_dpll_wait_lock(phy); +} + +static int ti_pipe3_init(struct phy *x) +{ + struct ti_pipe3 *phy = phy_get_drvdata(x); + u32 val; + int ret = 0; + + ti_pipe3_enable_clocks(phy); + /* + * Set pcie_pcs register to 0x96 for proper functioning of phy + * as recommended in AM572x TRM SPRUHZ6, section 18.5.2.2, table + * 18-1804. + */ + if (of_device_is_compatible(phy->dev->of_node, "ti,phy-pipe3-pcie")) { + if (!phy->pcs_syscon) { + omap_control_pcie_pcs(phy->control_dev, 0x96); + return 0; + } + + val = 0x96 << OMAP_CTRL_PCIE_PCS_DELAY_COUNT_SHIFT; + ret = regmap_update_bits(phy->pcs_syscon, phy->pcie_pcs_reg, + PCIE_PCS_MASK, val); + return ret; + } + + /* Bring it out of IDLE if it is IDLE */ + val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION2); + if (val & PLL_IDLE) { + val &= ~PLL_IDLE; + ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION2, val); + ret = ti_pipe3_dpll_wait_lock(phy); + } + + /* SATA has issues if re-programmed when locked */ + val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_STATUS); + if ((val & PLL_LOCK) && of_device_is_compatible(phy->dev->of_node, + "ti,phy-pipe3-sata")) + return ret; + + /* Program the DPLL */ + ret = ti_pipe3_dpll_program(phy); + if (ret) { + ti_pipe3_disable_clocks(phy); + return -EINVAL; + } + + return ret; +} + +static int ti_pipe3_exit(struct phy *x) +{ + struct ti_pipe3 *phy = phy_get_drvdata(x); + u32 val; + unsigned long timeout; + + /* If dpll_reset_syscon is not present we wont power down SATA DPLL + * due to Errata i783 + */ + if (of_device_is_compatible(phy->dev->of_node, "ti,phy-pipe3-sata") && + !phy->dpll_reset_syscon) + return 0; + + /* PCIe doesn't have internal DPLL */ + if (!of_device_is_compatible(phy->dev->of_node, "ti,phy-pipe3-pcie")) { + /* Put DPLL in IDLE mode */ + val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_CONFIGURATION2); + val |= PLL_IDLE; + ti_pipe3_writel(phy->pll_ctrl_base, PLL_CONFIGURATION2, val); + + /* wait for LDO and Oscillator to power down */ + timeout = jiffies + msecs_to_jiffies(PLL_IDLE_TIME); + do { + cpu_relax(); + val = ti_pipe3_readl(phy->pll_ctrl_base, PLL_STATUS); + if ((val & PLL_TICOPWDN) && (val & PLL_LDOPWDN)) + break; + } while (!time_after(jiffies, timeout)); + + if (!(val & PLL_TICOPWDN) || !(val & PLL_LDOPWDN)) { + dev_err(phy->dev, "Failed to power down: PLL_STATUS 0x%x\n", + val); + return -EBUSY; + } + } + + /* i783: SATA needs control bit toggle after PLL unlock */ + if (of_device_is_compatible(phy->dev->of_node, "ti,phy-pipe3-sata")) { + regmap_update_bits(phy->dpll_reset_syscon, phy->dpll_reset_reg, + SATA_PLL_SOFT_RESET, SATA_PLL_SOFT_RESET); + regmap_update_bits(phy->dpll_reset_syscon, phy->dpll_reset_reg, + SATA_PLL_SOFT_RESET, 0); + } + + ti_pipe3_disable_clocks(phy); + + return 0; +} +static const struct phy_ops ops = { + .init = ti_pipe3_init, + .exit = ti_pipe3_exit, + .power_on = ti_pipe3_power_on, + .power_off = ti_pipe3_power_off, + .owner = THIS_MODULE, +}; + +static const struct of_device_id ti_pipe3_id_table[]; + +static int ti_pipe3_get_clk(struct ti_pipe3 *phy) +{ + struct clk *clk; + struct device *dev = phy->dev; + struct device_node *node = dev->of_node; + + phy->refclk = devm_clk_get(dev, "refclk"); + if (IS_ERR(phy->refclk)) { + dev_err(dev, "unable to get refclk\n"); + /* older DTBs have missing refclk in SATA PHY + * so don't bail out in case of SATA PHY. + */ + if (!of_device_is_compatible(node, "ti,phy-pipe3-sata")) + return PTR_ERR(phy->refclk); + } + + if (!of_device_is_compatible(node, "ti,phy-pipe3-sata")) { + phy->wkupclk = devm_clk_get(dev, "wkupclk"); + if (IS_ERR(phy->wkupclk)) { + dev_err(dev, "unable to get wkupclk\n"); + return PTR_ERR(phy->wkupclk); + } + } else { + phy->wkupclk = ERR_PTR(-ENODEV); + } + + if (!of_device_is_compatible(node, "ti,phy-pipe3-pcie") || + phy->phy_power_syscon) { + phy->sys_clk = devm_clk_get(dev, "sysclk"); + if (IS_ERR(phy->sys_clk)) { + dev_err(dev, "unable to get sysclk\n"); + return -EINVAL; + } + } + + if (of_device_is_compatible(node, "ti,phy-pipe3-pcie")) { + clk = devm_clk_get(dev, "dpll_ref"); + if (IS_ERR(clk)) { + dev_err(dev, "unable to get dpll ref clk\n"); + return PTR_ERR(clk); + } + clk_set_rate(clk, 1500000000); + + clk = devm_clk_get(dev, "dpll_ref_m2"); + if (IS_ERR(clk)) { + dev_err(dev, "unable to get dpll ref m2 clk\n"); + return PTR_ERR(clk); + } + clk_set_rate(clk, 100000000); + + clk = devm_clk_get(dev, "phy-div"); + if (IS_ERR(clk)) { + dev_err(dev, "unable to get phy-div clk\n"); + return PTR_ERR(clk); + } + clk_set_rate(clk, 100000000); + + phy->div_clk = devm_clk_get(dev, "div-clk"); + if (IS_ERR(phy->div_clk)) { + dev_err(dev, "unable to get div-clk\n"); + return PTR_ERR(phy->div_clk); + } + } else { + phy->div_clk = ERR_PTR(-ENODEV); + } + + return 0; +} + +static int ti_pipe3_get_sysctrl(struct ti_pipe3 *phy) +{ + struct device *dev = phy->dev; + struct device_node *node = dev->of_node; + struct device_node *control_node; + struct platform_device *control_pdev; + + phy->phy_power_syscon = syscon_regmap_lookup_by_phandle(node, + "syscon-phy-power"); + if (IS_ERR(phy->phy_power_syscon)) { + dev_dbg(dev, + "can't get syscon-phy-power, using control device\n"); + phy->phy_power_syscon = NULL; + } else { + if (of_property_read_u32_index(node, + "syscon-phy-power", 1, + &phy->power_reg)) { + dev_err(dev, "couldn't get power reg. offset\n"); + return -EINVAL; + } + } + + if (!phy->phy_power_syscon) { + control_node = of_parse_phandle(node, "ctrl-module", 0); + if (!control_node) { + dev_err(dev, "Failed to get control device phandle\n"); + return -EINVAL; + } + + control_pdev = of_find_device_by_node(control_node); + if (!control_pdev) { + dev_err(dev, "Failed to get control device\n"); + return -EINVAL; + } + + phy->control_dev = &control_pdev->dev; + } + + if (of_device_is_compatible(node, "ti,phy-pipe3-pcie")) { + phy->pcs_syscon = syscon_regmap_lookup_by_phandle(node, + "syscon-pcs"); + if (IS_ERR(phy->pcs_syscon)) { + dev_dbg(dev, + "can't get syscon-pcs, using omap control\n"); + phy->pcs_syscon = NULL; + } else { + if (of_property_read_u32_index(node, + "syscon-pcs", 1, + &phy->pcie_pcs_reg)) { + dev_err(dev, + "couldn't get pcie pcs reg. offset\n"); + return -EINVAL; + } + } + } + + if (of_device_is_compatible(node, "ti,phy-pipe3-sata")) { + phy->dpll_reset_syscon = syscon_regmap_lookup_by_phandle(node, + "syscon-pllreset"); + if (IS_ERR(phy->dpll_reset_syscon)) { + dev_info(dev, + "can't get syscon-pllreset, sata dpll won't idle\n"); + phy->dpll_reset_syscon = NULL; + } else { + if (of_property_read_u32_index(node, + "syscon-pllreset", 1, + &phy->dpll_reset_reg)) { + dev_err(dev, + "couldn't get pllreset reg. offset\n"); + return -EINVAL; + } + } + } + + return 0; +} + +static int ti_pipe3_get_pll_base(struct ti_pipe3 *phy) +{ + struct resource *res; + const struct of_device_id *match; + struct device *dev = phy->dev; + struct device_node *node = dev->of_node; + struct platform_device *pdev = to_platform_device(dev); + + if (of_device_is_compatible(node, "ti,phy-pipe3-pcie")) + return 0; + + match = of_match_device(ti_pipe3_id_table, dev); + if (!match) + return -EINVAL; + + phy->dpll_map = (struct pipe3_dpll_map *)match->data; + if (!phy->dpll_map) { + dev_err(dev, "no DPLL data\n"); + return -EINVAL; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "pll_ctrl"); + phy->pll_ctrl_base = devm_ioremap_resource(dev, res); + return PTR_ERR_OR_ZERO(phy->pll_ctrl_base); +} + +static int ti_pipe3_probe(struct platform_device *pdev) +{ + struct ti_pipe3 *phy; + struct phy *generic_phy; + struct phy_provider *phy_provider; + struct device_node *node = pdev->dev.of_node; + struct device *dev = &pdev->dev; + int ret; + + phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); + if (!phy) + return -ENOMEM; + + phy->dev = dev; + + ret = ti_pipe3_get_pll_base(phy); + if (ret) + return ret; + + ret = ti_pipe3_get_sysctrl(phy); + if (ret) + return ret; + + ret = ti_pipe3_get_clk(phy); + if (ret) + return ret; + + platform_set_drvdata(pdev, phy); + pm_runtime_enable(dev); + + /* + * Prevent auto-disable of refclk for SATA PHY due to Errata i783 + */ + if (of_device_is_compatible(node, "ti,phy-pipe3-sata")) { + if (!IS_ERR(phy->refclk)) { + clk_prepare_enable(phy->refclk); + phy->sata_refclk_enabled = true; + } + } + + generic_phy = devm_phy_create(dev, NULL, &ops); + if (IS_ERR(generic_phy)) + return PTR_ERR(generic_phy); + + phy_set_drvdata(generic_phy, phy); + + ti_pipe3_power_off(generic_phy); + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + return PTR_ERR_OR_ZERO(phy_provider); +} + +static int ti_pipe3_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static int ti_pipe3_enable_clocks(struct ti_pipe3 *phy) +{ + int ret = 0; + + if (!IS_ERR(phy->refclk)) { + ret = clk_prepare_enable(phy->refclk); + if (ret) { + dev_err(phy->dev, "Failed to enable refclk %d\n", ret); + return ret; + } + } + + if (!IS_ERR(phy->wkupclk)) { + ret = clk_prepare_enable(phy->wkupclk); + if (ret) { + dev_err(phy->dev, "Failed to enable wkupclk %d\n", ret); + goto disable_refclk; + } + } + + if (!IS_ERR(phy->div_clk)) { + ret = clk_prepare_enable(phy->div_clk); + if (ret) { + dev_err(phy->dev, "Failed to enable div_clk %d\n", ret); + goto disable_wkupclk; + } + } + + return 0; + +disable_wkupclk: + if (!IS_ERR(phy->wkupclk)) + clk_disable_unprepare(phy->wkupclk); + +disable_refclk: + if (!IS_ERR(phy->refclk)) + clk_disable_unprepare(phy->refclk); + + return ret; +} + +static void ti_pipe3_disable_clocks(struct ti_pipe3 *phy) +{ + if (!IS_ERR(phy->wkupclk)) + clk_disable_unprepare(phy->wkupclk); + if (!IS_ERR(phy->refclk)) { + clk_disable_unprepare(phy->refclk); + /* + * SATA refclk needs an additional disable as we left it + * on in probe to avoid Errata i783 + */ + if (phy->sata_refclk_enabled) { + clk_disable_unprepare(phy->refclk); + phy->sata_refclk_enabled = false; + } + } + + if (!IS_ERR(phy->div_clk)) + clk_disable_unprepare(phy->div_clk); +} + +static const struct of_device_id ti_pipe3_id_table[] = { + { + .compatible = "ti,phy-usb3", + .data = dpll_map_usb, + }, + { + .compatible = "ti,omap-usb3", + .data = dpll_map_usb, + }, + { + .compatible = "ti,phy-pipe3-sata", + .data = dpll_map_sata, + }, + { + .compatible = "ti,phy-pipe3-pcie", + }, + {} +}; +MODULE_DEVICE_TABLE(of, ti_pipe3_id_table); + +static struct platform_driver ti_pipe3_driver = { + .probe = ti_pipe3_probe, + .remove = ti_pipe3_remove, + .driver = { + .name = "ti-pipe3", + .of_match_table = ti_pipe3_id_table, + }, +}; + +module_platform_driver(ti_pipe3_driver); + +MODULE_ALIAS("platform:ti_pipe3"); +MODULE_AUTHOR("Texas Instruments Inc."); +MODULE_DESCRIPTION("TI PIPE3 phy driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/ti/phy-tusb1210.c b/drivers/phy/ti/phy-tusb1210.c new file mode 100644 index 000000000000..bb3fb031c478 --- /dev/null +++ b/drivers/phy/ti/phy-tusb1210.c @@ -0,0 +1,146 @@ +/** + * tusb1210.c - TUSB1210 USB ULPI PHY driver + * + * Copyright (C) 2015 Intel Corporation + * + * Author: Heikki Krogerus + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include +#include +#include + +#define TUSB1210_VENDOR_SPECIFIC2 0x80 +#define TUSB1210_VENDOR_SPECIFIC2_IHSTX_SHIFT 0 +#define TUSB1210_VENDOR_SPECIFIC2_ZHSDRV_SHIFT 4 +#define TUSB1210_VENDOR_SPECIFIC2_DP_SHIFT 6 + +struct tusb1210 { + struct ulpi *ulpi; + struct phy *phy; + struct gpio_desc *gpio_reset; + struct gpio_desc *gpio_cs; + u8 vendor_specific2; +}; + +static int tusb1210_power_on(struct phy *phy) +{ + struct tusb1210 *tusb = phy_get_drvdata(phy); + + gpiod_set_value_cansleep(tusb->gpio_reset, 1); + gpiod_set_value_cansleep(tusb->gpio_cs, 1); + + /* Restore the optional eye diagram optimization value */ + if (tusb->vendor_specific2) + ulpi_write(tusb->ulpi, TUSB1210_VENDOR_SPECIFIC2, + tusb->vendor_specific2); + + return 0; +} + +static int tusb1210_power_off(struct phy *phy) +{ + struct tusb1210 *tusb = phy_get_drvdata(phy); + + gpiod_set_value_cansleep(tusb->gpio_reset, 0); + gpiod_set_value_cansleep(tusb->gpio_cs, 0); + + return 0; +} + +static const struct phy_ops phy_ops = { + .power_on = tusb1210_power_on, + .power_off = tusb1210_power_off, + .owner = THIS_MODULE, +}; + +static int tusb1210_probe(struct ulpi *ulpi) +{ + struct tusb1210 *tusb; + u8 val, reg; + + tusb = devm_kzalloc(&ulpi->dev, sizeof(*tusb), GFP_KERNEL); + if (!tusb) + return -ENOMEM; + + tusb->gpio_reset = devm_gpiod_get_optional(&ulpi->dev, "reset", + GPIOD_OUT_LOW); + if (IS_ERR(tusb->gpio_reset)) + return PTR_ERR(tusb->gpio_reset); + + gpiod_set_value_cansleep(tusb->gpio_reset, 1); + + tusb->gpio_cs = devm_gpiod_get_optional(&ulpi->dev, "cs", + GPIOD_OUT_LOW); + if (IS_ERR(tusb->gpio_cs)) + return PTR_ERR(tusb->gpio_cs); + + gpiod_set_value_cansleep(tusb->gpio_cs, 1); + + /* + * VENDOR_SPECIFIC2 register in TUSB1210 can be used for configuring eye + * diagram optimization and DP/DM swap. + */ + + /* High speed output drive strength configuration */ + device_property_read_u8(&ulpi->dev, "ihstx", &val); + reg = val << TUSB1210_VENDOR_SPECIFIC2_IHSTX_SHIFT; + + /* High speed output impedance configuration */ + device_property_read_u8(&ulpi->dev, "zhsdrv", &val); + reg |= val << TUSB1210_VENDOR_SPECIFIC2_ZHSDRV_SHIFT; + + /* DP/DM swap control */ + device_property_read_u8(&ulpi->dev, "datapolarity", &val); + reg |= val << TUSB1210_VENDOR_SPECIFIC2_DP_SHIFT; + + if (reg) { + ulpi_write(ulpi, TUSB1210_VENDOR_SPECIFIC2, reg); + tusb->vendor_specific2 = reg; + } + + tusb->phy = ulpi_phy_create(ulpi, &phy_ops); + if (IS_ERR(tusb->phy)) + return PTR_ERR(tusb->phy); + + tusb->ulpi = ulpi; + + phy_set_drvdata(tusb->phy, tusb); + ulpi_set_drvdata(ulpi, tusb); + return 0; +} + +static void tusb1210_remove(struct ulpi *ulpi) +{ + struct tusb1210 *tusb = ulpi_get_drvdata(ulpi); + + ulpi_phy_destroy(ulpi, tusb->phy); +} + +#define TI_VENDOR_ID 0x0451 + +static const struct ulpi_device_id tusb1210_ulpi_id[] = { + { TI_VENDOR_ID, 0x1507, }, + { }, +}; +MODULE_DEVICE_TABLE(ulpi, tusb1210_ulpi_id); + +static struct ulpi_driver tusb1210_driver = { + .id_table = tusb1210_ulpi_id, + .probe = tusb1210_probe, + .remove = tusb1210_remove, + .driver = { + .name = "tusb1210", + .owner = THIS_MODULE, + }, +}; + +module_ulpi_driver(tusb1210_driver); + +MODULE_AUTHOR("Intel Corporation"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("TUSB1210 ULPI PHY driver"); diff --git a/drivers/phy/ti/phy-twl4030-usb.c b/drivers/phy/ti/phy-twl4030-usb.c new file mode 100644 index 000000000000..2990b3965460 --- /dev/null +++ b/drivers/phy/ti/phy-twl4030-usb.c @@ -0,0 +1,839 @@ +/* + * twl4030_usb - TWL4030 USB transceiver, talking to OMAP OTG controller + * + * Copyright (C) 2004-2007 Texas Instruments + * Copyright (C) 2008 Nokia Corporation + * Contact: Felipe Balbi + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Current status: + * - HS USB ULPI mode works. + * - 3-pin mode support may be added in future. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Register defines */ + +#define MCPC_CTRL 0x30 +#define MCPC_CTRL_RTSOL (1 << 7) +#define MCPC_CTRL_EXTSWR (1 << 6) +#define MCPC_CTRL_EXTSWC (1 << 5) +#define MCPC_CTRL_VOICESW (1 << 4) +#define MCPC_CTRL_OUT64K (1 << 3) +#define MCPC_CTRL_RTSCTSSW (1 << 2) +#define MCPC_CTRL_HS_UART (1 << 0) + +#define MCPC_IO_CTRL 0x33 +#define MCPC_IO_CTRL_MICBIASEN (1 << 5) +#define MCPC_IO_CTRL_CTS_NPU (1 << 4) +#define MCPC_IO_CTRL_RXD_PU (1 << 3) +#define MCPC_IO_CTRL_TXDTYP (1 << 2) +#define MCPC_IO_CTRL_CTSTYP (1 << 1) +#define MCPC_IO_CTRL_RTSTYP (1 << 0) + +#define MCPC_CTRL2 0x36 +#define MCPC_CTRL2_MCPC_CK_EN (1 << 0) + +#define OTHER_FUNC_CTRL 0x80 +#define OTHER_FUNC_CTRL_BDIS_ACON_EN (1 << 4) +#define OTHER_FUNC_CTRL_FIVEWIRE_MODE (1 << 2) + +#define OTHER_IFC_CTRL 0x83 +#define OTHER_IFC_CTRL_OE_INT_EN (1 << 6) +#define OTHER_IFC_CTRL_CEA2011_MODE (1 << 5) +#define OTHER_IFC_CTRL_FSLSSERIALMODE_4PIN (1 << 4) +#define OTHER_IFC_CTRL_HIZ_ULPI_60MHZ_OUT (1 << 3) +#define OTHER_IFC_CTRL_HIZ_ULPI (1 << 2) +#define OTHER_IFC_CTRL_ALT_INT_REROUTE (1 << 0) + +#define OTHER_INT_EN_RISE 0x86 +#define OTHER_INT_EN_FALL 0x89 +#define OTHER_INT_STS 0x8C +#define OTHER_INT_LATCH 0x8D +#define OTHER_INT_VB_SESS_VLD (1 << 7) +#define OTHER_INT_DM_HI (1 << 6) /* not valid for "latch" reg */ +#define OTHER_INT_DP_HI (1 << 5) /* not valid for "latch" reg */ +#define OTHER_INT_BDIS_ACON (1 << 3) /* not valid for "fall" regs */ +#define OTHER_INT_MANU (1 << 1) +#define OTHER_INT_ABNORMAL_STRESS (1 << 0) + +#define ID_STATUS 0x96 +#define ID_RES_FLOAT (1 << 4) +#define ID_RES_440K (1 << 3) +#define ID_RES_200K (1 << 2) +#define ID_RES_102K (1 << 1) +#define ID_RES_GND (1 << 0) + +#define POWER_CTRL 0xAC +#define POWER_CTRL_OTG_ENAB (1 << 5) + +#define OTHER_IFC_CTRL2 0xAF +#define OTHER_IFC_CTRL2_ULPI_STP_LOW (1 << 4) +#define OTHER_IFC_CTRL2_ULPI_TXEN_POL (1 << 3) +#define OTHER_IFC_CTRL2_ULPI_4PIN_2430 (1 << 2) +#define OTHER_IFC_CTRL2_USB_INT_OUTSEL_MASK (3 << 0) /* bits 0 and 1 */ +#define OTHER_IFC_CTRL2_USB_INT_OUTSEL_INT1N (0 << 0) +#define OTHER_IFC_CTRL2_USB_INT_OUTSEL_INT2N (1 << 0) + +#define REG_CTRL_EN 0xB2 +#define REG_CTRL_ERROR 0xB5 +#define ULPI_I2C_CONFLICT_INTEN (1 << 0) + +#define OTHER_FUNC_CTRL2 0xB8 +#define OTHER_FUNC_CTRL2_VBAT_TIMER_EN (1 << 0) + +/* following registers do not have separate _clr and _set registers */ +#define VBUS_DEBOUNCE 0xC0 +#define ID_DEBOUNCE 0xC1 +#define VBAT_TIMER 0xD3 +#define PHY_PWR_CTRL 0xFD +#define PHY_PWR_PHYPWD (1 << 0) +#define PHY_CLK_CTRL 0xFE +#define PHY_CLK_CTRL_CLOCKGATING_EN (1 << 2) +#define PHY_CLK_CTRL_CLK32K_EN (1 << 1) +#define REQ_PHY_DPLL_CLK (1 << 0) +#define PHY_CLK_CTRL_STS 0xFF +#define PHY_DPLL_CLK (1 << 0) + +/* In module TWL_MODULE_PM_MASTER */ +#define STS_HW_CONDITIONS 0x0F + +/* In module TWL_MODULE_PM_RECEIVER */ +#define VUSB_DEDICATED1 0x7D +#define VUSB_DEDICATED2 0x7E +#define VUSB1V5_DEV_GRP 0x71 +#define VUSB1V5_TYPE 0x72 +#define VUSB1V5_REMAP 0x73 +#define VUSB1V8_DEV_GRP 0x74 +#define VUSB1V8_TYPE 0x75 +#define VUSB1V8_REMAP 0x76 +#define VUSB3V1_DEV_GRP 0x77 +#define VUSB3V1_TYPE 0x78 +#define VUSB3V1_REMAP 0x79 + +/* In module TWL4030_MODULE_INTBR */ +#define PMBR1 0x0D +#define GPIO_USB_4PIN_ULPI_2430C (3 << 0) + +/* + * If VBUS is valid or ID is ground, then we know a + * cable is present and we need to be runtime-enabled + */ +static inline bool cable_present(enum musb_vbus_id_status stat) +{ + return stat == MUSB_VBUS_VALID || + stat == MUSB_ID_GROUND; +} + +struct twl4030_usb { + struct usb_phy phy; + struct device *dev; + + /* TWL4030 internal USB regulator supplies */ + struct regulator *usb1v5; + struct regulator *usb1v8; + struct regulator *usb3v1; + + /* for vbus reporting with irqs disabled */ + struct mutex lock; + + /* pin configuration */ + enum twl4030_usb_mode usb_mode; + + int irq; + enum musb_vbus_id_status linkstat; + bool vbus_supplied; + bool musb_mailbox_pending; + + struct delayed_work id_workaround_work; +}; + +/* internal define on top of container_of */ +#define phy_to_twl(x) container_of((x), struct twl4030_usb, phy) + +/*-------------------------------------------------------------------------*/ + +static int twl4030_i2c_write_u8_verify(struct twl4030_usb *twl, + u8 module, u8 data, u8 address) +{ + u8 check; + + if ((twl_i2c_write_u8(module, data, address) >= 0) && + (twl_i2c_read_u8(module, &check, address) >= 0) && + (check == data)) + return 0; + dev_dbg(twl->dev, "Write%d[%d,0x%x] wrote %02x but read %02x\n", + 1, module, address, check, data); + + /* Failed once: Try again */ + if ((twl_i2c_write_u8(module, data, address) >= 0) && + (twl_i2c_read_u8(module, &check, address) >= 0) && + (check == data)) + return 0; + dev_dbg(twl->dev, "Write%d[%d,0x%x] wrote %02x but read %02x\n", + 2, module, address, check, data); + + /* Failed again: Return error */ + return -EBUSY; +} + +#define twl4030_usb_write_verify(twl, address, data) \ + twl4030_i2c_write_u8_verify(twl, TWL_MODULE_USB, (data), (address)) + +static inline int twl4030_usb_write(struct twl4030_usb *twl, + u8 address, u8 data) +{ + int ret = 0; + + ret = twl_i2c_write_u8(TWL_MODULE_USB, data, address); + if (ret < 0) + dev_dbg(twl->dev, + "TWL4030:USB:Write[0x%x] Error %d\n", address, ret); + return ret; +} + +static inline int twl4030_readb(struct twl4030_usb *twl, u8 module, u8 address) +{ + u8 data; + int ret = 0; + + ret = twl_i2c_read_u8(module, &data, address); + if (ret >= 0) + ret = data; + else + dev_dbg(twl->dev, + "TWL4030:readb[0x%x,0x%x] Error %d\n", + module, address, ret); + + return ret; +} + +static inline int twl4030_usb_read(struct twl4030_usb *twl, u8 address) +{ + return twl4030_readb(twl, TWL_MODULE_USB, address); +} + +/*-------------------------------------------------------------------------*/ + +static inline int +twl4030_usb_set_bits(struct twl4030_usb *twl, u8 reg, u8 bits) +{ + return twl4030_usb_write(twl, ULPI_SET(reg), bits); +} + +static inline int +twl4030_usb_clear_bits(struct twl4030_usb *twl, u8 reg, u8 bits) +{ + return twl4030_usb_write(twl, ULPI_CLR(reg), bits); +} + +/*-------------------------------------------------------------------------*/ + +static bool twl4030_is_driving_vbus(struct twl4030_usb *twl) +{ + int ret; + + ret = twl4030_usb_read(twl, PHY_CLK_CTRL_STS); + if (ret < 0 || !(ret & PHY_DPLL_CLK)) + /* + * if clocks are off, registers are not updated, + * but we can assume we don't drive VBUS in this case + */ + return false; + + ret = twl4030_usb_read(twl, ULPI_OTG_CTRL); + if (ret < 0) + return false; + + return (ret & (ULPI_OTG_DRVVBUS | ULPI_OTG_CHRGVBUS)) ? true : false; +} + +static enum musb_vbus_id_status + twl4030_usb_linkstat(struct twl4030_usb *twl) +{ + int status; + enum musb_vbus_id_status linkstat = MUSB_UNKNOWN; + + twl->vbus_supplied = false; + + /* + * For ID/VBUS sensing, see manual section 15.4.8 ... + * except when using only battery backup power, two + * comparators produce VBUS_PRES and ID_PRES signals, + * which don't match docs elsewhere. But ... BIT(7) + * and BIT(2) of STS_HW_CONDITIONS, respectively, do + * seem to match up. If either is true the USB_PRES + * signal is active, the OTG module is activated, and + * its interrupt may be raised (may wake the system). + */ + status = twl4030_readb(twl, TWL_MODULE_PM_MASTER, STS_HW_CONDITIONS); + if (status < 0) + dev_err(twl->dev, "USB link status err %d\n", status); + else if (status & (BIT(7) | BIT(2))) { + if (status & BIT(7)) { + if (twl4030_is_driving_vbus(twl)) + status &= ~BIT(7); + else + twl->vbus_supplied = true; + } + + if (status & BIT(2)) + linkstat = MUSB_ID_GROUND; + else if (status & BIT(7)) + linkstat = MUSB_VBUS_VALID; + else + linkstat = MUSB_VBUS_OFF; + } else { + if (twl->linkstat != MUSB_UNKNOWN) + linkstat = MUSB_VBUS_OFF; + } + + kobject_uevent(&twl->dev->kobj, linkstat == MUSB_VBUS_VALID + ? KOBJ_ONLINE : KOBJ_OFFLINE); + + dev_dbg(twl->dev, "HW_CONDITIONS 0x%02x/%d; link %d\n", + status, status, linkstat); + + /* REVISIT this assumes host and peripheral controllers + * are registered, and that both are active... + */ + + return linkstat; +} + +static void twl4030_usb_set_mode(struct twl4030_usb *twl, int mode) +{ + twl->usb_mode = mode; + + switch (mode) { + case T2_USB_MODE_ULPI: + twl4030_usb_clear_bits(twl, ULPI_IFC_CTRL, + ULPI_IFC_CTRL_CARKITMODE); + twl4030_usb_set_bits(twl, POWER_CTRL, POWER_CTRL_OTG_ENAB); + twl4030_usb_clear_bits(twl, ULPI_FUNC_CTRL, + ULPI_FUNC_CTRL_XCVRSEL_MASK | + ULPI_FUNC_CTRL_OPMODE_MASK); + break; + case -1: + /* FIXME: power on defaults */ + break; + default: + dev_err(twl->dev, "unsupported T2 transceiver mode %d\n", + mode); + break; + } +} + +static void twl4030_i2c_access(struct twl4030_usb *twl, int on) +{ + unsigned long timeout; + int val = twl4030_usb_read(twl, PHY_CLK_CTRL); + + if (val >= 0) { + if (on) { + /* enable DPLL to access PHY registers over I2C */ + val |= REQ_PHY_DPLL_CLK; + WARN_ON(twl4030_usb_write_verify(twl, PHY_CLK_CTRL, + (u8)val) < 0); + + timeout = jiffies + HZ; + while (!(twl4030_usb_read(twl, PHY_CLK_CTRL_STS) & + PHY_DPLL_CLK) + && time_before(jiffies, timeout)) + udelay(10); + if (!(twl4030_usb_read(twl, PHY_CLK_CTRL_STS) & + PHY_DPLL_CLK)) + dev_err(twl->dev, "Timeout setting T2 HSUSB " + "PHY DPLL clock\n"); + } else { + /* let ULPI control the DPLL clock */ + val &= ~REQ_PHY_DPLL_CLK; + WARN_ON(twl4030_usb_write_verify(twl, PHY_CLK_CTRL, + (u8)val) < 0); + } + } +} + +static void __twl4030_phy_power(struct twl4030_usb *twl, int on) +{ + u8 pwr = twl4030_usb_read(twl, PHY_PWR_CTRL); + + if (on) + pwr &= ~PHY_PWR_PHYPWD; + else + pwr |= PHY_PWR_PHYPWD; + + WARN_ON(twl4030_usb_write_verify(twl, PHY_PWR_CTRL, pwr) < 0); +} + +static int __maybe_unused twl4030_usb_runtime_suspend(struct device *dev) +{ + struct twl4030_usb *twl = dev_get_drvdata(dev); + + dev_dbg(twl->dev, "%s\n", __func__); + + __twl4030_phy_power(twl, 0); + regulator_disable(twl->usb1v5); + regulator_disable(twl->usb1v8); + regulator_disable(twl->usb3v1); + + return 0; +} + +static int __maybe_unused twl4030_usb_runtime_resume(struct device *dev) +{ + struct twl4030_usb *twl = dev_get_drvdata(dev); + int res; + + dev_dbg(twl->dev, "%s\n", __func__); + + res = regulator_enable(twl->usb3v1); + if (res) + dev_err(twl->dev, "Failed to enable usb3v1\n"); + + res = regulator_enable(twl->usb1v8); + if (res) + dev_err(twl->dev, "Failed to enable usb1v8\n"); + + /* + * Disabling usb3v1 regulator (= writing 0 to VUSB3V1_DEV_GRP + * in twl4030) resets the VUSB_DEDICATED2 register. This reset + * enables VUSB3V1_SLEEP bit that remaps usb3v1 ACTIVE state to + * SLEEP. We work around this by clearing the bit after usv3v1 + * is re-activated. This ensures that VUSB3V1 is really active. + */ + twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB_DEDICATED2); + + res = regulator_enable(twl->usb1v5); + if (res) + dev_err(twl->dev, "Failed to enable usb1v5\n"); + + __twl4030_phy_power(twl, 1); + twl4030_usb_write(twl, PHY_CLK_CTRL, + twl4030_usb_read(twl, PHY_CLK_CTRL) | + (PHY_CLK_CTRL_CLOCKGATING_EN | + PHY_CLK_CTRL_CLK32K_EN)); + + twl4030_i2c_access(twl, 1); + twl4030_usb_set_mode(twl, twl->usb_mode); + if (twl->usb_mode == T2_USB_MODE_ULPI) + twl4030_i2c_access(twl, 0); + /* + * According to the TPS65950 TRM, there has to be at least 50ms + * delay between setting POWER_CTRL_OTG_ENAB and enabling charging + * so wait here so that a fully enabled phy can be expected after + * resume + */ + msleep(50); + return 0; +} + +static int twl4030_phy_power_off(struct phy *phy) +{ + struct twl4030_usb *twl = phy_get_drvdata(phy); + + dev_dbg(twl->dev, "%s\n", __func__); + + return 0; +} + +static int twl4030_phy_power_on(struct phy *phy) +{ + struct twl4030_usb *twl = phy_get_drvdata(phy); + + dev_dbg(twl->dev, "%s\n", __func__); + pm_runtime_get_sync(twl->dev); + schedule_delayed_work(&twl->id_workaround_work, HZ); + pm_runtime_mark_last_busy(twl->dev); + pm_runtime_put_autosuspend(twl->dev); + + return 0; +} + +static int twl4030_usb_ldo_init(struct twl4030_usb *twl) +{ + /* Enable writing to power configuration registers */ + twl_i2c_write_u8(TWL_MODULE_PM_MASTER, TWL4030_PM_MASTER_KEY_CFG1, + TWL4030_PM_MASTER_PROTECT_KEY); + + twl_i2c_write_u8(TWL_MODULE_PM_MASTER, TWL4030_PM_MASTER_KEY_CFG2, + TWL4030_PM_MASTER_PROTECT_KEY); + + /* Keep VUSB3V1 LDO in sleep state until VBUS/ID change detected*/ + /*twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB_DEDICATED2);*/ + + /* input to VUSB3V1 LDO is from VBAT, not VBUS */ + twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0x14, VUSB_DEDICATED1); + + /* Initialize 3.1V regulator */ + twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB3V1_DEV_GRP); + + twl->usb3v1 = devm_regulator_get(twl->dev, "usb3v1"); + if (IS_ERR(twl->usb3v1)) + return -ENODEV; + + twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB3V1_TYPE); + + /* Initialize 1.5V regulator */ + twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB1V5_DEV_GRP); + + twl->usb1v5 = devm_regulator_get(twl->dev, "usb1v5"); + if (IS_ERR(twl->usb1v5)) + return -ENODEV; + + twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB1V5_TYPE); + + /* Initialize 1.8V regulator */ + twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB1V8_DEV_GRP); + + twl->usb1v8 = devm_regulator_get(twl->dev, "usb1v8"); + if (IS_ERR(twl->usb1v8)) + return -ENODEV; + + twl_i2c_write_u8(TWL_MODULE_PM_RECEIVER, 0, VUSB1V8_TYPE); + + /* disable access to power configuration registers */ + twl_i2c_write_u8(TWL_MODULE_PM_MASTER, 0, + TWL4030_PM_MASTER_PROTECT_KEY); + + return 0; +} + +static ssize_t twl4030_usb_vbus_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct twl4030_usb *twl = dev_get_drvdata(dev); + int ret = -EINVAL; + + mutex_lock(&twl->lock); + ret = sprintf(buf, "%s\n", + twl->vbus_supplied ? "on" : "off"); + mutex_unlock(&twl->lock); + + return ret; +} +static DEVICE_ATTR(vbus, 0444, twl4030_usb_vbus_show, NULL); + +static irqreturn_t twl4030_usb_irq(int irq, void *_twl) +{ + struct twl4030_usb *twl = _twl; + enum musb_vbus_id_status status; + bool status_changed = false; + int err; + + status = twl4030_usb_linkstat(twl); + + mutex_lock(&twl->lock); + if (status >= 0 && status != twl->linkstat) { + status_changed = + cable_present(twl->linkstat) != + cable_present(status); + twl->linkstat = status; + } + mutex_unlock(&twl->lock); + + if (status_changed) { + /* FIXME add a set_power() method so that B-devices can + * configure the charger appropriately. It's not always + * correct to consume VBUS power, and how much current to + * consume is a function of the USB configuration chosen + * by the host. + * + * REVISIT usb_gadget_vbus_connect(...) as needed, ditto + * its disconnect() sibling, when changing to/from the + * USB_LINK_VBUS state. musb_hdrc won't care until it + * starts to handle softconnect right. + */ + if (cable_present(status)) { + pm_runtime_get_sync(twl->dev); + } else { + pm_runtime_mark_last_busy(twl->dev); + pm_runtime_put_autosuspend(twl->dev); + } + twl->musb_mailbox_pending = true; + } + if (twl->musb_mailbox_pending) { + err = musb_mailbox(status); + if (!err) + twl->musb_mailbox_pending = false; + } + + /* don't schedule during sleep - irq works right then */ + if (status == MUSB_ID_GROUND && pm_runtime_active(twl->dev)) { + cancel_delayed_work(&twl->id_workaround_work); + schedule_delayed_work(&twl->id_workaround_work, HZ); + } + + if (irq) + sysfs_notify(&twl->dev->kobj, NULL, "vbus"); + + return IRQ_HANDLED; +} + +static void twl4030_id_workaround_work(struct work_struct *work) +{ + struct twl4030_usb *twl = container_of(work, struct twl4030_usb, + id_workaround_work.work); + + twl4030_usb_irq(0, twl); +} + +static int twl4030_phy_init(struct phy *phy) +{ + struct twl4030_usb *twl = phy_get_drvdata(phy); + + pm_runtime_get_sync(twl->dev); + twl->linkstat = MUSB_UNKNOWN; + schedule_delayed_work(&twl->id_workaround_work, HZ); + pm_runtime_mark_last_busy(twl->dev); + pm_runtime_put_autosuspend(twl->dev); + + return 0; +} + +static int twl4030_set_peripheral(struct usb_otg *otg, + struct usb_gadget *gadget) +{ + if (!otg) + return -ENODEV; + + otg->gadget = gadget; + if (!gadget) + otg->state = OTG_STATE_UNDEFINED; + + return 0; +} + +static int twl4030_set_host(struct usb_otg *otg, struct usb_bus *host) +{ + if (!otg) + return -ENODEV; + + otg->host = host; + if (!host) + otg->state = OTG_STATE_UNDEFINED; + + return 0; +} + +static const struct phy_ops ops = { + .init = twl4030_phy_init, + .power_on = twl4030_phy_power_on, + .power_off = twl4030_phy_power_off, + .owner = THIS_MODULE, +}; + +static const struct dev_pm_ops twl4030_usb_pm_ops = { + SET_RUNTIME_PM_OPS(twl4030_usb_runtime_suspend, + twl4030_usb_runtime_resume, NULL) +}; + +static int twl4030_usb_probe(struct platform_device *pdev) +{ + struct twl4030_usb_data *pdata = dev_get_platdata(&pdev->dev); + struct twl4030_usb *twl; + struct phy *phy; + int status, err; + struct usb_otg *otg; + struct device_node *np = pdev->dev.of_node; + struct phy_provider *phy_provider; + + twl = devm_kzalloc(&pdev->dev, sizeof(*twl), GFP_KERNEL); + if (!twl) + return -ENOMEM; + + if (np) + of_property_read_u32(np, "usb_mode", + (enum twl4030_usb_mode *)&twl->usb_mode); + else if (pdata) { + twl->usb_mode = pdata->usb_mode; + } else { + dev_err(&pdev->dev, "twl4030 initialized without pdata\n"); + return -EINVAL; + } + + otg = devm_kzalloc(&pdev->dev, sizeof(*otg), GFP_KERNEL); + if (!otg) + return -ENOMEM; + + twl->dev = &pdev->dev; + twl->irq = platform_get_irq(pdev, 0); + twl->vbus_supplied = false; + twl->linkstat = MUSB_UNKNOWN; + twl->musb_mailbox_pending = false; + + twl->phy.dev = twl->dev; + twl->phy.label = "twl4030"; + twl->phy.otg = otg; + twl->phy.type = USB_PHY_TYPE_USB2; + + otg->usb_phy = &twl->phy; + otg->set_host = twl4030_set_host; + otg->set_peripheral = twl4030_set_peripheral; + + phy = devm_phy_create(twl->dev, NULL, &ops); + if (IS_ERR(phy)) { + dev_dbg(&pdev->dev, "Failed to create PHY\n"); + return PTR_ERR(phy); + } + + phy_set_drvdata(phy, twl); + + phy_provider = devm_of_phy_provider_register(twl->dev, + of_phy_simple_xlate); + if (IS_ERR(phy_provider)) + return PTR_ERR(phy_provider); + + /* init mutex for workqueue */ + mutex_init(&twl->lock); + + INIT_DELAYED_WORK(&twl->id_workaround_work, twl4030_id_workaround_work); + + err = twl4030_usb_ldo_init(twl); + if (err) { + dev_err(&pdev->dev, "ldo init failed\n"); + return err; + } + usb_add_phy_dev(&twl->phy); + + platform_set_drvdata(pdev, twl); + if (device_create_file(&pdev->dev, &dev_attr_vbus)) + dev_warn(&pdev->dev, "could not create sysfs file\n"); + + ATOMIC_INIT_NOTIFIER_HEAD(&twl->phy.notifier); + + pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_set_autosuspend_delay(&pdev->dev, 2000); + pm_runtime_enable(&pdev->dev); + pm_runtime_get_sync(&pdev->dev); + + /* Our job is to use irqs and status from the power module + * to keep the transceiver disabled when nothing's connected. + * + * FIXME we actually shouldn't start enabling it until the + * USB controller drivers have said they're ready, by calling + * set_host() and/or set_peripheral() ... OTG_capable boards + * need both handles, otherwise just one suffices. + */ + status = devm_request_threaded_irq(twl->dev, twl->irq, NULL, + twl4030_usb_irq, IRQF_TRIGGER_FALLING | + IRQF_TRIGGER_RISING | IRQF_ONESHOT, "twl4030_usb", twl); + if (status < 0) { + dev_dbg(&pdev->dev, "can't get IRQ %d, err %d\n", + twl->irq, status); + return status; + } + + if (pdata) + err = phy_create_lookup(phy, "usb", "musb-hdrc.0"); + if (err) + return err; + + pm_runtime_mark_last_busy(&pdev->dev); + pm_runtime_put_autosuspend(twl->dev); + + dev_info(&pdev->dev, "Initialized TWL4030 USB module\n"); + return 0; +} + +static int twl4030_usb_remove(struct platform_device *pdev) +{ + struct twl4030_usb *twl = platform_get_drvdata(pdev); + int val; + + usb_remove_phy(&twl->phy); + pm_runtime_get_sync(twl->dev); + cancel_delayed_work(&twl->id_workaround_work); + device_remove_file(twl->dev, &dev_attr_vbus); + + /* set transceiver mode to power on defaults */ + twl4030_usb_set_mode(twl, -1); + + /* idle ulpi before powering off */ + if (cable_present(twl->linkstat)) + pm_runtime_put_noidle(twl->dev); + pm_runtime_mark_last_busy(twl->dev); + pm_runtime_dont_use_autosuspend(&pdev->dev); + pm_runtime_put_sync(twl->dev); + pm_runtime_disable(twl->dev); + + /* autogate 60MHz ULPI clock, + * clear dpll clock request for i2c access, + * disable 32KHz + */ + val = twl4030_usb_read(twl, PHY_CLK_CTRL); + if (val >= 0) { + val |= PHY_CLK_CTRL_CLOCKGATING_EN; + val &= ~(PHY_CLK_CTRL_CLK32K_EN | REQ_PHY_DPLL_CLK); + twl4030_usb_write(twl, PHY_CLK_CTRL, (u8)val); + } + + /* disable complete OTG block */ + twl4030_usb_clear_bits(twl, POWER_CTRL, POWER_CTRL_OTG_ENAB); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id twl4030_usb_id_table[] = { + { .compatible = "ti,twl4030-usb" }, + {} +}; +MODULE_DEVICE_TABLE(of, twl4030_usb_id_table); +#endif + +static struct platform_driver twl4030_usb_driver = { + .probe = twl4030_usb_probe, + .remove = twl4030_usb_remove, + .driver = { + .name = "twl4030_usb", + .pm = &twl4030_usb_pm_ops, + .of_match_table = of_match_ptr(twl4030_usb_id_table), + }, +}; + +static int __init twl4030_usb_init(void) +{ + return platform_driver_register(&twl4030_usb_driver); +} +subsys_initcall(twl4030_usb_init); + +static void __exit twl4030_usb_exit(void) +{ + platform_driver_unregister(&twl4030_usb_driver); +} +module_exit(twl4030_usb_exit); + +MODULE_ALIAS("platform:twl4030_usb"); +MODULE_AUTHOR("Texas Instruments, Inc, Nokia Corporation"); +MODULE_DESCRIPTION("TWL4030 USB transceiver driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 04fb365c453e14ff9e8a28f1c46050d920a27a4a Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Wed, 17 May 2017 15:57:45 +0300 Subject: usb: dwc3: replace %p with %pK %p will leak kernel pointers, so let's not expose the information on dmesg and instead use %pK. %pK will only show the actual addresses if explicitly enabled under /proc/sys/kernel/kptr_restrict. Cc: Acked-by: Greg Kroah-Hartman Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/dwc3-st.c | 2 +- drivers/usb/dwc3/gadget.c | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/drivers/usb/dwc3/dwc3-st.c b/drivers/usb/dwc3/dwc3-st.c index dfbf464eb88c..505676fd3ba4 100644 --- a/drivers/usb/dwc3/dwc3-st.c +++ b/drivers/usb/dwc3/dwc3-st.c @@ -230,7 +230,7 @@ static int st_dwc3_probe(struct platform_device *pdev) dwc3_data->syscfg_reg_off = res->start; - dev_vdbg(&pdev->dev, "glue-logic addr 0x%p, syscfg-reg offset 0x%x\n", + dev_vdbg(&pdev->dev, "glue-logic addr 0x%pK, syscfg-reg offset 0x%x\n", dwc3_data->glue_base, dwc3_data->syscfg_reg_off); dwc3_data->rstc_pwrdn = diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 750364eb11e1..011ac3ccfbe7 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -1224,12 +1224,9 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) return -ESHUTDOWN; } - if (WARN(req->dep != dep, "request %p belongs to '%s'\n", - &req->request, req->dep->name)) { - dev_err(dwc->dev, "%s: request %p belongs to '%s'\n", - dep->name, &req->request, req->dep->name); + if (WARN(req->dep != dep, "request %pK belongs to '%s'\n", + &req->request, req->dep->name)) return -EINVAL; - } pm_runtime_get(dwc->dev); @@ -1387,7 +1384,7 @@ static int dwc3_gadget_ep_dequeue(struct usb_ep *ep, } goto out1; } - dev_err(dwc->dev, "request %p was not queued to %s\n", + dev_err(dwc->dev, "request %pK was not queued to %s\n", request, ep->name); ret = -EINVAL; goto out0; -- cgit v1.2.3 From a7ea58f3814398ccfc154e2461f2a2011cc4a1bd Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Tue, 25 Apr 2017 14:23:41 +0300 Subject: tools: usb: testusb: update default vary for superspeed Currently, default vary will not accomodate superspeed endpoints causing unexpected babble errors in the IN direction. Let's update default 'vary' parameter so that we can maintain a "short-less" transfer as hinted at the comment. Reported-by: Ammy Yi Signed-off-by: Felipe Balbi --- tools/usb/testusb.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/tools/usb/testusb.c b/tools/usb/testusb.c index 0692d99b6d8f..2d89b5f686b1 100644 --- a/tools/usb/testusb.c +++ b/tools/usb/testusb.c @@ -387,15 +387,17 @@ int main (int argc, char **argv) /* pick defaults that works with all speeds, without short packets. * * Best per-frame data rates: - * high speed, bulk 512 * 13 * 8 = 53248 - * interrupt 1024 * 3 * 8 = 24576 - * full speed, bulk/intr 64 * 19 = 1216 - * interrupt 64 * 1 = 64 - * low speed, interrupt 8 * 1 = 8 + * super speed,bulk 1024 * 16 * 8 = 131072 + * interrupt 1024 * 3 * 8 = 24576 + * high speed, bulk 512 * 13 * 8 = 53248 + * interrupt 1024 * 3 * 8 = 24576 + * full speed, bulk/intr 64 * 19 = 1216 + * interrupt 64 * 1 = 64 + * low speed, interrupt 8 * 1 = 8 */ param.iterations = 1000; param.length = 1024; - param.vary = 512; + param.vary = 1024; param.sglen = 32; /* for easy use when hotplugging */ @@ -457,7 +459,7 @@ usage: "\t-c iterations default 1000\n" "\t-s transfer length default 1024\n" "\t-g sglen default 32\n" - "\t-v vary default 512\n", + "\t-v vary default 1024\n", argv[0]); return 1; } -- cgit v1.2.3 From bfad65ee9be2096a854e350ba6324e3e48ad5e1d Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Wed, 19 Apr 2017 14:59:27 +0300 Subject: usb: dwc3: update documentation No functional changes, just making sure we can use these for ReST docs later. Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/core.h | 44 +++++++++-------- drivers/usb/dwc3/ep0.c | 2 +- drivers/usb/dwc3/gadget.c | 121 +++++++++++++++++++++++++++++++--------------- drivers/usb/dwc3/gadget.h | 22 +++++++-- 4 files changed, 124 insertions(+), 65 deletions(-) diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 6f6294dbea9d..ea910acb4bb0 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -1,4 +1,4 @@ -/** +/* * core.h - DesignWare USB3 DRD Core Header * * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com @@ -523,7 +523,6 @@ struct dwc3_event_buffer { * @trb_pool_dma: dma address of @trb_pool * @trb_enqueue: enqueue 'pointer' into TRB array * @trb_dequeue: dequeue 'pointer' into TRB array - * @desc: usb_endpoint_descriptor pointer * @dwc: pointer to DWC controller * @saved_state: ep state saved during hibernation * @flags: endpoint flags (wedged, stalled, ...) @@ -665,7 +664,7 @@ enum dwc3_link_state { * @bpl: DW0-3 * @bph: DW4-7 * @size: DW8-B - * @trl: DWC-F + * @ctrl: DWC-F */ struct dwc3_trb { u32 bpl; @@ -675,16 +674,16 @@ struct dwc3_trb { } __packed; /** - * dwc3_hwparams - copy of HWPARAMS registers - * @hwparams0 - GHWPARAMS0 - * @hwparams1 - GHWPARAMS1 - * @hwparams2 - GHWPARAMS2 - * @hwparams3 - GHWPARAMS3 - * @hwparams4 - GHWPARAMS4 - * @hwparams5 - GHWPARAMS5 - * @hwparams6 - GHWPARAMS6 - * @hwparams7 - GHWPARAMS7 - * @hwparams8 - GHWPARAMS8 + * struct dwc3_hwparams - copy of HWPARAMS registers + * @hwparams0: GHWPARAMS0 + * @hwparams1: GHWPARAMS1 + * @hwparams2: GHWPARAMS2 + * @hwparams3: GHWPARAMS3 + * @hwparams4: GHWPARAMS4 + * @hwparams5: GHWPARAMS5 + * @hwparams6: GHWPARAMS6 + * @hwparams7: GHWPARAMS7 + * @hwparams8: GHWPARAMS8 */ struct dwc3_hwparams { u32 hwparams0; @@ -731,7 +730,8 @@ struct dwc3_hwparams { * @unaligned: true for OUT endpoints with length not divisible by maxp * @direction: IN or OUT direction flag * @mapped: true when request has been dma-mapped - * @queued: true when request has been queued to HW + * @started: request is started + * @zero: wants a ZLP */ struct dwc3_request { struct usb_request request; @@ -762,17 +762,23 @@ struct dwc3_scratchpad_array { /** * struct dwc3 - representation of our controller - * @drd_work - workqueue used for role swapping + * @drd_work: workqueue used for role swapping * @ep0_trb: trb which is used for the ctrl_req + * @bounce: address of bounce buffer + * @scratchbuf: address of scratch buffer * @setup_buf: used while precessing STD USB requests - * @ep0_trb: dma address of ep0_trb + * @ep0_trb_addr: dma address of @ep0_trb + * @bounce_addr: dma address of @bounce * @ep0_usb_req: dummy req used while handling STD USB requests * @scratch_addr: dma address of scratchbuf * @ep0_in_setup: one control transfer is completed and enter setup phase * @lock: for synchronizing * @dev: pointer to our struct device + * @sysdev: pointer to the DMA-capable device * @xhci: pointer to our xHCI child - * @event_buffer_list: a list of event buffers + * @xhci_resources: struct resources for our @xhci child + * @ev_buf: struct dwc3_event_buffer pointer + * @eps: endpoint array * @gadget: device side representation of the peripheral controller * @gadget_driver: pointer to the gadget driver * @regs: base address for our registers @@ -796,8 +802,6 @@ struct dwc3_scratchpad_array { * @usb2_generic_phy: pointer to USB2 PHY * @usb3_generic_phy: pointer to USB3 PHY * @ulpi: pointer to ulpi interface - * @dcfg: saved contents of DCFG register - * @gctl: saved contents of GCTL register * @isoch_delay: wValue from Set Isochronous Delay request; * @u2sel: parameter from Set SEL request. * @u2pel: parameter from Set SEL request. @@ -831,7 +835,6 @@ struct dwc3_scratchpad_array { * @pending_events: true when we have pending IRQs to be handled * @pullups_connected: true when Run/Stop bit is set * @setup_packet_pending: true when there's a Setup Packet in FIFO. Workaround - * @start_config_issued: true when StartConfig command has been issued * @three_stage_setup: set if we perform a three phase setup * @usb3_lpm_capable: set if hadrware supports Link Power Management * @disable_scramble_quirk: set if we enable the disable scramble quirk @@ -846,6 +849,7 @@ struct dwc3_scratchpad_array { * @dis_u2_susphy_quirk: set if we disable usb2 suspend phy * @dis_enblslpm_quirk: set if we clear enblslpm in GUSB2PHYCFG, * disabling the suspend signal to the PHY. + * @dis_rxdet_inp3_quirk: set if we disable Rx.Detect in P3 * @dis_u2_freeclk_exists_quirk : set if we clear u2_freeclk_exists * in GUSB2PHYCFG, specify that USB2 PHY doesn't * provide a free-running PHY clock. diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c index a78c78e7a8c3..8cfce8425101 100644 --- a/drivers/usb/dwc3/ep0.c +++ b/drivers/usb/dwc3/ep0.c @@ -1,4 +1,4 @@ -/** +/* * ep0.c - DesignWare USB3 DRD Controller Endpoint 0 Handling * * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 011ac3ccfbe7..e4e872c703f1 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -1,4 +1,4 @@ -/** +/* * gadget.c - DesignWare USB3 DRD Controller Gadget Framework Link * * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com @@ -36,13 +36,12 @@ #include "io.h" /** - * dwc3_gadget_set_test_mode - Enables USB2 Test Modes + * dwc3_gadget_set_test_mode - enables usb2 test modes * @dwc: pointer to our context structure * @mode: the mode to set (J, K SE0 NAK, Force Enable) * - * Caller should take care of locking. This function will - * return 0 on success or -EINVAL if wrong Test Selector - * is passed + * Caller should take care of locking. This function will return 0 on + * success or -EINVAL if wrong Test Selector is passed. */ int dwc3_gadget_set_test_mode(struct dwc3 *dwc, int mode) { @@ -69,7 +68,7 @@ int dwc3_gadget_set_test_mode(struct dwc3 *dwc, int mode) } /** - * dwc3_gadget_get_link_state - Gets current state of USB Link + * dwc3_gadget_get_link_state - gets current state of usb link * @dwc: pointer to our context structure * * Caller should take care of locking. This function will @@ -85,7 +84,7 @@ int dwc3_gadget_get_link_state(struct dwc3 *dwc) } /** - * dwc3_gadget_set_link_state - Sets USB Link to a particular State + * dwc3_gadget_set_link_state - sets usb link to a particular state * @dwc: pointer to our context structure * @state: the state to put link into * @@ -143,8 +142,8 @@ int dwc3_gadget_set_link_state(struct dwc3 *dwc, enum dwc3_link_state state) } /** - * dwc3_ep_inc_trb() - Increment a TRB index. - * @index - Pointer to the TRB index to increment. + * dwc3_ep_inc_trb - increment a trb index. + * @index: Pointer to the TRB index to increment. * * The index should never point to the link TRB. After incrementing, * if it is point to the link TRB, wrap around to the beginning. The @@ -157,16 +156,34 @@ static void dwc3_ep_inc_trb(u8 *index) *index = 0; } +/** + * dwc3_ep_inc_enq - increment endpoint's enqueue pointer + * @dep: The endpoint whose enqueue pointer we're incrementing + */ static void dwc3_ep_inc_enq(struct dwc3_ep *dep) { dwc3_ep_inc_trb(&dep->trb_enqueue); } +/** + * dwc3_ep_inc_deq - increment endpoint's dequeue pointer + * @dep: The endpoint whose enqueue pointer we're incrementing + */ static void dwc3_ep_inc_deq(struct dwc3_ep *dep) { dwc3_ep_inc_trb(&dep->trb_dequeue); } +/** + * dwc3_gadget_giveback - call struct usb_request's ->complete callback + * @dep: The endpoint to whom the request belongs to + * @req: The request we're giving back + * @status: completion code for the request + * + * Must be called with controller's lock held and interrupts disabled. This + * function will unmap @req and call its ->complete() callback to notify upper + * layers that it has completed. + */ void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req, int status) { @@ -193,6 +210,15 @@ void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req, pm_runtime_put(dwc->dev); } +/** + * dwc3_send_gadget_generic_command - issue a generic command for the controller + * @dwc: pointer to the controller context + * @cmd: the command to be issued + * @param: command parameter + * + * Caller should take care of locking. Issue @cmd with a given @param to @dwc + * and wait for its completion. + */ int dwc3_send_gadget_generic_command(struct dwc3 *dwc, unsigned cmd, u32 param) { u32 timeout = 500; @@ -225,6 +251,15 @@ int dwc3_send_gadget_generic_command(struct dwc3 *dwc, unsigned cmd, u32 param) static int __dwc3_gadget_wakeup(struct dwc3 *dwc); +/** + * dwc3_send_gadget_ep_cmd - issue an endpoint command + * @dep: the endpoint to which the command is going to be issued + * @cmd: the command to be issued + * @params: parameters to the command + * + * Caller should handle locking. This function will issue @cmd with given + * @params to @dep and wait for its completion. + */ int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned cmd, struct dwc3_gadget_ep_cmd_params *params) { @@ -422,36 +457,38 @@ static void dwc3_free_trb_pool(struct dwc3_ep *dep) static int dwc3_gadget_set_xfer_resource(struct dwc3 *dwc, struct dwc3_ep *dep); /** - * dwc3_gadget_start_config - Configure EP resources + * dwc3_gadget_start_config - configure ep resources * @dwc: pointer to our controller context structure * @dep: endpoint that is being enabled * - * The assignment of transfer resources cannot perfectly follow the - * data book due to the fact that the controller driver does not have - * all knowledge of the configuration in advance. It is given this - * information piecemeal by the composite gadget framework after every - * SET_CONFIGURATION and SET_INTERFACE. Trying to follow the databook - * programming model in this scenario can cause errors. For two - * reasons: + * Issue a %DWC3_DEPCMD_DEPSTARTCFG command to @dep. After the command's + * completion, it will set Transfer Resource for all available endpoints. * - * 1) The databook says to do DEPSTARTCFG for every SET_CONFIGURATION - * and SET_INTERFACE (8.1.5). This is incorrect in the scenario of - * multiple interfaces. + * The assignment of transfer resources cannot perfectly follow the data book + * due to the fact that the controller driver does not have all knowledge of the + * configuration in advance. It is given this information piecemeal by the + * composite gadget framework after every SET_CONFIGURATION and + * SET_INTERFACE. Trying to follow the databook programming model in this + * scenario can cause errors. For two reasons: * - * 2) The databook does not mention doing more DEPXFERCFG for new + * 1) The databook says to do %DWC3_DEPCMD_DEPSTARTCFG for every + * %USB_REQ_SET_CONFIGURATION and %USB_REQ_SET_INTERFACE (8.1.5). This is + * incorrect in the scenario of multiple interfaces. + * + * 2) The databook does not mention doing more %DWC3_DEPCMD_DEPXFERCFG for new * endpoint on alt setting (8.1.6). * * The following simplified method is used instead: * - * All hardware endpoints can be assigned a transfer resource and this - * setting will stay persistent until either a core reset or - * hibernation. So whenever we do a DEPSTARTCFG(0) we can go ahead and - * do DEPXFERCFG for every hardware endpoint as well. We are + * All hardware endpoints can be assigned a transfer resource and this setting + * will stay persistent until either a core reset or hibernation. So whenever we + * do a %DWC3_DEPCMD_DEPSTARTCFG(0) we can go ahead and do + * %DWC3_DEPCMD_DEPXFERCFG for every hardware endpoint as well. We are * guaranteed that there are as many transfer resources as endpoints. * - * This function is called for each endpoint when it is being enabled - * but is triggered only when called for EP0-out, which always happens - * first, and which should only happen in one of the above conditions. + * This function is called for each endpoint when it is being enabled but is + * triggered only when called for EP0-out, which always happens first, and which + * should only happen in one of the above conditions. */ static int dwc3_gadget_start_config(struct dwc3 *dwc, struct dwc3_ep *dep) { @@ -569,11 +606,13 @@ static int dwc3_gadget_set_xfer_resource(struct dwc3 *dwc, struct dwc3_ep *dep) } /** - * __dwc3_gadget_ep_enable - Initializes a HW endpoint + * __dwc3_gadget_ep_enable - initializes a hw endpoint * @dep: endpoint to be initialized - * @desc: USB Endpoint Descriptor + * @modify: if true, modify existing endpoint configuration + * @restore: if true, restore endpoint configuration from scratch buffer * - * Caller should take care of locking + * Caller should take care of locking. Execute all necessary commands to + * initialize a HW endpoint so it can be used by a gadget driver. */ static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep, bool modify, bool restore) @@ -685,11 +724,13 @@ static void dwc3_remove_requests(struct dwc3 *dwc, struct dwc3_ep *dep) } /** - * __dwc3_gadget_ep_disable - Disables a HW endpoint + * __dwc3_gadget_ep_disable - disables a hw endpoint * @dep: the endpoint to disable * - * This function also removes requests which are currently processed ny the - * hardware and those which are not yet scheduled. + * This function undoes what __dwc3_gadget_ep_enable did and also removes + * requests which are currently being processed by the hardware and those which + * are not yet scheduled. + * * Caller should take care of locking. */ static int __dwc3_gadget_ep_disable(struct dwc3_ep *dep) @@ -932,7 +973,7 @@ static void dwc3_prepare_one_trb(struct dwc3_ep *dep, } /** - * dwc3_ep_prev_trb() - Returns the previous TRB in the ring + * dwc3_ep_prev_trb - returns the previous TRB in the ring * @dep: The endpoint with the TRB ring * @index: The index of the current TRB in the ring * @@ -1729,8 +1770,8 @@ static irqreturn_t dwc3_interrupt(int irq, void *_dwc); static irqreturn_t dwc3_thread_interrupt(int irq, void *_dwc); /** - * dwc3_gadget_setup_nump - Calculate and initialize NUMP field of DCFG - * dwc: pointer to our context structure + * dwc3_gadget_setup_nump - calculate and initialize NUMP field of %DWC3_DCFG + * @dwc: pointer to our context structure * * The following looks like complex but it's actually very simple. In order to * calculate the number of packets we can burst at once on OUT transfers, we're @@ -1789,7 +1830,7 @@ static int __dwc3_gadget_start(struct dwc3 *dwc) reg = dwc3_readl(dwc->regs, DWC3_DCFG); reg &= ~(DWC3_DCFG_SPEED_MASK); - /** + /* * WORKAROUND: DWC3 revision < 2.20a have an issue * which would cause metastability state on Run/Stop * bit if we try to force the IP to USB2-only mode. @@ -2859,7 +2900,7 @@ static void dwc3_gadget_hibernation_interrupt(struct dwc3 *dwc, { unsigned int is_ss = evtinfo & BIT(4); - /** + /* * WORKAROUND: DWC3 revison 2.20a with hibernation support * have a known issue which can cause USB CV TD.9.23 to fail * randomly. @@ -3089,7 +3130,7 @@ out: } /** - * dwc3_gadget_init - Initializes gadget related registers + * dwc3_gadget_init - initializes gadget related registers * @dwc: pointer to our controller context structure * * Returns 0 on success otherwise negative errno. diff --git a/drivers/usb/dwc3/gadget.h b/drivers/usb/dwc3/gadget.h index e4602d0e515b..4a3227543255 100644 --- a/drivers/usb/dwc3/gadget.h +++ b/drivers/usb/dwc3/gadget.h @@ -1,4 +1,4 @@ -/** +/* * gadget.h - DesignWare USB3 DRD Gadget Header * * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com @@ -60,11 +60,25 @@ struct dwc3; #define to_dwc3_request(r) (container_of(r, struct dwc3_request, request)) +/** + * next_request - gets the next request on the given list + * @list: the request list to operate on + * + * Caller should take care of locking. This function return %NULL or the first + * request available on @list. + */ static inline struct dwc3_request *next_request(struct list_head *list) { return list_first_entry_or_null(list, struct dwc3_request, list); } +/** + * dwc3_gadget_move_started_request - move @req to the started_list + * @req: the request to be moved + * + * Caller should take care of locking. This function will move @req from its + * current list to the endpoint's started_list. + */ static inline void dwc3_gadget_move_started_request(struct dwc3_request *req) { struct dwc3_ep *dep = req->dep; @@ -87,10 +101,10 @@ int __dwc3_gadget_ep_set_halt(struct dwc3_ep *dep, int value, int protocol); /** * dwc3_gadget_ep_get_transfer_index - Gets transfer index from HW - * @dwc: DesignWare USB3 Pointer - * @number: DWC endpoint number + * @dep: dwc3 endpoint * - * Caller should take care of locking + * Caller should take care of locking. Returns the transfer resource + * index for a given endpoint. */ static inline u32 dwc3_gadget_ep_get_transfer_index(struct dwc3_ep *dep) { -- cgit v1.2.3 From 436841d53dd798ad67fc7c9bc7403613a9d9b4e0 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Thu, 20 Apr 2017 15:21:27 +0300 Subject: usb: dwc3: debugfs: slightly improve output of trb_ring Instead of printing out enqueue and dequeue pointer value as a header to the output, let's mark the TRBs in question with 'E' and 'D'. The output looks slightly easier to read. Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/debugfs.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/usb/dwc3/debugfs.c b/drivers/usb/dwc3/debugfs.c index 7be963dd8e3b..4e09be80e59f 100644 --- a/drivers/usb/dwc3/debugfs.c +++ b/drivers/usb/dwc3/debugfs.c @@ -653,16 +653,13 @@ static int dwc3_ep_trb_ring_show(struct seq_file *s, void *unused) goto out; } - seq_printf(s, "enqueue pointer %d\n", dep->trb_enqueue); - seq_printf(s, "dequeue pointer %d\n", dep->trb_dequeue); - seq_printf(s, "\n--------------------------------------------------\n\n"); seq_printf(s, "buffer_addr,size,type,ioc,isp_imi,csp,chn,lst,hwo\n"); for (i = 0; i < DWC3_TRB_NUM; i++) { struct dwc3_trb *trb = &dep->trb_pool[i]; unsigned int type = DWC3_TRBCTL_TYPE(trb->ctrl); - seq_printf(s, "%08x%08x,%d,%s,%d,%d,%d,%d,%d,%d\n", + seq_printf(s, "%08x%08x,%d,%s,%d,%d,%d,%d,%d,%d %c%c\n", trb->bph, trb->bpl, trb->size, dwc3_trb_type_string(type), !!(trb->ctrl & DWC3_TRB_CTRL_IOC), @@ -670,7 +667,9 @@ static int dwc3_ep_trb_ring_show(struct seq_file *s, void *unused) !!(trb->ctrl & DWC3_TRB_CTRL_CSP), !!(trb->ctrl & DWC3_TRB_CTRL_CHN), !!(trb->ctrl & DWC3_TRB_CTRL_LST), - !!(trb->ctrl & DWC3_TRB_CTRL_HWO)); + !!(trb->ctrl & DWC3_TRB_CTRL_HWO), + dep->trb_enqueue == i ? 'E' : ' ', + dep->trb_dequeue == i ? 'D' : ' '); } out: -- cgit v1.2.3 From dfc5e80578f21552e7d5880ea7c0556b8b625895 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Wed, 26 Apr 2017 13:44:51 +0300 Subject: usb: dwc3: gadget: slight cleanup to dwc3_process_event_entry() No functional changes, just a slight readability improvement. Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/gadget.c | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index e4e872c703f1..d2bd28dc28b6 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2972,20 +2972,12 @@ static void dwc3_process_event_entry(struct dwc3 *dwc, { trace_dwc3_event(event->raw, dwc); - /* Endpoint IRQ, handle it and return early */ - if (event->type.is_devspec == 0) { - /* depevt */ - return dwc3_endpoint_interrupt(dwc, &event->depevt); - } - - switch (event->type.type) { - case DWC3_EVENT_TYPE_DEV: + if (!event->type.is_devspec) + dwc3_endpoint_interrupt(dwc, &event->depevt); + else if (event->type.type == DWC3_EVENT_TYPE_DEV) dwc3_gadget_interrupt(dwc, &event->devt); - break; - /* REVISIT what to do with Carkit and I2C events ? */ - default: + else dev_err(dwc->dev, "UNKNOWN IRQ type %d\n", event->raw); - } } static irqreturn_t dwc3_process_event_buf(struct dwc3_event_buffer *evt) -- cgit v1.2.3 From e42f09b85f200e277d6d11d8c973f18efcc847fd Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Fri, 28 Apr 2017 12:54:52 +0300 Subject: usb: dwc3: trace: rely on __string() and __assign_str() Instead of going for a 512 byte buffer and using snprintf(), let's rely on helps __string() and __assign_str() where possible. Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/trace.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/usb/dwc3/trace.h b/drivers/usb/dwc3/trace.h index f1bd444d22a3..15909b579e69 100644 --- a/drivers/usb/dwc3/trace.h +++ b/drivers/usb/dwc3/trace.h @@ -107,7 +107,7 @@ DECLARE_EVENT_CLASS(dwc3_log_request, TP_PROTO(struct dwc3_request *req), TP_ARGS(req), TP_STRUCT__entry( - __dynamic_array(char, name, DWC3_MSG_MAX) + __string(name, req->dep->name) __field(struct dwc3_request *, req) __field(unsigned, actual) __field(unsigned, length) @@ -117,7 +117,7 @@ DECLARE_EVENT_CLASS(dwc3_log_request, __field(int, no_interrupt) ), TP_fast_assign( - snprintf(__get_str(name), DWC3_MSG_MAX, "%s", req->dep->name); + __assign_str(name, req->dep->name); __entry->req = req; __entry->actual = req->request.actual; __entry->length = req->request.length; @@ -190,7 +190,7 @@ DECLARE_EVENT_CLASS(dwc3_log_gadget_ep_cmd, struct dwc3_gadget_ep_cmd_params *params, int cmd_status), TP_ARGS(dep, cmd, params, cmd_status), TP_STRUCT__entry( - __dynamic_array(char, name, DWC3_MSG_MAX) + __string(name, dep->name) __field(unsigned int, cmd) __field(u32, param0) __field(u32, param1) @@ -198,7 +198,7 @@ DECLARE_EVENT_CLASS(dwc3_log_gadget_ep_cmd, __field(int, cmd_status) ), TP_fast_assign( - snprintf(__get_str(name), DWC3_MSG_MAX, "%s", dep->name); + __assign_str(name, dep->name); __entry->cmd = cmd; __entry->param0 = params->param0; __entry->param1 = params->param1; @@ -223,7 +223,7 @@ DECLARE_EVENT_CLASS(dwc3_log_trb, TP_PROTO(struct dwc3_ep *dep, struct dwc3_trb *trb), TP_ARGS(dep, trb), TP_STRUCT__entry( - __dynamic_array(char, name, DWC3_MSG_MAX) + __string(name, dep->name) __field(struct dwc3_trb *, trb) __field(u32, allocated) __field(u32, queued) @@ -234,7 +234,7 @@ DECLARE_EVENT_CLASS(dwc3_log_trb, __field(u32, type) ), TP_fast_assign( - snprintf(__get_str(name), DWC3_MSG_MAX, "%s", dep->name); + __assign_str(name, dep->name); __entry->trb = trb; __entry->allocated = dep->allocated_requests; __entry->queued = dep->queued_requests; @@ -291,7 +291,7 @@ DECLARE_EVENT_CLASS(dwc3_log_ep, TP_PROTO(struct dwc3_ep *dep), TP_ARGS(dep), TP_STRUCT__entry( - __dynamic_array(char, name, DWC3_MSG_MAX) + __string(name, dep->name) __field(unsigned, maxpacket) __field(unsigned, maxpacket_limit) __field(unsigned, max_streams) @@ -302,7 +302,7 @@ DECLARE_EVENT_CLASS(dwc3_log_ep, __field(u8, trb_dequeue) ), TP_fast_assign( - snprintf(__get_str(name), DWC3_MSG_MAX, "%s", dep->name); + __assign_str(name, dep->name); __entry->maxpacket = dep->endpoint.maxpacket; __entry->maxpacket_limit = dep->endpoint.maxpacket_limit; __entry->max_streams = dep->endpoint.max_streams; -- cgit v1.2.3 From 3587f36a125fd18596e8068ec70cbaae84bb624c Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Fri, 28 Apr 2017 11:28:35 +0300 Subject: usb: dwc3: debug: remove static char buffer from dwc3_decode_event() Instead, we can require caller to pass a buffer for the function to use. This cleans things quite a bit. Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/debug.h | 13 ++++++------- drivers/usb/dwc3/trace.h | 4 +++- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/drivers/usb/dwc3/debug.h b/drivers/usb/dwc3/debug.h index cb2d8d3f7f3d..1025713ebdd1 100644 --- a/drivers/usb/dwc3/debug.h +++ b/drivers/usb/dwc3/debug.h @@ -173,9 +173,8 @@ static inline const char *dwc3_ep0_state_string(enum dwc3_ep0_state state) * @event: the event code */ static inline const char * -dwc3_gadget_event_string(const struct dwc3_event_devt *event) +dwc3_gadget_event_string(char *str, const struct dwc3_event_devt *event) { - static char str[256]; enum dwc3_link_state state = event->event_info & DWC3_LINK_STATE_MASK; switch (event->type) { @@ -228,10 +227,10 @@ dwc3_gadget_event_string(const struct dwc3_event_devt *event) * @event: then event code */ static inline const char * -dwc3_ep_event_string(const struct dwc3_event_depevt *event, u32 ep0state) +dwc3_ep_event_string(char *str, const struct dwc3_event_depevt *event, + u32 ep0state) { u8 epnum = event->endpoint_number; - static char str[256]; size_t len; int status; int ret; @@ -332,14 +331,14 @@ static inline const char *dwc3_gadget_event_type_string(u8 event) } } -static inline const char *dwc3_decode_event(u32 event, u32 ep0state) +static inline const char *dwc3_decode_event(char *str, u32 event, u32 ep0state) { const union dwc3_event evt = (union dwc3_event) event; if (evt.type.is_devspec) - return dwc3_gadget_event_string(&evt.devt); + return dwc3_gadget_event_string(str, &evt.devt); else - return dwc3_ep_event_string(&evt.depevt, ep0state); + return dwc3_ep_event_string(str, &evt.depevt, ep0state); } static inline const char *dwc3_ep_cmd_status_string(int status) diff --git a/drivers/usb/dwc3/trace.h b/drivers/usb/dwc3/trace.h index 15909b579e69..af093b4e5dc5 100644 --- a/drivers/usb/dwc3/trace.h +++ b/drivers/usb/dwc3/trace.h @@ -60,13 +60,15 @@ DECLARE_EVENT_CLASS(dwc3_log_event, TP_STRUCT__entry( __field(u32, event) __field(u32, ep0state) + __dynamic_array(char, str, DWC3_MSG_MAX) ), TP_fast_assign( __entry->event = event; __entry->ep0state = dwc->ep0state; ), TP_printk("event (%08x): %s", __entry->event, - dwc3_decode_event(__entry->event, __entry->ep0state)) + dwc3_decode_event(__get_str(str), __entry->event, + __entry->ep0state)) ); DEFINE_EVENT(dwc3_log_event, dwc3_event, -- cgit v1.2.3 From af32423a2d866a3263552b000a70c11cffbd2b55 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Wed, 26 Apr 2017 16:09:09 +0300 Subject: usb: dwc3: trace: decode ctrl request Instead of *always* dumping raw ctrl bytes, let's decode standard requests which will make the lives of those debugging DWC3 quite a bit easier. Output will now look like so: irq/34-dwc3-1594 [000] d..1 107.573081: dwc3_ctrl_req: Get Device Descriptor(Index = 0, Length = 18) irq/34-dwc3-1594 [000] d..1 107.573694: dwc3_ctrl_req: Set Address(Addr = 01) irq/34-dwc3-1594 [000] d..1 107.588319: dwc3_ctrl_req: Get Device Descriptor(Index = 0, Length = 18) irq/34-dwc3-1594 [000] d..1 107.588816: dwc3_ctrl_req: Get Configuration Descriptor(Index = 0, Length = 9) irq/34-dwc3-1594 [000] d..1 107.589191: dwc3_ctrl_req: Set Configuration(Config = 3) irq/34-dwc3-1594 [000] d..1 107.589846: dwc3_ctrl_req: Get BOS Descriptor(Index = 0, Length = 5) irq/34-dwc3-1594 [000] d..1 107.590146: dwc3_ctrl_req: Get BOS Descriptor(Index = 0, Length = 22) irq/34-dwc3-1594 [000] d..1 107.590546: dwc3_ctrl_req: Get Configuration Descriptor(Index = 0, Length = 9) irq/34-dwc3-1594 [000] d..1 107.590840: dwc3_ctrl_req: Get Configuration Descriptor(Index = 0, Length = 69) irq/34-dwc3-1594 [000] d..1 107.591138: dwc3_ctrl_req: Get Configuration Descriptor(Index = 1, Length = 9) irq/34-dwc3-1594 [000] d..1 107.591541: dwc3_ctrl_req: Get Configuration Descriptor(Index = 1, Length = 32) irq/34-dwc3-1594 [000] d..1 107.591834: dwc3_ctrl_req: Get Device Descriptor(Index = 0, Length = 18) irq/34-dwc3-1594 [000] d..1 114.701005: dwc3_ctrl_req: Get Device Descriptor(Index = 0, Length = 18) irq/34-dwc3-1594 [000] d..1 114.721080: dwc3_ctrl_req: Get Device Descriptor(Index = 0, Length = 18) irq/34-dwc3-1594 [000] d..1 114.722709: dwc3_ctrl_req: Get Device Descriptor(Index = 0, Length = 18) irq/34-dwc3-1594 [000] d..1 114.728979: dwc3_ctrl_req: Get Device Descriptor(Index = 0, Length = 18) irq/34-dwc3-1594 [000] d..1 114.730544: dwc3_ctrl_req: Get Device Qualifier Descriptor(Index = 0, Length = 10) irq/34-dwc3-1594 [000] d..1 115.776018: dwc3_ctrl_req: Get Configuration Descriptor(Index = 0, Length = 9) irq/34-dwc3-1594 [000] d..1 115.776760: dwc3_ctrl_req: Set Configuration(Config = 0) irq/34-dwc3-1594 [000] d..1 115.777676: dwc3_ctrl_req: Get Configuration(Length = 1) irq/34-dwc3-1594 [000] d..1 115.924797: dwc3_ctrl_req: Get Device Descriptor(Index = 0, Length = 18) irq/34-dwc3-1594 [000] d..1 115.929025: dwc3_ctrl_req: Get String Descriptor(Index = 0, Length = 500) irq/34-dwc3-1594 [000] d..1 115.929566: dwc3_ctrl_req: Get String Descriptor(Index = 1, Length = 500) irq/34-dwc3-1594 [000] d..1 115.930911: dwc3_ctrl_req: Get String Descriptor(Index = 0, Length = 500) irq/34-dwc3-1594 [000] d..1 115.931528: dwc3_ctrl_req: Get String Descriptor(Index = 2, Length = 500) irq/34-dwc3-1594 [000] d..1 115.932950: dwc3_ctrl_req: Get String Descriptor(Index = 0, Length = 500) irq/34-dwc3-1594 [000] d..1 115.933533: dwc3_ctrl_req: Get String Descriptor(Index = 3, Length = 500) Note that Class and Vendor requests won't be decoded for obvious reasons. Those will be printed as a raw sequence of bytes. This patch has been tested against a normal host (both Linux and Windows) and USB30CV Chapter 9 tests. Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/debug.h | 234 +++++++++++++++++++++++++++++++++++++++++++++++ drivers/usb/dwc3/trace.h | 8 +- 2 files changed, 238 insertions(+), 4 deletions(-) diff --git a/drivers/usb/dwc3/debug.h b/drivers/usb/dwc3/debug.h index 1025713ebdd1..5e9c070ec874 100644 --- a/drivers/usb/dwc3/debug.h +++ b/drivers/usb/dwc3/debug.h @@ -222,6 +222,240 @@ dwc3_gadget_event_string(char *str, const struct dwc3_event_devt *event) return str; } +static inline void dwc3_decode_get_status(__u8 t, __u16 i, __u16 l, char *str) +{ + switch (t & USB_RECIP_MASK) { + case USB_RECIP_INTERFACE: + sprintf(str, "Get Interface Status(Intf = %d, Length = %d)", + i, l); + break; + case USB_RECIP_ENDPOINT: + sprintf(str, "Get Endpoint Status(ep%d%s)", + i & ~USB_DIR_IN, + i & USB_DIR_IN ? "in" : "out"); + break; + } +} + +static inline void dwc3_decode_set_clear_feature(__u8 t, __u8 b, __u16 v, + __u16 i, char *str) +{ + switch (t & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + sprintf(str, "%s Device Feature(%s%s)", + b == USB_REQ_CLEAR_FEATURE ? "Clear" : "Set", + ({char *s; + switch (v) { + case USB_DEVICE_SELF_POWERED: + s = "Self Powered"; + break; + case USB_DEVICE_REMOTE_WAKEUP: + s = "Remote Wakeup"; + break; + case USB_DEVICE_TEST_MODE: + s = "Test Mode"; + break; + default: + s = "UNKNOWN"; + } s; }), + v == USB_DEVICE_TEST_MODE ? + ({ char *s; + switch (i) { + case TEST_J: + s = ": TEST_J"; + break; + case TEST_K: + s = ": TEST_K"; + break; + case TEST_SE0_NAK: + s = ": TEST_SE0_NAK"; + break; + case TEST_PACKET: + s = ": TEST_PACKET"; + break; + case TEST_FORCE_EN: + s = ": TEST_FORCE_EN"; + break; + default: + s = ": UNKNOWN"; + } s; }) : ""); + break; + case USB_RECIP_INTERFACE: + sprintf(str, "%s Interface Feature(%s)", + b == USB_REQ_CLEAR_FEATURE ? "Clear" : "Set", + v == USB_INTRF_FUNC_SUSPEND ? + "Function Suspend" : "UNKNOWN"); + break; + case USB_RECIP_ENDPOINT: + sprintf(str, "%s Endpoint Feature(%s ep%d%s)", + b == USB_REQ_CLEAR_FEATURE ? "Clear" : "Set", + v == USB_ENDPOINT_HALT ? "Halt" : "UNKNOWN", + i & ~USB_DIR_IN, + i & USB_DIR_IN ? "in" : "out"); + break; + } +} + +static inline void dwc3_decode_set_address(__u16 v, char *str) +{ + sprintf(str, "Set Address(Addr = %02x)", v); +} + +static inline void dwc3_decode_get_set_descriptor(__u8 t, __u8 b, __u16 v, + __u16 i, __u16 l, char *str) +{ + sprintf(str, "%s %s Descriptor(Index = %d, Length = %d)", + b == USB_REQ_GET_DESCRIPTOR ? "Get" : "Set", + ({ char *s; + switch (v >> 8) { + case USB_DT_DEVICE: + s = "Device"; + break; + case USB_DT_CONFIG: + s = "Configuration"; + break; + case USB_DT_STRING: + s = "String"; + break; + case USB_DT_INTERFACE: + s = "Interface"; + break; + case USB_DT_ENDPOINT: + s = "Endpoint"; + break; + case USB_DT_DEVICE_QUALIFIER: + s = "Device Qualifier"; + break; + case USB_DT_OTHER_SPEED_CONFIG: + s = "Other Speed Config"; + break; + case USB_DT_INTERFACE_POWER: + s = "Interface Power"; + break; + case USB_DT_OTG: + s = "OTG"; + break; + case USB_DT_DEBUG: + s = "Debug"; + break; + case USB_DT_INTERFACE_ASSOCIATION: + s = "Interface Association"; + break; + case USB_DT_BOS: + s = "BOS"; + break; + case USB_DT_DEVICE_CAPABILITY: + s = "Device Capability"; + break; + case USB_DT_PIPE_USAGE: + s = "Pipe Usage"; + break; + case USB_DT_SS_ENDPOINT_COMP: + s = "SS Endpoint Companion"; + break; + case USB_DT_SSP_ISOC_ENDPOINT_COMP: + s = "SSP Isochronous Endpoint Companion"; + break; + default: + s = "UNKNOWN"; + break; + } s; }), v & 0xff, l); +} + + +static inline void dwc3_decode_get_configuration(__u16 l, char *str) +{ + sprintf(str, "Get Configuration(Length = %d)", l); +} + +static inline void dwc3_decode_set_configuration(__u8 v, char *str) +{ + sprintf(str, "Set Configuration(Config = %d)", v); +} + +static inline void dwc3_decode_get_intf(__u16 i, __u16 l, char *str) +{ + sprintf(str, "Get Interface(Intf = %d, Length = %d)", i, l); +} + +static inline void dwc3_decode_set_intf(__u8 v, __u16 i, char *str) +{ + sprintf(str, "Set Interface(Intf = %d, Alt.Setting = %d)", i, v); +} + +static inline void dwc3_decode_synch_frame(__u16 i, __u16 l, char *str) +{ + sprintf(str, "Synch Frame(Endpoint = %d, Length = %d)", i, l); +} + +static inline void dwc3_decode_set_sel(__u16 l, char *str) +{ + sprintf(str, "Set SEL(Length = %d)", l); +} + +static inline void dwc3_decode_set_isoch_delay(__u8 v, char *str) +{ + sprintf(str, "Set Isochronous Delay(Delay = %d ns)", v); +} + +/** + * dwc3_decode_ctrl - returns a string represetion of ctrl request + */ +static inline const char *dwc3_decode_ctrl(char *str, __u8 bRequestType, + __u8 bRequest, __u16 wValue, __u16 wIndex, __u16 wLength) +{ + switch (bRequest) { + case USB_REQ_GET_STATUS: + dwc3_decode_get_status(bRequestType, wIndex, wLength, str); + break; + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + dwc3_decode_set_clear_feature(bRequestType, bRequest, wValue, + wIndex, str); + break; + case USB_REQ_SET_ADDRESS: + dwc3_decode_set_address(wValue, str); + break; + case USB_REQ_GET_DESCRIPTOR: + case USB_REQ_SET_DESCRIPTOR: + dwc3_decode_get_set_descriptor(bRequestType, bRequest, wValue, + wIndex, wLength, str); + break; + case USB_REQ_GET_CONFIGURATION: + dwc3_decode_get_configuration(wLength, str); + break; + case USB_REQ_SET_CONFIGURATION: + dwc3_decode_set_configuration(wValue, str); + break; + case USB_REQ_GET_INTERFACE: + dwc3_decode_get_intf(wIndex, wLength, str); + break; + case USB_REQ_SET_INTERFACE: + dwc3_decode_set_intf(wValue, wIndex, str); + break; + case USB_REQ_SYNCH_FRAME: + dwc3_decode_synch_frame(wIndex, wLength, str); + break; + case USB_REQ_SET_SEL: + dwc3_decode_set_sel(wLength, str); + break; + case USB_REQ_SET_ISOCH_DELAY: + dwc3_decode_set_isoch_delay(wValue, str); + break; + default: + sprintf(str, "%02x %02x %02x %02x %02x %02x %02x %02x", + bRequestType, bRequest, + cpu_to_le16(wValue) & 0xff, + cpu_to_le16(wValue) >> 8, + cpu_to_le16(wIndex) & 0xff, + cpu_to_le16(wIndex) >> 8, + cpu_to_le16(wLength) & 0xff, + cpu_to_le16(wLength) >> 8); + } + + return str; +} + /** * dwc3_ep_event_string - returns event name * @event: then event code diff --git a/drivers/usb/dwc3/trace.h b/drivers/usb/dwc3/trace.h index af093b4e5dc5..6504b116da04 100644 --- a/drivers/usb/dwc3/trace.h +++ b/drivers/usb/dwc3/trace.h @@ -85,6 +85,7 @@ DECLARE_EVENT_CLASS(dwc3_log_ctrl, __field(__u16, wValue) __field(__u16, wIndex) __field(__u16, wLength) + __dynamic_array(char, str, DWC3_MSG_MAX) ), TP_fast_assign( __entry->bRequestType = ctrl->bRequestType; @@ -93,10 +94,9 @@ DECLARE_EVENT_CLASS(dwc3_log_ctrl, __entry->wIndex = le16_to_cpu(ctrl->wIndex); __entry->wLength = le16_to_cpu(ctrl->wLength); ), - TP_printk("bRequestType %02x bRequest %02x wValue %04x wIndex %04x wLength %d", - __entry->bRequestType, __entry->bRequest, - __entry->wValue, __entry->wIndex, - __entry->wLength + TP_printk("%s", dwc3_decode_ctrl(__get_str(str), __entry->bRequestType, + __entry->bRequest, __entry->wValue, + __entry->wIndex, __entry->wLength) ) ); -- cgit v1.2.3 From 8b4cfe0bb7a4117087e2431d668ab79faabe3faa Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Wed, 19 Apr 2017 15:02:20 +0300 Subject: usb: dwc3: add ReST documentation Document a few details about DWC3 in order to help people report bugs and debug DWC3. Signed-off-by: Felipe Balbi --- Documentation/driver-api/usb/dwc3.rst | 712 +++++++++++++++++++++++++++++++++ Documentation/driver-api/usb/index.rst | 1 + 2 files changed, 713 insertions(+) create mode 100644 Documentation/driver-api/usb/dwc3.rst diff --git a/Documentation/driver-api/usb/dwc3.rst b/Documentation/driver-api/usb/dwc3.rst new file mode 100644 index 000000000000..c3dc84a50ce5 --- /dev/null +++ b/Documentation/driver-api/usb/dwc3.rst @@ -0,0 +1,712 @@ +=============================================================== +Synopsys DesignWare Core SuperSpeed USB 3.0 Controller +=============================================================== + +:Author: Felipe Balbi +:Date: April 2017 + +Introduction +============ + +The *Synopsys DesignWare Core SuperSpeed USB 3.0 Controller* +(hereinafter referred to as *DWC3*) is a USB SuperSpeed compliant +controller which can be configured in one of 4 ways: + + 1. Peripheral-only configuration + 2. Host-only configuration + 3. Dual-Role configuration + 4. Hub configuration + +Linux currently supports several versions of this controller. In all +likelyhood, the version in your SoC is already supported. At the time +of this writing, known tested versions range from 2.02a to 3.10a. As a +rule of thumb, anything above 2.02a should work reliably well. + +Currently, we have many known users for this driver. In alphabetical +order: + + 1. Cavium + 2. Intel Corporation + 3. Qualcomm + 4. Rockchip + 5. ST + 6. Samsung + 7. Texas Instruments + 8. Xilinx + +Summary of Features +====================== + +For details about features supported by your version of DWC3, consult +your IP team and/or *Synopsys DesignWare Core SuperSpeed USB 3.0 +Controller Databook*. Following is a list of features supported by the +driver at the time of this writing: + + 1. Up to 16 bidirectional endpoints (including the control + pipe - ep0) + 2. Flexible endpoint configuration + 3. Simultaneous IN and OUT transfer support + 4. Scatter-list support + 5. Up to 256 TRBs [#trb]_ per endpoint + 6. Support for all transfer types (*Control*, *Bulk*, + *Interrupt*, and *Isochronous*) + 7. SuperSpeed Bulk Streams + 8. Link Power Management + 9. Trace Events for debugging + 10. DebugFS [#debugfs]_ interface + +These features have all been exercised with many of the **in-tree** +gadget drivers. We have verified both *ConfigFS* [#configfs]_ and +legacy gadget drivers. + +Driver Design +============== + +The DWC3 driver sits on the *drivers/usb/dwc3/* directory. All files +related to this driver are in this one directory. This makes it easy +for new-comers to read the code and understand how it behaves. + +Because of DWC3's configuration flexibility, the driver is a little +complex in some places but it should be rather straightforward to +understand. + +The biggest part of the driver refers to the Gadget API. + +Known Limitations +=================== + +Like any other HW, DWC3 has its own set of limitations. To avoid +constant questions about such problems, we decided to document them +here and have a single location to where we could point users. + +OUT Transfer Size Requirements +--------------------------------- + +According to Synopsys Databook, all OUT transfer TRBs [#trb]_ must +have their *size* field set to a value which is integer divisible by +the endpoint's *wMaxPacketSize*. This means that *e.g.* in order to +receive a Mass Storage *CBW* [#cbw]_, req->length must either be set +to a value that's divisible by *wMaxPacketSize* (1024 on SuperSpeed, +512 on HighSpeed, etc), or DWC3 driver must add a Chained TRB pointing +to a throw-away buffer for the remaining length. Without this, OUT +transfers will **NOT** start. + +Note that as of this writing, this won't be a problem because DWC3 is +fully capable of appending a chained TRB for the remaining length and +completely hide this detail from the gadget driver. It's still worth +mentioning because this seems to be the largest source of queries +about DWC3 and *non-working transfers*. + +TRB Ring Size Limitation +------------------------- + +We, currently, have a hard limit of 256 TRBs [#trb]_ per endpoint, +with the last TRB being a Link TRB [#link_trb]_ pointing back to the +first. This limit is arbitrary but it has the benefit of adding up to +exactly 4096 bytes, or 1 Page. + +DWC3 driver will try its best to cope with more than 255 requests and, +for the most part, it should work normally. However this is not +something that has been exercised very frequently. If you experience +any problems, see section **Reporting Bugs** below. + +Reporting Bugs +================ + +Whenever you encounter a problem with DWC3, first and foremost you +should make sure that: + + 1. You're running latest tag from `Linus' tree`_ + 2. You can reproduce the error without any out-of-tree changes + to DWC3 + 3. You have checked that it's not a fault on the host machine + +After all these are verified, then here's how to capture enough +information so we can be of any help to you. + +Required Information +--------------------- + +DWC3 relies exclusively on Trace Events for debugging. Everything is +exposed there, with some extra bits being exposed to DebugFS +[#debugfs]_. + +In order to capture DWC3's Trace Events you should run the following +commands **before** plugging the USB cable to a host machine: + +.. code-block:: sh + + # mkdir -p /d + # mkdir -p /t + # mount -t debugfs none /d + # mount -t tracefs none /t + # echo 81920 > /t/buffer_size_kb + # echo 1 > /t/events/dwc3/enable + +After this is done, you can connect your USB cable and reproduce the +problem. As soon as the fault is reproduced, make a copy of files +``trace`` and ``regdump``, like so: + +.. code-block:: sh + + # cp /t/trace /root/trace.txt + # cat /d/*dwc3*/regdump > /root/regdump.txt + +Make sure to compress ``trace.txt`` and ``regdump.txt`` in a tarball +and email it to `me`_ with `linux-usb`_ in Cc. If you want to be extra +sure that I'll help you, write your subject line in the following +format: + + **[BUG REPORT] usb: dwc3: Bug while doing XYZ** + +On the email body, make sure to detail what you doing, which gadget +driver you were using, how to reproduce the problem, what SoC you're +using, which OS (and its version) was running on the Host machine. + +With all this information, we should be able to understand what's +going on and be helpful to you. + +Debugging +=========== + +First and foremost a disclaimer:: + + DISCLAIMER: The information available on DebugFS and/or TraceFS can + change at any time at any Major Linux Kernel Release. If writing + scripts, do **NOT** assume information to be available in the + current format. + +With that out of the way, let's carry on. + +If you're willing to debug your own problem, you deserve a round of +applause :-) + +Anyway, there isn't much to say here other than Trace Events will be +really helpful in figuring out issues with DWC3. Also, access to +Synopsys Databook will be **really** valuable in this case. + +A USB Sniffer can be helpful at times but it's not entirely required, +there's a lot that can be understood without looking at the wire. + +Feel free to email `me`_ and Cc `linux-usb`_ if you need any help. + +``DebugFS`` +------------- + +``DebugFS`` is very good for gathering snapshots of what's going on +with DWC3 and/or any endpoint. + +On DWC3's ``DebugFS`` directory, you will find the following files and +directories: + +``ep[0..15]{in,out}/`` +``link_state`` +``regdump`` +``testmode`` + +``link_state`` +`````````````` + +When read, ``link_state`` will print out one of ``U0``, ``U1``, +``U2``, ``U3``, ``SS.Disabled``, ``RX.Detect``, ``SS.Inactive``, +``Polling``, ``Recovery``, ``Hot Reset``, ``Compliance``, +``Loopback``, ``Reset``, ``Resume`` or ``UNKNOWN link state``. + +This file can also be written to in order to force link to one of the +states above. + +``regdump`` +````````````` + +File name is self-explanatory. When read, ``regdump`` will print out a +register dump of DWC3. Note that this file can be grepped to find the +information you want. + +``testmode`` +`````````````` + +When read, ``testmode`` will print out a name of one of the specified +USB 2.0 Testmodes (``test_j``, ``test_k``, ``test_se0_nak``, +``test_packet``, ``test_force_enable``) or the string ``no test`` in +case no tests are currently being executed. + +In order to start any of these test modes, the same strings can be +written to the file and DWC3 will enter the requested test mode. + + +``ep[0..15]{in,out}`` +`````````````````````` + +For each endpoint we expose one directory following the naming +convention ``ep$num$dir`` *(ep0in, ep0out, ep1in, ...)*. Inside each +of these directories you will find the following files: + +``descriptor_fetch_queue`` +``event_queue`` +``rx_fifo_queue`` +``rx_info_queue`` +``rx_request_queue`` +``transfer_type`` +``trb_ring`` +``tx_fifo_queue`` +``tx_request_queue`` + +With access to Synopsys Databook, you can decode the information on +them. + +``transfer_type`` +~~~~~~~~~~~~~~~~~~ + +When read, ``transfer_type`` will print out one of ``control``, +``bulk``, ``interrupt`` or ``isochronous`` depending on what the +endpoint descriptor says. If the endpoint hasn't been enabled yet, it +will print ``--``. + +``trb_ring`` +~~~~~~~~~~~~~ + +When read, ``trb_ring`` will print out details about all TRBs on the +ring. It will also tell you where our enqueue and dequeue pointers are +located in the ring: + +.. code-block:: sh + + buffer_addr,size,type,ioc,isp_imi,csp,chn,lst,hwo + 000000002c754000,481,normal,1,0,1,0,0,0 + 000000002c75c000,481,normal,1,0,1,0,0,0 + 000000002c780000,481,normal,1,0,1,0,0,0 + 000000002c788000,481,normal,1,0,1,0,0,0 + 000000002c78c000,481,normal,1,0,1,0,0,0 + 000000002c754000,481,normal,1,0,1,0,0,0 + 000000002c75c000,481,normal,1,0,1,0,0,0 + 000000002c784000,481,normal,1,0,1,0,0,0 + 000000002c788000,481,normal,1,0,1,0,0,0 + 000000002c78c000,481,normal,1,0,1,0,0,0 + 000000002c790000,481,normal,1,0,1,0,0,0 + 000000002c758000,481,normal,1,0,1,0,0,0 + 000000002c780000,481,normal,1,0,1,0,0,0 + 000000002c788000,481,normal,1,0,1,0,0,0 + 000000002c790000,481,normal,1,0,1,0,0,0 + 000000002c758000,481,normal,1,0,1,0,0,0 + 000000002c780000,481,normal,1,0,1,0,0,0 + 000000002c784000,481,normal,1,0,1,0,0,0 + 000000002c788000,481,normal,1,0,1,0,0,0 + 000000002c78c000,481,normal,1,0,1,0,0,0 + 000000002c754000,481,normal,1,0,1,0,0,0 + 000000002c758000,481,normal,1,0,1,0,0,0 + 000000002c780000,481,normal,1,0,1,0,0,0 + 000000002c784000,481,normal,1,0,1,0,0,0 + 000000002c78c000,481,normal,1,0,1,0,0,0 + 000000002c790000,481,normal,1,0,1,0,0,0 + 000000002c758000,481,normal,1,0,1,0,0,0 + 000000002c780000,481,normal,1,0,1,0,0,0 + 000000002c788000,481,normal,1,0,1,0,0,0 + 000000002c790000,481,normal,1,0,1,0,0,0 + 000000002c758000,481,normal,1,0,1,0,0,0 + 000000002c780000,481,normal,1,0,1,0,0,0 + 000000002c788000,481,normal,1,0,1,0,0,0 + 000000002c790000,481,normal,1,0,1,0,0,0 + 000000002c758000,481,normal,1,0,1,0,0,0 + 000000002c780000,481,normal,1,0,1,0,0,0 + 000000002c788000,481,normal,1,0,1,0,0,0 + 000000002c790000,481,normal,1,0,1,0,0,0 + 000000002c758000,481,normal,1,0,1,0,0,0 + 000000002c780000,481,normal,1,0,1,0,0,0 + 000000002c788000,481,normal,1,0,1,0,0,0 + 000000002c790000,481,normal,1,0,1,0,0,0 + 000000002c758000,481,normal,1,0,1,0,0,0 + 000000002c780000,481,normal,1,0,1,0,0,0 + 000000002c788000,481,normal,1,0,1,0,0,0 + 000000002c790000,481,normal,1,0,1,0,0,0 + 000000002c758000,481,normal,1,0,1,0,0,0 + 000000002c780000,481,normal,1,0,1,0,0,0 + 000000002c788000,481,normal,1,0,1,0,0,0 + 000000002c790000,481,normal,1,0,1,0,0,0 + 000000002c758000,481,normal,1,0,1,0,0,0 + 000000002c780000,481,normal,1,0,1,0,0,0 + 000000002c788000,481,normal,1,0,1,0,0,0 + 000000002c790000,481,normal,1,0,1,0,0,0 + 000000002c758000,481,normal,1,0,1,0,0,0 + 000000002c780000,481,normal,1,0,1,0,0,0 + 000000002c78c000,481,normal,1,0,1,0,0,0 + 000000002c784000,481,normal,1,0,1,0,0,0 + 000000002c788000,481,normal,1,0,1,0,0,0 + 000000002c78c000,481,normal,1,0,1,0,0,0 + 000000002c754000,481,normal,1,0,1,0,0,0 + 000000002c758000,481,normal,1,0,1,0,0,0 + 000000002c780000,481,normal,1,0,1,0,0,0 + 000000002c788000,481,normal,1,0,1,0,0,0 + 000000002c790000,481,normal,1,0,1,0,0,0 + 000000002c758000,481,normal,1,0,1,0,0,0 + 000000002c780000,481,normal,1,0,1,0,0,0 + 000000002c758000,481,normal,1,0,1,0,0,0 + 000000002c780000,481,normal,1,0,1,0,0,0 + 000000002c78c000,481,normal,1,0,1,0,0,0 + 000000002c75c000,481,normal,1,0,1,0,0,0 + 000000002c78c000,481,normal,1,0,1,0,0,0 + 000000002c780000,481,normal,1,0,1,0,0,0 + 000000002c754000,481,normal,1,0,1,0,0,0 + 000000002c788000,481,normal,1,0,1,0,0,0 + 000000002c754000,481,normal,1,0,1,0,0,0 + 000000002c780000,481,normal,1,0,1,0,0,0 + 000000002c788000,481,normal,1,0,1,0,0,0 + 000000002c78c000,481,normal,1,0,1,0,0,0 + 000000002c790000,481,normal,1,0,1,0,0,0 + 000000002c754000,481,normal,1,0,1,0,0,0 + 000000002c758000,481,normal,1,0,1,0,0,0 + 000000002c75c000,481,normal,1,0,1,0,0,0 + 000000002c780000,481,normal,1,0,1,0,0,0 + 000000002c784000,481,normal,1,0,1,0,0,0 + 000000002c788000,481,normal,1,0,1,0,0,0 + 000000002c78c000,481,normal,1,0,1,0,0,0 + 000000002c790000,481,normal,1,0,1,0,0,0 + 000000002c754000,481,normal,1,0,1,0,0,0 + 000000002c758000,481,normal,1,0,1,0,0,0 + 000000002c75c000,512,normal,1,0,1,0,0,1 D + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 E + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 0000000000000000,0,UNKNOWN,0,0,0,0,0,0 + 00000000381ab000,0,link,0,0,0,0,0,1 + + +Trace Events +------------- + +DWC3 also provides several trace events which help us gathering +information about the behavior of the driver during runtime. + +In order to use these events, you must enable ``CONFIG_FTRACE`` in +your kernel config. + +For details about how enable DWC3 events, see section **Reporting +Bugs**. + +The following subsections will give details about each Event Class and +each Event defined by DWC3. + +MMIO +``````` + +It is sometimes useful to look at every MMIO access when looking for +bugs. Because of that, DWC3 offers two Trace Events (one for +dwc3_readl() and one for dwc3_writel()). ``TP_printk`` follows:: + + TP_printk("addr %p value %08x", __entry->base + __entry->offset, + __entry->value) + +Interrupt Events +```````````````` + +Every IRQ event can be logged and decoded into a human readable +string. Because every event will be different, we don't give an +example other than the ``TP_printk`` format used:: + + TP_printk("event (%08x): %s", __entry->event, + dwc3_decode_event(__entry->event, __entry->ep0state)) + +Control Request +````````````````` + +Every USB Control Request can be logged to the trace buffer. The +output format is:: + + TP_printk("%s", dwc3_decode_ctrl(__entry->bRequestType, + __entry->bRequest, __entry->wValue, + __entry->wIndex, __entry->wLength) + ) + +Note that Standard Control Requests will be decoded into +human-readable strings with their respective arguments. Class and +Vendor requests will be printed out a sequence of 8 bytes in hex +format. + +Lifetime of a ``struct usb_request`` +``````````````````````````````````````` + +The entire lifetime of a ``struct usb_request`` can be tracked on the +trace buffer. We have one event for each of allocation, free, +queueing, dequeueing, and giveback. Output format is:: + + TP_printk("%s: req %p length %u/%u %s%s%s ==> %d", + __get_str(name), __entry->req, __entry->actual, __entry->length, + __entry->zero ? "Z" : "z", + __entry->short_not_ok ? "S" : "s", + __entry->no_interrupt ? "i" : "I", + __entry->status + ) + +Generic Commands +```````````````````` + +We can log and decode every Generic Command with its completion +code. Format is:: + + TP_printk("cmd '%s' [%x] param %08x --> status: %s", + dwc3_gadget_generic_cmd_string(__entry->cmd), + __entry->cmd, __entry->param, + dwc3_gadget_generic_cmd_status_string(__entry->status) + ) + +Endpoint Commands +```````````````````` + +Endpoints commands can also be logged together with completion +code. Format is:: + + TP_printk("%s: cmd '%s' [%d] params %08x %08x %08x --> status: %s", + __get_str(name), dwc3_gadget_ep_cmd_string(__entry->cmd), + __entry->cmd, __entry->param0, + __entry->param1, __entry->param2, + dwc3_ep_cmd_status_string(__entry->cmd_status) + ) + +Lifetime of a ``TRB`` +`````````````````````` + +A ``TRB`` Lifetime is simple. We are either preparing a ``TRB`` or +completing it. With these two events, we can see how a ``TRB`` changes +over time. Format is:: + + TP_printk("%s: %d/%d trb %p buf %08x%08x size %s%d ctrl %08x (%c%c%c%c:%c%c:%s)", + __get_str(name), __entry->queued, __entry->allocated, + __entry->trb, __entry->bph, __entry->bpl, + ({char *s; + int pcm = ((__entry->size >> 24) & 3) + 1; + switch (__entry->type) { + case USB_ENDPOINT_XFER_INT: + case USB_ENDPOINT_XFER_ISOC: + switch (pcm) { + case 1: + s = "1x "; + break; + case 2: + s = "2x "; + break; + case 3: + s = "3x "; + break; + } + default: + s = ""; + } s; }), + DWC3_TRB_SIZE_LENGTH(__entry->size), __entry->ctrl, + __entry->ctrl & DWC3_TRB_CTRL_HWO ? 'H' : 'h', + __entry->ctrl & DWC3_TRB_CTRL_LST ? 'L' : 'l', + __entry->ctrl & DWC3_TRB_CTRL_CHN ? 'C' : 'c', + __entry->ctrl & DWC3_TRB_CTRL_CSP ? 'S' : 's', + __entry->ctrl & DWC3_TRB_CTRL_ISP_IMI ? 'S' : 's', + __entry->ctrl & DWC3_TRB_CTRL_IOC ? 'C' : 'c', + dwc3_trb_type_string(DWC3_TRBCTL_TYPE(__entry->ctrl)) + ) + +Lifetime of an Endpoint +``````````````````````` + +And endpoint's lifetime is summarized with enable and disable +operations, both of which can be traced. Format is:: + + TP_printk("%s: mps %d/%d streams %d burst %d ring %d/%d flags %c:%c%c%c%c%c:%c:%c", + __get_str(name), __entry->maxpacket, + __entry->maxpacket_limit, __entry->max_streams, + __entry->maxburst, __entry->trb_enqueue, + __entry->trb_dequeue, + __entry->flags & DWC3_EP_ENABLED ? 'E' : 'e', + __entry->flags & DWC3_EP_STALL ? 'S' : 's', + __entry->flags & DWC3_EP_WEDGE ? 'W' : 'w', + __entry->flags & DWC3_EP_BUSY ? 'B' : 'b', + __entry->flags & DWC3_EP_PENDING_REQUEST ? 'P' : 'p', + __entry->flags & DWC3_EP_MISSED_ISOC ? 'M' : 'm', + __entry->flags & DWC3_EP_END_TRANSFER_PENDING ? 'E' : 'e', + __entry->direction ? '<' : '>' + ) + + +Structures, Methods and Definitions +==================================== + +.. kernel-doc:: drivers/usb/dwc3/core.h + :doc: main data structures + :internal: + +.. kernel-doc:: drivers/usb/dwc3/gadget.h + :doc: gadget-only helpers + :internal: + +.. kernel-doc:: drivers/usb/dwc3/gadget.c + :doc: gadget-side implementation + :internal: + +.. kernel-doc:: drivers/usb/dwc3/core.c + :doc: core driver (probe, PM, etc) + :internal: + +.. [#trb] Transfer Request Block +.. [#link_trb] Transfer Request Block pointing to another Transfer + Request Block. +.. [#debugfs] The Debug File System +.. [#configfs] The Config File System +.. [#cbw] Command Block Wrapper +.. _Linus' tree: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/ +.. _me: felipe.balbi@linux.intel.com +.. _linux-usb: linux-usb@vger.kernel.org diff --git a/Documentation/driver-api/usb/index.rst b/Documentation/driver-api/usb/index.rst index 1bf64edc8c8a..3d357a83046b 100644 --- a/Documentation/driver-api/usb/index.rst +++ b/Documentation/driver-api/usb/index.rst @@ -16,6 +16,7 @@ Linux USB API persist error-codes writing_usb_driver + dwc3 writing_musb_glue_layer .. only:: subproject and html -- cgit v1.2.3 From b0b3ddf8fb37283d0d488d667fac4c069d612ba3 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Tue, 9 May 2017 16:30:24 +0300 Subject: usb: move ReST documentation to Documentation/driver-api/usb/ This is where all other USB ReST documentation has moved to. Signed-off-by: Felipe Balbi --- Documentation/driver-api/usb/index.rst | 2 + Documentation/driver-api/usb/typec.rst | 184 +++++++++++++++++++++++ Documentation/driver-api/usb/usb3-debug-port.rst | 100 ++++++++++++ Documentation/usb/typec.rst | 184 ----------------------- Documentation/usb/usb3-debug-port.rst | 100 ------------ 5 files changed, 286 insertions(+), 284 deletions(-) create mode 100644 Documentation/driver-api/usb/typec.rst create mode 100644 Documentation/driver-api/usb/usb3-debug-port.rst delete mode 100644 Documentation/usb/typec.rst delete mode 100644 Documentation/usb/usb3-debug-port.rst diff --git a/Documentation/driver-api/usb/index.rst b/Documentation/driver-api/usb/index.rst index 3d357a83046b..8fe995a1ec94 100644 --- a/Documentation/driver-api/usb/index.rst +++ b/Documentation/driver-api/usb/index.rst @@ -18,6 +18,8 @@ Linux USB API writing_usb_driver dwc3 writing_musb_glue_layer + typec + usb3-debug-port .. only:: subproject and html diff --git a/Documentation/driver-api/usb/typec.rst b/Documentation/driver-api/usb/typec.rst new file mode 100644 index 000000000000..b67a46779de9 --- /dev/null +++ b/Documentation/driver-api/usb/typec.rst @@ -0,0 +1,184 @@ + +USB Type-C connector class +========================== + +Introduction +------------ + +The typec class is meant for describing the USB Type-C ports in a system to the +user space in unified fashion. The class is designed to provide nothing else +except the user space interface implementation in hope that it can be utilized +on as many platforms as possible. + +The platforms are expected to register every USB Type-C port they have with the +class. In a normal case the registration will be done by a USB Type-C or PD PHY +driver, but it may be a driver for firmware interface such as UCSI, driver for +USB PD controller or even driver for Thunderbolt3 controller. This document +considers the component registering the USB Type-C ports with the class as "port +driver". + +On top of showing the capabilities, the class also offer user space control over +the roles and alternate modes of ports, partners and cable plugs when the port +driver is capable of supporting those features. + +The class provides an API for the port drivers described in this document. The +attributes are described in Documentation/ABI/testing/sysfs-class-typec. + +User space interface +-------------------- +Every port will be presented as its own device under /sys/class/typec/. The +first port will be named "port0", the second "port1" and so on. + +When connected, the partner will be presented also as its own device under +/sys/class/typec/. The parent of the partner device will always be the port it +is attached to. The partner attached to port "port0" will be named +"port0-partner". Full path to the device would be +/sys/class/typec/port0/port0-partner/. + +The cable and the two plugs on it may also be optionally presented as their own +devices under /sys/class/typec/. The cable attached to the port "port0" port +will be named port0-cable and the plug on the SOP Prime end (see USB Power +Delivery Specification ch. 2.4) will be named "port0-plug0" and on the SOP +Double Prime end "port0-plug1". The parent of a cable will always be the port, +and the parent of the cable plugs will always be the cable. + +If the port, partner or cable plug supports Alternate Modes, every supported +Alternate Mode SVID will have their own device describing them. Note that the +Alternate Mode devices will not be attached to the typec class. The parent of an +alternate mode will be the device that supports it, so for example an alternate +mode of port0-partner will be presented under /sys/class/typec/port0-partner/. +Every mode that is supported will have its own group under the Alternate Mode +device named "mode", for example /sys/class/typec/port0//mode1/. The requests for entering/exiting a mode can be done with "active" +attribute file in that group. + +Driver API +---------- + +Registering the ports +~~~~~~~~~~~~~~~~~~~~~ + +The port drivers will describe every Type-C port they control with struct +typec_capability data structure, and register them with the following API: + +.. kernel-doc:: drivers/usb/typec/typec.c + :functions: typec_register_port typec_unregister_port + +When registering the ports, the prefer_role member in struct typec_capability +deserves special notice. If the port that is being registered does not have +initial role preference, which means the port does not execute Try.SNK or +Try.SRC by default, the member must have value TYPEC_NO_PREFERRED_ROLE. +Otherwise if the port executes Try.SNK by default, the member must have value +TYPEC_DEVICE, and with Try.SRC the value must be TYPEC_HOST. + +Registering Partners +~~~~~~~~~~~~~~~~~~~~ + +After successful connection of a partner, the port driver needs to register the +partner with the class. Details about the partner need to be described in struct +typec_partner_desc. The class copies the details of the partner during +registration. The class offers the following API for registering/unregistering +partners. + +.. kernel-doc:: drivers/usb/typec/typec.c + :functions: typec_register_partner typec_unregister_partner + +The class will provide a handle to struct typec_partner if the registration was +successful, or NULL. + +If the partner is USB Power Delivery capable, and the port driver is able to +show the result of Discover Identity command, the partner descriptor structure +should include handle to struct usb_pd_identity instance. The class will then +create a sysfs directory for the identity under the partner device. The result +of Discover Identity command can then be reported with the following API: + +.. kernel-doc:: drivers/usb/typec/typec.c + :functions: typec_partner_set_identity + +Registering Cables +~~~~~~~~~~~~~~~~~~ + +After successful connection of a cable that supports USB Power Delivery +Structured VDM "Discover Identity", the port driver needs to register the cable +and one or two plugs, depending if there is CC Double Prime controller present +in the cable or not. So a cable capable of SOP Prime communication, but not SOP +Double Prime communication, should only have one plug registered. For more +information about SOP communication, please read chapter about it from the +latest USB Power Delivery specification. + +The plugs are represented as their own devices. The cable is registered first, +followed by registration of the cable plugs. The cable will be the parent device +for the plugs. Details about the cable need to be described in struct +typec_cable_desc and about a plug in struct typec_plug_desc. The class copies +the details during registration. The class offers the following API for +registering/unregistering cables and their plugs: + +.. kernel-doc:: drivers/usb/typec/typec.c + :functions: typec_register_cable typec_unregister_cable typec_register_plug + typec_unregister_plug + +The class will provide a handle to struct typec_cable and struct typec_plug if +the registration is successful, or NULL if it isn't. + +If the cable is USB Power Delivery capable, and the port driver is able to show +the result of Discover Identity command, the cable descriptor structure should +include handle to struct usb_pd_identity instance. The class will then create a +sysfs directory for the identity under the cable device. The result of Discover +Identity command can then be reported with the following API: + +.. kernel-doc:: drivers/usb/typec/typec.c + :functions: typec_cable_set_identity + +Notifications +~~~~~~~~~~~~~ + +When the partner has executed a role change, or when the default roles change +during connection of a partner or cable, the port driver must use the following +APIs to report it to the class: + +.. kernel-doc:: drivers/usb/typec/typec.c + :functions: typec_set_data_role typec_set_pwr_role typec_set_vconn_role + typec_set_pwr_opmode + +Alternate Modes +~~~~~~~~~~~~~~~ + +USB Type-C ports, partners and cable plugs may support Alternate Modes. Each +Alternate Mode will have identifier called SVID, which is either a Standard ID +given by USB-IF or vendor ID, and each supported SVID can have 1 - 6 modes. The +class provides struct typec_mode_desc for describing individual mode of a SVID, +and struct typec_altmode_desc which is a container for all the supported modes. + +Ports that support Alternate Modes need to register each SVID they support with +the following API: + +.. kernel-doc:: drivers/usb/typec/typec.c + :functions: typec_port_register_altmode + +If a partner or cable plug provides a list of SVIDs as response to USB Power +Delivery Structured VDM Discover SVIDs message, each SVID needs to be +registered. + +API for the partners: + +.. kernel-doc:: drivers/usb/typec/typec.c + :functions: typec_partner_register_altmode + +API for the Cable Plugs: + +.. kernel-doc:: drivers/usb/typec/typec.c + :functions: typec_plug_register_altmode + +So ports, partners and cable plugs will register the alternate modes with their +own functions, but the registration will always return a handle to struct +typec_altmode on success, or NULL. The unregistration will happen with the same +function: + +.. kernel-doc:: drivers/usb/typec/typec.c + :functions: typec_unregister_altmode + +If a partner or cable plug enters or exits a mode, the port driver needs to +notify the class with the following API: + +.. kernel-doc:: drivers/usb/typec/typec.c + :functions: typec_altmode_update_active diff --git a/Documentation/driver-api/usb/usb3-debug-port.rst b/Documentation/driver-api/usb/usb3-debug-port.rst new file mode 100644 index 000000000000..feb1a36a65b7 --- /dev/null +++ b/Documentation/driver-api/usb/usb3-debug-port.rst @@ -0,0 +1,100 @@ +=============== +USB3 debug port +=============== + +:Author: Lu Baolu +:Date: March 2017 + +GENERAL +======= + +This is a HOWTO for using the USB3 debug port on x86 systems. + +Before using any kernel debugging functionality based on USB3 +debug port, you need to:: + + 1) check whether any USB3 debug port is available in + your system; + 2) check which port is used for debugging purposes; + 3) have a USB 3.0 super-speed A-to-A debugging cable. + +INTRODUCTION +============ + +The xHCI debug capability (DbC) is an optional but standalone +functionality provided by the xHCI host controller. The xHCI +specification describes DbC in the section 7.6. + +When DbC is initialized and enabled, it will present a debug +device through the debug port (normally the first USB3 +super-speed port). The debug device is fully compliant with +the USB framework and provides the equivalent of a very high +performance full-duplex serial link between the debug target +(the system under debugging) and a debug host. + +EARLY PRINTK +============ + +DbC has been designed to log early printk messages. One use for +this feature is kernel debugging. For example, when your machine +crashes very early before the regular console code is initialized. +Other uses include simpler, lockless logging instead of a full- +blown printk console driver and klogd. + +On the debug target system, you need to customize a debugging +kernel with CONFIG_EARLY_PRINTK_USB_XDBC enabled. And, add below +kernel boot parameter:: + + "earlyprintk=xdbc" + +If there are multiple xHCI controllers in your system, you can +append a host contoller index to this kernel parameter. This +index starts from 0. + +Current design doesn't support DbC runtime suspend/resume. As +the result, you'd better disable runtime power management for +USB subsystem by adding below kernel boot parameter:: + + "usbcore.autosuspend=-1" + +Before starting the debug target, you should connect the debug +port to a USB port (root port or port of any external hub) on +the debug host. The cable used to connect these two ports +should be a USB 3.0 super-speed A-to-A debugging cable. + +During early boot of the debug target, DbC will be detected and +initialized. After initialization, the debug host should be able +to enumerate the debug device in debug target. The debug host +will then bind the debug device with the usb_debug driver module +and create the /dev/ttyUSB device. + +If the debug device enumeration goes smoothly, you should be able +to see below kernel messages on the debug host:: + + # tail -f /var/log/kern.log + [ 1815.983374] usb 4-3: new SuperSpeed USB device number 4 using xhci_hcd + [ 1815.999595] usb 4-3: LPM exit latency is zeroed, disabling LPM. + [ 1815.999899] usb 4-3: New USB device found, idVendor=1d6b, idProduct=0004 + [ 1815.999902] usb 4-3: New USB device strings: Mfr=1, Product=2, SerialNumber=3 + [ 1815.999903] usb 4-3: Product: Remote GDB + [ 1815.999904] usb 4-3: Manufacturer: Linux + [ 1815.999905] usb 4-3: SerialNumber: 0001 + [ 1816.000240] usb_debug 4-3:1.0: xhci_dbc converter detected + [ 1816.000360] usb 4-3: xhci_dbc converter now attached to ttyUSB0 + +You can use any communication program, for example minicom, to +read and view the messages. Below simple bash scripts can help +you to check the sanity of the setup. + +.. code-block:: sh + + ===== start of bash scripts ============= + #!/bin/bash + + while true ; do + while [ ! -d /sys/class/tty/ttyUSB0 ] ; do + : + done + cat /dev/ttyUSB0 + done + ===== end of bash scripts =============== diff --git a/Documentation/usb/typec.rst b/Documentation/usb/typec.rst deleted file mode 100644 index b67a46779de9..000000000000 --- a/Documentation/usb/typec.rst +++ /dev/null @@ -1,184 +0,0 @@ - -USB Type-C connector class -========================== - -Introduction ------------- - -The typec class is meant for describing the USB Type-C ports in a system to the -user space in unified fashion. The class is designed to provide nothing else -except the user space interface implementation in hope that it can be utilized -on as many platforms as possible. - -The platforms are expected to register every USB Type-C port they have with the -class. In a normal case the registration will be done by a USB Type-C or PD PHY -driver, but it may be a driver for firmware interface such as UCSI, driver for -USB PD controller or even driver for Thunderbolt3 controller. This document -considers the component registering the USB Type-C ports with the class as "port -driver". - -On top of showing the capabilities, the class also offer user space control over -the roles and alternate modes of ports, partners and cable plugs when the port -driver is capable of supporting those features. - -The class provides an API for the port drivers described in this document. The -attributes are described in Documentation/ABI/testing/sysfs-class-typec. - -User space interface --------------------- -Every port will be presented as its own device under /sys/class/typec/. The -first port will be named "port0", the second "port1" and so on. - -When connected, the partner will be presented also as its own device under -/sys/class/typec/. The parent of the partner device will always be the port it -is attached to. The partner attached to port "port0" will be named -"port0-partner". Full path to the device would be -/sys/class/typec/port0/port0-partner/. - -The cable and the two plugs on it may also be optionally presented as their own -devices under /sys/class/typec/. The cable attached to the port "port0" port -will be named port0-cable and the plug on the SOP Prime end (see USB Power -Delivery Specification ch. 2.4) will be named "port0-plug0" and on the SOP -Double Prime end "port0-plug1". The parent of a cable will always be the port, -and the parent of the cable plugs will always be the cable. - -If the port, partner or cable plug supports Alternate Modes, every supported -Alternate Mode SVID will have their own device describing them. Note that the -Alternate Mode devices will not be attached to the typec class. The parent of an -alternate mode will be the device that supports it, so for example an alternate -mode of port0-partner will be presented under /sys/class/typec/port0-partner/. -Every mode that is supported will have its own group under the Alternate Mode -device named "mode", for example /sys/class/typec/port0//mode1/. The requests for entering/exiting a mode can be done with "active" -attribute file in that group. - -Driver API ----------- - -Registering the ports -~~~~~~~~~~~~~~~~~~~~~ - -The port drivers will describe every Type-C port they control with struct -typec_capability data structure, and register them with the following API: - -.. kernel-doc:: drivers/usb/typec/typec.c - :functions: typec_register_port typec_unregister_port - -When registering the ports, the prefer_role member in struct typec_capability -deserves special notice. If the port that is being registered does not have -initial role preference, which means the port does not execute Try.SNK or -Try.SRC by default, the member must have value TYPEC_NO_PREFERRED_ROLE. -Otherwise if the port executes Try.SNK by default, the member must have value -TYPEC_DEVICE, and with Try.SRC the value must be TYPEC_HOST. - -Registering Partners -~~~~~~~~~~~~~~~~~~~~ - -After successful connection of a partner, the port driver needs to register the -partner with the class. Details about the partner need to be described in struct -typec_partner_desc. The class copies the details of the partner during -registration. The class offers the following API for registering/unregistering -partners. - -.. kernel-doc:: drivers/usb/typec/typec.c - :functions: typec_register_partner typec_unregister_partner - -The class will provide a handle to struct typec_partner if the registration was -successful, or NULL. - -If the partner is USB Power Delivery capable, and the port driver is able to -show the result of Discover Identity command, the partner descriptor structure -should include handle to struct usb_pd_identity instance. The class will then -create a sysfs directory for the identity under the partner device. The result -of Discover Identity command can then be reported with the following API: - -.. kernel-doc:: drivers/usb/typec/typec.c - :functions: typec_partner_set_identity - -Registering Cables -~~~~~~~~~~~~~~~~~~ - -After successful connection of a cable that supports USB Power Delivery -Structured VDM "Discover Identity", the port driver needs to register the cable -and one or two plugs, depending if there is CC Double Prime controller present -in the cable or not. So a cable capable of SOP Prime communication, but not SOP -Double Prime communication, should only have one plug registered. For more -information about SOP communication, please read chapter about it from the -latest USB Power Delivery specification. - -The plugs are represented as their own devices. The cable is registered first, -followed by registration of the cable plugs. The cable will be the parent device -for the plugs. Details about the cable need to be described in struct -typec_cable_desc and about a plug in struct typec_plug_desc. The class copies -the details during registration. The class offers the following API for -registering/unregistering cables and their plugs: - -.. kernel-doc:: drivers/usb/typec/typec.c - :functions: typec_register_cable typec_unregister_cable typec_register_plug - typec_unregister_plug - -The class will provide a handle to struct typec_cable and struct typec_plug if -the registration is successful, or NULL if it isn't. - -If the cable is USB Power Delivery capable, and the port driver is able to show -the result of Discover Identity command, the cable descriptor structure should -include handle to struct usb_pd_identity instance. The class will then create a -sysfs directory for the identity under the cable device. The result of Discover -Identity command can then be reported with the following API: - -.. kernel-doc:: drivers/usb/typec/typec.c - :functions: typec_cable_set_identity - -Notifications -~~~~~~~~~~~~~ - -When the partner has executed a role change, or when the default roles change -during connection of a partner or cable, the port driver must use the following -APIs to report it to the class: - -.. kernel-doc:: drivers/usb/typec/typec.c - :functions: typec_set_data_role typec_set_pwr_role typec_set_vconn_role - typec_set_pwr_opmode - -Alternate Modes -~~~~~~~~~~~~~~~ - -USB Type-C ports, partners and cable plugs may support Alternate Modes. Each -Alternate Mode will have identifier called SVID, which is either a Standard ID -given by USB-IF or vendor ID, and each supported SVID can have 1 - 6 modes. The -class provides struct typec_mode_desc for describing individual mode of a SVID, -and struct typec_altmode_desc which is a container for all the supported modes. - -Ports that support Alternate Modes need to register each SVID they support with -the following API: - -.. kernel-doc:: drivers/usb/typec/typec.c - :functions: typec_port_register_altmode - -If a partner or cable plug provides a list of SVIDs as response to USB Power -Delivery Structured VDM Discover SVIDs message, each SVID needs to be -registered. - -API for the partners: - -.. kernel-doc:: drivers/usb/typec/typec.c - :functions: typec_partner_register_altmode - -API for the Cable Plugs: - -.. kernel-doc:: drivers/usb/typec/typec.c - :functions: typec_plug_register_altmode - -So ports, partners and cable plugs will register the alternate modes with their -own functions, but the registration will always return a handle to struct -typec_altmode on success, or NULL. The unregistration will happen with the same -function: - -.. kernel-doc:: drivers/usb/typec/typec.c - :functions: typec_unregister_altmode - -If a partner or cable plug enters or exits a mode, the port driver needs to -notify the class with the following API: - -.. kernel-doc:: drivers/usb/typec/typec.c - :functions: typec_altmode_update_active diff --git a/Documentation/usb/usb3-debug-port.rst b/Documentation/usb/usb3-debug-port.rst deleted file mode 100644 index feb1a36a65b7..000000000000 --- a/Documentation/usb/usb3-debug-port.rst +++ /dev/null @@ -1,100 +0,0 @@ -=============== -USB3 debug port -=============== - -:Author: Lu Baolu -:Date: March 2017 - -GENERAL -======= - -This is a HOWTO for using the USB3 debug port on x86 systems. - -Before using any kernel debugging functionality based on USB3 -debug port, you need to:: - - 1) check whether any USB3 debug port is available in - your system; - 2) check which port is used for debugging purposes; - 3) have a USB 3.0 super-speed A-to-A debugging cable. - -INTRODUCTION -============ - -The xHCI debug capability (DbC) is an optional but standalone -functionality provided by the xHCI host controller. The xHCI -specification describes DbC in the section 7.6. - -When DbC is initialized and enabled, it will present a debug -device through the debug port (normally the first USB3 -super-speed port). The debug device is fully compliant with -the USB framework and provides the equivalent of a very high -performance full-duplex serial link between the debug target -(the system under debugging) and a debug host. - -EARLY PRINTK -============ - -DbC has been designed to log early printk messages. One use for -this feature is kernel debugging. For example, when your machine -crashes very early before the regular console code is initialized. -Other uses include simpler, lockless logging instead of a full- -blown printk console driver and klogd. - -On the debug target system, you need to customize a debugging -kernel with CONFIG_EARLY_PRINTK_USB_XDBC enabled. And, add below -kernel boot parameter:: - - "earlyprintk=xdbc" - -If there are multiple xHCI controllers in your system, you can -append a host contoller index to this kernel parameter. This -index starts from 0. - -Current design doesn't support DbC runtime suspend/resume. As -the result, you'd better disable runtime power management for -USB subsystem by adding below kernel boot parameter:: - - "usbcore.autosuspend=-1" - -Before starting the debug target, you should connect the debug -port to a USB port (root port or port of any external hub) on -the debug host. The cable used to connect these two ports -should be a USB 3.0 super-speed A-to-A debugging cable. - -During early boot of the debug target, DbC will be detected and -initialized. After initialization, the debug host should be able -to enumerate the debug device in debug target. The debug host -will then bind the debug device with the usb_debug driver module -and create the /dev/ttyUSB device. - -If the debug device enumeration goes smoothly, you should be able -to see below kernel messages on the debug host:: - - # tail -f /var/log/kern.log - [ 1815.983374] usb 4-3: new SuperSpeed USB device number 4 using xhci_hcd - [ 1815.999595] usb 4-3: LPM exit latency is zeroed, disabling LPM. - [ 1815.999899] usb 4-3: New USB device found, idVendor=1d6b, idProduct=0004 - [ 1815.999902] usb 4-3: New USB device strings: Mfr=1, Product=2, SerialNumber=3 - [ 1815.999903] usb 4-3: Product: Remote GDB - [ 1815.999904] usb 4-3: Manufacturer: Linux - [ 1815.999905] usb 4-3: SerialNumber: 0001 - [ 1816.000240] usb_debug 4-3:1.0: xhci_dbc converter detected - [ 1816.000360] usb 4-3: xhci_dbc converter now attached to ttyUSB0 - -You can use any communication program, for example minicom, to -read and view the messages. Below simple bash scripts can help -you to check the sanity of the setup. - -.. code-block:: sh - - ===== start of bash scripts ============= - #!/bin/bash - - while true ; do - while [ ! -d /sys/class/tty/ttyUSB0 ] ; do - : - done - cat /dev/ttyUSB0 - done - ===== end of bash scripts =============== -- cgit v1.2.3 From 8a8b161df5ce06ef5a315899f83978e765be09e8 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Sun, 16 Apr 2017 20:12:50 -0700 Subject: usb: gadget: remove redundant self assignment The assignment ret = ret is redundant and can be removed. Reviewed-by: Krzysztof Opasiak Reviewed-by: Peter Chen Signed-off-by: Stefan Agner Signed-off-by: Felipe Balbi --- drivers/usb/gadget/udc/core.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c index efce68e9a8e0..62d52fc0fcdc 100644 --- a/drivers/usb/gadget/udc/core.c +++ b/drivers/usb/gadget/udc/core.c @@ -139,10 +139,8 @@ int usb_ep_disable(struct usb_ep *ep) goto out; ret = ep->ops->disable(ep); - if (ret) { - ret = ret; + if (ret) goto out; - } ep->enabled = false; -- cgit v1.2.3 From 222155de45573e978cda988b7efc7d4e7b9a8ff9 Mon Sep 17 00:00:00 2001 From: Jerry Zhang Date: Wed, 19 Apr 2017 18:23:38 -0700 Subject: usb: gadget: function: f_fs: Let ffs_epfile_ioctl wait for enable. This allows users to make an ioctl call as the first action on a connection. Ex, some functions might want to get endpoint size before making any i/os. Previously, calling ioctls before read/write would depending on the timing of endpoints being enabled. ESHUTDOWN is now a possible return value and ENODEV is not, so change docs accordingly. Acked-by: Michal Nazarewicz Signed-off-by: Jerry Zhang Signed-off-by: Felipe Balbi --- drivers/usb/gadget/function/f_fs.c | 93 +++++++++++++++++++++---------------- include/uapi/linux/usb/functionfs.h | 7 +-- 2 files changed, 58 insertions(+), 42 deletions(-) diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index 71dd27c0d7f2..a24f9bf9c1c0 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -1189,6 +1189,7 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, unsigned long value) { struct ffs_epfile *epfile = file->private_data; + struct ffs_ep *ep; int ret; ENTER(); @@ -1196,50 +1197,64 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, if (WARN_ON(epfile->ffs->state != FFS_ACTIVE)) return -ENODEV; + /* Wait for endpoint to be enabled */ + ep = epfile->ep; + if (!ep) { + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + ret = wait_event_interruptible(epfile->wait, (ep = epfile->ep)); + if (ret) + return -EINTR; + } + spin_lock_irq(&epfile->ffs->eps_lock); - if (likely(epfile->ep)) { - switch (code) { - case FUNCTIONFS_FIFO_STATUS: - ret = usb_ep_fifo_status(epfile->ep->ep); - break; - case FUNCTIONFS_FIFO_FLUSH: - usb_ep_fifo_flush(epfile->ep->ep); - ret = 0; - break; - case FUNCTIONFS_CLEAR_HALT: - ret = usb_ep_clear_halt(epfile->ep->ep); - break; - case FUNCTIONFS_ENDPOINT_REVMAP: - ret = epfile->ep->num; - break; - case FUNCTIONFS_ENDPOINT_DESC: - { - int desc_idx; - struct usb_endpoint_descriptor *desc; - switch (epfile->ffs->gadget->speed) { - case USB_SPEED_SUPER: - desc_idx = 2; - break; - case USB_SPEED_HIGH: - desc_idx = 1; - break; - default: - desc_idx = 0; - } - desc = epfile->ep->descs[desc_idx]; + /* In the meantime, endpoint got disabled or changed. */ + if (epfile->ep != ep) { + spin_unlock_irq(&epfile->ffs->eps_lock); + return -ESHUTDOWN; + } - spin_unlock_irq(&epfile->ffs->eps_lock); - ret = copy_to_user((void *)value, desc, desc->bLength); - if (ret) - ret = -EFAULT; - return ret; - } + switch (code) { + case FUNCTIONFS_FIFO_STATUS: + ret = usb_ep_fifo_status(epfile->ep->ep); + break; + case FUNCTIONFS_FIFO_FLUSH: + usb_ep_fifo_flush(epfile->ep->ep); + ret = 0; + break; + case FUNCTIONFS_CLEAR_HALT: + ret = usb_ep_clear_halt(epfile->ep->ep); + break; + case FUNCTIONFS_ENDPOINT_REVMAP: + ret = epfile->ep->num; + break; + case FUNCTIONFS_ENDPOINT_DESC: + { + int desc_idx; + struct usb_endpoint_descriptor *desc; + + switch (epfile->ffs->gadget->speed) { + case USB_SPEED_SUPER: + desc_idx = 2; + break; + case USB_SPEED_HIGH: + desc_idx = 1; + break; default: - ret = -ENOTTY; + desc_idx = 0; } - } else { - ret = -ENODEV; + desc = epfile->ep->descs[desc_idx]; + + spin_unlock_irq(&epfile->ffs->eps_lock); + ret = copy_to_user((void *)value, desc, desc->bLength); + if (ret) + ret = -EFAULT; + return ret; + } + default: + ret = -ENOTTY; } spin_unlock_irq(&epfile->ffs->eps_lock); diff --git a/include/uapi/linux/usb/functionfs.h b/include/uapi/linux/usb/functionfs.h index 062606f02309..f913d08ab7bb 100644 --- a/include/uapi/linux/usb/functionfs.h +++ b/include/uapi/linux/usb/functionfs.h @@ -275,13 +275,14 @@ struct usb_functionfs_event { #define FUNCTIONFS_INTERFACE_REVMAP _IO('g', 128) /* - * Returns real bEndpointAddress of an endpoint. If function is not - * active returns -ENODEV. + * Returns real bEndpointAddress of an endpoint. If endpoint shuts down + * during the call, returns -ESHUTDOWN. */ #define FUNCTIONFS_ENDPOINT_REVMAP _IO('g', 129) /* - * Returns endpoint descriptor. If function is not active returns -ENODEV. + * Returns endpoint descriptor. If endpoint shuts down during the call, + * returns -ESHUTDOWN. */ #define FUNCTIONFS_ENDPOINT_DESC _IOR('g', 130, \ struct usb_endpoint_descriptor) -- cgit v1.2.3 From e16828cf945ca11b05df1cc755af8e4b669f6dd3 Mon Sep 17 00:00:00 2001 From: Jerry Zhang Date: Tue, 18 Apr 2017 16:11:48 -0700 Subject: usb: gadget: function: f_fs: Move epfile waitqueue to ffs_data. There were individual waitqueues for each epfile but eps_enable would iterate through all of them, resulting in essentially the same wakeup time. The waitqueue represents the function being enabled, so a central waitqueue in ffs_data makes more sense and is less redundant. Also use wake_up_interruptible to reflect use of wait_event_interruptible. Acked-by: Michal Nazarewicz Signed-off-by: Jerry Zhang Signed-off-by: Felipe Balbi --- drivers/usb/gadget/function/f_fs.c | 19 ++++++++++--------- drivers/usb/gadget/function/u_fs.h | 3 +++ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index a24f9bf9c1c0..519ea34ca699 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -127,7 +127,6 @@ struct ffs_ep { struct ffs_epfile { /* Protects ep->ep and ep->req. */ struct mutex mutex; - wait_queue_head_t wait; struct ffs_data *ffs; struct ffs_ep *ep; /* P: ffs->eps_lock */ @@ -889,7 +888,8 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) if (file->f_flags & O_NONBLOCK) return -EAGAIN; - ret = wait_event_interruptible(epfile->wait, (ep = epfile->ep)); + ret = wait_event_interruptible( + epfile->ffs->wait, (ep = epfile->ep)); if (ret) return -EINTR; } @@ -1203,7 +1203,8 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, if (file->f_flags & O_NONBLOCK) return -EAGAIN; - ret = wait_event_interruptible(epfile->wait, (ep = epfile->ep)); + ret = wait_event_interruptible( + epfile->ffs->wait, (ep = epfile->ep)); if (ret) return -EINTR; } @@ -1608,7 +1609,8 @@ static void ffs_data_put(struct ffs_data *ffs) pr_info("%s(): freeing\n", __func__); ffs_data_clear(ffs); BUG_ON(waitqueue_active(&ffs->ev.waitq) || - waitqueue_active(&ffs->ep0req_completion.wait)); + waitqueue_active(&ffs->ep0req_completion.wait) || + waitqueue_active(&ffs->wait)); kfree(ffs->dev_name); kfree(ffs); } @@ -1655,6 +1657,7 @@ static struct ffs_data *ffs_data_new(void) mutex_init(&ffs->mutex); spin_lock_init(&ffs->eps_lock); init_waitqueue_head(&ffs->ev.waitq); + init_waitqueue_head(&ffs->wait); init_completion(&ffs->ep0req_completion); /* XXX REVISIT need to update it in some places, or do we? */ @@ -1776,7 +1779,6 @@ static int ffs_epfiles_create(struct ffs_data *ffs) for (i = 1; i <= count; ++i, ++epfile) { epfile->ffs = ffs; mutex_init(&epfile->mutex); - init_waitqueue_head(&epfile->wait); if (ffs->user_flags & FUNCTIONFS_VIRTUAL_ADDR) sprintf(epfile->name, "ep%02x", ffs->eps_addrmap[i]); else @@ -1801,8 +1803,7 @@ static void ffs_epfiles_destroy(struct ffs_epfile *epfiles, unsigned count) ENTER(); for (; count; --count, ++epfile) { - BUG_ON(mutex_is_locked(&epfile->mutex) || - waitqueue_active(&epfile->wait)); + BUG_ON(mutex_is_locked(&epfile->mutex)); if (epfile->dentry) { d_delete(epfile->dentry); dput(epfile->dentry); @@ -1889,11 +1890,11 @@ static int ffs_func_eps_enable(struct ffs_function *func) break; } - wake_up(&epfile->wait); - ++ep; ++epfile; } + + wake_up_interruptible(&ffs->wait); spin_unlock_irqrestore(&func->ffs->eps_lock, flags); return ret; diff --git a/drivers/usb/gadget/function/u_fs.h b/drivers/usb/gadget/function/u_fs.h index 4378cc2fcac3..540f1c48c1a8 100644 --- a/drivers/usb/gadget/function/u_fs.h +++ b/drivers/usb/gadget/function/u_fs.h @@ -216,6 +216,9 @@ struct ffs_data { #define FFS_FL_CALL_CLOSED_CALLBACK 0 #define FFS_FL_BOUND 1 + /* For waking up blocked threads when function is enabled. */ + wait_queue_head_t wait; + /* Active function */ struct ffs_function *func; -- cgit v1.2.3 From 0b67a6be14be5fb050b0358022c497d0619ebc40 Mon Sep 17 00:00:00 2001 From: John Youn Date: Fri, 28 Apr 2017 12:55:14 +0400 Subject: usb: gadget: composite: Exclude SS Dev Cap Desc Don't send the SuperSpeed USB Device Capability descriptor if the gadget is not capable of SuperSpeed. Signed-off-by: John Youn Signed-off-by: Sevak Arakelyan Signed-off-by: Felipe Balbi --- drivers/usb/gadget/composite.c | 51 +++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index 49d685ad0da9..abec93ab81ee 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -607,7 +607,6 @@ static int count_configs(struct usb_composite_dev *cdev, unsigned type) static int bos_desc(struct usb_composite_dev *cdev) { struct usb_ext_cap_descriptor *usb_ext; - struct usb_ss_cap_descriptor *ss_cap; struct usb_dcd_config_params dcd_config_params; struct usb_bos_descriptor *bos = cdev->req->buf; @@ -633,29 +632,35 @@ static int bos_desc(struct usb_composite_dev *cdev) * The Superspeed USB Capability descriptor shall be implemented by all * SuperSpeed devices. */ - ss_cap = cdev->req->buf + le16_to_cpu(bos->wTotalLength); - bos->bNumDeviceCaps++; - le16_add_cpu(&bos->wTotalLength, USB_DT_USB_SS_CAP_SIZE); - ss_cap->bLength = USB_DT_USB_SS_CAP_SIZE; - ss_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY; - ss_cap->bDevCapabilityType = USB_SS_CAP_TYPE; - ss_cap->bmAttributes = 0; /* LTM is not supported yet */ - ss_cap->wSpeedSupported = cpu_to_le16(USB_LOW_SPEED_OPERATION | - USB_FULL_SPEED_OPERATION | - USB_HIGH_SPEED_OPERATION | - USB_5GBPS_OPERATION); - ss_cap->bFunctionalitySupport = USB_LOW_SPEED_OPERATION; - - /* Get Controller configuration */ - if (cdev->gadget->ops->get_config_params) - cdev->gadget->ops->get_config_params(&dcd_config_params); - else { - dcd_config_params.bU1devExitLat = USB_DEFAULT_U1_DEV_EXIT_LAT; - dcd_config_params.bU2DevExitLat = - cpu_to_le16(USB_DEFAULT_U2_DEV_EXIT_LAT); + if (gadget_is_superspeed(cdev->gadget)) { + struct usb_ss_cap_descriptor *ss_cap; + + ss_cap = cdev->req->buf + le16_to_cpu(bos->wTotalLength); + bos->bNumDeviceCaps++; + le16_add_cpu(&bos->wTotalLength, USB_DT_USB_SS_CAP_SIZE); + ss_cap->bLength = USB_DT_USB_SS_CAP_SIZE; + ss_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY; + ss_cap->bDevCapabilityType = USB_SS_CAP_TYPE; + ss_cap->bmAttributes = 0; /* LTM is not supported yet */ + ss_cap->wSpeedSupported = cpu_to_le16(USB_LOW_SPEED_OPERATION | + USB_FULL_SPEED_OPERATION | + USB_HIGH_SPEED_OPERATION | + USB_5GBPS_OPERATION); + ss_cap->bFunctionalitySupport = USB_LOW_SPEED_OPERATION; + + /* Get Controller configuration */ + if (cdev->gadget->ops->get_config_params) { + cdev->gadget->ops->get_config_params( + &dcd_config_params); + } else { + dcd_config_params.bU1devExitLat = + USB_DEFAULT_U1_DEV_EXIT_LAT; + dcd_config_params.bU2DevExitLat = + cpu_to_le16(USB_DEFAULT_U2_DEV_EXIT_LAT); + } + ss_cap->bU1devExitLat = dcd_config_params.bU1devExitLat; + ss_cap->bU2DevExitLat = dcd_config_params.bU2DevExitLat; } - ss_cap->bU1devExitLat = dcd_config_params.bU1devExitLat; - ss_cap->bU2DevExitLat = dcd_config_params.bU2DevExitLat; /* The SuperSpeedPlus USB Device Capability descriptor */ if (gadget_is_superspeed_plus(cdev->gadget)) { -- cgit v1.2.3 From a9548c55295a4268f9187e1ec93264a0682fa745 Mon Sep 17 00:00:00 2001 From: John Youn Date: Fri, 28 Apr 2017 12:55:20 +0400 Subject: usb: gadget: Allow a non-SuperSpeed gadget to support LPM This commit allows a gadget that does not support SuperSpeed to indicate that it supports LPM. It does this by setting the 'lpm_capable' flag in the gadget structure. If a gadget sets this, the composite gadget framework will set the bcdUSB to 0x0201 to indicate that this supports BOS descriptors, and also return a USB 2.0 Extension descriptor as part of the BOS descriptor set. See USB 2.0 LPM ECN Section 3. Signed-off-by: John Youn Signed-off-by: Sevak Arakelyan Signed-off-by: Felipe Balbi --- drivers/usb/gadget/composite.c | 8 ++++++-- include/linux/usb/gadget.h | 3 +++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index abec93ab81ee..d62f53d7f418 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -1608,7 +1608,10 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) cdev->desc.bcdUSB = cpu_to_le16(0x0210); } } else { - cdev->desc.bcdUSB = cpu_to_le16(0x0200); + if (gadget->lpm_capable) + cdev->desc.bcdUSB = cpu_to_le16(0x0201); + else + cdev->desc.bcdUSB = cpu_to_le16(0x0200); } value = min(w_length, (u16) sizeof cdev->desc); @@ -1639,7 +1642,8 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) value = min(w_length, (u16) value); break; case USB_DT_BOS: - if (gadget_is_superspeed(gadget)) { + if (gadget_is_superspeed(gadget) || + gadget->lpm_capable) { value = bos_desc(cdev); value = min(w_length, (u16) value); } diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h index fbc22a39e7bc..3ee5f2a7c0b4 100644 --- a/include/linux/usb/gadget.h +++ b/include/linux/usb/gadget.h @@ -352,6 +352,8 @@ struct usb_gadget_ops { * @deactivated: True if gadget is deactivated - in deactivated state it cannot * be connected. * @connected: True if gadget is connected. + * @lpm_capable: If the gadget max_speed is FULL or HIGH, this flag + * indicates that it supports LPM as per the LPM ECN & errata. * * Gadgets have a mostly-portable "gadget driver" implementing device * functions, handling all usb configurations and interfaces. Gadget @@ -404,6 +406,7 @@ struct usb_gadget { unsigned is_selfpowered:1; unsigned deactivated:1; unsigned connected:1; + unsigned lpm_capable:1; }; #define work_to_gadget(w) (container_of((w), struct usb_gadget, work)) -- cgit v1.2.3 From 11e1d25db643da9d620bfc53b14ff7151220b5fb Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 2 Jun 2017 16:36:25 +0300 Subject: xhci: remove unused stopped_td pointer We no longer keep track of where we stopped in a stopped_td pointer. We get the ring dequeue pointer from the endpoint or stream context Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 4 ---- drivers/usb/host/xhci.h | 1 - 2 files changed, 5 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 03f63f50afb6..ffb1c47cedc3 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -715,7 +715,6 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id, if (list_empty(&ep->cancelled_td_list)) { xhci_stop_watchdog_timer_in_irq(xhci, ep); - ep->stopped_td = NULL; ring_doorbell_for_active_rings(xhci, slot_id, ep_index); return; } @@ -780,8 +779,6 @@ remove_finished_td: ring_doorbell_for_active_rings(xhci, slot_id, ep_index); } - ep->stopped_td = NULL; - /* * Drop the lock and complete the URBs in the cancelled TD list. * New TDs to be cancelled might be added to the end of the list before @@ -1935,7 +1932,6 @@ static int finish_td(struct xhci_hcd *xhci, struct xhci_td *td, * stopped TDs. A stopped TD may be restarted, so don't update * the ring dequeue pointer or take this TD off any lists yet. */ - ep->stopped_td = td; return 0; } if (trb_comp_code == COMP_STALL_ERROR || diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 73a28a986d5e..12df1323212b 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -924,7 +924,6 @@ struct xhci_virt_ep { #define EP_GETTING_NO_STREAMS (1 << 5) /* ---- Related to URB cancellation ---- */ struct list_head cancelled_td_list; - struct xhci_td *stopped_td; unsigned int stopped_stream; /* Watchdog timer for stop endpoint command to cancel URBs */ struct timer_list stop_cmd_timer; -- cgit v1.2.3 From ed18c5fa945768a9bec994e786edbbbc7695acf6 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 2 Jun 2017 16:36:26 +0300 Subject: usb: optimize acpi companion search for usb port devices This optimization significantly reduces xhci driver load time. In ACPI tables the acpi companion port devices are children of the hub device. The port devices are identified by their port number returned by the ACPI _ADR method. _ADR 0 is reserved for the root hub device. The current implementation to find a acpi companion port device loops through all acpi port devices under that parent hub, evaluating their _ADR method each time a new port device is added. for a xHC controller with 25 ports under its roothub it will end up invoking ACPI bytecode 625 times before all ports are ready, making it really slow. The _ADR values are already read and cached earler. So instead of running the bytecode again we can check the cached _ADR value first, and then fall back to the old way. As one of the more significant changes, the xhci load time on Intel kabylake reduced by 70%, (28ms) from initcall xhci_pci_init+0x0/0x49 returned 0 after 39537 usecs to initcall xhci_pci_init+0x0/0x49 returned 0 after 11270 usecs Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/usb-acpi.c | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/drivers/usb/core/usb-acpi.c b/drivers/usb/core/usb-acpi.c index 2776cfe64c09..ef9cf4a21afe 100644 --- a/drivers/usb/core/usb-acpi.c +++ b/drivers/usb/core/usb-acpi.c @@ -127,6 +127,22 @@ out: */ #define USB_ACPI_LOCATION_VALID (1 << 31) +static struct acpi_device *usb_acpi_find_port(struct acpi_device *parent, + int raw) +{ + struct acpi_device *adev; + + if (!parent) + return NULL; + + list_for_each_entry(adev, &parent->children, node) { + if (acpi_device_adr(adev) == raw) + return adev; + } + + return acpi_find_child_device(parent, raw, false); +} + static struct acpi_device *usb_acpi_find_companion(struct device *dev) { struct usb_device *udev; @@ -174,8 +190,10 @@ static struct acpi_device *usb_acpi_find_companion(struct device *dev) int raw; raw = usb_hcd_find_raw_port_number(hcd, port1); - adev = acpi_find_child_device(ACPI_COMPANION(&udev->dev), - raw, false); + + adev = usb_acpi_find_port(ACPI_COMPANION(&udev->dev), + raw); + if (!adev) return NULL; } else { @@ -186,7 +204,9 @@ static struct acpi_device *usb_acpi_find_companion(struct device *dev) return NULL; acpi_bus_get_device(parent_handle, &adev); - adev = acpi_find_child_device(adev, port1, false); + + adev = usb_acpi_find_port(adev, port1); + if (!adev) return NULL; } -- cgit v1.2.3 From cdd504e11391f0811a681d6efc680ca1e1c8ffac Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 2 Jun 2017 16:36:24 +0300 Subject: xhci: Find out where an endpoint or stream stopped from its context. When xHC is asked to stop an endpoint it will save the position it stopped on in the endpoint or stream context. xhci driver needs to know if the controller stopped on the exact same TRB that the driver was asked to cancel as it then needs to move past the TD instead of turning the TD to no-op TRBs. xhci driver used to get the stopped position from a "stopped" transfer event before the stop endpoint command completed, but if the ring is already stopped, or in a halted or error state this event is missing. Get the stopped position from the endpoint or stream context instead Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index ffb1c47cedc3..8e73dad0a733 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -691,7 +691,7 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id, struct xhci_td *last_unlinked_td; struct xhci_ep_ctx *ep_ctx; struct xhci_virt_device *vdev; - + u64 hw_deq; struct xhci_dequeue_state deq_state; if (unlikely(TRB_TO_SUSPEND_PORT(le32_to_cpu(trb->generic.field[3])))) { @@ -752,12 +752,19 @@ static void xhci_handle_cmd_stop_ep(struct xhci_hcd *xhci, int slot_id, * If we stopped on the TD we need to cancel, then we have to * move the xHC endpoint ring dequeue pointer past this TD. */ - if (cur_td == ep->stopped_td) + hw_deq = xhci_get_hw_deq(xhci, vdev, ep_index, + cur_td->urb->stream_id); + hw_deq &= ~0xf; + + if (trb_in_td(xhci, cur_td->start_seg, cur_td->first_trb, + cur_td->last_trb, hw_deq, false)) { xhci_find_new_dequeue_state(xhci, slot_id, ep_index, - cur_td->urb->stream_id, - cur_td, &deq_state); - else + cur_td->urb->stream_id, + cur_td, &deq_state); + } else { td_to_noop(xhci, ep_ring, cur_td, false); + } + remove_finished_td: /* * The event handler won't see a completion for this TD anymore, -- cgit v1.2.3 From 8790736dbf2676ea398d8de952c791009290e3cc Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 2 Jun 2017 16:36:23 +0300 Subject: xhci: Add stream id to xhci_dequeue_state structure The values for the new dequeue segment, new dequeue pointer and new cycle state are needed for manually moving the xHC ring dequeue pointer. These are conveniently stored in a xhci_dequeue_state structure. stream support was added later and stream_id was carried as a function parameter. Move the stream_id to the xhci_dequeue_state structure instead. Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 10 +++++----- drivers/usb/host/xhci.c | 2 +- drivers/usb/host/xhci.h | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 8e73dad0a733..59be9b532100 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -483,7 +483,7 @@ struct xhci_ring *xhci_triad_to_transfer_ring(struct xhci_hcd *xhci, /* * Move the xHC's endpoint ring dequeue pointer past cur_td. * Record the new state of the xHC's endpoint ring dequeue segment, - * dequeue pointer, and new consumer cycle state in state. + * dequeue pointer, stream id, and new consumer cycle state in state. * Update our internal representation of the ring's dequeue pointer. * * We do this in three jumps: @@ -539,6 +539,7 @@ void xhci_find_new_dequeue_state(struct xhci_hcd *xhci, new_seg = ep_ring->deq_seg; new_deq = ep_ring->dequeue; state->new_cycle_state = hw_dequeue & 0x1; + state->stream_id = stream_id; /* * We want to find the pointer, segment and cycle state of the new trb @@ -779,7 +780,7 @@ remove_finished_td: /* If necessary, queue a Set Transfer Ring Dequeue Pointer command */ if (deq_state.new_deq_ptr && deq_state.new_deq_seg) { xhci_queue_new_dequeue_state(xhci, slot_id, ep_index, - ep->stopped_td->urb->stream_id, &deq_state); + &deq_state); xhci_ring_cmd_db(xhci); } else { /* Otherwise ring the doorbell(s) to restart queued transfers */ @@ -3966,13 +3967,12 @@ int xhci_queue_stop_endpoint(struct xhci_hcd *xhci, struct xhci_command *cmd, /* Set Transfer Ring Dequeue Pointer command */ void xhci_queue_new_dequeue_state(struct xhci_hcd *xhci, unsigned int slot_id, unsigned int ep_index, - unsigned int stream_id, struct xhci_dequeue_state *deq_state) { dma_addr_t addr; u32 trb_slot_id = SLOT_ID_FOR_TRB(slot_id); u32 trb_ep_index = EP_ID_FOR_TRB(ep_index); - u32 trb_stream_id = STREAM_ID_FOR_TRB(stream_id); + u32 trb_stream_id = STREAM_ID_FOR_TRB(deq_state->stream_id); u32 trb_sct = 0; u32 type = TRB_TYPE(TRB_SET_DEQ); struct xhci_virt_ep *ep; @@ -4010,7 +4010,7 @@ void xhci_queue_new_dequeue_state(struct xhci_hcd *xhci, ep->queued_deq_seg = deq_state->new_deq_seg; ep->queued_deq_ptr = deq_state->new_deq_ptr; - if (stream_id) + if (deq_state->stream_id) trb_sct = SCT_FOR_TRB(SCT_PRI_TR); ret = queue_command(xhci, cmd, lower_32_bits(addr) | trb_sct | deq_state->new_cycle_state, diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 30f47d92a610..2d6a98c1dc12 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -2849,7 +2849,7 @@ void xhci_cleanup_stalled_ring(struct xhci_hcd *xhci, xhci_dbg_trace(xhci, trace_xhci_dbg_reset_ep, "Queueing new dequeue state"); xhci_queue_new_dequeue_state(xhci, udev->slot_id, - ep_index, ep->stopped_stream, &deq_state); + ep_index, &deq_state); } else { /* Better hope no one uses the input context between now and the * reset endpoint completion! diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 12df1323212b..886f150bad0f 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1526,6 +1526,7 @@ struct xhci_dequeue_state { struct xhci_segment *new_deq_seg; union xhci_trb *new_deq_ptr; int new_cycle_state; + unsigned int stream_id; }; enum xhci_ring_type { @@ -2052,7 +2053,6 @@ void xhci_find_new_dequeue_state(struct xhci_hcd *xhci, struct xhci_dequeue_state *state); void xhci_queue_new_dequeue_state(struct xhci_hcd *xhci, unsigned int slot_id, unsigned int ep_index, - unsigned int stream_id, struct xhci_dequeue_state *deq_state); void xhci_cleanup_stalled_ring(struct xhci_hcd *xhci, unsigned int ep_index, struct xhci_td *td); -- cgit v1.2.3 From e6b20121c6d5d17d38ea93d1ec550713142e54c2 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Fri, 2 Jun 2017 16:36:22 +0300 Subject: xhci: Add helper to get hardware dequeue pointer for stopped rings. Add xhci_get_hw_deq() helper to retrieve the hardware dequeue pointer an endpoint or stream stopped on. Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 59be9b532100..3e27754426ec 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -480,6 +480,30 @@ struct xhci_ring *xhci_triad_to_transfer_ring(struct xhci_hcd *xhci, return NULL; } + +/* + * Get the hw dequeue pointer xHC stopped on, either directly from the + * endpoint context, or if streams are in use from the stream context. + * The returned hw_dequeue contains the lowest four bits with cycle state + * and possbile stream context type. + */ +static u64 xhci_get_hw_deq(struct xhci_hcd *xhci, struct xhci_virt_device *vdev, + unsigned int ep_index, unsigned int stream_id) +{ + struct xhci_ep_ctx *ep_ctx; + struct xhci_stream_ctx *st_ctx; + struct xhci_virt_ep *ep; + + ep = &vdev->eps[ep_index]; + + if (ep->ep_state & EP_HAS_STREAMS) { + st_ctx = &ep->stream_info->stream_ctx_array[stream_id]; + return le64_to_cpu(st_ctx->stream_ring); + } + ep_ctx = xhci_get_ep_ctx(xhci, vdev->out_ctx, ep_index); + return le64_to_cpu(ep_ctx->deq); +} + /* * Move the xHC's endpoint ring dequeue pointer past cur_td. * Record the new state of the xHC's endpoint ring dequeue segment, @@ -521,21 +545,11 @@ void xhci_find_new_dequeue_state(struct xhci_hcd *xhci, stream_id); return; } - /* Dig out the cycle state saved by the xHC during the stop ep cmd */ xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb, "Finding endpoint context"); - /* 4.6.9 the css flag is written to the stream context for streams */ - if (ep->ep_state & EP_HAS_STREAMS) { - struct xhci_stream_ctx *ctx = - &ep->stream_info->stream_ctx_array[stream_id]; - hw_dequeue = le64_to_cpu(ctx->stream_ring); - } else { - struct xhci_ep_ctx *ep_ctx - = xhci_get_ep_ctx(xhci, dev->out_ctx, ep_index); - hw_dequeue = le64_to_cpu(ep_ctx->deq); - } + hw_dequeue = xhci_get_hw_deq(xhci, dev, ep_index, stream_id); new_seg = ep_ring->deq_seg; new_deq = ep_ring->dequeue; state->new_cycle_state = hw_dequeue & 0x1; -- cgit v1.2.3 From 7ee4ce6e933fbb2e5db2c39977b847704589575e Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Tue, 16 May 2017 15:26:11 +0300 Subject: usb: typec: update partner power delivery support with opmode If USB PD contract is established after creation of the partner, the power delivery support attribute of the partner needs to be updated separately. This can be done in typec_set_pwr_opmode() by checking if the port has already partner and updating the value if it does. Signed-off-by: Heikki Krogerus Reviewed-by: Guenter Roeck Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/typec.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/drivers/usb/typec/typec.c b/drivers/usb/typec/typec.c index db5ee730ad24..1a6822ee1a52 100644 --- a/drivers/usb/typec/typec.c +++ b/drivers/usb/typec/typec.c @@ -1124,6 +1124,11 @@ void typec_set_vconn_role(struct typec_port *port, enum typec_role role) } EXPORT_SYMBOL_GPL(typec_set_vconn_role); +static int partner_match(struct device *dev, void *data) +{ + return is_typec_partner(dev); +} + /** * typec_set_pwr_opmode - Report changed power operation mode * @port: The USB Type-C Port where the mode was changed @@ -1137,12 +1142,26 @@ EXPORT_SYMBOL_GPL(typec_set_vconn_role); void typec_set_pwr_opmode(struct typec_port *port, enum typec_pwr_opmode opmode) { + struct device *partner_dev; + if (port->pwr_opmode == opmode) return; port->pwr_opmode = opmode; sysfs_notify(&port->dev.kobj, NULL, "power_operation_mode"); kobject_uevent(&port->dev.kobj, KOBJ_CHANGE); + + partner_dev = device_find_child(&port->dev, NULL, partner_match); + if (partner_dev) { + struct typec_partner *partner = to_typec_partner(partner_dev); + + if (opmode == TYPEC_PWR_MODE_PD && !partner->usb_pd) { + partner->usb_pd = 1; + sysfs_notify(&partner_dev->kobj, NULL, + "supports_usb_power_delivery"); + } + put_device(partner_dev); + } } EXPORT_SYMBOL_GPL(typec_set_pwr_opmode); -- cgit v1.2.3 From bab3548078237706f53baafe43ae58257225549d Mon Sep 17 00:00:00 2001 From: Badhri Jagan Sridharan Date: Tue, 30 May 2017 12:39:53 -0700 Subject: usb: typec: Add a sysfs node to manage port type User space applications in some cases have the need to enforce a specific port type(DFP/UFP/DRP). This change allows userspace to attempt setting the desired port type. Low level drivers can however reject the request if the specific port type is not supported. Signed-off-by: Badhri Jagan Sridharan Reviewed-by: Guenter Roeck Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-class-typec | 15 ++++ drivers/usb/typec/typec.c | 106 +++++++++++++++++++++++++--- include/linux/usb/typec.h | 4 ++ 3 files changed, 115 insertions(+), 10 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-class-typec b/Documentation/ABI/testing/sysfs-class-typec index d4a3d23eb09c..5be552e255e9 100644 --- a/Documentation/ABI/testing/sysfs-class-typec +++ b/Documentation/ABI/testing/sysfs-class-typec @@ -30,6 +30,21 @@ Description: Valid values: source, sink +What: /sys/class/typec//port_type +Date: May 2017 +Contact: Badhri Jagan Sridharan +Description: + Indicates the type of the port. This attribute can be used for + requesting a change in the port type. Port type change is + supported as a synchronous operation, so write(2) to the + attribute will not return until the operation has finished. + + Valid values: + - source (The port will behave as source only DFP port) + - sink (The port will behave as sink only UFP port) + - dual (The port will behave as dual-role-data and + dual-role-power port) + What: /sys/class/typec//vconn_source Date: April 2017 Contact: Heikki Krogerus diff --git a/drivers/usb/typec/typec.c b/drivers/usb/typec/typec.c index 1a6822ee1a52..24e355ba109d 100644 --- a/drivers/usb/typec/typec.c +++ b/drivers/usb/typec/typec.c @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -69,6 +70,8 @@ struct typec_port { enum typec_role pwr_role; enum typec_role vconn_role; enum typec_pwr_opmode pwr_opmode; + enum typec_port_type port_type; + struct mutex port_type_lock; const struct typec_capability *cap; }; @@ -790,6 +793,18 @@ static const char * const typec_data_roles[] = { [TYPEC_HOST] = "host", }; +static const char * const typec_port_types[] = { + [TYPEC_PORT_DFP] = "source", + [TYPEC_PORT_UFP] = "sink", + [TYPEC_PORT_DRP] = "dual", +}; + +static const char * const typec_port_types_drp[] = { + [TYPEC_PORT_DFP] = "dual [source] sink", + [TYPEC_PORT_UFP] = "dual source [sink]", + [TYPEC_PORT_DRP] = "[dual] source sink", +}; + static ssize_t preferred_role_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) @@ -847,11 +862,6 @@ static ssize_t data_role_store(struct device *dev, struct typec_port *port = to_typec_port(dev); int ret; - if (port->cap->type != TYPEC_PORT_DRP) { - dev_dbg(dev, "data role swap only supported with DRP ports\n"); - return -EOPNOTSUPP; - } - if (!port->cap->dr_set) { dev_dbg(dev, "data role swapping not supported\n"); return -EOPNOTSUPP; @@ -861,11 +871,22 @@ static ssize_t data_role_store(struct device *dev, if (ret < 0) return ret; + mutex_lock(&port->port_type_lock); + if (port->port_type != TYPEC_PORT_DRP) { + dev_dbg(dev, "port type fixed at \"%s\"", + typec_port_types[port->port_type]); + ret = -EOPNOTSUPP; + goto unlock_and_ret; + } + ret = port->cap->dr_set(port->cap, ret); if (ret) - return ret; + goto unlock_and_ret; - return size; + ret = size; +unlock_and_ret: + mutex_unlock(&port->port_type_lock); + return ret; } static ssize_t data_role_show(struct device *dev, @@ -886,7 +907,7 @@ static ssize_t power_role_store(struct device *dev, const char *buf, size_t size) { struct typec_port *port = to_typec_port(dev); - int ret = size; + int ret; if (!port->cap->pd_revision) { dev_dbg(dev, "USB Power Delivery not supported\n"); @@ -907,11 +928,22 @@ static ssize_t power_role_store(struct device *dev, if (ret < 0) return ret; + mutex_lock(&port->port_type_lock); + if (port->port_type != TYPEC_PORT_DRP) { + dev_dbg(dev, "port type fixed at \"%s\"", + typec_port_types[port->port_type]); + ret = -EOPNOTSUPP; + goto unlock_and_ret; + } + ret = port->cap->pr_set(port->cap, ret); if (ret) - return ret; + goto unlock_and_ret; - return size; + ret = size; +unlock_and_ret: + mutex_unlock(&port->port_type_lock); + return ret; } static ssize_t power_role_show(struct device *dev, @@ -927,6 +959,57 @@ static ssize_t power_role_show(struct device *dev, } static DEVICE_ATTR_RW(power_role); +static ssize_t +port_type_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct typec_port *port = to_typec_port(dev); + int ret; + enum typec_port_type type; + + if (!port->cap->port_type_set || port->cap->type != TYPEC_PORT_DRP) { + dev_dbg(dev, "changing port type not supported\n"); + return -EOPNOTSUPP; + } + + ret = sysfs_match_string(typec_port_types, buf); + if (ret < 0) + return ret; + + type = ret; + mutex_lock(&port->port_type_lock); + + if (port->port_type == type) { + ret = size; + goto unlock_and_ret; + } + + ret = port->cap->port_type_set(port->cap, type); + if (ret) + goto unlock_and_ret; + + port->port_type = type; + ret = size; + +unlock_and_ret: + mutex_unlock(&port->port_type_lock); + return ret; +} + +static ssize_t +port_type_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct typec_port *port = to_typec_port(dev); + + if (port->cap->type == TYPEC_PORT_DRP) + return sprintf(buf, "%s\n", + typec_port_types_drp[port->port_type]); + + return sprintf(buf, "[%s]\n", typec_port_types[port->cap->type]); +} +static DEVICE_ATTR_RW(port_type); + static const char * const typec_pwr_opmodes[] = { [TYPEC_PWR_MODE_USB] = "default", [TYPEC_PWR_MODE_1_5A] = "1.5A", @@ -1036,6 +1119,7 @@ static struct attribute *typec_attrs[] = { &dev_attr_usb_power_delivery_revision.attr, &dev_attr_usb_typec_revision.attr, &dev_attr_vconn_source.attr, + &dev_attr_port_type.attr, NULL, }; ATTRIBUTE_GROUPS(typec); @@ -1231,6 +1315,8 @@ struct typec_port *typec_register_port(struct device *parent, port->id = id; port->cap = cap; + port->port_type = cap->type; + mutex_init(&port->port_type_lock); port->prefer_role = cap->prefer_role; port->dev.class = typec_class; diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h index d1d2ebcf36ec..ffe7487886ca 100644 --- a/include/linux/usb/typec.h +++ b/include/linux/usb/typec.h @@ -190,6 +190,7 @@ struct typec_partner_desc { * @pr_set: Set Power Role * @vconn_set: Set VCONN Role * @activate_mode: Enter/exit given Alternate Mode + * @port_type_set: Set port type * * Static capabilities of a single USB Type-C port. */ @@ -214,6 +215,9 @@ struct typec_capability { int (*activate_mode)(const struct typec_capability *, int mode, int activate); + int (*port_type_set)(const struct typec_capability *, + enum typec_port_type); + }; /* Specific to try_role(). Indicates the user want's to clear the preference. */ -- cgit v1.2.3 From fa72e6afa795dbb35d0cc6332606e83e4415e45e Mon Sep 17 00:00:00 2001 From: Mariusz Skamra Date: Fri, 26 May 2017 12:15:59 +0200 Subject: usb: Make use of ktime_* comparison functions Start using ktime_* compare functions to make the code backportable. Now that may be a bit tricky due to recent change of ktime_t. Signed-off-by: Mariusz Skamra Acked-by: Kuppuswamy Sathyanarayanan Acked-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/otg_fsm.c | 8 ++++---- drivers/usb/host/ehci-timer.c | 2 +- drivers/usb/host/fotg210-hcd.c | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/usb/chipidea/otg_fsm.c b/drivers/usb/chipidea/otg_fsm.c index 93e24ce61a3a..949183ede16f 100644 --- a/drivers/usb/chipidea/otg_fsm.c +++ b/drivers/usb/chipidea/otg_fsm.c @@ -234,7 +234,7 @@ static void ci_otg_add_timer(struct ci_hdrc *ci, enum otg_fsm_timer t) ktime_set(timer_sec, timer_nsec)); ci->enabled_otg_timer_bits |= (1 << t); if ((ci->next_otg_timer == NUM_OTG_FSM_TIMERS) || - (ci->hr_timeouts[ci->next_otg_timer] > + ktime_after(ci->hr_timeouts[ci->next_otg_timer], ci->hr_timeouts[t])) { ci->next_otg_timer = t; hrtimer_start_range_ns(&ci->otg_fsm_hrtimer, @@ -269,7 +269,7 @@ static void ci_otg_del_timer(struct ci_hdrc *ci, enum otg_fsm_timer t) for_each_set_bit(cur_timer, &enabled_timer_bits, NUM_OTG_FSM_TIMERS) { if ((next_timer == NUM_OTG_FSM_TIMERS) || - (ci->hr_timeouts[next_timer] < + ktime_before(ci->hr_timeouts[next_timer], ci->hr_timeouts[cur_timer])) next_timer = cur_timer; } @@ -397,13 +397,13 @@ static enum hrtimer_restart ci_otg_hrtimer_func(struct hrtimer *t) now = ktime_get(); for_each_set_bit(cur_timer, &enabled_timer_bits, NUM_OTG_FSM_TIMERS) { - if (now >= ci->hr_timeouts[cur_timer]) { + if (ktime_compare(now, ci->hr_timeouts[cur_timer]) >= 0) { ci->enabled_otg_timer_bits &= ~(1 << cur_timer); if (otg_timer_handlers[cur_timer]) ret = otg_timer_handlers[cur_timer](ci); } else { if ((next_timer == NUM_OTG_FSM_TIMERS) || - (ci->hr_timeouts[cur_timer] < + ktime_before(ci->hr_timeouts[cur_timer], ci->hr_timeouts[next_timer])) next_timer = cur_timer; } diff --git a/drivers/usb/host/ehci-timer.c b/drivers/usb/host/ehci-timer.c index 3893b5bafd87..0b6cdb723192 100644 --- a/drivers/usb/host/ehci-timer.c +++ b/drivers/usb/host/ehci-timer.c @@ -424,7 +424,7 @@ static enum hrtimer_restart ehci_hrtimer_func(struct hrtimer *t) */ now = ktime_get(); for_each_set_bit(e, &events, EHCI_HRTIMER_NUM_EVENTS) { - if (now >= ehci->hr_timeouts[e]) + if (ktime_compare(now, ehci->hr_timeouts[e]) >= 0) event_handlers[e](ehci); else ehci_enable_event(ehci, e, false); diff --git a/drivers/usb/host/fotg210-hcd.c b/drivers/usb/host/fotg210-hcd.c index ced08dc229ad..457cc6525abd 100644 --- a/drivers/usb/host/fotg210-hcd.c +++ b/drivers/usb/host/fotg210-hcd.c @@ -1380,7 +1380,7 @@ static enum hrtimer_restart fotg210_hrtimer_func(struct hrtimer *t) */ now = ktime_get(); for_each_set_bit(e, &events, FOTG210_HRTIMER_NUM_EVENTS) { - if (now >= fotg210->hr_timeouts[e]) + if (ktime_compare(now, fotg210->hr_timeouts[e]) >= 0) event_handlers[e](fotg210); else fotg210_enable_event(fotg210, e, false); -- cgit v1.2.3 From 0aa0b93e7af663b6ca9d9ae31d1b73f2c36ddf46 Mon Sep 17 00:00:00 2001 From: Tony Lindgren Date: Thu, 25 May 2017 09:13:31 -0700 Subject: usb: host: ohci-platform: Add basic runtime PM support This is needed in preparation of adding support for omap3 and later OHCI. The runtime PM will only do something on platforms that implement it. Cc: devicetree@vger.kernel.org Cc: Hans de Goede Cc: Rob Herring Cc: Sebastian Reichel Cc: Yoshihiro Shimoda Signed-off-by: Tony Lindgren Acked-by: Roger Quadros Acked-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ohci-platform.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/usb/host/ohci-platform.c b/drivers/usb/host/ohci-platform.c index 6368fce43197..589177dca8ca 100644 --- a/drivers/usb/host/ohci-platform.c +++ b/drivers/usb/host/ohci-platform.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -242,6 +243,8 @@ static int ohci_platform_probe(struct platform_device *dev) } #endif + pm_runtime_set_active(&dev->dev); + pm_runtime_enable(&dev->dev); if (pdata->power_on) { err = pdata->power_on(dev); if (err < 0) @@ -271,6 +274,7 @@ err_power: if (pdata->power_off) pdata->power_off(dev); err_reset: + pm_runtime_disable(&dev->dev); while (--rst >= 0) reset_control_assert(priv->resets[rst]); err_put_clks: @@ -292,6 +296,7 @@ static int ohci_platform_remove(struct platform_device *dev) struct ohci_platform_priv *priv = hcd_to_ohci_priv(hcd); int clk, rst; + pm_runtime_get_sync(&dev->dev); usb_remove_hcd(hcd); if (pdata->power_off) @@ -305,6 +310,9 @@ static int ohci_platform_remove(struct platform_device *dev) usb_put_hcd(hcd); + pm_runtime_put_sync(&dev->dev); + pm_runtime_disable(&dev->dev); + if (pdata == &ohci_platform_defaults) dev->dev.platform_data = NULL; -- cgit v1.2.3 From 2545d85301cd646965bf21b21d51d079e7e6ef4b Mon Sep 17 00:00:00 2001 From: Tony Lindgren Date: Thu, 25 May 2017 09:13:32 -0700 Subject: usb: host: ohci-platform: Add support for omap3 and later With the runtime PM implemented for ohci-platform driver, we can now support omap3 and later OHCI by adding one device tree property. Cc: Hans de Goede Cc: Rob Herring Cc: Yoshihiro Shimoda Cc: Sebastian Reichel Acked-by: Alan Stern Acked-by: Roger Quadros Signed-off-by: Tony Lindgren Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/usb/usb-ohci.txt | 1 + drivers/usb/host/ohci-platform.c | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/usb-ohci.txt b/Documentation/devicetree/bindings/usb/usb-ohci.txt index 9df456968596..e8766b08c93b 100644 --- a/Documentation/devicetree/bindings/usb/usb-ohci.txt +++ b/Documentation/devicetree/bindings/usb/usb-ohci.txt @@ -10,6 +10,7 @@ Optional properties: - big-endian-desc : boolean, set this for hcds with big-endian descriptors - big-endian : boolean, for hcds with big-endian-regs + big-endian-desc - no-big-frame-no : boolean, set if frame_no lives in bits [15:0] of HCCA +- remote-wakeup-connected: remote wakeup is wired on the platform - num-ports : u32, to override the detected port count - clocks : a list of phandle + clock specifier pairs - phys : phandle + phy specifier pair diff --git a/drivers/usb/host/ohci-platform.c b/drivers/usb/host/ohci-platform.c index 589177dca8ca..61fe2b985070 100644 --- a/drivers/usb/host/ohci-platform.c +++ b/drivers/usb/host/ohci-platform.c @@ -164,6 +164,10 @@ static int ohci_platform_probe(struct platform_device *dev) if (of_property_read_bool(dev->dev.of_node, "no-big-frame-no")) ohci->flags |= OHCI_QUIRK_FRAME_NO; + if (of_property_read_bool(dev->dev.of_node, + "remote-wakeup-connected")) + ohci->hc_control = OHCI_CTRL_RWC; + of_property_read_u32(dev->dev.of_node, "num-ports", &ohci->num_ports); @@ -358,6 +362,7 @@ static int ohci_platform_resume(struct device *dev) static const struct of_device_id ohci_platform_ids[] = { { .compatible = "generic-ohci", }, { .compatible = "cavium,octeon-6335-ohci", }, + { .compatible = "ti,ohci-omap3", }, { } }; MODULE_DEVICE_TABLE(of, ohci_platform_ids); -- cgit v1.2.3 From ef189c8dde9a82962ddb2511ee00ab8d4719dfca Mon Sep 17 00:00:00 2001 From: Tony Lindgren Date: Thu, 25 May 2017 09:13:33 -0700 Subject: usb: host: ohci-omap3: Remove driver in favor of ohci-platform This driver is no longer needed and can be removed. The reason why it's safe to remove this driver is that most omap devices don't have a USB low-speed or full-speed compatible PHY installed and configured with drivers/mfd/omap-usb-host.c. This means that devices like beagleboard and pandaboard need to use a high-speed USB hub in order to use devices like keyboard and mice. Currently the only known configured for a full-speed PHY is the mdm6600 modem on droid 4 and I've verified it works just fine with ohci-platform. Acked-by: Alan Stern Acked-by: Roger Quadros Reviewed-by: Sebastian Reichel Signed-off-by: Tony Lindgren Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/Kconfig | 6 +- drivers/usb/host/Makefile | 1 - drivers/usb/host/ohci-omap3.c | 211 ------------------------------------------ 3 files changed, 5 insertions(+), 213 deletions(-) delete mode 100644 drivers/usb/host/ohci-omap3.c diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 70d32a0cacab..fa5692dec832 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -473,8 +473,12 @@ config USB_OHCI_HCD_AT91 config USB_OHCI_HCD_OMAP3 tristate "OHCI support for OMAP3 and later chips" depends on (ARCH_OMAP3 || ARCH_OMAP4 || SOC_OMAP5) + select USB_OHCI_HCD_PLATFORM default y - ---help--- + help + This option is deprecated now and the driver was removed, use + USB_OHCI_HCD_PLATFORM instead. + Enables support for the on-chip OHCI controller on OMAP3 and later chips. diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index c77b0a38557b..cf2691fffcc0 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -52,7 +52,6 @@ obj-$(CONFIG_USB_OHCI_HCD_PCI) += ohci-pci.o obj-$(CONFIG_USB_OHCI_HCD_PLATFORM) += ohci-platform.o obj-$(CONFIG_USB_OHCI_EXYNOS) += ohci-exynos.o obj-$(CONFIG_USB_OHCI_HCD_OMAP1) += ohci-omap.o -obj-$(CONFIG_USB_OHCI_HCD_OMAP3) += ohci-omap3.o obj-$(CONFIG_USB_OHCI_HCD_SPEAR) += ohci-spear.o obj-$(CONFIG_USB_OHCI_HCD_STI) += ohci-st.o obj-$(CONFIG_USB_OHCI_HCD_AT91) += ohci-at91.o diff --git a/drivers/usb/host/ohci-omap3.c b/drivers/usb/host/ohci-omap3.c deleted file mode 100644 index ec15aebe8786..000000000000 --- a/drivers/usb/host/ohci-omap3.c +++ /dev/null @@ -1,211 +0,0 @@ -/* - * ohci-omap3.c - driver for OHCI on OMAP3 and later processors - * - * Bus Glue for OMAP3 USBHOST 3 port OHCI controller - * This controller is also used in later OMAPs and AM35x chips - * - * Copyright (C) 2007-2010 Texas Instruments, Inc. - * Author: Vikram Pandita - * Author: Anand Gadiyar - * Author: Keshava Munegowda - * - * Based on ehci-omap.c and some other ohci glue layers - * - * 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; either version 2 of the License, or - * (at your option) any later version. - * - * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * TODO (last updated Feb 27, 2011): - * - add kernel-doc - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ohci.h" - -#define DRIVER_DESC "OHCI OMAP3 driver" - -static const char hcd_name[] = "ohci-omap3"; -static struct hc_driver __read_mostly ohci_omap3_hc_driver; - -/* - * configure so an HC device and id are always provided - * always called with process context; sleeping is OK - */ - -/** - * ohci_hcd_omap3_probe - initialize OMAP-based HCDs - * - * Allocates basic resources for this USB host controller, and - * then invokes the start() method for the HCD associated with it - * through the hotplug entry's driver_data. - */ -static int ohci_hcd_omap3_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct ohci_hcd *ohci; - struct usb_hcd *hcd = NULL; - void __iomem *regs = NULL; - struct resource *res; - int ret; - int irq; - - if (usb_disabled()) - return -ENODEV; - - if (!dev->parent) { - dev_err(dev, "Missing parent device\n"); - return -ENODEV; - } - - irq = platform_get_irq(pdev, 0); - if (irq < 0) { - dev_err(dev, "OHCI irq failed\n"); - return -ENODEV; - } - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - dev_err(dev, "UHH OHCI get resource failed\n"); - return -ENOMEM; - } - - regs = ioremap(res->start, resource_size(res)); - if (!regs) { - dev_err(dev, "UHH OHCI ioremap failed\n"); - return -ENOMEM; - } - - /* - * Right now device-tree probed devices don't get dma_mask set. - * Since shared usb code relies on it, set it here for now. - * Once we have dma capability bindings this can go away. - */ - ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32)); - if (ret) - goto err_io; - - ret = -ENODEV; - hcd = usb_create_hcd(&ohci_omap3_hc_driver, dev, - dev_name(dev)); - if (!hcd) { - dev_err(dev, "usb_create_hcd failed\n"); - goto err_io; - } - - hcd->rsrc_start = res->start; - hcd->rsrc_len = resource_size(res); - hcd->regs = regs; - - pm_runtime_enable(dev); - pm_runtime_get_sync(dev); - - ohci = hcd_to_ohci(hcd); - /* - * RemoteWakeupConnected has to be set explicitly before - * calling ohci_run. The reset value of RWC is 0. - */ - ohci->hc_control = OHCI_CTRL_RWC; - - ret = usb_add_hcd(hcd, irq, 0); - if (ret) { - dev_dbg(dev, "failed to add hcd with err %d\n", ret); - goto err_add_hcd; - } - device_wakeup_enable(hcd->self.controller); - - return 0; - -err_add_hcd: - pm_runtime_put_sync(dev); - usb_put_hcd(hcd); - -err_io: - iounmap(regs); - - return ret; -} - -/* - * may be called without controller electrically present - * may be called with controller, bus, and devices active - */ - -/** - * ohci_hcd_omap3_remove - shutdown processing for OHCI HCDs - * @pdev: USB Host Controller being removed - * - * Reverses the effect of ohci_hcd_omap3_probe(), first invoking - * the HCD's stop() method. It is always called from a thread - * context, normally "rmmod", "apmd", or something similar. - */ -static int ohci_hcd_omap3_remove(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct usb_hcd *hcd = dev_get_drvdata(dev); - - iounmap(hcd->regs); - usb_remove_hcd(hcd); - pm_runtime_put_sync(dev); - pm_runtime_disable(dev); - usb_put_hcd(hcd); - return 0; -} - -static const struct of_device_id omap_ohci_dt_ids[] = { - { .compatible = "ti,ohci-omap3" }, - { } -}; - -MODULE_DEVICE_TABLE(of, omap_ohci_dt_ids); - -static struct platform_driver ohci_hcd_omap3_driver = { - .probe = ohci_hcd_omap3_probe, - .remove = ohci_hcd_omap3_remove, - .shutdown = usb_hcd_platform_shutdown, - .driver = { - .name = "ohci-omap3", - .of_match_table = omap_ohci_dt_ids, - }, -}; - -static int __init ohci_omap3_init(void) -{ - if (usb_disabled()) - return -ENODEV; - - pr_info("%s: " DRIVER_DESC "\n", hcd_name); - - ohci_init_driver(&ohci_omap3_hc_driver, NULL); - return platform_driver_register(&ohci_hcd_omap3_driver); -} -module_init(ohci_omap3_init); - -static void __exit ohci_omap3_cleanup(void) -{ - platform_driver_unregister(&ohci_hcd_omap3_driver); -} -module_exit(ohci_omap3_cleanup); - -MODULE_DESCRIPTION(DRIVER_DESC); -MODULE_ALIAS("platform:ohci-omap3"); -MODULE_AUTHOR("Anand Gadiyar "); -MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 8cda2a0dfefa6ea99be80c7ed6ec57fb4d3d470f Mon Sep 17 00:00:00 2001 From: Martin Blumenstingl Date: Sat, 20 May 2017 15:50:40 +0200 Subject: dt-bindings: phy: meson-gxl-usb2-phy: Add documentation for the Meson GXL USB2 PHY This adds the DT binding documentation for the USB2 PHY(s) found in the Meson GXL and GXM SoCs. Signed-off-by: Martin Blumenstingl Acked-by: Rob Herring Signed-off-by: Kishon Vijay Abraham I --- .../devicetree/bindings/phy/meson-gxl-usb2-phy.txt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 Documentation/devicetree/bindings/phy/meson-gxl-usb2-phy.txt diff --git a/Documentation/devicetree/bindings/phy/meson-gxl-usb2-phy.txt b/Documentation/devicetree/bindings/phy/meson-gxl-usb2-phy.txt new file mode 100644 index 000000000000..a105494a0fc9 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/meson-gxl-usb2-phy.txt @@ -0,0 +1,17 @@ +* Amlogic Meson GXL and GXM USB2 PHY binding + +Required properties: +- compatible: Should be "amlogic,meson-gxl-usb2-phy" +- reg: The base address and length of the registers +- #phys-cells: must be 0 (see phy-bindings.txt in this directory) + +Optional properties: +- phy-supply: see phy-bindings.txt in this directory + + +Example: + usb2_phy0: phy@78000 { + compatible = "amlogic,meson-gxl-usb2-phy"; + #phy-cells = <0>; + reg = <0x0 0x78000 0x0 0x20>; + }; -- cgit v1.2.3 From 697b373c6df682dc6aba10cc1e81a2664c7cbbd4 Mon Sep 17 00:00:00 2001 From: Martin Blumenstingl Date: Sat, 20 May 2017 15:50:41 +0200 Subject: phy: meson: add USB2 PHY support for Meson GXL and GXM This adds a new driver for the USB2 PHYs found on Meson GXL and GXM SoCs (both SoCs are using the same USB PHY register layout). The USB2 PHY is a simple PHY which only has a few registers to configure the mode (host/device) and a reset register (to enable/disable the PHY). Unfortunately there are no datasheets available for this PHY. The driver was written by reading the code from Amlogic's GPL kernel sources and by analyzing the registers on an actual GXL and GXM device running the kernel that was shipped on the boards I have. Signed-off-by: Martin Blumenstingl Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/amlogic/Kconfig | 13 ++ drivers/phy/amlogic/Makefile | 1 + drivers/phy/amlogic/phy-meson-gxl-usb2.c | 273 +++++++++++++++++++++++++++++++ 3 files changed, 287 insertions(+) create mode 100644 drivers/phy/amlogic/phy-meson-gxl-usb2.c diff --git a/drivers/phy/amlogic/Kconfig b/drivers/phy/amlogic/Kconfig index edcd5b65179f..2044211c5b86 100644 --- a/drivers/phy/amlogic/Kconfig +++ b/drivers/phy/amlogic/Kconfig @@ -12,3 +12,16 @@ config PHY_MESON8B_USB2 Enable this to support the Meson USB2 PHYs found in Meson8b and GXBB SoCs. If unsure, say N. + +config PHY_MESON_GXL_USB2 + tristate "Meson GXL and GXM USB2 PHY drivers" + default ARCH_MESON + depends on OF && (ARCH_MESON || COMPILE_TEST) + depends on USB_SUPPORT + select USB_COMMON + select GENERIC_PHY + select REGMAP_MMIO + help + Enable this to support the Meson USB2 PHYs found in Meson + GXL and GXM SoCs. + If unsure, say N. diff --git a/drivers/phy/amlogic/Makefile b/drivers/phy/amlogic/Makefile index 47b6eecc3864..cfdc98715c30 100644 --- a/drivers/phy/amlogic/Makefile +++ b/drivers/phy/amlogic/Makefile @@ -1 +1,2 @@ obj-$(CONFIG_PHY_MESON8B_USB2) += phy-meson8b-usb2.o +obj-$(CONFIG_PHY_MESON_GXL_USB2) += phy-meson-gxl-usb2.o diff --git a/drivers/phy/amlogic/phy-meson-gxl-usb2.c b/drivers/phy/amlogic/phy-meson-gxl-usb2.c new file mode 100644 index 000000000000..e90c4ee25dfe --- /dev/null +++ b/drivers/phy/amlogic/phy-meson-gxl-usb2.c @@ -0,0 +1,273 @@ +/* + * Meson GXL and GXM USB2 PHY driver + * + * Copyright (C) 2017 Martin Blumenstingl + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* bits [31:27] are read-only */ +#define U2P_R0 0x0 + #define U2P_R0_BYPASS_SEL BIT(0) + #define U2P_R0_BYPASS_DM_EN BIT(1) + #define U2P_R0_BYPASS_DP_EN BIT(2) + #define U2P_R0_TXBITSTUFF_ENH BIT(3) + #define U2P_R0_TXBITSTUFF_EN BIT(4) + #define U2P_R0_DM_PULLDOWN BIT(5) + #define U2P_R0_DP_PULLDOWN BIT(6) + #define U2P_R0_DP_VBUS_VLD_EXT_SEL BIT(7) + #define U2P_R0_DP_VBUS_VLD_EXT BIT(8) + #define U2P_R0_ADP_PRB_EN BIT(9) + #define U2P_R0_ADP_DISCHARGE BIT(10) + #define U2P_R0_ADP_CHARGE BIT(11) + #define U2P_R0_DRV_VBUS BIT(12) + #define U2P_R0_ID_PULLUP BIT(13) + #define U2P_R0_LOOPBACK_EN_B BIT(14) + #define U2P_R0_OTG_DISABLE BIT(15) + #define U2P_R0_COMMON_ONN BIT(16) + #define U2P_R0_FSEL_MASK GENMASK(19, 17) + #define U2P_R0_REF_CLK_SEL_MASK GENMASK(21, 20) + #define U2P_R0_POWER_ON_RESET BIT(22) + #define U2P_R0_V_ATE_TEST_EN_B_MASK GENMASK(24, 23) + #define U2P_R0_ID_SET_ID_DQ BIT(25) + #define U2P_R0_ATE_RESET BIT(26) + #define U2P_R0_FSV_MINUS BIT(27) + #define U2P_R0_FSV_PLUS BIT(28) + #define U2P_R0_BYPASS_DM_DATA BIT(29) + #define U2P_R0_BYPASS_DP_DATA BIT(30) + +#define U2P_R1 0x4 + #define U2P_R1_BURN_IN_TEST BIT(0) + #define U2P_R1_ACA_ENABLE BIT(1) + #define U2P_R1_DCD_ENABLE BIT(2) + #define U2P_R1_VDAT_SRC_EN_B BIT(3) + #define U2P_R1_VDAT_DET_EN_B BIT(4) + #define U2P_R1_CHARGES_SEL BIT(5) + #define U2P_R1_TX_PREEMP_PULSE_TUNE BIT(6) + #define U2P_R1_TX_PREEMP_AMP_TUNE_MASK GENMASK(8, 7) + #define U2P_R1_TX_RES_TUNE_MASK GENMASK(10, 9) + #define U2P_R1_TX_RISE_TUNE_MASK GENMASK(12, 11) + #define U2P_R1_TX_VREF_TUNE_MASK GENMASK(16, 13) + #define U2P_R1_TX_FSLS_TUNE_MASK GENMASK(20, 17) + #define U2P_R1_TX_HSXV_TUNE_MASK GENMASK(22, 21) + #define U2P_R1_OTG_TUNE_MASK GENMASK(25, 23) + #define U2P_R1_SQRX_TUNE_MASK GENMASK(28, 26) + #define U2P_R1_COMP_DIS_TUNE_MASK GENMASK(31, 29) + +/* bits [31:14] are read-only */ +#define U2P_R2 0x8 + #define U2P_R2_DATA_IN_MASK GENMASK(3, 0) + #define U2P_R2_DATA_IN_EN_MASK GENMASK(7, 4) + #define U2P_R2_ADDR_MASK GENMASK(11, 8) + #define U2P_R2_DATA_OUT_SEL BIT(12) + #define U2P_R2_CLK BIT(13) + #define U2P_R2_DATA_OUT_MASK GENMASK(17, 14) + #define U2P_R2_ACA_PIN_RANGE_C BIT(18) + #define U2P_R2_ACA_PIN_RANGE_B BIT(19) + #define U2P_R2_ACA_PIN_RANGE_A BIT(20) + #define U2P_R2_ACA_PIN_GND BIT(21) + #define U2P_R2_ACA_PIN_FLOAT BIT(22) + #define U2P_R2_CHARGE_DETECT BIT(23) + #define U2P_R2_DEVICE_SESSION_VALID BIT(24) + #define U2P_R2_ADP_PROBE BIT(25) + #define U2P_R2_ADP_SENSE BIT(26) + #define U2P_R2_SESSION_END BIT(27) + #define U2P_R2_VBUS_VALID BIT(28) + #define U2P_R2_B_VALID BIT(29) + #define U2P_R2_A_VALID BIT(30) + #define U2P_R2_ID_DIG BIT(31) + +#define U2P_R3 0xc + +#define RESET_COMPLETE_TIME 500 + +struct phy_meson_gxl_usb2_priv { + struct regmap *regmap; + enum phy_mode mode; + int is_enabled; +}; + +static const struct regmap_config phy_meson_gxl_usb2_regmap_conf = { + .reg_bits = 8, + .val_bits = 32, + .reg_stride = 4, + .max_register = U2P_R3, +}; + +static int phy_meson_gxl_usb2_reset(struct phy *phy) +{ + struct phy_meson_gxl_usb2_priv *priv = phy_get_drvdata(phy); + + if (priv->is_enabled) { + /* reset the PHY and wait until settings are stabilized */ + regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_POWER_ON_RESET, + U2P_R0_POWER_ON_RESET); + udelay(RESET_COMPLETE_TIME); + regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_POWER_ON_RESET, + 0); + udelay(RESET_COMPLETE_TIME); + } + + return 0; +} + +static int phy_meson_gxl_usb2_set_mode(struct phy *phy, enum phy_mode mode) +{ + struct phy_meson_gxl_usb2_priv *priv = phy_get_drvdata(phy); + + switch (mode) { + case PHY_MODE_USB_HOST: + case PHY_MODE_USB_OTG: + regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_DM_PULLDOWN, + U2P_R0_DM_PULLDOWN); + regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_DP_PULLDOWN, + U2P_R0_DP_PULLDOWN); + regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_ID_PULLUP, 0); + break; + + case PHY_MODE_USB_DEVICE: + regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_DM_PULLDOWN, + 0); + regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_DP_PULLDOWN, + 0); + regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_ID_PULLUP, + U2P_R0_ID_PULLUP); + break; + + default: + return -EINVAL; + } + + phy_meson_gxl_usb2_reset(phy); + + priv->mode = mode; + + return 0; +} + +static int phy_meson_gxl_usb2_power_off(struct phy *phy) +{ + struct phy_meson_gxl_usb2_priv *priv = phy_get_drvdata(phy); + + priv->is_enabled = 0; + + /* power off the PHY by putting it into reset mode */ + regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_POWER_ON_RESET, + U2P_R0_POWER_ON_RESET); + + return 0; +} + +static int phy_meson_gxl_usb2_power_on(struct phy *phy) +{ + struct phy_meson_gxl_usb2_priv *priv = phy_get_drvdata(phy); + int ret; + + priv->is_enabled = 1; + + /* power on the PHY by taking it out of reset mode */ + regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_POWER_ON_RESET, 0); + + ret = phy_meson_gxl_usb2_set_mode(phy, priv->mode); + if (ret) { + phy_meson_gxl_usb2_power_off(phy); + + dev_err(&phy->dev, "Failed to initialize PHY with mode %d\n", + priv->mode); + return ret; + } + + return 0; +} + +static const struct phy_ops phy_meson_gxl_usb2_ops = { + .power_on = phy_meson_gxl_usb2_power_on, + .power_off = phy_meson_gxl_usb2_power_off, + .set_mode = phy_meson_gxl_usb2_set_mode, + .reset = phy_meson_gxl_usb2_reset, + .owner = THIS_MODULE, +}; + +static int phy_meson_gxl_usb2_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct phy_provider *phy_provider; + struct resource *res; + struct phy_meson_gxl_usb2_priv *priv; + struct phy *phy; + void __iomem *base; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + platform_set_drvdata(pdev, priv); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + switch (of_usb_get_dr_mode_by_phy(dev->of_node, -1)) { + case USB_DR_MODE_PERIPHERAL: + priv->mode = PHY_MODE_USB_DEVICE; + break; + case USB_DR_MODE_OTG: + priv->mode = PHY_MODE_USB_OTG; + break; + case USB_DR_MODE_HOST: + default: + priv->mode = PHY_MODE_USB_HOST; + break; + } + + priv->regmap = devm_regmap_init_mmio(dev, base, + &phy_meson_gxl_usb2_regmap_conf); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + phy = devm_phy_create(dev, NULL, &phy_meson_gxl_usb2_ops); + if (IS_ERR(phy)) { + dev_err(dev, "failed to create PHY\n"); + return PTR_ERR(phy); + } + + phy_set_drvdata(phy, priv); + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id phy_meson_gxl_usb2_of_match[] = { + { .compatible = "amlogic,meson-gxl-usb2-phy", }, + { }, +}; +MODULE_DEVICE_TABLE(of, phy_meson_gxl_usb2_of_match); + +static struct platform_driver phy_meson_gxl_usb2_driver = { + .probe = phy_meson_gxl_usb2_probe, + .driver = { + .name = "phy-meson-gxl-usb2", + .of_match_table = phy_meson_gxl_usb2_of_match, + }, +}; +module_platform_driver(phy_meson_gxl_usb2_driver); + +MODULE_AUTHOR("Martin Blumenstingl "); +MODULE_DESCRIPTION("Meson GXL and GXM USB2 PHY driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From b78e290868e60abbd0d78c988a9e94ef1e128544 Mon Sep 17 00:00:00 2001 From: Martin Blumenstingl Date: Sat, 20 May 2017 16:12:50 +0200 Subject: dt-bindings: phy: meson8b-usb2-phy: add support for the Meson8 SoCs Meson8 uses the same USB PHY as found on the Meson8b and GXBB SoCs. This adds a new compatible string to indicate that this binding also supports the Meson8 SoCs. Signed-off-by: Martin Blumenstingl Acked-by: Rob Herring Signed-off-by: Kishon Vijay Abraham I --- Documentation/devicetree/bindings/phy/meson8b-usb2-phy.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/phy/meson8b-usb2-phy.txt b/Documentation/devicetree/bindings/phy/meson8b-usb2-phy.txt index 5fa73b9d20f5..d81d73aea608 100644 --- a/Documentation/devicetree/bindings/phy/meson8b-usb2-phy.txt +++ b/Documentation/devicetree/bindings/phy/meson8b-usb2-phy.txt @@ -1,7 +1,8 @@ -* Amlogic Meson8b and GXBB USB2 PHY +* Amlogic Meson8, Meson8b and GXBB USB2 PHY Required properties: - compatible: Depending on the platform this should be one of: + "amlogic,meson8-usb2-phy" "amlogic,meson8b-usb2-phy" "amlogic,meson-gxbb-usb2-phy" - reg: The base address and length of the registers -- cgit v1.2.3 From 4a3449d1a0a10c28e685bd8dfd06fb88179c5168 Mon Sep 17 00:00:00 2001 From: Martin Blumenstingl Date: Thu, 1 Jun 2017 18:41:14 +0530 Subject: phy: meson8b-usb2: add support for the USB PHY on Meson8 SoCs Meson8 uses the same USB PHY as found on the Meson8b and GXBB SoCs. Add a new of_device_id to indicate this. Also update the Kconfig option and MODULE_DESCRIPTION accordingly. Signed-off-by: Martin Blumenstingl Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/amlogic/Kconfig | 6 +++--- drivers/phy/amlogic/phy-meson8b-usb2.c | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/phy/amlogic/Kconfig b/drivers/phy/amlogic/Kconfig index 2044211c5b86..cb8f4501652b 100644 --- a/drivers/phy/amlogic/Kconfig +++ b/drivers/phy/amlogic/Kconfig @@ -2,15 +2,15 @@ # Phy drivers for Amlogic platforms # config PHY_MESON8B_USB2 - tristate "Meson8b and GXBB USB2 PHY driver" + tristate "Meson8, Meson8b and GXBB USB2 PHY driver" default ARCH_MESON depends on OF && (ARCH_MESON || COMPILE_TEST) depends on USB_SUPPORT select USB_COMMON select GENERIC_PHY help - Enable this to support the Meson USB2 PHYs found in Meson8b - and GXBB SoCs. + Enable this to support the Meson USB2 PHYs found in Meson8, + Meson8b and GXBB SoCs. If unsure, say N. config PHY_MESON_GXL_USB2 diff --git a/drivers/phy/amlogic/phy-meson8b-usb2.c b/drivers/phy/amlogic/phy-meson8b-usb2.c index 30f56a6a411f..9c01b7e19b06 100644 --- a/drivers/phy/amlogic/phy-meson8b-usb2.c +++ b/drivers/phy/amlogic/phy-meson8b-usb2.c @@ -1,5 +1,5 @@ /* - * Meson8b and GXBB USB2 PHY driver + * Meson8, Meson8b and GXBB USB2 PHY driver * * Copyright (C) 2016 Martin Blumenstingl * @@ -266,6 +266,7 @@ static int phy_meson8b_usb2_probe(struct platform_device *pdev) } static const struct of_device_id phy_meson8b_usb2_of_match[] = { + { .compatible = "amlogic,meson8-usb2-phy", }, { .compatible = "amlogic,meson8b-usb2-phy", }, { .compatible = "amlogic,meson-gxbb-usb2-phy", }, { }, @@ -282,5 +283,5 @@ static struct platform_driver phy_meson8b_usb2_driver = { module_platform_driver(phy_meson8b_usb2_driver); MODULE_AUTHOR("Martin Blumenstingl "); -MODULE_DESCRIPTION("Meson8b and GXBB USB2 PHY driver"); +MODULE_DESCRIPTION("Meson8, Meson8b and GXBB USB2 PHY driver"); MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 6d6ce40f63af9860ba12e6540fc9c9feb4338a1d Mon Sep 17 00:00:00 2001 From: Tony Lindgren Date: Mon, 22 May 2017 17:37:23 -0700 Subject: phy: cpcap-usb: Add CPCAP PMIC USB support Some Motorola phones like droid 4 use a custom CPCAP PMIC that has a multiplexing USB PHY. This USB PHY can operate at least in four modes using pin multiplexing and two control GPIOS: - Pass through companion PHY for the SoC USB PHY - ULPI PHY for the SoC - Pass through USB for the modem - UART debug console for the SoC This patch adds support for droid 4 USB PHY and debug UART modes, support for other modes can be added later on as needed. Both peripheral and host mode are working for the USB. The host mode depends on the cpcap-charger driver for VBUS. VBUS and ID pin detection are done using cpcap-adc IIO ADC driver. Cc: devicetree@vger.kernel.org Cc: Marcel Partap Cc: Michael Scott Acked-by: Rob Herring Tested-by: Sebastian Reichel Signed-off-by: Tony Lindgren Signed-off-by: Kishon Vijay Abraham I --- .../devicetree/bindings/phy/phy-cpcap-usb.txt | 40 ++ drivers/phy/Kconfig | 1 + drivers/phy/Makefile | 1 + drivers/phy/motorola/Kconfig | 11 + drivers/phy/motorola/Makefile | 5 + drivers/phy/motorola/phy-cpcap-usb.c | 674 +++++++++++++++++++++ 6 files changed, 732 insertions(+) create mode 100644 Documentation/devicetree/bindings/phy/phy-cpcap-usb.txt create mode 100644 drivers/phy/motorola/Kconfig create mode 100644 drivers/phy/motorola/Makefile create mode 100644 drivers/phy/motorola/phy-cpcap-usb.c diff --git a/Documentation/devicetree/bindings/phy/phy-cpcap-usb.txt b/Documentation/devicetree/bindings/phy/phy-cpcap-usb.txt new file mode 100644 index 000000000000..2eb9b2b69037 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/phy-cpcap-usb.txt @@ -0,0 +1,40 @@ +Motorola CPCAP PMIC USB PHY binding + +Required properties: +compatible: Shall be either "motorola,cpcap-usb-phy" or + "motorola,mapphone-cpcap-usb-phy" +#phy-cells: Shall be 0 +interrupts: CPCAP PMIC interrupts used by the USB PHY +interrupt-names: Interrupt names +io-channels: IIO ADC channels used by the USB PHY +io-channel-names: IIO ADC channel names +vusb-supply: Regulator for the PHY + +Optional properties: +pinctrl: Optional alternate pin modes for the PHY +pinctrl-names: Names for optional pin modes +mode-gpios: Optional GPIOs for configuring alternate modes + +Example: +cpcap_usb2_phy: phy { + compatible = "motorola,mapphone-cpcap-usb-phy"; + pinctrl-0 = <&usb_gpio_mux_sel1 &usb_gpio_mux_sel2>; + pinctrl-1 = <&usb_ulpi_pins>; + pinctrl-2 = <&usb_utmi_pins>; + pinctrl-3 = <&uart3_pins>; + pinctrl-names = "default", "ulpi", "utmi", "uart"; + #phy-cells = <0>; + interrupts-extended = < + &cpcap 15 0 &cpcap 14 0 &cpcap 28 0 &cpcap 19 0 + &cpcap 18 0 &cpcap 17 0 &cpcap 16 0 &cpcap 49 0 + &cpcap 48 1 + >; + interrupt-names = + "id_ground", "id_float", "se0conn", "vbusvld", + "sessvld", "sessend", "se1", "dm", "dp"; + mode-gpios = <&gpio2 28 GPIO_ACTIVE_HIGH + &gpio1 0 GPIO_ACTIVE_HIGH>; + io-channels = <&cpcap_adc 2>, <&cpcap_adc 7>; + io-channel-names = "vbus", "id"; + vusb-supply = <&vusb>; +}; diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig index 01009b2a7d74..c1807d4a0079 100644 --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig @@ -53,6 +53,7 @@ source "drivers/phy/amlogic/Kconfig" source "drivers/phy/broadcom/Kconfig" source "drivers/phy/hisilicon/Kconfig" source "drivers/phy/marvell/Kconfig" +source "drivers/phy/motorola/Kconfig" source "drivers/phy/qualcomm/Kconfig" source "drivers/phy/renesas/Kconfig" source "drivers/phy/rockchip/Kconfig" diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile index c1bd1fa3c853..f252201e0ec9 100644 --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_ARCH_TEGRA) += tegra/ obj-y += broadcom/ \ hisilicon/ \ marvell/ \ + motorola/ \ qualcomm/ \ samsung/ \ st/ \ diff --git a/drivers/phy/motorola/Kconfig b/drivers/phy/motorola/Kconfig new file mode 100644 index 000000000000..91a46cffd639 --- /dev/null +++ b/drivers/phy/motorola/Kconfig @@ -0,0 +1,11 @@ +# +# Phy drivers for Motorola devices +# +config PHY_CPCAP_USB + tristate "CPCAP PMIC USB PHY driver" + depends on USB_SUPPORT && IIO + select GENERIC_PHY + select USB_PHY + help + Enable this for USB to work on Motorola phones and tablets + such as Droid 4. diff --git a/drivers/phy/motorola/Makefile b/drivers/phy/motorola/Makefile new file mode 100644 index 000000000000..b6cd618d671a --- /dev/null +++ b/drivers/phy/motorola/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for the phy drivers. +# + +obj-$(CONFIG_PHY_CPCAP_USB) += phy-cpcap-usb.o diff --git a/drivers/phy/motorola/phy-cpcap-usb.c b/drivers/phy/motorola/phy-cpcap-usb.c new file mode 100644 index 000000000000..082a48bd4d89 --- /dev/null +++ b/drivers/phy/motorola/phy-cpcap-usb.c @@ -0,0 +1,674 @@ +/* + * Motorola CPCAP PMIC USB PHY driver + * Copyright (C) 2017 Tony Lindgren + * + * Some parts based on earlier Motorola Linux kernel tree code in + * board-mapphone-usb.c and cpcap-usb-det.c: + * Copyright (C) 2007 - 2011 Motorola, Inc. + * + * 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. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* CPCAP_REG_USBC1 register bits */ +#define CPCAP_BIT_IDPULSE BIT(15) +#define CPCAP_BIT_ID100KPU BIT(14) +#define CPCAP_BIT_IDPUCNTRL BIT(13) +#define CPCAP_BIT_IDPU BIT(12) +#define CPCAP_BIT_IDPD BIT(11) +#define CPCAP_BIT_VBUSCHRGTMR3 BIT(10) +#define CPCAP_BIT_VBUSCHRGTMR2 BIT(9) +#define CPCAP_BIT_VBUSCHRGTMR1 BIT(8) +#define CPCAP_BIT_VBUSCHRGTMR0 BIT(7) +#define CPCAP_BIT_VBUSPU BIT(6) +#define CPCAP_BIT_VBUSPD BIT(5) +#define CPCAP_BIT_DMPD BIT(4) +#define CPCAP_BIT_DPPD BIT(3) +#define CPCAP_BIT_DM1K5PU BIT(2) +#define CPCAP_BIT_DP1K5PU BIT(1) +#define CPCAP_BIT_DP150KPU BIT(0) + +/* CPCAP_REG_USBC2 register bits */ +#define CPCAP_BIT_ZHSDRV1 BIT(15) +#define CPCAP_BIT_ZHSDRV0 BIT(14) +#define CPCAP_BIT_DPLLCLKREQ BIT(13) +#define CPCAP_BIT_SE0CONN BIT(12) +#define CPCAP_BIT_UARTTXTRI BIT(11) +#define CPCAP_BIT_UARTSWAP BIT(10) +#define CPCAP_BIT_UARTMUX1 BIT(9) +#define CPCAP_BIT_UARTMUX0 BIT(8) +#define CPCAP_BIT_ULPISTPLOW BIT(7) +#define CPCAP_BIT_TXENPOL BIT(6) +#define CPCAP_BIT_USBXCVREN BIT(5) +#define CPCAP_BIT_USBCNTRL BIT(4) +#define CPCAP_BIT_USBSUSPEND BIT(3) +#define CPCAP_BIT_EMUMODE2 BIT(2) +#define CPCAP_BIT_EMUMODE1 BIT(1) +#define CPCAP_BIT_EMUMODE0 BIT(0) + +/* CPCAP_REG_USBC3 register bits */ +#define CPCAP_BIT_SPARE_898_15 BIT(15) +#define CPCAP_BIT_IHSTX03 BIT(14) +#define CPCAP_BIT_IHSTX02 BIT(13) +#define CPCAP_BIT_IHSTX01 BIT(12) +#define CPCAP_BIT_IHSTX0 BIT(11) +#define CPCAP_BIT_IDPU_SPI BIT(10) +#define CPCAP_BIT_UNUSED_898_9 BIT(9) +#define CPCAP_BIT_VBUSSTBY_EN BIT(8) +#define CPCAP_BIT_VBUSEN_SPI BIT(7) +#define CPCAP_BIT_VBUSPU_SPI BIT(6) +#define CPCAP_BIT_VBUSPD_SPI BIT(5) +#define CPCAP_BIT_DMPD_SPI BIT(4) +#define CPCAP_BIT_DPPD_SPI BIT(3) +#define CPCAP_BIT_SUSPEND_SPI BIT(2) +#define CPCAP_BIT_PU_SPI BIT(1) +#define CPCAP_BIT_ULPI_SPI_SEL BIT(0) + +struct cpcap_usb_ints_state { + bool id_ground; + bool id_float; + bool chrg_det; + bool rvrs_chrg; + bool vbusov; + + bool chrg_se1b; + bool se0conn; + bool rvrs_mode; + bool chrgcurr1; + bool vbusvld; + bool sessvld; + bool sessend; + bool se1; + + bool battdetb; + bool dm; + bool dp; +}; + +enum cpcap_gpio_mode { + CPCAP_DM_DP, + CPCAP_MDM_RX_TX, + CPCAP_UNKNOWN, + CPCAP_OTG_DM_DP, +}; + +struct cpcap_phy_ddata { + struct regmap *reg; + struct device *dev; + struct clk *refclk; + struct usb_phy phy; + struct delayed_work detect_work; + struct pinctrl *pins; + struct pinctrl_state *pins_ulpi; + struct pinctrl_state *pins_utmi; + struct pinctrl_state *pins_uart; + struct gpio_desc *gpio[2]; + struct iio_channel *vbus; + struct iio_channel *id; + struct regulator *vusb; + atomic_t active; +}; + +static bool cpcap_usb_vbus_valid(struct cpcap_phy_ddata *ddata) +{ + int error, value = 0; + + error = iio_read_channel_processed(ddata->vbus, &value); + if (error >= 0) + return value > 3900 ? true : false; + + dev_err(ddata->dev, "error reading VBUS: %i\n", error); + + return false; +} + +static int cpcap_usb_phy_set_host(struct usb_otg *otg, struct usb_bus *host) +{ + otg->host = host; + if (!host) + otg->state = OTG_STATE_UNDEFINED; + + return 0; +} + +static int cpcap_usb_phy_set_peripheral(struct usb_otg *otg, + struct usb_gadget *gadget) +{ + otg->gadget = gadget; + if (!gadget) + otg->state = OTG_STATE_UNDEFINED; + + return 0; +} + +static const struct phy_ops ops = { + .owner = THIS_MODULE, +}; + +static int cpcap_phy_get_ints_state(struct cpcap_phy_ddata *ddata, + struct cpcap_usb_ints_state *s) +{ + int val, error; + + error = regmap_read(ddata->reg, CPCAP_REG_INTS1, &val); + if (error) + return error; + + s->id_ground = val & BIT(15); + s->id_float = val & BIT(14); + s->vbusov = val & BIT(11); + + error = regmap_read(ddata->reg, CPCAP_REG_INTS2, &val); + if (error) + return error; + + s->vbusvld = val & BIT(3); + s->sessvld = val & BIT(2); + s->sessend = val & BIT(1); + s->se1 = val & BIT(0); + + error = regmap_read(ddata->reg, CPCAP_REG_INTS4, &val); + if (error) + return error; + + s->dm = val & BIT(1); + s->dp = val & BIT(0); + + return 0; +} + +static int cpcap_usb_set_uart_mode(struct cpcap_phy_ddata *ddata); +static int cpcap_usb_set_usb_mode(struct cpcap_phy_ddata *ddata); + +static void cpcap_usb_detect(struct work_struct *work) +{ + struct cpcap_phy_ddata *ddata; + struct cpcap_usb_ints_state s; + bool vbus = false; + int error; + + ddata = container_of(work, struct cpcap_phy_ddata, detect_work.work); + + error = cpcap_phy_get_ints_state(ddata, &s); + if (error) + return; + + if (s.id_ground) { + dev_dbg(ddata->dev, "id ground, USB host mode\n"); + error = cpcap_usb_set_usb_mode(ddata); + if (error) + goto out_err; + + error = musb_mailbox(MUSB_ID_GROUND); + if (error) + goto out_err; + + error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC3, + CPCAP_BIT_VBUSSTBY_EN, + CPCAP_BIT_VBUSSTBY_EN); + if (error) + goto out_err; + + return; + } + + error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC3, + CPCAP_BIT_VBUSSTBY_EN, 0); + if (error) + goto out_err; + + vbus = cpcap_usb_vbus_valid(ddata); + + if (vbus) { + /* Are we connected to a docking station with vbus? */ + if (s.id_ground) { + dev_dbg(ddata->dev, "connected to a dock\n"); + + /* No VBUS needed with docks */ + error = cpcap_usb_set_usb_mode(ddata); + if (error) + goto out_err; + error = musb_mailbox(MUSB_ID_GROUND); + if (error) + goto out_err; + + return; + } + + /* Otherwise assume we're connected to a USB host */ + dev_dbg(ddata->dev, "connected to USB host\n"); + error = cpcap_usb_set_usb_mode(ddata); + if (error) + goto out_err; + error = musb_mailbox(MUSB_VBUS_VALID); + if (error) + goto out_err; + + return; + } + + /* Default to debug UART mode */ + error = cpcap_usb_set_uart_mode(ddata); + if (error) + goto out_err; + + error = musb_mailbox(MUSB_VBUS_OFF); + if (error) + goto out_err; + + dev_dbg(ddata->dev, "set UART mode\n"); + + return; + +out_err: + dev_err(ddata->dev, "error setting cable state: %i\n", error); +} + +static irqreturn_t cpcap_phy_irq_thread(int irq, void *data) +{ + struct cpcap_phy_ddata *ddata = data; + + if (!atomic_read(&ddata->active)) + return IRQ_NONE; + + schedule_delayed_work(&ddata->detect_work, msecs_to_jiffies(1)); + + return IRQ_HANDLED; +} + +static int cpcap_usb_init_irq(struct platform_device *pdev, + struct cpcap_phy_ddata *ddata, + const char *name) +{ + int irq, error; + + irq = platform_get_irq_byname(pdev, name); + if (!irq) + return -ENODEV; + + error = devm_request_threaded_irq(ddata->dev, irq, NULL, + cpcap_phy_irq_thread, + IRQF_SHARED, + name, ddata); + if (error) { + dev_err(ddata->dev, "could not get irq %s: %i\n", + name, error); + + return error; + } + + return 0; +} + +static const char * const cpcap_phy_irqs[] = { + /* REG_INT_0 */ + "id_ground", "id_float", + + /* REG_INT1 */ + "se0conn", "vbusvld", "sessvld", "sessend", "se1", + + /* REG_INT_3 */ + "dm", "dp", +}; + +static int cpcap_usb_init_interrupts(struct platform_device *pdev, + struct cpcap_phy_ddata *ddata) +{ + int i, error; + + for (i = 0; i < ARRAY_SIZE(cpcap_phy_irqs); i++) { + error = cpcap_usb_init_irq(pdev, ddata, cpcap_phy_irqs[i]); + if (error) + return error; + } + + return 0; +} + +/* + * Optional pins and modes. At least Motorola mapphone devices + * are using two GPIOs and dynamic pinctrl to multiplex PHY pins + * to UART, ULPI or UTMI mode. + */ + +static int cpcap_usb_gpio_set_mode(struct cpcap_phy_ddata *ddata, + enum cpcap_gpio_mode mode) +{ + if (!ddata->gpio[0] || !ddata->gpio[1]) + return 0; + + gpiod_set_value(ddata->gpio[0], mode & 1); + gpiod_set_value(ddata->gpio[1], mode >> 1); + + return 0; +} + +static int cpcap_usb_set_uart_mode(struct cpcap_phy_ddata *ddata) +{ + int error; + + error = cpcap_usb_gpio_set_mode(ddata, CPCAP_DM_DP); + if (error) + goto out_err; + + if (ddata->pins_uart) { + error = pinctrl_select_state(ddata->pins, ddata->pins_uart); + if (error) + goto out_err; + } + + error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC1, + CPCAP_BIT_VBUSPD, + CPCAP_BIT_VBUSPD); + if (error) + goto out_err; + + error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC2, + 0xffff, CPCAP_BIT_UARTMUX0 | + CPCAP_BIT_EMUMODE0); + if (error) + goto out_err; + + error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC3, 0x7fff, + CPCAP_BIT_IDPU_SPI); + if (error) + goto out_err; + + return 0; + +out_err: + dev_err(ddata->dev, "%s failed with %i\n", __func__, error); + + return error; +} + +static int cpcap_usb_set_usb_mode(struct cpcap_phy_ddata *ddata) +{ + int error; + + error = cpcap_usb_gpio_set_mode(ddata, CPCAP_OTG_DM_DP); + if (error) + return error; + + if (ddata->pins_utmi) { + error = pinctrl_select_state(ddata->pins, ddata->pins_utmi); + if (error) { + dev_err(ddata->dev, "could not set usb mode: %i\n", + error); + + return error; + } + } + + error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC1, + CPCAP_BIT_VBUSPD, 0); + if (error) + goto out_err; + + error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC2, + CPCAP_BIT_USBXCVREN, + CPCAP_BIT_USBXCVREN); + if (error) + goto out_err; + + error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC3, + CPCAP_BIT_PU_SPI | + CPCAP_BIT_DMPD_SPI | + CPCAP_BIT_DPPD_SPI | + CPCAP_BIT_SUSPEND_SPI | + CPCAP_BIT_ULPI_SPI_SEL, 0); + if (error) + goto out_err; + + error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC2, + CPCAP_BIT_USBXCVREN, + CPCAP_BIT_USBXCVREN); + if (error) + goto out_err; + + return 0; + +out_err: + dev_err(ddata->dev, "%s failed with %i\n", __func__, error); + + return error; +} + +static int cpcap_usb_init_optional_pins(struct cpcap_phy_ddata *ddata) +{ + ddata->pins = devm_pinctrl_get(ddata->dev); + if (IS_ERR(ddata->pins)) { + dev_info(ddata->dev, "default pins not configured: %ld\n", + PTR_ERR(ddata->pins)); + ddata->pins = NULL; + } + + ddata->pins_ulpi = pinctrl_lookup_state(ddata->pins, "ulpi"); + if (IS_ERR(ddata->pins_ulpi)) { + dev_info(ddata->dev, "ulpi pins not configured\n"); + ddata->pins_ulpi = NULL; + } + + ddata->pins_utmi = pinctrl_lookup_state(ddata->pins, "utmi"); + if (IS_ERR(ddata->pins_utmi)) { + dev_info(ddata->dev, "utmi pins not configured\n"); + ddata->pins_utmi = NULL; + } + + ddata->pins_uart = pinctrl_lookup_state(ddata->pins, "uart"); + if (IS_ERR(ddata->pins_uart)) { + dev_info(ddata->dev, "uart pins not configured\n"); + ddata->pins_uart = NULL; + } + + if (ddata->pins_uart) + return pinctrl_select_state(ddata->pins, ddata->pins_uart); + + return 0; +} + +static void cpcap_usb_init_optional_gpios(struct cpcap_phy_ddata *ddata) +{ + int i; + + for (i = 0; i < 2; i++) { + ddata->gpio[i] = devm_gpiod_get_index(ddata->dev, "mode", + i, GPIOD_OUT_HIGH); + if (IS_ERR(ddata->gpio[i])) { + dev_info(ddata->dev, "no mode change GPIO%i: %li\n", + i, PTR_ERR(ddata->gpio[i])); + ddata->gpio[i] = NULL; + } + } +} + +static int cpcap_usb_init_iio(struct cpcap_phy_ddata *ddata) +{ + enum iio_chan_type type; + int error; + + ddata->vbus = devm_iio_channel_get(ddata->dev, "vbus"); + if (IS_ERR(ddata->vbus)) { + error = PTR_ERR(ddata->vbus); + goto out_err; + } + + if (!ddata->vbus->indio_dev) { + error = -ENXIO; + goto out_err; + } + + error = iio_get_channel_type(ddata->vbus, &type); + if (error < 0) + goto out_err; + + if (type != IIO_VOLTAGE) { + error = -EINVAL; + goto out_err; + } + + return 0; + +out_err: + dev_err(ddata->dev, "could not initialize VBUS or ID IIO: %i\n", + error); + + return error; +} + +#ifdef CONFIG_OF +static const struct of_device_id cpcap_usb_phy_id_table[] = { + { + .compatible = "motorola,cpcap-usb-phy", + }, + { + .compatible = "motorola,mapphone-cpcap-usb-phy", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, cpcap_usb_phy_id_table); +#endif + +static int cpcap_usb_phy_probe(struct platform_device *pdev) +{ + struct cpcap_phy_ddata *ddata; + struct phy *generic_phy; + struct phy_provider *phy_provider; + struct usb_otg *otg; + const struct of_device_id *of_id; + int error; + + of_id = of_match_device(of_match_ptr(cpcap_usb_phy_id_table), + &pdev->dev); + if (!of_id) + return -EINVAL; + + ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL); + if (!ddata) + return -ENOMEM; + + ddata->reg = dev_get_regmap(pdev->dev.parent, NULL); + if (!ddata->reg) + return -ENODEV; + + otg = devm_kzalloc(&pdev->dev, sizeof(*otg), GFP_KERNEL); + if (!otg) + return -ENOMEM; + + ddata->dev = &pdev->dev; + ddata->phy.dev = ddata->dev; + ddata->phy.label = "cpcap_usb_phy"; + ddata->phy.otg = otg; + ddata->phy.type = USB_PHY_TYPE_USB2; + otg->set_host = cpcap_usb_phy_set_host; + otg->set_peripheral = cpcap_usb_phy_set_peripheral; + otg->usb_phy = &ddata->phy; + INIT_DELAYED_WORK(&ddata->detect_work, cpcap_usb_detect); + platform_set_drvdata(pdev, ddata); + + ddata->vusb = devm_regulator_get(&pdev->dev, "vusb"); + if (IS_ERR(ddata->vusb)) + return PTR_ERR(ddata->vusb); + + error = regulator_enable(ddata->vusb); + if (error) + return error; + + generic_phy = devm_phy_create(ddata->dev, NULL, &ops); + if (IS_ERR(generic_phy)) { + error = PTR_ERR(generic_phy); + return PTR_ERR(generic_phy); + } + + phy_set_drvdata(generic_phy, ddata); + + phy_provider = devm_of_phy_provider_register(ddata->dev, + of_phy_simple_xlate); + if (IS_ERR(phy_provider)) + return PTR_ERR(phy_provider); + + error = cpcap_usb_init_optional_pins(ddata); + if (error) + return error; + + cpcap_usb_init_optional_gpios(ddata); + + error = cpcap_usb_init_iio(ddata); + if (error) + return error; + + error = cpcap_usb_init_interrupts(pdev, ddata); + if (error) + return error; + + usb_add_phy_dev(&ddata->phy); + atomic_set(&ddata->active, 1); + schedule_delayed_work(&ddata->detect_work, msecs_to_jiffies(1)); + + return 0; +} + +static int cpcap_usb_phy_remove(struct platform_device *pdev) +{ + struct cpcap_phy_ddata *ddata = platform_get_drvdata(pdev); + int error; + + atomic_set(&ddata->active, 0); + error = cpcap_usb_set_uart_mode(ddata); + if (error) + dev_err(ddata->dev, "could not set UART mode\n"); + + error = musb_mailbox(MUSB_VBUS_OFF); + if (error) + dev_err(ddata->dev, "could not set mailbox\n"); + + usb_remove_phy(&ddata->phy); + cancel_delayed_work_sync(&ddata->detect_work); + clk_unprepare(ddata->refclk); + regulator_disable(ddata->vusb); + + return 0; +} + +static struct platform_driver cpcap_usb_phy_driver = { + .probe = cpcap_usb_phy_probe, + .remove = cpcap_usb_phy_remove, + .driver = { + .name = "cpcap-usb-phy", + .of_match_table = of_match_ptr(cpcap_usb_phy_id_table), + }, +}; + +module_platform_driver(cpcap_usb_phy_driver); + +MODULE_ALIAS("platform:cpcap_usb"); +MODULE_AUTHOR("Tony Lindgren "); +MODULE_DESCRIPTION("CPCAP usb phy driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From fbbe98cd44508def1c6b7f98d5dc676d23bc9031 Mon Sep 17 00:00:00 2001 From: William Wu Date: Fri, 2 Jun 2017 11:20:23 +0800 Subject: phy: rockchip-inno-usb2: add a delay after phy resume When resume phy, it need about 1.5 ~ 2ms to wait for utmi_clk which used for USB controller to become stable. Signed-off-by: William Wu Signed-off-by: Frank Wang Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/rockchip/phy-rockchip-inno-usb2.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c index 8efe78a49916..f12dc8db5230 100644 --- a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c +++ b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c @@ -463,6 +463,9 @@ static int rockchip_usb2phy_power_on(struct phy *phy) if (ret) return ret; + /* waiting for the utmi_clk to become stable */ + usleep_range(1500, 2000); + rport->suspended = false; return 0; } -- cgit v1.2.3 From 5a74a8b74233f0e3c018d4e1c2a52e9393605f47 Mon Sep 17 00:00:00 2001 From: William Wu Date: Fri, 2 Jun 2017 11:20:24 +0800 Subject: phy: rockchip-inno-usb2: increase otg sm work first schedule time In rockchip-inno-usb2 phy driver, we use otg_sm_work to dynamically manage power consumption for phy otg-port. If the otg-port works as peripheral mode and does not communicate with usb host, we will suspend phy. But once suspend phy, the phy no longer has any internal clock running, include the utmi_clk which supplied for usb controller. So if we suspend phy before usb controller init, it will cause usb controller fail to initialize. Specifically, without this patch, the observed order is: 1. unplug usb cable 2. start system, do dwc2 controller probe 3. dwc2_lowlevel_hw_enable() - phy_init() - rockchip_usb2phy_init() - schedule otg_sm_work after 2s put phy in suspend, and close utmi_clk 4. dwc2_hsotg_udc_start() - fail to initialize the usb core Generally, dwc2_hsotg_udc_start() can be called within 5s after start system on Rockchip platform, so we increase the the first schedule delay time to 6s for otg_sm_work afer usb controller calls phy_init(), this can make sure that the usb controller completes initialization before phy enter suspend. Signed-off-by: William Wu Signed-off-by: Frank Wang Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/rockchip/phy-rockchip-inno-usb2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c index f12dc8db5230..d6e459d7094a 100644 --- a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c +++ b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c @@ -421,7 +421,7 @@ static int rockchip_usb2phy_init(struct phy *phy) goto out; schedule_delayed_work(&rport->otg_sm_work, - OTG_SCHEDULE_DELAY); + OTG_SCHEDULE_DELAY * 3); } else { /* If OTG works in host only mode, do nothing. */ dev_dbg(&rport->phy->dev, "mode %d\n", rport->mode); -- cgit v1.2.3 From 9632781122e5f7adfaf29aeb4387d7b354556593 Mon Sep 17 00:00:00 2001 From: William Wu Date: Fri, 2 Jun 2017 11:20:25 +0800 Subject: phy: rockchip-inno-usb2: add one phy comprises with two host-ports support At the current rockchip-inno-usb2 phy driver framework, it can only support usb2-phy which comprises with one otg-port and one host-port. However, some Rockchip SoCs' (e.g RK3228, RK3229) usb2-phy comprises with two host-ports, so we use index of otg id for one host-port configuration, and make it work the same as otg-port host mode. Signed-off-by: William Wu Signed-off-by: Frank Wang Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/rockchip/phy-rockchip-inno-usb2.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c index d6e459d7094a..d026b4cf7523 100644 --- a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c +++ b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c @@ -406,7 +406,8 @@ static int rockchip_usb2phy_init(struct phy *phy) mutex_lock(&rport->mutex); if (rport->port_id == USB2PHY_PORT_OTG) { - if (rport->mode != USB_DR_MODE_HOST) { + if (rport->mode != USB_DR_MODE_HOST && + rport->mode != USB_DR_MODE_UNKNOWN) { /* clear bvalid status and enable bvalid detect irq */ ret = property_enable(rphy, &rport->port_cfg->bvalid_det_clr, @@ -496,7 +497,8 @@ static int rockchip_usb2phy_exit(struct phy *phy) struct rockchip_usb2phy_port *rport = phy_get_drvdata(phy); if (rport->port_id == USB2PHY_PORT_OTG && - rport->mode != USB_DR_MODE_HOST) { + rport->mode != USB_DR_MODE_HOST && + rport->mode != USB_DR_MODE_UNKNOWN) { cancel_delayed_work_sync(&rport->otg_sm_work); cancel_delayed_work_sync(&rport->chg_work); } else if (rport->port_id == USB2PHY_PORT_HOST) @@ -973,7 +975,8 @@ static int rockchip_usb2phy_otg_port_init(struct rockchip_usb2phy *rphy, mutex_init(&rport->mutex); rport->mode = of_usb_get_dr_mode_by_phy(child_np, -1); - if (rport->mode == USB_DR_MODE_HOST) { + if (rport->mode == USB_DR_MODE_HOST || + rport->mode == USB_DR_MODE_UNKNOWN) { ret = 0; goto out; } -- cgit v1.2.3 From b59b1d390483e3ff3342d7b7cd0ec19e847a6142 Mon Sep 17 00:00:00 2001 From: Frank Wang Date: Fri, 2 Jun 2017 11:20:26 +0800 Subject: phy: rockchip-inno-usb2: add support of usb2-phy for rk3228 SoCs This adds support usb2-phy for rk3228 SoCs and amend phy Documentation. Signed-off-by: Frank Wang Signed-off-by: Kishon Vijay Abraham I --- .../bindings/phy/phy-rockchip-inno-usb2.txt | 1 + drivers/phy/rockchip/phy-rockchip-inno-usb2.c | 60 ++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/Documentation/devicetree/bindings/phy/phy-rockchip-inno-usb2.txt b/Documentation/devicetree/bindings/phy/phy-rockchip-inno-usb2.txt index e71a8d23f4a8..84d59b0db8df 100644 --- a/Documentation/devicetree/bindings/phy/phy-rockchip-inno-usb2.txt +++ b/Documentation/devicetree/bindings/phy/phy-rockchip-inno-usb2.txt @@ -2,6 +2,7 @@ ROCKCHIP USB2.0 PHY WITH INNO IP BLOCK Required properties (phy (parent) node): - compatible : should be one of the listed compatibles: + * "rockchip,rk3228-usb2phy" * "rockchip,rk3328-usb2phy" * "rockchip,rk3366-usb2phy" * "rockchip,rk3399-usb2phy" diff --git a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c index d026b4cf7523..626883d9d176 100644 --- a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c +++ b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c @@ -1144,6 +1144,65 @@ disable_clks: return ret; } +static const struct rockchip_usb2phy_cfg rk3228_phy_cfgs[] = { + { + .reg = 0x760, + .num_ports = 2, + .clkout_ctl = { 0x0768, 4, 4, 1, 0 }, + .port_cfgs = { + [USB2PHY_PORT_OTG] = { + .phy_sus = { 0x0760, 15, 0, 0, 0x1d1 }, + .bvalid_det_en = { 0x0680, 3, 3, 0, 1 }, + .bvalid_det_st = { 0x0690, 3, 3, 0, 1 }, + .bvalid_det_clr = { 0x06a0, 3, 3, 0, 1 }, + .ls_det_en = { 0x0680, 2, 2, 0, 1 }, + .ls_det_st = { 0x0690, 2, 2, 0, 1 }, + .ls_det_clr = { 0x06a0, 2, 2, 0, 1 }, + .utmi_bvalid = { 0x0480, 4, 4, 0, 1 }, + .utmi_ls = { 0x0480, 3, 2, 0, 1 }, + }, + [USB2PHY_PORT_HOST] = { + .phy_sus = { 0x0764, 15, 0, 0, 0x1d1 }, + .ls_det_en = { 0x0680, 4, 4, 0, 1 }, + .ls_det_st = { 0x0690, 4, 4, 0, 1 }, + .ls_det_clr = { 0x06a0, 4, 4, 0, 1 } + } + }, + .chg_det = { + .opmode = { 0x0760, 3, 0, 5, 1 }, + .cp_det = { 0x0884, 4, 4, 0, 1 }, + .dcp_det = { 0x0884, 3, 3, 0, 1 }, + .dp_det = { 0x0884, 5, 5, 0, 1 }, + .idm_sink_en = { 0x0768, 8, 8, 0, 1 }, + .idp_sink_en = { 0x0768, 7, 7, 0, 1 }, + .idp_src_en = { 0x0768, 9, 9, 0, 1 }, + .rdm_pdwn_en = { 0x0768, 10, 10, 0, 1 }, + .vdm_src_en = { 0x0768, 12, 12, 0, 1 }, + .vdp_src_en = { 0x0768, 11, 11, 0, 1 }, + }, + }, + { + .reg = 0x800, + .num_ports = 2, + .clkout_ctl = { 0x0808, 4, 4, 1, 0 }, + .port_cfgs = { + [USB2PHY_PORT_OTG] = { + .phy_sus = { 0x800, 15, 0, 0, 0x1d1 }, + .ls_det_en = { 0x0684, 0, 0, 0, 1 }, + .ls_det_st = { 0x0694, 0, 0, 0, 1 }, + .ls_det_clr = { 0x06a4, 0, 0, 0, 1 } + }, + [USB2PHY_PORT_HOST] = { + .phy_sus = { 0x804, 15, 0, 0, 0x1d1 }, + .ls_det_en = { 0x0684, 1, 1, 0, 1 }, + .ls_det_st = { 0x0694, 1, 1, 0, 1 }, + .ls_det_clr = { 0x06a4, 1, 1, 0, 1 } + } + }, + }, + { /* sentinel */ } +}; + static const struct rockchip_usb2phy_cfg rk3328_phy_cfgs[] = { { .reg = 0x100, @@ -1269,6 +1328,7 @@ static const struct rockchip_usb2phy_cfg rk3399_phy_cfgs[] = { }; static const struct of_device_id rockchip_usb2phy_dt_match[] = { + { .compatible = "rockchip,rk3228-usb2phy", .data = &rk3228_phy_cfgs }, { .compatible = "rockchip,rk3328-usb2phy", .data = &rk3328_phy_cfgs }, { .compatible = "rockchip,rk3366-usb2phy", .data = &rk3366_phy_cfgs }, { .compatible = "rockchip,rk3399-usb2phy", .data = &rk3399_phy_cfgs }, -- cgit v1.2.3 From 7c7356bab8e85c8d2e335752dcb89d94fe10d660 Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Mon, 29 May 2017 19:42:15 +0900 Subject: phy: rcar-gen3-usb3: add support for R-Car Gen3 USB 3.0 PHY The USB 3.0 PHY modules of R-Car Gen3 SoCs have: - Spread spectrum clock (ssc). - Using USB 2.0 EXTAL clock instead of USB 3.0 clock. - Enabling VBUS detection for usb3.0 peripheral. So, this driver supports these features. Signed-off-by: Yoshihiro Shimoda Signed-off-by: Kishon Vijay Abraham I --- .../devicetree/bindings/phy/rcar-gen3-phy-usb3.txt | 46 +++++ MAINTAINERS | 4 +- drivers/phy/renesas/Kconfig | 7 + drivers/phy/renesas/Makefile | 1 + drivers/phy/renesas/phy-rcar-gen3-usb3.c | 226 +++++++++++++++++++++ 5 files changed, 282 insertions(+), 2 deletions(-) create mode 100644 Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb3.txt create mode 100644 drivers/phy/renesas/phy-rcar-gen3-usb3.c diff --git a/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb3.txt b/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb3.txt new file mode 100644 index 000000000000..f94cea48f6b1 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/rcar-gen3-phy-usb3.txt @@ -0,0 +1,46 @@ +* Renesas R-Car generation 3 USB 3.0 PHY + +This file provides information on what the device node for the R-Car generation +3 USB 3.0 PHY contains. +If you want to enable spread spectrum clock (ssc), you should use USB_EXTAL +instead of USB3_CLK. However, if you don't want to these features, you don't +need this driver. + +Required properties: +- compatible: "renesas,r8a7795-usb3-phy" if the device is a part of an R8A7795 + SoC. + "renesas,r8a7796-usb3-phy" if the device is a part of an R8A7796 + SoC. + "renesas,rcar-gen3-usb3-phy" for a generic R-Car Gen3 compatible + device. + + When compatible with the generic version, nodes must list the + SoC-specific version corresponding to the platform first + followed by the generic version. + +- reg: offset and length of the USB 3.0 PHY register block. +- clocks: A list of phandles and clock-specifier pairs. +- clock-names: Name of the clocks. + - The funcional clock must be "usb3-if". + - The usb3's external clock must be "usb3s_clk". + - The usb2's external clock must be "usb_extal". If you want to use the ssc, + the clock-frequency must not be 0. +- #phy-cells: see phy-bindings.txt in the same directory, must be <0>. + +Optional properties: +- renesas,ssc-range: Enable/disable spread spectrum clock (ssc) by using + the following values as u32: + - 0 (or the property doesn't exist): disable the ssc + - 4980: enable the ssc as -4980 ppm + - 4492: enable the ssc as -4492 ppm + - 4003: enable the ssc as -4003 ppm + +Example (R-Car H3): + + usb-phy@e65ee000 { + compatible = "renesas,r8a7795-usb3-phy", + "renesas,rcar-gen3-usb3-phy"; + reg = <0 0xe65ee000 0 0x90>; + clocks = <&cpg CPG_MOD 328>, <&usb3s0_clk>, <&usb_extal>; + clock-names = "usb3-if", "usb3s_clk", "usb_extal"; + }; diff --git a/MAINTAINERS b/MAINTAINERS index a47d3da4d35d..d711d53aecf4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10829,11 +10829,11 @@ L: linux-iio@vger.kernel.org S: Supported F: drivers/iio/adc/rcar_gyro_adc.c -RENESAS USB2 PHY DRIVER +RENESAS USB PHY DRIVER M: Yoshihiro Shimoda L: linux-renesas-soc@vger.kernel.org S: Maintained -F: drivers/phy/renesas/phy-rcar-gen3-usb2.c +F: drivers/phy/renesas/phy-rcar-gen3-usb*.c RESET CONTROLLER FRAMEWORK M: Philipp Zabel diff --git a/drivers/phy/renesas/Kconfig b/drivers/phy/renesas/Kconfig index 432e2715e9c4..cb09245e9b4c 100644 --- a/drivers/phy/renesas/Kconfig +++ b/drivers/phy/renesas/Kconfig @@ -15,3 +15,10 @@ config PHY_RCAR_GEN3_USB2 select GENERIC_PHY help Support for USB 2.0 PHY found on Renesas R-Car generation 3 SoCs. + +config PHY_RCAR_GEN3_USB3 + tristate "Renesas R-Car generation 3 USB 3.0 PHY driver" + depends on ARCH_RENESAS || COMPILE_TEST + select GENERIC_PHY + help + Support for USB 3.0 PHY found on Renesas R-Car generation 3 SoCs. diff --git a/drivers/phy/renesas/Makefile b/drivers/phy/renesas/Makefile index 695241aebf69..8b6025916a93 100644 --- a/drivers/phy/renesas/Makefile +++ b/drivers/phy/renesas/Makefile @@ -1,2 +1,3 @@ obj-$(CONFIG_PHY_RCAR_GEN2) += phy-rcar-gen2.o obj-$(CONFIG_PHY_RCAR_GEN3_USB2) += phy-rcar-gen3-usb2.o +obj-$(CONFIG_PHY_RCAR_GEN3_USB3) += phy-rcar-gen3-usb3.o diff --git a/drivers/phy/renesas/phy-rcar-gen3-usb3.c b/drivers/phy/renesas/phy-rcar-gen3-usb3.c new file mode 100644 index 000000000000..88c83c9b8ff9 --- /dev/null +++ b/drivers/phy/renesas/phy-rcar-gen3-usb3.c @@ -0,0 +1,226 @@ +/* + * Renesas R-Car Gen3 for USB3.0 PHY driver + * + * Copyright (C) 2017 Renesas Electronics Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define USB30_CLKSET0 0x034 +#define USB30_CLKSET1 0x036 +#define USB30_SSC_SET 0x038 +#define USB30_PHY_ENABLE 0x060 +#define USB30_VBUS_EN 0x064 + +/* USB30_CLKSET0 */ +#define CLKSET0_PRIVATE 0x05c0 +#define CLKSET0_USB30_FSEL_USB_EXTAL 0x0002 + +/* USB30_CLKSET1 */ +#define CLKSET1_USB30_PLL_MULTI_SHIFT 6 +#define CLKSET1_USB30_PLL_MULTI_USB_EXTAL (0x64 << \ + CLKSET1_USB30_PLL_MULTI_SHIFT) +#define CLKSET1_PHYRESET BIT(4) /* 1: reset */ +#define CLKSET1_REF_CLKDIV BIT(3) /* 1: USB_EXTAL */ +#define CLKSET1_PRIVATE_2_1 BIT(1) /* Write B'01 */ +#define CLKSET1_REF_CLK_SEL BIT(0) /* 1: USB3S0_CLK_P */ + +/* USB30_SSC_SET */ +#define SSC_SET_SSC_EN BIT(12) +#define SSC_SET_RANGE_SHIFT 9 +#define SSC_SET_RANGE_4980 (0x0 << SSC_SET_RANGE_SHIFT) +#define SSC_SET_RANGE_4492 (0x1 << SSC_SET_RANGE_SHIFT) +#define SSC_SET_RANGE_4003 (0x2 << SSC_SET_RANGE_SHIFT) + +/* USB30_PHY_ENABLE */ +#define PHY_ENABLE_RESET_EN BIT(4) + +/* USB30_VBUS_EN */ +#define VBUS_EN_VBUS_EN BIT(1) + +struct rcar_gen3_usb3 { + void __iomem *base; + struct phy *phy; + u32 ssc_range; + bool usb3s_clk; + bool usb_extal; +}; + +static void write_clkset1_for_usb_extal(struct rcar_gen3_usb3 *r, bool reset) +{ + u16 val = CLKSET1_USB30_PLL_MULTI_USB_EXTAL | + CLKSET1_REF_CLKDIV | CLKSET1_PRIVATE_2_1; + + if (reset) + val |= CLKSET1_PHYRESET; + + writew(val, r->base + USB30_CLKSET1); +} + +static void rcar_gen3_phy_usb3_enable_ssc(struct rcar_gen3_usb3 *r) +{ + u16 val = SSC_SET_SSC_EN; + + switch (r->ssc_range) { + case 4980: + val |= SSC_SET_RANGE_4980; + break; + case 4492: + val |= SSC_SET_RANGE_4492; + break; + case 4003: + val |= SSC_SET_RANGE_4003; + break; + default: + dev_err(&r->phy->dev, "%s: unsupported range (%x)\n", __func__, + r->ssc_range); + return; + } + + writew(val, r->base + USB30_SSC_SET); +} + +static void rcar_gen3_phy_usb3_select_usb_extal(struct rcar_gen3_usb3 *r) +{ + write_clkset1_for_usb_extal(r, false); + if (r->ssc_range) + rcar_gen3_phy_usb3_enable_ssc(r); + writew(CLKSET0_PRIVATE | CLKSET0_USB30_FSEL_USB_EXTAL, + r->base + USB30_CLKSET0); + writew(PHY_ENABLE_RESET_EN, r->base + USB30_PHY_ENABLE); + write_clkset1_for_usb_extal(r, true); + usleep_range(10, 20); + write_clkset1_for_usb_extal(r, false); +} + +static int rcar_gen3_phy_usb3_init(struct phy *p) +{ + struct rcar_gen3_usb3 *r = phy_get_drvdata(p); + + dev_vdbg(&r->phy->dev, "%s: enter (%d, %d, %d)\n", __func__, + r->usb3s_clk, r->usb_extal, r->ssc_range); + + if (!r->usb3s_clk && r->usb_extal) + rcar_gen3_phy_usb3_select_usb_extal(r); + + /* Enables VBUS detection anyway */ + writew(VBUS_EN_VBUS_EN, r->base + USB30_VBUS_EN); + + return 0; +} + +static const struct phy_ops rcar_gen3_phy_usb3_ops = { + .init = rcar_gen3_phy_usb3_init, + .owner = THIS_MODULE, +}; + +static const struct of_device_id rcar_gen3_phy_usb3_match_table[] = { + { .compatible = "renesas,rcar-gen3-usb3-phy" }, + { } +}; +MODULE_DEVICE_TABLE(of, rcar_gen3_phy_usb3_match_table); + +static int rcar_gen3_phy_usb3_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rcar_gen3_usb3 *r; + struct phy_provider *provider; + struct resource *res; + int ret = 0; + struct clk *clk; + + if (!dev->of_node) { + dev_err(dev, "This driver needs device tree\n"); + return -EINVAL; + } + + r = devm_kzalloc(dev, sizeof(*r), GFP_KERNEL); + if (!r) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + r->base = devm_ioremap_resource(dev, res); + if (IS_ERR(r->base)) + return PTR_ERR(r->base); + + clk = devm_clk_get(dev, "usb3s_clk"); + if (!IS_ERR(clk) && !clk_prepare_enable(clk)) { + r->usb3s_clk = !!clk_get_rate(clk); + clk_disable_unprepare(clk); + } + clk = devm_clk_get(dev, "usb_extal"); + if (!IS_ERR(clk) && !clk_prepare_enable(clk)) { + r->usb_extal = !!clk_get_rate(clk); + clk_disable_unprepare(clk); + } + + if (!r->usb3s_clk && !r->usb_extal) { + dev_err(dev, "This driver needs usb3s_clk and/or usb_extal\n"); + ret = -EINVAL; + goto error; + } + + /* + * devm_phy_create() will call pm_runtime_enable(&phy->dev); + * And then, phy-core will manage runtime pm for this device. + */ + pm_runtime_enable(dev); + + r->phy = devm_phy_create(dev, NULL, &rcar_gen3_phy_usb3_ops); + if (IS_ERR(r->phy)) { + dev_err(dev, "Failed to create USB3 PHY\n"); + ret = PTR_ERR(r->phy); + goto error; + } + + of_property_read_u32(dev->of_node, "renesas,ssc-range", &r->ssc_range); + + platform_set_drvdata(pdev, r); + phy_set_drvdata(r->phy, r); + + provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(provider)) { + dev_err(dev, "Failed to register PHY provider\n"); + ret = PTR_ERR(provider); + goto error; + } + + return 0; + +error: + pm_runtime_disable(dev); + + return ret; +} + +static int rcar_gen3_phy_usb3_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + return 0; +}; + +static struct platform_driver rcar_gen3_phy_usb3_driver = { + .driver = { + .name = "phy_rcar_gen3_usb3", + .of_match_table = rcar_gen3_phy_usb3_match_table, + }, + .probe = rcar_gen3_phy_usb3_probe, + .remove = rcar_gen3_phy_usb3_remove, +}; +module_platform_driver(rcar_gen3_phy_usb3_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Renesas R-Car Gen3 USB 3.0 PHY"); +MODULE_AUTHOR("Yoshihiro Shimoda "); -- cgit v1.2.3 From 82d9d5e0c0f9d22decacbca25ec53782140c8bb9 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Fri, 9 Jun 2017 13:20:41 +0300 Subject: phy: tusb1210: add support for TUSB1211 TUSB1211 is software compatible with TUSB1210 and as such we don't need an entire new driver to control it. Let's add its product ID to the existing TUSB1210 driver instead. Signed-off-by: Felipe Balbi Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/ti/phy-tusb1210.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/phy/ti/phy-tusb1210.c b/drivers/phy/ti/phy-tusb1210.c index bb3fb031c478..5dbb9a7b4945 100644 --- a/drivers/phy/ti/phy-tusb1210.c +++ b/drivers/phy/ti/phy-tusb1210.c @@ -124,7 +124,8 @@ static void tusb1210_remove(struct ulpi *ulpi) #define TI_VENDOR_ID 0x0451 static const struct ulpi_device_id tusb1210_ulpi_id[] = { - { TI_VENDOR_ID, 0x1507, }, + { TI_VENDOR_ID, 0x1507, }, /* TUSB1210 */ + { TI_VENDOR_ID, 0x1508, }, /* TUSB1211 */ { }, }; MODULE_DEVICE_TABLE(ulpi, tusb1210_ulpi_id); -- cgit v1.2.3 From 54fe30888901dd14a901bd3ad1a6f5d3c4ccd4a9 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Fri, 9 Jun 2017 13:20:42 +0300 Subject: phy: tusb1210: implement ->set_mode() ->set_mode() can be used to tell PHY to prepare itself to enter USB Host/Peripheral mode and that's very important for DRD configurations. Signed-off-by: Felipe Balbi Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/ti/phy-tusb1210.c | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/drivers/phy/ti/phy-tusb1210.c b/drivers/phy/ti/phy-tusb1210.c index 5dbb9a7b4945..b8ec39ac4dfc 100644 --- a/drivers/phy/ti/phy-tusb1210.c +++ b/drivers/phy/ti/phy-tusb1210.c @@ -11,6 +11,7 @@ */ #include #include +#include #include #include @@ -52,9 +53,43 @@ static int tusb1210_power_off(struct phy *phy) return 0; } +static int tusb1210_set_mode(struct phy *phy, enum phy_mode mode) +{ + struct tusb1210 *tusb = phy_get_drvdata(phy); + int ret; + + ret = ulpi_read(tusb->ulpi, ULPI_OTG_CTRL); + if (ret < 0) + return ret; + + switch (mode) { + case PHY_MODE_USB_HOST: + ret |= (ULPI_OTG_CTRL_DRVVBUS_EXT + | ULPI_OTG_CTRL_ID_PULLUP + | ULPI_OTG_CTRL_DP_PULLDOWN + | ULPI_OTG_CTRL_DM_PULLDOWN); + ulpi_write(tusb->ulpi, ULPI_OTG_CTRL, ret); + ret |= ULPI_OTG_CTRL_DRVVBUS; + break; + case PHY_MODE_USB_DEVICE: + ret &= ~(ULPI_OTG_CTRL_DRVVBUS + | ULPI_OTG_CTRL_DP_PULLDOWN + | ULPI_OTG_CTRL_DM_PULLDOWN); + ulpi_write(tusb->ulpi, ULPI_OTG_CTRL, ret); + ret &= ~ULPI_OTG_CTRL_DRVVBUS_EXT; + break; + default: + /* nothing */ + return 0; + } + + return ulpi_write(tusb->ulpi, ULPI_OTG_CTRL, ret); +} + static const struct phy_ops phy_ops = { .power_on = tusb1210_power_on, .power_off = tusb1210_power_off, + .set_mode = tusb1210_set_mode, .owner = THIS_MODULE, }; -- cgit v1.2.3 From d05c07c6fadc73fa580d8be1f01cb372c18fd338 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 9 Jun 2017 12:16:40 +0200 Subject: phy: cpcap-usb: add MUSB dependency When MUSB is a loadable module, we get a link error for a built-in CPCAP driver: drivers/phy/built-in.o: In function `cpcap_usb_phy_remove': phy-cpcap-usb.c:(.text+0xed9): undefined reference to `musb_mailbox' This adds a Kconfig dependency to prevent this broken configuration, enforcing that CPCAP can only be a module when MUSB is also a module. Fixes: 68a1f7c9d470 ("phy: cpcap-usb: Add CPCAP PMIC USB support") Signed-off-by: Arnd Bergmann Reviewed-by: Sebastian Reichel Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/motorola/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/phy/motorola/Kconfig b/drivers/phy/motorola/Kconfig index 91a46cffd639..6bb7d6bdf1bf 100644 --- a/drivers/phy/motorola/Kconfig +++ b/drivers/phy/motorola/Kconfig @@ -4,6 +4,7 @@ config PHY_CPCAP_USB tristate "CPCAP PMIC USB PHY driver" depends on USB_SUPPORT && IIO + depends on USB_MUSB_HDRC || USB_MUSB_HDRC=n select GENERIC_PHY select USB_PHY help -- cgit v1.2.3 From d532b7e6a916a2ec5f626269cfa8ab8e6b3680bd Mon Sep 17 00:00:00 2001 From: Raviteja Garimella Date: Fri, 9 Jun 2017 17:13:00 +0530 Subject: dt-bindings: phy: Add DT bindings documentation for NS2 USB DRD PHY This patch adds DT bindings documentation for NS2 DRD PHY driver. Signed-off-by: Raviteja Garimella Acked-by: Rob Herring Signed-off-by: Kishon Vijay Abraham I --- .../devicetree/bindings/phy/brcm,ns2-drd-phy.txt | 30 ++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 Documentation/devicetree/bindings/phy/brcm,ns2-drd-phy.txt diff --git a/Documentation/devicetree/bindings/phy/brcm,ns2-drd-phy.txt b/Documentation/devicetree/bindings/phy/brcm,ns2-drd-phy.txt new file mode 100644 index 000000000000..04f063aa7883 --- /dev/null +++ b/Documentation/devicetree/bindings/phy/brcm,ns2-drd-phy.txt @@ -0,0 +1,30 @@ +BROADCOM NORTHSTAR2 USB2 (DUAL ROLE DEVICE) PHY + +Required properties: + - compatible: brcm,ns2-drd-phy + - reg: offset and length of the NS2 PHY related registers. + - reg-names + The below registers must be provided. + icfg - for DRD ICFG configurations + rst-ctrl - for DRD IDM reset + crmu-ctrl - for CRMU core vdd, PHY and PHY PLL reset + usb2-strap - for port over current polarity reversal + - #phy-cells: Must be 0. No args required. + - vbus-gpios: vbus gpio binding + - id-gpios: id gpio binding + +Refer to phy/phy-bindings.txt for the generic PHY binding properties + +Example: + usbdrd_phy: phy@66000960 { + #phy-cells = <0>; + compatible = "brcm,ns2-drd-phy"; + reg = <0x66000960 0x24>, + <0x67012800 0x4>, + <0x6501d148 0x4>, + <0x664d0700 0x4>; + reg-names = "icfg", "rst-ctrl", + "crmu-ctrl", "usb2-strap"; + id-gpios = <&gpio_g 30 0>; + vbus-gpios = <&gpio_g 31 0>; + }; -- cgit v1.2.3 From 787f24543c4a599e5d9d311a3fce839ce87bbff0 Mon Sep 17 00:00:00 2001 From: Raviteja Garimella Date: Fri, 9 Jun 2017 17:13:01 +0530 Subject: phy: phy-bcm-ns2-usbdrd: Broadcom USB DRD PHY driver for Northstar2 This is driver for USB DRD PHY used in Broadcom's Northstar2 SoC. The phy can be configured to be in Device mode or Host mode based on the type of cable connected to the port. The driver registers to extcon framework to get appropriate connect events for Host/Device cables connect/disconnect states based on VBUS and ID interrupts. Signed-off-by: Raviteja Garimella Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/broadcom/Kconfig | 13 + drivers/phy/broadcom/Makefile | 1 + drivers/phy/broadcom/phy-bcm-ns2-usbdrd.c | 437 ++++++++++++++++++++++++++++++ 3 files changed, 451 insertions(+) create mode 100644 drivers/phy/broadcom/phy-bcm-ns2-usbdrd.c diff --git a/drivers/phy/broadcom/Kconfig b/drivers/phy/broadcom/Kconfig index d2d99023ec50..c4b632e86916 100644 --- a/drivers/phy/broadcom/Kconfig +++ b/drivers/phy/broadcom/Kconfig @@ -44,6 +44,19 @@ config PHY_NS2_PCIE Enable this to support the Broadcom Northstar2 PCIe PHY. If unsure, say N. +config PHY_NS2_USB_DRD + tristate "Broadcom Northstar2 USB DRD PHY support" + depends on OF && (ARCH_BCM_IPROC || COMPILE_TEST) + select GENERIC_PHY + select EXTCON + default ARCH_BCM_IPROC + help + Enable this to support the Broadcom Northstar2 USB DRD PHY. + This driver initializes the PHY in either HOST or DEVICE mode. + The host or device configuration is read from device tree. + + If unsure, say N. + config PHY_BRCM_SATA tristate "Broadcom SATA PHY driver" depends on ARCH_BRCMSTB || ARCH_BCM_IPROC || BMIPS_GENERIC || COMPILE_TEST diff --git a/drivers/phy/broadcom/Makefile b/drivers/phy/broadcom/Makefile index 357a7d16529f..4eb82ec8d491 100644 --- a/drivers/phy/broadcom/Makefile +++ b/drivers/phy/broadcom/Makefile @@ -3,4 +3,5 @@ obj-$(CONFIG_BCM_KONA_USB2_PHY) += phy-bcm-kona-usb2.o obj-$(CONFIG_PHY_BCM_NS_USB2) += phy-bcm-ns-usb2.o obj-$(CONFIG_PHY_BCM_NS_USB3) += phy-bcm-ns-usb3.o obj-$(CONFIG_PHY_NS2_PCIE) += phy-bcm-ns2-pcie.o +obj-$(CONFIG_PHY_NS2_USB_DRD) += phy-bcm-ns2-usbdrd.o obj-$(CONFIG_PHY_BRCM_SATA) += phy-brcm-sata.o diff --git a/drivers/phy/broadcom/phy-bcm-ns2-usbdrd.c b/drivers/phy/broadcom/phy-bcm-ns2-usbdrd.c new file mode 100644 index 000000000000..9ae59e223131 --- /dev/null +++ b/drivers/phy/broadcom/phy-bcm-ns2-usbdrd.c @@ -0,0 +1,437 @@ +/* + * Copyright (C) 2017 Broadcom + * + * 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. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ICFG_DRD_AFE 0x0 +#define ICFG_MISC_STAT 0x18 +#define ICFG_DRD_P0CTL 0x1C +#define ICFG_STRAP_CTRL 0x20 +#define ICFG_FSM_CTRL 0x24 + +#define ICFG_DEV_BIT BIT(2) +#define IDM_RST_BIT BIT(0) +#define AFE_CORERDY_VDDC BIT(18) +#define PHY_PLL_RESETB BIT(15) +#define PHY_RESETB BIT(14) +#define PHY_PLL_LOCK BIT(0) + +#define DRD_DEV_MODE BIT(20) +#define OHCI_OVRCUR_POL BIT(11) +#define ICFG_OFF_MODE BIT(6) +#define PLL_LOCK_RETRY 1000 + +#define EVT_DEVICE 0 +#define EVT_HOST 1 + +#define DRD_HOST_MODE (BIT(2) | BIT(3)) +#define DRD_DEVICE_MODE (BIT(4) | BIT(5)) +#define DRD_HOST_VAL 0x803 +#define DRD_DEV_VAL 0x807 +#define GPIO_DELAY 20 + +struct ns2_phy_data; +struct ns2_phy_driver { + void __iomem *icfgdrd_regs; + void __iomem *idmdrd_rst_ctrl; + void __iomem *crmu_usb2_ctrl; + void __iomem *usb2h_strap_reg; + struct ns2_phy_data *data; + struct extcon_dev *edev; + struct gpio_desc *vbus_gpiod; + struct gpio_desc *id_gpiod; + int id_irq; + int vbus_irq; + unsigned long debounce_jiffies; + struct delayed_work wq_extcon; +}; + +struct ns2_phy_data { + struct ns2_phy_driver *driver; + struct phy *phy; + int new_state; +}; + +static const unsigned int usb_extcon_cable[] = { + EXTCON_USB, + EXTCON_USB_HOST, + EXTCON_NONE, +}; + +static inline int pll_lock_stat(u32 usb_reg, int reg_mask, + struct ns2_phy_driver *driver) +{ + int retry = PLL_LOCK_RETRY; + u32 val; + + do { + udelay(1); + val = readl(driver->icfgdrd_regs + usb_reg); + if (val & reg_mask) + return 0; + } while (--retry > 0); + + return -EBUSY; +} + +static int ns2_drd_phy_init(struct phy *phy) +{ + struct ns2_phy_data *data = phy_get_drvdata(phy); + struct ns2_phy_driver *driver = data->driver; + u32 val; + + val = readl(driver->icfgdrd_regs + ICFG_FSM_CTRL); + + if (data->new_state == EVT_HOST) { + val &= ~DRD_DEVICE_MODE; + val |= DRD_HOST_MODE; + } else { + val &= ~DRD_HOST_MODE; + val |= DRD_DEVICE_MODE; + } + writel(val, driver->icfgdrd_regs + ICFG_FSM_CTRL); + + return 0; +} + +static int ns2_drd_phy_poweroff(struct phy *phy) +{ + struct ns2_phy_data *data = phy_get_drvdata(phy); + struct ns2_phy_driver *driver = data->driver; + u32 val; + + val = readl(driver->crmu_usb2_ctrl); + val &= ~AFE_CORERDY_VDDC; + writel(val, driver->crmu_usb2_ctrl); + + val = readl(driver->crmu_usb2_ctrl); + val &= ~DRD_DEV_MODE; + writel(val, driver->crmu_usb2_ctrl); + + /* Disable Host and Device Mode */ + val = readl(driver->icfgdrd_regs + ICFG_FSM_CTRL); + val &= ~(DRD_HOST_MODE | DRD_DEVICE_MODE | ICFG_OFF_MODE); + writel(val, driver->icfgdrd_regs + ICFG_FSM_CTRL); + + return 0; +} + +static int ns2_drd_phy_poweron(struct phy *phy) +{ + struct ns2_phy_data *data = phy_get_drvdata(phy); + struct ns2_phy_driver *driver = data->driver; + u32 extcon_event = data->new_state; + int ret; + u32 val; + + if (extcon_event == EVT_DEVICE) { + writel(DRD_DEV_VAL, driver->icfgdrd_regs + ICFG_DRD_P0CTL); + + val = readl(driver->idmdrd_rst_ctrl); + val &= ~IDM_RST_BIT; + writel(val, driver->idmdrd_rst_ctrl); + + val = readl(driver->crmu_usb2_ctrl); + val |= (AFE_CORERDY_VDDC | DRD_DEV_MODE); + writel(val, driver->crmu_usb2_ctrl); + + /* Bring PHY and PHY_PLL out of Reset */ + val = readl(driver->crmu_usb2_ctrl); + val |= (PHY_PLL_RESETB | PHY_RESETB); + writel(val, driver->crmu_usb2_ctrl); + + ret = pll_lock_stat(ICFG_MISC_STAT, PHY_PLL_LOCK, driver); + if (ret < 0) { + dev_err(&phy->dev, "Phy PLL lock failed\n"); + return ret; + } + } else { + writel(DRD_HOST_VAL, driver->icfgdrd_regs + ICFG_DRD_P0CTL); + + val = readl(driver->crmu_usb2_ctrl); + val |= AFE_CORERDY_VDDC; + writel(val, driver->crmu_usb2_ctrl); + + ret = pll_lock_stat(ICFG_MISC_STAT, PHY_PLL_LOCK, driver); + if (ret < 0) { + dev_err(&phy->dev, "Phy PLL lock failed\n"); + return ret; + } + + val = readl(driver->idmdrd_rst_ctrl); + val &= ~IDM_RST_BIT; + writel(val, driver->idmdrd_rst_ctrl); + + /* port over current Polarity */ + val = readl(driver->usb2h_strap_reg); + val |= OHCI_OVRCUR_POL; + writel(val, driver->usb2h_strap_reg); + } + + return 0; +} + +static void connect_change(struct ns2_phy_driver *driver) +{ + u32 extcon_event; + u32 val; + + extcon_event = driver->data->new_state; + val = readl(driver->icfgdrd_regs + ICFG_FSM_CTRL); + + switch (extcon_event) { + case EVT_DEVICE: + val &= ~(DRD_HOST_MODE | DRD_DEVICE_MODE); + writel(val, driver->icfgdrd_regs + ICFG_FSM_CTRL); + + val = (val & ~DRD_HOST_MODE) | DRD_DEVICE_MODE; + writel(val, driver->icfgdrd_regs + ICFG_FSM_CTRL); + + val = readl(driver->icfgdrd_regs + ICFG_DRD_P0CTL); + val |= ICFG_DEV_BIT; + writel(val, driver->icfgdrd_regs + ICFG_DRD_P0CTL); + break; + + case EVT_HOST: + val &= ~(DRD_HOST_MODE | DRD_DEVICE_MODE); + writel(val, driver->icfgdrd_regs + ICFG_FSM_CTRL); + + val = (val & ~DRD_DEVICE_MODE) | DRD_HOST_MODE; + writel(val, driver->icfgdrd_regs + ICFG_FSM_CTRL); + + val = readl(driver->usb2h_strap_reg); + val |= OHCI_OVRCUR_POL; + writel(val, driver->usb2h_strap_reg); + + val = readl(driver->icfgdrd_regs + ICFG_DRD_P0CTL); + val &= ~ICFG_DEV_BIT; + writel(val, driver->icfgdrd_regs + ICFG_DRD_P0CTL); + break; + + default: + pr_err("Invalid extcon event\n"); + break; + } +} + +static void extcon_work(struct work_struct *work) +{ + struct ns2_phy_driver *driver; + int vbus; + int id; + + driver = container_of(to_delayed_work(work), + struct ns2_phy_driver, wq_extcon); + + id = gpiod_get_value_cansleep(driver->id_gpiod); + vbus = gpiod_get_value_cansleep(driver->vbus_gpiod); + + if (!id && vbus) { /* Host connected */ + extcon_set_cable_state_(driver->edev, EXTCON_USB_HOST, true); + pr_debug("Host cable connected\n"); + driver->data->new_state = EVT_HOST; + connect_change(driver); + } else if (id && !vbus) { /* Disconnected */ + extcon_set_cable_state_(driver->edev, EXTCON_USB_HOST, false); + extcon_set_cable_state_(driver->edev, EXTCON_USB, false); + pr_debug("Cable disconnected\n"); + } else if (id && vbus) { /* Device connected */ + extcon_set_cable_state_(driver->edev, EXTCON_USB, true); + pr_debug("Device cable connected\n"); + driver->data->new_state = EVT_DEVICE; + connect_change(driver); + } +} + +static irqreturn_t gpio_irq_handler(int irq, void *dev_id) +{ + struct ns2_phy_driver *driver = dev_id; + + queue_delayed_work(system_power_efficient_wq, &driver->wq_extcon, + driver->debounce_jiffies); + + return IRQ_HANDLED; +} + +static struct phy_ops ops = { + .init = ns2_drd_phy_init, + .power_on = ns2_drd_phy_poweron, + .power_off = ns2_drd_phy_poweroff, + .owner = THIS_MODULE, +}; + +static const struct of_device_id ns2_drd_phy_dt_ids[] = { + { .compatible = "brcm,ns2-drd-phy", }, + { } +}; +MODULE_DEVICE_TABLE(of, ns2_drd_phy_dt_ids); + +static int ns2_drd_phy_probe(struct platform_device *pdev) +{ + struct phy_provider *phy_provider; + struct device *dev = &pdev->dev; + struct ns2_phy_driver *driver; + struct ns2_phy_data *data; + struct resource *res; + int ret; + u32 val; + + driver = devm_kzalloc(dev, sizeof(struct ns2_phy_driver), + GFP_KERNEL); + if (!driver) + return -ENOMEM; + + driver->data = devm_kzalloc(dev, sizeof(struct ns2_phy_data), + GFP_KERNEL); + if (!driver->data) + return -ENOMEM; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "icfg"); + driver->icfgdrd_regs = devm_ioremap_resource(dev, res); + if (IS_ERR(driver->icfgdrd_regs)) + return PTR_ERR(driver->icfgdrd_regs); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rst-ctrl"); + driver->idmdrd_rst_ctrl = devm_ioremap_resource(dev, res); + if (IS_ERR(driver->idmdrd_rst_ctrl)) + return PTR_ERR(driver->idmdrd_rst_ctrl); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "crmu-ctrl"); + driver->crmu_usb2_ctrl = devm_ioremap_resource(dev, res); + if (IS_ERR(driver->crmu_usb2_ctrl)) + return PTR_ERR(driver->crmu_usb2_ctrl); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "usb2-strap"); + driver->usb2h_strap_reg = devm_ioremap_resource(dev, res); + if (IS_ERR(driver->usb2h_strap_reg)) + return PTR_ERR(driver->usb2h_strap_reg); + + /* create extcon */ + driver->id_gpiod = devm_gpiod_get(&pdev->dev, "id", GPIOD_IN); + if (IS_ERR(driver->id_gpiod)) { + dev_err(dev, "failed to get ID GPIO\n"); + return PTR_ERR(driver->id_gpiod); + } + driver->vbus_gpiod = devm_gpiod_get(&pdev->dev, "vbus", GPIOD_IN); + if (IS_ERR(driver->vbus_gpiod)) { + dev_err(dev, "failed to get VBUS GPIO\n"); + return PTR_ERR(driver->vbus_gpiod); + } + + driver->edev = devm_extcon_dev_allocate(dev, usb_extcon_cable); + if (IS_ERR(driver->edev)) { + dev_err(dev, "failed to allocate extcon device\n"); + return -ENOMEM; + } + + ret = devm_extcon_dev_register(dev, driver->edev); + if (ret < 0) { + dev_err(dev, "failed to register extcon device\n"); + return ret; + } + + ret = gpiod_set_debounce(driver->id_gpiod, GPIO_DELAY * 1000); + if (ret < 0) + driver->debounce_jiffies = msecs_to_jiffies(GPIO_DELAY); + + INIT_DELAYED_WORK(&driver->wq_extcon, extcon_work); + + driver->id_irq = gpiod_to_irq(driver->id_gpiod); + if (driver->id_irq < 0) { + dev_err(dev, "failed to get ID IRQ\n"); + return driver->id_irq; + } + + driver->vbus_irq = gpiod_to_irq(driver->vbus_gpiod); + if (driver->vbus_irq < 0) { + dev_err(dev, "failed to get ID IRQ\n"); + return driver->vbus_irq; + } + + ret = devm_request_irq(dev, driver->id_irq, gpio_irq_handler, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "usb_id", driver); + if (ret < 0) { + dev_err(dev, "failed to request handler for ID IRQ\n"); + return ret; + } + + ret = devm_request_irq(dev, driver->vbus_irq, gpio_irq_handler, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "usb_vbus", driver); + if (ret < 0) { + dev_err(dev, "failed to request handler for VBUS IRQ\n"); + return ret; + } + + dev_set_drvdata(dev, driver); + + /* Shutdown all ports. They can be powered up as required */ + val = readl(driver->crmu_usb2_ctrl); + val &= ~(AFE_CORERDY_VDDC | PHY_RESETB); + writel(val, driver->crmu_usb2_ctrl); + + data = driver->data; + data->phy = devm_phy_create(dev, dev->of_node, &ops); + if (IS_ERR(data->phy)) { + dev_err(dev, "Failed to create usb drd phy\n"); + return PTR_ERR(data->phy); + } + + data->driver = driver; + phy_set_drvdata(data->phy, data); + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(phy_provider)) { + dev_err(dev, "Failed to register as phy provider\n"); + return PTR_ERR(phy_provider); + } + + platform_set_drvdata(pdev, driver); + + dev_info(dev, "Registered NS2 DRD Phy device\n"); + queue_delayed_work(system_power_efficient_wq, &driver->wq_extcon, + driver->debounce_jiffies); + + return 0; +} + +static struct platform_driver ns2_drd_phy_driver = { + .probe = ns2_drd_phy_probe, + .driver = { + .name = "bcm-ns2-usbphy", + .of_match_table = of_match_ptr(ns2_drd_phy_dt_ids), + }, +}; +module_platform_driver(ns2_drd_phy_driver); + +MODULE_ALIAS("platform:bcm-ns2-drd-phy"); +MODULE_AUTHOR("Broadcom"); +MODULE_DESCRIPTION("Broadcom NS2 USB2 PHY driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 1f4be24786b8521945b00e1810576911994ba504 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Sat, 10 Jun 2017 22:09:21 +0300 Subject: extcon: int3496: Switch to devm_acpi_dev_add_driver_gpios() Switch to use managed variant of acpi_dev_add_driver_gpios() to simplify error path and fix potentially wrong assingment if ->probe() fails. Signed-off-by: Andy Shevchenko Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-intel-int3496.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/extcon/extcon-intel-int3496.c b/drivers/extcon/extcon-intel-int3496.c index 9d17984bbbd4..d9f9afe45961 100644 --- a/drivers/extcon/extcon-intel-int3496.c +++ b/drivers/extcon/extcon-intel-int3496.c @@ -94,8 +94,7 @@ static int int3496_probe(struct platform_device *pdev) struct int3496_data *data; int ret; - ret = acpi_dev_add_driver_gpios(ACPI_COMPANION(dev), - acpi_int3496_default_gpios); + ret = devm_acpi_dev_add_driver_gpios(dev, acpi_int3496_default_gpios); if (ret) { dev_err(dev, "can't add GPIO ACPI mapping\n"); return ret; @@ -169,8 +168,6 @@ static int int3496_remove(struct platform_device *pdev) devm_free_irq(&pdev->dev, data->usb_id_irq, data); cancel_delayed_work_sync(&data->work); - acpi_dev_remove_driver_gpios(ACPI_COMPANION(&pdev->dev)); - return 0; } -- cgit v1.2.3 From 08f741a93333dc81b6d17d25f712ca167f250b1a Mon Sep 17 00:00:00 2001 From: Magnus Lynch Date: Mon, 12 Jun 2017 11:52:41 -0700 Subject: USB: serial: qcserial: expose methods for modem control The qcserial driver fails to expose the .tiocmget and .tiocmset methods available from usb_wwan. These methods are required by ioctl commands dealing with the modem control signals DTR, RTS, etc. With these methods not set ioctl calls intended to control the DTR state will fail. For example, pppd drops and raises DTR in preparation to dialing the modem, which handles the case of the modem already being connected by making it hang up and return to command mode. DTR control being unavailable will lead to a protracted failure to connect as the modem will be stuck in a state not responsive to command. I have tested that with this patch the described case is handled successfully. There is an analogous method for .ioctl available from usb_wwan (as used in option.c) but I conservatively omitted that for lack of familiarity. Signed-off-by: Magnus Lynch Signed-off-by: Johan Hovold --- drivers/usb/serial/qcserial.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/serial/qcserial.c b/drivers/usb/serial/qcserial.c index fd509ed6cf70..4ac137d070fb 100644 --- a/drivers/usb/serial/qcserial.c +++ b/drivers/usb/serial/qcserial.c @@ -454,6 +454,8 @@ static struct usb_serial_driver qcdevice = { .write = usb_wwan_write, .write_room = usb_wwan_write_room, .chars_in_buffer = usb_wwan_chars_in_buffer, + .tiocmget = usb_wwan_tiocmget, + .tiocmset = usb_wwan_tiocmset, .attach = qc_attach, .release = qc_release, .port_probe = usb_wwan_port_probe, -- cgit v1.2.3 From c01b244ad848ac7f0faa141182db80650a8a761a Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Mon, 5 Jun 2017 10:28:01 -0400 Subject: USB: add usbfs ioctl to retrieve the connection speed The usbfs interface does not provide any way for the user to learn the speed at which a device is connected. The current API includes a USBDEVFS_CONNECTINFO ioctl, but all it provides is the device's address and a one-bit value indicating whether the connection is low speed. That may have sufficed in the era of USB-1.1, but it isn't good enough today. This patch introduces a new ioctl, USBDEVFS_GET_SPEED, which returns a numeric value indicating the speed of the connection: unknown, low, full, high, wireless, super, or super-plus. Similar information (not exactly the same) is available through sysfs, but it seems reasonable to provide the actual value in usbfs. Signed-off-by: Alan Stern Reported-by: Reinhard Huck Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/devio.c | 3 +++ include/uapi/linux/usbdevice_fs.h | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c index 8e6ef671be9b..0e7d0e81a7cb 100644 --- a/drivers/usb/core/devio.c +++ b/drivers/usb/core/devio.c @@ -2537,6 +2537,9 @@ static long usbdev_do_ioctl(struct file *file, unsigned int cmd, case USBDEVFS_DROP_PRIVILEGES: ret = proc_drop_privileges(ps, p); break; + case USBDEVFS_GET_SPEED: + ret = ps->dev->speed; + break; } done: diff --git a/include/uapi/linux/usbdevice_fs.h b/include/uapi/linux/usbdevice_fs.h index a8653a6f40df..0bbfd4abd2e3 100644 --- a/include/uapi/linux/usbdevice_fs.h +++ b/include/uapi/linux/usbdevice_fs.h @@ -156,6 +156,11 @@ struct usbdevfs_streams { unsigned char eps[0]; }; +/* + * USB_SPEED_* values returned by USBDEVFS_GET_SPEED are defined in + * linux/usb/ch9.h + */ + #define USBDEVFS_CONTROL _IOWR('U', 0, struct usbdevfs_ctrltransfer) #define USBDEVFS_CONTROL32 _IOWR('U', 0, struct usbdevfs_ctrltransfer32) #define USBDEVFS_BULK _IOWR('U', 2, struct usbdevfs_bulktransfer) @@ -190,5 +195,6 @@ struct usbdevfs_streams { #define USBDEVFS_ALLOC_STREAMS _IOR('U', 28, struct usbdevfs_streams) #define USBDEVFS_FREE_STREAMS _IOR('U', 29, struct usbdevfs_streams) #define USBDEVFS_DROP_PRIVILEGES _IOW('U', 30, __u32) +#define USBDEVFS_GET_SPEED _IO('U', 31) #endif /* _UAPI_LINUX_USBDEVICE_FS_H */ -- cgit v1.2.3 From b3b51417d0af63fb9a06662dc292200aed9ea53f Mon Sep 17 00:00:00 2001 From: Michael Grzeschik Date: Mon, 22 May 2017 13:02:44 +0200 Subject: usb: usbip: set buffer pointers to NULL after free The usbip stack dynamically allocates the transfer_buffer and setup_packet of each urb that got generated by the tcp to usb stub code. As these pointers are always used only once we will set them to NULL after use. This is done likewise to the free_urb code in vudc_dev.c. This patch fixes double kfree situations where the usbip remote side added the URB_FREE_BUFFER. Cc: stable@vger.kernel.org Signed-off-by: Michael Grzeschik Acked-by: Shuah Khan Signed-off-by: Greg Kroah-Hartman --- drivers/usb/usbip/stub_main.c | 4 ++++ drivers/usb/usbip/stub_tx.c | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/drivers/usb/usbip/stub_main.c b/drivers/usb/usbip/stub_main.c index 44ab43fc4fcc..af10f7b131a4 100644 --- a/drivers/usb/usbip/stub_main.c +++ b/drivers/usb/usbip/stub_main.c @@ -262,7 +262,11 @@ void stub_device_cleanup_urbs(struct stub_device *sdev) kmem_cache_free(stub_priv_cache, priv); kfree(urb->transfer_buffer); + urb->transfer_buffer = NULL; + kfree(urb->setup_packet); + urb->setup_packet = NULL; + usb_free_urb(urb); } } diff --git a/drivers/usb/usbip/stub_tx.c b/drivers/usb/usbip/stub_tx.c index 6b1e8c3f0e4b..be50cef645d8 100644 --- a/drivers/usb/usbip/stub_tx.c +++ b/drivers/usb/usbip/stub_tx.c @@ -28,7 +28,11 @@ static void stub_free_priv_and_urb(struct stub_priv *priv) struct urb *urb = priv->urb; kfree(urb->setup_packet); + urb->setup_packet = NULL; + kfree(urb->transfer_buffer); + urb->transfer_buffer = NULL; + list_del(&priv->list); kmem_cache_free(stub_priv_cache, priv); usb_free_urb(urb); -- cgit v1.2.3 From c3509715fc9484a48b69a9f0196b728c960840c9 Mon Sep 17 00:00:00 2001 From: Yuyang Du Date: Mon, 22 May 2017 18:20:15 +0800 Subject: usb: usbip tool: Check the return of get_nports() If we get nonpositive number of ports, there is no sense to continue, then fail gracefully. In addition, the commit 0775a9cbc694e8c72 ("usbip: vhci extension: modifications to vhci driver") introduced configurable numbers of controllers and ports, but we have a static port number maximum, MAXNPORT. If exceeded, the idev array will be overflown. We fix it by validating the nports to make sure the port number max is not exceeded. Reviewed-by: Krzysztof Opasiak Signed-off-by: Yuyang Du Acked-by: Shuah Khan Signed-off-by: Greg Kroah-Hartman --- tools/usb/usbip/libsrc/vhci_driver.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tools/usb/usbip/libsrc/vhci_driver.c b/tools/usb/usbip/libsrc/vhci_driver.c index f659c146cdc8..036b62be0fc7 100644 --- a/tools/usb/usbip/libsrc/vhci_driver.c +++ b/tools/usb/usbip/libsrc/vhci_driver.c @@ -220,9 +220,16 @@ int usbip_vhci_driver_open(void) } vhci_driver->nports = get_nports(); - dbg("available ports: %d", vhci_driver->nports); + if (vhci_driver->nports <= 0) { + err("no available ports"); + goto err; + } else if (vhci_driver->nports > MAXNPORT) { + err("port number exceeds %d", MAXNPORT); + goto err; + } + if (refresh_imported_device_list()) goto err; -- cgit v1.2.3 From aa3ecb9154acb99282f315fe6c34ce4b7eb4d67e Mon Sep 17 00:00:00 2001 From: Yuyang Du Date: Mon, 22 May 2017 18:20:16 +0800 Subject: usb: usbip tool: Add ncontrollers in vhci_driver structure A new field ncontrollers is added to the vhci_driver structure. And this field is stored by scanning the vhci_hcd* dirs in the platform udev. Suggested-and-reviewed-by: Krzysztof Opasiak Signed-off-by: Yuyang Du Acked-by: Shuah Khan Signed-off-by: Greg Kroah-Hartman --- tools/usb/usbip/libsrc/vhci_driver.c | 36 ++++++++++++++++++++++++++++++++++++ tools/usb/usbip/libsrc/vhci_driver.h | 1 + 2 files changed, 37 insertions(+) diff --git a/tools/usb/usbip/libsrc/vhci_driver.c b/tools/usb/usbip/libsrc/vhci_driver.c index 036b62be0fc7..f6f3a19ba385 100644 --- a/tools/usb/usbip/libsrc/vhci_driver.c +++ b/tools/usb/usbip/libsrc/vhci_driver.c @@ -7,6 +7,7 @@ #include #include #include +#include #include "sysfs_utils.h" #undef PROGNAME @@ -134,6 +135,33 @@ static int get_nports(void) return (int)strtoul(attr_nports, NULL, 10); } +static int vhci_hcd_filter(const struct dirent *dirent) +{ + return strcmp(dirent->d_name, "vhci_hcd") >= 0; +} + +static int get_ncontrollers(void) +{ + struct dirent **namelist; + struct udev_device *platform; + int n; + + platform = udev_device_get_parent(vhci_driver->hc_device); + if (platform == NULL) + return -1; + + n = scandir(udev_device_get_syspath(platform), &namelist, vhci_hcd_filter, NULL); + if (n < 0) + err("scandir failed"); + else { + for (int i = 0; i < n; i++) + free(namelist[i]); + free(namelist); + } + + return n; +} + /* * Read the given port's record. * @@ -230,6 +258,14 @@ int usbip_vhci_driver_open(void) goto err; } + vhci_driver->ncontrollers = get_ncontrollers(); + dbg("available controllers: %d", vhci_driver->ncontrollers); + + if (vhci_driver->ncontrollers <=0) { + err("no available usb controllers"); + goto err; + } + if (refresh_imported_device_list()) goto err; diff --git a/tools/usb/usbip/libsrc/vhci_driver.h b/tools/usb/usbip/libsrc/vhci_driver.h index fa2316cf2cac..33add142c46e 100644 --- a/tools/usb/usbip/libsrc/vhci_driver.h +++ b/tools/usb/usbip/libsrc/vhci_driver.h @@ -31,6 +31,7 @@ struct usbip_vhci_driver { /* /sys/devices/platform/vhci_hcd */ struct udev_device *hc_device; + int ncontrollers; int nports; struct usbip_imported_device idev[MAXNPORT]; }; -- cgit v1.2.3 From fd92b7deb98a4edd31ffcc2d64cee36103805ff5 Mon Sep 17 00:00:00 2001 From: Yuyang Du Date: Mon, 22 May 2017 18:20:17 +0800 Subject: usb: usbip tool: Fix refresh_imported_device_list() The commit 0775a9cbc694e8c7 ("usbip: vhci extension: modifications to vhci driver") introduced multiple controllers, but the status of the ports are only extracted from the first status file, fix it. Reviewed-by: Krzysztof Opasiak Signed-off-by: Yuyang Du Acked-by: Shuah Khan Signed-off-by: Greg Kroah-Hartman --- tools/usb/usbip/libsrc/vhci_driver.c | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/tools/usb/usbip/libsrc/vhci_driver.c b/tools/usb/usbip/libsrc/vhci_driver.c index f6f3a19ba385..aa82c4b17797 100644 --- a/tools/usb/usbip/libsrc/vhci_driver.c +++ b/tools/usb/usbip/libsrc/vhci_driver.c @@ -108,18 +108,33 @@ static int parse_status(const char *value) return 0; } +#define MAX_STATUS_NAME 16 + static int refresh_imported_device_list(void) { const char *attr_status; + char status[MAX_STATUS_NAME+1] = "status"; + int i, ret; - attr_status = udev_device_get_sysattr_value(vhci_driver->hc_device, - "status"); - if (!attr_status) { - err("udev_device_get_sysattr_value failed"); - return -1; + for (i = 0; i < vhci_driver->ncontrollers; i++) { + if (i > 0) + snprintf(status, sizeof(status), "status.%d", i); + + attr_status = udev_device_get_sysattr_value(vhci_driver->hc_device, + status); + if (!attr_status) { + err("udev_device_get_sysattr_value failed"); + return -1; + } + + dbg("controller %d", i); + + ret = parse_status(attr_status); + if (ret != 0) + return ret; } - return parse_status(attr_status); + return 0; } static int get_nports(void) -- cgit v1.2.3 From e55dea8ede2245918c537b9a252a1269f5d7b78b Mon Sep 17 00:00:00 2001 From: Yuyang Du Date: Mon, 22 May 2017 18:20:18 +0800 Subject: usb: usbip tool: Fix parse_status() In parse_status(), all nports number of idev's are initiated to 0 by memset(), it is simply wrong, because parse_status() reads the status sys file one by one, therefore, it can only update the according vhci_driver->idev's for it to parse. Reviewed-by: Krzysztof Opasiak Signed-off-by: Yuyang Du Acked-by: Shuah Khan Signed-off-by: Greg Kroah-Hartman --- tools/usb/usbip/libsrc/vhci_driver.c | 38 ++++++++++++++---------------------- tools/usb/usbip/src/usbip_attach.c | 2 ++ 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/tools/usb/usbip/libsrc/vhci_driver.c b/tools/usb/usbip/libsrc/vhci_driver.c index aa82c4b17797..f519c73c6d99 100644 --- a/tools/usb/usbip/libsrc/vhci_driver.c +++ b/tools/usb/usbip/libsrc/vhci_driver.c @@ -36,18 +36,11 @@ err: return NULL; } - - static int parse_status(const char *value) { int ret = 0; char *c; - - for (int i = 0; i < vhci_driver->nports; i++) - memset(&vhci_driver->idev[i], 0, sizeof(vhci_driver->idev[i])); - - /* skip a header line */ c = strchr(value, '\n'); if (!c) @@ -58,6 +51,7 @@ static int parse_status(const char *value) int port, status, speed, devid; unsigned long socket; char lbusid[SYSFS_BUS_ID_SIZE]; + struct usbip_imported_device *idev; ret = sscanf(c, "%d %d %d %x %lx %31s\n", &port, &status, &speed, @@ -72,30 +66,28 @@ static int parse_status(const char *value) port, status, speed, devid); dbg("socket %lx lbusid %s", socket, lbusid); - /* if a device is connected, look at it */ - { - struct usbip_imported_device *idev = &vhci_driver->idev[port]; + idev = &vhci_driver->idev[port]; - idev->port = port; - idev->status = status; + memset(idev, 0, sizeof(*idev)); - idev->devid = devid; + idev->port = port; + idev->status = status; - idev->busnum = (devid >> 16); - idev->devnum = (devid & 0x0000ffff); + idev->devid = devid; - if (idev->status != VDEV_ST_NULL - && idev->status != VDEV_ST_NOTASSIGNED) { - idev = imported_device_init(idev, lbusid); - if (!idev) { - dbg("imported_device_init failed"); - return -1; - } + idev->busnum = (devid >> 16); + idev->devnum = (devid & 0x0000ffff); + + if (idev->status != VDEV_ST_NULL + && idev->status != VDEV_ST_NOTASSIGNED) { + idev = imported_device_init(idev, lbusid); + if (!idev) { + dbg("imported_device_init failed"); + return -1; } } - /* go to the next line */ c = strchr(c, '\n'); if (!c) diff --git a/tools/usb/usbip/src/usbip_attach.c b/tools/usb/usbip/src/usbip_attach.c index 70a6b507fb62..62a297ff647a 100644 --- a/tools/usb/usbip/src/usbip_attach.c +++ b/tools/usb/usbip/src/usbip_attach.c @@ -108,6 +108,8 @@ static int import_device(int sockfd, struct usbip_usb_device *udev) return -1; } + dbg("got free port %d", port); + rc = usbip_vhci_attach_device(port, sockfd, udev->busnum, udev->devnum, udev->speed); if (rc < 0) { -- cgit v1.2.3 From 7cf916bd639bd26db7214f2205bccdb4b9306256 Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Tue, 13 Jun 2017 16:01:13 +1000 Subject: usb: Fix typo in the definition of Endpoint[out]Request The current definition is wrong. This breaks my upcoming Aspeed virtual hub driver. Signed-off-by: Benjamin Herrenschmidt Acked-by: Alan Stern Cc: stable Signed-off-by: Greg Kroah-Hartman --- include/linux/usb/hcd.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h index 50398b69ca44..a1f03ebfde47 100644 --- a/include/linux/usb/hcd.h +++ b/include/linux/usb/hcd.h @@ -565,9 +565,9 @@ extern void usb_ep0_reinit(struct usb_device *); ((USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_INTERFACE)<<8) #define EndpointRequest \ - ((USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_INTERFACE)<<8) + ((USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_ENDPOINT)<<8) #define EndpointOutRequest \ - ((USB_DIR_OUT|USB_TYPE_STANDARD|USB_RECIP_INTERFACE)<<8) + ((USB_DIR_OUT|USB_TYPE_STANDARD|USB_RECIP_ENDPOINT)<<8) /* class requests from the USB 2.0 hub spec, table 11-15 */ #define HUB_CLASS_REQ(dir, type, request) ((((dir) | (type)) << 8) | (request)) -- cgit v1.2.3 From 264ffb194a92df256b604d649faadfbd23a054e2 Mon Sep 17 00:00:00 2001 From: Arvind Yadav Date: Mon, 12 Jun 2017 16:45:14 +0530 Subject: usb: host: ehci-exynos: Handle return value of clk_prepare_enable clk_prepare_enable() can fail here and we must check its return value. Signed-off-by: Arvind Yadav Acked-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ehci-exynos.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/usb/host/ehci-exynos.c b/drivers/usb/host/ehci-exynos.c index 7a603f66a9bc..26b641100639 100644 --- a/drivers/usb/host/ehci-exynos.c +++ b/drivers/usb/host/ehci-exynos.c @@ -279,7 +279,9 @@ static int exynos_ehci_resume(struct device *dev) struct exynos_ehci_hcd *exynos_ehci = to_exynos_ehci(hcd); int ret; - clk_prepare_enable(exynos_ehci->clk); + ret = clk_prepare_enable(exynos_ehci->clk); + if (ret) + return ret; ret = exynos_ehci_phy_enable(dev); if (ret) { -- cgit v1.2.3 From 5ec0edc96558a0090c7c3361adc0a5d98102cd80 Mon Sep 17 00:00:00 2001 From: Yuyang Du Date: Thu, 8 Jun 2017 13:04:05 +0800 Subject: usbip: vhci-hcd: Rename function names to reflect their struct names These helper function names are renamed to have their full struct names to avoid confusion: - hcd_to_vhci() -> hcd_to_vhci_hcd() - vhci_to_hcd() -> vhci_hcd_to_hcd() - vdev_to_vhci() -> vdev_to_vhci_hcd() Signed-off-by: Yuyang Du Acked-by: Shuah Khan Signed-off-by: Greg Kroah-Hartman --- drivers/usb/usbip/vhci.h | 11 +++++------ drivers/usb/usbip/vhci_hcd.c | 34 +++++++++++++++++----------------- drivers/usb/usbip/vhci_rx.c | 12 ++++++------ drivers/usb/usbip/vhci_sysfs.c | 6 +++--- 4 files changed, 31 insertions(+), 32 deletions(-) diff --git a/drivers/usb/usbip/vhci.h b/drivers/usb/usbip/vhci.h index 88b71c4e068f..bff472f748a9 100644 --- a/drivers/usb/usbip/vhci.h +++ b/drivers/usb/usbip/vhci.h @@ -134,7 +134,7 @@ static inline int port_to_pdev_nr(__u32 port) return port / VHCI_HC_PORTS; } -static inline struct vhci_hcd *hcd_to_vhci(struct usb_hcd *hcd) +static inline struct vhci_hcd *hcd_to_vhci_hcd(struct usb_hcd *hcd) { return (struct vhci_hcd *) (hcd->hcd_priv); } @@ -149,15 +149,14 @@ static inline const char *hcd_name(struct usb_hcd *hcd) return (hcd)->self.bus_name; } -static inline struct usb_hcd *vhci_to_hcd(struct vhci_hcd *vhci) +static inline struct usb_hcd *vhci_hcd_to_hcd(struct vhci_hcd *vhci_hcd) { - return container_of((void *) vhci, struct usb_hcd, hcd_priv); + return container_of((void *) vhci_hcd, struct usb_hcd, hcd_priv); } -static inline struct vhci_hcd *vdev_to_vhci(struct vhci_device *vdev) +static inline struct vhci_hcd *vdev_to_vhci_hcd(struct vhci_device *vdev) { - return container_of( - (void *)(vdev - vdev->rhport), struct vhci_hcd, vdev); + return container_of((void *)(vdev - vdev->rhport), struct vhci_hcd, vdev); } #endif /* __USBIP_VHCI_H */ diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c index 0585078638db..863a12d53933 100644 --- a/drivers/usb/usbip/vhci_hcd.c +++ b/drivers/usb/usbip/vhci_hcd.c @@ -124,7 +124,7 @@ static void dump_port_status_diff(u32 prev_status, u32 new_status) void rh_port_connect(struct vhci_device *vdev, enum usb_device_speed speed) { - struct vhci_hcd *vhci = vdev_to_vhci(vdev); + struct vhci_hcd *vhci = vdev_to_vhci_hcd(vdev); int rhport = vdev->rhport; u32 status; unsigned long flags; @@ -152,12 +152,12 @@ void rh_port_connect(struct vhci_device *vdev, enum usb_device_speed speed) spin_unlock_irqrestore(&vhci->lock, flags); - usb_hcd_poll_rh_status(vhci_to_hcd(vhci)); + usb_hcd_poll_rh_status(vhci_hcd_to_hcd(vhci)); } static void rh_port_disconnect(struct vhci_device *vdev) { - struct vhci_hcd *vhci = vdev_to_vhci(vdev); + struct vhci_hcd *vhci = vdev_to_vhci_hcd(vdev); int rhport = vdev->rhport; u32 status; unsigned long flags; @@ -174,7 +174,7 @@ static void rh_port_disconnect(struct vhci_device *vdev) vhci->port_status[rhport] = status; spin_unlock_irqrestore(&vhci->lock, flags); - usb_hcd_poll_rh_status(vhci_to_hcd(vhci)); + usb_hcd_poll_rh_status(vhci_hcd_to_hcd(vhci)); } #define PORT_C_MASK \ @@ -206,7 +206,7 @@ static int vhci_hub_status(struct usb_hcd *hcd, char *buf) retval = DIV_ROUND_UP(VHCI_HC_PORTS + 1, 8); memset(buf, 0, retval); - vhci = hcd_to_vhci(hcd); + vhci = hcd_to_vhci_hcd(hcd); spin_lock_irqsave(&vhci->lock, flags); if (!HCD_HW_ACCESSIBLE(hcd)) { @@ -273,7 +273,7 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, pr_err("invalid port number %d\n", wIndex); rhport = ((__u8)(wIndex & 0x00ff)) - 1; - dum = hcd_to_vhci(hcd); + dum = hcd_to_vhci_hcd(hcd); spin_lock_irqsave(&dum->lock, flags); @@ -445,7 +445,7 @@ static void vhci_tx_urb(struct urb *urb, struct vhci_device *vdev) pr_err("could not get virtual device"); return; } - vhci = vdev_to_vhci(vdev); + vhci = vdev_to_vhci_hcd(vdev); priv = kzalloc(sizeof(struct vhci_priv), GFP_ATOMIC); if (!priv) { @@ -473,7 +473,7 @@ static void vhci_tx_urb(struct urb *urb, struct vhci_device *vdev) static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags) { - struct vhci_hcd *vhci = hcd_to_vhci(hcd); + struct vhci_hcd *vhci = hcd_to_vhci_hcd(hcd); struct device *dev = &urb->dev->dev; u8 portnum = urb->dev->portnum; int ret = 0; @@ -640,7 +640,7 @@ no_need_unlink: */ static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) { - struct vhci_hcd *vhci = hcd_to_vhci(hcd); + struct vhci_hcd *vhci = hcd_to_vhci_hcd(hcd); struct vhci_priv *priv; struct vhci_device *vdev; unsigned long flags; @@ -691,7 +691,7 @@ static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) usb_hcd_unlink_urb_from_ep(hcd, urb); spin_unlock_irqrestore(&vhci->lock, flags); - usb_hcd_giveback_urb(vhci_to_hcd(vhci), urb, urb->status); + usb_hcd_giveback_urb(vhci_hcd_to_hcd(vhci), urb, urb->status); spin_lock_irqsave(&vhci->lock, flags); } else { @@ -733,8 +733,8 @@ static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) static void vhci_device_unlink_cleanup(struct vhci_device *vdev) { - struct vhci_hcd *vhci = vdev_to_vhci(vdev); - struct usb_hcd *hcd = vhci_to_hcd(vhci); + struct vhci_hcd *vhci = vdev_to_vhci_hcd(vdev); + struct usb_hcd *hcd = vhci_hcd_to_hcd(vhci); struct vhci_unlink *unlink, *tmp; unsigned long flags; @@ -920,7 +920,7 @@ static int hcd_name_to_id(const char *name) static int vhci_start(struct usb_hcd *hcd) { - struct vhci_hcd *vhci = hcd_to_vhci(hcd); + struct vhci_hcd *vhci = hcd_to_vhci_hcd(hcd); int id, rhport; int err = 0; @@ -968,7 +968,7 @@ static int vhci_start(struct usb_hcd *hcd) static void vhci_stop(struct usb_hcd *hcd) { - struct vhci_hcd *vhci = hcd_to_vhci(hcd); + struct vhci_hcd *vhci = hcd_to_vhci_hcd(hcd); int id, rhport; usbip_dbg_vhci_hc("stop VHCI controller\n"); @@ -1000,7 +1000,7 @@ static int vhci_get_frame_number(struct usb_hcd *hcd) /* FIXME: suspend/resume */ static int vhci_bus_suspend(struct usb_hcd *hcd) { - struct vhci_hcd *vhci = hcd_to_vhci(hcd); + struct vhci_hcd *vhci = hcd_to_vhci_hcd(hcd); unsigned long flags; dev_dbg(&hcd->self.root_hub->dev, "%s\n", __func__); @@ -1014,7 +1014,7 @@ static int vhci_bus_suspend(struct usb_hcd *hcd) static int vhci_bus_resume(struct usb_hcd *hcd) { - struct vhci_hcd *vhci = hcd_to_vhci(hcd); + struct vhci_hcd *vhci = hcd_to_vhci_hcd(hcd); int rc = 0; unsigned long flags; @@ -1124,7 +1124,7 @@ static int vhci_hcd_suspend(struct platform_device *pdev, pm_message_t state) hcd = platform_get_drvdata(pdev); if (!hcd) return 0; - vhci = hcd_to_vhci(hcd); + vhci = hcd_to_vhci_hcd(hcd); spin_lock_irqsave(&vhci->lock, flags); diff --git a/drivers/usb/usbip/vhci_rx.c b/drivers/usb/usbip/vhci_rx.c index fc2d319e2360..85abe898d7bd 100644 --- a/drivers/usb/usbip/vhci_rx.c +++ b/drivers/usb/usbip/vhci_rx.c @@ -70,7 +70,7 @@ struct urb *pickup_urb_and_free_priv(struct vhci_device *vdev, __u32 seqnum) static void vhci_recv_ret_submit(struct vhci_device *vdev, struct usbip_header *pdu) { - struct vhci_hcd *vhci = vdev_to_vhci(vdev); + struct vhci_hcd *vhci = vdev_to_vhci_hcd(vdev); struct usbip_device *ud = &vdev->ud; struct urb *urb; unsigned long flags; @@ -107,10 +107,10 @@ static void vhci_recv_ret_submit(struct vhci_device *vdev, usbip_dbg_vhci_rx("now giveback urb %p\n", urb); spin_lock_irqsave(&vhci->lock, flags); - usb_hcd_unlink_urb_from_ep(vhci_to_hcd(vhci), urb); + usb_hcd_unlink_urb_from_ep(vhci_hcd_to_hcd(vhci), urb); spin_unlock_irqrestore(&vhci->lock, flags); - usb_hcd_giveback_urb(vhci_to_hcd(vhci), urb, urb->status); + usb_hcd_giveback_urb(vhci_hcd_to_hcd(vhci), urb, urb->status); usbip_dbg_vhci_rx("Leave\n"); } @@ -143,7 +143,7 @@ static struct vhci_unlink *dequeue_pending_unlink(struct vhci_device *vdev, static void vhci_recv_ret_unlink(struct vhci_device *vdev, struct usbip_header *pdu) { - struct vhci_hcd *vhci = vdev_to_vhci(vdev); + struct vhci_hcd *vhci = vdev_to_vhci_hcd(vdev); struct vhci_unlink *unlink; struct urb *urb; unsigned long flags; @@ -177,10 +177,10 @@ static void vhci_recv_ret_unlink(struct vhci_device *vdev, pr_info("urb->status %d\n", urb->status); spin_lock_irqsave(&vhci->lock, flags); - usb_hcd_unlink_urb_from_ep(vhci_to_hcd(vhci), urb); + usb_hcd_unlink_urb_from_ep(vhci_hcd_to_hcd(vhci), urb); spin_unlock_irqrestore(&vhci->lock, flags); - usb_hcd_giveback_urb(vhci_to_hcd(vhci), urb, urb->status); + usb_hcd_giveback_urb(vhci_hcd_to_hcd(vhci), urb, urb->status); } kfree(unlink); diff --git a/drivers/usb/usbip/vhci_sysfs.c b/drivers/usb/usbip/vhci_sysfs.c index b96e5b189269..d878faa3f52c 100644 --- a/drivers/usb/usbip/vhci_sysfs.c +++ b/drivers/usb/usbip/vhci_sysfs.c @@ -43,7 +43,7 @@ static ssize_t status_show_vhci(int pdev_nr, char *out) return 0; } - vhci = hcd_to_vhci(platform_get_drvdata(pdev)); + vhci = hcd_to_vhci_hcd(platform_get_drvdata(pdev)); spin_lock_irqsave(&vhci->lock, flags); @@ -212,7 +212,7 @@ static ssize_t store_detach(struct device *dev, struct device_attribute *attr, return -EAGAIN; } - ret = vhci_port_disconnect(hcd_to_vhci(hcd), rhport); + ret = vhci_port_disconnect(hcd_to_vhci_hcd(hcd), rhport); if (ret < 0) return -EINVAL; @@ -292,7 +292,7 @@ static ssize_t store_attach(struct device *dev, struct device_attribute *attr, dev_err(dev, "port %d is not ready\n", port); return -EAGAIN; } - vhci = hcd_to_vhci(hcd); + vhci = hcd_to_vhci_hcd(hcd); vdev = &vhci->vdev[rhport]; /* Extract socket from fd. */ -- cgit v1.2.3 From 559e9c00b340ab4929e36a6bb176ca11f95ef46a Mon Sep 17 00:00:00 2001 From: Yuyang Du Date: Thu, 8 Jun 2017 13:04:06 +0800 Subject: usbip: vhci-hcd: Add vhci struct In order to support SuperSpeed devices, a USB3 HCD is added to share the USB2 HCD. As a result, a VHCI is composed of two vhci_hcds associated with the two HCDs respectively. So we add another level of abstraction, vhci, and thus this vhci structure. Signed-off-by: Yuyang Du Acked-by: Shuah Khan Signed-off-by: Greg Kroah-Hartman --- drivers/usb/usbip/vhci.h | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/usb/usbip/vhci.h b/drivers/usb/usbip/vhci.h index bff472f748a9..995958494263 100644 --- a/drivers/usb/usbip/vhci.h +++ b/drivers/usb/usbip/vhci.h @@ -87,8 +87,17 @@ struct vhci_unlink { #define MAX_STATUS_NAME 16 -/* for usb_bus.hcpriv */ +struct vhci { + spinlock_t lock; + + struct vhci_hcd *vhci_hcd_hs; + struct vhci_hcd *vhci_hcd_ss; +}; + +/* for usb_hcd.hcd_priv[0] */ struct vhci_hcd { + struct vhci *vhci; + spinlock_t lock; u32 port_status[VHCI_HC_PORTS]; @@ -108,6 +117,7 @@ struct vhci_hcd { extern int vhci_num_controllers; extern struct platform_device **vhci_pdevs; +extern struct vhci *vhcis; extern struct attribute_group vhci_attr_group; /* vhci_hcd.c */ -- cgit v1.2.3 From 89a73d281fa4f58942474ada19d34d7ea39af2f4 Mon Sep 17 00:00:00 2001 From: Yuyang Du Date: Thu, 8 Jun 2017 13:04:07 +0800 Subject: usbip: vhci-hcd: Move VHCI platform device into vhci struct Every VHCI is a platform device, so move the platform_device struct into the VHCI struct. Signed-off-by: Yuyang Du Acked-by: Shuah Khan Signed-off-by: Greg Kroah-Hartman --- drivers/usb/usbip/vhci.h | 3 ++- drivers/usb/usbip/vhci_hcd.c | 17 ++++++++--------- drivers/usb/usbip/vhci_sysfs.c | 6 +++--- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/drivers/usb/usbip/vhci.h b/drivers/usb/usbip/vhci.h index 995958494263..62ee537b44c8 100644 --- a/drivers/usb/usbip/vhci.h +++ b/drivers/usb/usbip/vhci.h @@ -90,6 +90,8 @@ struct vhci_unlink { struct vhci { spinlock_t lock; + struct platform_device *pdev; + struct vhci_hcd *vhci_hcd_hs; struct vhci_hcd *vhci_hcd_ss; }; @@ -116,7 +118,6 @@ struct vhci_hcd { }; extern int vhci_num_controllers; -extern struct platform_device **vhci_pdevs; extern struct vhci *vhcis; extern struct attribute_group vhci_attr_group; diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c index 863a12d53933..a445d237b0c3 100644 --- a/drivers/usb/usbip/vhci_hcd.c +++ b/drivers/usb/usbip/vhci_hcd.c @@ -58,8 +58,7 @@ static const char driver_name[] = "vhci_hcd"; static const char driver_desc[] = "USB/IP Virtual Host Controller"; int vhci_num_controllers = VHCI_NR_HCS; - -struct platform_device **vhci_pdevs; +struct vhci *vhcis; static const char * const bit_desc[] = { "CONNECTION", /*0*/ @@ -1193,7 +1192,7 @@ static int add_platform_device(int id) if (IS_ERR(pdev)) return PTR_ERR(pdev); - *(vhci_pdevs + id) = pdev; + vhcis[id].pdev = pdev; return 0; } @@ -1203,10 +1202,10 @@ static void del_platform_devices(void) int i; for (i = 0; i < vhci_num_controllers; i++) { - pdev = *(vhci_pdevs + i); + pdev = vhcis[i].pdev; if (pdev != NULL) platform_device_unregister(pdev); - *(vhci_pdevs + i) = NULL; + vhcis[i].pdev = NULL; } sysfs_remove_link(&platform_bus.kobj, driver_name); } @@ -1221,8 +1220,8 @@ static int __init vhci_hcd_init(void) if (vhci_num_controllers < 1) vhci_num_controllers = 1; - vhci_pdevs = kcalloc(vhci_num_controllers, sizeof(void *), GFP_KERNEL); - if (vhci_pdevs == NULL) + vhcis = kcalloc(vhci_num_controllers, sizeof(struct vhci), GFP_KERNEL); + if (vhcis == NULL) return -ENOMEM; ret = platform_driver_register(&vhci_driver); @@ -1242,7 +1241,7 @@ err_platform_device_register: del_platform_devices(); platform_driver_unregister(&vhci_driver); err_driver_register: - kfree(vhci_pdevs); + kfree(vhcis); return ret; } @@ -1250,7 +1249,7 @@ static void __exit vhci_hcd_exit(void) { del_platform_devices(); platform_driver_unregister(&vhci_driver); - kfree(vhci_pdevs); + kfree(vhcis); } module_init(vhci_hcd_init); diff --git a/drivers/usb/usbip/vhci_sysfs.c b/drivers/usb/usbip/vhci_sysfs.c index d878faa3f52c..07f0d3789a8c 100644 --- a/drivers/usb/usbip/vhci_sysfs.c +++ b/drivers/usb/usbip/vhci_sysfs.c @@ -32,7 +32,7 @@ /* Sysfs entry to show port status */ static ssize_t status_show_vhci(int pdev_nr, char *out) { - struct platform_device *pdev = *(vhci_pdevs + pdev_nr); + struct platform_device *pdev = vhcis[pdev_nr].pdev; struct vhci_hcd *vhci; char *s = out; int i = 0; @@ -206,7 +206,7 @@ static ssize_t store_detach(struct device *dev, struct device_attribute *attr, if (!valid_port(pdev_nr, rhport)) return -EINVAL; - hcd = platform_get_drvdata(*(vhci_pdevs + pdev_nr)); + hcd = platform_get_drvdata(vhcis[pdev_nr].pdev); if (hcd == NULL) { dev_err(dev, "port is not ready %u\n", port); return -EAGAIN; @@ -287,7 +287,7 @@ static ssize_t store_attach(struct device *dev, struct device_attribute *attr, if (!valid_args(pdev_nr, rhport, speed)) return -EINVAL; - hcd = platform_get_drvdata(*(vhci_pdevs + pdev_nr)); + hcd = platform_get_drvdata(vhcis[pdev_nr].pdev); if (hcd == NULL) { dev_err(dev, "port %d is not ready\n", port); return -EAGAIN; -- cgit v1.2.3 From dff3565b8e1c0be6fc83ba47dcab45c149dcab5b Mon Sep 17 00:00:00 2001 From: Yuyang Du Date: Thu, 8 Jun 2017 13:04:08 +0800 Subject: usbip: vhci-hcd: Rework vhci_hcd_init A vhci struct is added as the platform-specific data to the vhci platform device, in order to get the vhci by its platform device. This is done in vhci_hcd_init(). Signed-off-by: Yuyang Du Acked-by: Shuah Khan Signed-off-by: Greg Kroah-Hartman --- drivers/usb/usbip/vhci_hcd.c | 51 ++++++++++++++++++++---------------- tools/usb/usbip/libsrc/vhci_driver.c | 2 +- tools/usb/usbip/libsrc/vhci_driver.h | 1 + 3 files changed, 30 insertions(+), 24 deletions(-) diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c index a445d237b0c3..893a5dedd0e5 100644 --- a/drivers/usb/usbip/vhci_hcd.c +++ b/drivers/usb/usbip/vhci_hcd.c @@ -1178,24 +1178,6 @@ static struct platform_driver vhci_driver = { }, }; -static int add_platform_device(int id) -{ - struct platform_device *pdev; - int dev_nr; - - if (id == 0) - dev_nr = -1; - else - dev_nr = id; - - pdev = platform_device_register_simple(driver_name, dev_nr, NULL, 0); - if (IS_ERR(pdev)) - return PTR_ERR(pdev); - - vhcis[id].pdev = pdev; - return 0; -} - static void del_platform_devices(void) { struct platform_device *pdev; @@ -1224,23 +1206,46 @@ static int __init vhci_hcd_init(void) if (vhcis == NULL) return -ENOMEM; + for (i = 0; i < vhci_num_controllers; i++) { + vhcis[i].pdev = platform_device_alloc(driver_name, i); + if (!vhcis[i].pdev) { + i--; + while (i >= 0) + platform_device_put(vhcis[i--].pdev); + ret = -ENOMEM; + goto err_device_alloc; + } + } + for (i = 0; i < vhci_num_controllers; i++) { + void *vhci = &vhcis[i]; + ret = platform_device_add_data(vhcis[i].pdev, &vhci, sizeof(void *)); + if (ret) + goto err_driver_register; + } + ret = platform_driver_register(&vhci_driver); if (ret) goto err_driver_register; for (i = 0; i < vhci_num_controllers; i++) { - ret = add_platform_device(i); - if (ret) - goto err_platform_device_register; + ret = platform_device_add(vhcis[i].pdev); + if (ret < 0) { + i--; + while (i >= 0) + platform_device_del(vhcis[i--].pdev); + goto err_add_hcd; + } } pr_info(DRIVER_DESC " v" USBIP_VERSION "\n"); return ret; -err_platform_device_register: - del_platform_devices(); +err_add_hcd: platform_driver_unregister(&vhci_driver); err_driver_register: + for (i = 0; i < vhci_num_controllers; i++) + platform_device_put(vhcis[i].pdev); +err_device_alloc: kfree(vhcis); return ret; } diff --git a/tools/usb/usbip/libsrc/vhci_driver.c b/tools/usb/usbip/libsrc/vhci_driver.c index f519c73c6d99..3d8189b4f539 100644 --- a/tools/usb/usbip/libsrc/vhci_driver.c +++ b/tools/usb/usbip/libsrc/vhci_driver.c @@ -248,7 +248,7 @@ int usbip_vhci_driver_open(void) vhci_driver->hc_device = udev_device_new_from_subsystem_sysname(udev_context, USBIP_VHCI_BUS_TYPE, - USBIP_VHCI_DRV_NAME); + USBIP_VHCI_DEVICE_NAME); if (!vhci_driver->hc_device) { err("udev_device_new_from_subsystem_sysname failed"); goto err; diff --git a/tools/usb/usbip/libsrc/vhci_driver.h b/tools/usb/usbip/libsrc/vhci_driver.h index 33add142c46e..dfe19c1c0245 100644 --- a/tools/usb/usbip/libsrc/vhci_driver.h +++ b/tools/usb/usbip/libsrc/vhci_driver.h @@ -11,6 +11,7 @@ #include "usbip_common.h" #define USBIP_VHCI_BUS_TYPE "platform" +#define USBIP_VHCI_DEVICE_NAME "vhci_hcd.0" #define MAXNPORT 128 struct usbip_imported_device { -- cgit v1.2.3 From 03cd00d538a6feb0492cd153edf256ef7d7bd95e Mon Sep 17 00:00:00 2001 From: Yuyang Du Date: Thu, 8 Jun 2017 13:04:09 +0800 Subject: usbip: vhci-hcd: Set the vhci structure up to work This patch enables the new vhci structure. Its lock protects both the USB2 hub and the shared USB3 hub. Signed-off-by: Yuyang Du Acked-by: Shuah Khan Signed-off-by: Greg Kroah-Hartman --- drivers/usb/usbip/vhci.h | 2 - drivers/usb/usbip/vhci_hcd.c | 206 ++++++++++++++++++++++++----------------- drivers/usb/usbip/vhci_rx.c | 16 ++-- drivers/usb/usbip/vhci_sysfs.c | 26 ++++-- 4 files changed, 145 insertions(+), 105 deletions(-) diff --git a/drivers/usb/usbip/vhci.h b/drivers/usb/usbip/vhci.h index 62ee537b44c8..8a979fc00ac1 100644 --- a/drivers/usb/usbip/vhci.h +++ b/drivers/usb/usbip/vhci.h @@ -100,8 +100,6 @@ struct vhci { struct vhci_hcd { struct vhci *vhci; - spinlock_t lock; - u32 port_status[VHCI_HC_PORTS]; unsigned resuming:1; diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c index 893a5dedd0e5..c96dd44ff162 100644 --- a/drivers/usb/usbip/vhci_hcd.c +++ b/drivers/usb/usbip/vhci_hcd.c @@ -123,7 +123,8 @@ static void dump_port_status_diff(u32 prev_status, u32 new_status) void rh_port_connect(struct vhci_device *vdev, enum usb_device_speed speed) { - struct vhci_hcd *vhci = vdev_to_vhci_hcd(vdev); + struct vhci_hcd *vhci_hcd = vdev_to_vhci_hcd(vdev); + struct vhci *vhci = vhci_hcd->vhci; int rhport = vdev->rhport; u32 status; unsigned long flags; @@ -132,7 +133,7 @@ void rh_port_connect(struct vhci_device *vdev, enum usb_device_speed speed) spin_lock_irqsave(&vhci->lock, flags); - status = vhci->port_status[rhport]; + status = vhci_hcd->port_status[rhport]; status |= USB_PORT_STAT_CONNECTION | (1 << USB_PORT_FEAT_C_CONNECTION); @@ -147,16 +148,17 @@ void rh_port_connect(struct vhci_device *vdev, enum usb_device_speed speed) break; } - vhci->port_status[rhport] = status; + vhci_hcd->port_status[rhport] = status; spin_unlock_irqrestore(&vhci->lock, flags); - usb_hcd_poll_rh_status(vhci_hcd_to_hcd(vhci)); + usb_hcd_poll_rh_status(vhci_hcd_to_hcd(vhci_hcd)); } static void rh_port_disconnect(struct vhci_device *vdev) { - struct vhci_hcd *vhci = vdev_to_vhci_hcd(vdev); + struct vhci_hcd *vhci_hcd = vdev_to_vhci_hcd(vdev); + struct vhci *vhci = vhci_hcd->vhci; int rhport = vdev->rhport; u32 status; unsigned long flags; @@ -165,15 +167,15 @@ static void rh_port_disconnect(struct vhci_device *vdev) spin_lock_irqsave(&vhci->lock, flags); - status = vhci->port_status[rhport]; + status = vhci_hcd->port_status[rhport]; status &= ~USB_PORT_STAT_CONNECTION; status |= (1 << USB_PORT_FEAT_C_CONNECTION); - vhci->port_status[rhport] = status; + vhci_hcd->port_status[rhport] = status; spin_unlock_irqrestore(&vhci->lock, flags); - usb_hcd_poll_rh_status(vhci_hcd_to_hcd(vhci)); + usb_hcd_poll_rh_status(vhci_hcd_to_hcd(vhci_hcd)); } #define PORT_C_MASK \ @@ -196,17 +198,15 @@ static void rh_port_disconnect(struct vhci_device *vdev) */ static int vhci_hub_status(struct usb_hcd *hcd, char *buf) { - struct vhci_hcd *vhci; - int retval; + struct vhci_hcd *vhci_hcd = hcd_to_vhci_hcd(hcd); + struct vhci *vhci = vhci_hcd->vhci; + int retval = DIV_ROUND_UP(VHCI_HC_PORTS + 1, 8); int rhport; int changed = 0; unsigned long flags; - retval = DIV_ROUND_UP(VHCI_HC_PORTS + 1, 8); memset(buf, 0, retval); - vhci = hcd_to_vhci_hcd(hcd); - spin_lock_irqsave(&vhci->lock, flags); if (!HCD_HW_ACCESSIBLE(hcd)) { usbip_dbg_vhci_rh("hw accessible flag not on?\n"); @@ -215,7 +215,7 @@ static int vhci_hub_status(struct usb_hcd *hcd, char *buf) /* check pseudo status register for each port */ for (rhport = 0; rhport < VHCI_HC_PORTS; rhport++) { - if ((vhci->port_status[rhport] & PORT_C_MASK)) { + if ((vhci_hcd->port_status[rhport] & PORT_C_MASK)) { /* The status of a port has been changed, */ usbip_dbg_vhci_rh("port %d status changed\n", rhport); @@ -252,7 +252,8 @@ static inline void hub_descriptor(struct usb_hub_descriptor *desc) static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, u16 wIndex, char *buf, u16 wLength) { - struct vhci_hcd *dum; + struct vhci_hcd *vhci_hcd; + struct vhci *vhci; int retval = 0; int rhport; unsigned long flags; @@ -272,13 +273,14 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, pr_err("invalid port number %d\n", wIndex); rhport = ((__u8)(wIndex & 0x00ff)) - 1; - dum = hcd_to_vhci_hcd(hcd); + vhci_hcd = hcd_to_vhci_hcd(hcd); + vhci = vhci_hcd->vhci; - spin_lock_irqsave(&dum->lock, flags); + spin_lock_irqsave(&vhci->lock, flags); /* store old status and compare now and old later */ if (usbip_dbg_flag_vhci_rh) { - memcpy(prev_port_status, dum->port_status, + memcpy(prev_port_status, vhci_hcd->port_status, sizeof(prev_port_status)); } @@ -289,29 +291,29 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, case ClearPortFeature: switch (wValue) { case USB_PORT_FEAT_SUSPEND: - if (dum->port_status[rhport] & USB_PORT_STAT_SUSPEND) { + if (vhci_hcd->port_status[rhport] & USB_PORT_STAT_SUSPEND) { /* 20msec signaling */ - dum->resuming = 1; - dum->re_timeout = + vhci_hcd->resuming = 1; + vhci_hcd->re_timeout = jiffies + msecs_to_jiffies(20); } break; case USB_PORT_FEAT_POWER: usbip_dbg_vhci_rh( " ClearPortFeature: USB_PORT_FEAT_POWER\n"); - dum->port_status[rhport] = 0; - dum->resuming = 0; + vhci_hcd->port_status[rhport] = 0; + vhci_hcd->resuming = 0; break; case USB_PORT_FEAT_C_RESET: usbip_dbg_vhci_rh( " ClearPortFeature: USB_PORT_FEAT_C_RESET\n"); - switch (dum->vdev[rhport].speed) { + switch (vhci_hcd->vdev[rhport].speed) { case USB_SPEED_HIGH: - dum->port_status[rhport] |= + vhci_hcd->port_status[rhport] |= USB_PORT_STAT_HIGH_SPEED; break; case USB_SPEED_LOW: - dum->port_status[rhport] |= + vhci_hcd->port_status[rhport] |= USB_PORT_STAT_LOW_SPEED; break; default: @@ -321,7 +323,7 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, default: usbip_dbg_vhci_rh(" ClearPortFeature: default %x\n", wValue); - dum->port_status[rhport] &= ~(1 << wValue); + vhci_hcd->port_status[rhport] &= ~(1 << wValue); break; } break; @@ -345,36 +347,36 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, /* whoever resets or resumes must GetPortStatus to * complete it!! */ - if (dum->resuming && time_after(jiffies, dum->re_timeout)) { - dum->port_status[rhport] |= + if (vhci_hcd->resuming && time_after(jiffies, vhci_hcd->re_timeout)) { + vhci_hcd->port_status[rhport] |= (1 << USB_PORT_FEAT_C_SUSPEND); - dum->port_status[rhport] &= + vhci_hcd->port_status[rhport] &= ~(1 << USB_PORT_FEAT_SUSPEND); - dum->resuming = 0; - dum->re_timeout = 0; + vhci_hcd->resuming = 0; + vhci_hcd->re_timeout = 0; } - if ((dum->port_status[rhport] & (1 << USB_PORT_FEAT_RESET)) != - 0 && time_after(jiffies, dum->re_timeout)) { - dum->port_status[rhport] |= + if ((vhci_hcd->port_status[rhport] & (1 << USB_PORT_FEAT_RESET)) != + 0 && time_after(jiffies, vhci_hcd->re_timeout)) { + vhci_hcd->port_status[rhport] |= (1 << USB_PORT_FEAT_C_RESET); - dum->port_status[rhport] &= + vhci_hcd->port_status[rhport] &= ~(1 << USB_PORT_FEAT_RESET); - dum->re_timeout = 0; + vhci_hcd->re_timeout = 0; - if (dum->vdev[rhport].ud.status == + if (vhci_hcd->vdev[rhport].ud.status == VDEV_ST_NOTASSIGNED) { usbip_dbg_vhci_rh( " enable rhport %d (status %u)\n", rhport, - dum->vdev[rhport].ud.status); - dum->port_status[rhport] |= + vhci_hcd->vdev[rhport].ud.status); + vhci_hcd->port_status[rhport] |= USB_PORT_STAT_ENABLE; } } - ((__le16 *) buf)[0] = cpu_to_le16(dum->port_status[rhport]); + ((__le16 *) buf)[0] = cpu_to_le16(vhci_hcd->port_status[rhport]); ((__le16 *) buf)[1] = - cpu_to_le16(dum->port_status[rhport] >> 16); + cpu_to_le16(vhci_hcd->port_status[rhport] >> 16); usbip_dbg_vhci_rh(" GetPortStatus bye %x %x\n", ((u16 *)buf)[0], ((u16 *)buf)[1]); @@ -393,21 +395,21 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, usbip_dbg_vhci_rh( " SetPortFeature: USB_PORT_FEAT_RESET\n"); /* if it's already running, disconnect first */ - if (dum->port_status[rhport] & USB_PORT_STAT_ENABLE) { - dum->port_status[rhport] &= + if (vhci_hcd->port_status[rhport] & USB_PORT_STAT_ENABLE) { + vhci_hcd->port_status[rhport] &= ~(USB_PORT_STAT_ENABLE | USB_PORT_STAT_LOW_SPEED | USB_PORT_STAT_HIGH_SPEED); /* FIXME test that code path! */ } /* 50msec reset signaling */ - dum->re_timeout = jiffies + msecs_to_jiffies(50); + vhci_hcd->re_timeout = jiffies + msecs_to_jiffies(50); /* FALLTHROUGH */ default: usbip_dbg_vhci_rh(" SetPortFeature: default %d\n", wValue); - dum->port_status[rhport] |= (1 << wValue); + vhci_hcd->port_status[rhport] |= (1 << wValue); break; } break; @@ -424,12 +426,12 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, /* Only dump valid port status */ if (rhport >= 0) { dump_port_status_diff(prev_port_status[rhport], - dum->port_status[rhport]); + vhci_hcd->port_status[rhport]); } } usbip_dbg_vhci_rh(" bye\n"); - spin_unlock_irqrestore(&dum->lock, flags); + spin_unlock_irqrestore(&vhci->lock, flags); return retval; } @@ -437,14 +439,14 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, static void vhci_tx_urb(struct urb *urb, struct vhci_device *vdev) { struct vhci_priv *priv; - struct vhci_hcd *vhci; + struct vhci_hcd *vhci_hcd; unsigned long flags; if (!vdev) { pr_err("could not get virtual device"); return; } - vhci = vdev_to_vhci_hcd(vdev); + vhci_hcd = vdev_to_vhci_hcd(vdev); priv = kzalloc(sizeof(struct vhci_priv), GFP_ATOMIC); if (!priv) { @@ -454,7 +456,7 @@ static void vhci_tx_urb(struct urb *urb, struct vhci_device *vdev) spin_lock_irqsave(&vdev->priv_lock, flags); - priv->seqnum = atomic_inc_return(&vhci->seqnum); + priv->seqnum = atomic_inc_return(&vhci_hcd->seqnum); if (priv->seqnum == 0xffff) dev_info(&urb->dev->dev, "seqnum max\n"); @@ -472,7 +474,8 @@ static void vhci_tx_urb(struct urb *urb, struct vhci_device *vdev) static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags) { - struct vhci_hcd *vhci = hcd_to_vhci_hcd(hcd); + struct vhci_hcd *vhci_hcd = hcd_to_vhci_hcd(hcd); + struct vhci *vhci = vhci_hcd->vhci; struct device *dev = &urb->dev->dev; u8 portnum = urb->dev->portnum; int ret = 0; @@ -486,7 +489,7 @@ static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, pr_err("invalid port number %d\n", portnum); return -ENODEV; } - vdev = &vhci->vdev[portnum-1]; + vdev = &vhci_hcd->vdev[portnum-1]; /* patch to usb_sg_init() is in 2.5.60 */ BUG_ON(!urb->transfer_buffer && urb->transfer_buffer_length); @@ -639,7 +642,8 @@ no_need_unlink: */ static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) { - struct vhci_hcd *vhci = hcd_to_vhci_hcd(hcd); + struct vhci_hcd *vhci_hcd = hcd_to_vhci_hcd(hcd); + struct vhci *vhci = vhci_hcd->vhci; struct vhci_priv *priv; struct vhci_device *vdev; unsigned long flags; @@ -690,7 +694,7 @@ static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) usb_hcd_unlink_urb_from_ep(hcd, urb); spin_unlock_irqrestore(&vhci->lock, flags); - usb_hcd_giveback_urb(vhci_hcd_to_hcd(vhci), urb, urb->status); + usb_hcd_giveback_urb(hcd, urb, urb->status); spin_lock_irqsave(&vhci->lock, flags); } else { @@ -708,7 +712,7 @@ static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) return -ENOMEM; } - unlink->seqnum = atomic_inc_return(&vhci->seqnum); + unlink->seqnum = atomic_inc_return(&vhci_hcd->seqnum); if (unlink->seqnum == 0xffff) pr_info("seqnum max\n"); @@ -732,8 +736,9 @@ static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) static void vhci_device_unlink_cleanup(struct vhci_device *vdev) { - struct vhci_hcd *vhci = vdev_to_vhci_hcd(vdev); - struct usb_hcd *hcd = vhci_hcd_to_hcd(vhci); + struct vhci_hcd *vhci_hcd = vdev_to_vhci_hcd(vdev); + struct usb_hcd *hcd = vhci_hcd_to_hcd(vhci_hcd); + struct vhci *vhci = vhci_hcd->vhci; struct vhci_unlink *unlink, *tmp; unsigned long flags; @@ -917,29 +922,47 @@ static int hcd_name_to_id(const char *name) return val; } +static int vhci_setup(struct usb_hcd *hcd) +{ + struct vhci *vhci = *((void **)dev_get_platdata(hcd->self.controller)); + hcd->self.sg_tablesize = ~0; + + vhci->vhci_hcd_hs = hcd_to_vhci_hcd(hcd); + vhci->vhci_hcd_hs->vhci = vhci; + hcd->speed = HCD_USB2; + hcd->self.root_hub->speed = USB_SPEED_HIGH; + + return 0; +} + static int vhci_start(struct usb_hcd *hcd) { - struct vhci_hcd *vhci = hcd_to_vhci_hcd(hcd); + struct vhci_hcd *vhci_hcd = hcd_to_vhci_hcd(hcd); int id, rhport; - int err = 0; + int err; usbip_dbg_vhci_hc("enter vhci_start\n"); + spin_lock_init(&vhci_hcd->vhci->lock); + /* initialize private data of usb_hcd */ for (rhport = 0; rhport < VHCI_HC_PORTS; rhport++) { - struct vhci_device *vdev = &vhci->vdev[rhport]; + struct vhci_device *vdev = &vhci_hcd->vdev[rhport]; vhci_device_init(vdev); vdev->rhport = rhport; } - atomic_set(&vhci->seqnum, 0); - spin_lock_init(&vhci->lock); + atomic_set(&vhci_hcd->seqnum, 0); hcd->power_budget = 0; /* no limit */ hcd->uses_new_polling = 1; +#ifdef CONFIG_USB_OTG + hcd->self.otg_port = 1; +#endif + id = hcd_name_to_id(hcd_name(hcd)); if (id < 0) { pr_err("invalid vhci name %s\n", hcd_name(hcd)); @@ -967,7 +990,7 @@ static int vhci_start(struct usb_hcd *hcd) static void vhci_stop(struct usb_hcd *hcd) { - struct vhci_hcd *vhci = hcd_to_vhci_hcd(hcd); + struct vhci_hcd *vhci_hcd = hcd_to_vhci_hcd(hcd); int id, rhport; usbip_dbg_vhci_hc("stop VHCI controller\n"); @@ -981,7 +1004,7 @@ static void vhci_stop(struct usb_hcd *hcd) /* 2. shutdown all the ports of vhci_hcd */ for (rhport = 0; rhport < VHCI_HC_PORTS; rhport++) { - struct vhci_device *vdev = &vhci->vdev[rhport]; + struct vhci_device *vdev = &vhci_hcd->vdev[rhport]; usbip_event_add(&vdev->ud, VDEV_EVENT_REMOVED); usbip_stop_eh(&vdev->ud); @@ -999,7 +1022,7 @@ static int vhci_get_frame_number(struct usb_hcd *hcd) /* FIXME: suspend/resume */ static int vhci_bus_suspend(struct usb_hcd *hcd) { - struct vhci_hcd *vhci = hcd_to_vhci_hcd(hcd); + struct vhci *vhci = *((void **)dev_get_platdata(hcd->self.controller)); unsigned long flags; dev_dbg(&hcd->self.root_hub->dev, "%s\n", __func__); @@ -1013,7 +1036,7 @@ static int vhci_bus_suspend(struct usb_hcd *hcd) static int vhci_bus_resume(struct usb_hcd *hcd) { - struct vhci_hcd *vhci = hcd_to_vhci_hcd(hcd); + struct vhci *vhci = *((void **)dev_get_platdata(hcd->self.controller)); int rc = 0; unsigned long flags; @@ -1042,6 +1065,7 @@ static struct hc_driver vhci_hc_driver = { .flags = HCD_USB2, + .reset = vhci_setup, .start = vhci_start, .stop = vhci_stop, @@ -1058,7 +1082,8 @@ static struct hc_driver vhci_hc_driver = { static int vhci_hcd_probe(struct platform_device *pdev) { - struct usb_hcd *hcd; + struct vhci *vhci; + struct usb_hcd *hcd_hs; int ret; usbip_dbg_vhci_hc("name %s id %d\n", pdev->name, pdev->id); @@ -1067,43 +1092,45 @@ static int vhci_hcd_probe(struct platform_device *pdev) * Allocate and initialize hcd. * Our private data is also allocated automatically. */ - hcd = usb_create_hcd(&vhci_hc_driver, &pdev->dev, dev_name(&pdev->dev)); - if (!hcd) { + hcd_hs = usb_create_hcd(&vhci_hc_driver, &pdev->dev, dev_name(&pdev->dev)); + if (!hcd_hs) { pr_err("create hcd failed\n"); return -ENOMEM; } - hcd->has_tt = 1; + hcd_hs->has_tt = 1; /* * Finish generic HCD structure initialization and register. * Call the driver's reset() and start() routines. */ - ret = usb_add_hcd(hcd, 0, 0); + ret = usb_add_hcd(hcd_hs, 0, 0); if (ret != 0) { - pr_err("usb_add_hcd failed %d\n", ret); - usb_put_hcd(hcd); - return ret; + pr_err("usb_add_hcd hs failed %d\n", ret); + goto put_usb2_hcd; } usbip_dbg_vhci_hc("bye\n"); return 0; + +put_usb2_hcd: + usb_put_hcd(hcd_hs); + vhci->vhci_hcd_hs = NULL; + return ret; } static int vhci_hcd_remove(struct platform_device *pdev) { - struct usb_hcd *hcd; - - hcd = platform_get_drvdata(pdev); - if (!hcd) - return 0; + struct vhci *vhci = *((void **)dev_get_platdata(&pdev->dev)); /* * Disconnects the root hub, * then reverses the effects of usb_add_hcd(), * invoking the HCD's stop() methods. */ - usb_remove_hcd(hcd); - usb_put_hcd(hcd); + usb_remove_hcd(vhci_hcd_to_hcd(vhci->vhci_hcd_hs)); + usb_put_hcd(vhci_hcd_to_hcd(vhci->vhci_hcd_hs)); + + vhci->vhci_hcd_hs = NULL; return 0; } @@ -1114,22 +1141,27 @@ static int vhci_hcd_remove(struct platform_device *pdev) static int vhci_hcd_suspend(struct platform_device *pdev, pm_message_t state) { struct usb_hcd *hcd; - struct vhci_hcd *vhci; - int rhport; + struct vhci *vhci; + int rhport = 0; int connected = 0; int ret = 0; unsigned long flags; + dev_dbg(&pdev->dev, "%s\n", __func__); + hcd = platform_get_drvdata(pdev); if (!hcd) return 0; - vhci = hcd_to_vhci_hcd(hcd); + + vhci = *((void **)dev_get_platdata(hcd->self.controller)); spin_lock_irqsave(&vhci->lock, flags); - for (rhport = 0; rhport < VHCI_HC_PORTS; rhport++) - if (vhci->port_status[rhport] & USB_PORT_STAT_CONNECTION) + for (rhport = 0; rhport < VHCI_HC_PORTS; rhport++) { + if (vhci->vhci_hcd_hs->port_status[rhport] & + USB_PORT_STAT_CONNECTION) connected += 1; + } spin_unlock_irqrestore(&vhci->lock, flags); diff --git a/drivers/usb/usbip/vhci_rx.c b/drivers/usb/usbip/vhci_rx.c index 85abe898d7bd..ef2f2d5ca6b2 100644 --- a/drivers/usb/usbip/vhci_rx.c +++ b/drivers/usb/usbip/vhci_rx.c @@ -70,7 +70,8 @@ struct urb *pickup_urb_and_free_priv(struct vhci_device *vdev, __u32 seqnum) static void vhci_recv_ret_submit(struct vhci_device *vdev, struct usbip_header *pdu) { - struct vhci_hcd *vhci = vdev_to_vhci_hcd(vdev); + struct vhci_hcd *vhci_hcd = vdev_to_vhci_hcd(vdev); + struct vhci *vhci = vhci_hcd->vhci; struct usbip_device *ud = &vdev->ud; struct urb *urb; unsigned long flags; @@ -82,7 +83,7 @@ static void vhci_recv_ret_submit(struct vhci_device *vdev, if (!urb) { pr_err("cannot find a urb of seqnum %u\n", pdu->base.seqnum); pr_info("max seqnum %d\n", - atomic_read(&vhci->seqnum)); + atomic_read(&vhci_hcd->seqnum)); usbip_event_add(ud, VDEV_EVENT_ERROR_TCP); return; } @@ -107,10 +108,10 @@ static void vhci_recv_ret_submit(struct vhci_device *vdev, usbip_dbg_vhci_rx("now giveback urb %p\n", urb); spin_lock_irqsave(&vhci->lock, flags); - usb_hcd_unlink_urb_from_ep(vhci_hcd_to_hcd(vhci), urb); + usb_hcd_unlink_urb_from_ep(vhci_hcd_to_hcd(vhci_hcd), urb); spin_unlock_irqrestore(&vhci->lock, flags); - usb_hcd_giveback_urb(vhci_hcd_to_hcd(vhci), urb, urb->status); + usb_hcd_giveback_urb(vhci_hcd_to_hcd(vhci_hcd), urb, urb->status); usbip_dbg_vhci_rx("Leave\n"); } @@ -143,7 +144,8 @@ static struct vhci_unlink *dequeue_pending_unlink(struct vhci_device *vdev, static void vhci_recv_ret_unlink(struct vhci_device *vdev, struct usbip_header *pdu) { - struct vhci_hcd *vhci = vdev_to_vhci_hcd(vdev); + struct vhci_hcd *vhci_hcd = vdev_to_vhci_hcd(vdev); + struct vhci *vhci = vhci_hcd->vhci; struct vhci_unlink *unlink; struct urb *urb; unsigned long flags; @@ -177,10 +179,10 @@ static void vhci_recv_ret_unlink(struct vhci_device *vdev, pr_info("urb->status %d\n", urb->status); spin_lock_irqsave(&vhci->lock, flags); - usb_hcd_unlink_urb_from_ep(vhci_hcd_to_hcd(vhci), urb); + usb_hcd_unlink_urb_from_ep(vhci_hcd_to_hcd(vhci_hcd), urb); spin_unlock_irqrestore(&vhci->lock, flags); - usb_hcd_giveback_urb(vhci_hcd_to_hcd(vhci), urb, urb->status); + usb_hcd_giveback_urb(vhci_hcd_to_hcd(vhci_hcd), urb, urb->status); } kfree(unlink); diff --git a/drivers/usb/usbip/vhci_sysfs.c b/drivers/usb/usbip/vhci_sysfs.c index 07f0d3789a8c..63e10a4ffeec 100644 --- a/drivers/usb/usbip/vhci_sysfs.c +++ b/drivers/usb/usbip/vhci_sysfs.c @@ -33,9 +33,11 @@ static ssize_t status_show_vhci(int pdev_nr, char *out) { struct platform_device *pdev = vhcis[pdev_nr].pdev; - struct vhci_hcd *vhci; + struct vhci *vhci; + struct usb_hcd *hcd; + struct vhci_hcd *vhci_hcd; char *s = out; - int i = 0; + int i; unsigned long flags; if (!pdev || !out) { @@ -43,7 +45,9 @@ static ssize_t status_show_vhci(int pdev_nr, char *out) return 0; } - vhci = hcd_to_vhci_hcd(platform_get_drvdata(pdev)); + hcd = platform_get_drvdata(pdev); + vhci_hcd = hcd_to_vhci_hcd(hcd); + vhci = vhci_hcd->vhci; spin_lock_irqsave(&vhci->lock, flags); @@ -58,7 +62,7 @@ static ssize_t status_show_vhci(int pdev_nr, char *out) * port number and its peer IP address. */ for (i = 0; i < VHCI_HC_PORTS; i++) { - struct vhci_device *vdev = &vhci->vdev[i]; + struct vhci_device *vdev = &vhci_hcd->vdev[i]; spin_lock(&vdev->ud.lock); out += sprintf(out, "%04u %03u ", @@ -147,9 +151,10 @@ static ssize_t nports_show(struct device *dev, struct device_attribute *attr, static DEVICE_ATTR_RO(nports); /* Sysfs entry to shutdown a virtual connection */ -static int vhci_port_disconnect(struct vhci_hcd *vhci, __u32 rhport) +static int vhci_port_disconnect(struct vhci_hcd *vhci_hcd, __u32 rhport) { - struct vhci_device *vdev = &vhci->vdev[rhport]; + struct vhci_device *vdev = &vhci_hcd->vdev[rhport]; + struct vhci *vhci = vhci_hcd->vhci; unsigned long flags; usbip_dbg_vhci_sysfs("enter\n"); @@ -262,8 +267,9 @@ static ssize_t store_attach(struct device *dev, struct device_attribute *attr, int sockfd = 0; __u32 port = 0, pdev_nr = 0, rhport = 0, devid = 0, speed = 0; struct usb_hcd *hcd; - struct vhci_hcd *vhci; + struct vhci_hcd *vhci_hcd; struct vhci_device *vdev; + struct vhci *vhci; int err; unsigned long flags; @@ -292,8 +298,10 @@ static ssize_t store_attach(struct device *dev, struct device_attribute *attr, dev_err(dev, "port %d is not ready\n", port); return -EAGAIN; } - vhci = hcd_to_vhci_hcd(hcd); - vdev = &vhci->vdev[rhport]; + + vhci_hcd = hcd_to_vhci_hcd(hcd); + vhci = vhci_hcd->vhci; + vdev = &vhci_hcd->vdev[rhport]; /* Extract socket from fd. */ socket = sockfd_lookup(sockfd, &err); -- cgit v1.2.3 From 1c9de5bf428612458427943b724bea51abde520a Mon Sep 17 00:00:00 2001 From: Yuyang Du Date: Thu, 8 Jun 2017 13:04:10 +0800 Subject: usbip: vhci-hcd: Add USB3 SuperSpeed support This patch adds a USB3 HCD to an existing USB2 HCD and provides the support of SuperSpeed, in case the device can only be enumerated with SuperSpeed. The bulk of the added code in usb3_bos_desc and hub_control to support SuperSpeed is borrowed from the commit 1cd8fd2887e162ad ("usb: gadget: dummy_hcd: add SuperSpeed support"). With this patch, each vhci will have VHCI_HC_PORTS HighSpeed ports and VHCI_HC_PORTS SuperSpeed ports. Suggested-by: Krzysztof Opasiak Signed-off-by: Yuyang Du Acked-by: Shuah Khan Signed-off-by: Greg Kroah-Hartman --- drivers/usb/usbip/vhci.h | 7 +- drivers/usb/usbip/vhci_hcd.c | 323 ++++++++++++++++++++++++++++------- drivers/usb/usbip/vhci_sysfs.c | 109 ++++++++---- tools/usb/usbip/libsrc/vhci_driver.c | 23 ++- tools/usb/usbip/libsrc/vhci_driver.h | 8 +- tools/usb/usbip/src/usbip_attach.c | 3 +- 6 files changed, 370 insertions(+), 103 deletions(-) diff --git a/drivers/usb/usbip/vhci.h b/drivers/usb/usbip/vhci.h index 8a979fc00ac1..db28eb531e8c 100644 --- a/drivers/usb/usbip/vhci.h +++ b/drivers/usb/usbip/vhci.h @@ -72,6 +72,11 @@ struct vhci_unlink { unsigned long unlink_seqnum; }; +enum hub_speed { + HUB_SPEED_HIGH = 0, + HUB_SPEED_SUPER, +}; + /* Number of supported ports. Value has an upperbound of USB_MAXCHILDREN */ #ifdef CONFIG_USBIP_VHCI_HC_PORTS #define VHCI_HC_PORTS CONFIG_USBIP_VHCI_HC_PORTS @@ -140,7 +145,7 @@ static inline __u32 port_to_rhport(__u32 port) static inline int port_to_pdev_nr(__u32 port) { - return port / VHCI_HC_PORTS; + return port / (VHCI_HC_PORTS * 2); } static inline struct vhci_hcd *hcd_to_vhci_hcd(struct usb_hcd *hcd) diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c index c96dd44ff162..af42a6010205 100644 --- a/drivers/usb/usbip/vhci_hcd.c +++ b/drivers/usb/usbip/vhci_hcd.c @@ -232,6 +232,40 @@ done: return changed ? retval : 0; } +/* usb 3.0 root hub device descriptor */ +static struct { + struct usb_bos_descriptor bos; + struct usb_ss_cap_descriptor ss_cap; +} __packed usb3_bos_desc = { + + .bos = { + .bLength = USB_DT_BOS_SIZE, + .bDescriptorType = USB_DT_BOS, + .wTotalLength = cpu_to_le16(sizeof(usb3_bos_desc)), + .bNumDeviceCaps = 1, + }, + .ss_cap = { + .bLength = USB_DT_USB_SS_CAP_SIZE, + .bDescriptorType = USB_DT_DEVICE_CAPABILITY, + .bDevCapabilityType = USB_SS_CAP_TYPE, + .wSpeedSupported = cpu_to_le16(USB_5GBPS_OPERATION), + .bFunctionalitySupport = ilog2(USB_5GBPS_OPERATION), + }, +}; + +static inline void +ss_hub_descriptor(struct usb_hub_descriptor *desc) +{ + memset(desc, 0, sizeof *desc); + desc->bDescriptorType = USB_DT_SS_HUB; + desc->bDescLength = 12; + desc->wHubCharacteristics = cpu_to_le16( + HUB_CHAR_INDV_PORT_LPSM | HUB_CHAR_COMMON_OCPM); + desc->bNbrPorts = VHCI_HC_PORTS; + desc->u.ss.bHubHdrDecLat = 0x04; /* Worst case: 0.4 micro sec*/ + desc->u.ss.DeviceRemovable = 0xffff; +} + static inline void hub_descriptor(struct usb_hub_descriptor *desc) { int width; @@ -265,13 +299,15 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, /* * NOTE: - * wIndex shows the port number and begins from 1. + * wIndex (bits 0-7) shows the port number and begins from 1? */ + wIndex = ((__u8)(wIndex & 0x00ff)); usbip_dbg_vhci_rh("typeReq %x wValue %x wIndex %x\n", typeReq, wValue, wIndex); + if (wIndex > VHCI_HC_PORTS) pr_err("invalid port number %d\n", wIndex); - rhport = ((__u8)(wIndex & 0x00ff)) - 1; + rhport = wIndex - 1; vhci_hcd = hcd_to_vhci_hcd(hcd); vhci = vhci_hcd->vhci; @@ -291,34 +327,26 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, case ClearPortFeature: switch (wValue) { case USB_PORT_FEAT_SUSPEND: + if (hcd->speed == HCD_USB3) { + pr_err(" ClearPortFeature: USB_PORT_FEAT_SUSPEND req not " + "supported for USB 3.0 roothub\n"); + goto error; + } + usbip_dbg_vhci_rh( + " ClearPortFeature: USB_PORT_FEAT_SUSPEND\n"); if (vhci_hcd->port_status[rhport] & USB_PORT_STAT_SUSPEND) { /* 20msec signaling */ vhci_hcd->resuming = 1; - vhci_hcd->re_timeout = - jiffies + msecs_to_jiffies(20); + vhci_hcd->re_timeout = jiffies + msecs_to_jiffies(20); } break; case USB_PORT_FEAT_POWER: usbip_dbg_vhci_rh( " ClearPortFeature: USB_PORT_FEAT_POWER\n"); - vhci_hcd->port_status[rhport] = 0; - vhci_hcd->resuming = 0; - break; - case USB_PORT_FEAT_C_RESET: - usbip_dbg_vhci_rh( - " ClearPortFeature: USB_PORT_FEAT_C_RESET\n"); - switch (vhci_hcd->vdev[rhport].speed) { - case USB_SPEED_HIGH: - vhci_hcd->port_status[rhport] |= - USB_PORT_STAT_HIGH_SPEED; - break; - case USB_SPEED_LOW: - vhci_hcd->port_status[rhport] |= - USB_PORT_STAT_LOW_SPEED; - break; - default: - break; - } + if (hcd->speed == HCD_USB3) + vhci_hcd->port_status[rhport] &= ~USB_SS_PORT_STAT_POWER; + else + vhci_hcd->port_status[rhport] &= ~USB_PORT_STAT_POWER; break; default: usbip_dbg_vhci_rh(" ClearPortFeature: default %x\n", @@ -329,7 +357,26 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, break; case GetHubDescriptor: usbip_dbg_vhci_rh(" GetHubDescriptor\n"); - hub_descriptor((struct usb_hub_descriptor *) buf); + if (hcd->speed == HCD_USB3 && + (wLength < USB_DT_SS_HUB_SIZE || + wValue != (USB_DT_SS_HUB << 8))) { + pr_err("Wrong hub descriptor type for USB 3.0 roothub.\n"); + goto error; + } + if (hcd->speed == HCD_USB3) + ss_hub_descriptor((struct usb_hub_descriptor *) buf); + else + hub_descriptor((struct usb_hub_descriptor *) buf); + break; + case DeviceRequest | USB_REQ_GET_DESCRIPTOR: + if (hcd->speed != HCD_USB3) + goto error; + + if ((wValue >> 8) != USB_DT_BOS) + goto error; + + memcpy(buf, &usb3_bos_desc, sizeof(usb3_bos_desc)); + retval = sizeof(usb3_bos_desc); break; case GetHubStatus: usbip_dbg_vhci_rh(" GetHubStatus\n"); @@ -337,7 +384,7 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, break; case GetPortStatus: usbip_dbg_vhci_rh(" GetPortStatus port %x\n", wIndex); - if (wIndex > VHCI_HC_PORTS || wIndex < 1) { + if (wIndex < 1) { pr_err("invalid port number %d\n", wIndex); retval = -EPIPE; } @@ -348,20 +395,16 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, * complete it!! */ if (vhci_hcd->resuming && time_after(jiffies, vhci_hcd->re_timeout)) { - vhci_hcd->port_status[rhport] |= - (1 << USB_PORT_FEAT_C_SUSPEND); - vhci_hcd->port_status[rhport] &= - ~(1 << USB_PORT_FEAT_SUSPEND); + vhci_hcd->port_status[rhport] |= (1 << USB_PORT_FEAT_C_SUSPEND); + vhci_hcd->port_status[rhport] &= ~(1 << USB_PORT_FEAT_SUSPEND); vhci_hcd->resuming = 0; vhci_hcd->re_timeout = 0; } if ((vhci_hcd->port_status[rhport] & (1 << USB_PORT_FEAT_RESET)) != 0 && time_after(jiffies, vhci_hcd->re_timeout)) { - vhci_hcd->port_status[rhport] |= - (1 << USB_PORT_FEAT_C_RESET); - vhci_hcd->port_status[rhport] &= - ~(1 << USB_PORT_FEAT_RESET); + vhci_hcd->port_status[rhport] |= (1 << USB_PORT_FEAT_C_RESET); + vhci_hcd->port_status[rhport] &= ~(1 << USB_PORT_FEAT_RESET); vhci_hcd->re_timeout = 0; if (vhci_hcd->vdev[rhport].ud.status == @@ -373,6 +416,22 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, vhci_hcd->port_status[rhport] |= USB_PORT_STAT_ENABLE; } + + if (hcd->speed < HCD_USB3) { + switch (vhci_hcd->vdev[rhport].speed) { + case USB_SPEED_HIGH: + vhci_hcd->port_status[rhport] |= + USB_PORT_STAT_HIGH_SPEED; + break; + case USB_SPEED_LOW: + vhci_hcd->port_status[rhport] |= + USB_PORT_STAT_LOW_SPEED; + break; + default: + pr_err("vhci_device speed not set\n"); + break; + } + } } ((__le16 *) buf)[0] = cpu_to_le16(vhci_hcd->port_status[rhport]); ((__le16 *) buf)[1] = @@ -387,36 +446,119 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, break; case SetPortFeature: switch (wValue) { + case USB_PORT_FEAT_LINK_STATE: + usbip_dbg_vhci_rh( + " SetPortFeature: USB_PORT_FEAT_LINK_STATE\n"); + if (hcd->speed != HCD_USB3) { + pr_err("USB_PORT_FEAT_LINK_STATE req not " + "supported for USB 2.0 roothub\n"); + goto error; + } + /* + * Since this is dummy we don't have an actual link so + * there is nothing to do for the SET_LINK_STATE cmd + */ + break; + case USB_PORT_FEAT_U1_TIMEOUT: + usbip_dbg_vhci_rh( + " SetPortFeature: USB_PORT_FEAT_U1_TIMEOUT\n"); + case USB_PORT_FEAT_U2_TIMEOUT: + usbip_dbg_vhci_rh( + " SetPortFeature: USB_PORT_FEAT_U2_TIMEOUT\n"); + /* TODO: add suspend/resume support! */ + if (hcd->speed != HCD_USB3) { + pr_err("USB_PORT_FEAT_U1/2_TIMEOUT req not " + "supported for USB 2.0 roothub\n"); + goto error; + } + break; case USB_PORT_FEAT_SUSPEND: usbip_dbg_vhci_rh( " SetPortFeature: USB_PORT_FEAT_SUSPEND\n"); + /* Applicable only for USB2.0 hub */ + if (hcd->speed == HCD_USB3) { + pr_err("USB_PORT_FEAT_SUSPEND req not " + "supported for USB 3.0 roothub\n"); + goto error; + } + + vhci_hcd->port_status[rhport] |= USB_PORT_STAT_SUSPEND; + break; + case USB_PORT_FEAT_POWER: + usbip_dbg_vhci_rh( + " SetPortFeature: USB_PORT_FEAT_POWER\n"); + if (hcd->speed == HCD_USB3) + vhci_hcd->port_status[rhport] |= USB_SS_PORT_STAT_POWER; + else + vhci_hcd->port_status[rhport] |= USB_PORT_STAT_POWER; break; + case USB_PORT_FEAT_BH_PORT_RESET: + usbip_dbg_vhci_rh( + " SetPortFeature: USB_PORT_FEAT_BH_PORT_RESET\n"); + /* Applicable only for USB3.0 hub */ + if (hcd->speed != HCD_USB3) { + pr_err("USB_PORT_FEAT_BH_PORT_RESET req not " + "supported for USB 2.0 roothub\n"); + goto error; + } + /* FALLS THROUGH */ case USB_PORT_FEAT_RESET: usbip_dbg_vhci_rh( " SetPortFeature: USB_PORT_FEAT_RESET\n"); - /* if it's already running, disconnect first */ - if (vhci_hcd->port_status[rhport] & USB_PORT_STAT_ENABLE) { - vhci_hcd->port_status[rhport] &= - ~(USB_PORT_STAT_ENABLE | - USB_PORT_STAT_LOW_SPEED | - USB_PORT_STAT_HIGH_SPEED); - /* FIXME test that code path! */ + /* if it's already enabled, disable */ + if (hcd->speed == HCD_USB3) { + vhci_hcd->port_status[rhport] = 0; + vhci_hcd->port_status[rhport] = + (USB_SS_PORT_STAT_POWER | + USB_PORT_STAT_CONNECTION | + USB_PORT_STAT_RESET); + } else if (vhci_hcd->port_status[rhport] & USB_PORT_STAT_ENABLE) { + vhci_hcd->port_status[rhport] &= ~(USB_PORT_STAT_ENABLE + | USB_PORT_STAT_LOW_SPEED + | USB_PORT_STAT_HIGH_SPEED); } + /* 50msec reset signaling */ vhci_hcd->re_timeout = jiffies + msecs_to_jiffies(50); - /* FALLTHROUGH */ + /* FALLS THROUGH */ default: usbip_dbg_vhci_rh(" SetPortFeature: default %d\n", wValue); - vhci_hcd->port_status[rhport] |= (1 << wValue); - break; + if (hcd->speed == HCD_USB3) { + if ((vhci_hcd->port_status[rhport] & + USB_SS_PORT_STAT_POWER) != 0) { + vhci_hcd->port_status[rhport] |= (1 << wValue); + } + } else + if ((vhci_hcd->port_status[rhport] & + USB_PORT_STAT_POWER) != 0) { + vhci_hcd->port_status[rhport] |= (1 << wValue); + } + } + break; + case GetPortErrorCount: + usbip_dbg_vhci_rh(" GetPortErrorCount\n"); + if (hcd->speed != HCD_USB3) { + pr_err("GetPortErrorCount req not " + "supported for USB 2.0 roothub\n"); + goto error; + } + /* We'll always return 0 since this is a dummy hub */ + *(__le32 *) buf = cpu_to_le32(0); + break; + case SetHubDepth: + usbip_dbg_vhci_rh(" SetHubDepth\n"); + if (hcd->speed != HCD_USB3) { + pr_err("SetHubDepth req not supported for " + "USB 2.0 roothub\n"); + goto error; } break; - default: - pr_err("default: no such request\n"); - + pr_err("default hub control req: %04x v%04x i%04x l%d\n", + typeReq, wValue, wIndex, wLength); +error: /* "protocol stall" on error */ retval = -EPIPE; } @@ -433,6 +575,9 @@ static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, spin_unlock_irqrestore(&vhci->lock, flags); + if ((vhci_hcd->port_status[rhport] & PORT_C_MASK) != 0) + usb_hcd_poll_rh_status(hcd); + return retval; } @@ -471,8 +616,7 @@ static void vhci_tx_urb(struct urb *urb, struct vhci_device *vdev) spin_unlock_irqrestore(&vdev->priv_lock, flags); } -static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, - gfp_t mem_flags) +static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags) { struct vhci_hcd *vhci_hcd = hcd_to_vhci_hcd(hcd); struct vhci *vhci = vhci_hcd->vhci; @@ -850,7 +994,6 @@ static void vhci_shutdown_connection(struct usbip_device *ud) pr_info("disconnect device\n"); } - static void vhci_device_reset(struct usbip_device *ud) { struct vhci_device *vdev = container_of(ud, struct vhci_device, ud); @@ -926,12 +1069,22 @@ static int vhci_setup(struct usb_hcd *hcd) { struct vhci *vhci = *((void **)dev_get_platdata(hcd->self.controller)); hcd->self.sg_tablesize = ~0; - - vhci->vhci_hcd_hs = hcd_to_vhci_hcd(hcd); - vhci->vhci_hcd_hs->vhci = vhci; - hcd->speed = HCD_USB2; - hcd->self.root_hub->speed = USB_SPEED_HIGH; - + if (usb_hcd_is_primary_hcd(hcd)) { + vhci->vhci_hcd_hs = hcd_to_vhci_hcd(hcd); + vhci->vhci_hcd_hs->vhci = vhci; + /* + * Mark the first roothub as being USB 2.0. + * The USB 3.0 roothub will be registered later by + * vhci_hcd_probe() + */ + hcd->speed = HCD_USB2; + hcd->self.root_hub->speed = USB_SPEED_HIGH; + } else { + vhci->vhci_hcd_ss = hcd_to_vhci_hcd(hcd); + vhci->vhci_hcd_ss->vhci = vhci; + hcd->speed = HCD_USB3; + hcd->self.root_hub->speed = USB_SPEED_SUPER; + } return 0; } @@ -943,7 +1096,8 @@ static int vhci_start(struct usb_hcd *hcd) usbip_dbg_vhci_hc("enter vhci_start\n"); - spin_lock_init(&vhci_hcd->vhci->lock); + if (usb_hcd_is_primary_hcd(hcd)) + spin_lock_init(&vhci_hcd->vhci->lock); /* initialize private data of usb_hcd */ @@ -970,7 +1124,7 @@ static int vhci_start(struct usb_hcd *hcd) } /* vhci_hcd is now ready to be controlled through sysfs */ - if (id == 0) { + if (id == 0 && usb_hcd_is_primary_hcd(hcd)) { err = vhci_init_attr_group(); if (err) { pr_err("init attr group\n"); @@ -997,7 +1151,7 @@ static void vhci_stop(struct usb_hcd *hcd) /* 1. remove the userland interface of vhci_hcd */ id = hcd_name_to_id(hcd_name(hcd)); - if (id == 0) { + if (id == 0 && usb_hcd_is_primary_hcd(hcd)) { sysfs_remove_group(&hcd_dev(hcd)->kobj, &vhci_attr_group); vhci_finish_attr_group(); } @@ -1058,12 +1212,30 @@ static int vhci_bus_resume(struct usb_hcd *hcd) #define vhci_bus_resume NULL #endif +/* Change a group of bulk endpoints to support multiple stream IDs */ +static int vhci_alloc_streams(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint **eps, unsigned int num_eps, + unsigned int num_streams, gfp_t mem_flags) +{ + dev_dbg(&hcd->self.root_hub->dev, "vhci_alloc_streams not implemented\n"); + return 0; +} + +/* Reverts a group of bulk endpoints back to not using stream IDs. */ +static int vhci_free_streams(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint **eps, unsigned int num_eps, + gfp_t mem_flags) +{ + dev_dbg(&hcd->self.root_hub->dev, "vhci_free_streams not implemented\n"); + return 0; +} + static struct hc_driver vhci_hc_driver = { .description = driver_name, .product_desc = driver_desc, .hcd_priv_size = sizeof(struct vhci_hcd), - .flags = HCD_USB2, + .flags = HCD_USB3 | HCD_SHARED, .reset = vhci_setup, .start = vhci_start, @@ -1078,12 +1250,16 @@ static struct hc_driver vhci_hc_driver = { .hub_control = vhci_hub_control, .bus_suspend = vhci_bus_suspend, .bus_resume = vhci_bus_resume, + + .alloc_streams = vhci_alloc_streams, + .free_streams = vhci_free_streams, }; static int vhci_hcd_probe(struct platform_device *pdev) { struct vhci *vhci; struct usb_hcd *hcd_hs; + struct usb_hcd *hcd_ss; int ret; usbip_dbg_vhci_hc("name %s id %d\n", pdev->name, pdev->id); @@ -1094,7 +1270,7 @@ static int vhci_hcd_probe(struct platform_device *pdev) */ hcd_hs = usb_create_hcd(&vhci_hc_driver, &pdev->dev, dev_name(&pdev->dev)); if (!hcd_hs) { - pr_err("create hcd failed\n"); + pr_err("create primary hcd failed\n"); return -ENOMEM; } hcd_hs->has_tt = 1; @@ -1109,12 +1285,31 @@ static int vhci_hcd_probe(struct platform_device *pdev) goto put_usb2_hcd; } + hcd_ss = usb_create_shared_hcd(&vhci_hc_driver, &pdev->dev, + dev_name(&pdev->dev), hcd_hs); + if (!hcd_ss) { + ret = -ENOMEM; + pr_err("create shared hcd failed\n"); + goto remove_usb2_hcd; + } + + ret = usb_add_hcd(hcd_ss, 0, 0); + if (ret) { + pr_err("usb_add_hcd ss failed %d\n", ret); + goto put_usb3_hcd; + } + usbip_dbg_vhci_hc("bye\n"); return 0; +put_usb3_hcd: + usb_put_hcd(hcd_ss); +remove_usb2_hcd: + usb_remove_hcd(hcd_hs); put_usb2_hcd: usb_put_hcd(hcd_hs); vhci->vhci_hcd_hs = NULL; + vhci->vhci_hcd_ss = NULL; return ret; } @@ -1127,10 +1322,14 @@ static int vhci_hcd_remove(struct platform_device *pdev) * then reverses the effects of usb_add_hcd(), * invoking the HCD's stop() methods. */ + usb_remove_hcd(vhci_hcd_to_hcd(vhci->vhci_hcd_ss)); + usb_put_hcd(vhci_hcd_to_hcd(vhci->vhci_hcd_ss)); + usb_remove_hcd(vhci_hcd_to_hcd(vhci->vhci_hcd_hs)); usb_put_hcd(vhci_hcd_to_hcd(vhci->vhci_hcd_hs)); vhci->vhci_hcd_hs = NULL; + vhci->vhci_hcd_ss = NULL; return 0; } @@ -1142,7 +1341,7 @@ static int vhci_hcd_suspend(struct platform_device *pdev, pm_message_t state) { struct usb_hcd *hcd; struct vhci *vhci; - int rhport = 0; + int rhport; int connected = 0; int ret = 0; unsigned long flags; @@ -1161,6 +1360,10 @@ static int vhci_hcd_suspend(struct platform_device *pdev, pm_message_t state) if (vhci->vhci_hcd_hs->port_status[rhport] & USB_PORT_STAT_CONNECTION) connected += 1; + + if (vhci->vhci_hcd_ss->port_status[rhport] & + USB_PORT_STAT_CONNECTION) + connected += 1; } spin_unlock_irqrestore(&vhci->lock, flags); diff --git a/drivers/usb/usbip/vhci_sysfs.c b/drivers/usb/usbip/vhci_sysfs.c index 63e10a4ffeec..cac2319df742 100644 --- a/drivers/usb/usbip/vhci_sysfs.c +++ b/drivers/usb/usbip/vhci_sysfs.c @@ -29,6 +29,42 @@ /* TODO: refine locking ?*/ +/* + * output example: + * hub port sta spd dev socket local_busid + * hs 0000 004 000 00000000 c5a7bb80 1-2.3 + * ................................................ + * ss 0008 004 000 00000000 d8cee980 2-3.4 + * ................................................ + * + * IP address can be retrieved from a socket pointer address by looking + * up /proc/net/{tcp,tcp6}. Also, a userland program may remember a + * port number and its peer IP address. + */ +static void port_show_vhci(char **out, int hub, int port, struct vhci_device *vdev) +{ + if (hub == HUB_SPEED_HIGH) + *out += sprintf(*out, "hs %04u %03u ", + port, vdev->ud.status); + else /* hub == HUB_SPEED_SUPER */ + *out += sprintf(*out, "ss %04u %03u ", + port, vdev->ud.status); + + if (vdev->ud.status == VDEV_ST_USED) { + *out += sprintf(*out, "%03u %08x ", + vdev->speed, vdev->devid); + *out += sprintf(*out, "%16p %s", + vdev->ud.tcp_socket, + dev_name(&vdev->udev->dev)); + + } else { + *out += sprintf(*out, "000 00000000 "); + *out += sprintf(*out, "0000000000000000 0-0"); + } + + *out += sprintf(*out, "\n"); +} + /* Sysfs entry to show port status */ static ssize_t status_show_vhci(int pdev_nr, char *out) { @@ -51,37 +87,21 @@ static ssize_t status_show_vhci(int pdev_nr, char *out) spin_lock_irqsave(&vhci->lock, flags); - /* - * output example: - * port sta spd dev socket local_busid - * 0000 004 000 00000000 c5a7bb80 1-2.3 - * 0001 004 000 00000000 d8cee980 2-3.4 - * - * IP address can be retrieved from a socket pointer address by looking - * up /proc/net/{tcp,tcp6}. Also, a userland program may remember a - * port number and its peer IP address. - */ for (i = 0; i < VHCI_HC_PORTS; i++) { - struct vhci_device *vdev = &vhci_hcd->vdev[i]; + struct vhci_device *vdev = &vhci->vhci_hcd_hs->vdev[i]; spin_lock(&vdev->ud.lock); - out += sprintf(out, "%04u %03u ", - (pdev_nr * VHCI_HC_PORTS) + i, - vdev->ud.status); - - if (vdev->ud.status == VDEV_ST_USED) { - out += sprintf(out, "%03u %08x ", - vdev->speed, vdev->devid); - out += sprintf(out, "%16p %s", - vdev->ud.tcp_socket, - dev_name(&vdev->udev->dev)); - - } else { - out += sprintf(out, "000 00000000 "); - out += sprintf(out, "0000000000000000 0-0"); - } + port_show_vhci(&out, HUB_SPEED_HIGH, + pdev_nr * VHCI_HC_PORTS * 2 + i, vdev); + spin_unlock(&vdev->ud.lock); + } - out += sprintf(out, "\n"); + for (i = 0; i < VHCI_HC_PORTS; i++) { + struct vhci_device *vdev = &vhci->vhci_hcd_ss->vdev[i]; + + spin_lock(&vdev->ud.lock); + port_show_vhci(&out, HUB_SPEED_SUPER, + pdev_nr * VHCI_HC_PORTS * 2 + VHCI_HC_PORTS + i, vdev); spin_unlock(&vdev->ud.lock); } @@ -96,8 +116,16 @@ static ssize_t status_show_not_ready(int pdev_nr, char *out) int i = 0; for (i = 0; i < VHCI_HC_PORTS; i++) { - out += sprintf(out, "%04u %03u ", - (pdev_nr * VHCI_HC_PORTS) + i, + out += sprintf(out, "hs %04u %03u ", + (pdev_nr * VHCI_HC_PORTS * 2) + i, + VDEV_ST_NOTASSIGNED); + out += sprintf(out, "000 00000000 0000000000000000 0-0"); + out += sprintf(out, "\n"); + } + + for (i = 0; i < VHCI_HC_PORTS; i++) { + out += sprintf(out, "ss %04u %03u ", + (pdev_nr * VHCI_HC_PORTS * 2) + VHCI_HC_PORTS + i, VDEV_ST_NOTASSIGNED); out += sprintf(out, "000 00000000 0000000000000000 0-0"); out += sprintf(out, "\n"); @@ -129,7 +157,7 @@ static ssize_t status_show(struct device *dev, int pdev_nr; out += sprintf(out, - "port sta spd dev socket local_busid\n"); + "hub port sta spd dev socket local_busid\n"); pdev_nr = status_name_to_id(attr->attr.name); if (pdev_nr < 0) @@ -145,7 +173,10 @@ static ssize_t nports_show(struct device *dev, struct device_attribute *attr, { char *s = out; - out += sprintf(out, "%d\n", VHCI_HC_PORTS * vhci_num_controllers); + /* + * Half the ports are for SPEED_HIGH and half for SPEED_SUPER, thus the * 2. + */ + out += sprintf(out, "%d\n", VHCI_HC_PORTS * vhci_num_controllers * 2); return out - s; } static DEVICE_ATTR_RO(nports); @@ -200,6 +231,7 @@ static ssize_t store_detach(struct device *dev, struct device_attribute *attr, { __u32 port = 0, pdev_nr = 0, rhport = 0; struct usb_hcd *hcd; + struct vhci_hcd *vhci_hcd; int ret; if (kstrtoint(buf, 10, &port) < 0) @@ -217,7 +249,14 @@ static ssize_t store_detach(struct device *dev, struct device_attribute *attr, return -EAGAIN; } - ret = vhci_port_disconnect(hcd_to_vhci_hcd(hcd), rhport); + usbip_dbg_vhci_sysfs("rhport %d\n", rhport); + + if ((port / VHCI_HC_PORTS) % 2) + vhci_hcd = hcd_to_vhci_hcd(hcd)->vhci->vhci_hcd_ss; + else + vhci_hcd = hcd_to_vhci_hcd(hcd)->vhci->vhci_hcd_hs; + + ret = vhci_port_disconnect(vhci_hcd, rhport); if (ret < 0) return -EINVAL; @@ -301,7 +340,11 @@ static ssize_t store_attach(struct device *dev, struct device_attribute *attr, vhci_hcd = hcd_to_vhci_hcd(hcd); vhci = vhci_hcd->vhci; - vdev = &vhci_hcd->vdev[rhport]; + + if (speed == USB_SPEED_SUPER) + vdev = &vhci->vhci_hcd_ss->vdev[rhport]; + else + vdev = &vhci->vhci_hcd_hs->vdev[rhport]; /* Extract socket from fd. */ socket = sockfd_lookup(sockfd, &err); diff --git a/tools/usb/usbip/libsrc/vhci_driver.c b/tools/usb/usbip/libsrc/vhci_driver.c index 3d8189b4f539..9bd2cd71645d 100644 --- a/tools/usb/usbip/libsrc/vhci_driver.c +++ b/tools/usb/usbip/libsrc/vhci_driver.c @@ -52,9 +52,10 @@ static int parse_status(const char *value) unsigned long socket; char lbusid[SYSFS_BUS_ID_SIZE]; struct usbip_imported_device *idev; + char hub[3]; - ret = sscanf(c, "%d %d %d %x %lx %31s\n", - &port, &status, &speed, + ret = sscanf(c, "%2s %d %d %d %x %lx %31s\n", + hub, &port, &status, &speed, &devid, &socket, lbusid); if (ret < 5) { @@ -62,15 +63,19 @@ static int parse_status(const char *value) BUG(); } - dbg("port %d status %d speed %d devid %x", - port, status, speed, devid); + dbg("hub %s port %d status %d speed %d devid %x", + hub, port, status, speed, devid); dbg("socket %lx lbusid %s", socket, lbusid); /* if a device is connected, look at it */ idev = &vhci_driver->idev[port]; - memset(idev, 0, sizeof(*idev)); + if (strncmp("hs", hub, 2) == 0) + idev->hub = HUB_SPEED_HIGH; + else /* strncmp("ss", hub, 2) == 0 */ + idev->hub = HUB_SPEED_SUPER; + idev->port = port; idev->status = status; @@ -320,11 +325,15 @@ err: } -int usbip_vhci_get_free_port(void) +int usbip_vhci_get_free_port(uint32_t speed) { for (int i = 0; i < vhci_driver->nports; i++) { + if (speed == USB_SPEED_SUPER && + vhci_driver->idev[i].hub != HUB_SPEED_SUPER) + continue; + if (vhci_driver->idev[i].status == VDEV_ST_NULL) - return i; + return vhci_driver->idev[i].port; } return -1; diff --git a/tools/usb/usbip/libsrc/vhci_driver.h b/tools/usb/usbip/libsrc/vhci_driver.h index dfe19c1c0245..4898d3bafb10 100644 --- a/tools/usb/usbip/libsrc/vhci_driver.h +++ b/tools/usb/usbip/libsrc/vhci_driver.h @@ -14,7 +14,13 @@ #define USBIP_VHCI_DEVICE_NAME "vhci_hcd.0" #define MAXNPORT 128 +enum hub_speed { + HUB_SPEED_HIGH = 0, + HUB_SPEED_SUPER, +}; + struct usbip_imported_device { + enum hub_speed hub; uint8_t port; uint32_t status; @@ -46,7 +52,7 @@ void usbip_vhci_driver_close(void); int usbip_vhci_refresh_device_list(void); -int usbip_vhci_get_free_port(void); +int usbip_vhci_get_free_port(uint32_t speed); int usbip_vhci_attach_device2(uint8_t port, int sockfd, uint32_t devid, uint32_t speed); diff --git a/tools/usb/usbip/src/usbip_attach.c b/tools/usb/usbip/src/usbip_attach.c index 62a297ff647a..6e89768ffe30 100644 --- a/tools/usb/usbip/src/usbip_attach.c +++ b/tools/usb/usbip/src/usbip_attach.c @@ -94,6 +94,7 @@ static int import_device(int sockfd, struct usbip_usb_device *udev) { int rc; int port; + uint32_t speed = udev->speed; rc = usbip_vhci_driver_open(); if (rc < 0) { @@ -101,7 +102,7 @@ static int import_device(int sockfd, struct usbip_usb_device *udev) return -1; } - port = usbip_vhci_get_free_port(); + port = usbip_vhci_get_free_port(speed); if (port < 0) { err("no free port"); usbip_vhci_driver_close(); -- cgit v1.2.3 From df9032c13d3e065f606ed669a1afe88e8ca4f25a Mon Sep 17 00:00:00 2001 From: Yuyang Du Date: Thu, 8 Jun 2017 13:04:11 +0800 Subject: usbip: Add USB_SPEED_SUPER as valid arg With this patch, USB_SPEED_SUPER is a valid speed when attaching a USB3 SuperSpeed device. Signed-off-by: Yuyang Du Acked-by: Shuah Khan Signed-off-by: Greg Kroah-Hartman --- drivers/usb/usbip/vhci_sysfs.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/usbip/vhci_sysfs.c b/drivers/usb/usbip/vhci_sysfs.c index cac2319df742..3ad68ff4da2d 100644 --- a/drivers/usb/usbip/vhci_sysfs.c +++ b/drivers/usb/usbip/vhci_sysfs.c @@ -277,6 +277,7 @@ static int valid_args(__u32 pdev_nr, __u32 rhport, enum usb_device_speed speed) case USB_SPEED_FULL: case USB_SPEED_HIGH: case USB_SPEED_WIRELESS: + case USB_SPEED_SUPER: break; default: pr_err("Failed attach request for unsupported USB speed: %s\n", -- cgit v1.2.3 From a5c7f019c7d602c508b3eedc470568df4633c642 Mon Sep 17 00:00:00 2001 From: Yuyang Du Date: Thu, 8 Jun 2017 13:04:12 +0800 Subject: usbip: vhci-hcd: Add USB3 port status bits As USB3 has (slightly) different bit meanings in the port status. Add a new status bit array for USB3. Signed-off-by: Yuyang Du Acked-by: Shuah Khan Signed-off-by: Greg Kroah-Hartman --- drivers/usb/usbip/vhci_hcd.c | 56 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c index af42a6010205..64c38603df7b 100644 --- a/drivers/usb/usbip/vhci_hcd.c +++ b/drivers/usb/usbip/vhci_hcd.c @@ -66,7 +66,7 @@ static const char * const bit_desc[] = { "SUSPEND", /*2*/ "OVER_CURRENT", /*3*/ "RESET", /*4*/ - "R5", /*5*/ + "L1", /*5*/ "R6", /*6*/ "R7", /*7*/ "POWER", /*8*/ @@ -82,7 +82,7 @@ static const char * const bit_desc[] = { "C_SUSPEND", /*18*/ "C_OVER_CURRENT", /*19*/ "C_RESET", /*20*/ - "R21", /*21*/ + "C_L1", /*21*/ "R22", /*22*/ "R23", /*23*/ "R24", /*24*/ @@ -95,10 +95,49 @@ static const char * const bit_desc[] = { "R31", /*31*/ }; -static void dump_port_status_diff(u32 prev_status, u32 new_status) +static const char * const bit_desc_ss[] = { + "CONNECTION", /*0*/ + "ENABLE", /*1*/ + "SUSPEND", /*2*/ + "OVER_CURRENT", /*3*/ + "RESET", /*4*/ + "L1", /*5*/ + "R6", /*6*/ + "R7", /*7*/ + "R8", /*8*/ + "POWER", /*9*/ + "HIGHSPEED", /*10*/ + "PORT_TEST", /*11*/ + "INDICATOR", /*12*/ + "R13", /*13*/ + "R14", /*14*/ + "R15", /*15*/ + "C_CONNECTION", /*16*/ + "C_ENABLE", /*17*/ + "C_SUSPEND", /*18*/ + "C_OVER_CURRENT", /*19*/ + "C_RESET", /*20*/ + "C_BH_RESET", /*21*/ + "C_LINK_STATE", /*22*/ + "C_CONFIG_ERROR", /*23*/ + "R24", /*24*/ + "R25", /*25*/ + "R26", /*26*/ + "R27", /*27*/ + "R28", /*28*/ + "R29", /*29*/ + "R30", /*30*/ + "R31", /*31*/ +}; + +static void dump_port_status_diff(u32 prev_status, u32 new_status, bool usb3) { int i = 0; u32 bit = 1; + const char * const *desc = bit_desc; + + if (usb3) + desc = bit_desc_ss; pr_debug("status prev -> new: %08x -> %08x\n", prev_status, new_status); while (bit) { @@ -113,8 +152,12 @@ static void dump_port_status_diff(u32 prev_status, u32 new_status) else change = ' '; - if (prev || new) - pr_debug(" %c%s\n", change, bit_desc[i]); + if (prev || new) { + pr_debug(" %c%s\n", change, desc[i]); + + if (bit == 1) /* USB_PORT_STAT_CONNECTION */ + pr_debug(" %c%s\n", change, "USB_PORT_STAT_SPEED_5GBPS"); + } bit <<= 1; i++; } @@ -568,7 +611,8 @@ error: /* Only dump valid port status */ if (rhport >= 0) { dump_port_status_diff(prev_port_status[rhport], - vhci_hcd->port_status[rhport]); + vhci_hcd->port_status[rhport], + hcd->speed == HCD_USB3); } } usbip_dbg_vhci_rh(" bye\n"); -- cgit v1.2.3 From b891245bff79583b9c69b14b4429362a5d54096e Mon Sep 17 00:00:00 2001 From: Yuyang Du Date: Thu, 8 Jun 2017 13:04:13 +0800 Subject: usbip: vhci-hcd: Clean up the code by adding a new macro Each vhci has 2*VHCI_HC_PORTS ports, in which VHCI_HC_PORTS ports are HighSpeed (or below), and VHCI_HC_PORTS are SuperSpeed. This new macro VHCI_PORTS reflects this configuration. Signed-off-by: Yuyang Du Acked-by: Shuah Khan Signed-off-by: Greg Kroah-Hartman --- drivers/usb/usbip/vhci.h | 5 ++++- drivers/usb/usbip/vhci_sysfs.c | 10 +++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/usb/usbip/vhci.h b/drivers/usb/usbip/vhci.h index db28eb531e8c..5cfb59e98e44 100644 --- a/drivers/usb/usbip/vhci.h +++ b/drivers/usb/usbip/vhci.h @@ -84,6 +84,9 @@ enum hub_speed { #define VHCI_HC_PORTS 8 #endif +/* Each VHCI has 2 hubs (USB2 and USB3), each has VHCI_HC_PORTS ports */ +#define VHCI_PORTS (VHCI_HC_PORTS*2) + #ifdef CONFIG_USBIP_VHCI_NR_HCS #define VHCI_NR_HCS CONFIG_USBIP_VHCI_NR_HCS #else @@ -145,7 +148,7 @@ static inline __u32 port_to_rhport(__u32 port) static inline int port_to_pdev_nr(__u32 port) { - return port / (VHCI_HC_PORTS * 2); + return port / VHCI_PORTS; } static inline struct vhci_hcd *hcd_to_vhci_hcd(struct usb_hcd *hcd) diff --git a/drivers/usb/usbip/vhci_sysfs.c b/drivers/usb/usbip/vhci_sysfs.c index 3ad68ff4da2d..5778b640ba9c 100644 --- a/drivers/usb/usbip/vhci_sysfs.c +++ b/drivers/usb/usbip/vhci_sysfs.c @@ -92,7 +92,7 @@ static ssize_t status_show_vhci(int pdev_nr, char *out) spin_lock(&vdev->ud.lock); port_show_vhci(&out, HUB_SPEED_HIGH, - pdev_nr * VHCI_HC_PORTS * 2 + i, vdev); + pdev_nr * VHCI_PORTS + i, vdev); spin_unlock(&vdev->ud.lock); } @@ -101,7 +101,7 @@ static ssize_t status_show_vhci(int pdev_nr, char *out) spin_lock(&vdev->ud.lock); port_show_vhci(&out, HUB_SPEED_SUPER, - pdev_nr * VHCI_HC_PORTS * 2 + VHCI_HC_PORTS + i, vdev); + pdev_nr * VHCI_PORTS + VHCI_HC_PORTS + i, vdev); spin_unlock(&vdev->ud.lock); } @@ -117,7 +117,7 @@ static ssize_t status_show_not_ready(int pdev_nr, char *out) for (i = 0; i < VHCI_HC_PORTS; i++) { out += sprintf(out, "hs %04u %03u ", - (pdev_nr * VHCI_HC_PORTS * 2) + i, + (pdev_nr * VHCI_PORTS) + i, VDEV_ST_NOTASSIGNED); out += sprintf(out, "000 00000000 0000000000000000 0-0"); out += sprintf(out, "\n"); @@ -125,7 +125,7 @@ static ssize_t status_show_not_ready(int pdev_nr, char *out) for (i = 0; i < VHCI_HC_PORTS; i++) { out += sprintf(out, "ss %04u %03u ", - (pdev_nr * VHCI_HC_PORTS * 2) + VHCI_HC_PORTS + i, + (pdev_nr * VHCI_PORTS) + VHCI_HC_PORTS + i, VDEV_ST_NOTASSIGNED); out += sprintf(out, "000 00000000 0000000000000000 0-0"); out += sprintf(out, "\n"); @@ -176,7 +176,7 @@ static ssize_t nports_show(struct device *dev, struct device_attribute *attr, /* * Half the ports are for SPEED_HIGH and half for SPEED_SUPER, thus the * 2. */ - out += sprintf(out, "%d\n", VHCI_HC_PORTS * vhci_num_controllers * 2); + out += sprintf(out, "%d\n", VHCI_PORTS * vhci_num_controllers); return out - s; } static DEVICE_ATTR_RO(nports); -- cgit v1.2.3 From 0f4c3f9021ebc168e25d3a0ba6d65ac6e890e779 Mon Sep 17 00:00:00 2001 From: Arvind Yadav Date: Fri, 9 Jun 2017 17:33:31 +0530 Subject: usb: mtu3: Handle return value of clk_prepare_enable clk_prepare_enable() can fail here and we must check its return value. Signed-off-by: Arvind Yadav Reviewed-by: Matthias Brugger Signed-off-by: Greg Kroah-Hartman --- drivers/usb/mtu3/mtu3_plat.c | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/drivers/usb/mtu3/mtu3_plat.c b/drivers/usb/mtu3/mtu3_plat.c index 42550c7db3e7..0d3ebb353e08 100644 --- a/drivers/usb/mtu3/mtu3_plat.c +++ b/drivers/usb/mtu3/mtu3_plat.c @@ -458,6 +458,7 @@ static int __maybe_unused mtu3_resume(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct ssusb_mtk *ssusb = platform_get_drvdata(pdev); + int ret; dev_dbg(dev, "%s\n", __func__); @@ -465,12 +466,28 @@ static int __maybe_unused mtu3_resume(struct device *dev) return 0; ssusb_wakeup_disable(ssusb); - clk_prepare_enable(ssusb->sys_clk); - clk_prepare_enable(ssusb->ref_clk); - ssusb_phy_power_on(ssusb); + ret = clk_prepare_enable(ssusb->sys_clk); + if (ret) + goto err_sys_clk; + + ret = clk_prepare_enable(ssusb->ref_clk); + if (ret) + goto err_ref_clk; + + ret = ssusb_phy_power_on(ssusb); + if (ret) + goto err_power_on; + ssusb_host_enable(ssusb); return 0; + +err_power_on: + clk_disable_unprepare(ssusb->ref_clk); +err_ref_clk: + clk_disable_unprepare(ssusb->sys_clk); +err_sys_clk: + return ret; } static const struct dev_pm_ops mtu3_pm_ops = { -- cgit v1.2.3 From d3be974a9c4eac83c1af15c966857d123a8436ba Mon Sep 17 00:00:00 2001 From: Lu Baolu Date: Sat, 3 Jun 2017 17:15:06 +0800 Subject: usb/early: Remove trace_printk() callers in xhci-dbc Trace_printk() was used to log debug messages in xhci-dbc.c where printk() isn't feasible. As there should not be a single caller to trace_printk() in normal kernels, replace them with empty functions. Cc: Vlastimil Babka Cc: Steven Rostedt Cc: Peter Zijlstra Signed-off-by: Lu Baolu Signed-off-by: Greg Kroah-Hartman --- drivers/usb/early/xhci-dbc.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/usb/early/xhci-dbc.c b/drivers/usb/early/xhci-dbc.c index 1268818e2263..12fe70beae69 100644 --- a/drivers/usb/early/xhci-dbc.c +++ b/drivers/usb/early/xhci-dbc.c @@ -32,7 +32,6 @@ static struct xdbc_state xdbc; static bool early_console_keep; -#define XDBC_TRACE #ifdef XDBC_TRACE #define xdbc_trace trace_printk #else -- cgit v1.2.3 From 0bd08fc8db593eb79bd40729279016fd5d7edc09 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Sun, 11 Jun 2017 17:15:16 +0300 Subject: usb: misc: usbsevseg: Use sysfs_match_string() helper Use sysfs_match_string() helper instead of open coded variant. Signed-off-by: Andy Shevchenko Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/usbsevseg.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/drivers/usb/misc/usbsevseg.c b/drivers/usb/misc/usbsevseg.c index a0ba5298160c..388fae6373db 100644 --- a/drivers/usb/misc/usbsevseg.c +++ b/drivers/usb/misc/usbsevseg.c @@ -33,7 +33,7 @@ static const struct usb_device_id id_table[] = { MODULE_DEVICE_TABLE(usb, id_table); /* the different text display modes the device is capable of */ -static char *display_textmodes[] = {"raw", "hex", "ascii", NULL}; +static const char *display_textmodes[] = {"raw", "hex", "ascii"}; struct usb_sevsegdev { struct usb_device *udev; @@ -280,7 +280,7 @@ static ssize_t show_attr_textmode(struct device *dev, buf[0] = 0; - for (i = 0; display_textmodes[i]; i++) { + for (i = 0; i < ARRAY_SIZE(display_textmodes); i++) { if (mydev->textmode == i) { strcat(buf, " ["); strcat(buf, display_textmodes[i]); @@ -304,15 +304,13 @@ static ssize_t set_attr_textmode(struct device *dev, struct usb_sevsegdev *mydev = usb_get_intfdata(intf); int i; - for (i = 0; display_textmodes[i]; i++) { - if (sysfs_streq(display_textmodes[i], buf)) { - mydev->textmode = i; - update_display_visual(mydev, GFP_KERNEL); - return count; - } - } + i = sysfs_match_string(display_textmodes, buf); + if (i < 0) + return i; - return -EINVAL; + mydev->textmode = i; + update_display_visual(mydev, GFP_KERNEL); + return count; } static DEVICE_ATTR(textmode, S_IRUGO | S_IWUSR, show_attr_textmode, set_attr_textmode); -- cgit v1.2.3 From e271b2c909a22a2c13b2d5f77f2ce0091b74540c Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 6 Jun 2017 17:58:58 +0200 Subject: USB: core: fix device node leak Make sure to release any OF device-node reference taken when creating the USB device. Note that we currently do not hold a reference to the root hub device-tree node (i.e. the parent controller node). Fixes: 69bec7259853 ("USB: core: let USB device know device node") Cc: stable # v4.6 Acked-by: Peter Chen Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/usb.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index 28b053cacc90..62e1906bb2f3 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -416,6 +416,8 @@ static void usb_release_dev(struct device *dev) usb_destroy_configuration(udev); usb_release_bos_descriptor(udev); + if (udev->parent) + of_node_put(dev->of_node); usb_put_hcd(hcd); kfree(udev->product); kfree(udev->manufacturer); -- cgit v1.2.3 From 60a93cffcffdc86222c184aafc622b53b8460427 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 6 Jun 2017 17:58:59 +0200 Subject: USB: of: document reference taken by child-lookup helper Document that the child-node lookup helper takes a reference to the device-tree node which needs to be dropped after use. Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/of.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/usb/core/of.c b/drivers/usb/core/of.c index d563cbcf76cf..3863bb1ce8c5 100644 --- a/drivers/usb/core/of.c +++ b/drivers/usb/core/of.c @@ -28,7 +28,8 @@ * * Find the node from device tree according to its port number. * - * Return: On success, a pointer to the device node, %NULL on failure. + * Return: A pointer to the node with incremented refcount if found, or + * %NULL otherwise. */ struct device_node *usb_of_get_child_node(struct device_node *parent, int portnum) -- cgit v1.2.3 From 4e75e1d7dac9d7c95c57eceb451d01f2afcc8626 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 6 Jun 2017 17:59:00 +0200 Subject: driver core: add helper to reuse a device-tree node Add a helper function to be used when reusing the device-tree node of another device. It is fairly common for drivers to reuse the device-tree node of a parent (or other ancestor) device when creating class or bus devices (e.g. gpio chips, i2c adapters, iio chips, spi masters, serdev, phys, usb root hubs). But reusing a device-tree node may cause problems if the new device is later probed as for example driver core would currently attempt to reinitialise an already active associated pinmux configuration. Other potential issues include the platform-bus code unconditionally dropping the device-tree node reference in its device destructor, reinitialisation of other bus-managed resources such as clocks, and the recently added DMA-setup in driver core. Note that for most examples above this is currently not an issue as the devices are never probed, but this is a problem for the USB bus which has recently gained device-tree support. This was discovered and worked-around in a rather ad-hoc fashion by commit dc5878abf49c ("usb: core: move root hub's device node assignment after it is added to bus") by not setting the of_node pointer until after the root-hub device has been registered. Instead we can allow devices to reuse a device-tree node by setting a flag in their struct device that can be used by core, bus and driver code to avoid resources from being over-allocated. Note that the helper also grabs an extra reference to the device node, which specifically balances the unconditional put in the platform-device destructor. Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- drivers/base/core.c | 16 ++++++++++++++++ include/linux/device.h | 4 ++++ 2 files changed, 20 insertions(+) diff --git a/drivers/base/core.c b/drivers/base/core.c index bbecaf9293be..c3064ff09af5 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -2884,3 +2884,19 @@ void set_secondary_fwnode(struct device *dev, struct fwnode_handle *fwnode) else dev->fwnode = fwnode; } + +/** + * device_set_of_node_from_dev - reuse device-tree node of another device + * @dev: device whose device-tree node is being set + * @dev2: device whose device-tree node is being reused + * + * Takes another reference to the new device-tree node after first dropping + * any reference held to the old node. + */ +void device_set_of_node_from_dev(struct device *dev, const struct device *dev2) +{ + of_node_put(dev->of_node); + dev->of_node = of_node_get(dev2->of_node); + dev->of_node_reused = true; +} +EXPORT_SYMBOL_GPL(device_set_of_node_from_dev); diff --git a/include/linux/device.h b/include/linux/device.h index 9ef518af5515..60ab00b13095 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -879,6 +879,8 @@ struct dev_links_info { * * @offline_disabled: If set, the device is permanently online. * @offline: Set after successful invocation of bus type's .offline(). + * @of_node_reused: Set if the device-tree node is shared with an ancestor + * device. * * At the lowest level, every device in a Linux system is represented by an * instance of struct device. The device structure contains the information @@ -966,6 +968,7 @@ struct device { bool offline_disabled:1; bool offline:1; + bool of_node_reused:1; }; static inline struct device *kobj_to_dev(struct kobject *kobj) @@ -1144,6 +1147,7 @@ extern int device_offline(struct device *dev); extern int device_online(struct device *dev); extern void set_primary_fwnode(struct device *dev, struct fwnode_handle *fwnode); extern void set_secondary_fwnode(struct device *dev, struct fwnode_handle *fwnode); +void device_set_of_node_from_dev(struct device *dev, const struct device *dev2); static inline int dev_num_vf(struct device *dev) { -- cgit v1.2.3 From ed032c1bede83ec9bd5f7e803e663cb7f76e5947 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 6 Jun 2017 17:59:01 +0200 Subject: driver core: fix automatic pinctrl management Commit ab78029ecc34 ("drivers/pinctrl: grab default handles from device core") added automatic pin-control management to driver core by looking up and setting any default pinctrl state found in device tree while a device is being probed. This obviously runs into problems as soon as device-tree nodes are reused for child devices which are later also probed as pins would already have been claimed by the ancestor device. For example if a USB host controller claims a pin, its root hub would consequently fail to probe when its device-tree node is set to the node of the controller: pinctrl-single 48002030.pinmux: pin PIN204 already requested by 48064800.ehci; cannot claim for usb1 pinctrl-single 48002030.pinmux: pin-204 (usb1) status -22 pinctrl-single 48002030.pinmux: could not request pin 204 (PIN204) from group usb_dbg_pins on device pinctrl-single usb usb1: Error applying setting, reverse things back usb: probe of usb1 failed with error -22 Fix this by checking the new of_node_reused flag and skipping automatic pinctrl configuration during probe if set. Note that the flag is checked in driver core rather than in pinctrl (e.g. in pinctrl_dt_to_map()) which would specifically have prevented intentional use of a parent's pinctrl properties by a child device (should such a need ever arise). Fixes: ab78029ecc34 ("drivers/pinctrl: grab default handles from device core") Acked-by: Linus Walleij Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- drivers/base/pinctrl.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/base/pinctrl.c b/drivers/base/pinctrl.c index 5917b4b5fb99..eb929dd6ef1e 100644 --- a/drivers/base/pinctrl.c +++ b/drivers/base/pinctrl.c @@ -23,6 +23,9 @@ int pinctrl_bind_pins(struct device *dev) { int ret; + if (dev->of_node_reused) + return 0; + dev->pins = devm_kzalloc(dev, sizeof(*(dev->pins)), GFP_KERNEL); if (!dev->pins) return -ENOMEM; -- cgit v1.2.3 From 2bf698671205bb6f898db348b788d16f6976e086 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 6 Jun 2017 17:59:02 +0200 Subject: USB: of: fix root-hub device-tree node handling In an attempt to work around a pinmux over-allocation issue in driver core, commit dc5878abf49c ("usb: core: move root hub's device node assignment after it is added to bus") moved the device-tree node assignment until after the root hub had been registered. This not only makes the device-tree node unavailable to the usb driver during probe, but also prevents the of_node from being linked to in sysfs and causes a race with user-space for the (recently added) devspec attribute. Use the new device_set_of_node_from_dev() helper to reuse the node of the sysdev device, something which now prevents driver core from trying to reclaim any pinctrl pins during probe. Fixes: dc5878abf49c ("usb: core: move root hub's device node assignment after it is added to bus") Fixes: 51fa91475e43 ("usb/core: Added devspec sysfs entry for devices behind the usb hub") Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hcd.c | 2 -- drivers/usb/core/usb.c | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index e72cbc751619..ab1bb3b538ac 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -1077,7 +1077,6 @@ static void usb_deregister_bus (struct usb_bus *bus) static int register_root_hub(struct usb_hcd *hcd) { struct device *parent_dev = hcd->self.controller; - struct device *sysdev = hcd->self.sysdev; struct usb_device *usb_dev = hcd->self.root_hub; const int devnum = 1; int retval; @@ -1124,7 +1123,6 @@ static int register_root_hub(struct usb_hcd *hcd) /* Did the HC die before the root hub was registered? */ if (HCD_DEAD(hcd)) usb_hc_died (hcd); /* This time clean up */ - usb_dev->dev.of_node = sysdev->of_node; } mutex_unlock(&usb_bus_idr_lock); diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index 62e1906bb2f3..17681d5638ac 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -416,8 +416,7 @@ static void usb_release_dev(struct device *dev) usb_destroy_configuration(udev); usb_release_bos_descriptor(udev); - if (udev->parent) - of_node_put(dev->of_node); + of_node_put(dev->of_node); usb_put_hcd(hcd); kfree(udev->product); kfree(udev->manufacturer); @@ -616,6 +615,7 @@ struct usb_device *usb_alloc_dev(struct usb_device *parent, dev->route = 0; dev->dev.parent = bus->controller; + device_set_of_node_from_dev(&dev->dev, bus->sysdev); dev_set_name(&dev->dev, "usb%d", bus->busnum); root_hub = 1; } else { -- cgit v1.2.3 From c592fafbdbb6b1279b76a54722d1465ca77e5bde Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 6 Jun 2017 17:59:03 +0200 Subject: thermal: max77620: fix device-node reference imbalance The thermal child device reuses the parent MFD-device device-tree node when registering a thermal zone, but did not take a reference to the node. This leads to a reference imbalance, and potential use-after-free, when the node reference is dropped by the platform-bus device destructor (once for the child and later again for the parent). Fix this by dropping any reference already held to a device-tree node and getting a reference to the parent's node which will be balanced on reprobe or on platform-device release, whichever comes first. Note that simply clearing the of_node pointer on probe errors and on driver unbind would not allow the use of device-managed resources as specifically thermal_zone_of_sensor_unregister() claims that a valid device-tree node pointer is needed during deregistration (even if it currently does not seem to use it). Fixes: ec4664b3fd6d ("thermal: max77620: Add thermal driver for reporting junction temp") Cc: stable # 4.9 Cc: Laxman Dewangan Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- drivers/thermal/max77620_thermal.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/thermal/max77620_thermal.c b/drivers/thermal/max77620_thermal.c index e9a1fe342760..71d35f3c9215 100644 --- a/drivers/thermal/max77620_thermal.c +++ b/drivers/thermal/max77620_thermal.c @@ -104,8 +104,6 @@ static int max77620_thermal_probe(struct platform_device *pdev) return -EINVAL; } - pdev->dev.of_node = pdev->dev.parent->of_node; - mtherm->dev = &pdev->dev; mtherm->rmap = dev_get_regmap(pdev->dev.parent, NULL); if (!mtherm->rmap) { @@ -113,6 +111,14 @@ static int max77620_thermal_probe(struct platform_device *pdev) return -ENODEV; } + /* + * Drop any current reference to a device-tree node and get a + * reference to the parent's node which will be balanced on reprobe or + * on platform-device release. + */ + of_node_put(pdev->dev.of_node); + pdev->dev.of_node = of_node_get(pdev->dev.parent->of_node); + mtherm->tz_device = devm_thermal_zone_of_sensor_register(&pdev->dev, 0, mtherm, &max77620_thermal_ops); if (IS_ERR(mtherm->tz_device)) { -- cgit v1.2.3 From ce046e5d9b489554c29ae9277cdc2f86dee76a84 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 6 Jun 2017 17:59:04 +0200 Subject: thermal: max77620: fix pinmux conflict on reprobe Use the new helper for reusing a device-tree node of another device instead of managing the node references explicitly. This also makes sure that the new of_node_reuse flag is set if the device is ever reprobed, something which specifically now avoids driver core from attempting to claim any pinmux resources already claimed by the parent device. Fixes: ec4664b3fd6d ("thermal: max77620: Add thermal driver for reporting junction temp") Cc: Laxman Dewangan Signed-off-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- drivers/thermal/max77620_thermal.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/drivers/thermal/max77620_thermal.c b/drivers/thermal/max77620_thermal.c index 71d35f3c9215..159bbcee8821 100644 --- a/drivers/thermal/max77620_thermal.c +++ b/drivers/thermal/max77620_thermal.c @@ -112,12 +112,10 @@ static int max77620_thermal_probe(struct platform_device *pdev) } /* - * Drop any current reference to a device-tree node and get a - * reference to the parent's node which will be balanced on reprobe or - * on platform-device release. + * The reference taken to the parent's node which will be balanced on + * reprobe or on platform-device release. */ - of_node_put(pdev->dev.of_node); - pdev->dev.of_node = of_node_get(pdev->dev.parent->of_node); + device_set_of_node_from_dev(&pdev->dev, pdev->dev.parent); mtherm->tz_device = devm_thermal_zone_of_sensor_register(&pdev->dev, 0, mtherm, &max77620_thermal_ops); -- cgit v1.2.3 From 76180d716f91f035d9c8639497cf5459b44e1a51 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Fri, 14 Apr 2017 18:35:08 -0700 Subject: usb: gadget: configfs: make qw_sign attribute symmetric Currently qw_sign requires UTF-8 character to set, but returns UTF-16 when read. This isn't obvious when simply using cat since the null characters are not visible, but hexdump unveils the true string: # echo MSFT100 > os_desc/qw_sign # hexdump -C os_desc/qw_sign 00000000 4d 00 53 00 46 00 54 00 31 00 30 00 30 00 |M.S.F.T.1.0.0.| Make qw_sign symmetric by returning an UTF-8 string too. Also follow common convention and add a new line at the end. Reviewed-by: Krzysztof Opasiak Signed-off-by: Stefan Agner Signed-off-by: Felipe Balbi --- drivers/usb/gadget/configfs.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c index cbff3b02840d..863ca4ded1be 100644 --- a/drivers/usb/gadget/configfs.c +++ b/drivers/usb/gadget/configfs.c @@ -787,9 +787,13 @@ static ssize_t os_desc_b_vendor_code_store(struct config_item *item, static ssize_t os_desc_qw_sign_show(struct config_item *item, char *page) { struct gadget_info *gi = os_desc_item_to_gadget_info(item); + int res; - memcpy(page, gi->qw_sign, OS_STRING_QW_SIGN_LEN); - return OS_STRING_QW_SIGN_LEN; + res = utf16s_to_utf8s((wchar_t *) gi->qw_sign, OS_STRING_QW_SIGN_LEN, + UTF16_LITTLE_ENDIAN, page, PAGE_SIZE - 1); + page[res++] = '\n'; + + return res; } static ssize_t os_desc_qw_sign_store(struct config_item *item, const char *page, -- cgit v1.2.3 From e800e8cbdf42c73602b90258f82a2cfe86ec53a7 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Fri, 14 Apr 2017 18:35:09 -0700 Subject: usb: gadget: configfs: use hexadecimal values and new line Other unsigned properties return hexadecimal values, follow this convention when printing b_vendor_code too. Also add newlines to the OS Descriptor support related properties, like other sysfs files use. Reviewed-by: Krzysztof Opasiak Signed-off-by: Stefan Agner Signed-off-by: Felipe Balbi --- drivers/usb/gadget/configfs.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c index 863ca4ded1be..a22a892de7b7 100644 --- a/drivers/usb/gadget/configfs.c +++ b/drivers/usb/gadget/configfs.c @@ -738,7 +738,7 @@ static inline struct gadget_info *os_desc_item_to_gadget_info( static ssize_t os_desc_use_show(struct config_item *item, char *page) { - return sprintf(page, "%d", + return sprintf(page, "%d\n", os_desc_item_to_gadget_info(item)->use_os_desc); } @@ -762,7 +762,7 @@ static ssize_t os_desc_use_store(struct config_item *item, const char *page, static ssize_t os_desc_b_vendor_code_show(struct config_item *item, char *page) { - return sprintf(page, "%d", + return sprintf(page, "0x%02x\n", os_desc_item_to_gadget_info(item)->b_vendor_code); } @@ -904,7 +904,7 @@ static inline struct usb_os_desc_ext_prop static ssize_t ext_prop_type_show(struct config_item *item, char *page) { - return sprintf(page, "%d", to_usb_os_desc_ext_prop(item)->type); + return sprintf(page, "%d\n", to_usb_os_desc_ext_prop(item)->type); } static ssize_t ext_prop_type_store(struct config_item *item, -- cgit v1.2.3 From a676fb62b15b8fdb3a3dce9679863195d50bdd06 Mon Sep 17 00:00:00 2001 From: Raviteja Garimella Date: Wed, 10 May 2017 18:21:17 +0530 Subject: usb: gadget: udc: Rename amd5536udc driver file based on IP This patch renames the amd5536udc.c that has the core driver functionality of Synopsys UDC to snps_udc_core.c The symbols exported here can be used by any UDC driver that uses the same Synopsys IP. Signed-off-by: Raviteja Garimella Signed-off-by: Felipe Balbi --- drivers/usb/gadget/udc/Makefile | 2 +- drivers/usb/gadget/udc/amd5536udc.c | 3219 -------------------------------- drivers/usb/gadget/udc/snps_udc_core.c | 3219 ++++++++++++++++++++++++++++++++ 3 files changed, 3220 insertions(+), 3220 deletions(-) delete mode 100644 drivers/usb/gadget/udc/amd5536udc.c create mode 100644 drivers/usb/gadget/udc/snps_udc_core.c diff --git a/drivers/usb/gadget/udc/Makefile b/drivers/usb/gadget/udc/Makefile index 626e1f1c62da..4f4fd626b9ff 100644 --- a/drivers/usb/gadget/udc/Makefile +++ b/drivers/usb/gadget/udc/Makefile @@ -10,7 +10,7 @@ obj-$(CONFIG_USB_GADGET) += udc-core.o obj-$(CONFIG_USB_DUMMY_HCD) += dummy_hcd.o obj-$(CONFIG_USB_NET2272) += net2272.o obj-$(CONFIG_USB_NET2280) += net2280.o -obj-$(CONFIG_USB_SNP_CORE) += amd5536udc.o +obj-$(CONFIG_USB_SNP_CORE) += snps_udc_core.o obj-$(CONFIG_USB_AMD5536UDC) += amd5536udc_pci.o obj-$(CONFIG_USB_PXA25X) += pxa25x_udc.o obj-$(CONFIG_USB_PXA27X) += pxa27x_udc.o diff --git a/drivers/usb/gadget/udc/amd5536udc.c b/drivers/usb/gadget/udc/amd5536udc.c deleted file mode 100644 index 4ecd2f20ea48..000000000000 --- a/drivers/usb/gadget/udc/amd5536udc.c +++ /dev/null @@ -1,3219 +0,0 @@ -/* - * amd5536.c -- AMD 5536 UDC high/full speed USB device controller - * - * Copyright (C) 2005-2007 AMD (http://www.amd.com) - * Author: Thomas Dahlmann - * - * 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; either version 2 of the License, or - * (at your option) any later version. - */ - -/* - * This file does the core driver implementation for the UDC that is based - * on Synopsys device controller IP (different than HS OTG IP) that is either - * connected through PCI bus or integrated to SoC platforms. - */ - -/* Driver strings */ -#define UDC_MOD_DESCRIPTION "Synopsys USB Device Controller" -#define UDC_DRIVER_VERSION_STRING "01.00.0206" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "amd5536udc.h" - -static void udc_tasklet_disconnect(unsigned long); -static void empty_req_queue(struct udc_ep *); -static void udc_setup_endpoints(struct udc *dev); -static void udc_soft_reset(struct udc *dev); -static struct udc_request *udc_alloc_bna_dummy(struct udc_ep *ep); -static void udc_free_request(struct usb_ep *usbep, struct usb_request *usbreq); - -/* description */ -static const char mod_desc[] = UDC_MOD_DESCRIPTION; -static const char name[] = "udc"; - -/* structure to hold endpoint function pointers */ -static const struct usb_ep_ops udc_ep_ops; - -/* received setup data */ -static union udc_setup_data setup_data; - -/* pointer to device object */ -static struct udc *udc; - -/* irq spin lock for soft reset */ -static DEFINE_SPINLOCK(udc_irq_spinlock); -/* stall spin lock */ -static DEFINE_SPINLOCK(udc_stall_spinlock); - -/* -* slave mode: pending bytes in rx fifo after nyet, -* used if EPIN irq came but no req was available -*/ -static unsigned int udc_rxfifo_pending; - -/* count soft resets after suspend to avoid loop */ -static int soft_reset_occured; -static int soft_reset_after_usbreset_occured; - -/* timer */ -static struct timer_list udc_timer; -static int stop_timer; - -/* set_rde -- Is used to control enabling of RX DMA. Problem is - * that UDC has only one bit (RDE) to enable/disable RX DMA for - * all OUT endpoints. So we have to handle race conditions like - * when OUT data reaches the fifo but no request was queued yet. - * This cannot be solved by letting the RX DMA disabled until a - * request gets queued because there may be other OUT packets - * in the FIFO (important for not blocking control traffic). - * The value of set_rde controls the correspondig timer. - * - * set_rde -1 == not used, means it is alloed to be set to 0 or 1 - * set_rde 0 == do not touch RDE, do no start the RDE timer - * set_rde 1 == timer function will look whether FIFO has data - * set_rde 2 == set by timer function to enable RX DMA on next call - */ -static int set_rde = -1; - -static DECLARE_COMPLETION(on_exit); -static struct timer_list udc_pollstall_timer; -static int stop_pollstall_timer; -static DECLARE_COMPLETION(on_pollstall_exit); - -/* tasklet for usb disconnect */ -static DECLARE_TASKLET(disconnect_tasklet, udc_tasklet_disconnect, - (unsigned long) &udc); - - -/* endpoint names used for print */ -static const char ep0_string[] = "ep0in"; -static const struct { - const char *name; - const struct usb_ep_caps caps; -} ep_info[] = { -#define EP_INFO(_name, _caps) \ - { \ - .name = _name, \ - .caps = _caps, \ - } - - EP_INFO(ep0_string, - USB_EP_CAPS(USB_EP_CAPS_TYPE_CONTROL, USB_EP_CAPS_DIR_IN)), - EP_INFO("ep1in-int", - USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), - EP_INFO("ep2in-bulk", - USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), - EP_INFO("ep3in-bulk", - USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), - EP_INFO("ep4in-bulk", - USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), - EP_INFO("ep5in-bulk", - USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), - EP_INFO("ep6in-bulk", - USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), - EP_INFO("ep7in-bulk", - USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), - EP_INFO("ep8in-bulk", - USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), - EP_INFO("ep9in-bulk", - USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), - EP_INFO("ep10in-bulk", - USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), - EP_INFO("ep11in-bulk", - USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), - EP_INFO("ep12in-bulk", - USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), - EP_INFO("ep13in-bulk", - USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), - EP_INFO("ep14in-bulk", - USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), - EP_INFO("ep15in-bulk", - USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), - EP_INFO("ep0out", - USB_EP_CAPS(USB_EP_CAPS_TYPE_CONTROL, USB_EP_CAPS_DIR_OUT)), - EP_INFO("ep1out-bulk", - USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), - EP_INFO("ep2out-bulk", - USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), - EP_INFO("ep3out-bulk", - USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), - EP_INFO("ep4out-bulk", - USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), - EP_INFO("ep5out-bulk", - USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), - EP_INFO("ep6out-bulk", - USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), - EP_INFO("ep7out-bulk", - USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), - EP_INFO("ep8out-bulk", - USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), - EP_INFO("ep9out-bulk", - USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), - EP_INFO("ep10out-bulk", - USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), - EP_INFO("ep11out-bulk", - USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), - EP_INFO("ep12out-bulk", - USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), - EP_INFO("ep13out-bulk", - USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), - EP_INFO("ep14out-bulk", - USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), - EP_INFO("ep15out-bulk", - USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), - -#undef EP_INFO -}; - -/* buffer fill mode */ -static int use_dma_bufferfill_mode; -/* tx buffer size for high speed */ -static unsigned long hs_tx_buf = UDC_EPIN_BUFF_SIZE; - -/*---------------------------------------------------------------------------*/ -/* Prints UDC device registers and endpoint irq registers */ -static void print_regs(struct udc *dev) -{ - DBG(dev, "------- Device registers -------\n"); - DBG(dev, "dev config = %08x\n", readl(&dev->regs->cfg)); - DBG(dev, "dev control = %08x\n", readl(&dev->regs->ctl)); - DBG(dev, "dev status = %08x\n", readl(&dev->regs->sts)); - DBG(dev, "\n"); - DBG(dev, "dev int's = %08x\n", readl(&dev->regs->irqsts)); - DBG(dev, "dev intmask = %08x\n", readl(&dev->regs->irqmsk)); - DBG(dev, "\n"); - DBG(dev, "dev ep int's = %08x\n", readl(&dev->regs->ep_irqsts)); - DBG(dev, "dev ep intmask = %08x\n", readl(&dev->regs->ep_irqmsk)); - DBG(dev, "\n"); - DBG(dev, "USE DMA = %d\n", use_dma); - if (use_dma && use_dma_ppb && !use_dma_ppb_du) { - DBG(dev, "DMA mode = PPBNDU (packet per buffer " - "WITHOUT desc. update)\n"); - dev_info(&dev->pdev->dev, "DMA mode (%s)\n", "PPBNDU"); - } else if (use_dma && use_dma_ppb && use_dma_ppb_du) { - DBG(dev, "DMA mode = PPBDU (packet per buffer " - "WITH desc. update)\n"); - dev_info(&dev->pdev->dev, "DMA mode (%s)\n", "PPBDU"); - } - if (use_dma && use_dma_bufferfill_mode) { - DBG(dev, "DMA mode = BF (buffer fill mode)\n"); - dev_info(&dev->pdev->dev, "DMA mode (%s)\n", "BF"); - } - if (!use_dma) - dev_info(&dev->pdev->dev, "FIFO mode\n"); - DBG(dev, "-------------------------------------------------------\n"); -} - -/* Masks unused interrupts */ -int udc_mask_unused_interrupts(struct udc *dev) -{ - u32 tmp; - - /* mask all dev interrupts */ - tmp = AMD_BIT(UDC_DEVINT_SVC) | - AMD_BIT(UDC_DEVINT_ENUM) | - AMD_BIT(UDC_DEVINT_US) | - AMD_BIT(UDC_DEVINT_UR) | - AMD_BIT(UDC_DEVINT_ES) | - AMD_BIT(UDC_DEVINT_SI) | - AMD_BIT(UDC_DEVINT_SOF)| - AMD_BIT(UDC_DEVINT_SC); - writel(tmp, &dev->regs->irqmsk); - - /* mask all ep interrupts */ - writel(UDC_EPINT_MSK_DISABLE_ALL, &dev->regs->ep_irqmsk); - - return 0; -} -EXPORT_SYMBOL_GPL(udc_mask_unused_interrupts); - -/* Enables endpoint 0 interrupts */ -static int udc_enable_ep0_interrupts(struct udc *dev) -{ - u32 tmp; - - DBG(dev, "udc_enable_ep0_interrupts()\n"); - - /* read irq mask */ - tmp = readl(&dev->regs->ep_irqmsk); - /* enable ep0 irq's */ - tmp &= AMD_UNMASK_BIT(UDC_EPINT_IN_EP0) - & AMD_UNMASK_BIT(UDC_EPINT_OUT_EP0); - writel(tmp, &dev->regs->ep_irqmsk); - - return 0; -} - -/* Enables device interrupts for SET_INTF and SET_CONFIG */ -int udc_enable_dev_setup_interrupts(struct udc *dev) -{ - u32 tmp; - - DBG(dev, "enable device interrupts for setup data\n"); - - /* read irq mask */ - tmp = readl(&dev->regs->irqmsk); - - /* enable SET_INTERFACE, SET_CONFIG and other needed irq's */ - tmp &= AMD_UNMASK_BIT(UDC_DEVINT_SI) - & AMD_UNMASK_BIT(UDC_DEVINT_SC) - & AMD_UNMASK_BIT(UDC_DEVINT_UR) - & AMD_UNMASK_BIT(UDC_DEVINT_SVC) - & AMD_UNMASK_BIT(UDC_DEVINT_ENUM); - writel(tmp, &dev->regs->irqmsk); - - return 0; -} -EXPORT_SYMBOL_GPL(udc_enable_dev_setup_interrupts); - -/* Calculates fifo start of endpoint based on preceding endpoints */ -static int udc_set_txfifo_addr(struct udc_ep *ep) -{ - struct udc *dev; - u32 tmp; - int i; - - if (!ep || !(ep->in)) - return -EINVAL; - - dev = ep->dev; - ep->txfifo = dev->txfifo; - - /* traverse ep's */ - for (i = 0; i < ep->num; i++) { - if (dev->ep[i].regs) { - /* read fifo size */ - tmp = readl(&dev->ep[i].regs->bufin_framenum); - tmp = AMD_GETBITS(tmp, UDC_EPIN_BUFF_SIZE); - ep->txfifo += tmp; - } - } - return 0; -} - -/* CNAK pending field: bit0 = ep0in, bit16 = ep0out */ -static u32 cnak_pending; - -static void UDC_QUEUE_CNAK(struct udc_ep *ep, unsigned num) -{ - if (readl(&ep->regs->ctl) & AMD_BIT(UDC_EPCTL_NAK)) { - DBG(ep->dev, "NAK could not be cleared for ep%d\n", num); - cnak_pending |= 1 << (num); - ep->naking = 1; - } else - cnak_pending = cnak_pending & (~(1 << (num))); -} - - -/* Enables endpoint, is called by gadget driver */ -static int -udc_ep_enable(struct usb_ep *usbep, const struct usb_endpoint_descriptor *desc) -{ - struct udc_ep *ep; - struct udc *dev; - u32 tmp; - unsigned long iflags; - u8 udc_csr_epix; - unsigned maxpacket; - - if (!usbep - || usbep->name == ep0_string - || !desc - || desc->bDescriptorType != USB_DT_ENDPOINT) - return -EINVAL; - - ep = container_of(usbep, struct udc_ep, ep); - dev = ep->dev; - - DBG(dev, "udc_ep_enable() ep %d\n", ep->num); - - if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) - return -ESHUTDOWN; - - spin_lock_irqsave(&dev->lock, iflags); - ep->ep.desc = desc; - - ep->halted = 0; - - /* set traffic type */ - tmp = readl(&dev->ep[ep->num].regs->ctl); - tmp = AMD_ADDBITS(tmp, desc->bmAttributes, UDC_EPCTL_ET); - writel(tmp, &dev->ep[ep->num].regs->ctl); - - /* set max packet size */ - maxpacket = usb_endpoint_maxp(desc); - tmp = readl(&dev->ep[ep->num].regs->bufout_maxpkt); - tmp = AMD_ADDBITS(tmp, maxpacket, UDC_EP_MAX_PKT_SIZE); - ep->ep.maxpacket = maxpacket; - writel(tmp, &dev->ep[ep->num].regs->bufout_maxpkt); - - /* IN ep */ - if (ep->in) { - - /* ep ix in UDC CSR register space */ - udc_csr_epix = ep->num; - - /* set buffer size (tx fifo entries) */ - tmp = readl(&dev->ep[ep->num].regs->bufin_framenum); - /* double buffering: fifo size = 2 x max packet size */ - tmp = AMD_ADDBITS( - tmp, - maxpacket * UDC_EPIN_BUFF_SIZE_MULT - / UDC_DWORD_BYTES, - UDC_EPIN_BUFF_SIZE); - writel(tmp, &dev->ep[ep->num].regs->bufin_framenum); - - /* calc. tx fifo base addr */ - udc_set_txfifo_addr(ep); - - /* flush fifo */ - tmp = readl(&ep->regs->ctl); - tmp |= AMD_BIT(UDC_EPCTL_F); - writel(tmp, &ep->regs->ctl); - - /* OUT ep */ - } else { - /* ep ix in UDC CSR register space */ - udc_csr_epix = ep->num - UDC_CSR_EP_OUT_IX_OFS; - - /* set max packet size UDC CSR */ - tmp = readl(&dev->csr->ne[ep->num - UDC_CSR_EP_OUT_IX_OFS]); - tmp = AMD_ADDBITS(tmp, maxpacket, - UDC_CSR_NE_MAX_PKT); - writel(tmp, &dev->csr->ne[ep->num - UDC_CSR_EP_OUT_IX_OFS]); - - if (use_dma && !ep->in) { - /* alloc and init BNA dummy request */ - ep->bna_dummy_req = udc_alloc_bna_dummy(ep); - ep->bna_occurred = 0; - } - - if (ep->num != UDC_EP0OUT_IX) - dev->data_ep_enabled = 1; - } - - /* set ep values */ - tmp = readl(&dev->csr->ne[udc_csr_epix]); - /* max packet */ - tmp = AMD_ADDBITS(tmp, maxpacket, UDC_CSR_NE_MAX_PKT); - /* ep number */ - tmp = AMD_ADDBITS(tmp, desc->bEndpointAddress, UDC_CSR_NE_NUM); - /* ep direction */ - tmp = AMD_ADDBITS(tmp, ep->in, UDC_CSR_NE_DIR); - /* ep type */ - tmp = AMD_ADDBITS(tmp, desc->bmAttributes, UDC_CSR_NE_TYPE); - /* ep config */ - tmp = AMD_ADDBITS(tmp, ep->dev->cur_config, UDC_CSR_NE_CFG); - /* ep interface */ - tmp = AMD_ADDBITS(tmp, ep->dev->cur_intf, UDC_CSR_NE_INTF); - /* ep alt */ - tmp = AMD_ADDBITS(tmp, ep->dev->cur_alt, UDC_CSR_NE_ALT); - /* write reg */ - writel(tmp, &dev->csr->ne[udc_csr_epix]); - - /* enable ep irq */ - tmp = readl(&dev->regs->ep_irqmsk); - tmp &= AMD_UNMASK_BIT(ep->num); - writel(tmp, &dev->regs->ep_irqmsk); - - /* - * clear NAK by writing CNAK - * avoid BNA for OUT DMA, don't clear NAK until DMA desc. written - */ - if (!use_dma || ep->in) { - tmp = readl(&ep->regs->ctl); - tmp |= AMD_BIT(UDC_EPCTL_CNAK); - writel(tmp, &ep->regs->ctl); - ep->naking = 0; - UDC_QUEUE_CNAK(ep, ep->num); - } - tmp = desc->bEndpointAddress; - DBG(dev, "%s enabled\n", usbep->name); - - spin_unlock_irqrestore(&dev->lock, iflags); - return 0; -} - -/* Resets endpoint */ -static void ep_init(struct udc_regs __iomem *regs, struct udc_ep *ep) -{ - u32 tmp; - - VDBG(ep->dev, "ep-%d reset\n", ep->num); - ep->ep.desc = NULL; - ep->ep.ops = &udc_ep_ops; - INIT_LIST_HEAD(&ep->queue); - - usb_ep_set_maxpacket_limit(&ep->ep,(u16) ~0); - /* set NAK */ - tmp = readl(&ep->regs->ctl); - tmp |= AMD_BIT(UDC_EPCTL_SNAK); - writel(tmp, &ep->regs->ctl); - ep->naking = 1; - - /* disable interrupt */ - tmp = readl(®s->ep_irqmsk); - tmp |= AMD_BIT(ep->num); - writel(tmp, ®s->ep_irqmsk); - - if (ep->in) { - /* unset P and IN bit of potential former DMA */ - tmp = readl(&ep->regs->ctl); - tmp &= AMD_UNMASK_BIT(UDC_EPCTL_P); - writel(tmp, &ep->regs->ctl); - - tmp = readl(&ep->regs->sts); - tmp |= AMD_BIT(UDC_EPSTS_IN); - writel(tmp, &ep->regs->sts); - - /* flush the fifo */ - tmp = readl(&ep->regs->ctl); - tmp |= AMD_BIT(UDC_EPCTL_F); - writel(tmp, &ep->regs->ctl); - - } - /* reset desc pointer */ - writel(0, &ep->regs->desptr); -} - -/* Disables endpoint, is called by gadget driver */ -static int udc_ep_disable(struct usb_ep *usbep) -{ - struct udc_ep *ep = NULL; - unsigned long iflags; - - if (!usbep) - return -EINVAL; - - ep = container_of(usbep, struct udc_ep, ep); - if (usbep->name == ep0_string || !ep->ep.desc) - return -EINVAL; - - DBG(ep->dev, "Disable ep-%d\n", ep->num); - - spin_lock_irqsave(&ep->dev->lock, iflags); - udc_free_request(&ep->ep, &ep->bna_dummy_req->req); - empty_req_queue(ep); - ep_init(ep->dev->regs, ep); - spin_unlock_irqrestore(&ep->dev->lock, iflags); - - return 0; -} - -/* Allocates request packet, called by gadget driver */ -static struct usb_request * -udc_alloc_request(struct usb_ep *usbep, gfp_t gfp) -{ - struct udc_request *req; - struct udc_data_dma *dma_desc; - struct udc_ep *ep; - - if (!usbep) - return NULL; - - ep = container_of(usbep, struct udc_ep, ep); - - VDBG(ep->dev, "udc_alloc_req(): ep%d\n", ep->num); - req = kzalloc(sizeof(struct udc_request), gfp); - if (!req) - return NULL; - - req->req.dma = DMA_DONT_USE; - INIT_LIST_HEAD(&req->queue); - - if (ep->dma) { - /* ep0 in requests are allocated from data pool here */ - dma_desc = dma_pool_alloc(ep->dev->data_requests, gfp, - &req->td_phys); - if (!dma_desc) { - kfree(req); - return NULL; - } - - VDBG(ep->dev, "udc_alloc_req: req = %p dma_desc = %p, " - "td_phys = %lx\n", - req, dma_desc, - (unsigned long)req->td_phys); - /* prevent from using desc. - set HOST BUSY */ - dma_desc->status = AMD_ADDBITS(dma_desc->status, - UDC_DMA_STP_STS_BS_HOST_BUSY, - UDC_DMA_STP_STS_BS); - dma_desc->bufptr = cpu_to_le32(DMA_DONT_USE); - req->td_data = dma_desc; - req->td_data_last = NULL; - req->chain_len = 1; - } - - return &req->req; -} - -/* frees pci pool descriptors of a DMA chain */ -static void udc_free_dma_chain(struct udc *dev, struct udc_request *req) -{ - struct udc_data_dma *td = req->td_data; - unsigned int i; - - dma_addr_t addr_next = 0x00; - dma_addr_t addr = (dma_addr_t)td->next; - - DBG(dev, "free chain req = %p\n", req); - - /* do not free first desc., will be done by free for request */ - for (i = 1; i < req->chain_len; i++) { - td = phys_to_virt(addr); - addr_next = (dma_addr_t)td->next; - dma_pool_free(dev->data_requests, td, addr); - addr = addr_next; - } -} - -/* Frees request packet, called by gadget driver */ -static void -udc_free_request(struct usb_ep *usbep, struct usb_request *usbreq) -{ - struct udc_ep *ep; - struct udc_request *req; - - if (!usbep || !usbreq) - return; - - ep = container_of(usbep, struct udc_ep, ep); - req = container_of(usbreq, struct udc_request, req); - VDBG(ep->dev, "free_req req=%p\n", req); - BUG_ON(!list_empty(&req->queue)); - if (req->td_data) { - VDBG(ep->dev, "req->td_data=%p\n", req->td_data); - - /* free dma chain if created */ - if (req->chain_len > 1) - udc_free_dma_chain(ep->dev, req); - - dma_pool_free(ep->dev->data_requests, req->td_data, - req->td_phys); - } - kfree(req); -} - -/* Init BNA dummy descriptor for HOST BUSY and pointing to itself */ -static void udc_init_bna_dummy(struct udc_request *req) -{ - if (req) { - /* set last bit */ - req->td_data->status |= AMD_BIT(UDC_DMA_IN_STS_L); - /* set next pointer to itself */ - req->td_data->next = req->td_phys; - /* set HOST BUSY */ - req->td_data->status - = AMD_ADDBITS(req->td_data->status, - UDC_DMA_STP_STS_BS_DMA_DONE, - UDC_DMA_STP_STS_BS); -#ifdef UDC_VERBOSE - pr_debug("bna desc = %p, sts = %08x\n", - req->td_data, req->td_data->status); -#endif - } -} - -/* Allocate BNA dummy descriptor */ -static struct udc_request *udc_alloc_bna_dummy(struct udc_ep *ep) -{ - struct udc_request *req = NULL; - struct usb_request *_req = NULL; - - /* alloc the dummy request */ - _req = udc_alloc_request(&ep->ep, GFP_ATOMIC); - if (_req) { - req = container_of(_req, struct udc_request, req); - ep->bna_dummy_req = req; - udc_init_bna_dummy(req); - } - return req; -} - -/* Write data to TX fifo for IN packets */ -static void -udc_txfifo_write(struct udc_ep *ep, struct usb_request *req) -{ - u8 *req_buf; - u32 *buf; - int i, j; - unsigned bytes = 0; - unsigned remaining = 0; - - if (!req || !ep) - return; - - req_buf = req->buf + req->actual; - prefetch(req_buf); - remaining = req->length - req->actual; - - buf = (u32 *) req_buf; - - bytes = ep->ep.maxpacket; - if (bytes > remaining) - bytes = remaining; - - /* dwords first */ - for (i = 0; i < bytes / UDC_DWORD_BYTES; i++) - writel(*(buf + i), ep->txfifo); - - /* remaining bytes must be written by byte access */ - for (j = 0; j < bytes % UDC_DWORD_BYTES; j++) { - writeb((u8)(*(buf + i) >> (j << UDC_BITS_PER_BYTE_SHIFT)), - ep->txfifo); - } - - /* dummy write confirm */ - writel(0, &ep->regs->confirm); -} - -/* Read dwords from RX fifo for OUT transfers */ -static int udc_rxfifo_read_dwords(struct udc *dev, u32 *buf, int dwords) -{ - int i; - - VDBG(dev, "udc_read_dwords(): %d dwords\n", dwords); - - for (i = 0; i < dwords; i++) - *(buf + i) = readl(dev->rxfifo); - return 0; -} - -/* Read bytes from RX fifo for OUT transfers */ -static int udc_rxfifo_read_bytes(struct udc *dev, u8 *buf, int bytes) -{ - int i, j; - u32 tmp; - - VDBG(dev, "udc_read_bytes(): %d bytes\n", bytes); - - /* dwords first */ - for (i = 0; i < bytes / UDC_DWORD_BYTES; i++) - *((u32 *)(buf + (i<<2))) = readl(dev->rxfifo); - - /* remaining bytes must be read by byte access */ - if (bytes % UDC_DWORD_BYTES) { - tmp = readl(dev->rxfifo); - for (j = 0; j < bytes % UDC_DWORD_BYTES; j++) { - *(buf + (i<<2) + j) = (u8)(tmp & UDC_BYTE_MASK); - tmp = tmp >> UDC_BITS_PER_BYTE; - } - } - - return 0; -} - -/* Read data from RX fifo for OUT transfers */ -static int -udc_rxfifo_read(struct udc_ep *ep, struct udc_request *req) -{ - u8 *buf; - unsigned buf_space; - unsigned bytes = 0; - unsigned finished = 0; - - /* received number bytes */ - bytes = readl(&ep->regs->sts); - bytes = AMD_GETBITS(bytes, UDC_EPSTS_RX_PKT_SIZE); - - buf_space = req->req.length - req->req.actual; - buf = req->req.buf + req->req.actual; - if (bytes > buf_space) { - if ((buf_space % ep->ep.maxpacket) != 0) { - DBG(ep->dev, - "%s: rx %d bytes, rx-buf space = %d bytesn\n", - ep->ep.name, bytes, buf_space); - req->req.status = -EOVERFLOW; - } - bytes = buf_space; - } - req->req.actual += bytes; - - /* last packet ? */ - if (((bytes % ep->ep.maxpacket) != 0) || (!bytes) - || ((req->req.actual == req->req.length) && !req->req.zero)) - finished = 1; - - /* read rx fifo bytes */ - VDBG(ep->dev, "ep %s: rxfifo read %d bytes\n", ep->ep.name, bytes); - udc_rxfifo_read_bytes(ep->dev, buf, bytes); - - return finished; -} - -/* Creates or re-inits a DMA chain */ -static int udc_create_dma_chain( - struct udc_ep *ep, - struct udc_request *req, - unsigned long buf_len, gfp_t gfp_flags -) -{ - unsigned long bytes = req->req.length; - unsigned int i; - dma_addr_t dma_addr; - struct udc_data_dma *td = NULL; - struct udc_data_dma *last = NULL; - unsigned long txbytes; - unsigned create_new_chain = 0; - unsigned len; - - VDBG(ep->dev, "udc_create_dma_chain: bytes=%ld buf_len=%ld\n", - bytes, buf_len); - dma_addr = DMA_DONT_USE; - - /* unset L bit in first desc for OUT */ - if (!ep->in) - req->td_data->status &= AMD_CLEAR_BIT(UDC_DMA_IN_STS_L); - - /* alloc only new desc's if not already available */ - len = req->req.length / ep->ep.maxpacket; - if (req->req.length % ep->ep.maxpacket) - len++; - - if (len > req->chain_len) { - /* shorter chain already allocated before */ - if (req->chain_len > 1) - udc_free_dma_chain(ep->dev, req); - req->chain_len = len; - create_new_chain = 1; - } - - td = req->td_data; - /* gen. required number of descriptors and buffers */ - for (i = buf_len; i < bytes; i += buf_len) { - /* create or determine next desc. */ - if (create_new_chain) { - td = dma_pool_alloc(ep->dev->data_requests, - gfp_flags, &dma_addr); - if (!td) - return -ENOMEM; - - td->status = 0; - } else if (i == buf_len) { - /* first td */ - td = (struct udc_data_dma *)phys_to_virt( - req->td_data->next); - td->status = 0; - } else { - td = (struct udc_data_dma *)phys_to_virt(last->next); - td->status = 0; - } - - if (td) - td->bufptr = req->req.dma + i; /* assign buffer */ - else - break; - - /* short packet ? */ - if ((bytes - i) >= buf_len) { - txbytes = buf_len; - } else { - /* short packet */ - txbytes = bytes - i; - } - - /* link td and assign tx bytes */ - if (i == buf_len) { - if (create_new_chain) - req->td_data->next = dma_addr; - /* - * else - * req->td_data->next = virt_to_phys(td); - */ - /* write tx bytes */ - if (ep->in) { - /* first desc */ - req->td_data->status = - AMD_ADDBITS(req->td_data->status, - ep->ep.maxpacket, - UDC_DMA_IN_STS_TXBYTES); - /* second desc */ - td->status = AMD_ADDBITS(td->status, - txbytes, - UDC_DMA_IN_STS_TXBYTES); - } - } else { - if (create_new_chain) - last->next = dma_addr; - /* - * else - * last->next = virt_to_phys(td); - */ - if (ep->in) { - /* write tx bytes */ - td->status = AMD_ADDBITS(td->status, - txbytes, - UDC_DMA_IN_STS_TXBYTES); - } - } - last = td; - } - /* set last bit */ - if (td) { - td->status |= AMD_BIT(UDC_DMA_IN_STS_L); - /* last desc. points to itself */ - req->td_data_last = td; - } - - return 0; -} - -/* create/re-init a DMA descriptor or a DMA descriptor chain */ -static int prep_dma(struct udc_ep *ep, struct udc_request *req, gfp_t gfp) -{ - int retval = 0; - u32 tmp; - - VDBG(ep->dev, "prep_dma\n"); - VDBG(ep->dev, "prep_dma ep%d req->td_data=%p\n", - ep->num, req->td_data); - - /* set buffer pointer */ - req->td_data->bufptr = req->req.dma; - - /* set last bit */ - req->td_data->status |= AMD_BIT(UDC_DMA_IN_STS_L); - - /* build/re-init dma chain if maxpkt scatter mode, not for EP0 */ - if (use_dma_ppb) { - - retval = udc_create_dma_chain(ep, req, ep->ep.maxpacket, gfp); - if (retval != 0) { - if (retval == -ENOMEM) - DBG(ep->dev, "Out of DMA memory\n"); - return retval; - } - if (ep->in) { - if (req->req.length == ep->ep.maxpacket) { - /* write tx bytes */ - req->td_data->status = - AMD_ADDBITS(req->td_data->status, - ep->ep.maxpacket, - UDC_DMA_IN_STS_TXBYTES); - - } - } - - } - - if (ep->in) { - VDBG(ep->dev, "IN: use_dma_ppb=%d req->req.len=%d " - "maxpacket=%d ep%d\n", - use_dma_ppb, req->req.length, - ep->ep.maxpacket, ep->num); - /* - * if bytes < max packet then tx bytes must - * be written in packet per buffer mode - */ - if (!use_dma_ppb || req->req.length < ep->ep.maxpacket - || ep->num == UDC_EP0OUT_IX - || ep->num == UDC_EP0IN_IX) { - /* write tx bytes */ - req->td_data->status = - AMD_ADDBITS(req->td_data->status, - req->req.length, - UDC_DMA_IN_STS_TXBYTES); - /* reset frame num */ - req->td_data->status = - AMD_ADDBITS(req->td_data->status, - 0, - UDC_DMA_IN_STS_FRAMENUM); - } - /* set HOST BUSY */ - req->td_data->status = - AMD_ADDBITS(req->td_data->status, - UDC_DMA_STP_STS_BS_HOST_BUSY, - UDC_DMA_STP_STS_BS); - } else { - VDBG(ep->dev, "OUT set host ready\n"); - /* set HOST READY */ - req->td_data->status = - AMD_ADDBITS(req->td_data->status, - UDC_DMA_STP_STS_BS_HOST_READY, - UDC_DMA_STP_STS_BS); - - - /* clear NAK by writing CNAK */ - if (ep->naking) { - tmp = readl(&ep->regs->ctl); - tmp |= AMD_BIT(UDC_EPCTL_CNAK); - writel(tmp, &ep->regs->ctl); - ep->naking = 0; - UDC_QUEUE_CNAK(ep, ep->num); - } - - } - - return retval; -} - -/* Completes request packet ... caller MUST hold lock */ -static void -complete_req(struct udc_ep *ep, struct udc_request *req, int sts) -__releases(ep->dev->lock) -__acquires(ep->dev->lock) -{ - struct udc *dev; - unsigned halted; - - VDBG(ep->dev, "complete_req(): ep%d\n", ep->num); - - dev = ep->dev; - /* unmap DMA */ - if (ep->dma) - usb_gadget_unmap_request(&dev->gadget, &req->req, ep->in); - - halted = ep->halted; - ep->halted = 1; - - /* set new status if pending */ - if (req->req.status == -EINPROGRESS) - req->req.status = sts; - - /* remove from ep queue */ - list_del_init(&req->queue); - - VDBG(ep->dev, "req %p => complete %d bytes at %s with sts %d\n", - &req->req, req->req.length, ep->ep.name, sts); - - spin_unlock(&dev->lock); - usb_gadget_giveback_request(&ep->ep, &req->req); - spin_lock(&dev->lock); - ep->halted = halted; -} - -/* Iterates to the end of a DMA chain and returns last descriptor */ -static struct udc_data_dma *udc_get_last_dma_desc(struct udc_request *req) -{ - struct udc_data_dma *td; - - td = req->td_data; - while (td && !(td->status & AMD_BIT(UDC_DMA_IN_STS_L))) - td = phys_to_virt(td->next); - - return td; - -} - -/* Iterates to the end of a DMA chain and counts bytes received */ -static u32 udc_get_ppbdu_rxbytes(struct udc_request *req) -{ - struct udc_data_dma *td; - u32 count; - - td = req->td_data; - /* received number bytes */ - count = AMD_GETBITS(td->status, UDC_DMA_OUT_STS_RXBYTES); - - while (td && !(td->status & AMD_BIT(UDC_DMA_IN_STS_L))) { - td = phys_to_virt(td->next); - /* received number bytes */ - if (td) { - count += AMD_GETBITS(td->status, - UDC_DMA_OUT_STS_RXBYTES); - } - } - - return count; - -} - -/* Enabling RX DMA */ -static void udc_set_rde(struct udc *dev) -{ - u32 tmp; - - VDBG(dev, "udc_set_rde()\n"); - /* stop RDE timer */ - if (timer_pending(&udc_timer)) { - set_rde = 0; - mod_timer(&udc_timer, jiffies - 1); - } - /* set RDE */ - tmp = readl(&dev->regs->ctl); - tmp |= AMD_BIT(UDC_DEVCTL_RDE); - writel(tmp, &dev->regs->ctl); -} - -/* Queues a request packet, called by gadget driver */ -static int -udc_queue(struct usb_ep *usbep, struct usb_request *usbreq, gfp_t gfp) -{ - int retval = 0; - u8 open_rxfifo = 0; - unsigned long iflags; - struct udc_ep *ep; - struct udc_request *req; - struct udc *dev; - u32 tmp; - - /* check the inputs */ - req = container_of(usbreq, struct udc_request, req); - - if (!usbep || !usbreq || !usbreq->complete || !usbreq->buf - || !list_empty(&req->queue)) - return -EINVAL; - - ep = container_of(usbep, struct udc_ep, ep); - if (!ep->ep.desc && (ep->num != 0 && ep->num != UDC_EP0OUT_IX)) - return -EINVAL; - - VDBG(ep->dev, "udc_queue(): ep%d-in=%d\n", ep->num, ep->in); - dev = ep->dev; - - if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) - return -ESHUTDOWN; - - /* map dma (usually done before) */ - if (ep->dma) { - VDBG(dev, "DMA map req %p\n", req); - retval = usb_gadget_map_request(&udc->gadget, usbreq, ep->in); - if (retval) - return retval; - } - - VDBG(dev, "%s queue req %p, len %d req->td_data=%p buf %p\n", - usbep->name, usbreq, usbreq->length, - req->td_data, usbreq->buf); - - spin_lock_irqsave(&dev->lock, iflags); - usbreq->actual = 0; - usbreq->status = -EINPROGRESS; - req->dma_done = 0; - - /* on empty queue just do first transfer */ - if (list_empty(&ep->queue)) { - /* zlp */ - if (usbreq->length == 0) { - /* IN zlp's are handled by hardware */ - complete_req(ep, req, 0); - VDBG(dev, "%s: zlp\n", ep->ep.name); - /* - * if set_config or set_intf is waiting for ack by zlp - * then set CSR_DONE - */ - if (dev->set_cfg_not_acked) { - tmp = readl(&dev->regs->ctl); - tmp |= AMD_BIT(UDC_DEVCTL_CSR_DONE); - writel(tmp, &dev->regs->ctl); - dev->set_cfg_not_acked = 0; - } - /* setup command is ACK'ed now by zlp */ - if (dev->waiting_zlp_ack_ep0in) { - /* clear NAK by writing CNAK in EP0_IN */ - tmp = readl(&dev->ep[UDC_EP0IN_IX].regs->ctl); - tmp |= AMD_BIT(UDC_EPCTL_CNAK); - writel(tmp, &dev->ep[UDC_EP0IN_IX].regs->ctl); - dev->ep[UDC_EP0IN_IX].naking = 0; - UDC_QUEUE_CNAK(&dev->ep[UDC_EP0IN_IX], - UDC_EP0IN_IX); - dev->waiting_zlp_ack_ep0in = 0; - } - goto finished; - } - if (ep->dma) { - retval = prep_dma(ep, req, GFP_ATOMIC); - if (retval != 0) - goto finished; - /* write desc pointer to enable DMA */ - if (ep->in) { - /* set HOST READY */ - req->td_data->status = - AMD_ADDBITS(req->td_data->status, - UDC_DMA_IN_STS_BS_HOST_READY, - UDC_DMA_IN_STS_BS); - } - - /* disabled rx dma while descriptor update */ - if (!ep->in) { - /* stop RDE timer */ - if (timer_pending(&udc_timer)) { - set_rde = 0; - mod_timer(&udc_timer, jiffies - 1); - } - /* clear RDE */ - tmp = readl(&dev->regs->ctl); - tmp &= AMD_UNMASK_BIT(UDC_DEVCTL_RDE); - writel(tmp, &dev->regs->ctl); - open_rxfifo = 1; - - /* - * if BNA occurred then let BNA dummy desc. - * point to current desc. - */ - if (ep->bna_occurred) { - VDBG(dev, "copy to BNA dummy desc.\n"); - memcpy(ep->bna_dummy_req->td_data, - req->td_data, - sizeof(struct udc_data_dma)); - } - } - /* write desc pointer */ - writel(req->td_phys, &ep->regs->desptr); - - /* clear NAK by writing CNAK */ - if (ep->naking) { - tmp = readl(&ep->regs->ctl); - tmp |= AMD_BIT(UDC_EPCTL_CNAK); - writel(tmp, &ep->regs->ctl); - ep->naking = 0; - UDC_QUEUE_CNAK(ep, ep->num); - } - - if (ep->in) { - /* enable ep irq */ - tmp = readl(&dev->regs->ep_irqmsk); - tmp &= AMD_UNMASK_BIT(ep->num); - writel(tmp, &dev->regs->ep_irqmsk); - } - } else if (ep->in) { - /* enable ep irq */ - tmp = readl(&dev->regs->ep_irqmsk); - tmp &= AMD_UNMASK_BIT(ep->num); - writel(tmp, &dev->regs->ep_irqmsk); - } - - } else if (ep->dma) { - - /* - * prep_dma not used for OUT ep's, this is not possible - * for PPB modes, because of chain creation reasons - */ - if (ep->in) { - retval = prep_dma(ep, req, GFP_ATOMIC); - if (retval != 0) - goto finished; - } - } - VDBG(dev, "list_add\n"); - /* add request to ep queue */ - if (req) { - - list_add_tail(&req->queue, &ep->queue); - - /* open rxfifo if out data queued */ - if (open_rxfifo) { - /* enable DMA */ - req->dma_going = 1; - udc_set_rde(dev); - if (ep->num != UDC_EP0OUT_IX) - dev->data_ep_queued = 1; - } - /* stop OUT naking */ - if (!ep->in) { - if (!use_dma && udc_rxfifo_pending) { - DBG(dev, "udc_queue(): pending bytes in " - "rxfifo after nyet\n"); - /* - * read pending bytes afer nyet: - * referring to isr - */ - if (udc_rxfifo_read(ep, req)) { - /* finish */ - complete_req(ep, req, 0); - } - udc_rxfifo_pending = 0; - - } - } - } - -finished: - spin_unlock_irqrestore(&dev->lock, iflags); - return retval; -} - -/* Empty request queue of an endpoint; caller holds spinlock */ -static void empty_req_queue(struct udc_ep *ep) -{ - struct udc_request *req; - - ep->halted = 1; - while (!list_empty(&ep->queue)) { - req = list_entry(ep->queue.next, - struct udc_request, - queue); - complete_req(ep, req, -ESHUTDOWN); - } -} - -/* Dequeues a request packet, called by gadget driver */ -static int udc_dequeue(struct usb_ep *usbep, struct usb_request *usbreq) -{ - struct udc_ep *ep; - struct udc_request *req; - unsigned halted; - unsigned long iflags; - - ep = container_of(usbep, struct udc_ep, ep); - if (!usbep || !usbreq || (!ep->ep.desc && (ep->num != 0 - && ep->num != UDC_EP0OUT_IX))) - return -EINVAL; - - req = container_of(usbreq, struct udc_request, req); - - spin_lock_irqsave(&ep->dev->lock, iflags); - halted = ep->halted; - ep->halted = 1; - /* request in processing or next one */ - if (ep->queue.next == &req->queue) { - if (ep->dma && req->dma_going) { - if (ep->in) - ep->cancel_transfer = 1; - else { - u32 tmp; - u32 dma_sts; - /* stop potential receive DMA */ - tmp = readl(&udc->regs->ctl); - writel(tmp & AMD_UNMASK_BIT(UDC_DEVCTL_RDE), - &udc->regs->ctl); - /* - * Cancel transfer later in ISR - * if descriptor was touched. - */ - dma_sts = AMD_GETBITS(req->td_data->status, - UDC_DMA_OUT_STS_BS); - if (dma_sts != UDC_DMA_OUT_STS_BS_HOST_READY) - ep->cancel_transfer = 1; - else { - udc_init_bna_dummy(ep->req); - writel(ep->bna_dummy_req->td_phys, - &ep->regs->desptr); - } - writel(tmp, &udc->regs->ctl); - } - } - } - complete_req(ep, req, -ECONNRESET); - ep->halted = halted; - - spin_unlock_irqrestore(&ep->dev->lock, iflags); - return 0; -} - -/* Halt or clear halt of endpoint */ -static int -udc_set_halt(struct usb_ep *usbep, int halt) -{ - struct udc_ep *ep; - u32 tmp; - unsigned long iflags; - int retval = 0; - - if (!usbep) - return -EINVAL; - - pr_debug("set_halt %s: halt=%d\n", usbep->name, halt); - - ep = container_of(usbep, struct udc_ep, ep); - if (!ep->ep.desc && (ep->num != 0 && ep->num != UDC_EP0OUT_IX)) - return -EINVAL; - if (!ep->dev->driver || ep->dev->gadget.speed == USB_SPEED_UNKNOWN) - return -ESHUTDOWN; - - spin_lock_irqsave(&udc_stall_spinlock, iflags); - /* halt or clear halt */ - if (halt) { - if (ep->num == 0) - ep->dev->stall_ep0in = 1; - else { - /* - * set STALL - * rxfifo empty not taken into acount - */ - tmp = readl(&ep->regs->ctl); - tmp |= AMD_BIT(UDC_EPCTL_S); - writel(tmp, &ep->regs->ctl); - ep->halted = 1; - - /* setup poll timer */ - if (!timer_pending(&udc_pollstall_timer)) { - udc_pollstall_timer.expires = jiffies + - HZ * UDC_POLLSTALL_TIMER_USECONDS - / (1000 * 1000); - if (!stop_pollstall_timer) { - DBG(ep->dev, "start polltimer\n"); - add_timer(&udc_pollstall_timer); - } - } - } - } else { - /* ep is halted by set_halt() before */ - if (ep->halted) { - tmp = readl(&ep->regs->ctl); - /* clear stall bit */ - tmp = tmp & AMD_CLEAR_BIT(UDC_EPCTL_S); - /* clear NAK by writing CNAK */ - tmp |= AMD_BIT(UDC_EPCTL_CNAK); - writel(tmp, &ep->regs->ctl); - ep->halted = 0; - UDC_QUEUE_CNAK(ep, ep->num); - } - } - spin_unlock_irqrestore(&udc_stall_spinlock, iflags); - return retval; -} - -/* gadget interface */ -static const struct usb_ep_ops udc_ep_ops = { - .enable = udc_ep_enable, - .disable = udc_ep_disable, - - .alloc_request = udc_alloc_request, - .free_request = udc_free_request, - - .queue = udc_queue, - .dequeue = udc_dequeue, - - .set_halt = udc_set_halt, - /* fifo ops not implemented */ -}; - -/*-------------------------------------------------------------------------*/ - -/* Get frame counter (not implemented) */ -static int udc_get_frame(struct usb_gadget *gadget) -{ - return -EOPNOTSUPP; -} - -/* Initiates a remote wakeup */ -static int udc_remote_wakeup(struct udc *dev) -{ - unsigned long flags; - u32 tmp; - - DBG(dev, "UDC initiates remote wakeup\n"); - - spin_lock_irqsave(&dev->lock, flags); - - tmp = readl(&dev->regs->ctl); - tmp |= AMD_BIT(UDC_DEVCTL_RES); - writel(tmp, &dev->regs->ctl); - tmp &= AMD_CLEAR_BIT(UDC_DEVCTL_RES); - writel(tmp, &dev->regs->ctl); - - spin_unlock_irqrestore(&dev->lock, flags); - return 0; -} - -/* Remote wakeup gadget interface */ -static int udc_wakeup(struct usb_gadget *gadget) -{ - struct udc *dev; - - if (!gadget) - return -EINVAL; - dev = container_of(gadget, struct udc, gadget); - udc_remote_wakeup(dev); - - return 0; -} - -static int amd5536_udc_start(struct usb_gadget *g, - struct usb_gadget_driver *driver); -static int amd5536_udc_stop(struct usb_gadget *g); - -static const struct usb_gadget_ops udc_ops = { - .wakeup = udc_wakeup, - .get_frame = udc_get_frame, - .udc_start = amd5536_udc_start, - .udc_stop = amd5536_udc_stop, -}; - -/* Setups endpoint parameters, adds endpoints to linked list */ -static void make_ep_lists(struct udc *dev) -{ - /* make gadget ep lists */ - INIT_LIST_HEAD(&dev->gadget.ep_list); - list_add_tail(&dev->ep[UDC_EPIN_STATUS_IX].ep.ep_list, - &dev->gadget.ep_list); - list_add_tail(&dev->ep[UDC_EPIN_IX].ep.ep_list, - &dev->gadget.ep_list); - list_add_tail(&dev->ep[UDC_EPOUT_IX].ep.ep_list, - &dev->gadget.ep_list); - - /* fifo config */ - dev->ep[UDC_EPIN_STATUS_IX].fifo_depth = UDC_EPIN_SMALLINT_BUFF_SIZE; - if (dev->gadget.speed == USB_SPEED_FULL) - dev->ep[UDC_EPIN_IX].fifo_depth = UDC_FS_EPIN_BUFF_SIZE; - else if (dev->gadget.speed == USB_SPEED_HIGH) - dev->ep[UDC_EPIN_IX].fifo_depth = hs_tx_buf; - dev->ep[UDC_EPOUT_IX].fifo_depth = UDC_RXFIFO_SIZE; -} - -/* Inits UDC context */ -void udc_basic_init(struct udc *dev) -{ - u32 tmp; - - DBG(dev, "udc_basic_init()\n"); - - dev->gadget.speed = USB_SPEED_UNKNOWN; - - /* stop RDE timer */ - if (timer_pending(&udc_timer)) { - set_rde = 0; - mod_timer(&udc_timer, jiffies - 1); - } - /* stop poll stall timer */ - if (timer_pending(&udc_pollstall_timer)) - mod_timer(&udc_pollstall_timer, jiffies - 1); - /* disable DMA */ - tmp = readl(&dev->regs->ctl); - tmp &= AMD_UNMASK_BIT(UDC_DEVCTL_RDE); - tmp &= AMD_UNMASK_BIT(UDC_DEVCTL_TDE); - writel(tmp, &dev->regs->ctl); - - /* enable dynamic CSR programming */ - tmp = readl(&dev->regs->cfg); - tmp |= AMD_BIT(UDC_DEVCFG_CSR_PRG); - /* set self powered */ - tmp |= AMD_BIT(UDC_DEVCFG_SP); - /* set remote wakeupable */ - tmp |= AMD_BIT(UDC_DEVCFG_RWKP); - writel(tmp, &dev->regs->cfg); - - make_ep_lists(dev); - - dev->data_ep_enabled = 0; - dev->data_ep_queued = 0; -} -EXPORT_SYMBOL_GPL(udc_basic_init); - -/* init registers at driver load time */ -static int startup_registers(struct udc *dev) -{ - u32 tmp; - - /* init controller by soft reset */ - udc_soft_reset(dev); - - /* mask not needed interrupts */ - udc_mask_unused_interrupts(dev); - - /* put into initial config */ - udc_basic_init(dev); - /* link up all endpoints */ - udc_setup_endpoints(dev); - - /* program speed */ - tmp = readl(&dev->regs->cfg); - if (use_fullspeed) - tmp = AMD_ADDBITS(tmp, UDC_DEVCFG_SPD_FS, UDC_DEVCFG_SPD); - else - tmp = AMD_ADDBITS(tmp, UDC_DEVCFG_SPD_HS, UDC_DEVCFG_SPD); - writel(tmp, &dev->regs->cfg); - - return 0; -} - -/* Sets initial endpoint parameters */ -static void udc_setup_endpoints(struct udc *dev) -{ - struct udc_ep *ep; - u32 tmp; - u32 reg; - - DBG(dev, "udc_setup_endpoints()\n"); - - /* read enum speed */ - tmp = readl(&dev->regs->sts); - tmp = AMD_GETBITS(tmp, UDC_DEVSTS_ENUM_SPEED); - if (tmp == UDC_DEVSTS_ENUM_SPEED_HIGH) - dev->gadget.speed = USB_SPEED_HIGH; - else if (tmp == UDC_DEVSTS_ENUM_SPEED_FULL) - dev->gadget.speed = USB_SPEED_FULL; - - /* set basic ep parameters */ - for (tmp = 0; tmp < UDC_EP_NUM; tmp++) { - ep = &dev->ep[tmp]; - ep->dev = dev; - ep->ep.name = ep_info[tmp].name; - ep->ep.caps = ep_info[tmp].caps; - ep->num = tmp; - /* txfifo size is calculated at enable time */ - ep->txfifo = dev->txfifo; - - /* fifo size */ - if (tmp < UDC_EPIN_NUM) { - ep->fifo_depth = UDC_TXFIFO_SIZE; - ep->in = 1; - } else { - ep->fifo_depth = UDC_RXFIFO_SIZE; - ep->in = 0; - - } - ep->regs = &dev->ep_regs[tmp]; - /* - * ep will be reset only if ep was not enabled before to avoid - * disabling ep interrupts when ENUM interrupt occurs but ep is - * not enabled by gadget driver - */ - if (!ep->ep.desc) - ep_init(dev->regs, ep); - - if (use_dma) { - /* - * ep->dma is not really used, just to indicate that - * DMA is active: remove this - * dma regs = dev control regs - */ - ep->dma = &dev->regs->ctl; - - /* nak OUT endpoints until enable - not for ep0 */ - if (tmp != UDC_EP0IN_IX && tmp != UDC_EP0OUT_IX - && tmp > UDC_EPIN_NUM) { - /* set NAK */ - reg = readl(&dev->ep[tmp].regs->ctl); - reg |= AMD_BIT(UDC_EPCTL_SNAK); - writel(reg, &dev->ep[tmp].regs->ctl); - dev->ep[tmp].naking = 1; - - } - } - } - /* EP0 max packet */ - if (dev->gadget.speed == USB_SPEED_FULL) { - usb_ep_set_maxpacket_limit(&dev->ep[UDC_EP0IN_IX].ep, - UDC_FS_EP0IN_MAX_PKT_SIZE); - usb_ep_set_maxpacket_limit(&dev->ep[UDC_EP0OUT_IX].ep, - UDC_FS_EP0OUT_MAX_PKT_SIZE); - } else if (dev->gadget.speed == USB_SPEED_HIGH) { - usb_ep_set_maxpacket_limit(&dev->ep[UDC_EP0IN_IX].ep, - UDC_EP0IN_MAX_PKT_SIZE); - usb_ep_set_maxpacket_limit(&dev->ep[UDC_EP0OUT_IX].ep, - UDC_EP0OUT_MAX_PKT_SIZE); - } - - /* - * with suspend bug workaround, ep0 params for gadget driver - * are set at gadget driver bind() call - */ - dev->gadget.ep0 = &dev->ep[UDC_EP0IN_IX].ep; - dev->ep[UDC_EP0IN_IX].halted = 0; - INIT_LIST_HEAD(&dev->gadget.ep0->ep_list); - - /* init cfg/alt/int */ - dev->cur_config = 0; - dev->cur_intf = 0; - dev->cur_alt = 0; -} - -/* Bringup after Connect event, initial bringup to be ready for ep0 events */ -static void usb_connect(struct udc *dev) -{ - - dev_info(&dev->pdev->dev, "USB Connect\n"); - - dev->connected = 1; - - /* put into initial config */ - udc_basic_init(dev); - - /* enable device setup interrupts */ - udc_enable_dev_setup_interrupts(dev); -} - -/* - * Calls gadget with disconnect event and resets the UDC and makes - * initial bringup to be ready for ep0 events - */ -static void usb_disconnect(struct udc *dev) -{ - - dev_info(&dev->pdev->dev, "USB Disconnect\n"); - - dev->connected = 0; - - /* mask interrupts */ - udc_mask_unused_interrupts(dev); - - /* REVISIT there doesn't seem to be a point to having this - * talk to a tasklet ... do it directly, we already hold - * the spinlock needed to process the disconnect. - */ - - tasklet_schedule(&disconnect_tasklet); -} - -/* Tasklet for disconnect to be outside of interrupt context */ -static void udc_tasklet_disconnect(unsigned long par) -{ - struct udc *dev = (struct udc *)(*((struct udc **) par)); - u32 tmp; - - DBG(dev, "Tasklet disconnect\n"); - spin_lock_irq(&dev->lock); - - if (dev->driver) { - spin_unlock(&dev->lock); - dev->driver->disconnect(&dev->gadget); - spin_lock(&dev->lock); - - /* empty queues */ - for (tmp = 0; tmp < UDC_EP_NUM; tmp++) - empty_req_queue(&dev->ep[tmp]); - - } - - /* disable ep0 */ - ep_init(dev->regs, - &dev->ep[UDC_EP0IN_IX]); - - - if (!soft_reset_occured) { - /* init controller by soft reset */ - udc_soft_reset(dev); - soft_reset_occured++; - } - - /* re-enable dev interrupts */ - udc_enable_dev_setup_interrupts(dev); - /* back to full speed ? */ - if (use_fullspeed) { - tmp = readl(&dev->regs->cfg); - tmp = AMD_ADDBITS(tmp, UDC_DEVCFG_SPD_FS, UDC_DEVCFG_SPD); - writel(tmp, &dev->regs->cfg); - } - - spin_unlock_irq(&dev->lock); -} - -/* Reset the UDC core */ -static void udc_soft_reset(struct udc *dev) -{ - unsigned long flags; - - DBG(dev, "Soft reset\n"); - /* - * reset possible waiting interrupts, because int. - * status is lost after soft reset, - * ep int. status reset - */ - writel(UDC_EPINT_MSK_DISABLE_ALL, &dev->regs->ep_irqsts); - /* device int. status reset */ - writel(UDC_DEV_MSK_DISABLE, &dev->regs->irqsts); - - spin_lock_irqsave(&udc_irq_spinlock, flags); - writel(AMD_BIT(UDC_DEVCFG_SOFTRESET), &dev->regs->cfg); - readl(&dev->regs->cfg); - spin_unlock_irqrestore(&udc_irq_spinlock, flags); - -} - -/* RDE timer callback to set RDE bit */ -static void udc_timer_function(unsigned long v) -{ - u32 tmp; - - spin_lock_irq(&udc_irq_spinlock); - - if (set_rde > 0) { - /* - * open the fifo if fifo was filled on last timer call - * conditionally - */ - if (set_rde > 1) { - /* set RDE to receive setup data */ - tmp = readl(&udc->regs->ctl); - tmp |= AMD_BIT(UDC_DEVCTL_RDE); - writel(tmp, &udc->regs->ctl); - set_rde = -1; - } else if (readl(&udc->regs->sts) - & AMD_BIT(UDC_DEVSTS_RXFIFO_EMPTY)) { - /* - * if fifo empty setup polling, do not just - * open the fifo - */ - udc_timer.expires = jiffies + HZ/UDC_RDE_TIMER_DIV; - if (!stop_timer) - add_timer(&udc_timer); - } else { - /* - * fifo contains data now, setup timer for opening - * the fifo when timer expires to be able to receive - * setup packets, when data packets gets queued by - * gadget layer then timer will forced to expire with - * set_rde=0 (RDE is set in udc_queue()) - */ - set_rde++; - /* debug: lhadmot_timer_start = 221070 */ - udc_timer.expires = jiffies + HZ*UDC_RDE_TIMER_SECONDS; - if (!stop_timer) - add_timer(&udc_timer); - } - - } else - set_rde = -1; /* RDE was set by udc_queue() */ - spin_unlock_irq(&udc_irq_spinlock); - if (stop_timer) - complete(&on_exit); - -} - -/* Handle halt state, used in stall poll timer */ -static void udc_handle_halt_state(struct udc_ep *ep) -{ - u32 tmp; - /* set stall as long not halted */ - if (ep->halted == 1) { - tmp = readl(&ep->regs->ctl); - /* STALL cleared ? */ - if (!(tmp & AMD_BIT(UDC_EPCTL_S))) { - /* - * FIXME: MSC spec requires that stall remains - * even on receivng of CLEAR_FEATURE HALT. So - * we would set STALL again here to be compliant. - * But with current mass storage drivers this does - * not work (would produce endless host retries). - * So we clear halt on CLEAR_FEATURE. - * - DBG(ep->dev, "ep %d: set STALL again\n", ep->num); - tmp |= AMD_BIT(UDC_EPCTL_S); - writel(tmp, &ep->regs->ctl);*/ - - /* clear NAK by writing CNAK */ - tmp |= AMD_BIT(UDC_EPCTL_CNAK); - writel(tmp, &ep->regs->ctl); - ep->halted = 0; - UDC_QUEUE_CNAK(ep, ep->num); - } - } -} - -/* Stall timer callback to poll S bit and set it again after */ -static void udc_pollstall_timer_function(unsigned long v) -{ - struct udc_ep *ep; - int halted = 0; - - spin_lock_irq(&udc_stall_spinlock); - /* - * only one IN and OUT endpoints are handled - * IN poll stall - */ - ep = &udc->ep[UDC_EPIN_IX]; - udc_handle_halt_state(ep); - if (ep->halted) - halted = 1; - /* OUT poll stall */ - ep = &udc->ep[UDC_EPOUT_IX]; - udc_handle_halt_state(ep); - if (ep->halted) - halted = 1; - - /* setup timer again when still halted */ - if (!stop_pollstall_timer && halted) { - udc_pollstall_timer.expires = jiffies + - HZ * UDC_POLLSTALL_TIMER_USECONDS - / (1000 * 1000); - add_timer(&udc_pollstall_timer); - } - spin_unlock_irq(&udc_stall_spinlock); - - if (stop_pollstall_timer) - complete(&on_pollstall_exit); -} - -/* Inits endpoint 0 so that SETUP packets are processed */ -static void activate_control_endpoints(struct udc *dev) -{ - u32 tmp; - - DBG(dev, "activate_control_endpoints\n"); - - /* flush fifo */ - tmp = readl(&dev->ep[UDC_EP0IN_IX].regs->ctl); - tmp |= AMD_BIT(UDC_EPCTL_F); - writel(tmp, &dev->ep[UDC_EP0IN_IX].regs->ctl); - - /* set ep0 directions */ - dev->ep[UDC_EP0IN_IX].in = 1; - dev->ep[UDC_EP0OUT_IX].in = 0; - - /* set buffer size (tx fifo entries) of EP0_IN */ - tmp = readl(&dev->ep[UDC_EP0IN_IX].regs->bufin_framenum); - if (dev->gadget.speed == USB_SPEED_FULL) - tmp = AMD_ADDBITS(tmp, UDC_FS_EPIN0_BUFF_SIZE, - UDC_EPIN_BUFF_SIZE); - else if (dev->gadget.speed == USB_SPEED_HIGH) - tmp = AMD_ADDBITS(tmp, UDC_EPIN0_BUFF_SIZE, - UDC_EPIN_BUFF_SIZE); - writel(tmp, &dev->ep[UDC_EP0IN_IX].regs->bufin_framenum); - - /* set max packet size of EP0_IN */ - tmp = readl(&dev->ep[UDC_EP0IN_IX].regs->bufout_maxpkt); - if (dev->gadget.speed == USB_SPEED_FULL) - tmp = AMD_ADDBITS(tmp, UDC_FS_EP0IN_MAX_PKT_SIZE, - UDC_EP_MAX_PKT_SIZE); - else if (dev->gadget.speed == USB_SPEED_HIGH) - tmp = AMD_ADDBITS(tmp, UDC_EP0IN_MAX_PKT_SIZE, - UDC_EP_MAX_PKT_SIZE); - writel(tmp, &dev->ep[UDC_EP0IN_IX].regs->bufout_maxpkt); - - /* set max packet size of EP0_OUT */ - tmp = readl(&dev->ep[UDC_EP0OUT_IX].regs->bufout_maxpkt); - if (dev->gadget.speed == USB_SPEED_FULL) - tmp = AMD_ADDBITS(tmp, UDC_FS_EP0OUT_MAX_PKT_SIZE, - UDC_EP_MAX_PKT_SIZE); - else if (dev->gadget.speed == USB_SPEED_HIGH) - tmp = AMD_ADDBITS(tmp, UDC_EP0OUT_MAX_PKT_SIZE, - UDC_EP_MAX_PKT_SIZE); - writel(tmp, &dev->ep[UDC_EP0OUT_IX].regs->bufout_maxpkt); - - /* set max packet size of EP0 in UDC CSR */ - tmp = readl(&dev->csr->ne[0]); - if (dev->gadget.speed == USB_SPEED_FULL) - tmp = AMD_ADDBITS(tmp, UDC_FS_EP0OUT_MAX_PKT_SIZE, - UDC_CSR_NE_MAX_PKT); - else if (dev->gadget.speed == USB_SPEED_HIGH) - tmp = AMD_ADDBITS(tmp, UDC_EP0OUT_MAX_PKT_SIZE, - UDC_CSR_NE_MAX_PKT); - writel(tmp, &dev->csr->ne[0]); - - if (use_dma) { - dev->ep[UDC_EP0OUT_IX].td->status |= - AMD_BIT(UDC_DMA_OUT_STS_L); - /* write dma desc address */ - writel(dev->ep[UDC_EP0OUT_IX].td_stp_dma, - &dev->ep[UDC_EP0OUT_IX].regs->subptr); - writel(dev->ep[UDC_EP0OUT_IX].td_phys, - &dev->ep[UDC_EP0OUT_IX].regs->desptr); - /* stop RDE timer */ - if (timer_pending(&udc_timer)) { - set_rde = 0; - mod_timer(&udc_timer, jiffies - 1); - } - /* stop pollstall timer */ - if (timer_pending(&udc_pollstall_timer)) - mod_timer(&udc_pollstall_timer, jiffies - 1); - /* enable DMA */ - tmp = readl(&dev->regs->ctl); - tmp |= AMD_BIT(UDC_DEVCTL_MODE) - | AMD_BIT(UDC_DEVCTL_RDE) - | AMD_BIT(UDC_DEVCTL_TDE); - if (use_dma_bufferfill_mode) - tmp |= AMD_BIT(UDC_DEVCTL_BF); - else if (use_dma_ppb_du) - tmp |= AMD_BIT(UDC_DEVCTL_DU); - writel(tmp, &dev->regs->ctl); - } - - /* clear NAK by writing CNAK for EP0IN */ - tmp = readl(&dev->ep[UDC_EP0IN_IX].regs->ctl); - tmp |= AMD_BIT(UDC_EPCTL_CNAK); - writel(tmp, &dev->ep[UDC_EP0IN_IX].regs->ctl); - dev->ep[UDC_EP0IN_IX].naking = 0; - UDC_QUEUE_CNAK(&dev->ep[UDC_EP0IN_IX], UDC_EP0IN_IX); - - /* clear NAK by writing CNAK for EP0OUT */ - tmp = readl(&dev->ep[UDC_EP0OUT_IX].regs->ctl); - tmp |= AMD_BIT(UDC_EPCTL_CNAK); - writel(tmp, &dev->ep[UDC_EP0OUT_IX].regs->ctl); - dev->ep[UDC_EP0OUT_IX].naking = 0; - UDC_QUEUE_CNAK(&dev->ep[UDC_EP0OUT_IX], UDC_EP0OUT_IX); -} - -/* Make endpoint 0 ready for control traffic */ -static int setup_ep0(struct udc *dev) -{ - activate_control_endpoints(dev); - /* enable ep0 interrupts */ - udc_enable_ep0_interrupts(dev); - /* enable device setup interrupts */ - udc_enable_dev_setup_interrupts(dev); - - return 0; -} - -/* Called by gadget driver to register itself */ -static int amd5536_udc_start(struct usb_gadget *g, - struct usb_gadget_driver *driver) -{ - struct udc *dev = to_amd5536_udc(g); - u32 tmp; - - driver->driver.bus = NULL; - dev->driver = driver; - - /* Some gadget drivers use both ep0 directions. - * NOTE: to gadget driver, ep0 is just one endpoint... - */ - dev->ep[UDC_EP0OUT_IX].ep.driver_data = - dev->ep[UDC_EP0IN_IX].ep.driver_data; - - /* get ready for ep0 traffic */ - setup_ep0(dev); - - /* clear SD */ - tmp = readl(&dev->regs->ctl); - tmp = tmp & AMD_CLEAR_BIT(UDC_DEVCTL_SD); - writel(tmp, &dev->regs->ctl); - - usb_connect(dev); - - return 0; -} - -/* shutdown requests and disconnect from gadget */ -static void -shutdown(struct udc *dev, struct usb_gadget_driver *driver) -__releases(dev->lock) -__acquires(dev->lock) -{ - int tmp; - - /* empty queues and init hardware */ - udc_basic_init(dev); - - for (tmp = 0; tmp < UDC_EP_NUM; tmp++) - empty_req_queue(&dev->ep[tmp]); - - udc_setup_endpoints(dev); -} - -/* Called by gadget driver to unregister itself */ -static int amd5536_udc_stop(struct usb_gadget *g) -{ - struct udc *dev = to_amd5536_udc(g); - unsigned long flags; - u32 tmp; - - spin_lock_irqsave(&dev->lock, flags); - udc_mask_unused_interrupts(dev); - shutdown(dev, NULL); - spin_unlock_irqrestore(&dev->lock, flags); - - dev->driver = NULL; - - /* set SD */ - tmp = readl(&dev->regs->ctl); - tmp |= AMD_BIT(UDC_DEVCTL_SD); - writel(tmp, &dev->regs->ctl); - - return 0; -} - -/* Clear pending NAK bits */ -static void udc_process_cnak_queue(struct udc *dev) -{ - u32 tmp; - u32 reg; - - /* check epin's */ - DBG(dev, "CNAK pending queue processing\n"); - for (tmp = 0; tmp < UDC_EPIN_NUM_USED; tmp++) { - if (cnak_pending & (1 << tmp)) { - DBG(dev, "CNAK pending for ep%d\n", tmp); - /* clear NAK by writing CNAK */ - reg = readl(&dev->ep[tmp].regs->ctl); - reg |= AMD_BIT(UDC_EPCTL_CNAK); - writel(reg, &dev->ep[tmp].regs->ctl); - dev->ep[tmp].naking = 0; - UDC_QUEUE_CNAK(&dev->ep[tmp], dev->ep[tmp].num); - } - } - /* ... and ep0out */ - if (cnak_pending & (1 << UDC_EP0OUT_IX)) { - DBG(dev, "CNAK pending for ep%d\n", UDC_EP0OUT_IX); - /* clear NAK by writing CNAK */ - reg = readl(&dev->ep[UDC_EP0OUT_IX].regs->ctl); - reg |= AMD_BIT(UDC_EPCTL_CNAK); - writel(reg, &dev->ep[UDC_EP0OUT_IX].regs->ctl); - dev->ep[UDC_EP0OUT_IX].naking = 0; - UDC_QUEUE_CNAK(&dev->ep[UDC_EP0OUT_IX], - dev->ep[UDC_EP0OUT_IX].num); - } -} - -/* Enabling RX DMA after setup packet */ -static void udc_ep0_set_rde(struct udc *dev) -{ - if (use_dma) { - /* - * only enable RXDMA when no data endpoint enabled - * or data is queued - */ - if (!dev->data_ep_enabled || dev->data_ep_queued) { - udc_set_rde(dev); - } else { - /* - * setup timer for enabling RDE (to not enable - * RXFIFO DMA for data endpoints to early) - */ - if (set_rde != 0 && !timer_pending(&udc_timer)) { - udc_timer.expires = - jiffies + HZ/UDC_RDE_TIMER_DIV; - set_rde = 1; - if (!stop_timer) - add_timer(&udc_timer); - } - } - } -} - - -/* Interrupt handler for data OUT traffic */ -static irqreturn_t udc_data_out_isr(struct udc *dev, int ep_ix) -{ - irqreturn_t ret_val = IRQ_NONE; - u32 tmp; - struct udc_ep *ep; - struct udc_request *req; - unsigned int count; - struct udc_data_dma *td = NULL; - unsigned dma_done; - - VDBG(dev, "ep%d irq\n", ep_ix); - ep = &dev->ep[ep_ix]; - - tmp = readl(&ep->regs->sts); - if (use_dma) { - /* BNA event ? */ - if (tmp & AMD_BIT(UDC_EPSTS_BNA)) { - DBG(dev, "BNA ep%dout occurred - DESPTR = %x\n", - ep->num, readl(&ep->regs->desptr)); - /* clear BNA */ - writel(tmp | AMD_BIT(UDC_EPSTS_BNA), &ep->regs->sts); - if (!ep->cancel_transfer) - ep->bna_occurred = 1; - else - ep->cancel_transfer = 0; - ret_val = IRQ_HANDLED; - goto finished; - } - } - /* HE event ? */ - if (tmp & AMD_BIT(UDC_EPSTS_HE)) { - dev_err(&dev->pdev->dev, "HE ep%dout occurred\n", ep->num); - - /* clear HE */ - writel(tmp | AMD_BIT(UDC_EPSTS_HE), &ep->regs->sts); - ret_val = IRQ_HANDLED; - goto finished; - } - - if (!list_empty(&ep->queue)) { - - /* next request */ - req = list_entry(ep->queue.next, - struct udc_request, queue); - } else { - req = NULL; - udc_rxfifo_pending = 1; - } - VDBG(dev, "req = %p\n", req); - /* fifo mode */ - if (!use_dma) { - - /* read fifo */ - if (req && udc_rxfifo_read(ep, req)) { - ret_val = IRQ_HANDLED; - - /* finish */ - complete_req(ep, req, 0); - /* next request */ - if (!list_empty(&ep->queue) && !ep->halted) { - req = list_entry(ep->queue.next, - struct udc_request, queue); - } else - req = NULL; - } - - /* DMA */ - } else if (!ep->cancel_transfer && req) { - ret_val = IRQ_HANDLED; - - /* check for DMA done */ - if (!use_dma_ppb) { - dma_done = AMD_GETBITS(req->td_data->status, - UDC_DMA_OUT_STS_BS); - /* packet per buffer mode - rx bytes */ - } else { - /* - * if BNA occurred then recover desc. from - * BNA dummy desc. - */ - if (ep->bna_occurred) { - VDBG(dev, "Recover desc. from BNA dummy\n"); - memcpy(req->td_data, ep->bna_dummy_req->td_data, - sizeof(struct udc_data_dma)); - ep->bna_occurred = 0; - udc_init_bna_dummy(ep->req); - } - td = udc_get_last_dma_desc(req); - dma_done = AMD_GETBITS(td->status, UDC_DMA_OUT_STS_BS); - } - if (dma_done == UDC_DMA_OUT_STS_BS_DMA_DONE) { - /* buffer fill mode - rx bytes */ - if (!use_dma_ppb) { - /* received number bytes */ - count = AMD_GETBITS(req->td_data->status, - UDC_DMA_OUT_STS_RXBYTES); - VDBG(dev, "rx bytes=%u\n", count); - /* packet per buffer mode - rx bytes */ - } else { - VDBG(dev, "req->td_data=%p\n", req->td_data); - VDBG(dev, "last desc = %p\n", td); - /* received number bytes */ - if (use_dma_ppb_du) { - /* every desc. counts bytes */ - count = udc_get_ppbdu_rxbytes(req); - } else { - /* last desc. counts bytes */ - count = AMD_GETBITS(td->status, - UDC_DMA_OUT_STS_RXBYTES); - if (!count && req->req.length - == UDC_DMA_MAXPACKET) { - /* - * on 64k packets the RXBYTES - * field is zero - */ - count = UDC_DMA_MAXPACKET; - } - } - VDBG(dev, "last desc rx bytes=%u\n", count); - } - - tmp = req->req.length - req->req.actual; - if (count > tmp) { - if ((tmp % ep->ep.maxpacket) != 0) { - DBG(dev, "%s: rx %db, space=%db\n", - ep->ep.name, count, tmp); - req->req.status = -EOVERFLOW; - } - count = tmp; - } - req->req.actual += count; - req->dma_going = 0; - /* complete request */ - complete_req(ep, req, 0); - - /* next request */ - if (!list_empty(&ep->queue) && !ep->halted) { - req = list_entry(ep->queue.next, - struct udc_request, - queue); - /* - * DMA may be already started by udc_queue() - * called by gadget drivers completion - * routine. This happens when queue - * holds one request only. - */ - if (req->dma_going == 0) { - /* next dma */ - if (prep_dma(ep, req, GFP_ATOMIC) != 0) - goto finished; - /* write desc pointer */ - writel(req->td_phys, - &ep->regs->desptr); - req->dma_going = 1; - /* enable DMA */ - udc_set_rde(dev); - } - } else { - /* - * implant BNA dummy descriptor to allow - * RXFIFO opening by RDE - */ - if (ep->bna_dummy_req) { - /* write desc pointer */ - writel(ep->bna_dummy_req->td_phys, - &ep->regs->desptr); - ep->bna_occurred = 0; - } - - /* - * schedule timer for setting RDE if queue - * remains empty to allow ep0 packets pass - * through - */ - if (set_rde != 0 - && !timer_pending(&udc_timer)) { - udc_timer.expires = - jiffies - + HZ*UDC_RDE_TIMER_SECONDS; - set_rde = 1; - if (!stop_timer) - add_timer(&udc_timer); - } - if (ep->num != UDC_EP0OUT_IX) - dev->data_ep_queued = 0; - } - - } else { - /* - * RX DMA must be reenabled for each desc in PPBDU mode - * and must be enabled for PPBNDU mode in case of BNA - */ - udc_set_rde(dev); - } - - } else if (ep->cancel_transfer) { - ret_val = IRQ_HANDLED; - ep->cancel_transfer = 0; - } - - /* check pending CNAKS */ - if (cnak_pending) { - /* CNAk processing when rxfifo empty only */ - if (readl(&dev->regs->sts) & AMD_BIT(UDC_DEVSTS_RXFIFO_EMPTY)) - udc_process_cnak_queue(dev); - } - - /* clear OUT bits in ep status */ - writel(UDC_EPSTS_OUT_CLEAR, &ep->regs->sts); -finished: - return ret_val; -} - -/* Interrupt handler for data IN traffic */ -static irqreturn_t udc_data_in_isr(struct udc *dev, int ep_ix) -{ - irqreturn_t ret_val = IRQ_NONE; - u32 tmp; - u32 epsts; - struct udc_ep *ep; - struct udc_request *req; - struct udc_data_dma *td; - unsigned len; - - ep = &dev->ep[ep_ix]; - - epsts = readl(&ep->regs->sts); - if (use_dma) { - /* BNA ? */ - if (epsts & AMD_BIT(UDC_EPSTS_BNA)) { - dev_err(&dev->pdev->dev, - "BNA ep%din occurred - DESPTR = %08lx\n", - ep->num, - (unsigned long) readl(&ep->regs->desptr)); - - /* clear BNA */ - writel(epsts, &ep->regs->sts); - ret_val = IRQ_HANDLED; - goto finished; - } - } - /* HE event ? */ - if (epsts & AMD_BIT(UDC_EPSTS_HE)) { - dev_err(&dev->pdev->dev, - "HE ep%dn occurred - DESPTR = %08lx\n", - ep->num, (unsigned long) readl(&ep->regs->desptr)); - - /* clear HE */ - writel(epsts | AMD_BIT(UDC_EPSTS_HE), &ep->regs->sts); - ret_val = IRQ_HANDLED; - goto finished; - } - - /* DMA completion */ - if (epsts & AMD_BIT(UDC_EPSTS_TDC)) { - VDBG(dev, "TDC set- completion\n"); - ret_val = IRQ_HANDLED; - if (!ep->cancel_transfer && !list_empty(&ep->queue)) { - req = list_entry(ep->queue.next, - struct udc_request, queue); - /* - * length bytes transferred - * check dma done of last desc. in PPBDU mode - */ - if (use_dma_ppb_du) { - td = udc_get_last_dma_desc(req); - if (td) - req->req.actual = req->req.length; - } else { - /* assume all bytes transferred */ - req->req.actual = req->req.length; - } - - if (req->req.actual == req->req.length) { - /* complete req */ - complete_req(ep, req, 0); - req->dma_going = 0; - /* further request available ? */ - if (list_empty(&ep->queue)) { - /* disable interrupt */ - tmp = readl(&dev->regs->ep_irqmsk); - tmp |= AMD_BIT(ep->num); - writel(tmp, &dev->regs->ep_irqmsk); - } - } - } - ep->cancel_transfer = 0; - - } - /* - * status reg has IN bit set and TDC not set (if TDC was handled, - * IN must not be handled (UDC defect) ? - */ - if ((epsts & AMD_BIT(UDC_EPSTS_IN)) - && !(epsts & AMD_BIT(UDC_EPSTS_TDC))) { - ret_val = IRQ_HANDLED; - if (!list_empty(&ep->queue)) { - /* next request */ - req = list_entry(ep->queue.next, - struct udc_request, queue); - /* FIFO mode */ - if (!use_dma) { - /* write fifo */ - udc_txfifo_write(ep, &req->req); - len = req->req.length - req->req.actual; - if (len > ep->ep.maxpacket) - len = ep->ep.maxpacket; - req->req.actual += len; - if (req->req.actual == req->req.length - || (len != ep->ep.maxpacket)) { - /* complete req */ - complete_req(ep, req, 0); - } - /* DMA */ - } else if (req && !req->dma_going) { - VDBG(dev, "IN DMA : req=%p req->td_data=%p\n", - req, req->td_data); - if (req->td_data) { - - req->dma_going = 1; - - /* - * unset L bit of first desc. - * for chain - */ - if (use_dma_ppb && req->req.length > - ep->ep.maxpacket) { - req->td_data->status &= - AMD_CLEAR_BIT( - UDC_DMA_IN_STS_L); - } - - /* write desc pointer */ - writel(req->td_phys, &ep->regs->desptr); - - /* set HOST READY */ - req->td_data->status = - AMD_ADDBITS( - req->td_data->status, - UDC_DMA_IN_STS_BS_HOST_READY, - UDC_DMA_IN_STS_BS); - - /* set poll demand bit */ - tmp = readl(&ep->regs->ctl); - tmp |= AMD_BIT(UDC_EPCTL_P); - writel(tmp, &ep->regs->ctl); - } - } - - } else if (!use_dma && ep->in) { - /* disable interrupt */ - tmp = readl( - &dev->regs->ep_irqmsk); - tmp |= AMD_BIT(ep->num); - writel(tmp, - &dev->regs->ep_irqmsk); - } - } - /* clear status bits */ - writel(epsts, &ep->regs->sts); - -finished: - return ret_val; - -} - -/* Interrupt handler for Control OUT traffic */ -static irqreturn_t udc_control_out_isr(struct udc *dev) -__releases(dev->lock) -__acquires(dev->lock) -{ - irqreturn_t ret_val = IRQ_NONE; - u32 tmp; - int setup_supported; - u32 count; - int set = 0; - struct udc_ep *ep; - struct udc_ep *ep_tmp; - - ep = &dev->ep[UDC_EP0OUT_IX]; - - /* clear irq */ - writel(AMD_BIT(UDC_EPINT_OUT_EP0), &dev->regs->ep_irqsts); - - tmp = readl(&dev->ep[UDC_EP0OUT_IX].regs->sts); - /* check BNA and clear if set */ - if (tmp & AMD_BIT(UDC_EPSTS_BNA)) { - VDBG(dev, "ep0: BNA set\n"); - writel(AMD_BIT(UDC_EPSTS_BNA), - &dev->ep[UDC_EP0OUT_IX].regs->sts); - ep->bna_occurred = 1; - ret_val = IRQ_HANDLED; - goto finished; - } - - /* type of data: SETUP or DATA 0 bytes */ - tmp = AMD_GETBITS(tmp, UDC_EPSTS_OUT); - VDBG(dev, "data_typ = %x\n", tmp); - - /* setup data */ - if (tmp == UDC_EPSTS_OUT_SETUP) { - ret_val = IRQ_HANDLED; - - ep->dev->stall_ep0in = 0; - dev->waiting_zlp_ack_ep0in = 0; - - /* set NAK for EP0_IN */ - tmp = readl(&dev->ep[UDC_EP0IN_IX].regs->ctl); - tmp |= AMD_BIT(UDC_EPCTL_SNAK); - writel(tmp, &dev->ep[UDC_EP0IN_IX].regs->ctl); - dev->ep[UDC_EP0IN_IX].naking = 1; - /* get setup data */ - if (use_dma) { - - /* clear OUT bits in ep status */ - writel(UDC_EPSTS_OUT_CLEAR, - &dev->ep[UDC_EP0OUT_IX].regs->sts); - - setup_data.data[0] = - dev->ep[UDC_EP0OUT_IX].td_stp->data12; - setup_data.data[1] = - dev->ep[UDC_EP0OUT_IX].td_stp->data34; - /* set HOST READY */ - dev->ep[UDC_EP0OUT_IX].td_stp->status = - UDC_DMA_STP_STS_BS_HOST_READY; - } else { - /* read fifo */ - udc_rxfifo_read_dwords(dev, setup_data.data, 2); - } - - /* determine direction of control data */ - if ((setup_data.request.bRequestType & USB_DIR_IN) != 0) { - dev->gadget.ep0 = &dev->ep[UDC_EP0IN_IX].ep; - /* enable RDE */ - udc_ep0_set_rde(dev); - set = 0; - } else { - dev->gadget.ep0 = &dev->ep[UDC_EP0OUT_IX].ep; - /* - * implant BNA dummy descriptor to allow RXFIFO opening - * by RDE - */ - if (ep->bna_dummy_req) { - /* write desc pointer */ - writel(ep->bna_dummy_req->td_phys, - &dev->ep[UDC_EP0OUT_IX].regs->desptr); - ep->bna_occurred = 0; - } - - set = 1; - dev->ep[UDC_EP0OUT_IX].naking = 1; - /* - * setup timer for enabling RDE (to not enable - * RXFIFO DMA for data to early) - */ - set_rde = 1; - if (!timer_pending(&udc_timer)) { - udc_timer.expires = jiffies + - HZ/UDC_RDE_TIMER_DIV; - if (!stop_timer) - add_timer(&udc_timer); - } - } - - /* - * mass storage reset must be processed here because - * next packet may be a CLEAR_FEATURE HALT which would not - * clear the stall bit when no STALL handshake was received - * before (autostall can cause this) - */ - if (setup_data.data[0] == UDC_MSCRES_DWORD0 - && setup_data.data[1] == UDC_MSCRES_DWORD1) { - DBG(dev, "MSC Reset\n"); - /* - * clear stall bits - * only one IN and OUT endpoints are handled - */ - ep_tmp = &udc->ep[UDC_EPIN_IX]; - udc_set_halt(&ep_tmp->ep, 0); - ep_tmp = &udc->ep[UDC_EPOUT_IX]; - udc_set_halt(&ep_tmp->ep, 0); - } - - /* call gadget with setup data received */ - spin_unlock(&dev->lock); - setup_supported = dev->driver->setup(&dev->gadget, - &setup_data.request); - spin_lock(&dev->lock); - - tmp = readl(&dev->ep[UDC_EP0IN_IX].regs->ctl); - /* ep0 in returns data (not zlp) on IN phase */ - if (setup_supported >= 0 && setup_supported < - UDC_EP0IN_MAXPACKET) { - /* clear NAK by writing CNAK in EP0_IN */ - tmp |= AMD_BIT(UDC_EPCTL_CNAK); - writel(tmp, &dev->ep[UDC_EP0IN_IX].regs->ctl); - dev->ep[UDC_EP0IN_IX].naking = 0; - UDC_QUEUE_CNAK(&dev->ep[UDC_EP0IN_IX], UDC_EP0IN_IX); - - /* if unsupported request then stall */ - } else if (setup_supported < 0) { - tmp |= AMD_BIT(UDC_EPCTL_S); - writel(tmp, &dev->ep[UDC_EP0IN_IX].regs->ctl); - } else - dev->waiting_zlp_ack_ep0in = 1; - - - /* clear NAK by writing CNAK in EP0_OUT */ - if (!set) { - tmp = readl(&dev->ep[UDC_EP0OUT_IX].regs->ctl); - tmp |= AMD_BIT(UDC_EPCTL_CNAK); - writel(tmp, &dev->ep[UDC_EP0OUT_IX].regs->ctl); - dev->ep[UDC_EP0OUT_IX].naking = 0; - UDC_QUEUE_CNAK(&dev->ep[UDC_EP0OUT_IX], UDC_EP0OUT_IX); - } - - if (!use_dma) { - /* clear OUT bits in ep status */ - writel(UDC_EPSTS_OUT_CLEAR, - &dev->ep[UDC_EP0OUT_IX].regs->sts); - } - - /* data packet 0 bytes */ - } else if (tmp == UDC_EPSTS_OUT_DATA) { - /* clear OUT bits in ep status */ - writel(UDC_EPSTS_OUT_CLEAR, &dev->ep[UDC_EP0OUT_IX].regs->sts); - - /* get setup data: only 0 packet */ - if (use_dma) { - /* no req if 0 packet, just reactivate */ - if (list_empty(&dev->ep[UDC_EP0OUT_IX].queue)) { - VDBG(dev, "ZLP\n"); - - /* set HOST READY */ - dev->ep[UDC_EP0OUT_IX].td->status = - AMD_ADDBITS( - dev->ep[UDC_EP0OUT_IX].td->status, - UDC_DMA_OUT_STS_BS_HOST_READY, - UDC_DMA_OUT_STS_BS); - /* enable RDE */ - udc_ep0_set_rde(dev); - ret_val = IRQ_HANDLED; - - } else { - /* control write */ - ret_val |= udc_data_out_isr(dev, UDC_EP0OUT_IX); - /* re-program desc. pointer for possible ZLPs */ - writel(dev->ep[UDC_EP0OUT_IX].td_phys, - &dev->ep[UDC_EP0OUT_IX].regs->desptr); - /* enable RDE */ - udc_ep0_set_rde(dev); - } - } else { - - /* received number bytes */ - count = readl(&dev->ep[UDC_EP0OUT_IX].regs->sts); - count = AMD_GETBITS(count, UDC_EPSTS_RX_PKT_SIZE); - /* out data for fifo mode not working */ - count = 0; - - /* 0 packet or real data ? */ - if (count != 0) { - ret_val |= udc_data_out_isr(dev, UDC_EP0OUT_IX); - } else { - /* dummy read confirm */ - readl(&dev->ep[UDC_EP0OUT_IX].regs->confirm); - ret_val = IRQ_HANDLED; - } - } - } - - /* check pending CNAKS */ - if (cnak_pending) { - /* CNAk processing when rxfifo empty only */ - if (readl(&dev->regs->sts) & AMD_BIT(UDC_DEVSTS_RXFIFO_EMPTY)) - udc_process_cnak_queue(dev); - } - -finished: - return ret_val; -} - -/* Interrupt handler for Control IN traffic */ -static irqreturn_t udc_control_in_isr(struct udc *dev) -{ - irqreturn_t ret_val = IRQ_NONE; - u32 tmp; - struct udc_ep *ep; - struct udc_request *req; - unsigned len; - - ep = &dev->ep[UDC_EP0IN_IX]; - - /* clear irq */ - writel(AMD_BIT(UDC_EPINT_IN_EP0), &dev->regs->ep_irqsts); - - tmp = readl(&dev->ep[UDC_EP0IN_IX].regs->sts); - /* DMA completion */ - if (tmp & AMD_BIT(UDC_EPSTS_TDC)) { - VDBG(dev, "isr: TDC clear\n"); - ret_val = IRQ_HANDLED; - - /* clear TDC bit */ - writel(AMD_BIT(UDC_EPSTS_TDC), - &dev->ep[UDC_EP0IN_IX].regs->sts); - - /* status reg has IN bit set ? */ - } else if (tmp & AMD_BIT(UDC_EPSTS_IN)) { - ret_val = IRQ_HANDLED; - - if (ep->dma) { - /* clear IN bit */ - writel(AMD_BIT(UDC_EPSTS_IN), - &dev->ep[UDC_EP0IN_IX].regs->sts); - } - if (dev->stall_ep0in) { - DBG(dev, "stall ep0in\n"); - /* halt ep0in */ - tmp = readl(&ep->regs->ctl); - tmp |= AMD_BIT(UDC_EPCTL_S); - writel(tmp, &ep->regs->ctl); - } else { - if (!list_empty(&ep->queue)) { - /* next request */ - req = list_entry(ep->queue.next, - struct udc_request, queue); - - if (ep->dma) { - /* write desc pointer */ - writel(req->td_phys, &ep->regs->desptr); - /* set HOST READY */ - req->td_data->status = - AMD_ADDBITS( - req->td_data->status, - UDC_DMA_STP_STS_BS_HOST_READY, - UDC_DMA_STP_STS_BS); - - /* set poll demand bit */ - tmp = - readl(&dev->ep[UDC_EP0IN_IX].regs->ctl); - tmp |= AMD_BIT(UDC_EPCTL_P); - writel(tmp, - &dev->ep[UDC_EP0IN_IX].regs->ctl); - - /* all bytes will be transferred */ - req->req.actual = req->req.length; - - /* complete req */ - complete_req(ep, req, 0); - - } else { - /* write fifo */ - udc_txfifo_write(ep, &req->req); - - /* lengh bytes transferred */ - len = req->req.length - req->req.actual; - if (len > ep->ep.maxpacket) - len = ep->ep.maxpacket; - - req->req.actual += len; - if (req->req.actual == req->req.length - || (len != ep->ep.maxpacket)) { - /* complete req */ - complete_req(ep, req, 0); - } - } - - } - } - ep->halted = 0; - dev->stall_ep0in = 0; - if (!ep->dma) { - /* clear IN bit */ - writel(AMD_BIT(UDC_EPSTS_IN), - &dev->ep[UDC_EP0IN_IX].regs->sts); - } - } - - return ret_val; -} - - -/* Interrupt handler for global device events */ -static irqreturn_t udc_dev_isr(struct udc *dev, u32 dev_irq) -__releases(dev->lock) -__acquires(dev->lock) -{ - irqreturn_t ret_val = IRQ_NONE; - u32 tmp; - u32 cfg; - struct udc_ep *ep; - u16 i; - u8 udc_csr_epix; - - /* SET_CONFIG irq ? */ - if (dev_irq & AMD_BIT(UDC_DEVINT_SC)) { - ret_val = IRQ_HANDLED; - - /* read config value */ - tmp = readl(&dev->regs->sts); - cfg = AMD_GETBITS(tmp, UDC_DEVSTS_CFG); - DBG(dev, "SET_CONFIG interrupt: config=%d\n", cfg); - dev->cur_config = cfg; - dev->set_cfg_not_acked = 1; - - /* make usb request for gadget driver */ - memset(&setup_data, 0 , sizeof(union udc_setup_data)); - setup_data.request.bRequest = USB_REQ_SET_CONFIGURATION; - setup_data.request.wValue = cpu_to_le16(dev->cur_config); - - /* programm the NE registers */ - for (i = 0; i < UDC_EP_NUM; i++) { - ep = &dev->ep[i]; - if (ep->in) { - - /* ep ix in UDC CSR register space */ - udc_csr_epix = ep->num; - - - /* OUT ep */ - } else { - /* ep ix in UDC CSR register space */ - udc_csr_epix = ep->num - UDC_CSR_EP_OUT_IX_OFS; - } - - tmp = readl(&dev->csr->ne[udc_csr_epix]); - /* ep cfg */ - tmp = AMD_ADDBITS(tmp, ep->dev->cur_config, - UDC_CSR_NE_CFG); - /* write reg */ - writel(tmp, &dev->csr->ne[udc_csr_epix]); - - /* clear stall bits */ - ep->halted = 0; - tmp = readl(&ep->regs->ctl); - tmp = tmp & AMD_CLEAR_BIT(UDC_EPCTL_S); - writel(tmp, &ep->regs->ctl); - } - /* call gadget zero with setup data received */ - spin_unlock(&dev->lock); - tmp = dev->driver->setup(&dev->gadget, &setup_data.request); - spin_lock(&dev->lock); - - } /* SET_INTERFACE ? */ - if (dev_irq & AMD_BIT(UDC_DEVINT_SI)) { - ret_val = IRQ_HANDLED; - - dev->set_cfg_not_acked = 1; - /* read interface and alt setting values */ - tmp = readl(&dev->regs->sts); - dev->cur_alt = AMD_GETBITS(tmp, UDC_DEVSTS_ALT); - dev->cur_intf = AMD_GETBITS(tmp, UDC_DEVSTS_INTF); - - /* make usb request for gadget driver */ - memset(&setup_data, 0 , sizeof(union udc_setup_data)); - setup_data.request.bRequest = USB_REQ_SET_INTERFACE; - setup_data.request.bRequestType = USB_RECIP_INTERFACE; - setup_data.request.wValue = cpu_to_le16(dev->cur_alt); - setup_data.request.wIndex = cpu_to_le16(dev->cur_intf); - - DBG(dev, "SET_INTERFACE interrupt: alt=%d intf=%d\n", - dev->cur_alt, dev->cur_intf); - - /* programm the NE registers */ - for (i = 0; i < UDC_EP_NUM; i++) { - ep = &dev->ep[i]; - if (ep->in) { - - /* ep ix in UDC CSR register space */ - udc_csr_epix = ep->num; - - - /* OUT ep */ - } else { - /* ep ix in UDC CSR register space */ - udc_csr_epix = ep->num - UDC_CSR_EP_OUT_IX_OFS; - } - - /* UDC CSR reg */ - /* set ep values */ - tmp = readl(&dev->csr->ne[udc_csr_epix]); - /* ep interface */ - tmp = AMD_ADDBITS(tmp, ep->dev->cur_intf, - UDC_CSR_NE_INTF); - /* tmp = AMD_ADDBITS(tmp, 2, UDC_CSR_NE_INTF); */ - /* ep alt */ - tmp = AMD_ADDBITS(tmp, ep->dev->cur_alt, - UDC_CSR_NE_ALT); - /* write reg */ - writel(tmp, &dev->csr->ne[udc_csr_epix]); - - /* clear stall bits */ - ep->halted = 0; - tmp = readl(&ep->regs->ctl); - tmp = tmp & AMD_CLEAR_BIT(UDC_EPCTL_S); - writel(tmp, &ep->regs->ctl); - } - - /* call gadget zero with setup data received */ - spin_unlock(&dev->lock); - tmp = dev->driver->setup(&dev->gadget, &setup_data.request); - spin_lock(&dev->lock); - - } /* USB reset */ - if (dev_irq & AMD_BIT(UDC_DEVINT_UR)) { - DBG(dev, "USB Reset interrupt\n"); - ret_val = IRQ_HANDLED; - - /* allow soft reset when suspend occurs */ - soft_reset_occured = 0; - - dev->waiting_zlp_ack_ep0in = 0; - dev->set_cfg_not_acked = 0; - - /* mask not needed interrupts */ - udc_mask_unused_interrupts(dev); - - /* call gadget to resume and reset configs etc. */ - spin_unlock(&dev->lock); - if (dev->sys_suspended && dev->driver->resume) { - dev->driver->resume(&dev->gadget); - dev->sys_suspended = 0; - } - usb_gadget_udc_reset(&dev->gadget, dev->driver); - spin_lock(&dev->lock); - - /* disable ep0 to empty req queue */ - empty_req_queue(&dev->ep[UDC_EP0IN_IX]); - ep_init(dev->regs, &dev->ep[UDC_EP0IN_IX]); - - /* soft reset when rxfifo not empty */ - tmp = readl(&dev->regs->sts); - if (!(tmp & AMD_BIT(UDC_DEVSTS_RXFIFO_EMPTY)) - && !soft_reset_after_usbreset_occured) { - udc_soft_reset(dev); - soft_reset_after_usbreset_occured++; - } - - /* - * DMA reset to kill potential old DMA hw hang, - * POLL bit is already reset by ep_init() through - * disconnect() - */ - DBG(dev, "DMA machine reset\n"); - tmp = readl(&dev->regs->cfg); - writel(tmp | AMD_BIT(UDC_DEVCFG_DMARST), &dev->regs->cfg); - writel(tmp, &dev->regs->cfg); - - /* put into initial config */ - udc_basic_init(dev); - - /* enable device setup interrupts */ - udc_enable_dev_setup_interrupts(dev); - - /* enable suspend interrupt */ - tmp = readl(&dev->regs->irqmsk); - tmp &= AMD_UNMASK_BIT(UDC_DEVINT_US); - writel(tmp, &dev->regs->irqmsk); - - } /* USB suspend */ - if (dev_irq & AMD_BIT(UDC_DEVINT_US)) { - DBG(dev, "USB Suspend interrupt\n"); - ret_val = IRQ_HANDLED; - if (dev->driver->suspend) { - spin_unlock(&dev->lock); - dev->sys_suspended = 1; - dev->driver->suspend(&dev->gadget); - spin_lock(&dev->lock); - } - } /* new speed ? */ - if (dev_irq & AMD_BIT(UDC_DEVINT_ENUM)) { - DBG(dev, "ENUM interrupt\n"); - ret_val = IRQ_HANDLED; - soft_reset_after_usbreset_occured = 0; - - /* disable ep0 to empty req queue */ - empty_req_queue(&dev->ep[UDC_EP0IN_IX]); - ep_init(dev->regs, &dev->ep[UDC_EP0IN_IX]); - - /* link up all endpoints */ - udc_setup_endpoints(dev); - dev_info(&dev->pdev->dev, "Connect: %s\n", - usb_speed_string(dev->gadget.speed)); - - /* init ep 0 */ - activate_control_endpoints(dev); - - /* enable ep0 interrupts */ - udc_enable_ep0_interrupts(dev); - } - /* session valid change interrupt */ - if (dev_irq & AMD_BIT(UDC_DEVINT_SVC)) { - DBG(dev, "USB SVC interrupt\n"); - ret_val = IRQ_HANDLED; - - /* check that session is not valid to detect disconnect */ - tmp = readl(&dev->regs->sts); - if (!(tmp & AMD_BIT(UDC_DEVSTS_SESSVLD))) { - /* disable suspend interrupt */ - tmp = readl(&dev->regs->irqmsk); - tmp |= AMD_BIT(UDC_DEVINT_US); - writel(tmp, &dev->regs->irqmsk); - DBG(dev, "USB Disconnect (session valid low)\n"); - /* cleanup on disconnect */ - usb_disconnect(udc); - } - - } - - return ret_val; -} - -/* Interrupt Service Routine, see Linux Kernel Doc for parameters */ -irqreturn_t udc_irq(int irq, void *pdev) -{ - struct udc *dev = pdev; - u32 reg; - u16 i; - u32 ep_irq; - irqreturn_t ret_val = IRQ_NONE; - - spin_lock(&dev->lock); - - /* check for ep irq */ - reg = readl(&dev->regs->ep_irqsts); - if (reg) { - if (reg & AMD_BIT(UDC_EPINT_OUT_EP0)) - ret_val |= udc_control_out_isr(dev); - if (reg & AMD_BIT(UDC_EPINT_IN_EP0)) - ret_val |= udc_control_in_isr(dev); - - /* - * data endpoint - * iterate ep's - */ - for (i = 1; i < UDC_EP_NUM; i++) { - ep_irq = 1 << i; - if (!(reg & ep_irq) || i == UDC_EPINT_OUT_EP0) - continue; - - /* clear irq status */ - writel(ep_irq, &dev->regs->ep_irqsts); - - /* irq for out ep ? */ - if (i > UDC_EPIN_NUM) - ret_val |= udc_data_out_isr(dev, i); - else - ret_val |= udc_data_in_isr(dev, i); - } - - } - - - /* check for dev irq */ - reg = readl(&dev->regs->irqsts); - if (reg) { - /* clear irq */ - writel(reg, &dev->regs->irqsts); - ret_val |= udc_dev_isr(dev, reg); - } - - - spin_unlock(&dev->lock); - return ret_val; -} -EXPORT_SYMBOL_GPL(udc_irq); - -/* Tears down device */ -void gadget_release(struct device *pdev) -{ - struct amd5536udc *dev = dev_get_drvdata(pdev); - kfree(dev); -} -EXPORT_SYMBOL_GPL(gadget_release); - -/* Cleanup on device remove */ -void udc_remove(struct udc *dev) -{ - /* remove timer */ - stop_timer++; - if (timer_pending(&udc_timer)) - wait_for_completion(&on_exit); - if (udc_timer.data) - del_timer_sync(&udc_timer); - /* remove pollstall timer */ - stop_pollstall_timer++; - if (timer_pending(&udc_pollstall_timer)) - wait_for_completion(&on_pollstall_exit); - if (udc_pollstall_timer.data) - del_timer_sync(&udc_pollstall_timer); - udc = NULL; -} -EXPORT_SYMBOL_GPL(udc_remove); - -/* free all the dma pools */ -void free_dma_pools(struct udc *dev) -{ - dma_pool_free(dev->stp_requests, dev->ep[UDC_EP0OUT_IX].td, - dev->ep[UDC_EP0OUT_IX].td_phys); - dma_pool_free(dev->stp_requests, dev->ep[UDC_EP0OUT_IX].td_stp, - dev->ep[UDC_EP0OUT_IX].td_stp_dma); - dma_pool_destroy(dev->stp_requests); - dma_pool_destroy(dev->data_requests); -} -EXPORT_SYMBOL_GPL(free_dma_pools); - -/* create dma pools on init */ -int init_dma_pools(struct udc *dev) -{ - struct udc_stp_dma *td_stp; - struct udc_data_dma *td_data; - int retval; - - /* consistent DMA mode setting ? */ - if (use_dma_ppb) { - use_dma_bufferfill_mode = 0; - } else { - use_dma_ppb_du = 0; - use_dma_bufferfill_mode = 1; - } - - /* DMA setup */ - dev->data_requests = dma_pool_create("data_requests", NULL, - sizeof(struct udc_data_dma), 0, 0); - if (!dev->data_requests) { - DBG(dev, "can't get request data pool\n"); - return -ENOMEM; - } - - /* EP0 in dma regs = dev control regs */ - dev->ep[UDC_EP0IN_IX].dma = &dev->regs->ctl; - - /* dma desc for setup data */ - dev->stp_requests = dma_pool_create("setup requests", NULL, - sizeof(struct udc_stp_dma), 0, 0); - if (!dev->stp_requests) { - DBG(dev, "can't get stp request pool\n"); - retval = -ENOMEM; - goto err_create_dma_pool; - } - /* setup */ - td_stp = dma_pool_alloc(dev->stp_requests, GFP_KERNEL, - &dev->ep[UDC_EP0OUT_IX].td_stp_dma); - if (!td_stp) { - retval = -ENOMEM; - goto err_alloc_dma; - } - dev->ep[UDC_EP0OUT_IX].td_stp = td_stp; - - /* data: 0 packets !? */ - td_data = dma_pool_alloc(dev->stp_requests, GFP_KERNEL, - &dev->ep[UDC_EP0OUT_IX].td_phys); - if (!td_data) { - retval = -ENOMEM; - goto err_alloc_phys; - } - dev->ep[UDC_EP0OUT_IX].td = td_data; - return 0; - -err_alloc_phys: - dma_pool_free(dev->stp_requests, dev->ep[UDC_EP0OUT_IX].td_stp, - dev->ep[UDC_EP0OUT_IX].td_stp_dma); -err_alloc_dma: - dma_pool_destroy(dev->stp_requests); - dev->stp_requests = NULL; -err_create_dma_pool: - dma_pool_destroy(dev->data_requests); - dev->data_requests = NULL; - return retval; -} -EXPORT_SYMBOL_GPL(init_dma_pools); - -/* general probe */ -int udc_probe(struct udc *dev) -{ - char tmp[128]; - u32 reg; - int retval; - - /* mark timer as not initialized */ - udc_timer.data = 0; - udc_pollstall_timer.data = 0; - - /* device struct setup */ - dev->gadget.ops = &udc_ops; - - dev_set_name(&dev->gadget.dev, "gadget"); - dev->gadget.name = name; - dev->gadget.max_speed = USB_SPEED_HIGH; - - /* init registers, interrupts, ... */ - startup_registers(dev); - - dev_info(&dev->pdev->dev, "%s\n", mod_desc); - - snprintf(tmp, sizeof(tmp), "%d", dev->irq); - dev_info(&dev->pdev->dev, - "irq %s, pci mem %08lx, chip rev %02x(Geode5536 %s)\n", - tmp, dev->phys_addr, dev->chiprev, - (dev->chiprev == UDC_HSA0_REV) ? "A0" : "B1"); - strcpy(tmp, UDC_DRIVER_VERSION_STRING); - if (dev->chiprev == UDC_HSA0_REV) { - dev_err(&dev->pdev->dev, "chip revision is A0; too old\n"); - retval = -ENODEV; - goto finished; - } - dev_info(&dev->pdev->dev, - "driver version: %s(for Geode5536 B1)\n", tmp); - udc = dev; - - retval = usb_add_gadget_udc_release(&udc->pdev->dev, &dev->gadget, - gadget_release); - if (retval) - goto finished; - - /* timer init */ - init_timer(&udc_timer); - udc_timer.function = udc_timer_function; - udc_timer.data = 1; - /* timer pollstall init */ - init_timer(&udc_pollstall_timer); - udc_pollstall_timer.function = udc_pollstall_timer_function; - udc_pollstall_timer.data = 1; - - /* set SD */ - reg = readl(&dev->regs->ctl); - reg |= AMD_BIT(UDC_DEVCTL_SD); - writel(reg, &dev->regs->ctl); - - /* print dev register info */ - print_regs(dev); - - return 0; - -finished: - return retval; -} -EXPORT_SYMBOL_GPL(udc_probe); - -MODULE_DESCRIPTION(UDC_MOD_DESCRIPTION); -MODULE_AUTHOR("Thomas Dahlmann"); -MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/udc/snps_udc_core.c b/drivers/usb/gadget/udc/snps_udc_core.c new file mode 100644 index 000000000000..4ecd2f20ea48 --- /dev/null +++ b/drivers/usb/gadget/udc/snps_udc_core.c @@ -0,0 +1,3219 @@ +/* + * amd5536.c -- AMD 5536 UDC high/full speed USB device controller + * + * Copyright (C) 2005-2007 AMD (http://www.amd.com) + * Author: Thomas Dahlmann + * + * 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; either version 2 of the License, or + * (at your option) any later version. + */ + +/* + * This file does the core driver implementation for the UDC that is based + * on Synopsys device controller IP (different than HS OTG IP) that is either + * connected through PCI bus or integrated to SoC platforms. + */ + +/* Driver strings */ +#define UDC_MOD_DESCRIPTION "Synopsys USB Device Controller" +#define UDC_DRIVER_VERSION_STRING "01.00.0206" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "amd5536udc.h" + +static void udc_tasklet_disconnect(unsigned long); +static void empty_req_queue(struct udc_ep *); +static void udc_setup_endpoints(struct udc *dev); +static void udc_soft_reset(struct udc *dev); +static struct udc_request *udc_alloc_bna_dummy(struct udc_ep *ep); +static void udc_free_request(struct usb_ep *usbep, struct usb_request *usbreq); + +/* description */ +static const char mod_desc[] = UDC_MOD_DESCRIPTION; +static const char name[] = "udc"; + +/* structure to hold endpoint function pointers */ +static const struct usb_ep_ops udc_ep_ops; + +/* received setup data */ +static union udc_setup_data setup_data; + +/* pointer to device object */ +static struct udc *udc; + +/* irq spin lock for soft reset */ +static DEFINE_SPINLOCK(udc_irq_spinlock); +/* stall spin lock */ +static DEFINE_SPINLOCK(udc_stall_spinlock); + +/* +* slave mode: pending bytes in rx fifo after nyet, +* used if EPIN irq came but no req was available +*/ +static unsigned int udc_rxfifo_pending; + +/* count soft resets after suspend to avoid loop */ +static int soft_reset_occured; +static int soft_reset_after_usbreset_occured; + +/* timer */ +static struct timer_list udc_timer; +static int stop_timer; + +/* set_rde -- Is used to control enabling of RX DMA. Problem is + * that UDC has only one bit (RDE) to enable/disable RX DMA for + * all OUT endpoints. So we have to handle race conditions like + * when OUT data reaches the fifo but no request was queued yet. + * This cannot be solved by letting the RX DMA disabled until a + * request gets queued because there may be other OUT packets + * in the FIFO (important for not blocking control traffic). + * The value of set_rde controls the correspondig timer. + * + * set_rde -1 == not used, means it is alloed to be set to 0 or 1 + * set_rde 0 == do not touch RDE, do no start the RDE timer + * set_rde 1 == timer function will look whether FIFO has data + * set_rde 2 == set by timer function to enable RX DMA on next call + */ +static int set_rde = -1; + +static DECLARE_COMPLETION(on_exit); +static struct timer_list udc_pollstall_timer; +static int stop_pollstall_timer; +static DECLARE_COMPLETION(on_pollstall_exit); + +/* tasklet for usb disconnect */ +static DECLARE_TASKLET(disconnect_tasklet, udc_tasklet_disconnect, + (unsigned long) &udc); + + +/* endpoint names used for print */ +static const char ep0_string[] = "ep0in"; +static const struct { + const char *name; + const struct usb_ep_caps caps; +} ep_info[] = { +#define EP_INFO(_name, _caps) \ + { \ + .name = _name, \ + .caps = _caps, \ + } + + EP_INFO(ep0_string, + USB_EP_CAPS(USB_EP_CAPS_TYPE_CONTROL, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep1in-int", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep2in-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep3in-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep4in-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep5in-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep6in-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep7in-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep8in-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep9in-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep10in-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep11in-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep12in-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep13in-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep14in-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep15in-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_IN)), + EP_INFO("ep0out", + USB_EP_CAPS(USB_EP_CAPS_TYPE_CONTROL, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep1out-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep2out-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep3out-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep4out-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep5out-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep6out-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep7out-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep8out-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep9out-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep10out-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep11out-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep12out-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep13out-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep14out-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), + EP_INFO("ep15out-bulk", + USB_EP_CAPS(USB_EP_CAPS_TYPE_BULK, USB_EP_CAPS_DIR_OUT)), + +#undef EP_INFO +}; + +/* buffer fill mode */ +static int use_dma_bufferfill_mode; +/* tx buffer size for high speed */ +static unsigned long hs_tx_buf = UDC_EPIN_BUFF_SIZE; + +/*---------------------------------------------------------------------------*/ +/* Prints UDC device registers and endpoint irq registers */ +static void print_regs(struct udc *dev) +{ + DBG(dev, "------- Device registers -------\n"); + DBG(dev, "dev config = %08x\n", readl(&dev->regs->cfg)); + DBG(dev, "dev control = %08x\n", readl(&dev->regs->ctl)); + DBG(dev, "dev status = %08x\n", readl(&dev->regs->sts)); + DBG(dev, "\n"); + DBG(dev, "dev int's = %08x\n", readl(&dev->regs->irqsts)); + DBG(dev, "dev intmask = %08x\n", readl(&dev->regs->irqmsk)); + DBG(dev, "\n"); + DBG(dev, "dev ep int's = %08x\n", readl(&dev->regs->ep_irqsts)); + DBG(dev, "dev ep intmask = %08x\n", readl(&dev->regs->ep_irqmsk)); + DBG(dev, "\n"); + DBG(dev, "USE DMA = %d\n", use_dma); + if (use_dma && use_dma_ppb && !use_dma_ppb_du) { + DBG(dev, "DMA mode = PPBNDU (packet per buffer " + "WITHOUT desc. update)\n"); + dev_info(&dev->pdev->dev, "DMA mode (%s)\n", "PPBNDU"); + } else if (use_dma && use_dma_ppb && use_dma_ppb_du) { + DBG(dev, "DMA mode = PPBDU (packet per buffer " + "WITH desc. update)\n"); + dev_info(&dev->pdev->dev, "DMA mode (%s)\n", "PPBDU"); + } + if (use_dma && use_dma_bufferfill_mode) { + DBG(dev, "DMA mode = BF (buffer fill mode)\n"); + dev_info(&dev->pdev->dev, "DMA mode (%s)\n", "BF"); + } + if (!use_dma) + dev_info(&dev->pdev->dev, "FIFO mode\n"); + DBG(dev, "-------------------------------------------------------\n"); +} + +/* Masks unused interrupts */ +int udc_mask_unused_interrupts(struct udc *dev) +{ + u32 tmp; + + /* mask all dev interrupts */ + tmp = AMD_BIT(UDC_DEVINT_SVC) | + AMD_BIT(UDC_DEVINT_ENUM) | + AMD_BIT(UDC_DEVINT_US) | + AMD_BIT(UDC_DEVINT_UR) | + AMD_BIT(UDC_DEVINT_ES) | + AMD_BIT(UDC_DEVINT_SI) | + AMD_BIT(UDC_DEVINT_SOF)| + AMD_BIT(UDC_DEVINT_SC); + writel(tmp, &dev->regs->irqmsk); + + /* mask all ep interrupts */ + writel(UDC_EPINT_MSK_DISABLE_ALL, &dev->regs->ep_irqmsk); + + return 0; +} +EXPORT_SYMBOL_GPL(udc_mask_unused_interrupts); + +/* Enables endpoint 0 interrupts */ +static int udc_enable_ep0_interrupts(struct udc *dev) +{ + u32 tmp; + + DBG(dev, "udc_enable_ep0_interrupts()\n"); + + /* read irq mask */ + tmp = readl(&dev->regs->ep_irqmsk); + /* enable ep0 irq's */ + tmp &= AMD_UNMASK_BIT(UDC_EPINT_IN_EP0) + & AMD_UNMASK_BIT(UDC_EPINT_OUT_EP0); + writel(tmp, &dev->regs->ep_irqmsk); + + return 0; +} + +/* Enables device interrupts for SET_INTF and SET_CONFIG */ +int udc_enable_dev_setup_interrupts(struct udc *dev) +{ + u32 tmp; + + DBG(dev, "enable device interrupts for setup data\n"); + + /* read irq mask */ + tmp = readl(&dev->regs->irqmsk); + + /* enable SET_INTERFACE, SET_CONFIG and other needed irq's */ + tmp &= AMD_UNMASK_BIT(UDC_DEVINT_SI) + & AMD_UNMASK_BIT(UDC_DEVINT_SC) + & AMD_UNMASK_BIT(UDC_DEVINT_UR) + & AMD_UNMASK_BIT(UDC_DEVINT_SVC) + & AMD_UNMASK_BIT(UDC_DEVINT_ENUM); + writel(tmp, &dev->regs->irqmsk); + + return 0; +} +EXPORT_SYMBOL_GPL(udc_enable_dev_setup_interrupts); + +/* Calculates fifo start of endpoint based on preceding endpoints */ +static int udc_set_txfifo_addr(struct udc_ep *ep) +{ + struct udc *dev; + u32 tmp; + int i; + + if (!ep || !(ep->in)) + return -EINVAL; + + dev = ep->dev; + ep->txfifo = dev->txfifo; + + /* traverse ep's */ + for (i = 0; i < ep->num; i++) { + if (dev->ep[i].regs) { + /* read fifo size */ + tmp = readl(&dev->ep[i].regs->bufin_framenum); + tmp = AMD_GETBITS(tmp, UDC_EPIN_BUFF_SIZE); + ep->txfifo += tmp; + } + } + return 0; +} + +/* CNAK pending field: bit0 = ep0in, bit16 = ep0out */ +static u32 cnak_pending; + +static void UDC_QUEUE_CNAK(struct udc_ep *ep, unsigned num) +{ + if (readl(&ep->regs->ctl) & AMD_BIT(UDC_EPCTL_NAK)) { + DBG(ep->dev, "NAK could not be cleared for ep%d\n", num); + cnak_pending |= 1 << (num); + ep->naking = 1; + } else + cnak_pending = cnak_pending & (~(1 << (num))); +} + + +/* Enables endpoint, is called by gadget driver */ +static int +udc_ep_enable(struct usb_ep *usbep, const struct usb_endpoint_descriptor *desc) +{ + struct udc_ep *ep; + struct udc *dev; + u32 tmp; + unsigned long iflags; + u8 udc_csr_epix; + unsigned maxpacket; + + if (!usbep + || usbep->name == ep0_string + || !desc + || desc->bDescriptorType != USB_DT_ENDPOINT) + return -EINVAL; + + ep = container_of(usbep, struct udc_ep, ep); + dev = ep->dev; + + DBG(dev, "udc_ep_enable() ep %d\n", ep->num); + + if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + + spin_lock_irqsave(&dev->lock, iflags); + ep->ep.desc = desc; + + ep->halted = 0; + + /* set traffic type */ + tmp = readl(&dev->ep[ep->num].regs->ctl); + tmp = AMD_ADDBITS(tmp, desc->bmAttributes, UDC_EPCTL_ET); + writel(tmp, &dev->ep[ep->num].regs->ctl); + + /* set max packet size */ + maxpacket = usb_endpoint_maxp(desc); + tmp = readl(&dev->ep[ep->num].regs->bufout_maxpkt); + tmp = AMD_ADDBITS(tmp, maxpacket, UDC_EP_MAX_PKT_SIZE); + ep->ep.maxpacket = maxpacket; + writel(tmp, &dev->ep[ep->num].regs->bufout_maxpkt); + + /* IN ep */ + if (ep->in) { + + /* ep ix in UDC CSR register space */ + udc_csr_epix = ep->num; + + /* set buffer size (tx fifo entries) */ + tmp = readl(&dev->ep[ep->num].regs->bufin_framenum); + /* double buffering: fifo size = 2 x max packet size */ + tmp = AMD_ADDBITS( + tmp, + maxpacket * UDC_EPIN_BUFF_SIZE_MULT + / UDC_DWORD_BYTES, + UDC_EPIN_BUFF_SIZE); + writel(tmp, &dev->ep[ep->num].regs->bufin_framenum); + + /* calc. tx fifo base addr */ + udc_set_txfifo_addr(ep); + + /* flush fifo */ + tmp = readl(&ep->regs->ctl); + tmp |= AMD_BIT(UDC_EPCTL_F); + writel(tmp, &ep->regs->ctl); + + /* OUT ep */ + } else { + /* ep ix in UDC CSR register space */ + udc_csr_epix = ep->num - UDC_CSR_EP_OUT_IX_OFS; + + /* set max packet size UDC CSR */ + tmp = readl(&dev->csr->ne[ep->num - UDC_CSR_EP_OUT_IX_OFS]); + tmp = AMD_ADDBITS(tmp, maxpacket, + UDC_CSR_NE_MAX_PKT); + writel(tmp, &dev->csr->ne[ep->num - UDC_CSR_EP_OUT_IX_OFS]); + + if (use_dma && !ep->in) { + /* alloc and init BNA dummy request */ + ep->bna_dummy_req = udc_alloc_bna_dummy(ep); + ep->bna_occurred = 0; + } + + if (ep->num != UDC_EP0OUT_IX) + dev->data_ep_enabled = 1; + } + + /* set ep values */ + tmp = readl(&dev->csr->ne[udc_csr_epix]); + /* max packet */ + tmp = AMD_ADDBITS(tmp, maxpacket, UDC_CSR_NE_MAX_PKT); + /* ep number */ + tmp = AMD_ADDBITS(tmp, desc->bEndpointAddress, UDC_CSR_NE_NUM); + /* ep direction */ + tmp = AMD_ADDBITS(tmp, ep->in, UDC_CSR_NE_DIR); + /* ep type */ + tmp = AMD_ADDBITS(tmp, desc->bmAttributes, UDC_CSR_NE_TYPE); + /* ep config */ + tmp = AMD_ADDBITS(tmp, ep->dev->cur_config, UDC_CSR_NE_CFG); + /* ep interface */ + tmp = AMD_ADDBITS(tmp, ep->dev->cur_intf, UDC_CSR_NE_INTF); + /* ep alt */ + tmp = AMD_ADDBITS(tmp, ep->dev->cur_alt, UDC_CSR_NE_ALT); + /* write reg */ + writel(tmp, &dev->csr->ne[udc_csr_epix]); + + /* enable ep irq */ + tmp = readl(&dev->regs->ep_irqmsk); + tmp &= AMD_UNMASK_BIT(ep->num); + writel(tmp, &dev->regs->ep_irqmsk); + + /* + * clear NAK by writing CNAK + * avoid BNA for OUT DMA, don't clear NAK until DMA desc. written + */ + if (!use_dma || ep->in) { + tmp = readl(&ep->regs->ctl); + tmp |= AMD_BIT(UDC_EPCTL_CNAK); + writel(tmp, &ep->regs->ctl); + ep->naking = 0; + UDC_QUEUE_CNAK(ep, ep->num); + } + tmp = desc->bEndpointAddress; + DBG(dev, "%s enabled\n", usbep->name); + + spin_unlock_irqrestore(&dev->lock, iflags); + return 0; +} + +/* Resets endpoint */ +static void ep_init(struct udc_regs __iomem *regs, struct udc_ep *ep) +{ + u32 tmp; + + VDBG(ep->dev, "ep-%d reset\n", ep->num); + ep->ep.desc = NULL; + ep->ep.ops = &udc_ep_ops; + INIT_LIST_HEAD(&ep->queue); + + usb_ep_set_maxpacket_limit(&ep->ep,(u16) ~0); + /* set NAK */ + tmp = readl(&ep->regs->ctl); + tmp |= AMD_BIT(UDC_EPCTL_SNAK); + writel(tmp, &ep->regs->ctl); + ep->naking = 1; + + /* disable interrupt */ + tmp = readl(®s->ep_irqmsk); + tmp |= AMD_BIT(ep->num); + writel(tmp, ®s->ep_irqmsk); + + if (ep->in) { + /* unset P and IN bit of potential former DMA */ + tmp = readl(&ep->regs->ctl); + tmp &= AMD_UNMASK_BIT(UDC_EPCTL_P); + writel(tmp, &ep->regs->ctl); + + tmp = readl(&ep->regs->sts); + tmp |= AMD_BIT(UDC_EPSTS_IN); + writel(tmp, &ep->regs->sts); + + /* flush the fifo */ + tmp = readl(&ep->regs->ctl); + tmp |= AMD_BIT(UDC_EPCTL_F); + writel(tmp, &ep->regs->ctl); + + } + /* reset desc pointer */ + writel(0, &ep->regs->desptr); +} + +/* Disables endpoint, is called by gadget driver */ +static int udc_ep_disable(struct usb_ep *usbep) +{ + struct udc_ep *ep = NULL; + unsigned long iflags; + + if (!usbep) + return -EINVAL; + + ep = container_of(usbep, struct udc_ep, ep); + if (usbep->name == ep0_string || !ep->ep.desc) + return -EINVAL; + + DBG(ep->dev, "Disable ep-%d\n", ep->num); + + spin_lock_irqsave(&ep->dev->lock, iflags); + udc_free_request(&ep->ep, &ep->bna_dummy_req->req); + empty_req_queue(ep); + ep_init(ep->dev->regs, ep); + spin_unlock_irqrestore(&ep->dev->lock, iflags); + + return 0; +} + +/* Allocates request packet, called by gadget driver */ +static struct usb_request * +udc_alloc_request(struct usb_ep *usbep, gfp_t gfp) +{ + struct udc_request *req; + struct udc_data_dma *dma_desc; + struct udc_ep *ep; + + if (!usbep) + return NULL; + + ep = container_of(usbep, struct udc_ep, ep); + + VDBG(ep->dev, "udc_alloc_req(): ep%d\n", ep->num); + req = kzalloc(sizeof(struct udc_request), gfp); + if (!req) + return NULL; + + req->req.dma = DMA_DONT_USE; + INIT_LIST_HEAD(&req->queue); + + if (ep->dma) { + /* ep0 in requests are allocated from data pool here */ + dma_desc = dma_pool_alloc(ep->dev->data_requests, gfp, + &req->td_phys); + if (!dma_desc) { + kfree(req); + return NULL; + } + + VDBG(ep->dev, "udc_alloc_req: req = %p dma_desc = %p, " + "td_phys = %lx\n", + req, dma_desc, + (unsigned long)req->td_phys); + /* prevent from using desc. - set HOST BUSY */ + dma_desc->status = AMD_ADDBITS(dma_desc->status, + UDC_DMA_STP_STS_BS_HOST_BUSY, + UDC_DMA_STP_STS_BS); + dma_desc->bufptr = cpu_to_le32(DMA_DONT_USE); + req->td_data = dma_desc; + req->td_data_last = NULL; + req->chain_len = 1; + } + + return &req->req; +} + +/* frees pci pool descriptors of a DMA chain */ +static void udc_free_dma_chain(struct udc *dev, struct udc_request *req) +{ + struct udc_data_dma *td = req->td_data; + unsigned int i; + + dma_addr_t addr_next = 0x00; + dma_addr_t addr = (dma_addr_t)td->next; + + DBG(dev, "free chain req = %p\n", req); + + /* do not free first desc., will be done by free for request */ + for (i = 1; i < req->chain_len; i++) { + td = phys_to_virt(addr); + addr_next = (dma_addr_t)td->next; + dma_pool_free(dev->data_requests, td, addr); + addr = addr_next; + } +} + +/* Frees request packet, called by gadget driver */ +static void +udc_free_request(struct usb_ep *usbep, struct usb_request *usbreq) +{ + struct udc_ep *ep; + struct udc_request *req; + + if (!usbep || !usbreq) + return; + + ep = container_of(usbep, struct udc_ep, ep); + req = container_of(usbreq, struct udc_request, req); + VDBG(ep->dev, "free_req req=%p\n", req); + BUG_ON(!list_empty(&req->queue)); + if (req->td_data) { + VDBG(ep->dev, "req->td_data=%p\n", req->td_data); + + /* free dma chain if created */ + if (req->chain_len > 1) + udc_free_dma_chain(ep->dev, req); + + dma_pool_free(ep->dev->data_requests, req->td_data, + req->td_phys); + } + kfree(req); +} + +/* Init BNA dummy descriptor for HOST BUSY and pointing to itself */ +static void udc_init_bna_dummy(struct udc_request *req) +{ + if (req) { + /* set last bit */ + req->td_data->status |= AMD_BIT(UDC_DMA_IN_STS_L); + /* set next pointer to itself */ + req->td_data->next = req->td_phys; + /* set HOST BUSY */ + req->td_data->status + = AMD_ADDBITS(req->td_data->status, + UDC_DMA_STP_STS_BS_DMA_DONE, + UDC_DMA_STP_STS_BS); +#ifdef UDC_VERBOSE + pr_debug("bna desc = %p, sts = %08x\n", + req->td_data, req->td_data->status); +#endif + } +} + +/* Allocate BNA dummy descriptor */ +static struct udc_request *udc_alloc_bna_dummy(struct udc_ep *ep) +{ + struct udc_request *req = NULL; + struct usb_request *_req = NULL; + + /* alloc the dummy request */ + _req = udc_alloc_request(&ep->ep, GFP_ATOMIC); + if (_req) { + req = container_of(_req, struct udc_request, req); + ep->bna_dummy_req = req; + udc_init_bna_dummy(req); + } + return req; +} + +/* Write data to TX fifo for IN packets */ +static void +udc_txfifo_write(struct udc_ep *ep, struct usb_request *req) +{ + u8 *req_buf; + u32 *buf; + int i, j; + unsigned bytes = 0; + unsigned remaining = 0; + + if (!req || !ep) + return; + + req_buf = req->buf + req->actual; + prefetch(req_buf); + remaining = req->length - req->actual; + + buf = (u32 *) req_buf; + + bytes = ep->ep.maxpacket; + if (bytes > remaining) + bytes = remaining; + + /* dwords first */ + for (i = 0; i < bytes / UDC_DWORD_BYTES; i++) + writel(*(buf + i), ep->txfifo); + + /* remaining bytes must be written by byte access */ + for (j = 0; j < bytes % UDC_DWORD_BYTES; j++) { + writeb((u8)(*(buf + i) >> (j << UDC_BITS_PER_BYTE_SHIFT)), + ep->txfifo); + } + + /* dummy write confirm */ + writel(0, &ep->regs->confirm); +} + +/* Read dwords from RX fifo for OUT transfers */ +static int udc_rxfifo_read_dwords(struct udc *dev, u32 *buf, int dwords) +{ + int i; + + VDBG(dev, "udc_read_dwords(): %d dwords\n", dwords); + + for (i = 0; i < dwords; i++) + *(buf + i) = readl(dev->rxfifo); + return 0; +} + +/* Read bytes from RX fifo for OUT transfers */ +static int udc_rxfifo_read_bytes(struct udc *dev, u8 *buf, int bytes) +{ + int i, j; + u32 tmp; + + VDBG(dev, "udc_read_bytes(): %d bytes\n", bytes); + + /* dwords first */ + for (i = 0; i < bytes / UDC_DWORD_BYTES; i++) + *((u32 *)(buf + (i<<2))) = readl(dev->rxfifo); + + /* remaining bytes must be read by byte access */ + if (bytes % UDC_DWORD_BYTES) { + tmp = readl(dev->rxfifo); + for (j = 0; j < bytes % UDC_DWORD_BYTES; j++) { + *(buf + (i<<2) + j) = (u8)(tmp & UDC_BYTE_MASK); + tmp = tmp >> UDC_BITS_PER_BYTE; + } + } + + return 0; +} + +/* Read data from RX fifo for OUT transfers */ +static int +udc_rxfifo_read(struct udc_ep *ep, struct udc_request *req) +{ + u8 *buf; + unsigned buf_space; + unsigned bytes = 0; + unsigned finished = 0; + + /* received number bytes */ + bytes = readl(&ep->regs->sts); + bytes = AMD_GETBITS(bytes, UDC_EPSTS_RX_PKT_SIZE); + + buf_space = req->req.length - req->req.actual; + buf = req->req.buf + req->req.actual; + if (bytes > buf_space) { + if ((buf_space % ep->ep.maxpacket) != 0) { + DBG(ep->dev, + "%s: rx %d bytes, rx-buf space = %d bytesn\n", + ep->ep.name, bytes, buf_space); + req->req.status = -EOVERFLOW; + } + bytes = buf_space; + } + req->req.actual += bytes; + + /* last packet ? */ + if (((bytes % ep->ep.maxpacket) != 0) || (!bytes) + || ((req->req.actual == req->req.length) && !req->req.zero)) + finished = 1; + + /* read rx fifo bytes */ + VDBG(ep->dev, "ep %s: rxfifo read %d bytes\n", ep->ep.name, bytes); + udc_rxfifo_read_bytes(ep->dev, buf, bytes); + + return finished; +} + +/* Creates or re-inits a DMA chain */ +static int udc_create_dma_chain( + struct udc_ep *ep, + struct udc_request *req, + unsigned long buf_len, gfp_t gfp_flags +) +{ + unsigned long bytes = req->req.length; + unsigned int i; + dma_addr_t dma_addr; + struct udc_data_dma *td = NULL; + struct udc_data_dma *last = NULL; + unsigned long txbytes; + unsigned create_new_chain = 0; + unsigned len; + + VDBG(ep->dev, "udc_create_dma_chain: bytes=%ld buf_len=%ld\n", + bytes, buf_len); + dma_addr = DMA_DONT_USE; + + /* unset L bit in first desc for OUT */ + if (!ep->in) + req->td_data->status &= AMD_CLEAR_BIT(UDC_DMA_IN_STS_L); + + /* alloc only new desc's if not already available */ + len = req->req.length / ep->ep.maxpacket; + if (req->req.length % ep->ep.maxpacket) + len++; + + if (len > req->chain_len) { + /* shorter chain already allocated before */ + if (req->chain_len > 1) + udc_free_dma_chain(ep->dev, req); + req->chain_len = len; + create_new_chain = 1; + } + + td = req->td_data; + /* gen. required number of descriptors and buffers */ + for (i = buf_len; i < bytes; i += buf_len) { + /* create or determine next desc. */ + if (create_new_chain) { + td = dma_pool_alloc(ep->dev->data_requests, + gfp_flags, &dma_addr); + if (!td) + return -ENOMEM; + + td->status = 0; + } else if (i == buf_len) { + /* first td */ + td = (struct udc_data_dma *)phys_to_virt( + req->td_data->next); + td->status = 0; + } else { + td = (struct udc_data_dma *)phys_to_virt(last->next); + td->status = 0; + } + + if (td) + td->bufptr = req->req.dma + i; /* assign buffer */ + else + break; + + /* short packet ? */ + if ((bytes - i) >= buf_len) { + txbytes = buf_len; + } else { + /* short packet */ + txbytes = bytes - i; + } + + /* link td and assign tx bytes */ + if (i == buf_len) { + if (create_new_chain) + req->td_data->next = dma_addr; + /* + * else + * req->td_data->next = virt_to_phys(td); + */ + /* write tx bytes */ + if (ep->in) { + /* first desc */ + req->td_data->status = + AMD_ADDBITS(req->td_data->status, + ep->ep.maxpacket, + UDC_DMA_IN_STS_TXBYTES); + /* second desc */ + td->status = AMD_ADDBITS(td->status, + txbytes, + UDC_DMA_IN_STS_TXBYTES); + } + } else { + if (create_new_chain) + last->next = dma_addr; + /* + * else + * last->next = virt_to_phys(td); + */ + if (ep->in) { + /* write tx bytes */ + td->status = AMD_ADDBITS(td->status, + txbytes, + UDC_DMA_IN_STS_TXBYTES); + } + } + last = td; + } + /* set last bit */ + if (td) { + td->status |= AMD_BIT(UDC_DMA_IN_STS_L); + /* last desc. points to itself */ + req->td_data_last = td; + } + + return 0; +} + +/* create/re-init a DMA descriptor or a DMA descriptor chain */ +static int prep_dma(struct udc_ep *ep, struct udc_request *req, gfp_t gfp) +{ + int retval = 0; + u32 tmp; + + VDBG(ep->dev, "prep_dma\n"); + VDBG(ep->dev, "prep_dma ep%d req->td_data=%p\n", + ep->num, req->td_data); + + /* set buffer pointer */ + req->td_data->bufptr = req->req.dma; + + /* set last bit */ + req->td_data->status |= AMD_BIT(UDC_DMA_IN_STS_L); + + /* build/re-init dma chain if maxpkt scatter mode, not for EP0 */ + if (use_dma_ppb) { + + retval = udc_create_dma_chain(ep, req, ep->ep.maxpacket, gfp); + if (retval != 0) { + if (retval == -ENOMEM) + DBG(ep->dev, "Out of DMA memory\n"); + return retval; + } + if (ep->in) { + if (req->req.length == ep->ep.maxpacket) { + /* write tx bytes */ + req->td_data->status = + AMD_ADDBITS(req->td_data->status, + ep->ep.maxpacket, + UDC_DMA_IN_STS_TXBYTES); + + } + } + + } + + if (ep->in) { + VDBG(ep->dev, "IN: use_dma_ppb=%d req->req.len=%d " + "maxpacket=%d ep%d\n", + use_dma_ppb, req->req.length, + ep->ep.maxpacket, ep->num); + /* + * if bytes < max packet then tx bytes must + * be written in packet per buffer mode + */ + if (!use_dma_ppb || req->req.length < ep->ep.maxpacket + || ep->num == UDC_EP0OUT_IX + || ep->num == UDC_EP0IN_IX) { + /* write tx bytes */ + req->td_data->status = + AMD_ADDBITS(req->td_data->status, + req->req.length, + UDC_DMA_IN_STS_TXBYTES); + /* reset frame num */ + req->td_data->status = + AMD_ADDBITS(req->td_data->status, + 0, + UDC_DMA_IN_STS_FRAMENUM); + } + /* set HOST BUSY */ + req->td_data->status = + AMD_ADDBITS(req->td_data->status, + UDC_DMA_STP_STS_BS_HOST_BUSY, + UDC_DMA_STP_STS_BS); + } else { + VDBG(ep->dev, "OUT set host ready\n"); + /* set HOST READY */ + req->td_data->status = + AMD_ADDBITS(req->td_data->status, + UDC_DMA_STP_STS_BS_HOST_READY, + UDC_DMA_STP_STS_BS); + + + /* clear NAK by writing CNAK */ + if (ep->naking) { + tmp = readl(&ep->regs->ctl); + tmp |= AMD_BIT(UDC_EPCTL_CNAK); + writel(tmp, &ep->regs->ctl); + ep->naking = 0; + UDC_QUEUE_CNAK(ep, ep->num); + } + + } + + return retval; +} + +/* Completes request packet ... caller MUST hold lock */ +static void +complete_req(struct udc_ep *ep, struct udc_request *req, int sts) +__releases(ep->dev->lock) +__acquires(ep->dev->lock) +{ + struct udc *dev; + unsigned halted; + + VDBG(ep->dev, "complete_req(): ep%d\n", ep->num); + + dev = ep->dev; + /* unmap DMA */ + if (ep->dma) + usb_gadget_unmap_request(&dev->gadget, &req->req, ep->in); + + halted = ep->halted; + ep->halted = 1; + + /* set new status if pending */ + if (req->req.status == -EINPROGRESS) + req->req.status = sts; + + /* remove from ep queue */ + list_del_init(&req->queue); + + VDBG(ep->dev, "req %p => complete %d bytes at %s with sts %d\n", + &req->req, req->req.length, ep->ep.name, sts); + + spin_unlock(&dev->lock); + usb_gadget_giveback_request(&ep->ep, &req->req); + spin_lock(&dev->lock); + ep->halted = halted; +} + +/* Iterates to the end of a DMA chain and returns last descriptor */ +static struct udc_data_dma *udc_get_last_dma_desc(struct udc_request *req) +{ + struct udc_data_dma *td; + + td = req->td_data; + while (td && !(td->status & AMD_BIT(UDC_DMA_IN_STS_L))) + td = phys_to_virt(td->next); + + return td; + +} + +/* Iterates to the end of a DMA chain and counts bytes received */ +static u32 udc_get_ppbdu_rxbytes(struct udc_request *req) +{ + struct udc_data_dma *td; + u32 count; + + td = req->td_data; + /* received number bytes */ + count = AMD_GETBITS(td->status, UDC_DMA_OUT_STS_RXBYTES); + + while (td && !(td->status & AMD_BIT(UDC_DMA_IN_STS_L))) { + td = phys_to_virt(td->next); + /* received number bytes */ + if (td) { + count += AMD_GETBITS(td->status, + UDC_DMA_OUT_STS_RXBYTES); + } + } + + return count; + +} + +/* Enabling RX DMA */ +static void udc_set_rde(struct udc *dev) +{ + u32 tmp; + + VDBG(dev, "udc_set_rde()\n"); + /* stop RDE timer */ + if (timer_pending(&udc_timer)) { + set_rde = 0; + mod_timer(&udc_timer, jiffies - 1); + } + /* set RDE */ + tmp = readl(&dev->regs->ctl); + tmp |= AMD_BIT(UDC_DEVCTL_RDE); + writel(tmp, &dev->regs->ctl); +} + +/* Queues a request packet, called by gadget driver */ +static int +udc_queue(struct usb_ep *usbep, struct usb_request *usbreq, gfp_t gfp) +{ + int retval = 0; + u8 open_rxfifo = 0; + unsigned long iflags; + struct udc_ep *ep; + struct udc_request *req; + struct udc *dev; + u32 tmp; + + /* check the inputs */ + req = container_of(usbreq, struct udc_request, req); + + if (!usbep || !usbreq || !usbreq->complete || !usbreq->buf + || !list_empty(&req->queue)) + return -EINVAL; + + ep = container_of(usbep, struct udc_ep, ep); + if (!ep->ep.desc && (ep->num != 0 && ep->num != UDC_EP0OUT_IX)) + return -EINVAL; + + VDBG(ep->dev, "udc_queue(): ep%d-in=%d\n", ep->num, ep->in); + dev = ep->dev; + + if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + + /* map dma (usually done before) */ + if (ep->dma) { + VDBG(dev, "DMA map req %p\n", req); + retval = usb_gadget_map_request(&udc->gadget, usbreq, ep->in); + if (retval) + return retval; + } + + VDBG(dev, "%s queue req %p, len %d req->td_data=%p buf %p\n", + usbep->name, usbreq, usbreq->length, + req->td_data, usbreq->buf); + + spin_lock_irqsave(&dev->lock, iflags); + usbreq->actual = 0; + usbreq->status = -EINPROGRESS; + req->dma_done = 0; + + /* on empty queue just do first transfer */ + if (list_empty(&ep->queue)) { + /* zlp */ + if (usbreq->length == 0) { + /* IN zlp's are handled by hardware */ + complete_req(ep, req, 0); + VDBG(dev, "%s: zlp\n", ep->ep.name); + /* + * if set_config or set_intf is waiting for ack by zlp + * then set CSR_DONE + */ + if (dev->set_cfg_not_acked) { + tmp = readl(&dev->regs->ctl); + tmp |= AMD_BIT(UDC_DEVCTL_CSR_DONE); + writel(tmp, &dev->regs->ctl); + dev->set_cfg_not_acked = 0; + } + /* setup command is ACK'ed now by zlp */ + if (dev->waiting_zlp_ack_ep0in) { + /* clear NAK by writing CNAK in EP0_IN */ + tmp = readl(&dev->ep[UDC_EP0IN_IX].regs->ctl); + tmp |= AMD_BIT(UDC_EPCTL_CNAK); + writel(tmp, &dev->ep[UDC_EP0IN_IX].regs->ctl); + dev->ep[UDC_EP0IN_IX].naking = 0; + UDC_QUEUE_CNAK(&dev->ep[UDC_EP0IN_IX], + UDC_EP0IN_IX); + dev->waiting_zlp_ack_ep0in = 0; + } + goto finished; + } + if (ep->dma) { + retval = prep_dma(ep, req, GFP_ATOMIC); + if (retval != 0) + goto finished; + /* write desc pointer to enable DMA */ + if (ep->in) { + /* set HOST READY */ + req->td_data->status = + AMD_ADDBITS(req->td_data->status, + UDC_DMA_IN_STS_BS_HOST_READY, + UDC_DMA_IN_STS_BS); + } + + /* disabled rx dma while descriptor update */ + if (!ep->in) { + /* stop RDE timer */ + if (timer_pending(&udc_timer)) { + set_rde = 0; + mod_timer(&udc_timer, jiffies - 1); + } + /* clear RDE */ + tmp = readl(&dev->regs->ctl); + tmp &= AMD_UNMASK_BIT(UDC_DEVCTL_RDE); + writel(tmp, &dev->regs->ctl); + open_rxfifo = 1; + + /* + * if BNA occurred then let BNA dummy desc. + * point to current desc. + */ + if (ep->bna_occurred) { + VDBG(dev, "copy to BNA dummy desc.\n"); + memcpy(ep->bna_dummy_req->td_data, + req->td_data, + sizeof(struct udc_data_dma)); + } + } + /* write desc pointer */ + writel(req->td_phys, &ep->regs->desptr); + + /* clear NAK by writing CNAK */ + if (ep->naking) { + tmp = readl(&ep->regs->ctl); + tmp |= AMD_BIT(UDC_EPCTL_CNAK); + writel(tmp, &ep->regs->ctl); + ep->naking = 0; + UDC_QUEUE_CNAK(ep, ep->num); + } + + if (ep->in) { + /* enable ep irq */ + tmp = readl(&dev->regs->ep_irqmsk); + tmp &= AMD_UNMASK_BIT(ep->num); + writel(tmp, &dev->regs->ep_irqmsk); + } + } else if (ep->in) { + /* enable ep irq */ + tmp = readl(&dev->regs->ep_irqmsk); + tmp &= AMD_UNMASK_BIT(ep->num); + writel(tmp, &dev->regs->ep_irqmsk); + } + + } else if (ep->dma) { + + /* + * prep_dma not used for OUT ep's, this is not possible + * for PPB modes, because of chain creation reasons + */ + if (ep->in) { + retval = prep_dma(ep, req, GFP_ATOMIC); + if (retval != 0) + goto finished; + } + } + VDBG(dev, "list_add\n"); + /* add request to ep queue */ + if (req) { + + list_add_tail(&req->queue, &ep->queue); + + /* open rxfifo if out data queued */ + if (open_rxfifo) { + /* enable DMA */ + req->dma_going = 1; + udc_set_rde(dev); + if (ep->num != UDC_EP0OUT_IX) + dev->data_ep_queued = 1; + } + /* stop OUT naking */ + if (!ep->in) { + if (!use_dma && udc_rxfifo_pending) { + DBG(dev, "udc_queue(): pending bytes in " + "rxfifo after nyet\n"); + /* + * read pending bytes afer nyet: + * referring to isr + */ + if (udc_rxfifo_read(ep, req)) { + /* finish */ + complete_req(ep, req, 0); + } + udc_rxfifo_pending = 0; + + } + } + } + +finished: + spin_unlock_irqrestore(&dev->lock, iflags); + return retval; +} + +/* Empty request queue of an endpoint; caller holds spinlock */ +static void empty_req_queue(struct udc_ep *ep) +{ + struct udc_request *req; + + ep->halted = 1; + while (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, + struct udc_request, + queue); + complete_req(ep, req, -ESHUTDOWN); + } +} + +/* Dequeues a request packet, called by gadget driver */ +static int udc_dequeue(struct usb_ep *usbep, struct usb_request *usbreq) +{ + struct udc_ep *ep; + struct udc_request *req; + unsigned halted; + unsigned long iflags; + + ep = container_of(usbep, struct udc_ep, ep); + if (!usbep || !usbreq || (!ep->ep.desc && (ep->num != 0 + && ep->num != UDC_EP0OUT_IX))) + return -EINVAL; + + req = container_of(usbreq, struct udc_request, req); + + spin_lock_irqsave(&ep->dev->lock, iflags); + halted = ep->halted; + ep->halted = 1; + /* request in processing or next one */ + if (ep->queue.next == &req->queue) { + if (ep->dma && req->dma_going) { + if (ep->in) + ep->cancel_transfer = 1; + else { + u32 tmp; + u32 dma_sts; + /* stop potential receive DMA */ + tmp = readl(&udc->regs->ctl); + writel(tmp & AMD_UNMASK_BIT(UDC_DEVCTL_RDE), + &udc->regs->ctl); + /* + * Cancel transfer later in ISR + * if descriptor was touched. + */ + dma_sts = AMD_GETBITS(req->td_data->status, + UDC_DMA_OUT_STS_BS); + if (dma_sts != UDC_DMA_OUT_STS_BS_HOST_READY) + ep->cancel_transfer = 1; + else { + udc_init_bna_dummy(ep->req); + writel(ep->bna_dummy_req->td_phys, + &ep->regs->desptr); + } + writel(tmp, &udc->regs->ctl); + } + } + } + complete_req(ep, req, -ECONNRESET); + ep->halted = halted; + + spin_unlock_irqrestore(&ep->dev->lock, iflags); + return 0; +} + +/* Halt or clear halt of endpoint */ +static int +udc_set_halt(struct usb_ep *usbep, int halt) +{ + struct udc_ep *ep; + u32 tmp; + unsigned long iflags; + int retval = 0; + + if (!usbep) + return -EINVAL; + + pr_debug("set_halt %s: halt=%d\n", usbep->name, halt); + + ep = container_of(usbep, struct udc_ep, ep); + if (!ep->ep.desc && (ep->num != 0 && ep->num != UDC_EP0OUT_IX)) + return -EINVAL; + if (!ep->dev->driver || ep->dev->gadget.speed == USB_SPEED_UNKNOWN) + return -ESHUTDOWN; + + spin_lock_irqsave(&udc_stall_spinlock, iflags); + /* halt or clear halt */ + if (halt) { + if (ep->num == 0) + ep->dev->stall_ep0in = 1; + else { + /* + * set STALL + * rxfifo empty not taken into acount + */ + tmp = readl(&ep->regs->ctl); + tmp |= AMD_BIT(UDC_EPCTL_S); + writel(tmp, &ep->regs->ctl); + ep->halted = 1; + + /* setup poll timer */ + if (!timer_pending(&udc_pollstall_timer)) { + udc_pollstall_timer.expires = jiffies + + HZ * UDC_POLLSTALL_TIMER_USECONDS + / (1000 * 1000); + if (!stop_pollstall_timer) { + DBG(ep->dev, "start polltimer\n"); + add_timer(&udc_pollstall_timer); + } + } + } + } else { + /* ep is halted by set_halt() before */ + if (ep->halted) { + tmp = readl(&ep->regs->ctl); + /* clear stall bit */ + tmp = tmp & AMD_CLEAR_BIT(UDC_EPCTL_S); + /* clear NAK by writing CNAK */ + tmp |= AMD_BIT(UDC_EPCTL_CNAK); + writel(tmp, &ep->regs->ctl); + ep->halted = 0; + UDC_QUEUE_CNAK(ep, ep->num); + } + } + spin_unlock_irqrestore(&udc_stall_spinlock, iflags); + return retval; +} + +/* gadget interface */ +static const struct usb_ep_ops udc_ep_ops = { + .enable = udc_ep_enable, + .disable = udc_ep_disable, + + .alloc_request = udc_alloc_request, + .free_request = udc_free_request, + + .queue = udc_queue, + .dequeue = udc_dequeue, + + .set_halt = udc_set_halt, + /* fifo ops not implemented */ +}; + +/*-------------------------------------------------------------------------*/ + +/* Get frame counter (not implemented) */ +static int udc_get_frame(struct usb_gadget *gadget) +{ + return -EOPNOTSUPP; +} + +/* Initiates a remote wakeup */ +static int udc_remote_wakeup(struct udc *dev) +{ + unsigned long flags; + u32 tmp; + + DBG(dev, "UDC initiates remote wakeup\n"); + + spin_lock_irqsave(&dev->lock, flags); + + tmp = readl(&dev->regs->ctl); + tmp |= AMD_BIT(UDC_DEVCTL_RES); + writel(tmp, &dev->regs->ctl); + tmp &= AMD_CLEAR_BIT(UDC_DEVCTL_RES); + writel(tmp, &dev->regs->ctl); + + spin_unlock_irqrestore(&dev->lock, flags); + return 0; +} + +/* Remote wakeup gadget interface */ +static int udc_wakeup(struct usb_gadget *gadget) +{ + struct udc *dev; + + if (!gadget) + return -EINVAL; + dev = container_of(gadget, struct udc, gadget); + udc_remote_wakeup(dev); + + return 0; +} + +static int amd5536_udc_start(struct usb_gadget *g, + struct usb_gadget_driver *driver); +static int amd5536_udc_stop(struct usb_gadget *g); + +static const struct usb_gadget_ops udc_ops = { + .wakeup = udc_wakeup, + .get_frame = udc_get_frame, + .udc_start = amd5536_udc_start, + .udc_stop = amd5536_udc_stop, +}; + +/* Setups endpoint parameters, adds endpoints to linked list */ +static void make_ep_lists(struct udc *dev) +{ + /* make gadget ep lists */ + INIT_LIST_HEAD(&dev->gadget.ep_list); + list_add_tail(&dev->ep[UDC_EPIN_STATUS_IX].ep.ep_list, + &dev->gadget.ep_list); + list_add_tail(&dev->ep[UDC_EPIN_IX].ep.ep_list, + &dev->gadget.ep_list); + list_add_tail(&dev->ep[UDC_EPOUT_IX].ep.ep_list, + &dev->gadget.ep_list); + + /* fifo config */ + dev->ep[UDC_EPIN_STATUS_IX].fifo_depth = UDC_EPIN_SMALLINT_BUFF_SIZE; + if (dev->gadget.speed == USB_SPEED_FULL) + dev->ep[UDC_EPIN_IX].fifo_depth = UDC_FS_EPIN_BUFF_SIZE; + else if (dev->gadget.speed == USB_SPEED_HIGH) + dev->ep[UDC_EPIN_IX].fifo_depth = hs_tx_buf; + dev->ep[UDC_EPOUT_IX].fifo_depth = UDC_RXFIFO_SIZE; +} + +/* Inits UDC context */ +void udc_basic_init(struct udc *dev) +{ + u32 tmp; + + DBG(dev, "udc_basic_init()\n"); + + dev->gadget.speed = USB_SPEED_UNKNOWN; + + /* stop RDE timer */ + if (timer_pending(&udc_timer)) { + set_rde = 0; + mod_timer(&udc_timer, jiffies - 1); + } + /* stop poll stall timer */ + if (timer_pending(&udc_pollstall_timer)) + mod_timer(&udc_pollstall_timer, jiffies - 1); + /* disable DMA */ + tmp = readl(&dev->regs->ctl); + tmp &= AMD_UNMASK_BIT(UDC_DEVCTL_RDE); + tmp &= AMD_UNMASK_BIT(UDC_DEVCTL_TDE); + writel(tmp, &dev->regs->ctl); + + /* enable dynamic CSR programming */ + tmp = readl(&dev->regs->cfg); + tmp |= AMD_BIT(UDC_DEVCFG_CSR_PRG); + /* set self powered */ + tmp |= AMD_BIT(UDC_DEVCFG_SP); + /* set remote wakeupable */ + tmp |= AMD_BIT(UDC_DEVCFG_RWKP); + writel(tmp, &dev->regs->cfg); + + make_ep_lists(dev); + + dev->data_ep_enabled = 0; + dev->data_ep_queued = 0; +} +EXPORT_SYMBOL_GPL(udc_basic_init); + +/* init registers at driver load time */ +static int startup_registers(struct udc *dev) +{ + u32 tmp; + + /* init controller by soft reset */ + udc_soft_reset(dev); + + /* mask not needed interrupts */ + udc_mask_unused_interrupts(dev); + + /* put into initial config */ + udc_basic_init(dev); + /* link up all endpoints */ + udc_setup_endpoints(dev); + + /* program speed */ + tmp = readl(&dev->regs->cfg); + if (use_fullspeed) + tmp = AMD_ADDBITS(tmp, UDC_DEVCFG_SPD_FS, UDC_DEVCFG_SPD); + else + tmp = AMD_ADDBITS(tmp, UDC_DEVCFG_SPD_HS, UDC_DEVCFG_SPD); + writel(tmp, &dev->regs->cfg); + + return 0; +} + +/* Sets initial endpoint parameters */ +static void udc_setup_endpoints(struct udc *dev) +{ + struct udc_ep *ep; + u32 tmp; + u32 reg; + + DBG(dev, "udc_setup_endpoints()\n"); + + /* read enum speed */ + tmp = readl(&dev->regs->sts); + tmp = AMD_GETBITS(tmp, UDC_DEVSTS_ENUM_SPEED); + if (tmp == UDC_DEVSTS_ENUM_SPEED_HIGH) + dev->gadget.speed = USB_SPEED_HIGH; + else if (tmp == UDC_DEVSTS_ENUM_SPEED_FULL) + dev->gadget.speed = USB_SPEED_FULL; + + /* set basic ep parameters */ + for (tmp = 0; tmp < UDC_EP_NUM; tmp++) { + ep = &dev->ep[tmp]; + ep->dev = dev; + ep->ep.name = ep_info[tmp].name; + ep->ep.caps = ep_info[tmp].caps; + ep->num = tmp; + /* txfifo size is calculated at enable time */ + ep->txfifo = dev->txfifo; + + /* fifo size */ + if (tmp < UDC_EPIN_NUM) { + ep->fifo_depth = UDC_TXFIFO_SIZE; + ep->in = 1; + } else { + ep->fifo_depth = UDC_RXFIFO_SIZE; + ep->in = 0; + + } + ep->regs = &dev->ep_regs[tmp]; + /* + * ep will be reset only if ep was not enabled before to avoid + * disabling ep interrupts when ENUM interrupt occurs but ep is + * not enabled by gadget driver + */ + if (!ep->ep.desc) + ep_init(dev->regs, ep); + + if (use_dma) { + /* + * ep->dma is not really used, just to indicate that + * DMA is active: remove this + * dma regs = dev control regs + */ + ep->dma = &dev->regs->ctl; + + /* nak OUT endpoints until enable - not for ep0 */ + if (tmp != UDC_EP0IN_IX && tmp != UDC_EP0OUT_IX + && tmp > UDC_EPIN_NUM) { + /* set NAK */ + reg = readl(&dev->ep[tmp].regs->ctl); + reg |= AMD_BIT(UDC_EPCTL_SNAK); + writel(reg, &dev->ep[tmp].regs->ctl); + dev->ep[tmp].naking = 1; + + } + } + } + /* EP0 max packet */ + if (dev->gadget.speed == USB_SPEED_FULL) { + usb_ep_set_maxpacket_limit(&dev->ep[UDC_EP0IN_IX].ep, + UDC_FS_EP0IN_MAX_PKT_SIZE); + usb_ep_set_maxpacket_limit(&dev->ep[UDC_EP0OUT_IX].ep, + UDC_FS_EP0OUT_MAX_PKT_SIZE); + } else if (dev->gadget.speed == USB_SPEED_HIGH) { + usb_ep_set_maxpacket_limit(&dev->ep[UDC_EP0IN_IX].ep, + UDC_EP0IN_MAX_PKT_SIZE); + usb_ep_set_maxpacket_limit(&dev->ep[UDC_EP0OUT_IX].ep, + UDC_EP0OUT_MAX_PKT_SIZE); + } + + /* + * with suspend bug workaround, ep0 params for gadget driver + * are set at gadget driver bind() call + */ + dev->gadget.ep0 = &dev->ep[UDC_EP0IN_IX].ep; + dev->ep[UDC_EP0IN_IX].halted = 0; + INIT_LIST_HEAD(&dev->gadget.ep0->ep_list); + + /* init cfg/alt/int */ + dev->cur_config = 0; + dev->cur_intf = 0; + dev->cur_alt = 0; +} + +/* Bringup after Connect event, initial bringup to be ready for ep0 events */ +static void usb_connect(struct udc *dev) +{ + + dev_info(&dev->pdev->dev, "USB Connect\n"); + + dev->connected = 1; + + /* put into initial config */ + udc_basic_init(dev); + + /* enable device setup interrupts */ + udc_enable_dev_setup_interrupts(dev); +} + +/* + * Calls gadget with disconnect event and resets the UDC and makes + * initial bringup to be ready for ep0 events + */ +static void usb_disconnect(struct udc *dev) +{ + + dev_info(&dev->pdev->dev, "USB Disconnect\n"); + + dev->connected = 0; + + /* mask interrupts */ + udc_mask_unused_interrupts(dev); + + /* REVISIT there doesn't seem to be a point to having this + * talk to a tasklet ... do it directly, we already hold + * the spinlock needed to process the disconnect. + */ + + tasklet_schedule(&disconnect_tasklet); +} + +/* Tasklet for disconnect to be outside of interrupt context */ +static void udc_tasklet_disconnect(unsigned long par) +{ + struct udc *dev = (struct udc *)(*((struct udc **) par)); + u32 tmp; + + DBG(dev, "Tasklet disconnect\n"); + spin_lock_irq(&dev->lock); + + if (dev->driver) { + spin_unlock(&dev->lock); + dev->driver->disconnect(&dev->gadget); + spin_lock(&dev->lock); + + /* empty queues */ + for (tmp = 0; tmp < UDC_EP_NUM; tmp++) + empty_req_queue(&dev->ep[tmp]); + + } + + /* disable ep0 */ + ep_init(dev->regs, + &dev->ep[UDC_EP0IN_IX]); + + + if (!soft_reset_occured) { + /* init controller by soft reset */ + udc_soft_reset(dev); + soft_reset_occured++; + } + + /* re-enable dev interrupts */ + udc_enable_dev_setup_interrupts(dev); + /* back to full speed ? */ + if (use_fullspeed) { + tmp = readl(&dev->regs->cfg); + tmp = AMD_ADDBITS(tmp, UDC_DEVCFG_SPD_FS, UDC_DEVCFG_SPD); + writel(tmp, &dev->regs->cfg); + } + + spin_unlock_irq(&dev->lock); +} + +/* Reset the UDC core */ +static void udc_soft_reset(struct udc *dev) +{ + unsigned long flags; + + DBG(dev, "Soft reset\n"); + /* + * reset possible waiting interrupts, because int. + * status is lost after soft reset, + * ep int. status reset + */ + writel(UDC_EPINT_MSK_DISABLE_ALL, &dev->regs->ep_irqsts); + /* device int. status reset */ + writel(UDC_DEV_MSK_DISABLE, &dev->regs->irqsts); + + spin_lock_irqsave(&udc_irq_spinlock, flags); + writel(AMD_BIT(UDC_DEVCFG_SOFTRESET), &dev->regs->cfg); + readl(&dev->regs->cfg); + spin_unlock_irqrestore(&udc_irq_spinlock, flags); + +} + +/* RDE timer callback to set RDE bit */ +static void udc_timer_function(unsigned long v) +{ + u32 tmp; + + spin_lock_irq(&udc_irq_spinlock); + + if (set_rde > 0) { + /* + * open the fifo if fifo was filled on last timer call + * conditionally + */ + if (set_rde > 1) { + /* set RDE to receive setup data */ + tmp = readl(&udc->regs->ctl); + tmp |= AMD_BIT(UDC_DEVCTL_RDE); + writel(tmp, &udc->regs->ctl); + set_rde = -1; + } else if (readl(&udc->regs->sts) + & AMD_BIT(UDC_DEVSTS_RXFIFO_EMPTY)) { + /* + * if fifo empty setup polling, do not just + * open the fifo + */ + udc_timer.expires = jiffies + HZ/UDC_RDE_TIMER_DIV; + if (!stop_timer) + add_timer(&udc_timer); + } else { + /* + * fifo contains data now, setup timer for opening + * the fifo when timer expires to be able to receive + * setup packets, when data packets gets queued by + * gadget layer then timer will forced to expire with + * set_rde=0 (RDE is set in udc_queue()) + */ + set_rde++; + /* debug: lhadmot_timer_start = 221070 */ + udc_timer.expires = jiffies + HZ*UDC_RDE_TIMER_SECONDS; + if (!stop_timer) + add_timer(&udc_timer); + } + + } else + set_rde = -1; /* RDE was set by udc_queue() */ + spin_unlock_irq(&udc_irq_spinlock); + if (stop_timer) + complete(&on_exit); + +} + +/* Handle halt state, used in stall poll timer */ +static void udc_handle_halt_state(struct udc_ep *ep) +{ + u32 tmp; + /* set stall as long not halted */ + if (ep->halted == 1) { + tmp = readl(&ep->regs->ctl); + /* STALL cleared ? */ + if (!(tmp & AMD_BIT(UDC_EPCTL_S))) { + /* + * FIXME: MSC spec requires that stall remains + * even on receivng of CLEAR_FEATURE HALT. So + * we would set STALL again here to be compliant. + * But with current mass storage drivers this does + * not work (would produce endless host retries). + * So we clear halt on CLEAR_FEATURE. + * + DBG(ep->dev, "ep %d: set STALL again\n", ep->num); + tmp |= AMD_BIT(UDC_EPCTL_S); + writel(tmp, &ep->regs->ctl);*/ + + /* clear NAK by writing CNAK */ + tmp |= AMD_BIT(UDC_EPCTL_CNAK); + writel(tmp, &ep->regs->ctl); + ep->halted = 0; + UDC_QUEUE_CNAK(ep, ep->num); + } + } +} + +/* Stall timer callback to poll S bit and set it again after */ +static void udc_pollstall_timer_function(unsigned long v) +{ + struct udc_ep *ep; + int halted = 0; + + spin_lock_irq(&udc_stall_spinlock); + /* + * only one IN and OUT endpoints are handled + * IN poll stall + */ + ep = &udc->ep[UDC_EPIN_IX]; + udc_handle_halt_state(ep); + if (ep->halted) + halted = 1; + /* OUT poll stall */ + ep = &udc->ep[UDC_EPOUT_IX]; + udc_handle_halt_state(ep); + if (ep->halted) + halted = 1; + + /* setup timer again when still halted */ + if (!stop_pollstall_timer && halted) { + udc_pollstall_timer.expires = jiffies + + HZ * UDC_POLLSTALL_TIMER_USECONDS + / (1000 * 1000); + add_timer(&udc_pollstall_timer); + } + spin_unlock_irq(&udc_stall_spinlock); + + if (stop_pollstall_timer) + complete(&on_pollstall_exit); +} + +/* Inits endpoint 0 so that SETUP packets are processed */ +static void activate_control_endpoints(struct udc *dev) +{ + u32 tmp; + + DBG(dev, "activate_control_endpoints\n"); + + /* flush fifo */ + tmp = readl(&dev->ep[UDC_EP0IN_IX].regs->ctl); + tmp |= AMD_BIT(UDC_EPCTL_F); + writel(tmp, &dev->ep[UDC_EP0IN_IX].regs->ctl); + + /* set ep0 directions */ + dev->ep[UDC_EP0IN_IX].in = 1; + dev->ep[UDC_EP0OUT_IX].in = 0; + + /* set buffer size (tx fifo entries) of EP0_IN */ + tmp = readl(&dev->ep[UDC_EP0IN_IX].regs->bufin_framenum); + if (dev->gadget.speed == USB_SPEED_FULL) + tmp = AMD_ADDBITS(tmp, UDC_FS_EPIN0_BUFF_SIZE, + UDC_EPIN_BUFF_SIZE); + else if (dev->gadget.speed == USB_SPEED_HIGH) + tmp = AMD_ADDBITS(tmp, UDC_EPIN0_BUFF_SIZE, + UDC_EPIN_BUFF_SIZE); + writel(tmp, &dev->ep[UDC_EP0IN_IX].regs->bufin_framenum); + + /* set max packet size of EP0_IN */ + tmp = readl(&dev->ep[UDC_EP0IN_IX].regs->bufout_maxpkt); + if (dev->gadget.speed == USB_SPEED_FULL) + tmp = AMD_ADDBITS(tmp, UDC_FS_EP0IN_MAX_PKT_SIZE, + UDC_EP_MAX_PKT_SIZE); + else if (dev->gadget.speed == USB_SPEED_HIGH) + tmp = AMD_ADDBITS(tmp, UDC_EP0IN_MAX_PKT_SIZE, + UDC_EP_MAX_PKT_SIZE); + writel(tmp, &dev->ep[UDC_EP0IN_IX].regs->bufout_maxpkt); + + /* set max packet size of EP0_OUT */ + tmp = readl(&dev->ep[UDC_EP0OUT_IX].regs->bufout_maxpkt); + if (dev->gadget.speed == USB_SPEED_FULL) + tmp = AMD_ADDBITS(tmp, UDC_FS_EP0OUT_MAX_PKT_SIZE, + UDC_EP_MAX_PKT_SIZE); + else if (dev->gadget.speed == USB_SPEED_HIGH) + tmp = AMD_ADDBITS(tmp, UDC_EP0OUT_MAX_PKT_SIZE, + UDC_EP_MAX_PKT_SIZE); + writel(tmp, &dev->ep[UDC_EP0OUT_IX].regs->bufout_maxpkt); + + /* set max packet size of EP0 in UDC CSR */ + tmp = readl(&dev->csr->ne[0]); + if (dev->gadget.speed == USB_SPEED_FULL) + tmp = AMD_ADDBITS(tmp, UDC_FS_EP0OUT_MAX_PKT_SIZE, + UDC_CSR_NE_MAX_PKT); + else if (dev->gadget.speed == USB_SPEED_HIGH) + tmp = AMD_ADDBITS(tmp, UDC_EP0OUT_MAX_PKT_SIZE, + UDC_CSR_NE_MAX_PKT); + writel(tmp, &dev->csr->ne[0]); + + if (use_dma) { + dev->ep[UDC_EP0OUT_IX].td->status |= + AMD_BIT(UDC_DMA_OUT_STS_L); + /* write dma desc address */ + writel(dev->ep[UDC_EP0OUT_IX].td_stp_dma, + &dev->ep[UDC_EP0OUT_IX].regs->subptr); + writel(dev->ep[UDC_EP0OUT_IX].td_phys, + &dev->ep[UDC_EP0OUT_IX].regs->desptr); + /* stop RDE timer */ + if (timer_pending(&udc_timer)) { + set_rde = 0; + mod_timer(&udc_timer, jiffies - 1); + } + /* stop pollstall timer */ + if (timer_pending(&udc_pollstall_timer)) + mod_timer(&udc_pollstall_timer, jiffies - 1); + /* enable DMA */ + tmp = readl(&dev->regs->ctl); + tmp |= AMD_BIT(UDC_DEVCTL_MODE) + | AMD_BIT(UDC_DEVCTL_RDE) + | AMD_BIT(UDC_DEVCTL_TDE); + if (use_dma_bufferfill_mode) + tmp |= AMD_BIT(UDC_DEVCTL_BF); + else if (use_dma_ppb_du) + tmp |= AMD_BIT(UDC_DEVCTL_DU); + writel(tmp, &dev->regs->ctl); + } + + /* clear NAK by writing CNAK for EP0IN */ + tmp = readl(&dev->ep[UDC_EP0IN_IX].regs->ctl); + tmp |= AMD_BIT(UDC_EPCTL_CNAK); + writel(tmp, &dev->ep[UDC_EP0IN_IX].regs->ctl); + dev->ep[UDC_EP0IN_IX].naking = 0; + UDC_QUEUE_CNAK(&dev->ep[UDC_EP0IN_IX], UDC_EP0IN_IX); + + /* clear NAK by writing CNAK for EP0OUT */ + tmp = readl(&dev->ep[UDC_EP0OUT_IX].regs->ctl); + tmp |= AMD_BIT(UDC_EPCTL_CNAK); + writel(tmp, &dev->ep[UDC_EP0OUT_IX].regs->ctl); + dev->ep[UDC_EP0OUT_IX].naking = 0; + UDC_QUEUE_CNAK(&dev->ep[UDC_EP0OUT_IX], UDC_EP0OUT_IX); +} + +/* Make endpoint 0 ready for control traffic */ +static int setup_ep0(struct udc *dev) +{ + activate_control_endpoints(dev); + /* enable ep0 interrupts */ + udc_enable_ep0_interrupts(dev); + /* enable device setup interrupts */ + udc_enable_dev_setup_interrupts(dev); + + return 0; +} + +/* Called by gadget driver to register itself */ +static int amd5536_udc_start(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + struct udc *dev = to_amd5536_udc(g); + u32 tmp; + + driver->driver.bus = NULL; + dev->driver = driver; + + /* Some gadget drivers use both ep0 directions. + * NOTE: to gadget driver, ep0 is just one endpoint... + */ + dev->ep[UDC_EP0OUT_IX].ep.driver_data = + dev->ep[UDC_EP0IN_IX].ep.driver_data; + + /* get ready for ep0 traffic */ + setup_ep0(dev); + + /* clear SD */ + tmp = readl(&dev->regs->ctl); + tmp = tmp & AMD_CLEAR_BIT(UDC_DEVCTL_SD); + writel(tmp, &dev->regs->ctl); + + usb_connect(dev); + + return 0; +} + +/* shutdown requests and disconnect from gadget */ +static void +shutdown(struct udc *dev, struct usb_gadget_driver *driver) +__releases(dev->lock) +__acquires(dev->lock) +{ + int tmp; + + /* empty queues and init hardware */ + udc_basic_init(dev); + + for (tmp = 0; tmp < UDC_EP_NUM; tmp++) + empty_req_queue(&dev->ep[tmp]); + + udc_setup_endpoints(dev); +} + +/* Called by gadget driver to unregister itself */ +static int amd5536_udc_stop(struct usb_gadget *g) +{ + struct udc *dev = to_amd5536_udc(g); + unsigned long flags; + u32 tmp; + + spin_lock_irqsave(&dev->lock, flags); + udc_mask_unused_interrupts(dev); + shutdown(dev, NULL); + spin_unlock_irqrestore(&dev->lock, flags); + + dev->driver = NULL; + + /* set SD */ + tmp = readl(&dev->regs->ctl); + tmp |= AMD_BIT(UDC_DEVCTL_SD); + writel(tmp, &dev->regs->ctl); + + return 0; +} + +/* Clear pending NAK bits */ +static void udc_process_cnak_queue(struct udc *dev) +{ + u32 tmp; + u32 reg; + + /* check epin's */ + DBG(dev, "CNAK pending queue processing\n"); + for (tmp = 0; tmp < UDC_EPIN_NUM_USED; tmp++) { + if (cnak_pending & (1 << tmp)) { + DBG(dev, "CNAK pending for ep%d\n", tmp); + /* clear NAK by writing CNAK */ + reg = readl(&dev->ep[tmp].regs->ctl); + reg |= AMD_BIT(UDC_EPCTL_CNAK); + writel(reg, &dev->ep[tmp].regs->ctl); + dev->ep[tmp].naking = 0; + UDC_QUEUE_CNAK(&dev->ep[tmp], dev->ep[tmp].num); + } + } + /* ... and ep0out */ + if (cnak_pending & (1 << UDC_EP0OUT_IX)) { + DBG(dev, "CNAK pending for ep%d\n", UDC_EP0OUT_IX); + /* clear NAK by writing CNAK */ + reg = readl(&dev->ep[UDC_EP0OUT_IX].regs->ctl); + reg |= AMD_BIT(UDC_EPCTL_CNAK); + writel(reg, &dev->ep[UDC_EP0OUT_IX].regs->ctl); + dev->ep[UDC_EP0OUT_IX].naking = 0; + UDC_QUEUE_CNAK(&dev->ep[UDC_EP0OUT_IX], + dev->ep[UDC_EP0OUT_IX].num); + } +} + +/* Enabling RX DMA after setup packet */ +static void udc_ep0_set_rde(struct udc *dev) +{ + if (use_dma) { + /* + * only enable RXDMA when no data endpoint enabled + * or data is queued + */ + if (!dev->data_ep_enabled || dev->data_ep_queued) { + udc_set_rde(dev); + } else { + /* + * setup timer for enabling RDE (to not enable + * RXFIFO DMA for data endpoints to early) + */ + if (set_rde != 0 && !timer_pending(&udc_timer)) { + udc_timer.expires = + jiffies + HZ/UDC_RDE_TIMER_DIV; + set_rde = 1; + if (!stop_timer) + add_timer(&udc_timer); + } + } + } +} + + +/* Interrupt handler for data OUT traffic */ +static irqreturn_t udc_data_out_isr(struct udc *dev, int ep_ix) +{ + irqreturn_t ret_val = IRQ_NONE; + u32 tmp; + struct udc_ep *ep; + struct udc_request *req; + unsigned int count; + struct udc_data_dma *td = NULL; + unsigned dma_done; + + VDBG(dev, "ep%d irq\n", ep_ix); + ep = &dev->ep[ep_ix]; + + tmp = readl(&ep->regs->sts); + if (use_dma) { + /* BNA event ? */ + if (tmp & AMD_BIT(UDC_EPSTS_BNA)) { + DBG(dev, "BNA ep%dout occurred - DESPTR = %x\n", + ep->num, readl(&ep->regs->desptr)); + /* clear BNA */ + writel(tmp | AMD_BIT(UDC_EPSTS_BNA), &ep->regs->sts); + if (!ep->cancel_transfer) + ep->bna_occurred = 1; + else + ep->cancel_transfer = 0; + ret_val = IRQ_HANDLED; + goto finished; + } + } + /* HE event ? */ + if (tmp & AMD_BIT(UDC_EPSTS_HE)) { + dev_err(&dev->pdev->dev, "HE ep%dout occurred\n", ep->num); + + /* clear HE */ + writel(tmp | AMD_BIT(UDC_EPSTS_HE), &ep->regs->sts); + ret_val = IRQ_HANDLED; + goto finished; + } + + if (!list_empty(&ep->queue)) { + + /* next request */ + req = list_entry(ep->queue.next, + struct udc_request, queue); + } else { + req = NULL; + udc_rxfifo_pending = 1; + } + VDBG(dev, "req = %p\n", req); + /* fifo mode */ + if (!use_dma) { + + /* read fifo */ + if (req && udc_rxfifo_read(ep, req)) { + ret_val = IRQ_HANDLED; + + /* finish */ + complete_req(ep, req, 0); + /* next request */ + if (!list_empty(&ep->queue) && !ep->halted) { + req = list_entry(ep->queue.next, + struct udc_request, queue); + } else + req = NULL; + } + + /* DMA */ + } else if (!ep->cancel_transfer && req) { + ret_val = IRQ_HANDLED; + + /* check for DMA done */ + if (!use_dma_ppb) { + dma_done = AMD_GETBITS(req->td_data->status, + UDC_DMA_OUT_STS_BS); + /* packet per buffer mode - rx bytes */ + } else { + /* + * if BNA occurred then recover desc. from + * BNA dummy desc. + */ + if (ep->bna_occurred) { + VDBG(dev, "Recover desc. from BNA dummy\n"); + memcpy(req->td_data, ep->bna_dummy_req->td_data, + sizeof(struct udc_data_dma)); + ep->bna_occurred = 0; + udc_init_bna_dummy(ep->req); + } + td = udc_get_last_dma_desc(req); + dma_done = AMD_GETBITS(td->status, UDC_DMA_OUT_STS_BS); + } + if (dma_done == UDC_DMA_OUT_STS_BS_DMA_DONE) { + /* buffer fill mode - rx bytes */ + if (!use_dma_ppb) { + /* received number bytes */ + count = AMD_GETBITS(req->td_data->status, + UDC_DMA_OUT_STS_RXBYTES); + VDBG(dev, "rx bytes=%u\n", count); + /* packet per buffer mode - rx bytes */ + } else { + VDBG(dev, "req->td_data=%p\n", req->td_data); + VDBG(dev, "last desc = %p\n", td); + /* received number bytes */ + if (use_dma_ppb_du) { + /* every desc. counts bytes */ + count = udc_get_ppbdu_rxbytes(req); + } else { + /* last desc. counts bytes */ + count = AMD_GETBITS(td->status, + UDC_DMA_OUT_STS_RXBYTES); + if (!count && req->req.length + == UDC_DMA_MAXPACKET) { + /* + * on 64k packets the RXBYTES + * field is zero + */ + count = UDC_DMA_MAXPACKET; + } + } + VDBG(dev, "last desc rx bytes=%u\n", count); + } + + tmp = req->req.length - req->req.actual; + if (count > tmp) { + if ((tmp % ep->ep.maxpacket) != 0) { + DBG(dev, "%s: rx %db, space=%db\n", + ep->ep.name, count, tmp); + req->req.status = -EOVERFLOW; + } + count = tmp; + } + req->req.actual += count; + req->dma_going = 0; + /* complete request */ + complete_req(ep, req, 0); + + /* next request */ + if (!list_empty(&ep->queue) && !ep->halted) { + req = list_entry(ep->queue.next, + struct udc_request, + queue); + /* + * DMA may be already started by udc_queue() + * called by gadget drivers completion + * routine. This happens when queue + * holds one request only. + */ + if (req->dma_going == 0) { + /* next dma */ + if (prep_dma(ep, req, GFP_ATOMIC) != 0) + goto finished; + /* write desc pointer */ + writel(req->td_phys, + &ep->regs->desptr); + req->dma_going = 1; + /* enable DMA */ + udc_set_rde(dev); + } + } else { + /* + * implant BNA dummy descriptor to allow + * RXFIFO opening by RDE + */ + if (ep->bna_dummy_req) { + /* write desc pointer */ + writel(ep->bna_dummy_req->td_phys, + &ep->regs->desptr); + ep->bna_occurred = 0; + } + + /* + * schedule timer for setting RDE if queue + * remains empty to allow ep0 packets pass + * through + */ + if (set_rde != 0 + && !timer_pending(&udc_timer)) { + udc_timer.expires = + jiffies + + HZ*UDC_RDE_TIMER_SECONDS; + set_rde = 1; + if (!stop_timer) + add_timer(&udc_timer); + } + if (ep->num != UDC_EP0OUT_IX) + dev->data_ep_queued = 0; + } + + } else { + /* + * RX DMA must be reenabled for each desc in PPBDU mode + * and must be enabled for PPBNDU mode in case of BNA + */ + udc_set_rde(dev); + } + + } else if (ep->cancel_transfer) { + ret_val = IRQ_HANDLED; + ep->cancel_transfer = 0; + } + + /* check pending CNAKS */ + if (cnak_pending) { + /* CNAk processing when rxfifo empty only */ + if (readl(&dev->regs->sts) & AMD_BIT(UDC_DEVSTS_RXFIFO_EMPTY)) + udc_process_cnak_queue(dev); + } + + /* clear OUT bits in ep status */ + writel(UDC_EPSTS_OUT_CLEAR, &ep->regs->sts); +finished: + return ret_val; +} + +/* Interrupt handler for data IN traffic */ +static irqreturn_t udc_data_in_isr(struct udc *dev, int ep_ix) +{ + irqreturn_t ret_val = IRQ_NONE; + u32 tmp; + u32 epsts; + struct udc_ep *ep; + struct udc_request *req; + struct udc_data_dma *td; + unsigned len; + + ep = &dev->ep[ep_ix]; + + epsts = readl(&ep->regs->sts); + if (use_dma) { + /* BNA ? */ + if (epsts & AMD_BIT(UDC_EPSTS_BNA)) { + dev_err(&dev->pdev->dev, + "BNA ep%din occurred - DESPTR = %08lx\n", + ep->num, + (unsigned long) readl(&ep->regs->desptr)); + + /* clear BNA */ + writel(epsts, &ep->regs->sts); + ret_val = IRQ_HANDLED; + goto finished; + } + } + /* HE event ? */ + if (epsts & AMD_BIT(UDC_EPSTS_HE)) { + dev_err(&dev->pdev->dev, + "HE ep%dn occurred - DESPTR = %08lx\n", + ep->num, (unsigned long) readl(&ep->regs->desptr)); + + /* clear HE */ + writel(epsts | AMD_BIT(UDC_EPSTS_HE), &ep->regs->sts); + ret_val = IRQ_HANDLED; + goto finished; + } + + /* DMA completion */ + if (epsts & AMD_BIT(UDC_EPSTS_TDC)) { + VDBG(dev, "TDC set- completion\n"); + ret_val = IRQ_HANDLED; + if (!ep->cancel_transfer && !list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, + struct udc_request, queue); + /* + * length bytes transferred + * check dma done of last desc. in PPBDU mode + */ + if (use_dma_ppb_du) { + td = udc_get_last_dma_desc(req); + if (td) + req->req.actual = req->req.length; + } else { + /* assume all bytes transferred */ + req->req.actual = req->req.length; + } + + if (req->req.actual == req->req.length) { + /* complete req */ + complete_req(ep, req, 0); + req->dma_going = 0; + /* further request available ? */ + if (list_empty(&ep->queue)) { + /* disable interrupt */ + tmp = readl(&dev->regs->ep_irqmsk); + tmp |= AMD_BIT(ep->num); + writel(tmp, &dev->regs->ep_irqmsk); + } + } + } + ep->cancel_transfer = 0; + + } + /* + * status reg has IN bit set and TDC not set (if TDC was handled, + * IN must not be handled (UDC defect) ? + */ + if ((epsts & AMD_BIT(UDC_EPSTS_IN)) + && !(epsts & AMD_BIT(UDC_EPSTS_TDC))) { + ret_val = IRQ_HANDLED; + if (!list_empty(&ep->queue)) { + /* next request */ + req = list_entry(ep->queue.next, + struct udc_request, queue); + /* FIFO mode */ + if (!use_dma) { + /* write fifo */ + udc_txfifo_write(ep, &req->req); + len = req->req.length - req->req.actual; + if (len > ep->ep.maxpacket) + len = ep->ep.maxpacket; + req->req.actual += len; + if (req->req.actual == req->req.length + || (len != ep->ep.maxpacket)) { + /* complete req */ + complete_req(ep, req, 0); + } + /* DMA */ + } else if (req && !req->dma_going) { + VDBG(dev, "IN DMA : req=%p req->td_data=%p\n", + req, req->td_data); + if (req->td_data) { + + req->dma_going = 1; + + /* + * unset L bit of first desc. + * for chain + */ + if (use_dma_ppb && req->req.length > + ep->ep.maxpacket) { + req->td_data->status &= + AMD_CLEAR_BIT( + UDC_DMA_IN_STS_L); + } + + /* write desc pointer */ + writel(req->td_phys, &ep->regs->desptr); + + /* set HOST READY */ + req->td_data->status = + AMD_ADDBITS( + req->td_data->status, + UDC_DMA_IN_STS_BS_HOST_READY, + UDC_DMA_IN_STS_BS); + + /* set poll demand bit */ + tmp = readl(&ep->regs->ctl); + tmp |= AMD_BIT(UDC_EPCTL_P); + writel(tmp, &ep->regs->ctl); + } + } + + } else if (!use_dma && ep->in) { + /* disable interrupt */ + tmp = readl( + &dev->regs->ep_irqmsk); + tmp |= AMD_BIT(ep->num); + writel(tmp, + &dev->regs->ep_irqmsk); + } + } + /* clear status bits */ + writel(epsts, &ep->regs->sts); + +finished: + return ret_val; + +} + +/* Interrupt handler for Control OUT traffic */ +static irqreturn_t udc_control_out_isr(struct udc *dev) +__releases(dev->lock) +__acquires(dev->lock) +{ + irqreturn_t ret_val = IRQ_NONE; + u32 tmp; + int setup_supported; + u32 count; + int set = 0; + struct udc_ep *ep; + struct udc_ep *ep_tmp; + + ep = &dev->ep[UDC_EP0OUT_IX]; + + /* clear irq */ + writel(AMD_BIT(UDC_EPINT_OUT_EP0), &dev->regs->ep_irqsts); + + tmp = readl(&dev->ep[UDC_EP0OUT_IX].regs->sts); + /* check BNA and clear if set */ + if (tmp & AMD_BIT(UDC_EPSTS_BNA)) { + VDBG(dev, "ep0: BNA set\n"); + writel(AMD_BIT(UDC_EPSTS_BNA), + &dev->ep[UDC_EP0OUT_IX].regs->sts); + ep->bna_occurred = 1; + ret_val = IRQ_HANDLED; + goto finished; + } + + /* type of data: SETUP or DATA 0 bytes */ + tmp = AMD_GETBITS(tmp, UDC_EPSTS_OUT); + VDBG(dev, "data_typ = %x\n", tmp); + + /* setup data */ + if (tmp == UDC_EPSTS_OUT_SETUP) { + ret_val = IRQ_HANDLED; + + ep->dev->stall_ep0in = 0; + dev->waiting_zlp_ack_ep0in = 0; + + /* set NAK for EP0_IN */ + tmp = readl(&dev->ep[UDC_EP0IN_IX].regs->ctl); + tmp |= AMD_BIT(UDC_EPCTL_SNAK); + writel(tmp, &dev->ep[UDC_EP0IN_IX].regs->ctl); + dev->ep[UDC_EP0IN_IX].naking = 1; + /* get setup data */ + if (use_dma) { + + /* clear OUT bits in ep status */ + writel(UDC_EPSTS_OUT_CLEAR, + &dev->ep[UDC_EP0OUT_IX].regs->sts); + + setup_data.data[0] = + dev->ep[UDC_EP0OUT_IX].td_stp->data12; + setup_data.data[1] = + dev->ep[UDC_EP0OUT_IX].td_stp->data34; + /* set HOST READY */ + dev->ep[UDC_EP0OUT_IX].td_stp->status = + UDC_DMA_STP_STS_BS_HOST_READY; + } else { + /* read fifo */ + udc_rxfifo_read_dwords(dev, setup_data.data, 2); + } + + /* determine direction of control data */ + if ((setup_data.request.bRequestType & USB_DIR_IN) != 0) { + dev->gadget.ep0 = &dev->ep[UDC_EP0IN_IX].ep; + /* enable RDE */ + udc_ep0_set_rde(dev); + set = 0; + } else { + dev->gadget.ep0 = &dev->ep[UDC_EP0OUT_IX].ep; + /* + * implant BNA dummy descriptor to allow RXFIFO opening + * by RDE + */ + if (ep->bna_dummy_req) { + /* write desc pointer */ + writel(ep->bna_dummy_req->td_phys, + &dev->ep[UDC_EP0OUT_IX].regs->desptr); + ep->bna_occurred = 0; + } + + set = 1; + dev->ep[UDC_EP0OUT_IX].naking = 1; + /* + * setup timer for enabling RDE (to not enable + * RXFIFO DMA for data to early) + */ + set_rde = 1; + if (!timer_pending(&udc_timer)) { + udc_timer.expires = jiffies + + HZ/UDC_RDE_TIMER_DIV; + if (!stop_timer) + add_timer(&udc_timer); + } + } + + /* + * mass storage reset must be processed here because + * next packet may be a CLEAR_FEATURE HALT which would not + * clear the stall bit when no STALL handshake was received + * before (autostall can cause this) + */ + if (setup_data.data[0] == UDC_MSCRES_DWORD0 + && setup_data.data[1] == UDC_MSCRES_DWORD1) { + DBG(dev, "MSC Reset\n"); + /* + * clear stall bits + * only one IN and OUT endpoints are handled + */ + ep_tmp = &udc->ep[UDC_EPIN_IX]; + udc_set_halt(&ep_tmp->ep, 0); + ep_tmp = &udc->ep[UDC_EPOUT_IX]; + udc_set_halt(&ep_tmp->ep, 0); + } + + /* call gadget with setup data received */ + spin_unlock(&dev->lock); + setup_supported = dev->driver->setup(&dev->gadget, + &setup_data.request); + spin_lock(&dev->lock); + + tmp = readl(&dev->ep[UDC_EP0IN_IX].regs->ctl); + /* ep0 in returns data (not zlp) on IN phase */ + if (setup_supported >= 0 && setup_supported < + UDC_EP0IN_MAXPACKET) { + /* clear NAK by writing CNAK in EP0_IN */ + tmp |= AMD_BIT(UDC_EPCTL_CNAK); + writel(tmp, &dev->ep[UDC_EP0IN_IX].regs->ctl); + dev->ep[UDC_EP0IN_IX].naking = 0; + UDC_QUEUE_CNAK(&dev->ep[UDC_EP0IN_IX], UDC_EP0IN_IX); + + /* if unsupported request then stall */ + } else if (setup_supported < 0) { + tmp |= AMD_BIT(UDC_EPCTL_S); + writel(tmp, &dev->ep[UDC_EP0IN_IX].regs->ctl); + } else + dev->waiting_zlp_ack_ep0in = 1; + + + /* clear NAK by writing CNAK in EP0_OUT */ + if (!set) { + tmp = readl(&dev->ep[UDC_EP0OUT_IX].regs->ctl); + tmp |= AMD_BIT(UDC_EPCTL_CNAK); + writel(tmp, &dev->ep[UDC_EP0OUT_IX].regs->ctl); + dev->ep[UDC_EP0OUT_IX].naking = 0; + UDC_QUEUE_CNAK(&dev->ep[UDC_EP0OUT_IX], UDC_EP0OUT_IX); + } + + if (!use_dma) { + /* clear OUT bits in ep status */ + writel(UDC_EPSTS_OUT_CLEAR, + &dev->ep[UDC_EP0OUT_IX].regs->sts); + } + + /* data packet 0 bytes */ + } else if (tmp == UDC_EPSTS_OUT_DATA) { + /* clear OUT bits in ep status */ + writel(UDC_EPSTS_OUT_CLEAR, &dev->ep[UDC_EP0OUT_IX].regs->sts); + + /* get setup data: only 0 packet */ + if (use_dma) { + /* no req if 0 packet, just reactivate */ + if (list_empty(&dev->ep[UDC_EP0OUT_IX].queue)) { + VDBG(dev, "ZLP\n"); + + /* set HOST READY */ + dev->ep[UDC_EP0OUT_IX].td->status = + AMD_ADDBITS( + dev->ep[UDC_EP0OUT_IX].td->status, + UDC_DMA_OUT_STS_BS_HOST_READY, + UDC_DMA_OUT_STS_BS); + /* enable RDE */ + udc_ep0_set_rde(dev); + ret_val = IRQ_HANDLED; + + } else { + /* control write */ + ret_val |= udc_data_out_isr(dev, UDC_EP0OUT_IX); + /* re-program desc. pointer for possible ZLPs */ + writel(dev->ep[UDC_EP0OUT_IX].td_phys, + &dev->ep[UDC_EP0OUT_IX].regs->desptr); + /* enable RDE */ + udc_ep0_set_rde(dev); + } + } else { + + /* received number bytes */ + count = readl(&dev->ep[UDC_EP0OUT_IX].regs->sts); + count = AMD_GETBITS(count, UDC_EPSTS_RX_PKT_SIZE); + /* out data for fifo mode not working */ + count = 0; + + /* 0 packet or real data ? */ + if (count != 0) { + ret_val |= udc_data_out_isr(dev, UDC_EP0OUT_IX); + } else { + /* dummy read confirm */ + readl(&dev->ep[UDC_EP0OUT_IX].regs->confirm); + ret_val = IRQ_HANDLED; + } + } + } + + /* check pending CNAKS */ + if (cnak_pending) { + /* CNAk processing when rxfifo empty only */ + if (readl(&dev->regs->sts) & AMD_BIT(UDC_DEVSTS_RXFIFO_EMPTY)) + udc_process_cnak_queue(dev); + } + +finished: + return ret_val; +} + +/* Interrupt handler for Control IN traffic */ +static irqreturn_t udc_control_in_isr(struct udc *dev) +{ + irqreturn_t ret_val = IRQ_NONE; + u32 tmp; + struct udc_ep *ep; + struct udc_request *req; + unsigned len; + + ep = &dev->ep[UDC_EP0IN_IX]; + + /* clear irq */ + writel(AMD_BIT(UDC_EPINT_IN_EP0), &dev->regs->ep_irqsts); + + tmp = readl(&dev->ep[UDC_EP0IN_IX].regs->sts); + /* DMA completion */ + if (tmp & AMD_BIT(UDC_EPSTS_TDC)) { + VDBG(dev, "isr: TDC clear\n"); + ret_val = IRQ_HANDLED; + + /* clear TDC bit */ + writel(AMD_BIT(UDC_EPSTS_TDC), + &dev->ep[UDC_EP0IN_IX].regs->sts); + + /* status reg has IN bit set ? */ + } else if (tmp & AMD_BIT(UDC_EPSTS_IN)) { + ret_val = IRQ_HANDLED; + + if (ep->dma) { + /* clear IN bit */ + writel(AMD_BIT(UDC_EPSTS_IN), + &dev->ep[UDC_EP0IN_IX].regs->sts); + } + if (dev->stall_ep0in) { + DBG(dev, "stall ep0in\n"); + /* halt ep0in */ + tmp = readl(&ep->regs->ctl); + tmp |= AMD_BIT(UDC_EPCTL_S); + writel(tmp, &ep->regs->ctl); + } else { + if (!list_empty(&ep->queue)) { + /* next request */ + req = list_entry(ep->queue.next, + struct udc_request, queue); + + if (ep->dma) { + /* write desc pointer */ + writel(req->td_phys, &ep->regs->desptr); + /* set HOST READY */ + req->td_data->status = + AMD_ADDBITS( + req->td_data->status, + UDC_DMA_STP_STS_BS_HOST_READY, + UDC_DMA_STP_STS_BS); + + /* set poll demand bit */ + tmp = + readl(&dev->ep[UDC_EP0IN_IX].regs->ctl); + tmp |= AMD_BIT(UDC_EPCTL_P); + writel(tmp, + &dev->ep[UDC_EP0IN_IX].regs->ctl); + + /* all bytes will be transferred */ + req->req.actual = req->req.length; + + /* complete req */ + complete_req(ep, req, 0); + + } else { + /* write fifo */ + udc_txfifo_write(ep, &req->req); + + /* lengh bytes transferred */ + len = req->req.length - req->req.actual; + if (len > ep->ep.maxpacket) + len = ep->ep.maxpacket; + + req->req.actual += len; + if (req->req.actual == req->req.length + || (len != ep->ep.maxpacket)) { + /* complete req */ + complete_req(ep, req, 0); + } + } + + } + } + ep->halted = 0; + dev->stall_ep0in = 0; + if (!ep->dma) { + /* clear IN bit */ + writel(AMD_BIT(UDC_EPSTS_IN), + &dev->ep[UDC_EP0IN_IX].regs->sts); + } + } + + return ret_val; +} + + +/* Interrupt handler for global device events */ +static irqreturn_t udc_dev_isr(struct udc *dev, u32 dev_irq) +__releases(dev->lock) +__acquires(dev->lock) +{ + irqreturn_t ret_val = IRQ_NONE; + u32 tmp; + u32 cfg; + struct udc_ep *ep; + u16 i; + u8 udc_csr_epix; + + /* SET_CONFIG irq ? */ + if (dev_irq & AMD_BIT(UDC_DEVINT_SC)) { + ret_val = IRQ_HANDLED; + + /* read config value */ + tmp = readl(&dev->regs->sts); + cfg = AMD_GETBITS(tmp, UDC_DEVSTS_CFG); + DBG(dev, "SET_CONFIG interrupt: config=%d\n", cfg); + dev->cur_config = cfg; + dev->set_cfg_not_acked = 1; + + /* make usb request for gadget driver */ + memset(&setup_data, 0 , sizeof(union udc_setup_data)); + setup_data.request.bRequest = USB_REQ_SET_CONFIGURATION; + setup_data.request.wValue = cpu_to_le16(dev->cur_config); + + /* programm the NE registers */ + for (i = 0; i < UDC_EP_NUM; i++) { + ep = &dev->ep[i]; + if (ep->in) { + + /* ep ix in UDC CSR register space */ + udc_csr_epix = ep->num; + + + /* OUT ep */ + } else { + /* ep ix in UDC CSR register space */ + udc_csr_epix = ep->num - UDC_CSR_EP_OUT_IX_OFS; + } + + tmp = readl(&dev->csr->ne[udc_csr_epix]); + /* ep cfg */ + tmp = AMD_ADDBITS(tmp, ep->dev->cur_config, + UDC_CSR_NE_CFG); + /* write reg */ + writel(tmp, &dev->csr->ne[udc_csr_epix]); + + /* clear stall bits */ + ep->halted = 0; + tmp = readl(&ep->regs->ctl); + tmp = tmp & AMD_CLEAR_BIT(UDC_EPCTL_S); + writel(tmp, &ep->regs->ctl); + } + /* call gadget zero with setup data received */ + spin_unlock(&dev->lock); + tmp = dev->driver->setup(&dev->gadget, &setup_data.request); + spin_lock(&dev->lock); + + } /* SET_INTERFACE ? */ + if (dev_irq & AMD_BIT(UDC_DEVINT_SI)) { + ret_val = IRQ_HANDLED; + + dev->set_cfg_not_acked = 1; + /* read interface and alt setting values */ + tmp = readl(&dev->regs->sts); + dev->cur_alt = AMD_GETBITS(tmp, UDC_DEVSTS_ALT); + dev->cur_intf = AMD_GETBITS(tmp, UDC_DEVSTS_INTF); + + /* make usb request for gadget driver */ + memset(&setup_data, 0 , sizeof(union udc_setup_data)); + setup_data.request.bRequest = USB_REQ_SET_INTERFACE; + setup_data.request.bRequestType = USB_RECIP_INTERFACE; + setup_data.request.wValue = cpu_to_le16(dev->cur_alt); + setup_data.request.wIndex = cpu_to_le16(dev->cur_intf); + + DBG(dev, "SET_INTERFACE interrupt: alt=%d intf=%d\n", + dev->cur_alt, dev->cur_intf); + + /* programm the NE registers */ + for (i = 0; i < UDC_EP_NUM; i++) { + ep = &dev->ep[i]; + if (ep->in) { + + /* ep ix in UDC CSR register space */ + udc_csr_epix = ep->num; + + + /* OUT ep */ + } else { + /* ep ix in UDC CSR register space */ + udc_csr_epix = ep->num - UDC_CSR_EP_OUT_IX_OFS; + } + + /* UDC CSR reg */ + /* set ep values */ + tmp = readl(&dev->csr->ne[udc_csr_epix]); + /* ep interface */ + tmp = AMD_ADDBITS(tmp, ep->dev->cur_intf, + UDC_CSR_NE_INTF); + /* tmp = AMD_ADDBITS(tmp, 2, UDC_CSR_NE_INTF); */ + /* ep alt */ + tmp = AMD_ADDBITS(tmp, ep->dev->cur_alt, + UDC_CSR_NE_ALT); + /* write reg */ + writel(tmp, &dev->csr->ne[udc_csr_epix]); + + /* clear stall bits */ + ep->halted = 0; + tmp = readl(&ep->regs->ctl); + tmp = tmp & AMD_CLEAR_BIT(UDC_EPCTL_S); + writel(tmp, &ep->regs->ctl); + } + + /* call gadget zero with setup data received */ + spin_unlock(&dev->lock); + tmp = dev->driver->setup(&dev->gadget, &setup_data.request); + spin_lock(&dev->lock); + + } /* USB reset */ + if (dev_irq & AMD_BIT(UDC_DEVINT_UR)) { + DBG(dev, "USB Reset interrupt\n"); + ret_val = IRQ_HANDLED; + + /* allow soft reset when suspend occurs */ + soft_reset_occured = 0; + + dev->waiting_zlp_ack_ep0in = 0; + dev->set_cfg_not_acked = 0; + + /* mask not needed interrupts */ + udc_mask_unused_interrupts(dev); + + /* call gadget to resume and reset configs etc. */ + spin_unlock(&dev->lock); + if (dev->sys_suspended && dev->driver->resume) { + dev->driver->resume(&dev->gadget); + dev->sys_suspended = 0; + } + usb_gadget_udc_reset(&dev->gadget, dev->driver); + spin_lock(&dev->lock); + + /* disable ep0 to empty req queue */ + empty_req_queue(&dev->ep[UDC_EP0IN_IX]); + ep_init(dev->regs, &dev->ep[UDC_EP0IN_IX]); + + /* soft reset when rxfifo not empty */ + tmp = readl(&dev->regs->sts); + if (!(tmp & AMD_BIT(UDC_DEVSTS_RXFIFO_EMPTY)) + && !soft_reset_after_usbreset_occured) { + udc_soft_reset(dev); + soft_reset_after_usbreset_occured++; + } + + /* + * DMA reset to kill potential old DMA hw hang, + * POLL bit is already reset by ep_init() through + * disconnect() + */ + DBG(dev, "DMA machine reset\n"); + tmp = readl(&dev->regs->cfg); + writel(tmp | AMD_BIT(UDC_DEVCFG_DMARST), &dev->regs->cfg); + writel(tmp, &dev->regs->cfg); + + /* put into initial config */ + udc_basic_init(dev); + + /* enable device setup interrupts */ + udc_enable_dev_setup_interrupts(dev); + + /* enable suspend interrupt */ + tmp = readl(&dev->regs->irqmsk); + tmp &= AMD_UNMASK_BIT(UDC_DEVINT_US); + writel(tmp, &dev->regs->irqmsk); + + } /* USB suspend */ + if (dev_irq & AMD_BIT(UDC_DEVINT_US)) { + DBG(dev, "USB Suspend interrupt\n"); + ret_val = IRQ_HANDLED; + if (dev->driver->suspend) { + spin_unlock(&dev->lock); + dev->sys_suspended = 1; + dev->driver->suspend(&dev->gadget); + spin_lock(&dev->lock); + } + } /* new speed ? */ + if (dev_irq & AMD_BIT(UDC_DEVINT_ENUM)) { + DBG(dev, "ENUM interrupt\n"); + ret_val = IRQ_HANDLED; + soft_reset_after_usbreset_occured = 0; + + /* disable ep0 to empty req queue */ + empty_req_queue(&dev->ep[UDC_EP0IN_IX]); + ep_init(dev->regs, &dev->ep[UDC_EP0IN_IX]); + + /* link up all endpoints */ + udc_setup_endpoints(dev); + dev_info(&dev->pdev->dev, "Connect: %s\n", + usb_speed_string(dev->gadget.speed)); + + /* init ep 0 */ + activate_control_endpoints(dev); + + /* enable ep0 interrupts */ + udc_enable_ep0_interrupts(dev); + } + /* session valid change interrupt */ + if (dev_irq & AMD_BIT(UDC_DEVINT_SVC)) { + DBG(dev, "USB SVC interrupt\n"); + ret_val = IRQ_HANDLED; + + /* check that session is not valid to detect disconnect */ + tmp = readl(&dev->regs->sts); + if (!(tmp & AMD_BIT(UDC_DEVSTS_SESSVLD))) { + /* disable suspend interrupt */ + tmp = readl(&dev->regs->irqmsk); + tmp |= AMD_BIT(UDC_DEVINT_US); + writel(tmp, &dev->regs->irqmsk); + DBG(dev, "USB Disconnect (session valid low)\n"); + /* cleanup on disconnect */ + usb_disconnect(udc); + } + + } + + return ret_val; +} + +/* Interrupt Service Routine, see Linux Kernel Doc for parameters */ +irqreturn_t udc_irq(int irq, void *pdev) +{ + struct udc *dev = pdev; + u32 reg; + u16 i; + u32 ep_irq; + irqreturn_t ret_val = IRQ_NONE; + + spin_lock(&dev->lock); + + /* check for ep irq */ + reg = readl(&dev->regs->ep_irqsts); + if (reg) { + if (reg & AMD_BIT(UDC_EPINT_OUT_EP0)) + ret_val |= udc_control_out_isr(dev); + if (reg & AMD_BIT(UDC_EPINT_IN_EP0)) + ret_val |= udc_control_in_isr(dev); + + /* + * data endpoint + * iterate ep's + */ + for (i = 1; i < UDC_EP_NUM; i++) { + ep_irq = 1 << i; + if (!(reg & ep_irq) || i == UDC_EPINT_OUT_EP0) + continue; + + /* clear irq status */ + writel(ep_irq, &dev->regs->ep_irqsts); + + /* irq for out ep ? */ + if (i > UDC_EPIN_NUM) + ret_val |= udc_data_out_isr(dev, i); + else + ret_val |= udc_data_in_isr(dev, i); + } + + } + + + /* check for dev irq */ + reg = readl(&dev->regs->irqsts); + if (reg) { + /* clear irq */ + writel(reg, &dev->regs->irqsts); + ret_val |= udc_dev_isr(dev, reg); + } + + + spin_unlock(&dev->lock); + return ret_val; +} +EXPORT_SYMBOL_GPL(udc_irq); + +/* Tears down device */ +void gadget_release(struct device *pdev) +{ + struct amd5536udc *dev = dev_get_drvdata(pdev); + kfree(dev); +} +EXPORT_SYMBOL_GPL(gadget_release); + +/* Cleanup on device remove */ +void udc_remove(struct udc *dev) +{ + /* remove timer */ + stop_timer++; + if (timer_pending(&udc_timer)) + wait_for_completion(&on_exit); + if (udc_timer.data) + del_timer_sync(&udc_timer); + /* remove pollstall timer */ + stop_pollstall_timer++; + if (timer_pending(&udc_pollstall_timer)) + wait_for_completion(&on_pollstall_exit); + if (udc_pollstall_timer.data) + del_timer_sync(&udc_pollstall_timer); + udc = NULL; +} +EXPORT_SYMBOL_GPL(udc_remove); + +/* free all the dma pools */ +void free_dma_pools(struct udc *dev) +{ + dma_pool_free(dev->stp_requests, dev->ep[UDC_EP0OUT_IX].td, + dev->ep[UDC_EP0OUT_IX].td_phys); + dma_pool_free(dev->stp_requests, dev->ep[UDC_EP0OUT_IX].td_stp, + dev->ep[UDC_EP0OUT_IX].td_stp_dma); + dma_pool_destroy(dev->stp_requests); + dma_pool_destroy(dev->data_requests); +} +EXPORT_SYMBOL_GPL(free_dma_pools); + +/* create dma pools on init */ +int init_dma_pools(struct udc *dev) +{ + struct udc_stp_dma *td_stp; + struct udc_data_dma *td_data; + int retval; + + /* consistent DMA mode setting ? */ + if (use_dma_ppb) { + use_dma_bufferfill_mode = 0; + } else { + use_dma_ppb_du = 0; + use_dma_bufferfill_mode = 1; + } + + /* DMA setup */ + dev->data_requests = dma_pool_create("data_requests", NULL, + sizeof(struct udc_data_dma), 0, 0); + if (!dev->data_requests) { + DBG(dev, "can't get request data pool\n"); + return -ENOMEM; + } + + /* EP0 in dma regs = dev control regs */ + dev->ep[UDC_EP0IN_IX].dma = &dev->regs->ctl; + + /* dma desc for setup data */ + dev->stp_requests = dma_pool_create("setup requests", NULL, + sizeof(struct udc_stp_dma), 0, 0); + if (!dev->stp_requests) { + DBG(dev, "can't get stp request pool\n"); + retval = -ENOMEM; + goto err_create_dma_pool; + } + /* setup */ + td_stp = dma_pool_alloc(dev->stp_requests, GFP_KERNEL, + &dev->ep[UDC_EP0OUT_IX].td_stp_dma); + if (!td_stp) { + retval = -ENOMEM; + goto err_alloc_dma; + } + dev->ep[UDC_EP0OUT_IX].td_stp = td_stp; + + /* data: 0 packets !? */ + td_data = dma_pool_alloc(dev->stp_requests, GFP_KERNEL, + &dev->ep[UDC_EP0OUT_IX].td_phys); + if (!td_data) { + retval = -ENOMEM; + goto err_alloc_phys; + } + dev->ep[UDC_EP0OUT_IX].td = td_data; + return 0; + +err_alloc_phys: + dma_pool_free(dev->stp_requests, dev->ep[UDC_EP0OUT_IX].td_stp, + dev->ep[UDC_EP0OUT_IX].td_stp_dma); +err_alloc_dma: + dma_pool_destroy(dev->stp_requests); + dev->stp_requests = NULL; +err_create_dma_pool: + dma_pool_destroy(dev->data_requests); + dev->data_requests = NULL; + return retval; +} +EXPORT_SYMBOL_GPL(init_dma_pools); + +/* general probe */ +int udc_probe(struct udc *dev) +{ + char tmp[128]; + u32 reg; + int retval; + + /* mark timer as not initialized */ + udc_timer.data = 0; + udc_pollstall_timer.data = 0; + + /* device struct setup */ + dev->gadget.ops = &udc_ops; + + dev_set_name(&dev->gadget.dev, "gadget"); + dev->gadget.name = name; + dev->gadget.max_speed = USB_SPEED_HIGH; + + /* init registers, interrupts, ... */ + startup_registers(dev); + + dev_info(&dev->pdev->dev, "%s\n", mod_desc); + + snprintf(tmp, sizeof(tmp), "%d", dev->irq); + dev_info(&dev->pdev->dev, + "irq %s, pci mem %08lx, chip rev %02x(Geode5536 %s)\n", + tmp, dev->phys_addr, dev->chiprev, + (dev->chiprev == UDC_HSA0_REV) ? "A0" : "B1"); + strcpy(tmp, UDC_DRIVER_VERSION_STRING); + if (dev->chiprev == UDC_HSA0_REV) { + dev_err(&dev->pdev->dev, "chip revision is A0; too old\n"); + retval = -ENODEV; + goto finished; + } + dev_info(&dev->pdev->dev, + "driver version: %s(for Geode5536 B1)\n", tmp); + udc = dev; + + retval = usb_add_gadget_udc_release(&udc->pdev->dev, &dev->gadget, + gadget_release); + if (retval) + goto finished; + + /* timer init */ + init_timer(&udc_timer); + udc_timer.function = udc_timer_function; + udc_timer.data = 1; + /* timer pollstall init */ + init_timer(&udc_pollstall_timer); + udc_pollstall_timer.function = udc_pollstall_timer_function; + udc_pollstall_timer.data = 1; + + /* set SD */ + reg = readl(&dev->regs->ctl); + reg |= AMD_BIT(UDC_DEVCTL_SD); + writel(reg, &dev->regs->ctl); + + /* print dev register info */ + print_regs(dev); + + return 0; + +finished: + return retval; +} +EXPORT_SYMBOL_GPL(udc_probe); + +MODULE_DESCRIPTION(UDC_MOD_DESCRIPTION); +MODULE_AUTHOR("Thomas Dahlmann"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 498beb42814e3f734ea6d376a4854c7e0b6060a8 Mon Sep 17 00:00:00 2001 From: Raviteja Garimella Date: Wed, 10 May 2017 18:21:18 +0530 Subject: usb: gadget: udc: make debug prints compatible with both pci and platform devices This patch adds a struct device member to UDC data structure and makes changes to the arguments of dev_err and dev_dbg calls so that the debug prints work for both pci and platform devices. Signed-off-by: Raviteja Garimella Signed-off-by: Felipe Balbi --- drivers/usb/gadget/udc/amd5536udc.h | 4 +++- drivers/usb/gadget/udc/amd5536udc_pci.c | 1 + drivers/usb/gadget/udc/snps_udc_core.c | 28 ++++++++++++++-------------- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/drivers/usb/gadget/udc/amd5536udc.h b/drivers/usb/gadget/udc/amd5536udc.h index fae49bf3833e..91aae23b7370 100644 --- a/drivers/usb/gadget/udc/amd5536udc.h +++ b/drivers/usb/gadget/udc/amd5536udc.h @@ -563,6 +563,8 @@ struct udc { u16 cur_config; u16 cur_intf; u16 cur_alt; + + struct device *dev; }; #define to_amd5536_udc(g) (container_of((g), struct udc, gadget)) @@ -639,7 +641,7 @@ MODULE_PARM_DESC(use_fullspeed, "true for fullspeed only"); /* debug macros ------------------------------------------------------------*/ -#define DBG(udc , args...) dev_dbg(&(udc)->pdev->dev, args) +#define DBG(udc , args...) dev_dbg(udc->dev, args) #ifdef UDC_VERBOSE #define VDBG DBG diff --git a/drivers/usb/gadget/udc/amd5536udc_pci.c b/drivers/usb/gadget/udc/amd5536udc_pci.c index 2a2d0a96fe24..57a13f080a79 100644 --- a/drivers/usb/gadget/udc/amd5536udc_pci.c +++ b/drivers/usb/gadget/udc/amd5536udc_pci.c @@ -168,6 +168,7 @@ static int udc_pci_probe( dev->phys_addr = resource; dev->irq = pdev->irq; dev->pdev = pdev; + dev->dev = &pdev->dev; /* general probing */ if (udc_probe(dev)) { diff --git a/drivers/usb/gadget/udc/snps_udc_core.c b/drivers/usb/gadget/udc/snps_udc_core.c index 4ecd2f20ea48..4c43ce2de4f4 100644 --- a/drivers/usb/gadget/udc/snps_udc_core.c +++ b/drivers/usb/gadget/udc/snps_udc_core.c @@ -209,18 +209,18 @@ static void print_regs(struct udc *dev) if (use_dma && use_dma_ppb && !use_dma_ppb_du) { DBG(dev, "DMA mode = PPBNDU (packet per buffer " "WITHOUT desc. update)\n"); - dev_info(&dev->pdev->dev, "DMA mode (%s)\n", "PPBNDU"); + dev_info(dev->dev, "DMA mode (%s)\n", "PPBNDU"); } else if (use_dma && use_dma_ppb && use_dma_ppb_du) { DBG(dev, "DMA mode = PPBDU (packet per buffer " "WITH desc. update)\n"); - dev_info(&dev->pdev->dev, "DMA mode (%s)\n", "PPBDU"); + dev_info(dev->dev, "DMA mode (%s)\n", "PPBDU"); } if (use_dma && use_dma_bufferfill_mode) { DBG(dev, "DMA mode = BF (buffer fill mode)\n"); - dev_info(&dev->pdev->dev, "DMA mode (%s)\n", "BF"); + dev_info(dev->dev, "DMA mode (%s)\n", "BF"); } if (!use_dma) - dev_info(&dev->pdev->dev, "FIFO mode\n"); + dev_info(dev->dev, "FIFO mode\n"); DBG(dev, "-------------------------------------------------------\n"); } @@ -1624,7 +1624,7 @@ static void udc_setup_endpoints(struct udc *dev) static void usb_connect(struct udc *dev) { - dev_info(&dev->pdev->dev, "USB Connect\n"); + dev_info(dev->dev, "USB Connect\n"); dev->connected = 1; @@ -1642,7 +1642,7 @@ static void usb_connect(struct udc *dev) static void usb_disconnect(struct udc *dev) { - dev_info(&dev->pdev->dev, "USB Disconnect\n"); + dev_info(dev->dev, "USB Disconnect\n"); dev->connected = 0; @@ -2106,7 +2106,7 @@ static irqreturn_t udc_data_out_isr(struct udc *dev, int ep_ix) } /* HE event ? */ if (tmp & AMD_BIT(UDC_EPSTS_HE)) { - dev_err(&dev->pdev->dev, "HE ep%dout occurred\n", ep->num); + dev_err(dev->dev, "HE ep%dout occurred\n", ep->num); /* clear HE */ writel(tmp | AMD_BIT(UDC_EPSTS_HE), &ep->regs->sts); @@ -2305,7 +2305,7 @@ static irqreturn_t udc_data_in_isr(struct udc *dev, int ep_ix) if (use_dma) { /* BNA ? */ if (epsts & AMD_BIT(UDC_EPSTS_BNA)) { - dev_err(&dev->pdev->dev, + dev_err(dev->dev, "BNA ep%din occurred - DESPTR = %08lx\n", ep->num, (unsigned long) readl(&ep->regs->desptr)); @@ -2318,7 +2318,7 @@ static irqreturn_t udc_data_in_isr(struct udc *dev, int ep_ix) } /* HE event ? */ if (epsts & AMD_BIT(UDC_EPSTS_HE)) { - dev_err(&dev->pdev->dev, + dev_err(dev->dev, "HE ep%dn occurred - DESPTR = %08lx\n", ep->num, (unsigned long) readl(&ep->regs->desptr)); @@ -2956,7 +2956,7 @@ __acquires(dev->lock) /* link up all endpoints */ udc_setup_endpoints(dev); - dev_info(&dev->pdev->dev, "Connect: %s\n", + dev_info(dev->dev, "Connect: %s\n", usb_speed_string(dev->gadget.speed)); /* init ep 0 */ @@ -3168,20 +3168,20 @@ int udc_probe(struct udc *dev) /* init registers, interrupts, ... */ startup_registers(dev); - dev_info(&dev->pdev->dev, "%s\n", mod_desc); + dev_info(dev->dev, "%s\n", mod_desc); snprintf(tmp, sizeof(tmp), "%d", dev->irq); - dev_info(&dev->pdev->dev, + dev_info(dev->dev, "irq %s, pci mem %08lx, chip rev %02x(Geode5536 %s)\n", tmp, dev->phys_addr, dev->chiprev, (dev->chiprev == UDC_HSA0_REV) ? "A0" : "B1"); strcpy(tmp, UDC_DRIVER_VERSION_STRING); if (dev->chiprev == UDC_HSA0_REV) { - dev_err(&dev->pdev->dev, "chip revision is A0; too old\n"); + dev_err(dev->dev, "chip revision is A0; too old\n"); retval = -ENODEV; goto finished; } - dev_info(&dev->pdev->dev, + dev_info(dev->dev, "driver version: %s(for Geode5536 B1)\n", tmp); udc = dev; -- cgit v1.2.3 From 7c51247a1f62bce954b9b58246930890b7209ce6 Mon Sep 17 00:00:00 2001 From: Raviteja Garimella Date: Wed, 10 May 2017 18:21:19 +0530 Subject: usb: gadget: udc: Provide correct arguments for 'dma_pool_create' Change the argument from NULL to a struct device for the dma_pool_create call during dma init. Signed-off-by: Raviteja Garimella Signed-off-by: Felipe Balbi --- drivers/usb/gadget/udc/snps_udc_core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/gadget/udc/snps_udc_core.c b/drivers/usb/gadget/udc/snps_udc_core.c index 4c43ce2de4f4..d592f77da744 100644 --- a/drivers/usb/gadget/udc/snps_udc_core.c +++ b/drivers/usb/gadget/udc/snps_udc_core.c @@ -3097,7 +3097,7 @@ int init_dma_pools(struct udc *dev) } /* DMA setup */ - dev->data_requests = dma_pool_create("data_requests", NULL, + dev->data_requests = dma_pool_create("data_requests", dev->dev, sizeof(struct udc_data_dma), 0, 0); if (!dev->data_requests) { DBG(dev, "can't get request data pool\n"); @@ -3108,7 +3108,7 @@ int init_dma_pools(struct udc *dev) dev->ep[UDC_EP0IN_IX].dma = &dev->regs->ctl; /* dma desc for setup data */ - dev->stp_requests = dma_pool_create("setup requests", NULL, + dev->stp_requests = dma_pool_create("setup requests", dev->dev, sizeof(struct udc_stp_dma), 0, 0); if (!dev->stp_requests) { DBG(dev, "can't get stp request pool\n"); -- cgit v1.2.3 From 92122a60d9f0ef3e3bc760e087588f71b2e86c97 Mon Sep 17 00:00:00 2001 From: Raviteja Garimella Date: Wed, 10 May 2017 18:21:20 +0530 Subject: dt-bindings: usb: DT bindings documentation for Broadcom IPROC USB Device controller. The device node is used for UDCs integrated into Broadcom's iProc family of SoCs'. The UDC is based on Synopsys Designware Cores AHB Subsystem USB Device Controller IP. Signed-off-by: Raviteja Garimella Signed-off-by: Felipe Balbi --- Documentation/devicetree/bindings/usb/iproc-udc.txt | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 Documentation/devicetree/bindings/usb/iproc-udc.txt diff --git a/Documentation/devicetree/bindings/usb/iproc-udc.txt b/Documentation/devicetree/bindings/usb/iproc-udc.txt new file mode 100644 index 000000000000..272d7faf1a97 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/iproc-udc.txt @@ -0,0 +1,21 @@ +Broadcom IPROC USB Device controller. + +The device node is used for UDCs integrated into Broadcom's +iProc family (Northstar2, Cygnus) of SoCs'. The UDC is based +on Synopsys Designware Cores AHB Subsystem Device Controller +IP. + +Required properties: + - compatible: Add the compatibility strings for supported platforms. + For Broadcom NS2 platform, add "brcm,ns2-udc","brcm,iproc-udc". + For Broadcom Cygnus platform, add "brcm,cygnus-udc", "brcm,iproc-udc". + - reg: Offset and length of UDC register set + - interrupts: description of interrupt line + - phys: phandle to phy node. + +Example: + udc_dwc: usb@664e0000 { + compatible = "brcm,ns2-udc", "brcm,iproc-udc"; + reg = <0x664e0000 0x2000>; + interrupts = ; + phys = <&usbdrd_phy>; -- cgit v1.2.3 From 1b9f35adb0ffa143c7972a8459d6979c77d6c3c0 Mon Sep 17 00:00:00 2001 From: Raviteja Garimella Date: Wed, 10 May 2017 18:21:21 +0530 Subject: usb: gadget: udc: Add Synopsys UDC Platform driver This patch adds platform driver support for Synopsys UDC. A new driver file (snps_udc_plat.c) is created for this purpose where the platform driver registration is done based on OF node. Currently, UDC integrated into Broadcom's iProc SoCs (Northstar2 and Cygnus) work with this driver. New members are added to the UDC data structure for having platform device support along with extcon and phy support. Kconfig and Makefiles are modified to select platform driver for compilation. Signed-off-by: Raviteja Garimella Signed-off-by: Felipe Balbi --- drivers/usb/gadget/udc/Kconfig | 16 +- drivers/usb/gadget/udc/Makefile | 1 + drivers/usb/gadget/udc/amd5536udc.h | 14 ++ drivers/usb/gadget/udc/snps_udc_core.c | 54 ++++-- drivers/usb/gadget/udc/snps_udc_plat.c | 344 +++++++++++++++++++++++++++++++++ 5 files changed, 409 insertions(+), 20 deletions(-) create mode 100644 drivers/usb/gadget/udc/snps_udc_plat.c diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig index 1c14c283cc47..e5d3ba9a8604 100644 --- a/drivers/usb/gadget/udc/Kconfig +++ b/drivers/usb/gadget/udc/Kconfig @@ -256,7 +256,7 @@ config USB_MV_U3D controller, which support super speed USB peripheral. config USB_SNP_CORE - depends on USB_AMD5536UDC + depends on (USB_AMD5536UDC || USB_SNP_UDC_PLAT) tristate help This enables core driver support for Synopsys USB 2.0 Device @@ -269,6 +269,20 @@ config USB_SNP_CORE This IP is different to the High Speed OTG IP that can be enabled by selecting USB_DWC2 or USB_DWC3 options. +config USB_SNP_UDC_PLAT + tristate "Synopsys USB 2.0 Device controller" + depends on (USB_GADGET && OF) + select USB_GADGET_DUALSPEED + select USB_SNP_CORE + default ARCH_BCM_IPROC + help + This adds Platform Device support for Synopsys Designware core + AHB subsystem USB2.0 Device Controller (UDC). + + This driver works with UDCs integrated into Broadcom's Northstar2 + and Cygnus SoCs. + + If unsure, say N. # # Controllers available in both integrated and discrete versions # diff --git a/drivers/usb/gadget/udc/Makefile b/drivers/usb/gadget/udc/Makefile index 4f4fd626b9ff..ea9e1c7f1923 100644 --- a/drivers/usb/gadget/udc/Makefile +++ b/drivers/usb/gadget/udc/Makefile @@ -37,4 +37,5 @@ obj-$(CONFIG_USB_FOTG210_UDC) += fotg210-udc.o obj-$(CONFIG_USB_MV_U3D) += mv_u3d_core.o obj-$(CONFIG_USB_GR_UDC) += gr_udc.o obj-$(CONFIG_USB_GADGET_XILINX) += udc-xilinx.o +obj-$(CONFIG_USB_SNP_UDC_PLAT) += snps_udc_plat.o obj-$(CONFIG_USB_BDC_UDC) += bdc/ diff --git a/drivers/usb/gadget/udc/amd5536udc.h b/drivers/usb/gadget/udc/amd5536udc.h index 91aae23b7370..4fe22d432af2 100644 --- a/drivers/usb/gadget/udc/amd5536udc.h +++ b/drivers/usb/gadget/udc/amd5536udc.h @@ -16,6 +16,7 @@ /* debug control */ /* #define UDC_VERBOSE */ +#include #include #include @@ -28,6 +29,9 @@ #define UDC_HSA0_REV 1 #define UDC_HSB1_REV 2 +/* Broadcom chip rev. */ +#define UDC_BCM_REV 10 + /* * SETUP usb commands * needed, because some SETUP's are handled in hw, but must be passed to @@ -112,6 +116,7 @@ #define UDC_DEVCTL_BRLEN_MASK 0x00ff0000 #define UDC_DEVCTL_BRLEN_OFS 16 +#define UDC_DEVCTL_SRX_FLUSH 14 #define UDC_DEVCTL_CSR_DONE 13 #define UDC_DEVCTL_DEVNAK 12 #define UDC_DEVCTL_SD 10 @@ -564,7 +569,15 @@ struct udc { u16 cur_intf; u16 cur_alt; + /* for platform device and extcon support */ struct device *dev; + struct phy *udc_phy; + struct extcon_dev *edev; + struct extcon_specific_cable_nb extcon_nb; + struct notifier_block nb; + struct delayed_work drd_work; + struct workqueue_struct *drd_wq; + u32 conn_type; }; #define to_amd5536_udc(g) (container_of((g), struct udc, gadget)) @@ -580,6 +593,7 @@ int udc_enable_dev_setup_interrupts(struct udc *dev); int udc_mask_unused_interrupts(struct udc *dev); irqreturn_t udc_irq(int irq, void *pdev); void gadget_release(struct device *pdev); +void empty_req_queue(struct udc_ep *ep); void udc_basic_init(struct udc *dev); void free_dma_pools(struct udc *dev); int init_dma_pools(struct udc *dev); diff --git a/drivers/usb/gadget/udc/snps_udc_core.c b/drivers/usb/gadget/udc/snps_udc_core.c index d592f77da744..38a165dbf924 100644 --- a/drivers/usb/gadget/udc/snps_udc_core.c +++ b/drivers/usb/gadget/udc/snps_udc_core.c @@ -41,7 +41,6 @@ #include "amd5536udc.h" static void udc_tasklet_disconnect(unsigned long); -static void empty_req_queue(struct udc_ep *); static void udc_setup_endpoints(struct udc *dev); static void udc_soft_reset(struct udc *dev); static struct udc_request *udc_alloc_bna_dummy(struct udc_ep *ep); @@ -1244,7 +1243,7 @@ finished: } /* Empty request queue of an endpoint; caller holds spinlock */ -static void empty_req_queue(struct udc_ep *ep) +void empty_req_queue(struct udc_ep *ep) { struct udc_request *req; @@ -1256,6 +1255,7 @@ static void empty_req_queue(struct udc_ep *ep) complete_req(ep, req, -ESHUTDOWN); } } +EXPORT_SYMBOL_GPL(empty_req_queue); /* Dequeues a request packet, called by gadget driver */ static int udc_dequeue(struct usb_ep *usbep, struct usb_request *usbreq) @@ -1623,6 +1623,9 @@ static void udc_setup_endpoints(struct udc *dev) /* Bringup after Connect event, initial bringup to be ready for ep0 events */ static void usb_connect(struct udc *dev) { + /* Return if already connected */ + if (dev->connected) + return; dev_info(dev->dev, "USB Connect\n"); @@ -1641,6 +1644,9 @@ static void usb_connect(struct udc *dev) */ static void usb_disconnect(struct udc *dev) { + /* Return if already disconnected */ + if (!dev->connected) + return; dev_info(dev->dev, "USB Disconnect\n"); @@ -1715,11 +1721,15 @@ static void udc_soft_reset(struct udc *dev) /* device int. status reset */ writel(UDC_DEV_MSK_DISABLE, &dev->regs->irqsts); - spin_lock_irqsave(&udc_irq_spinlock, flags); - writel(AMD_BIT(UDC_DEVCFG_SOFTRESET), &dev->regs->cfg); - readl(&dev->regs->cfg); - spin_unlock_irqrestore(&udc_irq_spinlock, flags); - + /* Don't do this for Broadcom UDC since this is a reserved + * bit. + */ + if (dev->chiprev != UDC_BCM_REV) { + spin_lock_irqsave(&udc_irq_spinlock, flags); + writel(AMD_BIT(UDC_DEVCFG_SOFTRESET), &dev->regs->cfg); + readl(&dev->regs->cfg); + spin_unlock_irqrestore(&udc_irq_spinlock, flags); + } } /* RDE timer callback to set RDE bit */ @@ -3171,21 +3181,27 @@ int udc_probe(struct udc *dev) dev_info(dev->dev, "%s\n", mod_desc); snprintf(tmp, sizeof(tmp), "%d", dev->irq); - dev_info(dev->dev, - "irq %s, pci mem %08lx, chip rev %02x(Geode5536 %s)\n", - tmp, dev->phys_addr, dev->chiprev, - (dev->chiprev == UDC_HSA0_REV) ? "A0" : "B1"); - strcpy(tmp, UDC_DRIVER_VERSION_STRING); - if (dev->chiprev == UDC_HSA0_REV) { - dev_err(dev->dev, "chip revision is A0; too old\n"); - retval = -ENODEV; - goto finished; + + /* Print this device info for AMD chips only*/ + if (dev->chiprev == UDC_HSA0_REV || + dev->chiprev == UDC_HSB1_REV) { + dev_info(dev->dev, "irq %s, pci mem %08lx, chip rev %02x(Geode5536 %s)\n", + tmp, dev->phys_addr, dev->chiprev, + (dev->chiprev == UDC_HSA0_REV) ? + "A0" : "B1"); + strcpy(tmp, UDC_DRIVER_VERSION_STRING); + if (dev->chiprev == UDC_HSA0_REV) { + dev_err(dev->dev, "chip revision is A0; too old\n"); + retval = -ENODEV; + goto finished; + } + dev_info(dev->dev, + "driver version: %s(for Geode5536 B1)\n", tmp); } - dev_info(dev->dev, - "driver version: %s(for Geode5536 B1)\n", tmp); + udc = dev; - retval = usb_add_gadget_udc_release(&udc->pdev->dev, &dev->gadget, + retval = usb_add_gadget_udc_release(udc->dev, &dev->gadget, gadget_release); if (retval) goto finished; diff --git a/drivers/usb/gadget/udc/snps_udc_plat.c b/drivers/usb/gadget/udc/snps_udc_plat.c new file mode 100644 index 000000000000..2e11f19e07ae --- /dev/null +++ b/drivers/usb/gadget/udc/snps_udc_plat.c @@ -0,0 +1,344 @@ +/* + * snps_udc_plat.c - Synopsys UDC Platform Driver + * + * Copyright (C) 2016 Broadcom + * + * 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. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "amd5536udc.h" + +/* description */ +#define UDC_MOD_DESCRIPTION "Synopsys UDC platform driver" + +void start_udc(struct udc *udc) +{ + if (udc->driver) { + dev_info(udc->dev, "Connecting...\n"); + udc_enable_dev_setup_interrupts(udc); + udc_basic_init(udc); + udc->connected = 1; + } +} + +void stop_udc(struct udc *udc) +{ + int tmp; + u32 reg; + + spin_lock(&udc->lock); + + /* Flush the receieve fifo */ + reg = readl(&udc->regs->ctl); + reg |= AMD_BIT(UDC_DEVCTL_SRX_FLUSH); + writel(reg, &udc->regs->ctl); + + reg = readl(&udc->regs->ctl); + reg &= ~(AMD_BIT(UDC_DEVCTL_SRX_FLUSH)); + writel(reg, &udc->regs->ctl); + dev_dbg(udc->dev, "ep rx queue flushed\n"); + + /* Mask interrupts. Required more so when the + * UDC is connected to a DRD phy. + */ + udc_mask_unused_interrupts(udc); + + /* Disconnect gadget driver */ + if (udc->driver) { + spin_unlock(&udc->lock); + udc->driver->disconnect(&udc->gadget); + spin_lock(&udc->lock); + + /* empty queues */ + for (tmp = 0; tmp < UDC_EP_NUM; tmp++) + empty_req_queue(&udc->ep[tmp]); + } + udc->connected = 0; + + spin_unlock(&udc->lock); + dev_info(udc->dev, "Device disconnected\n"); +} + +void udc_drd_work(struct work_struct *work) +{ + struct udc *udc; + + udc = container_of(to_delayed_work(work), + struct udc, drd_work); + + if (udc->conn_type) { + dev_dbg(udc->dev, "idle -> device\n"); + start_udc(udc); + } else { + dev_dbg(udc->dev, "device -> idle\n"); + stop_udc(udc); + } +} + +static int usbd_connect_notify(struct notifier_block *self, + unsigned long event, void *ptr) +{ + struct udc *udc = container_of(self, struct udc, nb); + + dev_dbg(udc->dev, "%s: event: %lu\n", __func__, event); + + udc->conn_type = event; + + schedule_delayed_work(&udc->drd_work, 0); + + return NOTIFY_OK; +} + +static int udc_plat_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct udc *udc; + int ret; + + udc = devm_kzalloc(dev, sizeof(*udc), GFP_KERNEL); + if (!udc) + return -ENOMEM; + + spin_lock_init(&udc->lock); + udc->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + udc->virt_addr = devm_ioremap_resource(dev, res); + if (IS_ERR(udc->regs)) + return PTR_ERR(udc->regs); + + /* udc csr registers base */ + udc->csr = udc->virt_addr + UDC_CSR_ADDR; + + /* dev registers base */ + udc->regs = udc->virt_addr + UDC_DEVCFG_ADDR; + + /* ep registers base */ + udc->ep_regs = udc->virt_addr + UDC_EPREGS_ADDR; + + /* fifo's base */ + udc->rxfifo = (u32 __iomem *)(udc->virt_addr + UDC_RXFIFO_ADDR); + udc->txfifo = (u32 __iomem *)(udc->virt_addr + UDC_TXFIFO_ADDR); + + udc->phys_addr = (unsigned long)res->start; + + udc->irq = irq_of_parse_and_map(dev->of_node, 0); + if (udc->irq <= 0) { + dev_err(dev, "Can't parse and map interrupt\n"); + return -EINVAL; + } + + udc->udc_phy = devm_of_phy_get_by_index(dev, dev->of_node, 0); + if (IS_ERR(udc->udc_phy)) { + dev_err(dev, "Failed to obtain phy from device tree\n"); + return PTR_ERR(udc->udc_phy); + } + + ret = phy_init(udc->udc_phy); + if (ret) { + dev_err(dev, "UDC phy init failed"); + return ret; + } + + ret = phy_power_on(udc->udc_phy); + if (ret) { + dev_err(dev, "UDC phy power on failed"); + phy_exit(udc->udc_phy); + return ret; + } + + /* Register for extcon if supported */ + if (of_get_property(dev->of_node, "extcon", NULL)) { + udc->edev = extcon_get_edev_by_phandle(dev, 0); + if (IS_ERR(udc->edev)) { + if (PTR_ERR(udc->edev) == -EPROBE_DEFER) + return -EPROBE_DEFER; + dev_err(dev, "Invalid or missing extcon\n"); + ret = PTR_ERR(udc->edev); + goto exit_phy; + } + + udc->nb.notifier_call = usbd_connect_notify; + ret = extcon_register_notifier(udc->edev, EXTCON_USB, + &udc->nb); + if (ret < 0) { + dev_err(dev, "Can't register extcon device\n"); + goto exit_phy; + } + + ret = extcon_get_cable_state_(udc->edev, EXTCON_USB); + if (ret < 0) { + dev_err(dev, "Can't get cable state\n"); + goto exit_extcon; + } else if (ret) { + udc->conn_type = ret; + } + INIT_DELAYED_WORK(&udc->drd_work, udc_drd_work); + } + + /* init dma pools */ + if (use_dma) { + ret = init_dma_pools(udc); + if (ret != 0) + goto exit_extcon; + } + + ret = devm_request_irq(dev, udc->irq, udc_irq, IRQF_SHARED, + "snps-udc", udc); + if (ret < 0) { + dev_err(dev, "Request irq %d failed for UDC\n", udc->irq); + goto exit_dma; + } + + platform_set_drvdata(pdev, udc); + udc->chiprev = UDC_BCM_REV; + + if (udc_probe(udc)) { + ret = -ENODEV; + goto exit_dma; + } + dev_info(dev, "Synopsys UDC platform driver probe successful\n"); + + return 0; + +exit_dma: + if (use_dma) + free_dma_pools(udc); +exit_extcon: + if (udc->edev) + extcon_unregister_notifier(udc->edev, EXTCON_USB, &udc->nb); +exit_phy: + if (udc->udc_phy) { + phy_power_off(udc->udc_phy); + phy_exit(udc->udc_phy); + } + return ret; +} + +static int udc_plat_remove(struct platform_device *pdev) +{ + struct udc *dev; + + dev = platform_get_drvdata(pdev); + + usb_del_gadget_udc(&dev->gadget); + /* gadget driver must not be registered */ + if (WARN_ON(dev->driver)) + return 0; + + /* dma pool cleanup */ + free_dma_pools(dev); + + udc_remove(dev); + + platform_set_drvdata(pdev, NULL); + + if (dev->drd_wq) { + flush_workqueue(dev->drd_wq); + destroy_workqueue(dev->drd_wq); + } + + phy_power_off(dev->udc_phy); + phy_exit(dev->udc_phy); + extcon_unregister_notifier(dev->edev, EXTCON_USB, &dev->nb); + + dev_info(&pdev->dev, "Synopsys UDC platform driver removed\n"); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int udc_plat_suspend(struct device *dev) +{ + struct udc *udc; + + udc = dev_get_drvdata(dev); + stop_udc(udc); + + if (extcon_get_cable_state_(udc->edev, EXTCON_USB) > 0) { + dev_dbg(udc->dev, "device -> idle\n"); + stop_udc(udc); + } + phy_power_off(udc->udc_phy); + phy_exit(udc->udc_phy); + + return 0; +} + +static int udc_plat_resume(struct device *dev) +{ + struct udc *udc; + int ret; + + udc = dev_get_drvdata(dev); + + ret = phy_init(udc->udc_phy); + if (ret) { + dev_err(udc->dev, "UDC phy init failure"); + return ret; + } + + ret = phy_power_on(udc->udc_phy); + if (ret) { + dev_err(udc->dev, "UDC phy power on failure"); + phy_exit(udc->udc_phy); + return ret; + } + + if (extcon_get_cable_state_(udc->edev, EXTCON_USB) > 0) { + dev_dbg(udc->dev, "idle -> device\n"); + start_udc(udc); + } + + return 0; +} +static const struct dev_pm_ops udc_plat_pm_ops = { + .suspend = udc_plat_suspend, + .resume = udc_plat_resume, +}; +#endif + +#if defined(CONFIG_OF) +static const struct of_device_id of_udc_match[] = { + { .compatible = "brcm,ns2-udc", }, + { .compatible = "brcm,cygnus-udc", }, + { .compatible = "brcm,iproc-udc", }, + { } +}; +MODULE_DEVICE_TABLE(of, of_udc_match); +#endif + +static struct platform_driver udc_plat_driver = { + .probe = udc_plat_probe, + .remove = udc_plat_remove, + .driver = { + .name = "snps-udc-plat", + .of_match_table = of_match_ptr(of_udc_match), +#ifdef CONFIG_PM_SLEEP + .pm = &udc_plat_pm_ops, +#endif + }, +}; +module_platform_driver(udc_plat_driver); + +MODULE_DESCRIPTION(UDC_MOD_DESCRIPTION); +MODULE_AUTHOR("Broadcom"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 67fdfda4a99edea939a63bad1797d69dd8de00d6 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Tue, 6 Jun 2017 16:03:19 +0300 Subject: usb: gadget: core: introduce ->udc_set_speed() method Sometimes, the gadget driver we want to run has max_speed lower than what the UDC supports. In such situations, UDC might want to make sure we don't try to connect on speeds not supported by the gadget driver (e.g. super-speed capable dwc3 with high-speed capable g_midi) because that will just fail. In order to make sure this situation never happens, we introduce a new optional ->udc_set_speed() method which can be implemented by interested UDC drivers. Signed-off-by: Felipe Balbi --- drivers/usb/gadget/udc/core.c | 20 ++++++++++++++++++++ include/linux/usb/gadget.h | 1 + 2 files changed, 21 insertions(+) diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c index 62d52fc0fcdc..eec388bccb25 100644 --- a/drivers/usb/gadget/udc/core.c +++ b/drivers/usb/gadget/udc/core.c @@ -1055,6 +1055,23 @@ static inline void usb_gadget_udc_stop(struct usb_udc *udc) udc->gadget->ops->udc_stop(udc->gadget); } +/** + * usb_gadget_udc_set_speed - tells usb device controller speed supported by + * current driver + * @udc: The device we want to set maximum speed + * @speed: The maximum speed to allowed to run + * + * This call is issued by the UDC Class driver before calling + * usb_gadget_udc_start() in order to make sure that we don't try to + * connect on speeds the gadget driver doesn't support. + */ +static inline void usb_gadget_udc_set_speed(struct usb_udc *udc, + enum usb_device_speed speed) +{ + if (udc->gadget->ops->udc_set_speed) + udc->gadget->ops->udc_set_speed(udc->gadget, speed); +} + /** * usb_udc_release - release the usb_udc struct * @dev: the dev member within usb_udc @@ -1288,6 +1305,9 @@ static int udc_bind_to_driver(struct usb_udc *udc, struct usb_gadget_driver *dri udc->dev.driver = &driver->driver; udc->gadget->dev.driver = &driver->driver; + if (driver->max_speed < udc->gadget->max_speed) + usb_gadget_udc_set_speed(udc, driver->max_speed); + ret = driver->bind(udc->gadget, driver); if (ret) goto err1; diff --git a/include/linux/usb/gadget.h b/include/linux/usb/gadget.h index 3ee5f2a7c0b4..1a4a4bacfae6 100644 --- a/include/linux/usb/gadget.h +++ b/include/linux/usb/gadget.h @@ -304,6 +304,7 @@ struct usb_gadget_ops { int (*udc_start)(struct usb_gadget *, struct usb_gadget_driver *); int (*udc_stop)(struct usb_gadget *); + void (*udc_set_speed)(struct usb_gadget *, enum usb_device_speed); struct usb_ep *(*match_ep)(struct usb_gadget *, struct usb_endpoint_descriptor *, struct usb_ss_ep_comp_descriptor *); -- cgit v1.2.3 From 7d8d0639565ff22d5fad419c2f2887213d7c2915 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Tue, 6 Jun 2017 16:05:23 +0300 Subject: usb: dwc3: gadget: implement ->udc_set_speed() Use this method to make sure we don't try to connect on speeds not supported by the gadget driver. Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/gadget.c | 101 ++++++++++++++++++++++++++-------------------- 1 file changed, 58 insertions(+), 43 deletions(-) diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index d2bd28dc28b6..01cd7ddc9981 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -1827,49 +1827,6 @@ static int __dwc3_gadget_start(struct dwc3 *dwc) dwc3_writel(dwc->regs, DWC3_DEV_IMOD(0), 0); } - reg = dwc3_readl(dwc->regs, DWC3_DCFG); - reg &= ~(DWC3_DCFG_SPEED_MASK); - - /* - * WORKAROUND: DWC3 revision < 2.20a have an issue - * which would cause metastability state on Run/Stop - * bit if we try to force the IP to USB2-only mode. - * - * Because of that, we cannot configure the IP to any - * speed other than the SuperSpeed - * - * Refers to: - * - * STAR#9000525659: Clock Domain Crossing on DCTL in - * USB 2.0 Mode - */ - if (dwc->revision < DWC3_REVISION_220A) { - reg |= DWC3_DCFG_SUPERSPEED; - } else { - switch (dwc->maximum_speed) { - case USB_SPEED_LOW: - reg |= DWC3_DCFG_LOWSPEED; - break; - case USB_SPEED_FULL: - reg |= DWC3_DCFG_FULLSPEED; - break; - case USB_SPEED_HIGH: - reg |= DWC3_DCFG_HIGHSPEED; - break; - case USB_SPEED_SUPER_PLUS: - reg |= DWC3_DCFG_SUPERSPEED_PLUS; - break; - default: - dev_err(dwc->dev, "invalid dwc->maximum_speed (%d)\n", - dwc->maximum_speed); - /* fall through */ - case USB_SPEED_SUPER: - reg |= DWC3_DCFG_SUPERSPEED; - break; - } - } - dwc3_writel(dwc->regs, DWC3_DCFG, reg); - /* * We are telling dwc3 that we want to use DCFG.NUMP as ACK TP's NUMP * field instead of letting dwc3 itself calculate that automatically. @@ -2001,6 +1958,63 @@ out: return 0; } +static void dwc3_gadget_set_speed(struct usb_gadget *g, + enum usb_device_speed speed) +{ + struct dwc3 *dwc = gadget_to_dwc(g); + unsigned long flags; + u32 reg; + + spin_lock_irqsave(&dwc->lock, flags); + reg = dwc3_readl(dwc->regs, DWC3_DCFG); + reg &= ~(DWC3_DCFG_SPEED_MASK); + + /* + * WORKAROUND: DWC3 revision < 2.20a have an issue + * which would cause metastability state on Run/Stop + * bit if we try to force the IP to USB2-only mode. + * + * Because of that, we cannot configure the IP to any + * speed other than the SuperSpeed + * + * Refers to: + * + * STAR#9000525659: Clock Domain Crossing on DCTL in + * USB 2.0 Mode + */ + if (dwc->revision < DWC3_REVISION_220A) { + reg |= DWC3_DCFG_SUPERSPEED; + } else { + switch (speed) { + case USB_SPEED_LOW: + reg |= DWC3_DCFG_LOWSPEED; + break; + case USB_SPEED_FULL: + reg |= DWC3_DCFG_FULLSPEED; + break; + case USB_SPEED_HIGH: + reg |= DWC3_DCFG_HIGHSPEED; + break; + case USB_SPEED_SUPER: + reg |= DWC3_DCFG_SUPERSPEED; + break; + case USB_SPEED_SUPER_PLUS: + reg |= DWC3_DCFG_SUPERSPEED_PLUS; + break; + default: + dev_err(dwc->dev, "invalid speed (%d)\n", speed); + + if (dwc->revision & DWC3_REVISION_IS_DWC31) + reg |= DWC3_DCFG_SUPERSPEED_PLUS; + else + reg |= DWC3_DCFG_SUPERSPEED; + } + } + dwc3_writel(dwc->regs, DWC3_DCFG, reg); + + spin_unlock_irqrestore(&dwc->lock, flags); +} + static const struct usb_gadget_ops dwc3_gadget_ops = { .get_frame = dwc3_gadget_get_frame, .wakeup = dwc3_gadget_wakeup, @@ -2008,6 +2022,7 @@ static const struct usb_gadget_ops dwc3_gadget_ops = { .pullup = dwc3_gadget_pullup, .udc_start = dwc3_gadget_start, .udc_stop = dwc3_gadget_stop, + .udc_set_speed = dwc3_gadget_set_speed, }; /* -------------------------------------------------------------------------- */ -- cgit v1.2.3 From 06644aafb042a20802504d3447bfb0b64985e15d Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Wed, 7 Jun 2017 13:52:54 +0300 Subject: usb: gadget: dummy: implement ->udc_set_speed() Move the code which was part of pullup() to the newly introduced method. Signed-off-by: Felipe Balbi --- drivers/usb/gadget/udc/dummy_hcd.c | 39 ++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/drivers/usb/gadget/udc/dummy_hcd.c b/drivers/usb/gadget/udc/dummy_hcd.c index c79081952ea0..156380e4e0af 100644 --- a/drivers/usb/gadget/udc/dummy_hcd.c +++ b/drivers/usb/gadget/udc/dummy_hcd.c @@ -888,22 +888,6 @@ static int dummy_pullup(struct usb_gadget *_gadget, int value) unsigned long flags; dum = gadget_dev_to_dummy(&_gadget->dev); - - if (value && dum->driver) { - if (mod_data.is_super_speed) - dum->gadget.speed = dum->driver->max_speed; - else if (mod_data.is_high_speed) - dum->gadget.speed = min_t(u8, USB_SPEED_HIGH, - dum->driver->max_speed); - else - dum->gadget.speed = USB_SPEED_FULL; - dummy_udc_update_ep0(dum); - - if (dum->gadget.speed < dum->driver->max_speed) - dev_dbg(udc_dev(dum), "This device can perform faster" - " if you connect it to a %s port...\n", - usb_speed_string(dum->driver->max_speed)); - } dum_hcd = gadget_to_dummy_hcd(_gadget); spin_lock_irqsave(&dum->lock, flags); @@ -915,6 +899,28 @@ static int dummy_pullup(struct usb_gadget *_gadget, int value) return 0; } +static void dummy_udc_set_speed(struct usb_gadget *_gadget, + enum usb_device_speed speed) +{ + struct dummy *dum; + + dum = gadget_dev_to_dummy(&_gadget->dev); + + if (mod_data.is_super_speed) + dum->gadget.speed = min_t(u8, USB_SPEED_SUPER, speed); + else if (mod_data.is_high_speed) + dum->gadget.speed = min_t(u8, USB_SPEED_HIGH, speed); + else + dum->gadget.speed = USB_SPEED_FULL; + + dummy_udc_update_ep0(dum); + + if (dum->gadget.speed < speed) + dev_dbg(udc_dev(dum), "This device can perform faster" + " if you connect it to a %s port...\n", + usb_speed_string(speed)); +} + static int dummy_udc_start(struct usb_gadget *g, struct usb_gadget_driver *driver); static int dummy_udc_stop(struct usb_gadget *g); @@ -926,6 +932,7 @@ static const struct usb_gadget_ops dummy_ops = { .pullup = dummy_pullup, .udc_start = dummy_udc_start, .udc_stop = dummy_udc_stop, + .udc_set_speed = dummy_udc_set_speed, }; /*-------------------------------------------------------------------------*/ -- cgit v1.2.3 From 104165686ea388ddec6ec2834bf2c13873fbe91a Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Wed, 7 Jun 2017 14:07:01 +0300 Subject: usb: gadget: udc: add a 'function' sysfs file This file will print out the name of the currently running USB Gadget Driver. It can be read even when there are no functions loaded. Suggested-by: Alan Stern Signed-off-by: Felipe Balbi --- Documentation/ABI/stable/sysfs-class-udc | 8 ++++++++ drivers/usb/gadget/udc/core.c | 13 +++++++++++++ 2 files changed, 21 insertions(+) diff --git a/Documentation/ABI/stable/sysfs-class-udc b/Documentation/ABI/stable/sysfs-class-udc index 85d3dac2e204..7370deb87f87 100644 --- a/Documentation/ABI/stable/sysfs-class-udc +++ b/Documentation/ABI/stable/sysfs-class-udc @@ -91,3 +91,11 @@ Description: 'configured', and 'suspended'; however not all USB Device Controllers support reporting all states. Users: + +What: /sys/class/udc//function +Date: June 2017 +KernelVersion: 4.13 +Contact: Felipe Balbi +Description: + Prints out name of currently running USB Gadget Driver. +Users: diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c index eec388bccb25..08facbada30e 100644 --- a/drivers/usb/gadget/udc/core.c +++ b/drivers/usb/gadget/udc/core.c @@ -1460,6 +1460,18 @@ static ssize_t state_show(struct device *dev, struct device_attribute *attr, } static DEVICE_ATTR_RO(state); +static ssize_t function_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_udc *udc = container_of(dev, struct usb_udc, dev); + struct usb_gadget_driver *drv = udc->driver; + + if (!drv || !drv->function) + return 0; + return scnprintf(buf, PAGE_SIZE, "%s\n", drv->function); +} +static DEVICE_ATTR_RO(function); + #define USB_UDC_SPEED_ATTR(name, param) \ ssize_t name##_show(struct device *dev, \ struct device_attribute *attr, char *buf) \ @@ -1495,6 +1507,7 @@ static struct attribute *usb_udc_attrs[] = { &dev_attr_srp.attr, &dev_attr_soft_connect.attr, &dev_attr_state.attr, + &dev_attr_function.attr, &dev_attr_current_speed.attr, &dev_attr_maximum_speed.attr, -- cgit v1.2.3 From 1aed4178ec7d4350019bc4211463deb5543e3e95 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Wed, 7 Jun 2017 14:08:18 +0300 Subject: Documentation: ABI: sysfs-class-udc: remove duplicated entry maximum_speed entry was duplicated. Remove one instance. Signed-off-by: Felipe Balbi --- Documentation/ABI/stable/sysfs-class-udc | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Documentation/ABI/stable/sysfs-class-udc b/Documentation/ABI/stable/sysfs-class-udc index 7370deb87f87..d1e2f3ec1fc9 100644 --- a/Documentation/ABI/stable/sysfs-class-udc +++ b/Documentation/ABI/stable/sysfs-class-udc @@ -55,14 +55,6 @@ Description: Indicates the maximum USB speed supported by this port. Users: -What: /sys/class/udc//maximum_speed -Date: June 2011 -KernelVersion: 3.1 -Contact: Felipe Balbi -Description: - Indicates the maximum USB speed supported by this port. -Users: - What: /sys/class/udc//soft_connect Date: June 2011 KernelVersion: 3.1 -- cgit v1.2.3 From 9b0a1f95c4b48a18ae95e204b1bcde47436b0248 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Thu, 8 Jun 2017 13:16:18 +0300 Subject: usb: dwc3: ep0: make sure wValue is 0 on GetStatus() We don't (yet) support PTM_STATUS messages so let's not reply to them erroneously. Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/ep0.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c index 8cfce8425101..827e376bfa97 100644 --- a/drivers/usb/dwc3/ep0.c +++ b/drivers/usb/dwc3/ep0.c @@ -319,10 +319,16 @@ static int dwc3_ep0_handle_status(struct dwc3 *dwc, { struct dwc3_ep *dep; u32 recip; + u32 value; u32 reg; u16 usb_status = 0; __le16 *response_pkt; + /* We don't support PTM_STATUS */ + value = le16_to_cpu(ctrl->wValue); + if (value != 0) + return -EINVAL; + recip = ctrl->bRequestType & USB_RECIP_MASK; switch (recip) { case USB_RECIP_DEVICE: -- cgit v1.2.3 From e0082698b68981c8456061e509eb32c5f65b228c Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Mon, 5 Jun 2017 17:01:22 +0300 Subject: usb: dwc3: ulpi: conditionally resume ULPI PHY If PHY is suspended by the time we want to issue ULPI transfers, we will observe timeouts on the ULPI interface. In order to avoid such issue, let's make sure PHY is resumed before issuing a ULPI transfer. Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/ulpi.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/usb/dwc3/ulpi.c b/drivers/usb/dwc3/ulpi.c index bd86f84f3790..e87ce8e9edee 100644 --- a/drivers/usb/dwc3/ulpi.c +++ b/drivers/usb/dwc3/ulpi.c @@ -41,6 +41,12 @@ static int dwc3_ulpi_read(struct device *dev, u8 addr) u32 reg; int ret; + reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0)); + if (reg & DWC3_GUSB2PHYCFG_SUSPHY) { + reg &= ~DWC3_GUSB2PHYCFG_SUSPHY; + dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg); + } + reg = DWC3_GUSB2PHYACC_NEWREGREQ | DWC3_ULPI_ADDR(addr); dwc3_writel(dwc->regs, DWC3_GUSB2PHYACC(0), reg); @@ -58,6 +64,12 @@ static int dwc3_ulpi_write(struct device *dev, u8 addr, u8 val) struct dwc3 *dwc = dev_get_drvdata(dev); u32 reg; + reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0)); + if (reg & DWC3_GUSB2PHYCFG_SUSPHY) { + reg &= ~DWC3_GUSB2PHYCFG_SUSPHY; + dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg); + } + reg = DWC3_GUSB2PHYACC_NEWREGREQ | DWC3_ULPI_ADDR(addr); reg |= DWC3_GUSB2PHYACC_WRITE | val; dwc3_writel(dwc->regs, DWC3_GUSB2PHYACC(0), reg); -- cgit v1.2.3 From f54edb539c1167e7a96073848d0afad100df4580 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Mon, 5 Jun 2017 17:03:18 +0300 Subject: usb: dwc3: core: initialize ULPI before trying to get the PHY If don't reorder initialization like this, we will never be able to get a reference to ULPI PHYs. Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/core.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 9d5a67cc2645..25165a97db5e 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -721,6 +721,8 @@ static void dwc3_core_setup_global_control(struct dwc3 *dwc) dwc3_writel(dwc->regs, DWC3_GCTL, reg); } +static int dwc3_core_get_phy(struct dwc3 *dwc); + /** * dwc3_core_init - Low-level initialization of DWC3 Core * @dwc: Pointer to our controller context structure @@ -759,6 +761,10 @@ static int dwc3_core_init(struct dwc3 *dwc) if (ret) goto err0; + ret = dwc3_core_get_phy(dwc); + if (ret) + goto err0; + dwc3_core_setup_global_control(dwc); dwc3_core_num_eps(dwc); @@ -1156,10 +1162,6 @@ static int dwc3_probe(struct platform_device *pdev) platform_set_drvdata(pdev, dwc); dwc3_cache_hwparams(dwc); - ret = dwc3_core_get_phy(dwc); - if (ret) - goto err0; - spin_lock_init(&dwc->lock); pm_runtime_set_active(dev); -- cgit v1.2.3 From 958d1a4c40ddc24e02683074d04ad5cb30380705 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Mon, 5 Jun 2017 17:22:10 +0300 Subject: usb: dwc3: core: program PHY for proper DRD modes If PHY is entering Host mode, we need to enable VBUS. Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/core.c | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 25165a97db5e..326b302fc440 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -151,11 +151,24 @@ static void __dwc3_set_mode(struct work_struct *work) switch (dwc->desired_dr_role) { case DWC3_GCTL_PRTCAP_HOST: ret = dwc3_host_init(dwc); - if (ret) + if (ret) { dev_err(dwc->dev, "failed to initialize host\n"); + } else { + if (dwc->usb2_phy) + otg_set_vbus(dwc->usb2_phy->otg, true); + if (dwc->usb2_generic_phy) + phy_set_mode(dwc->usb2_generic_phy, PHY_MODE_USB_HOST); + + } break; case DWC3_GCTL_PRTCAP_DEVICE: dwc3_event_buffers_setup(dwc); + + if (dwc->usb2_phy) + otg_set_vbus(dwc->usb2_phy->otg, false); + if (dwc->usb2_generic_phy) + phy_set_mode(dwc->usb2_generic_phy, PHY_MODE_USB_DEVICE); + ret = dwc3_gadget_init(dwc); if (ret) dev_err(dwc->dev, "failed to initialize peripheral\n"); @@ -915,6 +928,12 @@ static int dwc3_core_init_mode(struct dwc3 *dwc) switch (dwc->dr_mode) { case USB_DR_MODE_PERIPHERAL: dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_DEVICE); + + if (dwc->usb2_phy) + otg_set_vbus(dwc->usb2_phy->otg, false); + if (dwc->usb2_generic_phy) + phy_set_mode(dwc->usb2_generic_phy, PHY_MODE_USB_DEVICE); + ret = dwc3_gadget_init(dwc); if (ret) { if (ret != -EPROBE_DEFER) @@ -924,6 +943,12 @@ static int dwc3_core_init_mode(struct dwc3 *dwc) break; case USB_DR_MODE_HOST: dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_HOST); + + if (dwc->usb2_phy) + otg_set_vbus(dwc->usb2_phy->otg, true); + if (dwc->usb2_generic_phy) + phy_set_mode(dwc->usb2_generic_phy, PHY_MODE_USB_HOST); + ret = dwc3_host_init(dwc); if (ret) { if (ret != -EPROBE_DEFER) -- cgit v1.2.3 From e6d385692a98926c2bec9dc8e5050076bbcc4e37 Mon Sep 17 00:00:00 2001 From: Arvind Yadav Date: Fri, 9 Jun 2017 17:33:31 +0530 Subject: usb: mtu3: Handle return value of clk_prepare_enable clk_prepare_enable() can fail here and we must check its return value. Signed-off-by: Arvind Yadav Signed-off-by: Felipe Balbi --- drivers/usb/mtu3/mtu3_plat.c | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/drivers/usb/mtu3/mtu3_plat.c b/drivers/usb/mtu3/mtu3_plat.c index 42550c7db3e7..0d3ebb353e08 100644 --- a/drivers/usb/mtu3/mtu3_plat.c +++ b/drivers/usb/mtu3/mtu3_plat.c @@ -458,6 +458,7 @@ static int __maybe_unused mtu3_resume(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct ssusb_mtk *ssusb = platform_get_drvdata(pdev); + int ret; dev_dbg(dev, "%s\n", __func__); @@ -465,12 +466,28 @@ static int __maybe_unused mtu3_resume(struct device *dev) return 0; ssusb_wakeup_disable(ssusb); - clk_prepare_enable(ssusb->sys_clk); - clk_prepare_enable(ssusb->ref_clk); - ssusb_phy_power_on(ssusb); + ret = clk_prepare_enable(ssusb->sys_clk); + if (ret) + goto err_sys_clk; + + ret = clk_prepare_enable(ssusb->ref_clk); + if (ret) + goto err_ref_clk; + + ret = ssusb_phy_power_on(ssusb); + if (ret) + goto err_power_on; + ssusb_host_enable(ssusb); return 0; + +err_power_on: + clk_disable_unprepare(ssusb->ref_clk); +err_ref_clk: + clk_disable_unprepare(ssusb->sys_clk); +err_sys_clk: + return ret; } static const struct dev_pm_ops mtu3_pm_ops = { -- cgit v1.2.3 From 46ddd79e893bca1ec65bf2d20ba17ffabf25efb9 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 7 Jun 2017 18:21:04 +0300 Subject: usb: gadget: udc: atmel: Remove AVR32 bits from the driver AVR32 is gone. Now it's time to clean up the driver by removing leftovers that was used by AVR32 related code. Signed-off-by: Andy Shevchenko Signed-off-by: Felipe Balbi --- drivers/usb/gadget/udc/Kconfig | 2 +- drivers/usb/gadget/udc/atmel_usba_udc.h | 11 ----------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig index e5d3ba9a8604..9ffb11ec9ed9 100644 --- a/drivers/usb/gadget/udc/Kconfig +++ b/drivers/usb/gadget/udc/Kconfig @@ -55,7 +55,7 @@ config USB_LPC32XX config USB_ATMEL_USBA tristate "Atmel USBA" - depends on ((AVR32 && !OF) || ARCH_AT91) + depends on ARCH_AT91 help USBA is the integrated high-speed USB Device controller on the AT32AP700x, some AT91SAM9 and AT91CAP9 processors from Atmel. diff --git a/drivers/usb/gadget/udc/atmel_usba_udc.h b/drivers/usb/gadget/udc/atmel_usba_udc.h index 9551b704bfd3..433bed0c481e 100644 --- a/drivers/usb/gadget/udc/atmel_usba_udc.h +++ b/drivers/usb/gadget/udc/atmel_usba_udc.h @@ -43,13 +43,8 @@ #define USBA_REMOTE_WAKE_UP (1 << 10) #define USBA_PULLD_DIS (1 << 11) -#if defined(CONFIG_AVR32) -#define USBA_ENABLE_MASK USBA_EN_USBA -#define USBA_DISABLE_MASK 0 -#elif defined(CONFIG_ARCH_AT91) #define USBA_ENABLE_MASK (USBA_EN_USBA | USBA_PULLD_DIS) #define USBA_DISABLE_MASK USBA_DETACH -#endif /* CONFIG_ARCH_AT91 */ /* Bitfields in FNUM */ #define USBA_MICRO_FRAME_NUM_OFFSET 0 @@ -191,15 +186,9 @@ | USBA_BF(name, value)) /* Register access macros */ -#ifdef CONFIG_AVR32 -#define usba_io_readl __raw_readl -#define usba_io_writel __raw_writel -#define usba_io_writew __raw_writew -#else #define usba_io_readl readl_relaxed #define usba_io_writel writel_relaxed #define usba_io_writew writew_relaxed -#endif #define usba_readl(udc, reg) \ usba_io_readl((udc)->regs + USBA_##reg) -- cgit v1.2.3 From 2d4aa21a73ba1019195f2200361c0fabe6f7d261 Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda Date: Tue, 6 Jun 2017 20:24:20 +0900 Subject: usb: gadget: udc: renesas_usb3: add support for dedicated DMAC The USB3.0 peripheral controller on R-Car SoCs has a dedicated DMAC. The DMAC needs a "PRD table" in system memory and the DMAC can have four PRD tables. This patch adds support for the DMAC. Signed-off-by: Yoshihiro Shimoda Signed-off-by: Felipe Balbi --- drivers/usb/gadget/udc/renesas_usb3.c | 392 ++++++++++++++++++++++++++++++++++ 1 file changed, 392 insertions(+) diff --git a/drivers/usb/gadget/udc/renesas_usb3.c b/drivers/usb/gadget/udc/renesas_usb3.c index 5a2d845fb1a6..078f7737ed6f 100644 --- a/drivers/usb/gadget/udc/renesas_usb3.c +++ b/drivers/usb/gadget/udc/renesas_usb3.c @@ -9,6 +9,7 @@ */ #include +#include #include #include #include @@ -27,6 +28,8 @@ #define USB3_AXI_INT_ENA 0x00c #define USB3_DMA_INT_STA 0x010 #define USB3_DMA_INT_ENA 0x014 +#define USB3_DMA_CH0_CON(n) (0x030 + ((n) - 1) * 0x10) /* n = 1 to 4 */ +#define USB3_DMA_CH0_PRD_ADR(n) (0x034 + ((n) - 1) * 0x10) /* n = 1 to 4 */ #define USB3_USB_COM_CON 0x200 #define USB3_USB20_CON 0x204 #define USB3_USB30_CON 0x208 @@ -64,6 +67,22 @@ /* AXI_INT_ENA and AXI_INT_STA */ #define AXI_INT_DMAINT BIT(31) #define AXI_INT_EPCINT BIT(30) +/* PRD's n = from 1 to 4 */ +#define AXI_INT_PRDEN_CLR_STA_SHIFT(n) (16 + (n) - 1) +#define AXI_INT_PRDERR_STA_SHIFT(n) (0 + (n) - 1) +#define AXI_INT_PRDEN_CLR_STA(n) (1 << AXI_INT_PRDEN_CLR_STA_SHIFT(n)) +#define AXI_INT_PRDERR_STA(n) (1 << AXI_INT_PRDERR_STA_SHIFT(n)) + +/* DMA_INT_ENA and DMA_INT_STA */ +#define DMA_INT(n) BIT(n) + +/* DMA_CH0_CONn */ +#define DMA_CON_PIPE_DIR BIT(15) /* 1: In Transfer */ +#define DMA_CON_PIPE_NO_SHIFT 8 +#define DMA_CON_PIPE_NO_MASK GENMASK(12, DMA_CON_PIPE_NO_SHIFT) +#define DMA_COM_PIPE_NO(n) (((n) << DMA_CON_PIPE_NO_SHIFT) & \ + DMA_CON_PIPE_NO_MASK) +#define DMA_CON_PRD_EN BIT(0) /* LCLKSEL */ #define LCLKSEL_LSEL BIT(18) @@ -231,8 +250,50 @@ #define USB3_EP0_BUF_SIZE 8 #define USB3_MAX_NUM_PIPES 30 #define USB3_WAIT_US 3 +#define USB3_DMA_NUM_SETTING_AREA 4 +/* + * To avoid double-meaning of "0" (xferred 65536 bytes or received zlp if + * buffer size is 65536), this driver uses the maximum size per a entry is + * 32768 bytes. + */ +#define USB3_DMA_MAX_XFER_SIZE 32768 +#define USB3_DMA_PRD_SIZE 4096 struct renesas_usb3; + +/* Physical Region Descriptor Table */ +struct renesas_usb3_prd { + u32 word1; +#define USB3_PRD1_E BIT(30) /* the end of chain */ +#define USB3_PRD1_U BIT(29) /* completion of transfer */ +#define USB3_PRD1_D BIT(28) /* Error occurred */ +#define USB3_PRD1_INT BIT(27) /* Interrupt occurred */ +#define USB3_PRD1_LST BIT(26) /* Last Packet */ +#define USB3_PRD1_B_INC BIT(24) +#define USB3_PRD1_MPS_8 0 +#define USB3_PRD1_MPS_16 BIT(21) +#define USB3_PRD1_MPS_32 BIT(22) +#define USB3_PRD1_MPS_64 (BIT(22) | BIT(21)) +#define USB3_PRD1_MPS_512 BIT(23) +#define USB3_PRD1_MPS_1024 (BIT(23) | BIT(21)) +#define USB3_PRD1_MPS_RESERVED (BIT(23) | BIT(22) | BIT(21)) +#define USB3_PRD1_SIZE_MASK GENMASK(15, 0) + + u32 bap; +}; +#define USB3_DMA_NUM_PRD_ENTRIES (USB3_DMA_PRD_SIZE / \ + sizeof(struct renesas_usb3_prd)) +#define USB3_DMA_MAX_XFER_SIZE_ALL_PRDS (USB3_DMA_PRD_SIZE / \ + sizeof(struct renesas_usb3_prd) * \ + USB3_DMA_MAX_XFER_SIZE) + +struct renesas_usb3_dma { + struct renesas_usb3_prd *prd; + dma_addr_t prd_dma; + int num; /* Setting area number (from 1 to 4) */ + bool used; +}; + struct renesas_usb3_request { struct usb_request req; struct list_head queue; @@ -242,6 +303,7 @@ struct renesas_usb3_request { struct renesas_usb3_ep { struct usb_ep ep; struct renesas_usb3 *usb3; + struct renesas_usb3_dma *dma; int num; char ep_name[USB3_EP_NAME_SIZE]; struct list_head queue; @@ -270,6 +332,8 @@ struct renesas_usb3 { struct renesas_usb3_ep *usb3_ep; int num_usb3_eps; + struct renesas_usb3_dma dma[USB3_DMA_NUM_SETTING_AREA]; + spinlock_t lock; int disabled_count; @@ -298,8 +362,18 @@ struct renesas_usb3 { (i) < (usb3)->num_usb3_eps; \ (i)++, usb3_ep = usb3_get_ep(usb3, (i))) +#define usb3_get_dma(usb3, i) (&(usb3)->dma[i]) +#define usb3_for_each_dma(usb3, dma, i) \ + for ((i) = 0, dma = usb3_get_dma((usb3), (i)); \ + (i) < USB3_DMA_NUM_SETTING_AREA; \ + (i)++, dma = usb3_get_dma((usb3), (i))) + static const char udc_name[] = "renesas_usb3"; +static bool use_dma = 1; +module_param(use_dma, bool, 0644); +MODULE_PARM_DESC(use_dma, "use dedicated DMAC"); + static void usb3_write(struct renesas_usb3 *usb3, u32 data, u32 offs) { iowrite32(data, usb3->reg + offs); @@ -1060,6 +1134,273 @@ static void usb3_start_pipe0(struct renesas_usb3_ep *usb3_ep, usb3_p0_xfer(usb3_ep, usb3_req); } +static void usb3_enable_dma_pipen(struct renesas_usb3 *usb3) +{ + usb3_set_bit(usb3, PN_CON_DATAIF_EN, USB3_PN_CON); +} + +static void usb3_disable_dma_pipen(struct renesas_usb3 *usb3) +{ + usb3_clear_bit(usb3, PN_CON_DATAIF_EN, USB3_PN_CON); +} + +static void usb3_enable_dma_irq(struct renesas_usb3 *usb3, int num) +{ + usb3_set_bit(usb3, DMA_INT(num), USB3_DMA_INT_ENA); +} + +static void usb3_disable_dma_irq(struct renesas_usb3 *usb3, int num) +{ + usb3_clear_bit(usb3, DMA_INT(num), USB3_DMA_INT_ENA); +} + +static u32 usb3_dma_mps_to_prd_word1(struct renesas_usb3_ep *usb3_ep) +{ + switch (usb3_ep->ep.maxpacket) { + case 8: + return USB3_PRD1_MPS_8; + case 16: + return USB3_PRD1_MPS_16; + case 32: + return USB3_PRD1_MPS_32; + case 64: + return USB3_PRD1_MPS_64; + case 512: + return USB3_PRD1_MPS_512; + case 1024: + return USB3_PRD1_MPS_1024; + default: + return USB3_PRD1_MPS_RESERVED; + } +} + +static bool usb3_dma_get_setting_area(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + struct renesas_usb3_dma *dma; + int i; + bool ret = false; + + if (usb3_req->req.length > USB3_DMA_MAX_XFER_SIZE_ALL_PRDS) { + dev_dbg(usb3_to_dev(usb3), "%s: the length is too big (%d)\n", + __func__, usb3_req->req.length); + return false; + } + + /* The driver doesn't handle zero-length packet via dmac */ + if (!usb3_req->req.length) + return false; + + if (usb3_dma_mps_to_prd_word1(usb3_ep) == USB3_PRD1_MPS_RESERVED) + return false; + + usb3_for_each_dma(usb3, dma, i) { + if (dma->used) + continue; + + if (usb_gadget_map_request(&usb3->gadget, &usb3_req->req, + usb3_ep->dir_in) < 0) + break; + + dma->used = true; + usb3_ep->dma = dma; + ret = true; + break; + } + + return ret; +} + +static void usb3_dma_put_setting_area(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + int i; + struct renesas_usb3_dma *dma; + + usb3_for_each_dma(usb3, dma, i) { + if (usb3_ep->dma == dma) { + usb_gadget_unmap_request(&usb3->gadget, &usb3_req->req, + usb3_ep->dir_in); + dma->used = false; + usb3_ep->dma = NULL; + break; + } + } +} + +static void usb3_dma_fill_prd(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req) +{ + struct renesas_usb3_prd *cur_prd = usb3_ep->dma->prd; + u32 remain = usb3_req->req.length; + u32 dma = usb3_req->req.dma; + u32 len; + int i = 0; + + do { + len = min_t(u32, remain, USB3_DMA_MAX_XFER_SIZE) & + USB3_PRD1_SIZE_MASK; + cur_prd->word1 = usb3_dma_mps_to_prd_word1(usb3_ep) | + USB3_PRD1_B_INC | len; + cur_prd->bap = dma; + remain -= len; + dma += len; + if (!remain || (i + 1) < USB3_DMA_NUM_PRD_ENTRIES) + break; + + cur_prd++; + i++; + } while (1); + + cur_prd->word1 |= USB3_PRD1_E | USB3_PRD1_INT; + if (usb3_ep->dir_in) + cur_prd->word1 |= USB3_PRD1_LST; +} + +static void usb3_dma_kick_prd(struct renesas_usb3_ep *usb3_ep) +{ + struct renesas_usb3_dma *dma = usb3_ep->dma; + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + u32 dma_con = DMA_COM_PIPE_NO(usb3_ep->num) | DMA_CON_PRD_EN; + + if (usb3_ep->dir_in) + dma_con |= DMA_CON_PIPE_DIR; + + wmb(); /* prd entries should be in system memory here */ + + usb3_write(usb3, 1 << usb3_ep->num, USB3_DMA_INT_STA); + usb3_write(usb3, AXI_INT_PRDEN_CLR_STA(dma->num) | + AXI_INT_PRDERR_STA(dma->num), USB3_AXI_INT_STA); + + usb3_write(usb3, dma->prd_dma, USB3_DMA_CH0_PRD_ADR(dma->num)); + usb3_write(usb3, dma_con, USB3_DMA_CH0_CON(dma->num)); + usb3_enable_dma_irq(usb3, usb3_ep->num); +} + +static void usb3_dma_stop_prd(struct renesas_usb3_ep *usb3_ep) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + struct renesas_usb3_dma *dma = usb3_ep->dma; + + usb3_disable_dma_irq(usb3, usb3_ep->num); + usb3_write(usb3, 0, USB3_DMA_CH0_CON(dma->num)); +} + +static int usb3_dma_update_status(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req) +{ + struct renesas_usb3_prd *cur_prd = usb3_ep->dma->prd; + struct usb_request *req = &usb3_req->req; + u32 remain, len; + int i = 0; + int status = 0; + + rmb(); /* The controller updated prd entries */ + + do { + if (cur_prd->word1 & USB3_PRD1_D) + status = -EIO; + if (cur_prd->word1 & USB3_PRD1_E) + len = req->length % USB3_DMA_MAX_XFER_SIZE; + else + len = USB3_DMA_MAX_XFER_SIZE; + remain = cur_prd->word1 & USB3_PRD1_SIZE_MASK; + req->actual += len - remain; + + if (cur_prd->word1 & USB3_PRD1_E || + (i + 1) < USB3_DMA_NUM_PRD_ENTRIES) + break; + + cur_prd++; + i++; + } while (1); + + return status; +} + +static bool usb3_dma_try_start(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + + if (!use_dma) + return false; + + if (usb3_dma_get_setting_area(usb3_ep, usb3_req)) { + usb3_pn_stop(usb3); + usb3_enable_dma_pipen(usb3); + usb3_dma_fill_prd(usb3_ep, usb3_req); + usb3_dma_kick_prd(usb3_ep); + usb3_pn_start(usb3); + return true; + } + + return false; +} + +static int usb3_dma_try_stop(struct renesas_usb3_ep *usb3_ep, + struct renesas_usb3_request *usb3_req) +{ + struct renesas_usb3 *usb3 = usb3_ep_to_usb3(usb3_ep); + unsigned long flags; + int status = 0; + + spin_lock_irqsave(&usb3->lock, flags); + if (!usb3_ep->dma) + goto out; + + if (!usb3_pn_change(usb3, usb3_ep->num)) + usb3_disable_dma_pipen(usb3); + usb3_dma_stop_prd(usb3_ep); + status = usb3_dma_update_status(usb3_ep, usb3_req); + usb3_dma_put_setting_area(usb3_ep, usb3_req); + +out: + spin_unlock_irqrestore(&usb3->lock, flags); + return status; +} + +static int renesas_usb3_dma_free_prd(struct renesas_usb3 *usb3, + struct device *dev) +{ + int i; + struct renesas_usb3_dma *dma; + + usb3_for_each_dma(usb3, dma, i) { + if (dma->prd) { + dma_free_coherent(dev, USB3_DMA_MAX_XFER_SIZE, + dma->prd, dma->prd_dma); + dma->prd = NULL; + } + } + + return 0; +} + +static int renesas_usb3_dma_alloc_prd(struct renesas_usb3 *usb3, + struct device *dev) +{ + int i; + struct renesas_usb3_dma *dma; + + if (!use_dma) + return 0; + + usb3_for_each_dma(usb3, dma, i) { + dma->prd = dma_alloc_coherent(dev, USB3_DMA_PRD_SIZE, + &dma->prd_dma, GFP_KERNEL); + if (!dma->prd) { + renesas_usb3_dma_free_prd(usb3, dev); + return -ENOMEM; + } + dma->num = i + 1; + } + + return 0; +} + static void usb3_start_pipen(struct renesas_usb3_ep *usb3_ep, struct renesas_usb3_request *usb3_req) { @@ -1079,6 +1420,10 @@ static void usb3_start_pipen(struct renesas_usb3_ep *usb3_ep, goto out; usb3_ep->started = true; + + if (usb3_dma_try_start(usb3_ep, usb3_req)) + goto out; + usb3_pn_start(usb3); if (usb3_ep->dir_in) { @@ -1582,12 +1927,49 @@ static void usb3_irq_epc(struct renesas_usb3 *usb3) } } +static void usb3_irq_dma_int(struct renesas_usb3 *usb3, u32 dma_sta) +{ + struct renesas_usb3_ep *usb3_ep; + struct renesas_usb3_request *usb3_req; + int i, status; + + for (i = 0; i < usb3->num_usb3_eps; i++) { + if (!(dma_sta & DMA_INT(i))) + continue; + + usb3_ep = usb3_get_ep(usb3, i); + if (!(usb3_read(usb3, USB3_AXI_INT_STA) & + AXI_INT_PRDEN_CLR_STA(usb3_ep->dma->num))) + continue; + + usb3_req = usb3_get_request(usb3_ep); + status = usb3_dma_try_stop(usb3_ep, usb3_req); + usb3_request_done_pipen(usb3, usb3_ep, usb3_req, status); + } +} + +static void usb3_irq_dma(struct renesas_usb3 *usb3) +{ + u32 dma_sta = usb3_read(usb3, USB3_DMA_INT_STA); + + dma_sta &= usb3_read(usb3, USB3_DMA_INT_ENA); + if (dma_sta) { + usb3_write(usb3, dma_sta, USB3_DMA_INT_STA); + usb3_irq_dma_int(usb3, dma_sta); + } +} + static irqreturn_t renesas_usb3_irq(int irq, void *_usb3) { struct renesas_usb3 *usb3 = _usb3; irqreturn_t ret = IRQ_NONE; u32 axi_int_sta = usb3_read(usb3, USB3_AXI_INT_STA); + if (axi_int_sta & AXI_INT_DMAINT) { + usb3_irq_dma(usb3); + ret = IRQ_HANDLED; + } + if (axi_int_sta & AXI_INT_EPCINT) { usb3_irq_epc(usb3); ret = IRQ_HANDLED; @@ -1686,6 +2068,7 @@ static int renesas_usb3_ep_disable(struct usb_ep *_ep) usb3_req = usb3_get_request(usb3_ep); if (!usb3_req) break; + usb3_dma_try_stop(usb3_ep, usb3_req); usb3_request_done(usb3_ep, usb3_req, -ESHUTDOWN); } while (1); @@ -1733,6 +2116,7 @@ static int renesas_usb3_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) dev_dbg(usb3_to_dev(usb3), "ep_dequeue: ep%2d, %u\n", usb3_ep->num, _req->length); + usb3_dma_try_stop(usb3_ep, usb3_req); usb3_request_done_pipen(usb3, usb3_ep, usb3_req, -ECONNRESET); return 0; @@ -1895,6 +2279,7 @@ static int renesas_usb3_remove(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); usb_del_gadget_udc(&usb3->gadget); + renesas_usb3_dma_free_prd(usb3, &pdev->dev); __renesas_usb3_ep_free_request(usb3->ep0_req); @@ -2089,6 +2474,10 @@ static int renesas_usb3_probe(struct platform_device *pdev) if (!usb3->ep0_req) return -ENOMEM; + ret = renesas_usb3_dma_alloc_prd(usb3, &pdev->dev); + if (ret < 0) + goto err_alloc_prd; + ret = usb_add_gadget_udc(&pdev->dev, &usb3->gadget); if (ret < 0) goto err_add_udc; @@ -2110,6 +2499,9 @@ err_dev_create: usb_del_gadget_udc(&usb3->gadget); err_add_udc: + renesas_usb3_dma_free_prd(usb3, &pdev->dev); + +err_alloc_prd: __renesas_usb3_ep_free_request(usb3->ep0_req); return ret; -- cgit v1.2.3 From 8e55d30322c6a0ef746c256a1beda9c73ecb27a6 Mon Sep 17 00:00:00 2001 From: Li Jun Date: Fri, 14 Apr 2017 19:12:07 +0800 Subject: usb: gadget: mass_storage: set msg_registered after msg registered If there is no UDC available, the msg register will fail and this flag will not be set, but the driver is already added into pending driver list, then the module removal modprobe -r can not remove the driver from the pending list. Signed-off-by: Li Jun Signed-off-by: Felipe Balbi --- drivers/usb/gadget/legacy/mass_storage.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/usb/gadget/legacy/mass_storage.c b/drivers/usb/gadget/legacy/mass_storage.c index 125974f32f50..e99ab57ee3e5 100644 --- a/drivers/usb/gadget/legacy/mass_storage.c +++ b/drivers/usb/gadget/legacy/mass_storage.c @@ -210,7 +210,6 @@ static int msg_bind(struct usb_composite_dev *cdev) usb_composite_overwrite_options(cdev, &coverwrite); dev_info(&cdev->gadget->dev, DRIVER_DESC ", version: " DRIVER_VERSION "\n"); - set_bit(0, &msg_registered); return 0; fail_otg_desc: @@ -257,7 +256,12 @@ MODULE_LICENSE("GPL"); static int __init msg_init(void) { - return usb_composite_probe(&msg_driver); + int ret; + + ret = usb_composite_probe(&msg_driver); + set_bit(0, &msg_registered); + + return ret; } module_init(msg_init); -- cgit v1.2.3 From 46b780d46bccdc75b63a98b2a9cca5a4e0ff2cec Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 12 Jun 2017 15:11:25 +0300 Subject: usb: dwc3: gadget: increase readability of dwc3_gadget_init_endpoints() The commit 47d3946ea220 usb: dwc3: refactor gadget endpoint count calculation refactored dwc3_gadget_init_endpoints() and in particular changed in or out endpoint numbering to be through. It's not always convenient and makes code a slightly harder to read. Introduce a new temporary variable to make it easier to understand what is going on inside the function. While doing that, rename local variables as follows: u8 num -> u8 total int num -> int kbytes Replace implicit direction check via epnum with explicit use of direction variable. While here, replace %d to %u when compounding endpoint name since we are using unsigned type. Signed-off-by: Andy Shevchenko Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/gadget.c | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 01cd7ddc9981..7fd4b22f4e97 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2027,15 +2027,16 @@ static const struct usb_gadget_ops dwc3_gadget_ops = { /* -------------------------------------------------------------------------- */ -static int dwc3_gadget_init_endpoints(struct dwc3 *dwc, u8 num) +static int dwc3_gadget_init_endpoints(struct dwc3 *dwc, u8 total) { struct dwc3_ep *dep; u8 epnum; INIT_LIST_HEAD(&dwc->gadget.ep_list); - for (epnum = 0; epnum < num; epnum++) { + for (epnum = 0; epnum < total; epnum++) { bool direction = epnum & 1; + u8 num = epnum >> 1; dep = kzalloc(sizeof(*dep), GFP_KERNEL); if (!dep) @@ -2047,7 +2048,7 @@ static int dwc3_gadget_init_endpoints(struct dwc3 *dwc, u8 num) dep->regs = dwc->regs + DWC3_DEP_BASE(epnum); dwc->eps[epnum] = dep; - snprintf(dep->name, sizeof(dep->name), "ep%d%s", epnum >> 1, + snprintf(dep->name, sizeof(dep->name), "ep%u%s", num, direction ? "in" : "out"); dep->endpoint.name = dep->name; @@ -2059,39 +2060,39 @@ static int dwc3_gadget_init_endpoints(struct dwc3 *dwc, u8 num) spin_lock_init(&dep->lock); - if (epnum == 0 || epnum == 1) { + if (num == 0) { usb_ep_set_maxpacket_limit(&dep->endpoint, 512); dep->endpoint.maxburst = 1; dep->endpoint.ops = &dwc3_gadget_ep0_ops; - if (!epnum) + if (!direction) dwc->gadget.ep0 = &dep->endpoint; } else if (direction) { int mdwidth; + int kbytes; int size; int ret; - int num; mdwidth = DWC3_MDWIDTH(dwc->hwparams.hwparams0); /* MDWIDTH is represented in bits, we need it in bytes */ mdwidth /= 8; - size = dwc3_readl(dwc->regs, DWC3_GTXFIFOSIZ(epnum >> 1)); + size = dwc3_readl(dwc->regs, DWC3_GTXFIFOSIZ(num)); size = DWC3_GTXFIFOSIZ_TXFDEF(size); /* FIFO Depth is in MDWDITH bytes. Multiply */ size *= mdwidth; - num = size / 1024; - if (num == 0) - num = 1; + kbytes = size / 1024; + if (kbytes == 0) + kbytes = 1; /* - * FIFO sizes account an extra MDWIDTH * (num + 1) bytes for + * FIFO sizes account an extra MDWIDTH * (kbytes + 1) bytes for * internal overhead. We don't really know how these are used, * but documentation say it exists. */ - size -= mdwidth * (num + 1); - size /= num; + size -= mdwidth * (kbytes + 1); + size /= kbytes; usb_ep_set_maxpacket_limit(&dep->endpoint, size); @@ -2117,7 +2118,7 @@ static int dwc3_gadget_init_endpoints(struct dwc3 *dwc, u8 num) return ret; } - if (epnum == 0 || epnum == 1) { + if (num == 0) { dep->endpoint.caps.type_control = true; } else { dep->endpoint.caps.type_iso = true; -- cgit v1.2.3 From 0e1b89e54ddac207e73c9b3e0cd9d4e431509a24 Mon Sep 17 00:00:00 2001 From: Arvind Yadav Date: Wed, 14 Jun 2017 15:20:08 +0530 Subject: usb: gadget: mv_udc: Handle return value of clk_prepare_enable. clk_prepare_enable() can fail here and we must check its return value. Signed-off-by: Arvind Yadav Signed-off-by: Felipe Balbi --- drivers/usb/gadget/udc/mv_udc_core.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/usb/gadget/udc/mv_udc_core.c b/drivers/usb/gadget/udc/mv_udc_core.c index 76f56c5762f9..8a708d0a1042 100644 --- a/drivers/usb/gadget/udc/mv_udc_core.c +++ b/drivers/usb/gadget/udc/mv_udc_core.c @@ -960,9 +960,9 @@ static const struct usb_ep_ops mv_ep_ops = { .fifo_flush = mv_ep_fifo_flush, /* flush fifo */ }; -static void udc_clock_enable(struct mv_udc *udc) +static int udc_clock_enable(struct mv_udc *udc) { - clk_prepare_enable(udc->clk); + return clk_prepare_enable(udc->clk); } static void udc_clock_disable(struct mv_udc *udc) @@ -1070,7 +1070,10 @@ static int mv_udc_enable_internal(struct mv_udc *udc) return 0; dev_dbg(&udc->dev->dev, "enable udc\n"); - udc_clock_enable(udc); + retval = udc_clock_enable(udc); + if (retval) + return retval; + if (udc->pdata->phy_init) { retval = udc->pdata->phy_init(udc->phy_regs); if (retval) { -- cgit v1.2.3 From ece7af5f787ee94ef4a8adb5cd9c40d6f475a7ba Mon Sep 17 00:00:00 2001 From: Arvind Yadav Date: Mon, 12 Jun 2017 16:23:31 +0530 Subject: usb: dwc3: exynos: Handle return value of clk_prepare_enable clk_prepare_enable() can fail here and we must check its return value. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Arvind Yadav Signed-off-by: Felipe Balbi --- drivers/usb/dwc3/dwc3-exynos.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/usb/dwc3/dwc3-exynos.c b/drivers/usb/dwc3/dwc3-exynos.c index 98f74ff66120..e089df72f766 100644 --- a/drivers/usb/dwc3/dwc3-exynos.c +++ b/drivers/usb/dwc3/dwc3-exynos.c @@ -125,12 +125,16 @@ static int dwc3_exynos_probe(struct platform_device *pdev) dev_err(dev, "couldn't get clock\n"); return -EINVAL; } - clk_prepare_enable(exynos->clk); + ret = clk_prepare_enable(exynos->clk); + if (ret) + return ret; exynos->susp_clk = devm_clk_get(dev, "usbdrd30_susp_clk"); if (IS_ERR(exynos->susp_clk)) exynos->susp_clk = NULL; - clk_prepare_enable(exynos->susp_clk); + ret = clk_prepare_enable(exynos->susp_clk); + if (ret) + goto susp_clk_err; if (of_device_is_compatible(node, "samsung,exynos7-dwusb3")) { exynos->axius_clk = devm_clk_get(dev, "usbdrd30_axius_clk"); @@ -139,7 +143,9 @@ static int dwc3_exynos_probe(struct platform_device *pdev) ret = -ENODEV; goto axius_clk_err; } - clk_prepare_enable(exynos->axius_clk); + ret = clk_prepare_enable(exynos->axius_clk); + if (ret) + goto axius_clk_err; } else { exynos->axius_clk = NULL; } @@ -197,6 +203,7 @@ vdd33_err: clk_disable_unprepare(exynos->axius_clk); axius_clk_err: clk_disable_unprepare(exynos->susp_clk); +susp_clk_err: clk_disable_unprepare(exynos->clk); return ret; } -- cgit v1.2.3 From d590c23111505635e1beb01006612971e5ede8aa Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Thu, 15 Jun 2017 11:55:41 +0300 Subject: usb: Avoid unnecessary LPM enabling and disabling during suspend and resume The original motivation for disabling/enabling Link PM at device suspend/resume was to force link state to go via U0 before suspend sets the link state to U3. Going directly from U2 to U3 is not allowed. Disabling LPM will forced the link state to U0, but will send a lot of Set port feature requests for evert suspend and resume. This is not needed as Hub hardware will take care of going via U0 when a U2 -> U3 transition is requested [1] [1] USB 3.1 specification section 10.16.2.10 Set Port Feature: "If the value is 3, then host software wants to selectively suspend the device connected to this port. The hub shall transition the link to U3 from any of the other U states using allowed link state transitions. If the port is not already in the U0 state, then it shall transition the port to the U0 state and then initiate the transition to U3. While this state is active, the hub does not propagate downstream-directed traffic to this port, but the hub will respond to resume signaling from the port" Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index b8bb20d7acdb..59e0418a5ed5 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -3155,12 +3155,6 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg) if (PMSG_IS_AUTO(msg)) goto err_ltm; } - if (usb_unlocked_disable_lpm(udev)) { - dev_err(&udev->dev, "Failed to disable LPM before suspend\n."); - status = -ENOMEM; - if (PMSG_IS_AUTO(msg)) - goto err_lpm3; - } /* see 7.1.7.6 */ if (hub_is_superspeed(hub->hdev)) @@ -3187,9 +3181,7 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg) if (status) { dev_dbg(&port_dev->dev, "can't suspend, status %d\n", status); - /* Try to enable USB3 LPM and LTM again */ - usb_unlocked_enable_lpm(udev); - err_lpm3: + /* Try to enable USB3 LTM again */ usb_enable_ltm(udev); err_ltm: /* Try to enable USB2 hardware LPM again */ @@ -3473,9 +3465,8 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg) if (udev->usb2_hw_lpm_capable == 1) usb_set_usb2_hardware_lpm(udev, 1); - /* Try to enable USB3 LTM and LPM */ + /* Try to enable USB3 LTM */ usb_enable_ltm(udev); - usb_unlocked_enable_lpm(udev); } usb_unlock_port(port_dev); -- cgit v1.2.3 From c5628a2af83aeda6cc02b6c64acb91f249727b1c Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Thu, 15 Jun 2017 11:55:42 +0300 Subject: xhci: remove endpoint ring cache Anurag Kumar Vulisha reported several issues with xhci endpoint ring caching. 31 Rings are cached per device before a ring is freed. These cached rings are not used as default if a new ring is needed. They are only used if the driver fails to allocate memory for a ring. The current ring cache is more a reason to why we run out memory than a help when we actually do so. Anurag Kumar Vulisha tried to use cached rings as a first option and found new issues with cached ring initialization. Cached rings were first zeroed and then manually reinitialized with link trbs etc, but forgetting to set some important bits like cycle toggle bit. Remove the ring cache completely as it's a faulty premature optimization eating memory Reported-by: Anurag Kumar Vulisha Tested-by: Anurag Kumar Vulisha Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-mem.c | 81 ++++----------------------------------------- drivers/usb/host/xhci.c | 17 +++++----- drivers/usb/host/xhci.h | 6 +--- 3 files changed, 15 insertions(+), 89 deletions(-) diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 1f1687e888d6..7c2501a607c8 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -407,64 +407,17 @@ fail: return NULL; } -void xhci_free_or_cache_endpoint_ring(struct xhci_hcd *xhci, +void xhci_free_endpoint_ring(struct xhci_hcd *xhci, struct xhci_virt_device *virt_dev, unsigned int ep_index) { - int rings_cached; - - rings_cached = virt_dev->num_rings_cached; - if (rings_cached < XHCI_MAX_RINGS_CACHED) { - virt_dev->ring_cache[rings_cached] = - virt_dev->eps[ep_index].ring; - virt_dev->num_rings_cached++; - xhci_dbg(xhci, "Cached old ring, " - "%d ring%s cached\n", - virt_dev->num_rings_cached, - (virt_dev->num_rings_cached > 1) ? "s" : ""); - } else { - xhci_ring_free(xhci, virt_dev->eps[ep_index].ring); - xhci_dbg(xhci, "Ring cache full (%d rings), " - "freeing ring\n", - virt_dev->num_rings_cached); - } + xhci_ring_free(xhci, virt_dev->eps[ep_index].ring); virt_dev->eps[ep_index].ring = NULL; } -/* Zero an endpoint ring (except for link TRBs) and move the enqueue and dequeue - * pointers to the beginning of the ring. - */ -static void xhci_reinit_cached_ring(struct xhci_hcd *xhci, - struct xhci_ring *ring, unsigned int cycle_state, - enum xhci_ring_type type) -{ - struct xhci_segment *seg = ring->first_seg; - int i; - - do { - memset(seg->trbs, 0, - sizeof(union xhci_trb)*TRBS_PER_SEGMENT); - if (cycle_state == 0) { - for (i = 0; i < TRBS_PER_SEGMENT; i++) - seg->trbs[i].link.control |= - cpu_to_le32(TRB_CYCLE); - } - /* All endpoint rings have link TRBs */ - xhci_link_segments(xhci, seg, seg->next, type); - seg = seg->next; - } while (seg != ring->first_seg); - ring->type = type; - xhci_initialize_ring_info(ring, cycle_state); - /* td list should be empty since all URBs have been cancelled, - * but just in case... - */ - INIT_LIST_HEAD(&ring->td_list); -} - /* * Expand an existing ring. - * Look for a cached ring or allocate a new ring which has same segment numbers - * and link the two rings. + * Allocate a new ring which has same segment numbers and link the two rings. */ int xhci_ring_expansion(struct xhci_hcd *xhci, struct xhci_ring *ring, unsigned int num_trbs, gfp_t flags) @@ -968,12 +921,6 @@ void xhci_free_virt_device(struct xhci_hcd *xhci, int slot_id) /* If necessary, update the number of active TTs on this root port */ xhci_update_tt_active_eps(xhci, dev, old_active_eps); - if (dev->ring_cache) { - for (i = 0; i < dev->num_rings_cached; i++) - xhci_ring_free(xhci, dev->ring_cache[i]); - kfree(dev->ring_cache); - } - if (dev->in_ctx) xhci_free_container_ctx(xhci, dev->in_ctx); if (dev->out_ctx) @@ -1062,14 +1009,6 @@ int xhci_alloc_virt_device(struct xhci_hcd *xhci, int slot_id, if (!dev->eps[0].ring) goto fail; - /* Allocate pointers to the ring cache */ - dev->ring_cache = kzalloc( - sizeof(struct xhci_ring *)*XHCI_MAX_RINGS_CACHED, - flags); - if (!dev->ring_cache) - goto fail; - dev->num_rings_cached = 0; - dev->udev = udev; /* Point to output device context in dcbaa. */ @@ -1537,17 +1476,9 @@ int xhci_endpoint_init(struct xhci_hcd *xhci, /* Set up the endpoint ring */ virt_dev->eps[ep_index].new_ring = xhci_ring_alloc(xhci, 2, 1, ring_type, max_packet, mem_flags); - if (!virt_dev->eps[ep_index].new_ring) { - /* Attempt to use the ring cache */ - if (virt_dev->num_rings_cached == 0) - return -ENOMEM; - virt_dev->num_rings_cached--; - virt_dev->eps[ep_index].new_ring = - virt_dev->ring_cache[virt_dev->num_rings_cached]; - virt_dev->ring_cache[virt_dev->num_rings_cached] = NULL; - xhci_reinit_cached_ring(xhci, virt_dev->eps[ep_index].new_ring, - 1, ring_type); - } + if (!virt_dev->eps[ep_index].new_ring) + return -ENOMEM; + virt_dev->eps[ep_index].skip = false; ep_ring = virt_dev->eps[ep_index].new_ring; diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 2d6a98c1dc12..ddeb943dc242 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -1695,8 +1695,7 @@ static int xhci_add_endpoint(struct usb_hcd *hcd, struct usb_device *udev, if (xhci->quirks & XHCI_MTK_HOST) { ret = xhci_mtk_add_ep_quirk(hcd, udev, ep); if (ret < 0) { - xhci_free_or_cache_endpoint_ring(xhci, - virt_dev, ep_index); + xhci_free_endpoint_ring(xhci, virt_dev, ep_index); return ret; } } @@ -2720,23 +2719,23 @@ static int xhci_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) for (i = 1; i < 31; i++) { if ((le32_to_cpu(ctrl_ctx->drop_flags) & (1 << (i + 1))) && !(le32_to_cpu(ctrl_ctx->add_flags) & (1 << (i + 1)))) { - xhci_free_or_cache_endpoint_ring(xhci, virt_dev, i); + xhci_free_endpoint_ring(xhci, virt_dev, i); xhci_check_bw_drop_ep_streams(xhci, virt_dev, i); } } xhci_zero_in_ctx(xhci, virt_dev); /* * Install any rings for completely new endpoints or changed endpoints, - * and free or cache any old rings from changed endpoints. + * and free any old rings from changed endpoints. */ for (i = 1; i < 31; i++) { if (!virt_dev->eps[i].new_ring) continue; - /* Only cache or free the old ring if it exists. + /* Only free the old ring if it exists. * It may not if this is the first add of an endpoint. */ if (virt_dev->eps[i].ring) { - xhci_free_or_cache_endpoint_ring(xhci, virt_dev, i); + xhci_free_endpoint_ring(xhci, virt_dev, i); } xhci_check_bw_drop_ep_streams(xhci, virt_dev, i); virt_dev->eps[i].ring = virt_dev->eps[i].new_ring; @@ -3336,7 +3335,7 @@ void xhci_free_device_endpoint_resources(struct xhci_hcd *xhci, * * Wait for the Reset Device command to finish. Remove all structures * associated with the endpoints that were disabled. Clear the input device - * structure? Cache the rings? Reset the control endpoint 0 max packet size? + * structure? Reset the control endpoint 0 max packet size? * * If the virt_dev to be reset does not exist or does not match the udev, * it means the device is lost, possibly due to the xHC restore error and @@ -3466,7 +3465,7 @@ static int xhci_discover_or_reset_device(struct usb_hcd *hcd, spin_unlock_irqrestore(&xhci->lock, flags); } - /* Everything but endpoint 0 is disabled, so free or cache the rings. */ + /* Everything but endpoint 0 is disabled, so free the rings. */ last_freed_endpoint = 1; for (i = 1; i < 31; i++) { struct xhci_virt_ep *ep = &virt_dev->eps[i]; @@ -3480,7 +3479,7 @@ static int xhci_discover_or_reset_device(struct usb_hcd *hcd, } if (ep->ring) { - xhci_free_or_cache_endpoint_ring(xhci, virt_dev, i); + xhci_free_endpoint_ring(xhci, virt_dev, i); last_freed_endpoint = i; } if (!list_empty(&virt_dev->eps[i].bw_endpoint_list)) diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 886f150bad0f..deb85342e0a3 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -992,10 +992,6 @@ struct xhci_virt_device { struct xhci_container_ctx *out_ctx; /* Used for addressing devices and configuration changes */ struct xhci_container_ctx *in_ctx; - /* Rings saved to ensure old alt settings can be re-instated */ - struct xhci_ring **ring_cache; - int num_rings_cached; -#define XHCI_MAX_RINGS_CACHED 31 struct xhci_virt_ep eps[31]; u8 fake_port; u8 real_port; @@ -1960,7 +1956,7 @@ int xhci_endpoint_init(struct xhci_hcd *xhci, struct xhci_virt_device *virt_dev, void xhci_ring_free(struct xhci_hcd *xhci, struct xhci_ring *ring); int xhci_ring_expansion(struct xhci_hcd *xhci, struct xhci_ring *ring, unsigned int num_trbs, gfp_t flags); -void xhci_free_or_cache_endpoint_ring(struct xhci_hcd *xhci, +void xhci_free_endpoint_ring(struct xhci_hcd *xhci, struct xhci_virt_device *virt_dev, unsigned int ep_index); struct xhci_stream_info *xhci_alloc_stream_info(struct xhci_hcd *xhci, -- cgit v1.2.3 From b3368382efe6e987961e1aaec6b29507e2175954 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Thu, 15 Jun 2017 11:55:43 +0300 Subject: xhci: refactor transfer event errors and completion codes Parse the transfer event first, and remove duplicate debugging code. Reorder completion codes according to endpoint state. No functional changes We are not handling some transfer events correcly and need to clean up this before fixing it Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 78 +++++++++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 3e27754426ec..417b3c61fd82 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -2298,39 +2298,26 @@ static int handle_tx_event(struct xhci_hcd *xhci, bool handling_skipped_tds = false; slot_id = TRB_TO_SLOT_ID(le32_to_cpu(event->flags)); + ep_index = TRB_TO_EP_ID(le32_to_cpu(event->flags)) - 1; + trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len)); + ep_trb_dma = le64_to_cpu(event->buffer); + xdev = xhci->devs[slot_id]; if (!xdev) { xhci_err(xhci, "ERROR Transfer event pointed to bad slot %u\n", slot_id); - xhci_err(xhci, "@%016llx %08x %08x %08x %08x\n", - (unsigned long long) xhci_trb_virt_to_dma( - xhci->event_ring->deq_seg, - xhci->event_ring->dequeue), - lower_32_bits(le64_to_cpu(event->buffer)), - upper_32_bits(le64_to_cpu(event->buffer)), - le32_to_cpu(event->transfer_len), - le32_to_cpu(event->flags)); - return -ENODEV; - } - - /* Endpoint ID is 1 based, our index is zero based */ - ep_index = TRB_TO_EP_ID(le32_to_cpu(event->flags)) - 1; + goto err_out; + } + ep = &xdev->eps[ep_index]; - ep_ring = xhci_dma_to_transfer_ring(ep, le64_to_cpu(event->buffer)); + ep_ring = xhci_dma_to_transfer_ring(ep, ep_trb_dma); ep_ctx = xhci_get_ep_ctx(xhci, xdev->out_ctx, ep_index); - if (!ep_ring || GET_EP_CTX_STATE(ep_ctx) == EP_STATE_DISABLED) { + + if (!ep_ring || GET_EP_CTX_STATE(ep_ctx) == EP_STATE_DISABLED) { xhci_err(xhci, "ERROR Transfer event for disabled endpoint slot %u ep %u or incorrect stream ring\n", slot_id, ep_index); - xhci_err(xhci, "@%016llx %08x %08x %08x %08x\n", - (unsigned long long) xhci_trb_virt_to_dma( - xhci->event_ring->deq_seg, - xhci->event_ring->dequeue), - lower_32_bits(le64_to_cpu(event->buffer)), - upper_32_bits(le64_to_cpu(event->buffer)), - le32_to_cpu(event->transfer_len), - le32_to_cpu(event->flags)); - return -ENODEV; + goto err_out; } /* Count current td numbers if ep->skip is set */ @@ -2339,8 +2326,6 @@ static int handle_tx_event(struct xhci_hcd *xhci, td_num++; } - ep_trb_dma = le64_to_cpu(event->buffer); - trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len)); /* Look for common error cases */ switch (trb_comp_code) { /* Skip codes that require special handling depending on @@ -2357,6 +2342,7 @@ static int handle_tx_event(struct xhci_hcd *xhci, slot_id, ep_index); case COMP_SHORT_PACKET: break; + /* Completion codes for endpoint stopped state */ case COMP_STOPPED: xhci_dbg(xhci, "Stopped on Transfer TRB for slot %u ep %u\n", slot_id, ep_index); @@ -2371,18 +2357,13 @@ static int handle_tx_event(struct xhci_hcd *xhci, "Stopped with short packet transfer detected for slot %u ep %u\n", slot_id, ep_index); break; + /* Completion codes for endpoint halted state */ case COMP_STALL_ERROR: xhci_dbg(xhci, "Stalled endpoint for slot %u ep %u\n", slot_id, ep_index); ep->ep_state |= EP_HALTED; status = -EPIPE; break; - case COMP_TRB_ERROR: - xhci_warn(xhci, - "WARN: TRB error for slot %u ep %u on endpoint\n", - slot_id, ep_index); - status = -EILSEQ; - break; case COMP_SPLIT_TRANSACTION_ERROR: case COMP_USB_TRANSACTION_ERROR: xhci_dbg(xhci, "Transfer error for slot %u ep %u on endpoint\n", @@ -2394,6 +2375,14 @@ static int handle_tx_event(struct xhci_hcd *xhci, slot_id, ep_index); status = -EOVERFLOW; break; + /* Completion codes for endpoint error state */ + case COMP_TRB_ERROR: + xhci_warn(xhci, + "WARN: TRB error for slot %u ep %u on endpoint\n", + slot_id, ep_index); + status = -EILSEQ; + break; + /* completion codes not indicating endpoint state change */ case COMP_DATA_BUFFER_ERROR: xhci_warn(xhci, "WARN: HC couldn't access mem fast enough for slot %u ep %u\n", @@ -2431,12 +2420,6 @@ static int handle_tx_event(struct xhci_hcd *xhci, TRB_TO_SLOT_ID(le32_to_cpu(event->flags)), ep_index); goto cleanup; - case COMP_INCOMPATIBLE_DEVICE_ERROR: - xhci_warn(xhci, - "WARN: detect an incompatible device for slot %u ep %u", - slot_id, ep_index); - status = -EPROTO; - break; case COMP_MISSED_SERVICE_ERROR: /* * When encounter missed service error, one or more isoc tds @@ -2455,6 +2438,14 @@ static int handle_tx_event(struct xhci_hcd *xhci, "No Ping response error for slot %u ep %u, Skip one Isoc TD\n", slot_id, ep_index); goto cleanup; + + case COMP_INCOMPATIBLE_DEVICE_ERROR: + /* needs disable slot command to recover */ + xhci_warn(xhci, + "WARN: detect an incompatible device for slot %u ep %u", + slot_id, ep_index); + status = -EPROTO; + break; default: if (xhci_is_vendor_info_code(xhci, trb_comp_code)) { status = 0; @@ -2607,6 +2598,17 @@ cleanup: } while (handling_skipped_tds); return 0; + +err_out: + xhci_err(xhci, "@%016llx %08x %08x %08x %08x\n", + (unsigned long long) xhci_trb_virt_to_dma( + xhci->event_ring->deq_seg, + xhci->event_ring->dequeue), + lower_32_bits(le64_to_cpu(event->buffer)), + upper_32_bits(le64_to_cpu(event->buffer)), + le32_to_cpu(event->transfer_len), + le32_to_cpu(event->flags)); + return -ENODEV; } /* -- cgit v1.2.3 From 217491487c43892318c18b6dcafe33b6353efd40 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Thu, 15 Jun 2017 11:55:44 +0300 Subject: xhci: Add support for endpoint soft reset xhci supports soft retry recovery when the host halted the host side of an endopint but the connected USB device is not aware of the halt. In this case xhci needs to issue a reset endopint command with a TSP (Transfer State Preserve) flag set which preserves the Data toggle and Sequence number of the endpoint. This feature is needed to handle a few special transfer event types such as USB Transaction error that don't always point to a causing TRB. see xhci 4.6.8.1 for more details Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 8 ++++++-- drivers/usb/host/xhci.h | 8 +++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 417b3c61fd82..ae55ed1c3f08 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -1830,7 +1830,7 @@ static void xhci_cleanup_halted_endpoint(struct xhci_hcd *xhci, ep->ep_state |= EP_HALTED; ep->stopped_stream = stream_id; - xhci_queue_reset_ep(xhci, command, slot_id, ep_index); + xhci_queue_reset_ep(xhci, command, slot_id, ep_index, EP_HARD_RESET); xhci_cleanup_stalled_ring(xhci, ep_index, td); ep->stopped_stream = 0; @@ -4046,12 +4046,16 @@ void xhci_queue_new_dequeue_state(struct xhci_hcd *xhci, } int xhci_queue_reset_ep(struct xhci_hcd *xhci, struct xhci_command *cmd, - int slot_id, unsigned int ep_index) + int slot_id, unsigned int ep_index, + enum xhci_ep_reset_type reset_type) { u32 trb_slot_id = SLOT_ID_FOR_TRB(slot_id); u32 trb_ep_index = EP_ID_FOR_TRB(ep_index); u32 type = TRB_TYPE(TRB_RESET_EP); + if (reset_type == EP_SOFT_RESET) + type |= TRB_TSP; + return queue_command(xhci, cmd, 0, 0, 0, trb_slot_id | trb_ep_index | type, false); } diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index deb85342e0a3..acd66f7ea25c 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1205,6 +1205,11 @@ struct xhci_event_cmd { /* Stop Ring - Transfer State Preserve */ #define TRB_TSP (1<<9) +enum xhci_ep_reset_type { + EP_HARD_RESET, + EP_SOFT_RESET, +}; + /* Force Event */ #define TRB_TO_VF_INTR_TARGET(p) (((p) & (0x3ff << 22)) >> 22) #define TRB_TO_VF_ID(p) (((p) & (0xff << 16)) >> 16) @@ -2040,7 +2045,8 @@ int xhci_queue_configure_endpoint(struct xhci_hcd *xhci, int xhci_queue_evaluate_context(struct xhci_hcd *xhci, struct xhci_command *cmd, dma_addr_t in_ctx_ptr, u32 slot_id, bool command_must_succeed); int xhci_queue_reset_ep(struct xhci_hcd *xhci, struct xhci_command *cmd, - int slot_id, unsigned int ep_index); + int slot_id, unsigned int ep_index, + enum xhci_ep_reset_type reset_type); int xhci_queue_reset_device(struct xhci_hcd *xhci, struct xhci_command *cmd, u32 slot_id); void xhci_find_new_dequeue_state(struct xhci_hcd *xhci, -- cgit v1.2.3 From 5eee4b6b4f572bfce953f1e38cea087b9933e0e9 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Thu, 15 Jun 2017 11:55:45 +0300 Subject: xhci: support calling cleanup_halted_endpoint with soft retry Add soft reset support to cleanup_halted_endpoint(). using soft reset will prevent it from setting a new dequeue pointer to start the transfer from. Let it continue where it halted. Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index ae55ed1c3f08..1dd00dcbb85a 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -1819,7 +1819,8 @@ struct xhci_segment *trb_in_td(struct xhci_hcd *xhci, static void xhci_cleanup_halted_endpoint(struct xhci_hcd *xhci, unsigned int slot_id, unsigned int ep_index, unsigned int stream_id, - struct xhci_td *td, union xhci_trb *ep_trb) + struct xhci_td *td, union xhci_trb *ep_trb, + enum xhci_ep_reset_type reset_type) { struct xhci_virt_ep *ep = &xhci->devs[slot_id]->eps[ep_index]; struct xhci_command *command; @@ -1828,12 +1829,14 @@ static void xhci_cleanup_halted_endpoint(struct xhci_hcd *xhci, return; ep->ep_state |= EP_HALTED; - ep->stopped_stream = stream_id; - xhci_queue_reset_ep(xhci, command, slot_id, ep_index, EP_HARD_RESET); - xhci_cleanup_stalled_ring(xhci, ep_index, td); + xhci_queue_reset_ep(xhci, command, slot_id, ep_index, reset_type); - ep->stopped_stream = 0; + if (reset_type == EP_HARD_RESET) { + ep->stopped_stream = stream_id; + xhci_cleanup_stalled_ring(xhci, ep_index, td); + ep->stopped_stream = 0; + } xhci_ring_cmd_db(xhci); } @@ -1965,7 +1968,8 @@ static int finish_td(struct xhci_hcd *xhci, struct xhci_td *td, * The class driver clears the device side halt later. */ xhci_cleanup_halted_endpoint(xhci, slot_id, ep_index, - ep_ring->stream_id, td, ep_trb); + ep_ring->stream_id, td, ep_trb, + EP_HARD_RESET); } else { /* Update ring dequeue pointer */ while (ep_ring->dequeue != td->last_trb) -- cgit v1.2.3 From ade2e3a148a1740a6f3abea9c54f11630b9d7d1e Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Thu, 15 Jun 2017 11:55:46 +0300 Subject: xhci: handle transfer events without TRB pointer Most transfer events have a TRB pointer indicating which TRB caused the event. In the case of streams, transfer events such as USB Transaction error may have its TRB pointer set to zero. driver won't know which stream or what TRB on that stream caused the error, but it can issue a soft reset to recover the transfer. A soft reset will clear the host side halt of the endpoint without clearing Data toggle or sequence number, and let the transfer continue from where it halted. see xhci section 4.12 streams and 4.6.8.2 soft retry. USB Transaction errors with a zero TRB pointer are seen with UAS usb devices. Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 1dd00dcbb85a..43ec7005701b 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -2317,13 +2317,33 @@ static int handle_tx_event(struct xhci_hcd *xhci, ep_ring = xhci_dma_to_transfer_ring(ep, ep_trb_dma); ep_ctx = xhci_get_ep_ctx(xhci, xdev->out_ctx, ep_index); - if (!ep_ring || GET_EP_CTX_STATE(ep_ctx) == EP_STATE_DISABLED) { + if (GET_EP_CTX_STATE(ep_ctx) == EP_STATE_DISABLED) { xhci_err(xhci, - "ERROR Transfer event for disabled endpoint slot %u ep %u or incorrect stream ring\n", + "ERROR Transfer event for disabled endpoint slot %u ep %u\n", slot_id, ep_index); goto err_out; } + /* Some transfer events don't always point to a trb, see xhci 4.17.4 */ + if (!ep_ring) { + switch (trb_comp_code) { + case COMP_STALL_ERROR: + case COMP_USB_TRANSACTION_ERROR: + case COMP_INVALID_STREAM_TYPE_ERROR: + case COMP_INVALID_STREAM_ID_ERROR: + xhci_cleanup_halted_endpoint(xhci, slot_id, ep_index, 0, + NULL, NULL, EP_SOFT_RESET); + goto cleanup; + case COMP_RING_UNDERRUN: + case COMP_RING_OVERRUN: + goto cleanup; + default: + xhci_err(xhci, "ERROR Transfer event for unknown stream ring slot %u ep %u\n", + slot_id, ep_index); + goto err_out; + } + } + /* Count current td numbers if ep->skip is set */ if (ep->skip) { list_for_each(tmp, &ep_ring->td_list) -- cgit v1.2.3 From d36374fdfb259bac4511762bb349e69c6d093d37 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Thu, 15 Jun 2017 11:55:47 +0300 Subject: xhci: cleanup virtual endoint structure, remove stopped_stream Get rid of stopped_stream member in virtual endpoint structure as it is only used in one case when cleaning a halted endpoint. Pass it as function parameter instead. No functional changes Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 7 ++----- drivers/usb/host/xhci.c | 6 +++--- drivers/usb/host/xhci.h | 5 ++--- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 43ec7005701b..7f1139b49b3a 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -1832,11 +1832,8 @@ static void xhci_cleanup_halted_endpoint(struct xhci_hcd *xhci, xhci_queue_reset_ep(xhci, command, slot_id, ep_index, reset_type); - if (reset_type == EP_HARD_RESET) { - ep->stopped_stream = stream_id; - xhci_cleanup_stalled_ring(xhci, ep_index, td); - ep->stopped_stream = 0; - } + if (reset_type == EP_HARD_RESET) + xhci_cleanup_stalled_ring(xhci, ep_index, stream_id, td); xhci_ring_cmd_db(xhci); } diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index ddeb943dc242..56f85df013db 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -2822,8 +2822,8 @@ static void xhci_setup_input_ctx_for_quirk(struct xhci_hcd *xhci, added_ctxs, added_ctxs); } -void xhci_cleanup_stalled_ring(struct xhci_hcd *xhci, - unsigned int ep_index, struct xhci_td *td) +void xhci_cleanup_stalled_ring(struct xhci_hcd *xhci, unsigned int ep_index, + unsigned int stream_id, struct xhci_td *td) { struct xhci_dequeue_state deq_state; struct xhci_virt_ep *ep; @@ -2836,7 +2836,7 @@ void xhci_cleanup_stalled_ring(struct xhci_hcd *xhci, * or it will attempt to resend it on the next doorbell ring. */ xhci_find_new_dequeue_state(xhci, udev->slot_id, - ep_index, ep->stopped_stream, td, &deq_state); + ep_index, stream_id, td, &deq_state); if (!deq_state.new_deq_ptr || !deq_state.new_deq_seg) return; diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index acd66f7ea25c..650a2d9d4aec 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -924,7 +924,6 @@ struct xhci_virt_ep { #define EP_GETTING_NO_STREAMS (1 << 5) /* ---- Related to URB cancellation ---- */ struct list_head cancelled_td_list; - unsigned int stopped_stream; /* Watchdog timer for stop endpoint command to cancel URBs */ struct timer_list stop_cmd_timer; struct xhci_hcd *xhci; @@ -2056,8 +2055,8 @@ void xhci_find_new_dequeue_state(struct xhci_hcd *xhci, void xhci_queue_new_dequeue_state(struct xhci_hcd *xhci, unsigned int slot_id, unsigned int ep_index, struct xhci_dequeue_state *deq_state); -void xhci_cleanup_stalled_ring(struct xhci_hcd *xhci, - unsigned int ep_index, struct xhci_td *td); +void xhci_cleanup_stalled_ring(struct xhci_hcd *xhci, unsigned int ep_index, + unsigned int stream_id, struct xhci_td *td); void xhci_stop_endpoint_command_watchdog(unsigned long arg); void xhci_handle_command_timeout(struct work_struct *work); -- cgit v1.2.3 From 3134bc9c52ed90c4aea5e4201eb13145cfd12b05 Mon Sep 17 00:00:00 2001 From: Mathias Nyman Date: Thu, 15 Jun 2017 11:55:48 +0300 Subject: xhci: cleanup finish_td() skip option finish_td() could be called with a skip option to bypass most of the function and only call xhci_td_cleanup() at the end. Remove this skip option and call xhci_td_cleanup() directly instead when needed No functional changes Signed-off-by: Mathias Nyman Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/xhci-ring.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 7f1139b49b3a..c50c902d009e 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -1928,7 +1928,7 @@ static int xhci_td_cleanup(struct xhci_hcd *xhci, struct xhci_td *td, static int finish_td(struct xhci_hcd *xhci, struct xhci_td *td, union xhci_trb *ep_trb, struct xhci_transfer_event *event, - struct xhci_virt_ep *ep, int *status, bool skip) + struct xhci_virt_ep *ep, int *status) { struct xhci_virt_device *xdev; struct xhci_ep_ctx *ep_ctx; @@ -1944,9 +1944,6 @@ static int finish_td(struct xhci_hcd *xhci, struct xhci_td *td, ep_ctx = xhci_get_ep_ctx(xhci, xdev->out_ctx, ep_index); trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len)); - if (skip) - goto td_cleanup; - if (trb_comp_code == COMP_STOPPED_LENGTH_INVALID || trb_comp_code == COMP_STOPPED || trb_comp_code == COMP_STOPPED_SHORT_PACKET) { @@ -1974,7 +1971,6 @@ static int finish_td(struct xhci_hcd *xhci, struct xhci_td *td, inc_deq(xhci, ep_ring); } -td_cleanup: return xhci_td_cleanup(xhci, td, ep_ring, status); } @@ -2094,7 +2090,7 @@ static int process_ctrl_td(struct xhci_hcd *xhci, struct xhci_td *td, td->urb->actual_length = requested; finish_td: - return finish_td(xhci, td, ep_trb, event, ep, status, false); + return finish_td(xhci, td, ep_trb, event, ep, status); } /* @@ -2181,7 +2177,7 @@ static int process_isoc_td(struct xhci_hcd *xhci, struct xhci_td *td, td->urb->actual_length += frame->actual_length; - return finish_td(xhci, td, ep_trb, event, ep, status, false); + return finish_td(xhci, td, ep_trb, event, ep, status); } static int skip_isoc_td(struct xhci_hcd *xhci, struct xhci_td *td, @@ -2209,7 +2205,7 @@ static int skip_isoc_td(struct xhci_hcd *xhci, struct xhci_td *td, inc_deq(xhci, ep_ring); inc_deq(xhci, ep_ring); - return finish_td(xhci, td, NULL, event, ep, status, true); + return xhci_td_cleanup(xhci, td, ep_ring, status); } /* @@ -2271,7 +2267,7 @@ finish_td: remaining); td->urb->actual_length = 0; } - return finish_td(xhci, td, ep_trb, event, ep, status, false); + return finish_td(xhci, td, ep_trb, event, ep, status); } /* -- cgit v1.2.3 From 259a24001a501c09e7c6b718aed3b1b58641534b Mon Sep 17 00:00:00 2001 From: Tony Lindgren Date: Mon, 12 Jun 2017 01:12:23 -0700 Subject: phy: cpcap-usb: Fix missing return statement Commit 8ae904e3c236 ("phy: cpcap-usb: Add CPCAP PMIC USB support") is missing return statement as noted by Colin Ian King . If the optional pins are not configured, we just want to return early and not attempt to configure the pins. Fixes: 8ae904e3c236 ("phy: cpcap-usb: Add CPCAP PMIC USB support") Reported-by: Colin Ian King Signed-off-by: Tony Lindgren Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/motorola/phy-cpcap-usb.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/phy/motorola/phy-cpcap-usb.c b/drivers/phy/motorola/phy-cpcap-usb.c index 082a48bd4d89..9b63efa5ae4d 100644 --- a/drivers/phy/motorola/phy-cpcap-usb.c +++ b/drivers/phy/motorola/phy-cpcap-usb.c @@ -468,6 +468,8 @@ static int cpcap_usb_init_optional_pins(struct cpcap_phy_ddata *ddata) dev_info(ddata->dev, "default pins not configured: %ld\n", PTR_ERR(ddata->pins)); ddata->pins = NULL; + + return 0; } ddata->pins_ulpi = pinctrl_lookup_state(ddata->pins, "ulpi"); -- cgit v1.2.3 From 3ac255bc8e9716ba581d10764aaf957f9f8dd187 Mon Sep 17 00:00:00 2001 From: Srinath Mannam Date: Thu, 8 Jun 2017 17:01:38 +0530 Subject: dt-bindings: Update documentation for stingray SATA phy Update BRCM SATA phy dt-binding documentation for stingray. Signed-off-by: Srinath Mannam Reviewed-by: Ray Jui Reviewed-by: Scott Branden Reviewed-by: Florian Fainelli Acked-by: Rob Herring Signed-off-by: Kishon Vijay Abraham I --- Documentation/devicetree/bindings/phy/brcm-sata-phy.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/phy/brcm-sata-phy.txt b/Documentation/devicetree/bindings/phy/brcm-sata-phy.txt index 6ccce09d8bbf..97977cd29a98 100644 --- a/Documentation/devicetree/bindings/phy/brcm-sata-phy.txt +++ b/Documentation/devicetree/bindings/phy/brcm-sata-phy.txt @@ -7,12 +7,13 @@ Required properties: "brcm,iproc-ns2-sata-phy" "brcm,iproc-nsp-sata-phy" "brcm,phy-sata3" + "brcm,iproc-sr-sata-phy" - address-cells: should be 1 - size-cells: should be 0 - reg: register ranges for the PHY PCB interface - reg-names: should be "phy" and "phy-ctrl" The "phy-ctrl" registers are only required for - "brcm,iproc-ns2-sata-phy". + "brcm,iproc-ns2-sata-phy" and "brcm,iproc-sr-sata-phy". Sub-nodes: Each port's PHY should be represented as a sub-node. @@ -23,8 +24,8 @@ Sub-nodes required properties: Sub-nodes optional properties: - brcm,enable-ssc: use spread spectrum clocking (SSC) on this port - This property is not applicable for "brcm,iproc-ns2-sata-phy" and - "brcm,iproc-nsp-sata-phy". + This property is not applicable for "brcm,iproc-ns2-sata-phy", + "brcm,iproc-nsp-sata-phy" and "brcm,iproc-sr-sata-phy". Example: sata-phy@f0458100 { -- cgit v1.2.3 From 80886f7c698fd64366a6eced011ec82f4daf968b Mon Sep 17 00:00:00 2001 From: Srinath Mannam Date: Thu, 8 Jun 2017 17:01:39 +0530 Subject: phy: Add stingray SATA phy support This patch adds support for stingray SATA phy in the SATA BRCM phy driver. Signed-off-by: Srinath Mannam Reviewed-by: Florian Fainelli Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/broadcom/phy-brcm-sata.c | 73 ++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/drivers/phy/broadcom/phy-brcm-sata.c b/drivers/phy/broadcom/phy-brcm-sata.c index ccbc3d994998..e6544c8b1ace 100644 --- a/drivers/phy/broadcom/phy-brcm-sata.c +++ b/drivers/phy/broadcom/phy-brcm-sata.c @@ -46,6 +46,7 @@ enum brcm_sata_phy_version { BRCM_SATA_PHY_STB_40NM, BRCM_SATA_PHY_IPROC_NS2, BRCM_SATA_PHY_IPROC_NSP, + BRCM_SATA_PHY_IPROC_SR, }; struct brcm_sata_port { @@ -81,12 +82,17 @@ enum sata_phy_regs { PLL_ACTRL2 = 0x8b, PLL_ACTRL2_SELDIV_MASK = 0x1f, PLL_ACTRL2_SELDIV_SHIFT = 9, + PLL_ACTRL6 = 0x86, PLL1_REG_BANK = 0x060, PLL1_ACTRL2 = 0x82, PLL1_ACTRL3 = 0x83, PLL1_ACTRL4 = 0x84, + TX_REG_BANK = 0x070, + TX_ACTRL0 = 0x80, + TX_ACTRL0_TXPOL_FLIP = BIT(6), + OOB_REG_BANK = 0x150, OOB1_REG_BANK = 0x160, OOB_CTRL1 = 0x80, @@ -347,6 +353,68 @@ static int brcm_nsp_sata_init(struct brcm_sata_port *port) return 0; } +/* SR PHY PLL0 registers */ +#define SR_PLL0_ACTRL6_MAGIC 0xa + +/* SR PHY PLL1 registers */ +#define SR_PLL1_ACTRL2_MAGIC 0x32 +#define SR_PLL1_ACTRL3_MAGIC 0x2 +#define SR_PLL1_ACTRL4_MAGIC 0x3e8 + +static int brcm_sr_sata_init(struct brcm_sata_port *port) +{ + struct brcm_sata_phy *priv = port->phy_priv; + struct device *dev = port->phy_priv->dev; + void __iomem *base = priv->phy_base; + unsigned int val, try; + + /* Configure PHY PLL register bank 1 */ + val = SR_PLL1_ACTRL2_MAGIC; + brcm_sata_phy_wr(base, PLL1_REG_BANK, PLL1_ACTRL2, 0x0, val); + val = SR_PLL1_ACTRL3_MAGIC; + brcm_sata_phy_wr(base, PLL1_REG_BANK, PLL1_ACTRL3, 0x0, val); + val = SR_PLL1_ACTRL4_MAGIC; + brcm_sata_phy_wr(base, PLL1_REG_BANK, PLL1_ACTRL4, 0x0, val); + + /* Configure PHY PLL register bank 0 */ + val = SR_PLL0_ACTRL6_MAGIC; + brcm_sata_phy_wr(base, PLL_REG_BANK_0, PLL_ACTRL6, 0x0, val); + + /* Wait for PHY PLL lock by polling pll_lock bit */ + try = 50; + do { + val = brcm_sata_phy_rd(base, BLOCK0_REG_BANK, + BLOCK0_XGXSSTATUS); + if (val & BLOCK0_XGXSSTATUS_PLL_LOCK) + break; + msleep(20); + try--; + } while (try); + + if ((val & BLOCK0_XGXSSTATUS_PLL_LOCK) == 0) { + /* PLL did not lock; give up */ + dev_err(dev, "port%d PLL did not lock\n", port->portnum); + return -ETIMEDOUT; + } + + /* Invert Tx polarity */ + brcm_sata_phy_wr(base, TX_REG_BANK, TX_ACTRL0, + ~TX_ACTRL0_TXPOL_FLIP, TX_ACTRL0_TXPOL_FLIP); + + /* Configure OOB control to handle 100MHz reference clock */ + val = ((0xc << OOB_CTRL1_BURST_MAX_SHIFT) | + (0x4 << OOB_CTRL1_BURST_MIN_SHIFT) | + (0x8 << OOB_CTRL1_WAKE_IDLE_MAX_SHIFT) | + (0x3 << OOB_CTRL1_WAKE_IDLE_MIN_SHIFT)); + brcm_sata_phy_wr(base, OOB_REG_BANK, OOB_CTRL1, 0x0, val); + val = ((0x1b << OOB_CTRL2_RESET_IDLE_MAX_SHIFT) | + (0x2 << OOB_CTRL2_BURST_CNT_SHIFT) | + (0x9 << OOB_CTRL2_RESET_IDLE_MIN_SHIFT)); + brcm_sata_phy_wr(base, OOB_REG_BANK, OOB_CTRL2, 0x0, val); + + return 0; +} + static int brcm_sata_phy_init(struct phy *phy) { int rc; @@ -363,6 +431,9 @@ static int brcm_sata_phy_init(struct phy *phy) case BRCM_SATA_PHY_IPROC_NSP: rc = brcm_nsp_sata_init(port); break; + case BRCM_SATA_PHY_IPROC_SR: + rc = brcm_sr_sata_init(port); + break; default: rc = -ENODEV; } @@ -384,6 +455,8 @@ static const struct of_device_id brcm_sata_phy_of_match[] = { .data = (void *)BRCM_SATA_PHY_IPROC_NS2 }, { .compatible = "brcm,iproc-nsp-sata-phy", .data = (void *)BRCM_SATA_PHY_IPROC_NSP }, + { .compatible = "brcm,iproc-sr-sata-phy", + .data = (void *)BRCM_SATA_PHY_IPROC_SR }, {}, }; MODULE_DEVICE_TABLE(of, brcm_sata_phy_of_match); -- cgit v1.2.3 From 412512c4f924cb508895237722b36f673f5e113c Mon Sep 17 00:00:00 2001 From: Rafał Miłecki Date: Thu, 8 Jun 2017 22:04:24 +0200 Subject: phy: bcm-ns-usb3: always wait for idle after writing to the PHY reg MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move MDIO specific code to the writing helper function. This makes init code a bit more generic and doesn't require it to track what happens after every write. Signed-off-by: Rafał Miłecki Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/broadcom/phy-bcm-ns-usb3.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/drivers/phy/broadcom/phy-bcm-ns-usb3.c b/drivers/phy/broadcom/phy-bcm-ns-usb3.c index 22b5e7047fa6..5e89326886dc 100644 --- a/drivers/phy/broadcom/phy-bcm-ns-usb3.c +++ b/drivers/phy/broadcom/phy-bcm-ns-usb3.c @@ -112,7 +112,7 @@ static int bcm_ns_usb3_mdio_phy_write(struct bcm_ns_usb3 *usb3, u16 reg, tmp |= value; writel(tmp, usb3->ccb_mii + BCMA_CCB_MII_MNG_CMD_DATA); - return 0; + return bcm_ns_usb3_mii_mng_wait_idle(usb3); } static int bcm_ns_usb3_phy_init_ns_bx(struct bcm_ns_usb3 *usb3) @@ -143,9 +143,6 @@ static int bcm_ns_usb3_phy_init_ns_bx(struct bcm_ns_usb3 *usb3) /* Deaaserting PLL Reset */ bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PLLA_CONTROL1, 0x8000); - /* Waiting MII Mgt interface idle */ - bcm_ns_usb3_mii_mng_wait_idle(usb3); - /* Deasserting USB3 system reset */ writel(0, usb3->dmp + BCMA_RESET_CTL); @@ -169,9 +166,6 @@ static int bcm_ns_usb3_phy_init_ns_bx(struct bcm_ns_usb3 *usb3) /* Enabling SSC */ bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_TX_PMD_CONTROL1, 0x1003); - /* Waiting MII Mgt interface idle */ - bcm_ns_usb3_mii_mng_wait_idle(usb3); - return 0; } @@ -205,9 +199,6 @@ static int bcm_ns_usb3_phy_init_ns_ax(struct bcm_ns_usb3 *usb3) bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_TX_PMD_CONTROL1, 0x1003); - /* Waiting MII Mgt interface idle */ - bcm_ns_usb3_mii_mng_wait_idle(usb3); - /* Deasserting USB3 system reset */ writel(0, usb3->dmp + BCMA_RESET_CTL); -- cgit v1.2.3 From b20f506f6c63a3e6ee77aa4d519e4880c8a9d2f4 Mon Sep 17 00:00:00 2001 From: Rafał Miłecki Date: Thu, 8 Jun 2017 22:04:25 +0200 Subject: phy: bcm-ns-usb3: use pointer for PHY writing function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Our current writing function accesses PHY directly bypassing MDIO layer. The aim is to extend this module to also behave as MDIO driver. This will require using different writing function which can be handled cleanly by having an extra pointer like this. Signed-off-by: Rafał Miłecki Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/broadcom/phy-bcm-ns-usb3.c | 98 +++++++++++++++++++--------------- 1 file changed, 56 insertions(+), 42 deletions(-) diff --git a/drivers/phy/broadcom/phy-bcm-ns-usb3.c b/drivers/phy/broadcom/phy-bcm-ns-usb3.c index 5e89326886dc..3d0fe5728029 100644 --- a/drivers/phy/broadcom/phy-bcm-ns-usb3.c +++ b/drivers/phy/broadcom/phy-bcm-ns-usb3.c @@ -53,6 +53,8 @@ struct bcm_ns_usb3 { void __iomem *dmp; void __iomem *ccb_mii; struct phy *phy; + + int (*phy_write)(struct bcm_ns_usb3 *usb3, u16 reg, u16 value); }; static const struct of_device_id bcm_ns_usb3_id_table[] = { @@ -68,51 +70,10 @@ static const struct of_device_id bcm_ns_usb3_id_table[] = { }; MODULE_DEVICE_TABLE(of, bcm_ns_usb3_id_table); -static int bcm_ns_usb3_wait_reg(struct bcm_ns_usb3 *usb3, void __iomem *addr, - u32 mask, u32 value, unsigned long timeout) -{ - unsigned long deadline = jiffies + timeout; - u32 val; - - do { - val = readl(addr); - if ((val & mask) == value) - return 0; - cpu_relax(); - udelay(10); - } while (!time_after_eq(jiffies, deadline)); - - dev_err(usb3->dev, "Timeout waiting for register %p\n", addr); - - return -EBUSY; -} - -static inline int bcm_ns_usb3_mii_mng_wait_idle(struct bcm_ns_usb3 *usb3) -{ - return bcm_ns_usb3_wait_reg(usb3, usb3->ccb_mii + BCMA_CCB_MII_MNG_CTL, - 0x0100, 0x0000, - usecs_to_jiffies(BCM_NS_USB3_MII_MNG_TIMEOUT_US)); -} - static int bcm_ns_usb3_mdio_phy_write(struct bcm_ns_usb3 *usb3, u16 reg, u16 value) { - u32 tmp = 0; - int err; - - err = bcm_ns_usb3_mii_mng_wait_idle(usb3); - if (err < 0) { - dev_err(usb3->dev, "Couldn't write 0x%08x value\n", value); - return err; - } - - /* TODO: Use a proper MDIO bus layer */ - tmp |= 0x58020000; /* Magic value for MDIO PHY write */ - tmp |= reg << 18; - tmp |= value; - writel(tmp, usb3->ccb_mii + BCMA_CCB_MII_MNG_CMD_DATA); - - return bcm_ns_usb3_mii_mng_wait_idle(usb3); + return usb3->phy_write(usb3, reg, value); } static int bcm_ns_usb3_phy_init_ns_bx(struct bcm_ns_usb3 *usb3) @@ -233,6 +194,57 @@ static const struct phy_ops ops = { .owner = THIS_MODULE, }; +/************************************************** + * Platform driver code + **************************************************/ + +static int bcm_ns_usb3_wait_reg(struct bcm_ns_usb3 *usb3, void __iomem *addr, + u32 mask, u32 value, unsigned long timeout) +{ + unsigned long deadline = jiffies + timeout; + u32 val; + + do { + val = readl(addr); + if ((val & mask) == value) + return 0; + cpu_relax(); + udelay(10); + } while (!time_after_eq(jiffies, deadline)); + + dev_err(usb3->dev, "Timeout waiting for register %p\n", addr); + + return -EBUSY; +} + +static inline int bcm_ns_usb3_mii_mng_wait_idle(struct bcm_ns_usb3 *usb3) +{ + return bcm_ns_usb3_wait_reg(usb3, usb3->ccb_mii + BCMA_CCB_MII_MNG_CTL, + 0x0100, 0x0000, + usecs_to_jiffies(BCM_NS_USB3_MII_MNG_TIMEOUT_US)); +} + +static int bcm_ns_usb3_platform_phy_write(struct bcm_ns_usb3 *usb3, u16 reg, + u16 value) +{ + u32 tmp = 0; + int err; + + err = bcm_ns_usb3_mii_mng_wait_idle(usb3); + if (err < 0) { + dev_err(usb3->dev, "Couldn't write 0x%08x value\n", value); + return err; + } + + /* TODO: Use a proper MDIO bus layer */ + tmp |= 0x58020000; /* Magic value for MDIO PHY write */ + tmp |= reg << 18; + tmp |= value; + writel(tmp, usb3->ccb_mii + BCMA_CCB_MII_MNG_CMD_DATA); + + return bcm_ns_usb3_mii_mng_wait_idle(usb3); +} + static int bcm_ns_usb3_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -266,6 +278,8 @@ static int bcm_ns_usb3_probe(struct platform_device *pdev) return PTR_ERR(usb3->ccb_mii); } + usb3->phy_write = bcm_ns_usb3_platform_phy_write; + usb3->phy = devm_phy_create(dev, NULL, &ops); if (IS_ERR(usb3->phy)) { dev_err(dev, "Failed to create PHY\n"); -- cgit v1.2.3 From 4536adee0a96864a1ff000025c4c9a6299541f83 Mon Sep 17 00:00:00 2001 From: Rafał Miłecki Date: Thu, 8 Jun 2017 22:04:26 +0200 Subject: phy: bcm-ns-usb3: enable MDIO in the platform specific code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When we finally start using MDIO layer then bus initialization will be handled in a separated driver. It means our code handling this has to be used for the platform driver only. Signed-off-by: Rafał Miłecki Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/broadcom/phy-bcm-ns-usb3.c | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/drivers/phy/broadcom/phy-bcm-ns-usb3.c b/drivers/phy/broadcom/phy-bcm-ns-usb3.c index 3d0fe5728029..2c9a0d5f43d8 100644 --- a/drivers/phy/broadcom/phy-bcm-ns-usb3.c +++ b/drivers/phy/broadcom/phy-bcm-ns-usb3.c @@ -80,12 +80,6 @@ static int bcm_ns_usb3_phy_init_ns_bx(struct bcm_ns_usb3 *usb3) { int err; - /* Enable MDIO. Setting MDCDIV as 26 */ - writel(0x0000009a, usb3->ccb_mii + BCMA_CCB_MII_MNG_CTL); - - /* Wait for MDIO? */ - udelay(2); - /* USB3 PLL Block */ err = bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, BCM_NS_USB3_PHY_PLL30_BLOCK); @@ -134,12 +128,6 @@ static int bcm_ns_usb3_phy_init_ns_ax(struct bcm_ns_usb3 *usb3) { int err; - /* Enable MDIO. Setting MDCDIV as 26 */ - writel(0x0000009a, usb3->ccb_mii + BCMA_CCB_MII_MNG_CTL); - - /* Wait for MDIO? */ - udelay(2); - /* PLL30 block */ err = bcm_ns_usb3_mdio_phy_write(usb3, BCM_NS_USB3_PHY_BASE_ADDR_REG, BCM_NS_USB3_PHY_PLL30_BLOCK); @@ -278,6 +266,12 @@ static int bcm_ns_usb3_probe(struct platform_device *pdev) return PTR_ERR(usb3->ccb_mii); } + /* Enable MDIO. Setting MDCDIV as 26 */ + writel(0x0000009a, usb3->ccb_mii + BCMA_CCB_MII_MNG_CTL); + + /* Wait for MDIO? */ + udelay(2); + usb3->phy_write = bcm_ns_usb3_platform_phy_write; usb3->phy = devm_phy_create(dev, NULL, &ops); -- cgit v1.2.3 From 91699e98f19a392dc59defd0731a0434f842f9d4 Mon Sep 17 00:00:00 2001 From: Rafał Miłecki Date: Thu, 8 Jun 2017 22:04:27 +0200 Subject: dt-bindings: phy: Modify Broadcom NS USB 3.0 PHY binding to use MDIO MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thanks to work done by Broadcom explaining their USB 3.0 PHY details we know it's attached to the MDIO bus. Use this knowledge to update the binding: make it a subnode to the MDIO bus and rework way of specifying required registers. This will describe hardware more precisely and will allow to support (describe) more devices attached to the MDIO. While compatibility strings remain the same there isn't a direct conflict (compatibility breakage) for the binding. Originally it wasn't supposed to be used for MDIO subnode so this change should be safe unless some operating system was probing MDIO subnodes as standalone devices. Signed-off-by: Rafał Miłecki Acked-by: Rob Herring Signed-off-by: Kishon Vijay Abraham I --- .../devicetree/bindings/phy/bcm-ns-usb3-phy.txt | 27 +++++++++++++++------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/Documentation/devicetree/bindings/phy/bcm-ns-usb3-phy.txt b/Documentation/devicetree/bindings/phy/bcm-ns-usb3-phy.txt index 09aeba94538d..32f057260351 100644 --- a/Documentation/devicetree/bindings/phy/bcm-ns-usb3-phy.txt +++ b/Documentation/devicetree/bindings/phy/bcm-ns-usb3-phy.txt @@ -3,9 +3,10 @@ Driver for Broadcom Northstar USB 3.0 PHY Required properties: - compatible: one of: "brcm,ns-ax-usb3-phy", "brcm,ns-bx-usb3-phy". -- reg: register mappings for DMP (Device Management Plugin) and ChipCommon B - MMI. -- reg-names: "dmp" and "ccb-mii" +- reg: address of MDIO bus device +- usb3-dmp-syscon: phandle to syscon with DMP (Device Management Plugin) + registers +- #phy-cells: must be 0 Initialization of USB 3.0 PHY depends on Northstar version. There are currently three known series: Ax, Bx and Cx. @@ -15,9 +16,19 @@ Known B1: BCM4707 rev 6 Known C0: BCM47094 rev 0 Example: - usb3-phy { - compatible = "brcm,ns-ax-usb3-phy"; - reg = <0x18105000 0x1000>, <0x18003000 0x1000>; - reg-names = "dmp", "ccb-mii"; - #phy-cells = <0>; + mdio: mdio@0 { + reg = <0x0>; + #size-cells = <1>; + #address-cells = <0>; + + usb3-phy@10 { + compatible = "brcm,ns-ax-usb3-phy"; + reg = <0x10>; + usb3-dmp-syscon = <&usb3_dmp>; + #phy-cells = <0>; + }; + }; + + usb3_dmp: syscon@18105000 { + reg = <0x18105000 0x1000>; }; -- cgit v1.2.3 From af850e14a7ae493e85090723eb9cf85dafb8b243 Mon Sep 17 00:00:00 2001 From: Rafał Miłecki Date: Thu, 8 Jun 2017 22:04:28 +0200 Subject: phy: bcm-ns-usb3: add MDIO driver using proper bus layer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As USB 3.0 PHY is attached to the MDIO bus this module should provide a MDIO driver and use a proper bus layer. This is a proper (cleaner) solution which doesn't require code to know this specific MDIO bus details. It also allows reusing the driver with other MDIO buses. For now keep platform device support in place. We may consider dropping it once MDIO bindings gets used "everywhere". Signed-off-by: Rafał Miłecki Signed-off-by: Kishon Vijay Abraham I --- drivers/phy/broadcom/Kconfig | 1 + drivers/phy/broadcom/phy-bcm-ns-usb3.c | 105 ++++++++++++++++++++++++++++++++- 2 files changed, 105 insertions(+), 1 deletion(-) diff --git a/drivers/phy/broadcom/Kconfig b/drivers/phy/broadcom/Kconfig index c4b632e86916..37371b89b14f 100644 --- a/drivers/phy/broadcom/Kconfig +++ b/drivers/phy/broadcom/Kconfig @@ -31,6 +31,7 @@ config PHY_BCM_NS_USB3 depends on ARCH_BCM_IPROC || COMPILE_TEST depends on HAS_IOMEM && OF select GENERIC_PHY + select MDIO_DEVICE help Enable this to support Broadcom USB 3.0 PHY connected to the USB controller on Northstar family. diff --git a/drivers/phy/broadcom/phy-bcm-ns-usb3.c b/drivers/phy/broadcom/phy-bcm-ns-usb3.c index 2c9a0d5f43d8..a53ae128eadf 100644 --- a/drivers/phy/broadcom/phy-bcm-ns-usb3.c +++ b/drivers/phy/broadcom/phy-bcm-ns-usb3.c @@ -16,7 +16,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -52,6 +54,7 @@ struct bcm_ns_usb3 { enum bcm_ns_family family; void __iomem *dmp; void __iomem *ccb_mii; + struct mdio_device *mdiodev; struct phy *phy; int (*phy_write)(struct bcm_ns_usb3 *usb3, u16 reg, u16 value); @@ -182,6 +185,77 @@ static const struct phy_ops ops = { .owner = THIS_MODULE, }; +/************************************************** + * MDIO driver code + **************************************************/ + +static int bcm_ns_usb3_mdiodev_phy_write(struct bcm_ns_usb3 *usb3, u16 reg, + u16 value) +{ + struct mdio_device *mdiodev = usb3->mdiodev; + + return mdiobus_write(mdiodev->bus, mdiodev->addr, reg, value); +} + +static int bcm_ns_usb3_mdio_probe(struct mdio_device *mdiodev) +{ + struct device *dev = &mdiodev->dev; + const struct of_device_id *of_id; + struct phy_provider *phy_provider; + struct device_node *syscon_np; + struct bcm_ns_usb3 *usb3; + struct resource res; + int err; + + usb3 = devm_kzalloc(dev, sizeof(*usb3), GFP_KERNEL); + if (!usb3) + return -ENOMEM; + + usb3->dev = dev; + usb3->mdiodev = mdiodev; + + of_id = of_match_device(bcm_ns_usb3_id_table, dev); + if (!of_id) + return -EINVAL; + usb3->family = (enum bcm_ns_family)of_id->data; + + syscon_np = of_parse_phandle(dev->of_node, "usb3-dmp-syscon", 0); + err = of_address_to_resource(syscon_np, 0, &res); + of_node_put(syscon_np); + if (err) + return err; + + usb3->dmp = devm_ioremap_resource(dev, &res); + if (IS_ERR(usb3->dmp)) { + dev_err(dev, "Failed to map DMP regs\n"); + return PTR_ERR(usb3->dmp); + } + + usb3->phy_write = bcm_ns_usb3_mdiodev_phy_write; + + usb3->phy = devm_phy_create(dev, NULL, &ops); + if (IS_ERR(usb3->phy)) { + dev_err(dev, "Failed to create PHY\n"); + return PTR_ERR(usb3->phy); + } + + phy_set_drvdata(usb3->phy, usb3); + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static struct mdio_driver bcm_ns_usb3_mdio_driver = { + .mdiodrv = { + .driver = { + .name = "bcm_ns_mdio_usb3", + .of_match_table = bcm_ns_usb3_id_table, + }, + }, + .probe = bcm_ns_usb3_mdio_probe, +}; + /************************************************** * Platform driver code **************************************************/ @@ -297,6 +371,35 @@ static struct platform_driver bcm_ns_usb3_driver = { .of_match_table = bcm_ns_usb3_id_table, }, }; -module_platform_driver(bcm_ns_usb3_driver); + +static int __init bcm_ns_usb3_module_init(void) +{ + int err; + + /* + * For backward compatibility we register as MDIO and platform driver. + * After getting MDIO binding commonly used (e.g. switching all DT files + * to use it) we should deprecate the old binding and eventually drop + * support for it. + */ + + err = mdio_driver_register(&bcm_ns_usb3_mdio_driver); + if (err) + return err; + + err = platform_driver_register(&bcm_ns_usb3_driver); + if (err) + mdio_driver_unregister(&bcm_ns_usb3_mdio_driver); + + return err; +} +module_init(bcm_ns_usb3_module_init); + +static void __exit bcm_ns_usb3_module_exit(void) +{ + platform_driver_unregister(&bcm_ns_usb3_driver); + mdio_driver_unregister(&bcm_ns_usb3_mdio_driver); +} +module_exit(bcm_ns_usb3_module_exit) MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From c8e4e5bdb62a5ac6f860ebcaaf7b467b62f453f1 Mon Sep 17 00:00:00 2001 From: Srinath Mannam Date: Thu, 15 Jun 2017 14:39:22 +0530 Subject: usb: gadget: bdc: 64-bit pointer capability check Corrected the register to check the 64-bit pointer capability state. 64-bit pointer implementation capability was checking in wrong register, which causes the BDC enumeration failure in 64-bit memory address. Fixes: efed421a94e6 ("usb: gadget: Add UDC driver for Broadcom USB3.0 device controller IP BDC") Reviewed-by: Florian Fainelli Signed-off-by: Srinath Mannam Signed-off-by: Felipe Balbi --- drivers/usb/gadget/udc/bdc/bdc_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/gadget/udc/bdc/bdc_core.c b/drivers/usb/gadget/udc/bdc/bdc_core.c index ccb9c213cc9f..e9bd8d4abca0 100644 --- a/drivers/usb/gadget/udc/bdc/bdc_core.c +++ b/drivers/usb/gadget/udc/bdc/bdc_core.c @@ -475,7 +475,7 @@ static int bdc_probe(struct platform_device *pdev) bdc->dev = dev; dev_dbg(bdc->dev, "bdc->regs: %p irq=%d\n", bdc->regs, bdc->irq); - temp = bdc_readl(bdc->regs, BDC_BDCSC); + temp = bdc_readl(bdc->regs, BDC_BDCCAP1); if ((temp & BDC_P64) && !dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64))) { dev_dbg(bdc->dev, "Using 64-bit address\n"); -- cgit v1.2.3 From d423b9657f27c0e7de514a8bce8bb71a31a7549b Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Fri, 16 Jun 2017 13:30:18 +0200 Subject: usb: gadget: udc: atmel: Remove unnecessary macros commit 46ddd79e893b ("usb: gadget: udc: atmel: Remove AVR32 bits from the driver") left the accessor macros introduced by commit a3dd3befd7cb ("usb: gadget: atmel_usba: use endian agnostic IO on ARM"). They can now be removed. Signed-off-by: Alexandre Belloni Signed-off-by: Felipe Balbi --- drivers/usb/gadget/udc/atmel_usba_udc.c | 4 ++-- drivers/usb/gadget/udc/atmel_usba_udc.h | 16 ++++++---------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/drivers/usb/gadget/udc/atmel_usba_udc.c b/drivers/usb/gadget/udc/atmel_usba_udc.c index 3ccc34176a5a..98d71400f8a1 100644 --- a/drivers/usb/gadget/udc/atmel_usba_udc.c +++ b/drivers/usb/gadget/udc/atmel_usba_udc.c @@ -152,7 +152,7 @@ static int regs_dbg_open(struct inode *inode, struct file *file) spin_lock_irq(&udc->lock); for (i = 0; i < inode->i_size / 4; i++) - data[i] = usba_io_readl(udc->regs + i * 4); + data[i] = readl_relaxed(udc->regs + i * 4); spin_unlock_irq(&udc->lock); file->private_data = data; @@ -1369,7 +1369,7 @@ static int handle_ep0_setup(struct usba_udc *udc, struct usba_ep *ep, if (crq->wLength != cpu_to_le16(sizeof(status))) goto stall; ep->state = DATA_STAGE_IN; - usba_io_writew(status, ep->fifo); + writew_relaxed(status, ep->fifo); usba_ep_writel(ep, SET_STA, USBA_TX_PK_RDY); break; } diff --git a/drivers/usb/gadget/udc/atmel_usba_udc.h b/drivers/usb/gadget/udc/atmel_usba_udc.h index 433bed0c481e..f8ebe0389bd4 100644 --- a/drivers/usb/gadget/udc/atmel_usba_udc.h +++ b/drivers/usb/gadget/udc/atmel_usba_udc.h @@ -186,22 +186,18 @@ | USBA_BF(name, value)) /* Register access macros */ -#define usba_io_readl readl_relaxed -#define usba_io_writel writel_relaxed -#define usba_io_writew writew_relaxed - #define usba_readl(udc, reg) \ - usba_io_readl((udc)->regs + USBA_##reg) + readl_relaxed((udc)->regs + USBA_##reg) #define usba_writel(udc, reg, value) \ - usba_io_writel((value), (udc)->regs + USBA_##reg) + writel_relaxed((value), (udc)->regs + USBA_##reg) #define usba_ep_readl(ep, reg) \ - usba_io_readl((ep)->ep_regs + USBA_EPT_##reg) + readl_relaxed((ep)->ep_regs + USBA_EPT_##reg) #define usba_ep_writel(ep, reg, value) \ - usba_io_writel((value), (ep)->ep_regs + USBA_EPT_##reg) + writel_relaxed((value), (ep)->ep_regs + USBA_EPT_##reg) #define usba_dma_readl(ep, reg) \ - usba_io_readl((ep)->dma_regs + USBA_DMA_##reg) + readl_relaxed((ep)->dma_regs + USBA_DMA_##reg) #define usba_dma_writel(ep, reg, value) \ - usba_io_writel((value), (ep)->dma_regs + USBA_DMA_##reg) + writel_relaxed((value), (ep)->dma_regs + USBA_DMA_##reg) /* Calculate base address for a given endpoint or DMA controller */ #define USBA_EPT_BASE(x) (0x100 + (x) * 0x20) -- cgit v1.2.3 From 1fc4926d92b9515b44f35b339bab5d2ca474a723 Mon Sep 17 00:00:00 2001 From: Ruslan Bilovol Date: Sun, 18 Jun 2017 00:23:58 +0300 Subject: usb: gadget: function: f_uac1: implement get_alt() After commit 7e4da3fcf7c9 ("usb: gadget: composite: Test get_alt() presence instead of set_alt()") f_uac1 function became broken because it doesn't have get_alt() callback implementation and composite framework never set altsetting 1 for audiostreaming interface. On host site it looks like: [424339.017711] 21:1:1: usb_set_interface failed (-32) Since host can't set altsetting 1, it can't start playing audio. In order to fix it implemented get_alt along with minor improvements (error conditions checking) similar to what existing f_uac2 has. Cc: Krzysztof Opasiak Signed-off-by: Ruslan Bilovol Signed-off-by: Felipe Balbi --- drivers/usb/gadget/function/f_uac1.c | 40 +++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/drivers/usb/gadget/function/f_uac1.c b/drivers/usb/gadget/function/f_uac1.c index f2ac0cbc29a4..5dfc94b8e69a 100644 --- a/drivers/usb/gadget/function/f_uac1.c +++ b/drivers/usb/gadget/function/f_uac1.c @@ -277,6 +277,9 @@ static void f_audio_buffer_free(struct f_audio_buf *audio_buf) struct f_audio { struct gaudio card; + u8 ac_intf, ac_alt; + u8 as_intf, as_alt; + /* endpoints handle full and/or high speeds */ struct usb_ep *out_ep; @@ -586,7 +589,20 @@ static int f_audio_set_alt(struct usb_function *f, unsigned intf, unsigned alt) req_count = opts->req_count; audio_buf_size = opts->audio_buf_size; - if (intf == 1) { + /* No i/f has more than 2 alt settings */ + if (alt > 1) { + ERROR(cdev, "%s:%d Error!\n", __func__, __LINE__); + return -EINVAL; + } + + if (intf == audio->ac_intf) { + /* Control I/f has only 1 AltSetting - 0 */ + if (alt) { + ERROR(cdev, "%s:%d Error!\n", __func__, __LINE__); + return -EINVAL; + } + return 0; + } else if (intf == audio->as_intf) { if (alt == 1) { err = config_ep_by_speed(cdev->gadget, f, out_ep); if (err) @@ -631,11 +647,28 @@ static int f_audio_set_alt(struct usb_function *f, unsigned intf, unsigned alt) schedule_work(&audio->playback_work); } } + audio->as_alt = alt; } return err; } +static int f_audio_get_alt(struct usb_function *f, unsigned intf) +{ + struct f_audio *audio = func_to_audio(f); + struct usb_composite_dev *cdev = f->config->cdev; + + if (intf == audio->ac_intf) + return audio->ac_alt; + else if (intf == audio->as_intf) + return audio->as_alt; + else + ERROR(cdev, "%s:%d Invalid Interface %d!\n", + __func__, __LINE__, intf); + + return -EINVAL; +} + static void f_audio_disable(struct usb_function *f) { return; @@ -702,12 +735,16 @@ f_audio_bind(struct usb_configuration *c, struct usb_function *f) if (status < 0) goto fail; ac_interface_desc.bInterfaceNumber = status; + audio->ac_intf = status; + audio->ac_alt = 0; status = usb_interface_id(c, f); if (status < 0) goto fail; as_interface_alt_0_desc.bInterfaceNumber = status; as_interface_alt_1_desc.bInterfaceNumber = status; + audio->as_intf = status; + audio->as_alt = 0; status = -ENODEV; @@ -966,6 +1003,7 @@ static struct usb_function *f_audio_alloc(struct usb_function_instance *fi) audio->card.func.bind = f_audio_bind; audio->card.func.unbind = f_audio_unbind; audio->card.func.set_alt = f_audio_set_alt; + audio->card.func.get_alt = f_audio_get_alt; audio->card.func.setup = f_audio_setup; audio->card.func.disable = f_audio_disable; audio->card.func.free_func = f_audio_free; -- cgit v1.2.3 From 7158b57a495635c04507d986117ae26b2eb5e4e5 Mon Sep 17 00:00:00 2001 From: Ruslan Bilovol Date: Sun, 18 Jun 2017 16:23:51 +0300 Subject: usb: gadget: f_uac2: remove platform driver/device creation Simplify f_uac2 by removing platform driver/device creation; use composite's usb_gadget device as parent for sound card and for debug prints. This removes extra layer of code without any functional change. Signed-off-by: Ruslan Bilovol Signed-off-by: Felipe Balbi --- drivers/usb/gadget/function/f_uac2.c | 107 +++++++++-------------------------- 1 file changed, 28 insertions(+), 79 deletions(-) diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c index 5a7ba058d947..f674bae17e8d 100644 --- a/drivers/usb/gadget/function/f_uac2.c +++ b/drivers/usb/gadget/function/f_uac2.c @@ -13,7 +13,6 @@ #include #include -#include #include #include @@ -51,8 +50,6 @@ #define UNFLW_CTRL 8 #define OVFLW_CTRL 10 -static const char *uac2_name = "snd_uac2"; - struct uac2_req { struct uac2_rtd_params *pp; /* parent param */ struct usb_request *req; @@ -81,9 +78,6 @@ struct uac2_rtd_params { }; struct snd_uac2_chip { - struct platform_device pdev; - struct platform_driver pdrv; - struct uac2_rtd_params p_prm; struct uac2_rtd_params c_prm; @@ -122,6 +116,7 @@ struct audio_dev { struct usb_ep *in_ep, *out_ep; struct usb_function func; + struct usb_gadget *gadget; /* The ALSA Sound Card it represents on the USB-Client side */ struct snd_uac2_chip uac2; @@ -139,12 +134,6 @@ struct audio_dev *uac2_to_agdev(struct snd_uac2_chip *u) return container_of(u, struct audio_dev, uac2); } -static inline -struct snd_uac2_chip *pdev_to_uac2(struct platform_device *p) -{ - return container_of(p, struct snd_uac2_chip, pdev); -} - static inline struct f_uac2_opts *agdev_to_uac2_opts(struct audio_dev *agdev) { @@ -254,7 +243,7 @@ agdev_iso_complete(struct usb_ep *ep, struct usb_request *req) exit: if (usb_ep_queue(ep, req, GFP_ATOMIC)) - dev_err(&uac2->pdev.dev, "%d Error!\n", __LINE__); + dev_err(uac2->card->dev, "%d Error!\n", __LINE__); if (update_alsa) snd_pcm_period_elapsed(substream); @@ -440,23 +429,22 @@ static struct snd_pcm_ops uac2_pcm_ops = { .prepare = uac2_pcm_null, }; -static int snd_uac2_probe(struct platform_device *pdev) +static int snd_uac2_probe(struct audio_dev *audio_dev) { - struct snd_uac2_chip *uac2 = pdev_to_uac2(pdev); + struct snd_uac2_chip *uac2 = &audio_dev->uac2; struct snd_card *card; struct snd_pcm *pcm; - struct audio_dev *audio_dev; struct f_uac2_opts *opts; int err; int p_chmask, c_chmask; - audio_dev = uac2_to_agdev(uac2); opts = container_of(audio_dev->func.fi, struct f_uac2_opts, func_inst); p_chmask = opts->p_chmask; c_chmask = opts->c_chmask; /* Choose any slot, with no id */ - err = snd_card_new(&pdev->dev, -1, NULL, THIS_MODULE, 0, &card); + err = snd_card_new(&audio_dev->gadget->dev, + -1, NULL, THIS_MODULE, 0, &card); if (err < 0) return err; @@ -481,16 +469,15 @@ static int snd_uac2_probe(struct platform_device *pdev) strcpy(card->driver, "UAC2_Gadget"); strcpy(card->shortname, "UAC2_Gadget"); - sprintf(card->longname, "UAC2_Gadget %i", pdev->id); + sprintf(card->longname, "UAC2_Gadget %i", card->dev->id); snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, snd_dma_continuous_data(GFP_KERNEL), 0, BUFF_SIZE_MAX); err = snd_card_register(card); - if (!err) { - platform_set_drvdata(pdev, card); + + if (!err) return 0; - } snd_fail: snd_card_free(card); @@ -501,9 +488,9 @@ snd_fail: return err; } -static int snd_uac2_remove(struct platform_device *pdev) +static int snd_uac2_remove(struct audio_dev *audio_dev) { - struct snd_card *card = platform_get_drvdata(pdev); + struct snd_card *card = audio_dev->uac2.card; if (card) return snd_card_free(card); @@ -511,45 +498,6 @@ static int snd_uac2_remove(struct platform_device *pdev) return 0; } -static void snd_uac2_release(struct device *dev) -{ - dev_dbg(dev, "releasing '%s'\n", dev_name(dev)); -} - -static int alsa_uac2_init(struct audio_dev *agdev) -{ - struct snd_uac2_chip *uac2 = &agdev->uac2; - int err; - - uac2->pdrv.probe = snd_uac2_probe; - uac2->pdrv.remove = snd_uac2_remove; - uac2->pdrv.driver.name = uac2_name; - - uac2->pdev.id = 0; - uac2->pdev.name = uac2_name; - uac2->pdev.dev.release = snd_uac2_release; - - /* Register snd_uac2 driver */ - err = platform_driver_register(&uac2->pdrv); - if (err) - return err; - - /* Register snd_uac2 device */ - err = platform_device_register(&uac2->pdev); - if (err) - platform_driver_unregister(&uac2->pdrv); - - return err; -} - -static void alsa_uac2_exit(struct audio_dev *agdev) -{ - struct snd_uac2_chip *uac2 = &agdev->uac2; - - platform_driver_unregister(&uac2->pdrv); - platform_device_unregister(&uac2->pdev); -} - /* --------- USB Function Interface ------------- */ @@ -960,7 +908,7 @@ free_ep(struct uac2_rtd_params *prm, struct usb_ep *ep) } if (usb_ep_disable(ep)) - dev_err(&uac2->pdev.dev, + dev_err(uac2->card->dev, "%s:%d Error!\n", __func__, __LINE__); } @@ -994,7 +942,7 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) struct snd_uac2_chip *uac2 = &agdev->uac2; struct usb_composite_dev *cdev = cfg->cdev; struct usb_gadget *gadget = cdev->gadget; - struct device *dev = &uac2->pdev.dev; + struct device *dev = &gadget->dev; struct uac2_rtd_params *prm; struct f_uac2_opts *uac2_opts; struct usb_string *us; @@ -1094,6 +1042,8 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) if (ret) return ret; + agdev->gadget = gadget; + prm = &agdev->uac2.c_prm; prm->max_psize = hs_epout_desc.wMaxPacketSize; prm->ureq = kcalloc(uac2_opts->req_number, sizeof(struct uac2_req), @@ -1124,7 +1074,7 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) goto err_no_memory; } - ret = alsa_uac2_init(agdev); + ret = snd_uac2_probe(agdev); if (ret) goto err_no_memory; return 0; @@ -1136,6 +1086,7 @@ err_no_memory: kfree(agdev->uac2.c_prm.rbuf); err_free_descs: usb_free_all_descriptors(fn); + agdev->gadget = NULL; return ret; } @@ -1147,7 +1098,7 @@ afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt) struct f_uac2_opts *opts = agdev_to_uac2_opts(agdev); struct snd_uac2_chip *uac2 = &agdev->uac2; struct usb_gadget *gadget = cdev->gadget; - struct device *dev = &uac2->pdev.dev; + struct device *dev = &gadget->dev; struct usb_request *req; struct usb_ep *ep; struct uac2_rtd_params *prm; @@ -1247,7 +1198,6 @@ static int afunc_get_alt(struct usb_function *fn, unsigned intf) { struct audio_dev *agdev = func_to_agdev(fn); - struct snd_uac2_chip *uac2 = &agdev->uac2; if (intf == agdev->ac_intf) return agdev->ac_alt; @@ -1256,7 +1206,7 @@ afunc_get_alt(struct usb_function *fn, unsigned intf) else if (intf == agdev->as_in_intf) return agdev->as_in_alt; else - dev_err(&uac2->pdev.dev, + dev_err(&agdev->gadget->dev, "%s:%d Invalid Interface %d!\n", __func__, __LINE__, intf); @@ -1281,7 +1231,6 @@ in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr) { struct usb_request *req = fn->config->cdev->req; struct audio_dev *agdev = func_to_agdev(fn); - struct snd_uac2_chip *uac2 = &agdev->uac2; struct f_uac2_opts *opts; u16 w_length = le16_to_cpu(cr->wLength); u16 w_index = le16_to_cpu(cr->wIndex); @@ -1310,7 +1259,7 @@ in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr) *(u8 *)req->buf = 1; value = min_t(unsigned, w_length, 1); } else { - dev_err(&uac2->pdev.dev, + dev_err(&agdev->gadget->dev, "%s:%d control_selector=%d TODO!\n", __func__, __LINE__, control_selector); } @@ -1323,7 +1272,6 @@ in_rq_range(struct usb_function *fn, const struct usb_ctrlrequest *cr) { struct usb_request *req = fn->config->cdev->req; struct audio_dev *agdev = func_to_agdev(fn); - struct snd_uac2_chip *uac2 = &agdev->uac2; struct f_uac2_opts *opts; u16 w_length = le16_to_cpu(cr->wLength); u16 w_index = le16_to_cpu(cr->wIndex); @@ -1353,7 +1301,7 @@ in_rq_range(struct usb_function *fn, const struct usb_ctrlrequest *cr) value = min_t(unsigned, w_length, sizeof r); memcpy(req->buf, &r, value); } else { - dev_err(&uac2->pdev.dev, + dev_err(&agdev->gadget->dev, "%s:%d control_selector=%d TODO!\n", __func__, __LINE__, control_selector); } @@ -1389,12 +1337,11 @@ static int setup_rq_inf(struct usb_function *fn, const struct usb_ctrlrequest *cr) { struct audio_dev *agdev = func_to_agdev(fn); - struct snd_uac2_chip *uac2 = &agdev->uac2; u16 w_index = le16_to_cpu(cr->wIndex); u8 intf = w_index & 0xff; if (intf != agdev->ac_intf) { - dev_err(&uac2->pdev.dev, + dev_err(&agdev->gadget->dev, "%s:%d Error!\n", __func__, __LINE__); return -EOPNOTSUPP; } @@ -1412,7 +1359,6 @@ afunc_setup(struct usb_function *fn, const struct usb_ctrlrequest *cr) { struct usb_composite_dev *cdev = fn->config->cdev; struct audio_dev *agdev = func_to_agdev(fn); - struct snd_uac2_chip *uac2 = &agdev->uac2; struct usb_request *req = cdev->req; u16 w_length = le16_to_cpu(cr->wLength); int value = -EOPNOTSUPP; @@ -1424,14 +1370,15 @@ afunc_setup(struct usb_function *fn, const struct usb_ctrlrequest *cr) if ((cr->bRequestType & USB_RECIP_MASK) == USB_RECIP_INTERFACE) value = setup_rq_inf(fn, cr); else - dev_err(&uac2->pdev.dev, "%s:%d Error!\n", __func__, __LINE__); + dev_err(&agdev->gadget->dev, "%s:%d Error!\n", + __func__, __LINE__); if (value >= 0) { req->length = value; req->zero = value < w_length; value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); if (value < 0) { - dev_err(&uac2->pdev.dev, + dev_err(&agdev->gadget->dev, "%s:%d Error!\n", __func__, __LINE__); req->status = 0; } @@ -1573,7 +1520,7 @@ static void afunc_unbind(struct usb_configuration *c, struct usb_function *f) struct audio_dev *agdev = func_to_agdev(f); struct uac2_rtd_params *prm; - alsa_uac2_exit(agdev); + snd_uac2_remove(agdev); prm = &agdev->uac2.p_prm; kfree(prm->rbuf); @@ -1582,6 +1529,8 @@ static void afunc_unbind(struct usb_configuration *c, struct usb_function *f) kfree(prm->rbuf); kfree(prm->ureq); usb_free_all_descriptors(f); + + agdev->gadget = NULL; } static struct usb_function *afunc_alloc(struct usb_function_instance *fi) -- cgit v1.2.3 From eb9fecb9e69b0be8c267c55b0bb52a08e8fb6bee Mon Sep 17 00:00:00 2001 From: Ruslan Bilovol Date: Sun, 18 Jun 2017 16:23:52 +0300 Subject: usb: gadget: f_uac2: split out audio core Abstract the peripheral side ALSA sound card code from the f_uac2 function into a component that can be called by various functions, so the various flavors can be split apart and selectively reused. Visible changes: - add uac_params structure to pass audio paramteres for g_audio_setup - make ALSA sound card's name configurable - add [in/out]_ep_maxpsize - allocate snd_uac_chip structure during g_audio_setup - add u_audio_[start/stop]_[capture/playback] functions Signed-off-by: Ruslan Bilovol Signed-off-by: Felipe Balbi --- drivers/usb/gadget/Kconfig | 4 + drivers/usb/gadget/function/Makefile | 1 + drivers/usb/gadget/function/f_uac2.c | 718 ++++------------------------------ drivers/usb/gadget/function/u_audio.c | 662 +++++++++++++++++++++++++++++++ drivers/usb/gadget/function/u_audio.h | 95 +++++ drivers/usb/gadget/legacy/Kconfig | 1 + 6 files changed, 845 insertions(+), 636 deletions(-) create mode 100644 drivers/usb/gadget/function/u_audio.c create mode 100644 drivers/usb/gadget/function/u_audio.h diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index b3c879b75a39..3b0ffd6e515b 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -158,6 +158,9 @@ config USB_U_SERIAL config USB_U_ETHER tristate +config USB_U_AUDIO + tristate + config USB_F_SERIAL tristate @@ -381,6 +384,7 @@ config USB_CONFIGFS_F_UAC2 depends on SND select USB_LIBCOMPOSITE select SND_PCM + select USB_U_AUDIO select USB_F_UAC2 help This Audio function is compatible with USB Audio Class diff --git a/drivers/usb/gadget/function/Makefile b/drivers/usb/gadget/function/Makefile index cb8c225e8549..b29f2ae23357 100644 --- a/drivers/usb/gadget/function/Makefile +++ b/drivers/usb/gadget/function/Makefile @@ -32,6 +32,7 @@ usb_f_mass_storage-y := f_mass_storage.o storage_common.o obj-$(CONFIG_USB_F_MASS_STORAGE)+= usb_f_mass_storage.o usb_f_fs-y := f_fs.o obj-$(CONFIG_USB_F_FS) += usb_f_fs.o +obj-$(CONFIG_USB_U_AUDIO) += u_audio.o usb_f_uac1-y := f_uac1.o u_uac1.o obj-$(CONFIG_USB_F_UAC1) += usb_f_uac1.o usb_f_uac2-y := f_uac2.o diff --git a/drivers/usb/gadget/function/f_uac2.c b/drivers/usb/gadget/function/f_uac2.c index f674bae17e8d..9082ce261e70 100644 --- a/drivers/usb/gadget/function/f_uac2.c +++ b/drivers/usb/gadget/function/f_uac2.c @@ -15,10 +15,7 @@ #include #include -#include -#include -#include - +#include "u_audio.h" #include "u_uac2.h" /* @@ -50,455 +47,23 @@ #define UNFLW_CTRL 8 #define OVFLW_CTRL 10 -struct uac2_req { - struct uac2_rtd_params *pp; /* parent param */ - struct usb_request *req; +struct f_uac2 { + struct g_audio g_audio; + u8 ac_intf, as_in_intf, as_out_intf; + u8 ac_alt, as_in_alt, as_out_alt; /* needed for get_alt() */ }; -struct uac2_rtd_params { - struct snd_uac2_chip *uac2; /* parent chip */ - bool ep_enabled; /* if the ep is enabled */ - /* Size of the ring buffer */ - size_t dma_bytes; - unsigned char *dma_area; - - struct snd_pcm_substream *ss; - - /* Ring buffer */ - ssize_t hw_ptr; - - void *rbuf; - - size_t period_size; - - unsigned max_psize; - struct uac2_req *ureq; - - spinlock_t lock; -}; - -struct snd_uac2_chip { - struct uac2_rtd_params p_prm; - struct uac2_rtd_params c_prm; - - struct snd_card *card; - struct snd_pcm *pcm; - - /* timekeeping for the playback endpoint */ - unsigned int p_interval; - unsigned int p_residue; - - /* pre-calculated values for playback iso completion */ - unsigned int p_pktsize; - unsigned int p_pktsize_residue; - unsigned int p_framesize; -}; - -#define BUFF_SIZE_MAX (PAGE_SIZE * 16) -#define PRD_SIZE_MAX PAGE_SIZE -#define MIN_PERIODS 4 - -static struct snd_pcm_hardware uac2_pcm_hardware = { - .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER - | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID - | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, - .rates = SNDRV_PCM_RATE_CONTINUOUS, - .periods_max = BUFF_SIZE_MAX / PRD_SIZE_MAX, - .buffer_bytes_max = BUFF_SIZE_MAX, - .period_bytes_max = PRD_SIZE_MAX, - .periods_min = MIN_PERIODS, -}; - -struct audio_dev { - u8 ac_intf, ac_alt; - u8 as_out_intf, as_out_alt; - u8 as_in_intf, as_in_alt; - - struct usb_ep *in_ep, *out_ep; - struct usb_function func; - struct usb_gadget *gadget; - - /* The ALSA Sound Card it represents on the USB-Client side */ - struct snd_uac2_chip uac2; -}; - -static inline -struct audio_dev *func_to_agdev(struct usb_function *f) -{ - return container_of(f, struct audio_dev, func); -} - -static inline -struct audio_dev *uac2_to_agdev(struct snd_uac2_chip *u) +static inline struct f_uac2 *func_to_uac2(struct usb_function *f) { - return container_of(u, struct audio_dev, uac2); + return container_of(f, struct f_uac2, g_audio.func); } static inline -struct f_uac2_opts *agdev_to_uac2_opts(struct audio_dev *agdev) +struct f_uac2_opts *g_audio_to_uac2_opts(struct g_audio *agdev) { return container_of(agdev->func.fi, struct f_uac2_opts, func_inst); } -static inline -uint num_channels(uint chanmask) -{ - uint num = 0; - - while (chanmask) { - num += (chanmask & 1); - chanmask >>= 1; - } - - return num; -} - -static void -agdev_iso_complete(struct usb_ep *ep, struct usb_request *req) -{ - unsigned pending; - unsigned long flags; - unsigned int hw_ptr; - bool update_alsa = false; - int status = req->status; - struct uac2_req *ur = req->context; - struct snd_pcm_substream *substream; - struct uac2_rtd_params *prm = ur->pp; - struct snd_uac2_chip *uac2 = prm->uac2; - - /* i/f shutting down */ - if (!prm->ep_enabled || req->status == -ESHUTDOWN) - return; - - /* - * We can't really do much about bad xfers. - * Afterall, the ISOCH xfers could fail legitimately. - */ - if (status) - pr_debug("%s: iso_complete status(%d) %d/%d\n", - __func__, status, req->actual, req->length); - - substream = prm->ss; - - /* Do nothing if ALSA isn't active */ - if (!substream) - goto exit; - - spin_lock_irqsave(&prm->lock, flags); - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - /* - * For each IN packet, take the quotient of the current data - * rate and the endpoint's interval as the base packet size. - * If there is a residue from this division, add it to the - * residue accumulator. - */ - req->length = uac2->p_pktsize; - uac2->p_residue += uac2->p_pktsize_residue; - - /* - * Whenever there are more bytes in the accumulator than we - * need to add one more sample frame, increase this packet's - * size and decrease the accumulator. - */ - if (uac2->p_residue / uac2->p_interval >= uac2->p_framesize) { - req->length += uac2->p_framesize; - uac2->p_residue -= uac2->p_framesize * - uac2->p_interval; - } - - req->actual = req->length; - } - - pending = prm->hw_ptr % prm->period_size; - pending += req->actual; - if (pending >= prm->period_size) - update_alsa = true; - - hw_ptr = prm->hw_ptr; - prm->hw_ptr = (prm->hw_ptr + req->actual) % prm->dma_bytes; - - spin_unlock_irqrestore(&prm->lock, flags); - - /* Pack USB load in ALSA ring buffer */ - pending = prm->dma_bytes - hw_ptr; - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - if (unlikely(pending < req->actual)) { - memcpy(req->buf, prm->dma_area + hw_ptr, pending); - memcpy(req->buf + pending, prm->dma_area, - req->actual - pending); - } else { - memcpy(req->buf, prm->dma_area + hw_ptr, req->actual); - } - } else { - if (unlikely(pending < req->actual)) { - memcpy(prm->dma_area + hw_ptr, req->buf, pending); - memcpy(prm->dma_area, req->buf + pending, - req->actual - pending); - } else { - memcpy(prm->dma_area + hw_ptr, req->buf, req->actual); - } - } - -exit: - if (usb_ep_queue(ep, req, GFP_ATOMIC)) - dev_err(uac2->card->dev, "%d Error!\n", __LINE__); - - if (update_alsa) - snd_pcm_period_elapsed(substream); - - return; -} - -static int -uac2_pcm_trigger(struct snd_pcm_substream *substream, int cmd) -{ - struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream); - struct audio_dev *agdev = uac2_to_agdev(uac2); - struct f_uac2_opts *uac2_opts = agdev_to_uac2_opts(agdev); - struct uac2_rtd_params *prm; - unsigned long flags; - int err = 0; - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - prm = &uac2->p_prm; - else - prm = &uac2->c_prm; - - spin_lock_irqsave(&prm->lock, flags); - - /* Reset */ - prm->hw_ptr = 0; - - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - case SNDRV_PCM_TRIGGER_RESUME: - prm->ss = substream; - break; - case SNDRV_PCM_TRIGGER_STOP: - case SNDRV_PCM_TRIGGER_SUSPEND: - prm->ss = NULL; - break; - default: - err = -EINVAL; - } - - spin_unlock_irqrestore(&prm->lock, flags); - - /* Clear buffer after Play stops */ - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && !prm->ss) - memset(prm->rbuf, 0, prm->max_psize * uac2_opts->req_number); - - return err; -} - -static snd_pcm_uframes_t uac2_pcm_pointer(struct snd_pcm_substream *substream) -{ - struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream); - struct uac2_rtd_params *prm; - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - prm = &uac2->p_prm; - else - prm = &uac2->c_prm; - - return bytes_to_frames(substream->runtime, prm->hw_ptr); -} - -static int uac2_pcm_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *hw_params) -{ - struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream); - struct uac2_rtd_params *prm; - int err; - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - prm = &uac2->p_prm; - else - prm = &uac2->c_prm; - - err = snd_pcm_lib_malloc_pages(substream, - params_buffer_bytes(hw_params)); - if (err >= 0) { - prm->dma_bytes = substream->runtime->dma_bytes; - prm->dma_area = substream->runtime->dma_area; - prm->period_size = params_period_bytes(hw_params); - } - - return err; -} - -static int uac2_pcm_hw_free(struct snd_pcm_substream *substream) -{ - struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream); - struct uac2_rtd_params *prm; - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - prm = &uac2->p_prm; - else - prm = &uac2->c_prm; - - prm->dma_area = NULL; - prm->dma_bytes = 0; - prm->period_size = 0; - - return snd_pcm_lib_free_pages(substream); -} - -static int uac2_pcm_open(struct snd_pcm_substream *substream) -{ - struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream); - struct snd_pcm_runtime *runtime = substream->runtime; - struct audio_dev *audio_dev; - struct f_uac2_opts *opts; - int p_ssize, c_ssize; - int p_srate, c_srate; - int p_chmask, c_chmask; - - audio_dev = uac2_to_agdev(uac2); - opts = container_of(audio_dev->func.fi, struct f_uac2_opts, func_inst); - p_ssize = opts->p_ssize; - c_ssize = opts->c_ssize; - p_srate = opts->p_srate; - c_srate = opts->c_srate; - p_chmask = opts->p_chmask; - c_chmask = opts->c_chmask; - uac2->p_residue = 0; - - runtime->hw = uac2_pcm_hardware; - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - spin_lock_init(&uac2->p_prm.lock); - runtime->hw.rate_min = p_srate; - switch (p_ssize) { - case 3: - runtime->hw.formats = SNDRV_PCM_FMTBIT_S24_3LE; - break; - case 4: - runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE; - break; - default: - runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; - break; - } - runtime->hw.channels_min = num_channels(p_chmask); - runtime->hw.period_bytes_min = 2 * uac2->p_prm.max_psize - / runtime->hw.periods_min; - } else { - spin_lock_init(&uac2->c_prm.lock); - runtime->hw.rate_min = c_srate; - switch (c_ssize) { - case 3: - runtime->hw.formats = SNDRV_PCM_FMTBIT_S24_3LE; - break; - case 4: - runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE; - break; - default: - runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; - break; - } - runtime->hw.channels_min = num_channels(c_chmask); - runtime->hw.period_bytes_min = 2 * uac2->c_prm.max_psize - / runtime->hw.periods_min; - } - - runtime->hw.rate_max = runtime->hw.rate_min; - runtime->hw.channels_max = runtime->hw.channels_min; - - snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); - - return 0; -} - -/* ALSA cries without these function pointers */ -static int uac2_pcm_null(struct snd_pcm_substream *substream) -{ - return 0; -} - -static struct snd_pcm_ops uac2_pcm_ops = { - .open = uac2_pcm_open, - .close = uac2_pcm_null, - .ioctl = snd_pcm_lib_ioctl, - .hw_params = uac2_pcm_hw_params, - .hw_free = uac2_pcm_hw_free, - .trigger = uac2_pcm_trigger, - .pointer = uac2_pcm_pointer, - .prepare = uac2_pcm_null, -}; - -static int snd_uac2_probe(struct audio_dev *audio_dev) -{ - struct snd_uac2_chip *uac2 = &audio_dev->uac2; - struct snd_card *card; - struct snd_pcm *pcm; - struct f_uac2_opts *opts; - int err; - int p_chmask, c_chmask; - - opts = container_of(audio_dev->func.fi, struct f_uac2_opts, func_inst); - p_chmask = opts->p_chmask; - c_chmask = opts->c_chmask; - - /* Choose any slot, with no id */ - err = snd_card_new(&audio_dev->gadget->dev, - -1, NULL, THIS_MODULE, 0, &card); - if (err < 0) - return err; - - uac2->card = card; - - /* - * Create first PCM device - * Create a substream only for non-zero channel streams - */ - err = snd_pcm_new(uac2->card, "UAC2 PCM", 0, - p_chmask ? 1 : 0, c_chmask ? 1 : 0, &pcm); - if (err < 0) - goto snd_fail; - - strcpy(pcm->name, "UAC2 PCM"); - pcm->private_data = uac2; - - uac2->pcm = pcm; - - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &uac2_pcm_ops); - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &uac2_pcm_ops); - - strcpy(card->driver, "UAC2_Gadget"); - strcpy(card->shortname, "UAC2_Gadget"); - sprintf(card->longname, "UAC2_Gadget %i", card->dev->id); - - snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, - snd_dma_continuous_data(GFP_KERNEL), 0, BUFF_SIZE_MAX); - - err = snd_card_register(card); - - if (!err) - return 0; - -snd_fail: - snd_card_free(card); - - uac2->pcm = NULL; - uac2->card = NULL; - - return err; -} - -static int snd_uac2_remove(struct audio_dev *audio_dev) -{ - struct snd_card *card = audio_dev->uac2.card; - - if (card) - return snd_card_free(card); - - return 0; -} - - /* --------- USB Function Interface ------------- */ enum { @@ -886,32 +451,6 @@ struct cntrl_range_lay3 { __u32 dRES; } __packed; -static inline void -free_ep(struct uac2_rtd_params *prm, struct usb_ep *ep) -{ - struct snd_uac2_chip *uac2 = prm->uac2; - struct audio_dev *agdev = uac2_to_agdev(uac2); - struct f_uac2_opts *uac2_opts = agdev_to_uac2_opts(agdev); - int i; - - if (!prm->ep_enabled) - return; - - prm->ep_enabled = false; - - for (i = 0; i < uac2_opts->req_number; i++) { - if (prm->ureq[i].req) { - usb_ep_dequeue(ep, prm->ureq[i].req); - usb_ep_free_request(ep, prm->ureq[i].req); - prm->ureq[i].req = NULL; - } - } - - if (usb_ep_disable(ep)) - dev_err(uac2->card->dev, - "%s:%d Error!\n", __func__, __LINE__); -} - static void set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts, struct usb_endpoint_descriptor *ep_desc, unsigned int factor, bool is_playback) @@ -938,12 +477,11 @@ static void set_ep_max_packet_size(const struct f_uac2_opts *uac2_opts, static int afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) { - struct audio_dev *agdev = func_to_agdev(fn); - struct snd_uac2_chip *uac2 = &agdev->uac2; + struct f_uac2 *uac2 = func_to_uac2(fn); + struct g_audio *agdev = func_to_g_audio(fn); struct usb_composite_dev *cdev = cfg->cdev; struct usb_gadget *gadget = cdev->gadget; struct device *dev = &gadget->dev; - struct uac2_rtd_params *prm; struct f_uac2_opts *uac2_opts; struct usb_string *us; int ret; @@ -990,8 +528,8 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) return ret; } std_ac_if_desc.bInterfaceNumber = ret; - agdev->ac_intf = ret; - agdev->ac_alt = 0; + uac2->ac_intf = ret; + uac2->ac_alt = 0; ret = usb_interface_id(cfg, fn); if (ret < 0) { @@ -1000,8 +538,8 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) } std_as_out_if0_desc.bInterfaceNumber = ret; std_as_out_if1_desc.bInterfaceNumber = ret; - agdev->as_out_intf = ret; - agdev->as_out_alt = 0; + uac2->as_out_intf = ret; + uac2->as_out_alt = 0; ret = usb_interface_id(cfg, fn); if (ret < 0) { @@ -1010,8 +548,8 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) } std_as_in_if0_desc.bInterfaceNumber = ret; std_as_in_if1_desc.bInterfaceNumber = ret; - agdev->as_in_intf = ret; - agdev->as_in_alt = 0; + uac2->as_in_intf = ret; + uac2->as_in_alt = 0; /* Calculate wMaxPacketSize according to audio bandwidth */ set_ep_max_packet_size(uac2_opts, &fs_epin_desc, 1000, true); @@ -1031,8 +569,10 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) return ret; } - uac2->p_prm.uac2 = uac2; - uac2->c_prm.uac2 = uac2; + agdev->in_ep_maxpsize = max(fs_epin_desc.wMaxPacketSize, + hs_epin_desc.wMaxPacketSize); + agdev->out_ep_maxpsize = max(fs_epout_desc.wMaxPacketSize, + hs_epout_desc.wMaxPacketSize); hs_epout_desc.bEndpointAddress = fs_epout_desc.bEndpointAddress; hs_epin_desc.bEndpointAddress = fs_epin_desc.bEndpointAddress; @@ -1044,46 +584,18 @@ afunc_bind(struct usb_configuration *cfg, struct usb_function *fn) agdev->gadget = gadget; - prm = &agdev->uac2.c_prm; - prm->max_psize = hs_epout_desc.wMaxPacketSize; - prm->ureq = kcalloc(uac2_opts->req_number, sizeof(struct uac2_req), - GFP_KERNEL); - if (!prm->ureq) { - ret = -ENOMEM; - goto err_free_descs; - } - prm->rbuf = kcalloc(uac2_opts->req_number, prm->max_psize, GFP_KERNEL); - if (!prm->rbuf) { - prm->max_psize = 0; - ret = -ENOMEM; - goto err_free_descs; - } - - prm = &agdev->uac2.p_prm; - prm->max_psize = hs_epin_desc.wMaxPacketSize; - prm->ureq = kcalloc(uac2_opts->req_number, sizeof(struct uac2_req), - GFP_KERNEL); - if (!prm->ureq) { - ret = -ENOMEM; - goto err_free_descs; - } - prm->rbuf = kcalloc(uac2_opts->req_number, prm->max_psize, GFP_KERNEL); - if (!prm->rbuf) { - prm->max_psize = 0; - ret = -ENOMEM; - goto err_no_memory; - } - - ret = snd_uac2_probe(agdev); + agdev->params.p_chmask = uac2_opts->p_chmask; + agdev->params.p_srate = uac2_opts->p_srate; + agdev->params.p_ssize = uac2_opts->p_ssize; + agdev->params.c_chmask = uac2_opts->c_chmask; + agdev->params.c_srate = uac2_opts->c_srate; + agdev->params.c_ssize = uac2_opts->c_ssize; + agdev->params.req_number = uac2_opts->req_number; + ret = g_audio_setup(agdev, "UAC2 PCM", "UAC2_Gadget"); if (ret) - goto err_no_memory; + goto err_free_descs; return 0; -err_no_memory: - kfree(agdev->uac2.p_prm.ureq); - kfree(agdev->uac2.c_prm.ureq); - kfree(agdev->uac2.p_prm.rbuf); - kfree(agdev->uac2.c_prm.rbuf); err_free_descs: usb_free_all_descriptors(fn); agdev->gadget = NULL; @@ -1094,15 +606,10 @@ static int afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt) { struct usb_composite_dev *cdev = fn->config->cdev; - struct audio_dev *agdev = func_to_agdev(fn); - struct f_uac2_opts *opts = agdev_to_uac2_opts(agdev); - struct snd_uac2_chip *uac2 = &agdev->uac2; + struct f_uac2 *uac2 = func_to_uac2(fn); struct usb_gadget *gadget = cdev->gadget; struct device *dev = &gadget->dev; - struct usb_request *req; - struct usb_ep *ep; - struct uac2_rtd_params *prm; - int req_len, i; + int ret = 0; /* No i/f has more than 2 alt settings */ if (alt > 1) { @@ -1110,7 +617,7 @@ afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt) return -EINVAL; } - if (intf == agdev->ac_intf) { + if (intf == uac2->ac_intf) { /* Control I/f has only 1 AltSetting - 0 */ if (alt) { dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); @@ -1119,92 +626,40 @@ afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt) return 0; } - if (intf == agdev->as_out_intf) { - ep = agdev->out_ep; - prm = &uac2->c_prm; - config_ep_by_speed(gadget, fn, ep); - agdev->as_out_alt = alt; - req_len = prm->max_psize; - } else if (intf == agdev->as_in_intf) { - unsigned int factor, rate; - struct usb_endpoint_descriptor *ep_desc; - - ep = agdev->in_ep; - prm = &uac2->p_prm; - config_ep_by_speed(gadget, fn, ep); - agdev->as_in_alt = alt; - - /* pre-calculate the playback endpoint's interval */ - if (gadget->speed == USB_SPEED_FULL) { - ep_desc = &fs_epin_desc; - factor = 1000; - } else { - ep_desc = &hs_epin_desc; - factor = 8000; - } - - /* pre-compute some values for iso_complete() */ - uac2->p_framesize = opts->p_ssize * - num_channels(opts->p_chmask); - rate = opts->p_srate * uac2->p_framesize; - uac2->p_interval = factor / (1 << (ep_desc->bInterval - 1)); - uac2->p_pktsize = min_t(unsigned int, rate / uac2->p_interval, - prm->max_psize); + if (intf == uac2->as_out_intf) { + uac2->as_out_alt = alt; - if (uac2->p_pktsize < prm->max_psize) - uac2->p_pktsize_residue = rate % uac2->p_interval; + if (alt) + ret = u_audio_start_capture(&uac2->g_audio); else - uac2->p_pktsize_residue = 0; + u_audio_stop_capture(&uac2->g_audio); + } else if (intf == uac2->as_in_intf) { + uac2->as_in_alt = alt; - req_len = uac2->p_pktsize; - uac2->p_residue = 0; + if (alt) + ret = u_audio_start_playback(&uac2->g_audio); + else + u_audio_stop_playback(&uac2->g_audio); } else { dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); return -EINVAL; } - if (alt == 0) { - free_ep(prm, ep); - return 0; - } - - prm->ep_enabled = true; - usb_ep_enable(ep); - - for (i = 0; i < opts->req_number; i++) { - if (!prm->ureq[i].req) { - req = usb_ep_alloc_request(ep, GFP_ATOMIC); - if (req == NULL) - return -ENOMEM; - - prm->ureq[i].req = req; - prm->ureq[i].pp = prm; - - req->zero = 0; - req->context = &prm->ureq[i]; - req->length = req_len; - req->complete = agdev_iso_complete; - req->buf = prm->rbuf + i * prm->max_psize; - } - - if (usb_ep_queue(ep, prm->ureq[i].req, GFP_ATOMIC)) - dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); - } - - return 0; + return ret; } static int afunc_get_alt(struct usb_function *fn, unsigned intf) { - struct audio_dev *agdev = func_to_agdev(fn); - - if (intf == agdev->ac_intf) - return agdev->ac_alt; - else if (intf == agdev->as_out_intf) - return agdev->as_out_alt; - else if (intf == agdev->as_in_intf) - return agdev->as_in_alt; + struct f_uac2 *uac2 = func_to_uac2(fn); + struct g_audio *agdev = func_to_g_audio(fn); + + if (intf == uac2->ac_intf) + return uac2->ac_alt; + else if (intf == uac2->as_out_intf) + return uac2->as_out_alt; + else if (intf == uac2->as_in_intf) + return uac2->as_in_alt; else dev_err(&agdev->gadget->dev, "%s:%d Invalid Interface %d!\n", @@ -1216,21 +671,19 @@ afunc_get_alt(struct usb_function *fn, unsigned intf) static void afunc_disable(struct usb_function *fn) { - struct audio_dev *agdev = func_to_agdev(fn); - struct snd_uac2_chip *uac2 = &agdev->uac2; - - free_ep(&uac2->p_prm, agdev->in_ep); - agdev->as_in_alt = 0; + struct f_uac2 *uac2 = func_to_uac2(fn); - free_ep(&uac2->c_prm, agdev->out_ep); - agdev->as_out_alt = 0; + uac2->as_in_alt = 0; + uac2->as_out_alt = 0; + u_audio_stop_capture(&uac2->g_audio); + u_audio_stop_playback(&uac2->g_audio); } static int in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr) { struct usb_request *req = fn->config->cdev->req; - struct audio_dev *agdev = func_to_agdev(fn); + struct g_audio *agdev = func_to_g_audio(fn); struct f_uac2_opts *opts; u16 w_length = le16_to_cpu(cr->wLength); u16 w_index = le16_to_cpu(cr->wIndex); @@ -1240,7 +693,7 @@ in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr) int value = -EOPNOTSUPP; int p_srate, c_srate; - opts = agdev_to_uac2_opts(agdev); + opts = g_audio_to_uac2_opts(agdev); p_srate = opts->p_srate; c_srate = opts->c_srate; @@ -1271,7 +724,7 @@ static int in_rq_range(struct usb_function *fn, const struct usb_ctrlrequest *cr) { struct usb_request *req = fn->config->cdev->req; - struct audio_dev *agdev = func_to_agdev(fn); + struct g_audio *agdev = func_to_g_audio(fn); struct f_uac2_opts *opts; u16 w_length = le16_to_cpu(cr->wLength); u16 w_index = le16_to_cpu(cr->wIndex); @@ -1282,7 +735,7 @@ in_rq_range(struct usb_function *fn, const struct usb_ctrlrequest *cr) int value = -EOPNOTSUPP; int p_srate, c_srate; - opts = agdev_to_uac2_opts(agdev); + opts = g_audio_to_uac2_opts(agdev); p_srate = opts->p_srate; c_srate = opts->c_srate; @@ -1336,11 +789,12 @@ out_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr) static int setup_rq_inf(struct usb_function *fn, const struct usb_ctrlrequest *cr) { - struct audio_dev *agdev = func_to_agdev(fn); + struct f_uac2 *uac2 = func_to_uac2(fn); + struct g_audio *agdev = func_to_g_audio(fn); u16 w_index = le16_to_cpu(cr->wIndex); u8 intf = w_index & 0xff; - if (intf != agdev->ac_intf) { + if (intf != uac2->ac_intf) { dev_err(&agdev->gadget->dev, "%s:%d Error!\n", __func__, __LINE__); return -EOPNOTSUPP; @@ -1358,7 +812,7 @@ static int afunc_setup(struct usb_function *fn, const struct usb_ctrlrequest *cr) { struct usb_composite_dev *cdev = fn->config->cdev; - struct audio_dev *agdev = func_to_agdev(fn); + struct g_audio *agdev = func_to_g_audio(fn); struct usb_request *req = cdev->req; u16 w_length = le16_to_cpu(cr->wLength); int value = -EOPNOTSUPP; @@ -1504,10 +958,10 @@ static struct usb_function_instance *afunc_alloc_inst(void) static void afunc_free(struct usb_function *f) { - struct audio_dev *agdev; + struct g_audio *agdev; struct f_uac2_opts *opts; - agdev = func_to_agdev(f); + agdev = func_to_g_audio(f); opts = container_of(f->fi, struct f_uac2_opts, func_inst); kfree(agdev); mutex_lock(&opts->lock); @@ -1517,17 +971,9 @@ static void afunc_free(struct usb_function *f) static void afunc_unbind(struct usb_configuration *c, struct usb_function *f) { - struct audio_dev *agdev = func_to_agdev(f); - struct uac2_rtd_params *prm; - - snd_uac2_remove(agdev); - - prm = &agdev->uac2.p_prm; - kfree(prm->rbuf); + struct g_audio *agdev = func_to_g_audio(f); - prm = &agdev->uac2.c_prm; - kfree(prm->rbuf); - kfree(prm->ureq); + g_audio_cleanup(agdev); usb_free_all_descriptors(f); agdev->gadget = NULL; @@ -1535,11 +981,11 @@ static void afunc_unbind(struct usb_configuration *c, struct usb_function *f) static struct usb_function *afunc_alloc(struct usb_function_instance *fi) { - struct audio_dev *agdev; + struct f_uac2 *uac2; struct f_uac2_opts *opts; - agdev = kzalloc(sizeof(*agdev), GFP_KERNEL); - if (agdev == NULL) + uac2 = kzalloc(sizeof(*uac2), GFP_KERNEL); + if (uac2 == NULL) return ERR_PTR(-ENOMEM); opts = container_of(fi, struct f_uac2_opts, func_inst); @@ -1547,16 +993,16 @@ static struct usb_function *afunc_alloc(struct usb_function_instance *fi) ++opts->refcnt; mutex_unlock(&opts->lock); - agdev->func.name = "uac2_func"; - agdev->func.bind = afunc_bind; - agdev->func.unbind = afunc_unbind; - agdev->func.set_alt = afunc_set_alt; - agdev->func.get_alt = afunc_get_alt; - agdev->func.disable = afunc_disable; - agdev->func.setup = afunc_setup; - agdev->func.free_func = afunc_free; + uac2->g_audio.func.name = "uac2_func"; + uac2->g_audio.func.bind = afunc_bind; + uac2->g_audio.func.unbind = afunc_unbind; + uac2->g_audio.func.set_alt = afunc_set_alt; + uac2->g_audio.func.get_alt = afunc_get_alt; + uac2->g_audio.func.disable = afunc_disable; + uac2->g_audio.func.setup = afunc_setup; + uac2->g_audio.func.free_func = afunc_free; - return &agdev->func; + return &uac2->g_audio.func; } DECLARE_USB_FUNCTION_INIT(uac2, afunc_alloc_inst, afunc_alloc); diff --git a/drivers/usb/gadget/function/u_audio.c b/drivers/usb/gadget/function/u_audio.c new file mode 100644 index 000000000000..5dd73b9e5172 --- /dev/null +++ b/drivers/usb/gadget/function/u_audio.c @@ -0,0 +1,662 @@ +/* + * u_audio.c -- interface to USB gadget "ALSA sound card" utilities + * + * Copyright (C) 2016 + * Author: Ruslan Bilovol + * + * Sound card implementation was cut-and-pasted with changes + * from f_uac2.c and has: + * Copyright (C) 2011 + * Yadwinder Singh (yadi.brar01@gmail.com) + * Jaswinder Singh (jaswinder.singh@linaro.org) + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include + +#include "u_audio.h" + +#define BUFF_SIZE_MAX (PAGE_SIZE * 16) +#define PRD_SIZE_MAX PAGE_SIZE +#define MIN_PERIODS 4 + +struct uac_req { + struct uac_rtd_params *pp; /* parent param */ + struct usb_request *req; +}; + +/* Runtime data params for one stream */ +struct uac_rtd_params { + struct snd_uac_chip *uac; /* parent chip */ + bool ep_enabled; /* if the ep is enabled */ + /* Size of the ring buffer */ + size_t dma_bytes; + unsigned char *dma_area; + + struct snd_pcm_substream *ss; + + /* Ring buffer */ + ssize_t hw_ptr; + + void *rbuf; + + size_t period_size; + + unsigned max_psize; /* MaxPacketSize of endpoint */ + struct uac_req *ureq; + + spinlock_t lock; +}; + +struct snd_uac_chip { + struct g_audio *audio_dev; + + struct uac_rtd_params p_prm; + struct uac_rtd_params c_prm; + + struct snd_card *card; + struct snd_pcm *pcm; + + /* timekeeping for the playback endpoint */ + unsigned int p_interval; + unsigned int p_residue; + + /* pre-calculated values for playback iso completion */ + unsigned int p_pktsize; + unsigned int p_pktsize_residue; + unsigned int p_framesize; +}; + +static struct snd_pcm_hardware uac_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER + | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID + | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .periods_max = BUFF_SIZE_MAX / PRD_SIZE_MAX, + .buffer_bytes_max = BUFF_SIZE_MAX, + .period_bytes_max = PRD_SIZE_MAX, + .periods_min = MIN_PERIODS, +}; + +static void u_audio_iso_complete(struct usb_ep *ep, struct usb_request *req) +{ + unsigned pending; + unsigned long flags; + unsigned int hw_ptr; + bool update_alsa = false; + int status = req->status; + struct uac_req *ur = req->context; + struct snd_pcm_substream *substream; + struct uac_rtd_params *prm = ur->pp; + struct snd_uac_chip *uac = prm->uac; + + /* i/f shutting down */ + if (!prm->ep_enabled || req->status == -ESHUTDOWN) + return; + + /* + * We can't really do much about bad xfers. + * Afterall, the ISOCH xfers could fail legitimately. + */ + if (status) + pr_debug("%s: iso_complete status(%d) %d/%d\n", + __func__, status, req->actual, req->length); + + substream = prm->ss; + + /* Do nothing if ALSA isn't active */ + if (!substream) + goto exit; + + spin_lock_irqsave(&prm->lock, flags); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* + * For each IN packet, take the quotient of the current data + * rate and the endpoint's interval as the base packet size. + * If there is a residue from this division, add it to the + * residue accumulator. + */ + req->length = uac->p_pktsize; + uac->p_residue += uac->p_pktsize_residue; + + /* + * Whenever there are more bytes in the accumulator than we + * need to add one more sample frame, increase this packet's + * size and decrease the accumulator. + */ + if (uac->p_residue / uac->p_interval >= uac->p_framesize) { + req->length += uac->p_framesize; + uac->p_residue -= uac->p_framesize * + uac->p_interval; + } + + req->actual = req->length; + } + + pending = prm->hw_ptr % prm->period_size; + pending += req->actual; + if (pending >= prm->period_size) + update_alsa = true; + + hw_ptr = prm->hw_ptr; + prm->hw_ptr = (prm->hw_ptr + req->actual) % prm->dma_bytes; + + spin_unlock_irqrestore(&prm->lock, flags); + + /* Pack USB load in ALSA ring buffer */ + pending = prm->dma_bytes - hw_ptr; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (unlikely(pending < req->actual)) { + memcpy(req->buf, prm->dma_area + hw_ptr, pending); + memcpy(req->buf + pending, prm->dma_area, + req->actual - pending); + } else { + memcpy(req->buf, prm->dma_area + hw_ptr, req->actual); + } + } else { + if (unlikely(pending < req->actual)) { + memcpy(prm->dma_area + hw_ptr, req->buf, pending); + memcpy(prm->dma_area, req->buf + pending, + req->actual - pending); + } else { + memcpy(prm->dma_area + hw_ptr, req->buf, req->actual); + } + } + +exit: + if (usb_ep_queue(ep, req, GFP_ATOMIC)) + dev_err(uac->card->dev, "%d Error!\n", __LINE__); + + if (update_alsa) + snd_pcm_period_elapsed(substream); +} + +static int uac_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_uac_chip *uac = snd_pcm_substream_chip(substream); + struct uac_rtd_params *prm; + struct g_audio *audio_dev; + struct uac_params *params; + unsigned long flags; + int err = 0; + + audio_dev = uac->audio_dev; + params = &audio_dev->params; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + prm = &uac->p_prm; + else + prm = &uac->c_prm; + + spin_lock_irqsave(&prm->lock, flags); + + /* Reset */ + prm->hw_ptr = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + prm->ss = substream; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + prm->ss = NULL; + break; + default: + err = -EINVAL; + } + + spin_unlock_irqrestore(&prm->lock, flags); + + /* Clear buffer after Play stops */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && !prm->ss) + memset(prm->rbuf, 0, prm->max_psize * params->req_number); + + return err; +} + +static snd_pcm_uframes_t uac_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_uac_chip *uac = snd_pcm_substream_chip(substream); + struct uac_rtd_params *prm; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + prm = &uac->p_prm; + else + prm = &uac->c_prm; + + return bytes_to_frames(substream->runtime, prm->hw_ptr); +} + +static int uac_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_uac_chip *uac = snd_pcm_substream_chip(substream); + struct uac_rtd_params *prm; + int err; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + prm = &uac->p_prm; + else + prm = &uac->c_prm; + + err = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); + if (err >= 0) { + prm->dma_bytes = substream->runtime->dma_bytes; + prm->dma_area = substream->runtime->dma_area; + prm->period_size = params_period_bytes(hw_params); + } + + return err; +} + +static int uac_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_uac_chip *uac = snd_pcm_substream_chip(substream); + struct uac_rtd_params *prm; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + prm = &uac->p_prm; + else + prm = &uac->c_prm; + + prm->dma_area = NULL; + prm->dma_bytes = 0; + prm->period_size = 0; + + return snd_pcm_lib_free_pages(substream); +} + +static int uac_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_uac_chip *uac = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct g_audio *audio_dev; + struct uac_params *params; + int p_ssize, c_ssize; + int p_srate, c_srate; + int p_chmask, c_chmask; + + audio_dev = uac->audio_dev; + params = &audio_dev->params; + p_ssize = params->p_ssize; + c_ssize = params->c_ssize; + p_srate = params->p_srate; + c_srate = params->c_srate; + p_chmask = params->p_chmask; + c_chmask = params->c_chmask; + uac->p_residue = 0; + + runtime->hw = uac_pcm_hardware; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + spin_lock_init(&uac->p_prm.lock); + runtime->hw.rate_min = p_srate; + switch (p_ssize) { + case 3: + runtime->hw.formats = SNDRV_PCM_FMTBIT_S24_3LE; + break; + case 4: + runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE; + break; + default: + runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; + break; + } + runtime->hw.channels_min = num_channels(p_chmask); + runtime->hw.period_bytes_min = 2 * uac->p_prm.max_psize + / runtime->hw.periods_min; + } else { + spin_lock_init(&uac->c_prm.lock); + runtime->hw.rate_min = c_srate; + switch (c_ssize) { + case 3: + runtime->hw.formats = SNDRV_PCM_FMTBIT_S24_3LE; + break; + case 4: + runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE; + break; + default: + runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; + break; + } + runtime->hw.channels_min = num_channels(c_chmask); + runtime->hw.period_bytes_min = 2 * uac->c_prm.max_psize + / runtime->hw.periods_min; + } + + runtime->hw.rate_max = runtime->hw.rate_min; + runtime->hw.channels_max = runtime->hw.channels_min; + + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + + return 0; +} + +/* ALSA cries without these function pointers */ +static int uac_pcm_null(struct snd_pcm_substream *substream) +{ + return 0; +} + +static struct snd_pcm_ops uac_pcm_ops = { + .open = uac_pcm_open, + .close = uac_pcm_null, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = uac_pcm_hw_params, + .hw_free = uac_pcm_hw_free, + .trigger = uac_pcm_trigger, + .pointer = uac_pcm_pointer, + .prepare = uac_pcm_null, +}; + +static inline void free_ep(struct uac_rtd_params *prm, struct usb_ep *ep) +{ + struct snd_uac_chip *uac = prm->uac; + struct g_audio *audio_dev; + struct uac_params *params; + int i; + + if (!prm->ep_enabled) + return; + + prm->ep_enabled = false; + + audio_dev = uac->audio_dev; + params = &audio_dev->params; + + for (i = 0; i < params->req_number; i++) { + if (prm->ureq[i].req) { + usb_ep_dequeue(ep, prm->ureq[i].req); + usb_ep_free_request(ep, prm->ureq[i].req); + prm->ureq[i].req = NULL; + } + } + + if (usb_ep_disable(ep)) + dev_err(uac->card->dev, "%s:%d Error!\n", __func__, __LINE__); +} + + +int u_audio_start_capture(struct g_audio *audio_dev) +{ + struct snd_uac_chip *uac = audio_dev->uac; + struct usb_gadget *gadget = audio_dev->gadget; + struct device *dev = &gadget->dev; + struct usb_request *req; + struct usb_ep *ep; + struct uac_rtd_params *prm; + struct uac_params *params = &audio_dev->params; + int req_len, i; + + ep = audio_dev->out_ep; + prm = &uac->c_prm; + config_ep_by_speed(gadget, &audio_dev->func, ep); + req_len = prm->max_psize; + + prm->ep_enabled = true; + usb_ep_enable(ep); + + for (i = 0; i < params->req_number; i++) { + if (!prm->ureq[i].req) { + req = usb_ep_alloc_request(ep, GFP_ATOMIC); + if (req == NULL) + return -ENOMEM; + + prm->ureq[i].req = req; + prm->ureq[i].pp = prm; + + req->zero = 0; + req->context = &prm->ureq[i]; + req->length = req_len; + req->complete = u_audio_iso_complete; + req->buf = prm->rbuf + i * prm->max_psize; + } + + if (usb_ep_queue(ep, prm->ureq[i].req, GFP_ATOMIC)) + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + } + + return 0; +} +EXPORT_SYMBOL_GPL(u_audio_start_capture); + +void u_audio_stop_capture(struct g_audio *audio_dev) +{ + struct snd_uac_chip *uac = audio_dev->uac; + + free_ep(&uac->c_prm, audio_dev->out_ep); +} +EXPORT_SYMBOL_GPL(u_audio_stop_capture); + +int u_audio_start_playback(struct g_audio *audio_dev) +{ + struct snd_uac_chip *uac = audio_dev->uac; + struct usb_gadget *gadget = audio_dev->gadget; + struct device *dev = &gadget->dev; + struct usb_request *req; + struct usb_ep *ep; + struct uac_rtd_params *prm; + struct uac_params *params = &audio_dev->params; + unsigned int factor, rate; + const struct usb_endpoint_descriptor *ep_desc; + int req_len, i; + + ep = audio_dev->in_ep; + prm = &uac->p_prm; + config_ep_by_speed(gadget, &audio_dev->func, ep); + + ep_desc = ep->desc; + + /* pre-calculate the playback endpoint's interval */ + if (gadget->speed == USB_SPEED_FULL) + factor = 1000; + else + factor = 8000; + + /* pre-compute some values for iso_complete() */ + uac->p_framesize = params->p_ssize * + num_channels(params->p_chmask); + rate = params->p_srate * uac->p_framesize; + uac->p_interval = factor / (1 << (ep_desc->bInterval - 1)); + uac->p_pktsize = min_t(unsigned int, rate / uac->p_interval, + prm->max_psize); + + if (uac->p_pktsize < prm->max_psize) + uac->p_pktsize_residue = rate % uac->p_interval; + else + uac->p_pktsize_residue = 0; + + req_len = uac->p_pktsize; + uac->p_residue = 0; + + prm->ep_enabled = true; + usb_ep_enable(ep); + + for (i = 0; i < params->req_number; i++) { + if (!prm->ureq[i].req) { + req = usb_ep_alloc_request(ep, GFP_ATOMIC); + if (req == NULL) + return -ENOMEM; + + prm->ureq[i].req = req; + prm->ureq[i].pp = prm; + + req->zero = 0; + req->context = &prm->ureq[i]; + req->length = req_len; + req->complete = u_audio_iso_complete; + req->buf = prm->rbuf + i * prm->max_psize; + } + + if (usb_ep_queue(ep, prm->ureq[i].req, GFP_ATOMIC)) + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + } + + return 0; +} +EXPORT_SYMBOL_GPL(u_audio_start_playback); + +void u_audio_stop_playback(struct g_audio *audio_dev) +{ + struct snd_uac_chip *uac = audio_dev->uac; + + free_ep(&uac->p_prm, audio_dev->in_ep); +} +EXPORT_SYMBOL_GPL(u_audio_stop_playback); + +int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, + const char *card_name) +{ + struct snd_uac_chip *uac; + struct snd_card *card; + struct snd_pcm *pcm; + struct uac_params *params; + int p_chmask, c_chmask; + int err; + + if (!g_audio) + return -EINVAL; + + uac = kzalloc(sizeof(*uac), GFP_KERNEL); + if (!uac) + return -ENOMEM; + g_audio->uac = uac; + uac->audio_dev = g_audio; + + params = &g_audio->params; + p_chmask = params->p_chmask; + c_chmask = params->c_chmask; + + if (c_chmask) { + struct uac_rtd_params *prm = &uac->c_prm; + + uac->c_prm.uac = uac; + prm->max_psize = g_audio->out_ep_maxpsize; + + prm->ureq = kcalloc(params->req_number, sizeof(struct uac_req), + GFP_KERNEL); + if (!prm->ureq) { + err = -ENOMEM; + goto fail; + } + + prm->rbuf = kcalloc(params->req_number, prm->max_psize, + GFP_KERNEL); + if (!prm->rbuf) { + prm->max_psize = 0; + err = -ENOMEM; + goto fail; + } + } + + if (p_chmask) { + struct uac_rtd_params *prm = &uac->p_prm; + + uac->p_prm.uac = uac; + prm->max_psize = g_audio->in_ep_maxpsize; + + prm->ureq = kcalloc(params->req_number, sizeof(struct uac_req), + GFP_KERNEL); + if (!prm->ureq) { + err = -ENOMEM; + goto fail; + } + + prm->rbuf = kcalloc(params->req_number, prm->max_psize, + GFP_KERNEL); + if (!prm->rbuf) { + prm->max_psize = 0; + err = -ENOMEM; + goto fail; + } + } + + /* Choose any slot, with no id */ + err = snd_card_new(&g_audio->gadget->dev, + -1, NULL, THIS_MODULE, 0, &card); + if (err < 0) + goto fail; + + uac->card = card; + + /* + * Create first PCM device + * Create a substream only for non-zero channel streams + */ + err = snd_pcm_new(uac->card, pcm_name, 0, + p_chmask ? 1 : 0, c_chmask ? 1 : 0, &pcm); + if (err < 0) + goto snd_fail; + + strcpy(pcm->name, pcm_name); + pcm->private_data = uac; + uac->pcm = pcm; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &uac_pcm_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &uac_pcm_ops); + + strcpy(card->driver, card_name); + strcpy(card->shortname, card_name); + sprintf(card->longname, "%s %i", card_name, card->dev->id); + + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), 0, BUFF_SIZE_MAX); + + err = snd_card_register(card); + + if (!err) + return 0; + +snd_fail: + snd_card_free(card); +fail: + kfree(uac->p_prm.ureq); + kfree(uac->c_prm.ureq); + kfree(uac->p_prm.rbuf); + kfree(uac->c_prm.rbuf); + kfree(uac); + + return err; +} +EXPORT_SYMBOL_GPL(g_audio_setup); + +void g_audio_cleanup(struct g_audio *g_audio) +{ + struct snd_uac_chip *uac; + struct snd_card *card; + + if (!g_audio || !g_audio->uac) + return; + + uac = g_audio->uac; + card = uac->card; + if (card) + snd_card_free(card); + + kfree(uac->p_prm.ureq); + kfree(uac->c_prm.ureq); + kfree(uac->p_prm.rbuf); + kfree(uac->c_prm.rbuf); + kfree(uac); +} +EXPORT_SYMBOL_GPL(g_audio_cleanup); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("USB gadget \"ALSA sound card\" utilities"); +MODULE_AUTHOR("Ruslan Bilovol"); diff --git a/drivers/usb/gadget/function/u_audio.h b/drivers/usb/gadget/function/u_audio.h new file mode 100644 index 000000000000..07e13784cbb8 --- /dev/null +++ b/drivers/usb/gadget/function/u_audio.h @@ -0,0 +1,95 @@ +/* + * u_audio.h -- interface to USB gadget "ALSA sound card" utilities + * + * Copyright (C) 2016 + * Author: Ruslan Bilovol + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + */ + +#ifndef __U_AUDIO_H +#define __U_AUDIO_H + +#include + +struct uac_params { + /* playback */ + int p_chmask; /* channel mask */ + int p_srate; /* rate in Hz */ + int p_ssize; /* sample size */ + + /* capture */ + int c_chmask; /* channel mask */ + int c_srate; /* rate in Hz */ + int c_ssize; /* sample size */ + + int req_number; /* number of preallocated requests */ +}; + +struct g_audio { + struct usb_function func; + struct usb_gadget *gadget; + + struct usb_ep *in_ep; + struct usb_ep *out_ep; + + /* Max packet size for all in_ep possible speeds */ + unsigned int in_ep_maxpsize; + /* Max packet size for all out_ep possible speeds */ + unsigned int out_ep_maxpsize; + + /* The ALSA Sound Card it represents on the USB-Client side */ + struct snd_uac_chip *uac; + + struct uac_params params; +}; + +static inline struct g_audio *func_to_g_audio(struct usb_function *f) +{ + return container_of(f, struct g_audio, func); +} + +static inline uint num_channels(uint chanmask) +{ + uint num = 0; + + while (chanmask) { + num += (chanmask & 1); + chanmask >>= 1; + } + + return num; +} + +/* + * g_audio_setup - initialize one virtual ALSA sound card + * @g_audio: struct with filled params, in_ep_maxpsize, out_ep_maxpsize + * @pcm_name: the id string for a PCM instance of this sound card + * @card_name: name of this soundcard + * + * This sets up the single virtual ALSA sound card that may be exported by a + * gadget driver using this framework. + * + * Context: may sleep + * + * Returns zero on success, or a negative error on failure. + */ +int g_audio_setup(struct g_audio *g_audio, const char *pcm_name, + const char *card_name); +void g_audio_cleanup(struct g_audio *g_audio); + +int u_audio_start_capture(struct g_audio *g_audio); +void u_audio_stop_capture(struct g_audio *g_audio); +int u_audio_start_playback(struct g_audio *g_audio); +void u_audio_stop_playback(struct g_audio *g_audio); + +#endif /* __U_AUDIO_H */ diff --git a/drivers/usb/gadget/legacy/Kconfig b/drivers/usb/gadget/legacy/Kconfig index 0b36878eb5fd..5344064f5721 100644 --- a/drivers/usb/gadget/legacy/Kconfig +++ b/drivers/usb/gadget/legacy/Kconfig @@ -56,6 +56,7 @@ config USB_AUDIO select SND_PCM select USB_F_UAC1 if GADGET_UAC1 select USB_F_UAC2 if !GADGET_UAC1 + select USB_U_AUDIO if USB_F_UAC2 help This Gadget Audio driver is compatible with USB Audio Class specification 2.0. It implements 1 AudioControl interface, -- cgit v1.2.3 From d355339eecd986648420e05f8c958fbc78dbb382 Mon Sep 17 00:00:00 2001 From: Ruslan Bilovol Date: Sun, 18 Jun 2017 16:23:53 +0300 Subject: usb: gadget: function: make current f_uac1 implementation legacy Before introducing new f_uac1 function (with virtual ALSA card) make current implementation legacy. This includes renaming of existing files, some variables, config options and documentation Signed-off-by: Ruslan Bilovol Signed-off-by: Felipe Balbi --- Documentation/ABI/testing/configfs-usb-gadget-uac1 | 12 - .../ABI/testing/configfs-usb-gadget-uac1_legacy | 12 + Documentation/usb/gadget-testing.txt | 9 +- drivers/usb/gadget/Kconfig | 8 +- drivers/usb/gadget/function/Makefile | 4 +- drivers/usb/gadget/function/f_uac1.c | 1020 ------------------- drivers/usb/gadget/function/f_uac1_legacy.c | 1021 ++++++++++++++++++++ drivers/usb/gadget/function/u_uac1.c | 314 ------ drivers/usb/gadget/function/u_uac1.h | 82 -- drivers/usb/gadget/function/u_uac1_legacy.c | 315 ++++++ drivers/usb/gadget/function/u_uac1_legacy.h | 82 ++ drivers/usb/gadget/legacy/Kconfig | 6 +- drivers/usb/gadget/legacy/audio.c | 26 +- 13 files changed, 1457 insertions(+), 1454 deletions(-) delete mode 100644 Documentation/ABI/testing/configfs-usb-gadget-uac1 create mode 100644 Documentation/ABI/testing/configfs-usb-gadget-uac1_legacy delete mode 100644 drivers/usb/gadget/function/f_uac1.c create mode 100644 drivers/usb/gadget/function/f_uac1_legacy.c delete mode 100644 drivers/usb/gadget/function/u_uac1.c delete mode 100644 drivers/usb/gadget/function/u_uac1.h create mode 100644 drivers/usb/gadget/function/u_uac1_legacy.c create mode 100644 drivers/usb/gadget/function/u_uac1_legacy.h diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uac1 b/Documentation/ABI/testing/configfs-usb-gadget-uac1 deleted file mode 100644 index 8ba9a123316e..000000000000 --- a/Documentation/ABI/testing/configfs-usb-gadget-uac1 +++ /dev/null @@ -1,12 +0,0 @@ -What: /config/usb-gadget/gadget/functions/uac1.name -Date: Sep 2014 -KernelVersion: 3.18 -Description: - The attributes: - - audio_buf_size - audio buffer size - fn_cap - capture pcm device file name - fn_cntl - control device file name - fn_play - playback pcm device file name - req_buf_size - ISO OUT endpoint request buffer size - req_count - ISO OUT endpoint request count diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uac1_legacy b/Documentation/ABI/testing/configfs-usb-gadget-uac1_legacy new file mode 100644 index 000000000000..b2eaefd9bc49 --- /dev/null +++ b/Documentation/ABI/testing/configfs-usb-gadget-uac1_legacy @@ -0,0 +1,12 @@ +What: /config/usb-gadget/gadget/functions/uac1_legacy.name +Date: Sep 2014 +KernelVersion: 3.18 +Description: + The attributes: + + audio_buf_size - audio buffer size + fn_cap - capture pcm device file name + fn_cntl - control device file name + fn_play - playback pcm device file name + req_buf_size - ISO OUT endpoint request buffer size + req_count - ISO OUT endpoint request count diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt index fb0cc4df1765..ce51d6e4e7d0 100644 --- a/Documentation/usb/gadget-testing.txt +++ b/Documentation/usb/gadget-testing.txt @@ -16,7 +16,7 @@ provided by gadgets. 13. RNDIS function 14. SERIAL function 15. SOURCESINK function -16. UAC1 function +16. UAC1 function (legacy implementation) 17. UAC2 function 18. UVC function 19. PRINTER function @@ -589,15 +589,16 @@ device: run the gadget host: test-usb (tools/usb/testusb.c) -16. UAC1 function +16. UAC1 function (legacy implementation) ================= -The function is provided by usb_f_uac1.ko module. +The function is provided by usb_f_uac1_legacy.ko module. Function-specific configfs interface ------------------------------------ -The function name to use when creating the function directory is "uac1". +The function name to use when creating the function directory +is "uac1_legacy". The uac1 function provides these attributes in its function directory: audio_buf_size - audio buffer size diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 3b0ffd6e515b..01c2e2b9ab1d 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -191,7 +191,7 @@ config USB_F_MASS_STORAGE config USB_F_FS tristate -config USB_F_UAC1 +config USB_F_UAC1_LEGACY tristate config USB_F_UAC2 @@ -365,13 +365,13 @@ config USB_CONFIGFS_F_FS implemented in kernel space (for instance Ethernet, serial or mass storage) and other are implemented in user space. -config USB_CONFIGFS_F_UAC1 - bool "Audio Class 1.0" +config USB_CONFIGFS_F_UAC1_LEGACY + bool "Audio Class 1.0 (legacy implementation)" depends on USB_CONFIGFS depends on SND select USB_LIBCOMPOSITE select SND_PCM - select USB_F_UAC1 + select USB_F_UAC1_LEGACY help This Audio function implements 1 AudioControl interface, 1 AudioStreaming Interface each for USB-OUT and USB-IN. diff --git a/drivers/usb/gadget/function/Makefile b/drivers/usb/gadget/function/Makefile index b29f2ae23357..50ee517faf74 100644 --- a/drivers/usb/gadget/function/Makefile +++ b/drivers/usb/gadget/function/Makefile @@ -33,8 +33,8 @@ obj-$(CONFIG_USB_F_MASS_STORAGE)+= usb_f_mass_storage.o usb_f_fs-y := f_fs.o obj-$(CONFIG_USB_F_FS) += usb_f_fs.o obj-$(CONFIG_USB_U_AUDIO) += u_audio.o -usb_f_uac1-y := f_uac1.o u_uac1.o -obj-$(CONFIG_USB_F_UAC1) += usb_f_uac1.o +usb_f_uac1_legacy-y := f_uac1_legacy.o u_uac1_legacy.o +obj-$(CONFIG_USB_F_UAC1_LEGACY) += usb_f_uac1_legacy.o usb_f_uac2-y := f_uac2.o obj-$(CONFIG_USB_F_UAC2) += usb_f_uac2.o usb_f_uvc-y := f_uvc.o uvc_queue.o uvc_v4l2.o uvc_video.o uvc_configfs.o diff --git a/drivers/usb/gadget/function/f_uac1.c b/drivers/usb/gadget/function/f_uac1.c deleted file mode 100644 index 5dfc94b8e69a..000000000000 --- a/drivers/usb/gadget/function/f_uac1.c +++ /dev/null @@ -1,1020 +0,0 @@ -/* - * f_audio.c -- USB Audio class function driver - * - * Copyright (C) 2008 Bryan Wu - * Copyright (C) 2008 Analog Devices, Inc - * - * Enter bugs at http://blackfin.uclinux.org/ - * - * Licensed under the GPL-2 or later. - */ - -#include -#include -#include -#include -#include - -#include "u_uac1.h" - -static int generic_set_cmd(struct usb_audio_control *con, u8 cmd, int value); -static int generic_get_cmd(struct usb_audio_control *con, u8 cmd); - -/* - * DESCRIPTORS ... most are static, but strings and full - * configuration descriptors are built on demand. - */ - -/* - * We have two interfaces- AudioControl and AudioStreaming - * TODO: only supcard playback currently - */ -#define F_AUDIO_AC_INTERFACE 0 -#define F_AUDIO_AS_INTERFACE 1 -#define F_AUDIO_NUM_INTERFACES 1 - -/* B.3.1 Standard AC Interface Descriptor */ -static struct usb_interface_descriptor ac_interface_desc = { - .bLength = USB_DT_INTERFACE_SIZE, - .bDescriptorType = USB_DT_INTERFACE, - .bNumEndpoints = 0, - .bInterfaceClass = USB_CLASS_AUDIO, - .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, -}; - -/* - * The number of AudioStreaming and MIDIStreaming interfaces - * in the Audio Interface Collection - */ -DECLARE_UAC_AC_HEADER_DESCRIPTOR(1); - -#define UAC_DT_AC_HEADER_LENGTH UAC_DT_AC_HEADER_SIZE(F_AUDIO_NUM_INTERFACES) -/* 1 input terminal, 1 output terminal and 1 feature unit */ -#define UAC_DT_TOTAL_LENGTH (UAC_DT_AC_HEADER_LENGTH + UAC_DT_INPUT_TERMINAL_SIZE \ - + UAC_DT_OUTPUT_TERMINAL_SIZE + UAC_DT_FEATURE_UNIT_SIZE(0)) -/* B.3.2 Class-Specific AC Interface Descriptor */ -static struct uac1_ac_header_descriptor_1 ac_header_desc = { - .bLength = UAC_DT_AC_HEADER_LENGTH, - .bDescriptorType = USB_DT_CS_INTERFACE, - .bDescriptorSubtype = UAC_HEADER, - .bcdADC = __constant_cpu_to_le16(0x0100), - .wTotalLength = __constant_cpu_to_le16(UAC_DT_TOTAL_LENGTH), - .bInCollection = F_AUDIO_NUM_INTERFACES, - .baInterfaceNr = { - /* Interface number of the first AudioStream interface */ - [0] = 1, - } -}; - -#define INPUT_TERMINAL_ID 1 -static struct uac_input_terminal_descriptor input_terminal_desc = { - .bLength = UAC_DT_INPUT_TERMINAL_SIZE, - .bDescriptorType = USB_DT_CS_INTERFACE, - .bDescriptorSubtype = UAC_INPUT_TERMINAL, - .bTerminalID = INPUT_TERMINAL_ID, - .wTerminalType = UAC_TERMINAL_STREAMING, - .bAssocTerminal = 0, - .wChannelConfig = 0x3, -}; - -DECLARE_UAC_FEATURE_UNIT_DESCRIPTOR(0); - -#define FEATURE_UNIT_ID 2 -static struct uac_feature_unit_descriptor_0 feature_unit_desc = { - .bLength = UAC_DT_FEATURE_UNIT_SIZE(0), - .bDescriptorType = USB_DT_CS_INTERFACE, - .bDescriptorSubtype = UAC_FEATURE_UNIT, - .bUnitID = FEATURE_UNIT_ID, - .bSourceID = INPUT_TERMINAL_ID, - .bControlSize = 2, - .bmaControls[0] = (UAC_FU_MUTE | UAC_FU_VOLUME), -}; - -static struct usb_audio_control mute_control = { - .list = LIST_HEAD_INIT(mute_control.list), - .name = "Mute Control", - .type = UAC_FU_MUTE, - /* Todo: add real Mute control code */ - .set = generic_set_cmd, - .get = generic_get_cmd, -}; - -static struct usb_audio_control volume_control = { - .list = LIST_HEAD_INIT(volume_control.list), - .name = "Volume Control", - .type = UAC_FU_VOLUME, - /* Todo: add real Volume control code */ - .set = generic_set_cmd, - .get = generic_get_cmd, -}; - -static struct usb_audio_control_selector feature_unit = { - .list = LIST_HEAD_INIT(feature_unit.list), - .id = FEATURE_UNIT_ID, - .name = "Mute & Volume Control", - .type = UAC_FEATURE_UNIT, - .desc = (struct usb_descriptor_header *)&feature_unit_desc, -}; - -#define OUTPUT_TERMINAL_ID 3 -static struct uac1_output_terminal_descriptor output_terminal_desc = { - .bLength = UAC_DT_OUTPUT_TERMINAL_SIZE, - .bDescriptorType = USB_DT_CS_INTERFACE, - .bDescriptorSubtype = UAC_OUTPUT_TERMINAL, - .bTerminalID = OUTPUT_TERMINAL_ID, - .wTerminalType = UAC_OUTPUT_TERMINAL_SPEAKER, - .bAssocTerminal = FEATURE_UNIT_ID, - .bSourceID = FEATURE_UNIT_ID, -}; - -/* B.4.1 Standard AS Interface Descriptor */ -static struct usb_interface_descriptor as_interface_alt_0_desc = { - .bLength = USB_DT_INTERFACE_SIZE, - .bDescriptorType = USB_DT_INTERFACE, - .bAlternateSetting = 0, - .bNumEndpoints = 0, - .bInterfaceClass = USB_CLASS_AUDIO, - .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING, -}; - -static struct usb_interface_descriptor as_interface_alt_1_desc = { - .bLength = USB_DT_INTERFACE_SIZE, - .bDescriptorType = USB_DT_INTERFACE, - .bAlternateSetting = 1, - .bNumEndpoints = 1, - .bInterfaceClass = USB_CLASS_AUDIO, - .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING, -}; - -/* B.4.2 Class-Specific AS Interface Descriptor */ -static struct uac1_as_header_descriptor as_header_desc = { - .bLength = UAC_DT_AS_HEADER_SIZE, - .bDescriptorType = USB_DT_CS_INTERFACE, - .bDescriptorSubtype = UAC_AS_GENERAL, - .bTerminalLink = INPUT_TERMINAL_ID, - .bDelay = 1, - .wFormatTag = UAC_FORMAT_TYPE_I_PCM, -}; - -DECLARE_UAC_FORMAT_TYPE_I_DISCRETE_DESC(1); - -static struct uac_format_type_i_discrete_descriptor_1 as_type_i_desc = { - .bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1), - .bDescriptorType = USB_DT_CS_INTERFACE, - .bDescriptorSubtype = UAC_FORMAT_TYPE, - .bFormatType = UAC_FORMAT_TYPE_I, - .bSubframeSize = 2, - .bBitResolution = 16, - .bSamFreqType = 1, -}; - -/* Standard ISO OUT Endpoint Descriptor */ -static struct usb_endpoint_descriptor as_out_ep_desc = { - .bLength = USB_DT_ENDPOINT_AUDIO_SIZE, - .bDescriptorType = USB_DT_ENDPOINT, - .bEndpointAddress = USB_DIR_OUT, - .bmAttributes = USB_ENDPOINT_SYNC_ADAPTIVE - | USB_ENDPOINT_XFER_ISOC, - .wMaxPacketSize = cpu_to_le16(UAC1_OUT_EP_MAX_PACKET_SIZE), - .bInterval = 4, -}; - -/* Class-specific AS ISO OUT Endpoint Descriptor */ -static struct uac_iso_endpoint_descriptor as_iso_out_desc = { - .bLength = UAC_ISO_ENDPOINT_DESC_SIZE, - .bDescriptorType = USB_DT_CS_ENDPOINT, - .bDescriptorSubtype = UAC_EP_GENERAL, - .bmAttributes = 1, - .bLockDelayUnits = 1, - .wLockDelay = __constant_cpu_to_le16(1), -}; - -static struct usb_descriptor_header *f_audio_desc[] = { - (struct usb_descriptor_header *)&ac_interface_desc, - (struct usb_descriptor_header *)&ac_header_desc, - - (struct usb_descriptor_header *)&input_terminal_desc, - (struct usb_descriptor_header *)&output_terminal_desc, - (struct usb_descriptor_header *)&feature_unit_desc, - - (struct usb_descriptor_header *)&as_interface_alt_0_desc, - (struct usb_descriptor_header *)&as_interface_alt_1_desc, - (struct usb_descriptor_header *)&as_header_desc, - - (struct usb_descriptor_header *)&as_type_i_desc, - - (struct usb_descriptor_header *)&as_out_ep_desc, - (struct usb_descriptor_header *)&as_iso_out_desc, - NULL, -}; - -enum { - STR_AC_IF, - STR_INPUT_TERMINAL, - STR_INPUT_TERMINAL_CH_NAMES, - STR_FEAT_DESC_0, - STR_OUTPUT_TERMINAL, - STR_AS_IF_ALT0, - STR_AS_IF_ALT1, -}; - -static struct usb_string strings_uac1[] = { - [STR_AC_IF].s = "AC Interface", - [STR_INPUT_TERMINAL].s = "Input terminal", - [STR_INPUT_TERMINAL_CH_NAMES].s = "Channels", - [STR_FEAT_DESC_0].s = "Volume control & mute", - [STR_OUTPUT_TERMINAL].s = "Output terminal", - [STR_AS_IF_ALT0].s = "AS Interface", - [STR_AS_IF_ALT1].s = "AS Interface", - { }, -}; - -static struct usb_gadget_strings str_uac1 = { - .language = 0x0409, /* en-us */ - .strings = strings_uac1, -}; - -static struct usb_gadget_strings *uac1_strings[] = { - &str_uac1, - NULL, -}; - -/* - * This function is an ALSA sound card following USB Audio Class Spec 1.0. - */ - -/*-------------------------------------------------------------------------*/ -struct f_audio_buf { - u8 *buf; - int actual; - struct list_head list; -}; - -static struct f_audio_buf *f_audio_buffer_alloc(int buf_size) -{ - struct f_audio_buf *copy_buf; - - copy_buf = kzalloc(sizeof *copy_buf, GFP_ATOMIC); - if (!copy_buf) - return ERR_PTR(-ENOMEM); - - copy_buf->buf = kzalloc(buf_size, GFP_ATOMIC); - if (!copy_buf->buf) { - kfree(copy_buf); - return ERR_PTR(-ENOMEM); - } - - return copy_buf; -} - -static void f_audio_buffer_free(struct f_audio_buf *audio_buf) -{ - kfree(audio_buf->buf); - kfree(audio_buf); -} -/*-------------------------------------------------------------------------*/ - -struct f_audio { - struct gaudio card; - - u8 ac_intf, ac_alt; - u8 as_intf, as_alt; - - /* endpoints handle full and/or high speeds */ - struct usb_ep *out_ep; - - spinlock_t lock; - struct f_audio_buf *copy_buf; - struct work_struct playback_work; - struct list_head play_queue; - - /* Control Set command */ - struct list_head cs; - u8 set_cmd; - struct usb_audio_control *set_con; -}; - -static inline struct f_audio *func_to_audio(struct usb_function *f) -{ - return container_of(f, struct f_audio, card.func); -} - -/*-------------------------------------------------------------------------*/ - -static void f_audio_playback_work(struct work_struct *data) -{ - struct f_audio *audio = container_of(data, struct f_audio, - playback_work); - struct f_audio_buf *play_buf; - - spin_lock_irq(&audio->lock); - if (list_empty(&audio->play_queue)) { - spin_unlock_irq(&audio->lock); - return; - } - play_buf = list_first_entry(&audio->play_queue, - struct f_audio_buf, list); - list_del(&play_buf->list); - spin_unlock_irq(&audio->lock); - - u_audio_playback(&audio->card, play_buf->buf, play_buf->actual); - f_audio_buffer_free(play_buf); -} - -static int f_audio_out_ep_complete(struct usb_ep *ep, struct usb_request *req) -{ - struct f_audio *audio = req->context; - struct usb_composite_dev *cdev = audio->card.func.config->cdev; - struct f_audio_buf *copy_buf = audio->copy_buf; - struct f_uac1_opts *opts; - int audio_buf_size; - int err; - - opts = container_of(audio->card.func.fi, struct f_uac1_opts, - func_inst); - audio_buf_size = opts->audio_buf_size; - - if (!copy_buf) - return -EINVAL; - - /* Copy buffer is full, add it to the play_queue */ - if (audio_buf_size - copy_buf->actual < req->actual) { - list_add_tail(©_buf->list, &audio->play_queue); - schedule_work(&audio->playback_work); - copy_buf = f_audio_buffer_alloc(audio_buf_size); - if (IS_ERR(copy_buf)) - return -ENOMEM; - } - - memcpy(copy_buf->buf + copy_buf->actual, req->buf, req->actual); - copy_buf->actual += req->actual; - audio->copy_buf = copy_buf; - - err = usb_ep_queue(ep, req, GFP_ATOMIC); - if (err) - ERROR(cdev, "%s queue req: %d\n", ep->name, err); - - return 0; - -} - -static void f_audio_complete(struct usb_ep *ep, struct usb_request *req) -{ - struct f_audio *audio = req->context; - int status = req->status; - u32 data = 0; - struct usb_ep *out_ep = audio->out_ep; - - switch (status) { - - case 0: /* normal completion? */ - if (ep == out_ep) - f_audio_out_ep_complete(ep, req); - else if (audio->set_con) { - memcpy(&data, req->buf, req->length); - audio->set_con->set(audio->set_con, audio->set_cmd, - le16_to_cpu(data)); - audio->set_con = NULL; - } - break; - default: - break; - } -} - -static int audio_set_intf_req(struct usb_function *f, - const struct usb_ctrlrequest *ctrl) -{ - struct f_audio *audio = func_to_audio(f); - struct usb_composite_dev *cdev = f->config->cdev; - struct usb_request *req = cdev->req; - u8 id = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF); - u16 len = le16_to_cpu(ctrl->wLength); - u16 w_value = le16_to_cpu(ctrl->wValue); - u8 con_sel = (w_value >> 8) & 0xFF; - u8 cmd = (ctrl->bRequest & 0x0F); - struct usb_audio_control_selector *cs; - struct usb_audio_control *con; - - DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, entity %d\n", - ctrl->bRequest, w_value, len, id); - - list_for_each_entry(cs, &audio->cs, list) { - if (cs->id == id) { - list_for_each_entry(con, &cs->control, list) { - if (con->type == con_sel) { - audio->set_con = con; - break; - } - } - break; - } - } - - audio->set_cmd = cmd; - req->context = audio; - req->complete = f_audio_complete; - - return len; -} - -static int audio_get_intf_req(struct usb_function *f, - const struct usb_ctrlrequest *ctrl) -{ - struct f_audio *audio = func_to_audio(f); - struct usb_composite_dev *cdev = f->config->cdev; - struct usb_request *req = cdev->req; - int value = -EOPNOTSUPP; - u8 id = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF); - u16 len = le16_to_cpu(ctrl->wLength); - u16 w_value = le16_to_cpu(ctrl->wValue); - u8 con_sel = (w_value >> 8) & 0xFF; - u8 cmd = (ctrl->bRequest & 0x0F); - struct usb_audio_control_selector *cs; - struct usb_audio_control *con; - - DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, entity %d\n", - ctrl->bRequest, w_value, len, id); - - list_for_each_entry(cs, &audio->cs, list) { - if (cs->id == id) { - list_for_each_entry(con, &cs->control, list) { - if (con->type == con_sel && con->get) { - value = con->get(con, cmd); - break; - } - } - break; - } - } - - req->context = audio; - req->complete = f_audio_complete; - len = min_t(size_t, sizeof(value), len); - memcpy(req->buf, &value, len); - - return len; -} - -static int audio_set_endpoint_req(struct usb_function *f, - const struct usb_ctrlrequest *ctrl) -{ - struct usb_composite_dev *cdev = f->config->cdev; - int value = -EOPNOTSUPP; - u16 ep = le16_to_cpu(ctrl->wIndex); - u16 len = le16_to_cpu(ctrl->wLength); - u16 w_value = le16_to_cpu(ctrl->wValue); - - DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n", - ctrl->bRequest, w_value, len, ep); - - switch (ctrl->bRequest) { - case UAC_SET_CUR: - value = len; - break; - - case UAC_SET_MIN: - break; - - case UAC_SET_MAX: - break; - - case UAC_SET_RES: - break; - - case UAC_SET_MEM: - break; - - default: - break; - } - - return value; -} - -static int audio_get_endpoint_req(struct usb_function *f, - const struct usb_ctrlrequest *ctrl) -{ - struct usb_composite_dev *cdev = f->config->cdev; - int value = -EOPNOTSUPP; - u8 ep = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF); - u16 len = le16_to_cpu(ctrl->wLength); - u16 w_value = le16_to_cpu(ctrl->wValue); - - DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n", - ctrl->bRequest, w_value, len, ep); - - switch (ctrl->bRequest) { - case UAC_GET_CUR: - case UAC_GET_MIN: - case UAC_GET_MAX: - case UAC_GET_RES: - value = len; - break; - case UAC_GET_MEM: - break; - default: - break; - } - - return value; -} - -static int -f_audio_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) -{ - struct usb_composite_dev *cdev = f->config->cdev; - struct usb_request *req = cdev->req; - int value = -EOPNOTSUPP; - u16 w_index = le16_to_cpu(ctrl->wIndex); - u16 w_value = le16_to_cpu(ctrl->wValue); - u16 w_length = le16_to_cpu(ctrl->wLength); - - /* composite driver infrastructure handles everything; interface - * activation uses set_alt(). - */ - switch (ctrl->bRequestType) { - case USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE: - value = audio_set_intf_req(f, ctrl); - break; - - case USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE: - value = audio_get_intf_req(f, ctrl); - break; - - case USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT: - value = audio_set_endpoint_req(f, ctrl); - break; - - case USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT: - value = audio_get_endpoint_req(f, ctrl); - break; - - default: - ERROR(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n", - ctrl->bRequestType, ctrl->bRequest, - w_value, w_index, w_length); - } - - /* respond with data transfer or status phase? */ - if (value >= 0) { - DBG(cdev, "audio req%02x.%02x v%04x i%04x l%d\n", - ctrl->bRequestType, ctrl->bRequest, - w_value, w_index, w_length); - req->zero = 0; - req->length = value; - value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); - if (value < 0) - ERROR(cdev, "audio response on err %d\n", value); - } - - /* device either stalls (value < 0) or reports success */ - return value; -} - -static int f_audio_set_alt(struct usb_function *f, unsigned intf, unsigned alt) -{ - struct f_audio *audio = func_to_audio(f); - struct usb_composite_dev *cdev = f->config->cdev; - struct usb_ep *out_ep = audio->out_ep; - struct usb_request *req; - struct f_uac1_opts *opts; - int req_buf_size, req_count, audio_buf_size; - int i = 0, err = 0; - - DBG(cdev, "intf %d, alt %d\n", intf, alt); - - opts = container_of(f->fi, struct f_uac1_opts, func_inst); - req_buf_size = opts->req_buf_size; - req_count = opts->req_count; - audio_buf_size = opts->audio_buf_size; - - /* No i/f has more than 2 alt settings */ - if (alt > 1) { - ERROR(cdev, "%s:%d Error!\n", __func__, __LINE__); - return -EINVAL; - } - - if (intf == audio->ac_intf) { - /* Control I/f has only 1 AltSetting - 0 */ - if (alt) { - ERROR(cdev, "%s:%d Error!\n", __func__, __LINE__); - return -EINVAL; - } - return 0; - } else if (intf == audio->as_intf) { - if (alt == 1) { - err = config_ep_by_speed(cdev->gadget, f, out_ep); - if (err) - return err; - - usb_ep_enable(out_ep); - audio->copy_buf = f_audio_buffer_alloc(audio_buf_size); - if (IS_ERR(audio->copy_buf)) - return -ENOMEM; - - /* - * allocate a bunch of read buffers - * and queue them all at once. - */ - for (i = 0; i < req_count && err == 0; i++) { - req = usb_ep_alloc_request(out_ep, GFP_ATOMIC); - if (req) { - req->buf = kzalloc(req_buf_size, - GFP_ATOMIC); - if (req->buf) { - req->length = req_buf_size; - req->context = audio; - req->complete = - f_audio_complete; - err = usb_ep_queue(out_ep, - req, GFP_ATOMIC); - if (err) - ERROR(cdev, - "%s queue req: %d\n", - out_ep->name, err); - } else - err = -ENOMEM; - } else - err = -ENOMEM; - } - - } else { - struct f_audio_buf *copy_buf = audio->copy_buf; - if (copy_buf) { - list_add_tail(©_buf->list, - &audio->play_queue); - schedule_work(&audio->playback_work); - } - } - audio->as_alt = alt; - } - - return err; -} - -static int f_audio_get_alt(struct usb_function *f, unsigned intf) -{ - struct f_audio *audio = func_to_audio(f); - struct usb_composite_dev *cdev = f->config->cdev; - - if (intf == audio->ac_intf) - return audio->ac_alt; - else if (intf == audio->as_intf) - return audio->as_alt; - else - ERROR(cdev, "%s:%d Invalid Interface %d!\n", - __func__, __LINE__, intf); - - return -EINVAL; -} - -static void f_audio_disable(struct usb_function *f) -{ - return; -} - -/*-------------------------------------------------------------------------*/ - -static void f_audio_build_desc(struct f_audio *audio) -{ - struct gaudio *card = &audio->card; - u8 *sam_freq; - int rate; - - /* Set channel numbers */ - input_terminal_desc.bNrChannels = u_audio_get_playback_channels(card); - as_type_i_desc.bNrChannels = u_audio_get_playback_channels(card); - - /* Set sample rates */ - rate = u_audio_get_playback_rate(card); - sam_freq = as_type_i_desc.tSamFreq[0]; - memcpy(sam_freq, &rate, 3); - - /* Todo: Set Sample bits and other parameters */ - - return; -} - -/* audio function driver setup/binding */ -static int -f_audio_bind(struct usb_configuration *c, struct usb_function *f) -{ - struct usb_composite_dev *cdev = c->cdev; - struct f_audio *audio = func_to_audio(f); - struct usb_string *us; - int status; - struct usb_ep *ep = NULL; - struct f_uac1_opts *audio_opts; - - audio_opts = container_of(f->fi, struct f_uac1_opts, func_inst); - audio->card.gadget = c->cdev->gadget; - /* set up ASLA audio devices */ - if (!audio_opts->bound) { - status = gaudio_setup(&audio->card); - if (status < 0) - return status; - audio_opts->bound = true; - } - us = usb_gstrings_attach(cdev, uac1_strings, ARRAY_SIZE(strings_uac1)); - if (IS_ERR(us)) - return PTR_ERR(us); - ac_interface_desc.iInterface = us[STR_AC_IF].id; - input_terminal_desc.iTerminal = us[STR_INPUT_TERMINAL].id; - input_terminal_desc.iChannelNames = us[STR_INPUT_TERMINAL_CH_NAMES].id; - feature_unit_desc.iFeature = us[STR_FEAT_DESC_0].id; - output_terminal_desc.iTerminal = us[STR_OUTPUT_TERMINAL].id; - as_interface_alt_0_desc.iInterface = us[STR_AS_IF_ALT0].id; - as_interface_alt_1_desc.iInterface = us[STR_AS_IF_ALT1].id; - - - f_audio_build_desc(audio); - - /* allocate instance-specific interface IDs, and patch descriptors */ - status = usb_interface_id(c, f); - if (status < 0) - goto fail; - ac_interface_desc.bInterfaceNumber = status; - audio->ac_intf = status; - audio->ac_alt = 0; - - status = usb_interface_id(c, f); - if (status < 0) - goto fail; - as_interface_alt_0_desc.bInterfaceNumber = status; - as_interface_alt_1_desc.bInterfaceNumber = status; - audio->as_intf = status; - audio->as_alt = 0; - - status = -ENODEV; - - /* allocate instance-specific endpoints */ - ep = usb_ep_autoconfig(cdev->gadget, &as_out_ep_desc); - if (!ep) - goto fail; - audio->out_ep = ep; - audio->out_ep->desc = &as_out_ep_desc; - - status = -ENOMEM; - - /* copy descriptors, and track endpoint copies */ - status = usb_assign_descriptors(f, f_audio_desc, f_audio_desc, NULL, - NULL); - if (status) - goto fail; - return 0; - -fail: - gaudio_cleanup(&audio->card); - return status; -} - -/*-------------------------------------------------------------------------*/ - -static int generic_set_cmd(struct usb_audio_control *con, u8 cmd, int value) -{ - con->data[cmd] = value; - - return 0; -} - -static int generic_get_cmd(struct usb_audio_control *con, u8 cmd) -{ - return con->data[cmd]; -} - -/* Todo: add more control selecotor dynamically */ -static int control_selector_init(struct f_audio *audio) -{ - INIT_LIST_HEAD(&audio->cs); - list_add(&feature_unit.list, &audio->cs); - - INIT_LIST_HEAD(&feature_unit.control); - list_add(&mute_control.list, &feature_unit.control); - list_add(&volume_control.list, &feature_unit.control); - - volume_control.data[UAC__CUR] = 0xffc0; - volume_control.data[UAC__MIN] = 0xe3a0; - volume_control.data[UAC__MAX] = 0xfff0; - volume_control.data[UAC__RES] = 0x0030; - - return 0; -} - -static inline struct f_uac1_opts *to_f_uac1_opts(struct config_item *item) -{ - return container_of(to_config_group(item), struct f_uac1_opts, - func_inst.group); -} - -static void f_uac1_attr_release(struct config_item *item) -{ - struct f_uac1_opts *opts = to_f_uac1_opts(item); - - usb_put_function_instance(&opts->func_inst); -} - -static struct configfs_item_operations f_uac1_item_ops = { - .release = f_uac1_attr_release, -}; - -#define UAC1_INT_ATTRIBUTE(name) \ -static ssize_t f_uac1_opts_##name##_show(struct config_item *item, \ - char *page) \ -{ \ - struct f_uac1_opts *opts = to_f_uac1_opts(item); \ - int result; \ - \ - mutex_lock(&opts->lock); \ - result = sprintf(page, "%u\n", opts->name); \ - mutex_unlock(&opts->lock); \ - \ - return result; \ -} \ - \ -static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \ - const char *page, size_t len) \ -{ \ - struct f_uac1_opts *opts = to_f_uac1_opts(item); \ - int ret; \ - u32 num; \ - \ - mutex_lock(&opts->lock); \ - if (opts->refcnt) { \ - ret = -EBUSY; \ - goto end; \ - } \ - \ - ret = kstrtou32(page, 0, &num); \ - if (ret) \ - goto end; \ - \ - opts->name = num; \ - ret = len; \ - \ -end: \ - mutex_unlock(&opts->lock); \ - return ret; \ -} \ - \ -CONFIGFS_ATTR(f_uac1_opts_, name) - -UAC1_INT_ATTRIBUTE(req_buf_size); -UAC1_INT_ATTRIBUTE(req_count); -UAC1_INT_ATTRIBUTE(audio_buf_size); - -#define UAC1_STR_ATTRIBUTE(name) \ -static ssize_t f_uac1_opts_##name##_show(struct config_item *item, \ - char *page) \ -{ \ - struct f_uac1_opts *opts = to_f_uac1_opts(item); \ - int result; \ - \ - mutex_lock(&opts->lock); \ - result = sprintf(page, "%s\n", opts->name); \ - mutex_unlock(&opts->lock); \ - \ - return result; \ -} \ - \ -static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \ - const char *page, size_t len) \ -{ \ - struct f_uac1_opts *opts = to_f_uac1_opts(item); \ - int ret = -EBUSY; \ - char *tmp; \ - \ - mutex_lock(&opts->lock); \ - if (opts->refcnt) \ - goto end; \ - \ - tmp = kstrndup(page, len, GFP_KERNEL); \ - if (tmp) { \ - ret = -ENOMEM; \ - goto end; \ - } \ - if (opts->name##_alloc) \ - kfree(opts->name); \ - opts->name##_alloc = true; \ - opts->name = tmp; \ - ret = len; \ - \ -end: \ - mutex_unlock(&opts->lock); \ - return ret; \ -} \ - \ -CONFIGFS_ATTR(f_uac1_opts_, name) - -UAC1_STR_ATTRIBUTE(fn_play); -UAC1_STR_ATTRIBUTE(fn_cap); -UAC1_STR_ATTRIBUTE(fn_cntl); - -static struct configfs_attribute *f_uac1_attrs[] = { - &f_uac1_opts_attr_req_buf_size, - &f_uac1_opts_attr_req_count, - &f_uac1_opts_attr_audio_buf_size, - &f_uac1_opts_attr_fn_play, - &f_uac1_opts_attr_fn_cap, - &f_uac1_opts_attr_fn_cntl, - NULL, -}; - -static struct config_item_type f_uac1_func_type = { - .ct_item_ops = &f_uac1_item_ops, - .ct_attrs = f_uac1_attrs, - .ct_owner = THIS_MODULE, -}; - -static void f_audio_free_inst(struct usb_function_instance *f) -{ - struct f_uac1_opts *opts; - - opts = container_of(f, struct f_uac1_opts, func_inst); - if (opts->fn_play_alloc) - kfree(opts->fn_play); - if (opts->fn_cap_alloc) - kfree(opts->fn_cap); - if (opts->fn_cntl_alloc) - kfree(opts->fn_cntl); - kfree(opts); -} - -static struct usb_function_instance *f_audio_alloc_inst(void) -{ - struct f_uac1_opts *opts; - - opts = kzalloc(sizeof(*opts), GFP_KERNEL); - if (!opts) - return ERR_PTR(-ENOMEM); - - mutex_init(&opts->lock); - opts->func_inst.free_func_inst = f_audio_free_inst; - - config_group_init_type_name(&opts->func_inst.group, "", - &f_uac1_func_type); - - opts->req_buf_size = UAC1_OUT_EP_MAX_PACKET_SIZE; - opts->req_count = UAC1_REQ_COUNT; - opts->audio_buf_size = UAC1_AUDIO_BUF_SIZE; - opts->fn_play = FILE_PCM_PLAYBACK; - opts->fn_cap = FILE_PCM_CAPTURE; - opts->fn_cntl = FILE_CONTROL; - return &opts->func_inst; -} - -static void f_audio_free(struct usb_function *f) -{ - struct f_audio *audio = func_to_audio(f); - struct f_uac1_opts *opts; - - gaudio_cleanup(&audio->card); - opts = container_of(f->fi, struct f_uac1_opts, func_inst); - kfree(audio); - mutex_lock(&opts->lock); - --opts->refcnt; - mutex_unlock(&opts->lock); -} - -static void f_audio_unbind(struct usb_configuration *c, struct usb_function *f) -{ - usb_free_all_descriptors(f); -} - -static struct usb_function *f_audio_alloc(struct usb_function_instance *fi) -{ - struct f_audio *audio; - struct f_uac1_opts *opts; - - /* allocate and initialize one new instance */ - audio = kzalloc(sizeof(*audio), GFP_KERNEL); - if (!audio) - return ERR_PTR(-ENOMEM); - - audio->card.func.name = "g_audio"; - - opts = container_of(fi, struct f_uac1_opts, func_inst); - mutex_lock(&opts->lock); - ++opts->refcnt; - mutex_unlock(&opts->lock); - INIT_LIST_HEAD(&audio->play_queue); - spin_lock_init(&audio->lock); - - audio->card.func.bind = f_audio_bind; - audio->card.func.unbind = f_audio_unbind; - audio->card.func.set_alt = f_audio_set_alt; - audio->card.func.get_alt = f_audio_get_alt; - audio->card.func.setup = f_audio_setup; - audio->card.func.disable = f_audio_disable; - audio->card.func.free_func = f_audio_free; - - control_selector_init(audio); - - INIT_WORK(&audio->playback_work, f_audio_playback_work); - - return &audio->card.func; -} - -DECLARE_USB_FUNCTION_INIT(uac1, f_audio_alloc_inst, f_audio_alloc); -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Bryan Wu"); diff --git a/drivers/usb/gadget/function/f_uac1_legacy.c b/drivers/usb/gadget/function/f_uac1_legacy.c new file mode 100644 index 000000000000..5d229e72912e --- /dev/null +++ b/drivers/usb/gadget/function/f_uac1_legacy.c @@ -0,0 +1,1021 @@ +/* + * f_audio.c -- USB Audio class function driver + * + * Copyright (C) 2008 Bryan Wu + * Copyright (C) 2008 Analog Devices, Inc + * + * Enter bugs at http://blackfin.uclinux.org/ + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include + +#include "u_uac1_legacy.h" + +static int generic_set_cmd(struct usb_audio_control *con, u8 cmd, int value); +static int generic_get_cmd(struct usb_audio_control *con, u8 cmd); + +/* + * DESCRIPTORS ... most are static, but strings and full + * configuration descriptors are built on demand. + */ + +/* + * We have two interfaces- AudioControl and AudioStreaming + * TODO: only supcard playback currently + */ +#define F_AUDIO_AC_INTERFACE 0 +#define F_AUDIO_AS_INTERFACE 1 +#define F_AUDIO_NUM_INTERFACES 1 + +/* B.3.1 Standard AC Interface Descriptor */ +static struct usb_interface_descriptor ac_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, +}; + +/* + * The number of AudioStreaming and MIDIStreaming interfaces + * in the Audio Interface Collection + */ +DECLARE_UAC_AC_HEADER_DESCRIPTOR(1); + +#define UAC_DT_AC_HEADER_LENGTH UAC_DT_AC_HEADER_SIZE(F_AUDIO_NUM_INTERFACES) +/* 1 input terminal, 1 output terminal and 1 feature unit */ +#define UAC_DT_TOTAL_LENGTH (UAC_DT_AC_HEADER_LENGTH + UAC_DT_INPUT_TERMINAL_SIZE \ + + UAC_DT_OUTPUT_TERMINAL_SIZE + UAC_DT_FEATURE_UNIT_SIZE(0)) +/* B.3.2 Class-Specific AC Interface Descriptor */ +static struct uac1_ac_header_descriptor_1 ac_header_desc = { + .bLength = UAC_DT_AC_HEADER_LENGTH, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_HEADER, + .bcdADC = __constant_cpu_to_le16(0x0100), + .wTotalLength = __constant_cpu_to_le16(UAC_DT_TOTAL_LENGTH), + .bInCollection = F_AUDIO_NUM_INTERFACES, + .baInterfaceNr = { + /* Interface number of the first AudioStream interface */ + [0] = 1, + } +}; + +#define INPUT_TERMINAL_ID 1 +static struct uac_input_terminal_descriptor input_terminal_desc = { + .bLength = UAC_DT_INPUT_TERMINAL_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_INPUT_TERMINAL, + .bTerminalID = INPUT_TERMINAL_ID, + .wTerminalType = UAC_TERMINAL_STREAMING, + .bAssocTerminal = 0, + .wChannelConfig = 0x3, +}; + +DECLARE_UAC_FEATURE_UNIT_DESCRIPTOR(0); + +#define FEATURE_UNIT_ID 2 +static struct uac_feature_unit_descriptor_0 feature_unit_desc = { + .bLength = UAC_DT_FEATURE_UNIT_SIZE(0), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_FEATURE_UNIT, + .bUnitID = FEATURE_UNIT_ID, + .bSourceID = INPUT_TERMINAL_ID, + .bControlSize = 2, + .bmaControls[0] = (UAC_FU_MUTE | UAC_FU_VOLUME), +}; + +static struct usb_audio_control mute_control = { + .list = LIST_HEAD_INIT(mute_control.list), + .name = "Mute Control", + .type = UAC_FU_MUTE, + /* Todo: add real Mute control code */ + .set = generic_set_cmd, + .get = generic_get_cmd, +}; + +static struct usb_audio_control volume_control = { + .list = LIST_HEAD_INIT(volume_control.list), + .name = "Volume Control", + .type = UAC_FU_VOLUME, + /* Todo: add real Volume control code */ + .set = generic_set_cmd, + .get = generic_get_cmd, +}; + +static struct usb_audio_control_selector feature_unit = { + .list = LIST_HEAD_INIT(feature_unit.list), + .id = FEATURE_UNIT_ID, + .name = "Mute & Volume Control", + .type = UAC_FEATURE_UNIT, + .desc = (struct usb_descriptor_header *)&feature_unit_desc, +}; + +#define OUTPUT_TERMINAL_ID 3 +static struct uac1_output_terminal_descriptor output_terminal_desc = { + .bLength = UAC_DT_OUTPUT_TERMINAL_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_OUTPUT_TERMINAL, + .bTerminalID = OUTPUT_TERMINAL_ID, + .wTerminalType = UAC_OUTPUT_TERMINAL_SPEAKER, + .bAssocTerminal = FEATURE_UNIT_ID, + .bSourceID = FEATURE_UNIT_ID, +}; + +/* B.4.1 Standard AS Interface Descriptor */ +static struct usb_interface_descriptor as_interface_alt_0_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING, +}; + +static struct usb_interface_descriptor as_interface_alt_1_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bAlternateSetting = 1, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING, +}; + +/* B.4.2 Class-Specific AS Interface Descriptor */ +static struct uac1_as_header_descriptor as_header_desc = { + .bLength = UAC_DT_AS_HEADER_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_AS_GENERAL, + .bTerminalLink = INPUT_TERMINAL_ID, + .bDelay = 1, + .wFormatTag = UAC_FORMAT_TYPE_I_PCM, +}; + +DECLARE_UAC_FORMAT_TYPE_I_DISCRETE_DESC(1); + +static struct uac_format_type_i_discrete_descriptor_1 as_type_i_desc = { + .bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_FORMAT_TYPE, + .bFormatType = UAC_FORMAT_TYPE_I, + .bSubframeSize = 2, + .bBitResolution = 16, + .bSamFreqType = 1, +}; + +/* Standard ISO OUT Endpoint Descriptor */ +static struct usb_endpoint_descriptor as_out_ep_desc = { + .bLength = USB_DT_ENDPOINT_AUDIO_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_SYNC_ADAPTIVE + | USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = cpu_to_le16(UAC1_OUT_EP_MAX_PACKET_SIZE), + .bInterval = 4, +}; + +/* Class-specific AS ISO OUT Endpoint Descriptor */ +static struct uac_iso_endpoint_descriptor as_iso_out_desc = { + .bLength = UAC_ISO_ENDPOINT_DESC_SIZE, + .bDescriptorType = USB_DT_CS_ENDPOINT, + .bDescriptorSubtype = UAC_EP_GENERAL, + .bmAttributes = 1, + .bLockDelayUnits = 1, + .wLockDelay = __constant_cpu_to_le16(1), +}; + +static struct usb_descriptor_header *f_audio_desc[] = { + (struct usb_descriptor_header *)&ac_interface_desc, + (struct usb_descriptor_header *)&ac_header_desc, + + (struct usb_descriptor_header *)&input_terminal_desc, + (struct usb_descriptor_header *)&output_terminal_desc, + (struct usb_descriptor_header *)&feature_unit_desc, + + (struct usb_descriptor_header *)&as_interface_alt_0_desc, + (struct usb_descriptor_header *)&as_interface_alt_1_desc, + (struct usb_descriptor_header *)&as_header_desc, + + (struct usb_descriptor_header *)&as_type_i_desc, + + (struct usb_descriptor_header *)&as_out_ep_desc, + (struct usb_descriptor_header *)&as_iso_out_desc, + NULL, +}; + +enum { + STR_AC_IF, + STR_INPUT_TERMINAL, + STR_INPUT_TERMINAL_CH_NAMES, + STR_FEAT_DESC_0, + STR_OUTPUT_TERMINAL, + STR_AS_IF_ALT0, + STR_AS_IF_ALT1, +}; + +static struct usb_string strings_uac1[] = { + [STR_AC_IF].s = "AC Interface", + [STR_INPUT_TERMINAL].s = "Input terminal", + [STR_INPUT_TERMINAL_CH_NAMES].s = "Channels", + [STR_FEAT_DESC_0].s = "Volume control & mute", + [STR_OUTPUT_TERMINAL].s = "Output terminal", + [STR_AS_IF_ALT0].s = "AS Interface", + [STR_AS_IF_ALT1].s = "AS Interface", + { }, +}; + +static struct usb_gadget_strings str_uac1 = { + .language = 0x0409, /* en-us */ + .strings = strings_uac1, +}; + +static struct usb_gadget_strings *uac1_strings[] = { + &str_uac1, + NULL, +}; + +/* + * This function is an ALSA sound card following USB Audio Class Spec 1.0. + */ + +/*-------------------------------------------------------------------------*/ +struct f_audio_buf { + u8 *buf; + int actual; + struct list_head list; +}; + +static struct f_audio_buf *f_audio_buffer_alloc(int buf_size) +{ + struct f_audio_buf *copy_buf; + + copy_buf = kzalloc(sizeof *copy_buf, GFP_ATOMIC); + if (!copy_buf) + return ERR_PTR(-ENOMEM); + + copy_buf->buf = kzalloc(buf_size, GFP_ATOMIC); + if (!copy_buf->buf) { + kfree(copy_buf); + return ERR_PTR(-ENOMEM); + } + + return copy_buf; +} + +static void f_audio_buffer_free(struct f_audio_buf *audio_buf) +{ + kfree(audio_buf->buf); + kfree(audio_buf); +} +/*-------------------------------------------------------------------------*/ + +struct f_audio { + struct gaudio card; + + u8 ac_intf, ac_alt; + u8 as_intf, as_alt; + + /* endpoints handle full and/or high speeds */ + struct usb_ep *out_ep; + + spinlock_t lock; + struct f_audio_buf *copy_buf; + struct work_struct playback_work; + struct list_head play_queue; + + /* Control Set command */ + struct list_head cs; + u8 set_cmd; + struct usb_audio_control *set_con; +}; + +static inline struct f_audio *func_to_audio(struct usb_function *f) +{ + return container_of(f, struct f_audio, card.func); +} + +/*-------------------------------------------------------------------------*/ + +static void f_audio_playback_work(struct work_struct *data) +{ + struct f_audio *audio = container_of(data, struct f_audio, + playback_work); + struct f_audio_buf *play_buf; + + spin_lock_irq(&audio->lock); + if (list_empty(&audio->play_queue)) { + spin_unlock_irq(&audio->lock); + return; + } + play_buf = list_first_entry(&audio->play_queue, + struct f_audio_buf, list); + list_del(&play_buf->list); + spin_unlock_irq(&audio->lock); + + u_audio_playback(&audio->card, play_buf->buf, play_buf->actual); + f_audio_buffer_free(play_buf); +} + +static int f_audio_out_ep_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_audio *audio = req->context; + struct usb_composite_dev *cdev = audio->card.func.config->cdev; + struct f_audio_buf *copy_buf = audio->copy_buf; + struct f_uac1_legacy_opts *opts; + int audio_buf_size; + int err; + + opts = container_of(audio->card.func.fi, struct f_uac1_legacy_opts, + func_inst); + audio_buf_size = opts->audio_buf_size; + + if (!copy_buf) + return -EINVAL; + + /* Copy buffer is full, add it to the play_queue */ + if (audio_buf_size - copy_buf->actual < req->actual) { + list_add_tail(©_buf->list, &audio->play_queue); + schedule_work(&audio->playback_work); + copy_buf = f_audio_buffer_alloc(audio_buf_size); + if (IS_ERR(copy_buf)) + return -ENOMEM; + } + + memcpy(copy_buf->buf + copy_buf->actual, req->buf, req->actual); + copy_buf->actual += req->actual; + audio->copy_buf = copy_buf; + + err = usb_ep_queue(ep, req, GFP_ATOMIC); + if (err) + ERROR(cdev, "%s queue req: %d\n", ep->name, err); + + return 0; + +} + +static void f_audio_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_audio *audio = req->context; + int status = req->status; + u32 data = 0; + struct usb_ep *out_ep = audio->out_ep; + + switch (status) { + + case 0: /* normal completion? */ + if (ep == out_ep) + f_audio_out_ep_complete(ep, req); + else if (audio->set_con) { + memcpy(&data, req->buf, req->length); + audio->set_con->set(audio->set_con, audio->set_cmd, + le16_to_cpu(data)); + audio->set_con = NULL; + } + break; + default: + break; + } +} + +static int audio_set_intf_req(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct f_audio *audio = func_to_audio(f); + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + u8 id = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF); + u16 len = le16_to_cpu(ctrl->wLength); + u16 w_value = le16_to_cpu(ctrl->wValue); + u8 con_sel = (w_value >> 8) & 0xFF; + u8 cmd = (ctrl->bRequest & 0x0F); + struct usb_audio_control_selector *cs; + struct usb_audio_control *con; + + DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, entity %d\n", + ctrl->bRequest, w_value, len, id); + + list_for_each_entry(cs, &audio->cs, list) { + if (cs->id == id) { + list_for_each_entry(con, &cs->control, list) { + if (con->type == con_sel) { + audio->set_con = con; + break; + } + } + break; + } + } + + audio->set_cmd = cmd; + req->context = audio; + req->complete = f_audio_complete; + + return len; +} + +static int audio_get_intf_req(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct f_audio *audio = func_to_audio(f); + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + int value = -EOPNOTSUPP; + u8 id = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF); + u16 len = le16_to_cpu(ctrl->wLength); + u16 w_value = le16_to_cpu(ctrl->wValue); + u8 con_sel = (w_value >> 8) & 0xFF; + u8 cmd = (ctrl->bRequest & 0x0F); + struct usb_audio_control_selector *cs; + struct usb_audio_control *con; + + DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, entity %d\n", + ctrl->bRequest, w_value, len, id); + + list_for_each_entry(cs, &audio->cs, list) { + if (cs->id == id) { + list_for_each_entry(con, &cs->control, list) { + if (con->type == con_sel && con->get) { + value = con->get(con, cmd); + break; + } + } + break; + } + } + + req->context = audio; + req->complete = f_audio_complete; + len = min_t(size_t, sizeof(value), len); + memcpy(req->buf, &value, len); + + return len; +} + +static int audio_set_endpoint_req(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct usb_composite_dev *cdev = f->config->cdev; + int value = -EOPNOTSUPP; + u16 ep = le16_to_cpu(ctrl->wIndex); + u16 len = le16_to_cpu(ctrl->wLength); + u16 w_value = le16_to_cpu(ctrl->wValue); + + DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n", + ctrl->bRequest, w_value, len, ep); + + switch (ctrl->bRequest) { + case UAC_SET_CUR: + value = len; + break; + + case UAC_SET_MIN: + break; + + case UAC_SET_MAX: + break; + + case UAC_SET_RES: + break; + + case UAC_SET_MEM: + break; + + default: + break; + } + + return value; +} + +static int audio_get_endpoint_req(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct usb_composite_dev *cdev = f->config->cdev; + int value = -EOPNOTSUPP; + u8 ep = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF); + u16 len = le16_to_cpu(ctrl->wLength); + u16 w_value = le16_to_cpu(ctrl->wValue); + + DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n", + ctrl->bRequest, w_value, len, ep); + + switch (ctrl->bRequest) { + case UAC_GET_CUR: + case UAC_GET_MIN: + case UAC_GET_MAX: + case UAC_GET_RES: + value = len; + break; + case UAC_GET_MEM: + break; + default: + break; + } + + return value; +} + +static int +f_audio_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + int value = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + + /* composite driver infrastructure handles everything; interface + * activation uses set_alt(). + */ + switch (ctrl->bRequestType) { + case USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE: + value = audio_set_intf_req(f, ctrl); + break; + + case USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE: + value = audio_get_intf_req(f, ctrl); + break; + + case USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT: + value = audio_set_endpoint_req(f, ctrl); + break; + + case USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT: + value = audio_get_endpoint_req(f, ctrl); + break; + + default: + ERROR(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + } + + /* respond with data transfer or status phase? */ + if (value >= 0) { + DBG(cdev, "audio req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + req->zero = 0; + req->length = value; + value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (value < 0) + ERROR(cdev, "audio response on err %d\n", value); + } + + /* device either stalls (value < 0) or reports success */ + return value; +} + +static int f_audio_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct f_audio *audio = func_to_audio(f); + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_ep *out_ep = audio->out_ep; + struct usb_request *req; + struct f_uac1_legacy_opts *opts; + int req_buf_size, req_count, audio_buf_size; + int i = 0, err = 0; + + DBG(cdev, "intf %d, alt %d\n", intf, alt); + + opts = container_of(f->fi, struct f_uac1_legacy_opts, func_inst); + req_buf_size = opts->req_buf_size; + req_count = opts->req_count; + audio_buf_size = opts->audio_buf_size; + + /* No i/f has more than 2 alt settings */ + if (alt > 1) { + ERROR(cdev, "%s:%d Error!\n", __func__, __LINE__); + return -EINVAL; + } + + if (intf == audio->ac_intf) { + /* Control I/f has only 1 AltSetting - 0 */ + if (alt) { + ERROR(cdev, "%s:%d Error!\n", __func__, __LINE__); + return -EINVAL; + } + return 0; + } else if (intf == audio->as_intf) { + if (alt == 1) { + err = config_ep_by_speed(cdev->gadget, f, out_ep); + if (err) + return err; + + usb_ep_enable(out_ep); + audio->copy_buf = f_audio_buffer_alloc(audio_buf_size); + if (IS_ERR(audio->copy_buf)) + return -ENOMEM; + + /* + * allocate a bunch of read buffers + * and queue them all at once. + */ + for (i = 0; i < req_count && err == 0; i++) { + req = usb_ep_alloc_request(out_ep, GFP_ATOMIC); + if (req) { + req->buf = kzalloc(req_buf_size, + GFP_ATOMIC); + if (req->buf) { + req->length = req_buf_size; + req->context = audio; + req->complete = + f_audio_complete; + err = usb_ep_queue(out_ep, + req, GFP_ATOMIC); + if (err) + ERROR(cdev, + "%s queue req: %d\n", + out_ep->name, err); + } else + err = -ENOMEM; + } else + err = -ENOMEM; + } + + } else { + struct f_audio_buf *copy_buf = audio->copy_buf; + if (copy_buf) { + list_add_tail(©_buf->list, + &audio->play_queue); + schedule_work(&audio->playback_work); + } + } + audio->as_alt = alt; + } + + return err; +} + +static int f_audio_get_alt(struct usb_function *f, unsigned intf) +{ + struct f_audio *audio = func_to_audio(f); + struct usb_composite_dev *cdev = f->config->cdev; + + if (intf == audio->ac_intf) + return audio->ac_alt; + else if (intf == audio->as_intf) + return audio->as_alt; + else + ERROR(cdev, "%s:%d Invalid Interface %d!\n", + __func__, __LINE__, intf); + + return -EINVAL; +} + +static void f_audio_disable(struct usb_function *f) +{ + return; +} + +/*-------------------------------------------------------------------------*/ + +static void f_audio_build_desc(struct f_audio *audio) +{ + struct gaudio *card = &audio->card; + u8 *sam_freq; + int rate; + + /* Set channel numbers */ + input_terminal_desc.bNrChannels = u_audio_get_playback_channels(card); + as_type_i_desc.bNrChannels = u_audio_get_playback_channels(card); + + /* Set sample rates */ + rate = u_audio_get_playback_rate(card); + sam_freq = as_type_i_desc.tSamFreq[0]; + memcpy(sam_freq, &rate, 3); + + /* Todo: Set Sample bits and other parameters */ + + return; +} + +/* audio function driver setup/binding */ +static int +f_audio_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct f_audio *audio = func_to_audio(f); + struct usb_string *us; + int status; + struct usb_ep *ep = NULL; + struct f_uac1_legacy_opts *audio_opts; + + audio_opts = container_of(f->fi, struct f_uac1_legacy_opts, func_inst); + audio->card.gadget = c->cdev->gadget; + /* set up ASLA audio devices */ + if (!audio_opts->bound) { + status = gaudio_setup(&audio->card); + if (status < 0) + return status; + audio_opts->bound = true; + } + us = usb_gstrings_attach(cdev, uac1_strings, ARRAY_SIZE(strings_uac1)); + if (IS_ERR(us)) + return PTR_ERR(us); + ac_interface_desc.iInterface = us[STR_AC_IF].id; + input_terminal_desc.iTerminal = us[STR_INPUT_TERMINAL].id; + input_terminal_desc.iChannelNames = us[STR_INPUT_TERMINAL_CH_NAMES].id; + feature_unit_desc.iFeature = us[STR_FEAT_DESC_0].id; + output_terminal_desc.iTerminal = us[STR_OUTPUT_TERMINAL].id; + as_interface_alt_0_desc.iInterface = us[STR_AS_IF_ALT0].id; + as_interface_alt_1_desc.iInterface = us[STR_AS_IF_ALT1].id; + + + f_audio_build_desc(audio); + + /* allocate instance-specific interface IDs, and patch descriptors */ + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + ac_interface_desc.bInterfaceNumber = status; + audio->ac_intf = status; + audio->ac_alt = 0; + + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + as_interface_alt_0_desc.bInterfaceNumber = status; + as_interface_alt_1_desc.bInterfaceNumber = status; + audio->as_intf = status; + audio->as_alt = 0; + + status = -ENODEV; + + /* allocate instance-specific endpoints */ + ep = usb_ep_autoconfig(cdev->gadget, &as_out_ep_desc); + if (!ep) + goto fail; + audio->out_ep = ep; + audio->out_ep->desc = &as_out_ep_desc; + + status = -ENOMEM; + + /* copy descriptors, and track endpoint copies */ + status = usb_assign_descriptors(f, f_audio_desc, f_audio_desc, NULL, + NULL); + if (status) + goto fail; + return 0; + +fail: + gaudio_cleanup(&audio->card); + return status; +} + +/*-------------------------------------------------------------------------*/ + +static int generic_set_cmd(struct usb_audio_control *con, u8 cmd, int value) +{ + con->data[cmd] = value; + + return 0; +} + +static int generic_get_cmd(struct usb_audio_control *con, u8 cmd) +{ + return con->data[cmd]; +} + +/* Todo: add more control selecotor dynamically */ +static int control_selector_init(struct f_audio *audio) +{ + INIT_LIST_HEAD(&audio->cs); + list_add(&feature_unit.list, &audio->cs); + + INIT_LIST_HEAD(&feature_unit.control); + list_add(&mute_control.list, &feature_unit.control); + list_add(&volume_control.list, &feature_unit.control); + + volume_control.data[UAC__CUR] = 0xffc0; + volume_control.data[UAC__MIN] = 0xe3a0; + volume_control.data[UAC__MAX] = 0xfff0; + volume_control.data[UAC__RES] = 0x0030; + + return 0; +} + +static inline +struct f_uac1_legacy_opts *to_f_uac1_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_uac1_legacy_opts, + func_inst.group); +} + +static void f_uac1_attr_release(struct config_item *item) +{ + struct f_uac1_legacy_opts *opts = to_f_uac1_opts(item); + + usb_put_function_instance(&opts->func_inst); +} + +static struct configfs_item_operations f_uac1_item_ops = { + .release = f_uac1_attr_release, +}; + +#define UAC1_INT_ATTRIBUTE(name) \ +static ssize_t f_uac1_opts_##name##_show(struct config_item *item, \ + char *page) \ +{ \ + struct f_uac1_legacy_opts *opts = to_f_uac1_opts(item); \ + int result; \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%u\n", opts->name); \ + mutex_unlock(&opts->lock); \ + \ + return result; \ +} \ + \ +static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \ + const char *page, size_t len) \ +{ \ + struct f_uac1_legacy_opts *opts = to_f_uac1_opts(item); \ + int ret; \ + u32 num; \ + \ + mutex_lock(&opts->lock); \ + if (opts->refcnt) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + ret = kstrtou32(page, 0, &num); \ + if (ret) \ + goto end; \ + \ + opts->name = num; \ + ret = len; \ + \ +end: \ + mutex_unlock(&opts->lock); \ + return ret; \ +} \ + \ +CONFIGFS_ATTR(f_uac1_opts_, name) + +UAC1_INT_ATTRIBUTE(req_buf_size); +UAC1_INT_ATTRIBUTE(req_count); +UAC1_INT_ATTRIBUTE(audio_buf_size); + +#define UAC1_STR_ATTRIBUTE(name) \ +static ssize_t f_uac1_opts_##name##_show(struct config_item *item, \ + char *page) \ +{ \ + struct f_uac1_legacy_opts *opts = to_f_uac1_opts(item); \ + int result; \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%s\n", opts->name); \ + mutex_unlock(&opts->lock); \ + \ + return result; \ +} \ + \ +static ssize_t f_uac1_opts_##name##_store(struct config_item *item, \ + const char *page, size_t len) \ +{ \ + struct f_uac1_legacy_opts *opts = to_f_uac1_opts(item); \ + int ret = -EBUSY; \ + char *tmp; \ + \ + mutex_lock(&opts->lock); \ + if (opts->refcnt) \ + goto end; \ + \ + tmp = kstrndup(page, len, GFP_KERNEL); \ + if (tmp) { \ + ret = -ENOMEM; \ + goto end; \ + } \ + if (opts->name##_alloc) \ + kfree(opts->name); \ + opts->name##_alloc = true; \ + opts->name = tmp; \ + ret = len; \ + \ +end: \ + mutex_unlock(&opts->lock); \ + return ret; \ +} \ + \ +CONFIGFS_ATTR(f_uac1_opts_, name) + +UAC1_STR_ATTRIBUTE(fn_play); +UAC1_STR_ATTRIBUTE(fn_cap); +UAC1_STR_ATTRIBUTE(fn_cntl); + +static struct configfs_attribute *f_uac1_attrs[] = { + &f_uac1_opts_attr_req_buf_size, + &f_uac1_opts_attr_req_count, + &f_uac1_opts_attr_audio_buf_size, + &f_uac1_opts_attr_fn_play, + &f_uac1_opts_attr_fn_cap, + &f_uac1_opts_attr_fn_cntl, + NULL, +}; + +static struct config_item_type f_uac1_func_type = { + .ct_item_ops = &f_uac1_item_ops, + .ct_attrs = f_uac1_attrs, + .ct_owner = THIS_MODULE, +}; + +static void f_audio_free_inst(struct usb_function_instance *f) +{ + struct f_uac1_legacy_opts *opts; + + opts = container_of(f, struct f_uac1_legacy_opts, func_inst); + if (opts->fn_play_alloc) + kfree(opts->fn_play); + if (opts->fn_cap_alloc) + kfree(opts->fn_cap); + if (opts->fn_cntl_alloc) + kfree(opts->fn_cntl); + kfree(opts); +} + +static struct usb_function_instance *f_audio_alloc_inst(void) +{ + struct f_uac1_legacy_opts *opts; + + opts = kzalloc(sizeof(*opts), GFP_KERNEL); + if (!opts) + return ERR_PTR(-ENOMEM); + + mutex_init(&opts->lock); + opts->func_inst.free_func_inst = f_audio_free_inst; + + config_group_init_type_name(&opts->func_inst.group, "", + &f_uac1_func_type); + + opts->req_buf_size = UAC1_OUT_EP_MAX_PACKET_SIZE; + opts->req_count = UAC1_REQ_COUNT; + opts->audio_buf_size = UAC1_AUDIO_BUF_SIZE; + opts->fn_play = FILE_PCM_PLAYBACK; + opts->fn_cap = FILE_PCM_CAPTURE; + opts->fn_cntl = FILE_CONTROL; + return &opts->func_inst; +} + +static void f_audio_free(struct usb_function *f) +{ + struct f_audio *audio = func_to_audio(f); + struct f_uac1_legacy_opts *opts; + + gaudio_cleanup(&audio->card); + opts = container_of(f->fi, struct f_uac1_legacy_opts, func_inst); + kfree(audio); + mutex_lock(&opts->lock); + --opts->refcnt; + mutex_unlock(&opts->lock); +} + +static void f_audio_unbind(struct usb_configuration *c, struct usb_function *f) +{ + usb_free_all_descriptors(f); +} + +static struct usb_function *f_audio_alloc(struct usb_function_instance *fi) +{ + struct f_audio *audio; + struct f_uac1_legacy_opts *opts; + + /* allocate and initialize one new instance */ + audio = kzalloc(sizeof(*audio), GFP_KERNEL); + if (!audio) + return ERR_PTR(-ENOMEM); + + audio->card.func.name = "g_audio"; + + opts = container_of(fi, struct f_uac1_legacy_opts, func_inst); + mutex_lock(&opts->lock); + ++opts->refcnt; + mutex_unlock(&opts->lock); + INIT_LIST_HEAD(&audio->play_queue); + spin_lock_init(&audio->lock); + + audio->card.func.bind = f_audio_bind; + audio->card.func.unbind = f_audio_unbind; + audio->card.func.set_alt = f_audio_set_alt; + audio->card.func.get_alt = f_audio_get_alt; + audio->card.func.setup = f_audio_setup; + audio->card.func.disable = f_audio_disable; + audio->card.func.free_func = f_audio_free; + + control_selector_init(audio); + + INIT_WORK(&audio->playback_work, f_audio_playback_work); + + return &audio->card.func; +} + +DECLARE_USB_FUNCTION_INIT(uac1_legacy, f_audio_alloc_inst, f_audio_alloc); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Bryan Wu"); diff --git a/drivers/usb/gadget/function/u_uac1.c b/drivers/usb/gadget/function/u_uac1.c deleted file mode 100644 index c78c84138a28..000000000000 --- a/drivers/usb/gadget/function/u_uac1.c +++ /dev/null @@ -1,314 +0,0 @@ -/* - * u_uac1.c -- ALSA audio utilities for Gadget stack - * - * Copyright (C) 2008 Bryan Wu - * Copyright (C) 2008 Analog Devices, Inc - * - * Enter bugs at http://blackfin.uclinux.org/ - * - * Licensed under the GPL-2 or later. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "u_uac1.h" - -/* - * This component encapsulates the ALSA devices for USB audio gadget - */ - -/*-------------------------------------------------------------------------*/ - -/** - * Some ALSA internal helper functions - */ -static int snd_interval_refine_set(struct snd_interval *i, unsigned int val) -{ - struct snd_interval t; - t.empty = 0; - t.min = t.max = val; - t.openmin = t.openmax = 0; - t.integer = 1; - return snd_interval_refine(i, &t); -} - -static int _snd_pcm_hw_param_set(struct snd_pcm_hw_params *params, - snd_pcm_hw_param_t var, unsigned int val, - int dir) -{ - int changed; - if (hw_is_mask(var)) { - struct snd_mask *m = hw_param_mask(params, var); - if (val == 0 && dir < 0) { - changed = -EINVAL; - snd_mask_none(m); - } else { - if (dir > 0) - val++; - else if (dir < 0) - val--; - changed = snd_mask_refine_set( - hw_param_mask(params, var), val); - } - } else if (hw_is_interval(var)) { - struct snd_interval *i = hw_param_interval(params, var); - if (val == 0 && dir < 0) { - changed = -EINVAL; - snd_interval_none(i); - } else if (dir == 0) - changed = snd_interval_refine_set(i, val); - else { - struct snd_interval t; - t.openmin = 1; - t.openmax = 1; - t.empty = 0; - t.integer = 0; - if (dir < 0) { - t.min = val - 1; - t.max = val; - } else { - t.min = val; - t.max = val+1; - } - changed = snd_interval_refine(i, &t); - } - } else - return -EINVAL; - if (changed) { - params->cmask |= 1 << var; - params->rmask |= 1 << var; - } - return changed; -} -/*-------------------------------------------------------------------------*/ - -/** - * Set default hardware params - */ -static int playback_default_hw_params(struct gaudio_snd_dev *snd) -{ - struct snd_pcm_substream *substream = snd->substream; - struct snd_pcm_hw_params *params; - snd_pcm_sframes_t result; - - /* - * SNDRV_PCM_ACCESS_RW_INTERLEAVED, - * SNDRV_PCM_FORMAT_S16_LE - * CHANNELS: 2 - * RATE: 48000 - */ - snd->access = SNDRV_PCM_ACCESS_RW_INTERLEAVED; - snd->format = SNDRV_PCM_FORMAT_S16_LE; - snd->channels = 2; - snd->rate = 48000; - - params = kzalloc(sizeof(*params), GFP_KERNEL); - if (!params) - return -ENOMEM; - - _snd_pcm_hw_params_any(params); - _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_ACCESS, - snd->access, 0); - _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_FORMAT, - snd->format, 0); - _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_CHANNELS, - snd->channels, 0); - _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_RATE, - snd->rate, 0); - - snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL); - snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_HW_PARAMS, params); - - result = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_PREPARE, NULL); - if (result < 0) { - ERROR(snd->card, - "Preparing sound card failed: %d\n", (int)result); - kfree(params); - return result; - } - - /* Store the hardware parameters */ - snd->access = params_access(params); - snd->format = params_format(params); - snd->channels = params_channels(params); - snd->rate = params_rate(params); - - kfree(params); - - INFO(snd->card, - "Hardware params: access %x, format %x, channels %d, rate %d\n", - snd->access, snd->format, snd->channels, snd->rate); - - return 0; -} - -/** - * Playback audio buffer data by ALSA PCM device - */ -size_t u_audio_playback(struct gaudio *card, void *buf, size_t count) -{ - struct gaudio_snd_dev *snd = &card->playback; - struct snd_pcm_substream *substream = snd->substream; - struct snd_pcm_runtime *runtime = substream->runtime; - mm_segment_t old_fs; - ssize_t result; - snd_pcm_sframes_t frames; - -try_again: - if (runtime->status->state == SNDRV_PCM_STATE_XRUN || - runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) { - result = snd_pcm_kernel_ioctl(substream, - SNDRV_PCM_IOCTL_PREPARE, NULL); - if (result < 0) { - ERROR(card, "Preparing sound card failed: %d\n", - (int)result); - return result; - } - } - - frames = bytes_to_frames(runtime, count); - old_fs = get_fs(); - set_fs(KERNEL_DS); - result = snd_pcm_lib_write(snd->substream, (void __user *)buf, frames); - if (result != frames) { - ERROR(card, "Playback error: %d\n", (int)result); - set_fs(old_fs); - goto try_again; - } - set_fs(old_fs); - - return 0; -} - -int u_audio_get_playback_channels(struct gaudio *card) -{ - return card->playback.channels; -} - -int u_audio_get_playback_rate(struct gaudio *card) -{ - return card->playback.rate; -} - -/** - * Open ALSA PCM and control device files - * Initial the PCM or control device - */ -static int gaudio_open_snd_dev(struct gaudio *card) -{ - struct snd_pcm_file *pcm_file; - struct gaudio_snd_dev *snd; - struct f_uac1_opts *opts; - char *fn_play, *fn_cap, *fn_cntl; - - opts = container_of(card->func.fi, struct f_uac1_opts, func_inst); - fn_play = opts->fn_play; - fn_cap = opts->fn_cap; - fn_cntl = opts->fn_cntl; - - /* Open control device */ - snd = &card->control; - snd->filp = filp_open(fn_cntl, O_RDWR, 0); - if (IS_ERR(snd->filp)) { - int ret = PTR_ERR(snd->filp); - ERROR(card, "unable to open sound control device file: %s\n", - fn_cntl); - snd->filp = NULL; - return ret; - } - snd->card = card; - - /* Open PCM playback device and setup substream */ - snd = &card->playback; - snd->filp = filp_open(fn_play, O_WRONLY, 0); - if (IS_ERR(snd->filp)) { - int ret = PTR_ERR(snd->filp); - - ERROR(card, "No such PCM playback device: %s\n", fn_play); - snd->filp = NULL; - return ret; - } - pcm_file = snd->filp->private_data; - snd->substream = pcm_file->substream; - snd->card = card; - playback_default_hw_params(snd); - - /* Open PCM capture device and setup substream */ - snd = &card->capture; - snd->filp = filp_open(fn_cap, O_RDONLY, 0); - if (IS_ERR(snd->filp)) { - ERROR(card, "No such PCM capture device: %s\n", fn_cap); - snd->substream = NULL; - snd->card = NULL; - snd->filp = NULL; - } else { - pcm_file = snd->filp->private_data; - snd->substream = pcm_file->substream; - snd->card = card; - } - - return 0; -} - -/** - * Close ALSA PCM and control device files - */ -static int gaudio_close_snd_dev(struct gaudio *gau) -{ - struct gaudio_snd_dev *snd; - - /* Close control device */ - snd = &gau->control; - if (snd->filp) - filp_close(snd->filp, NULL); - - /* Close PCM playback device and setup substream */ - snd = &gau->playback; - if (snd->filp) - filp_close(snd->filp, NULL); - - /* Close PCM capture device and setup substream */ - snd = &gau->capture; - if (snd->filp) - filp_close(snd->filp, NULL); - - return 0; -} - -/** - * gaudio_setup - setup ALSA interface and preparing for USB transfer - * - * This sets up PCM, mixer or MIDI ALSA devices fore USB gadget using. - * - * Returns negative errno, or zero on success - */ -int gaudio_setup(struct gaudio *card) -{ - int ret; - - ret = gaudio_open_snd_dev(card); - if (ret) - ERROR(card, "we need at least one control device\n"); - - return ret; - -} - -/** - * gaudio_cleanup - remove ALSA device interface - * - * This is called to free all resources allocated by @gaudio_setup(). - */ -void gaudio_cleanup(struct gaudio *the_card) -{ - if (the_card) - gaudio_close_snd_dev(the_card); -} - diff --git a/drivers/usb/gadget/function/u_uac1.h b/drivers/usb/gadget/function/u_uac1.h deleted file mode 100644 index 5c2ac8e8456d..000000000000 --- a/drivers/usb/gadget/function/u_uac1.h +++ /dev/null @@ -1,82 +0,0 @@ -/* - * u_uac1.h -- interface to USB gadget "ALSA AUDIO" utilities - * - * Copyright (C) 2008 Bryan Wu - * Copyright (C) 2008 Analog Devices, Inc - * - * Enter bugs at http://blackfin.uclinux.org/ - * - * Licensed under the GPL-2 or later. - */ - -#ifndef __U_AUDIO_H -#define __U_AUDIO_H - -#include -#include -#include -#include - -#include -#include -#include - -#define FILE_PCM_PLAYBACK "/dev/snd/pcmC0D0p" -#define FILE_PCM_CAPTURE "/dev/snd/pcmC0D0c" -#define FILE_CONTROL "/dev/snd/controlC0" - -#define UAC1_OUT_EP_MAX_PACKET_SIZE 200 -#define UAC1_REQ_COUNT 256 -#define UAC1_AUDIO_BUF_SIZE 48000 - -/* - * This represents the USB side of an audio card device, managed by a USB - * function which provides control and stream interfaces. - */ - -struct gaudio_snd_dev { - struct gaudio *card; - struct file *filp; - struct snd_pcm_substream *substream; - int access; - int format; - int channels; - int rate; -}; - -struct gaudio { - struct usb_function func; - struct usb_gadget *gadget; - - /* ALSA sound device interfaces */ - struct gaudio_snd_dev control; - struct gaudio_snd_dev playback; - struct gaudio_snd_dev capture; - - /* TODO */ -}; - -struct f_uac1_opts { - struct usb_function_instance func_inst; - int req_buf_size; - int req_count; - int audio_buf_size; - char *fn_play; - char *fn_cap; - char *fn_cntl; - unsigned bound:1; - unsigned fn_play_alloc:1; - unsigned fn_cap_alloc:1; - unsigned fn_cntl_alloc:1; - struct mutex lock; - int refcnt; -}; - -int gaudio_setup(struct gaudio *card); -void gaudio_cleanup(struct gaudio *the_card); - -size_t u_audio_playback(struct gaudio *card, void *buf, size_t count); -int u_audio_get_playback_channels(struct gaudio *card); -int u_audio_get_playback_rate(struct gaudio *card); - -#endif /* __U_AUDIO_H */ diff --git a/drivers/usb/gadget/function/u_uac1_legacy.c b/drivers/usb/gadget/function/u_uac1_legacy.c new file mode 100644 index 000000000000..8aa76b4dc117 --- /dev/null +++ b/drivers/usb/gadget/function/u_uac1_legacy.c @@ -0,0 +1,315 @@ +/* + * u_uac1.c -- ALSA audio utilities for Gadget stack + * + * Copyright (C) 2008 Bryan Wu + * Copyright (C) 2008 Analog Devices, Inc + * + * Enter bugs at http://blackfin.uclinux.org/ + * + * Licensed under the GPL-2 or later. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "u_uac1_legacy.h" + +/* + * This component encapsulates the ALSA devices for USB audio gadget + */ + +/*-------------------------------------------------------------------------*/ + +/** + * Some ALSA internal helper functions + */ +static int snd_interval_refine_set(struct snd_interval *i, unsigned int val) +{ + struct snd_interval t; + t.empty = 0; + t.min = t.max = val; + t.openmin = t.openmax = 0; + t.integer = 1; + return snd_interval_refine(i, &t); +} + +static int _snd_pcm_hw_param_set(struct snd_pcm_hw_params *params, + snd_pcm_hw_param_t var, unsigned int val, + int dir) +{ + int changed; + if (hw_is_mask(var)) { + struct snd_mask *m = hw_param_mask(params, var); + if (val == 0 && dir < 0) { + changed = -EINVAL; + snd_mask_none(m); + } else { + if (dir > 0) + val++; + else if (dir < 0) + val--; + changed = snd_mask_refine_set( + hw_param_mask(params, var), val); + } + } else if (hw_is_interval(var)) { + struct snd_interval *i = hw_param_interval(params, var); + if (val == 0 && dir < 0) { + changed = -EINVAL; + snd_interval_none(i); + } else if (dir == 0) + changed = snd_interval_refine_set(i, val); + else { + struct snd_interval t; + t.openmin = 1; + t.openmax = 1; + t.empty = 0; + t.integer = 0; + if (dir < 0) { + t.min = val - 1; + t.max = val; + } else { + t.min = val; + t.max = val+1; + } + changed = snd_interval_refine(i, &t); + } + } else + return -EINVAL; + if (changed) { + params->cmask |= 1 << var; + params->rmask |= 1 << var; + } + return changed; +} +/*-------------------------------------------------------------------------*/ + +/** + * Set default hardware params + */ +static int playback_default_hw_params(struct gaudio_snd_dev *snd) +{ + struct snd_pcm_substream *substream = snd->substream; + struct snd_pcm_hw_params *params; + snd_pcm_sframes_t result; + + /* + * SNDRV_PCM_ACCESS_RW_INTERLEAVED, + * SNDRV_PCM_FORMAT_S16_LE + * CHANNELS: 2 + * RATE: 48000 + */ + snd->access = SNDRV_PCM_ACCESS_RW_INTERLEAVED; + snd->format = SNDRV_PCM_FORMAT_S16_LE; + snd->channels = 2; + snd->rate = 48000; + + params = kzalloc(sizeof(*params), GFP_KERNEL); + if (!params) + return -ENOMEM; + + _snd_pcm_hw_params_any(params); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_ACCESS, + snd->access, 0); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_FORMAT, + snd->format, 0); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_CHANNELS, + snd->channels, 0); + _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_RATE, + snd->rate, 0); + + snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL); + snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_HW_PARAMS, params); + + result = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_PREPARE, NULL); + if (result < 0) { + ERROR(snd->card, + "Preparing sound card failed: %d\n", (int)result); + kfree(params); + return result; + } + + /* Store the hardware parameters */ + snd->access = params_access(params); + snd->format = params_format(params); + snd->channels = params_channels(params); + snd->rate = params_rate(params); + + kfree(params); + + INFO(snd->card, + "Hardware params: access %x, format %x, channels %d, rate %d\n", + snd->access, snd->format, snd->channels, snd->rate); + + return 0; +} + +/** + * Playback audio buffer data by ALSA PCM device + */ +size_t u_audio_playback(struct gaudio *card, void *buf, size_t count) +{ + struct gaudio_snd_dev *snd = &card->playback; + struct snd_pcm_substream *substream = snd->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + mm_segment_t old_fs; + ssize_t result; + snd_pcm_sframes_t frames; + +try_again: + if (runtime->status->state == SNDRV_PCM_STATE_XRUN || + runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) { + result = snd_pcm_kernel_ioctl(substream, + SNDRV_PCM_IOCTL_PREPARE, NULL); + if (result < 0) { + ERROR(card, "Preparing sound card failed: %d\n", + (int)result); + return result; + } + } + + frames = bytes_to_frames(runtime, count); + old_fs = get_fs(); + set_fs(KERNEL_DS); + result = snd_pcm_lib_write(snd->substream, (void __user *)buf, frames); + if (result != frames) { + ERROR(card, "Playback error: %d\n", (int)result); + set_fs(old_fs); + goto try_again; + } + set_fs(old_fs); + + return 0; +} + +int u_audio_get_playback_channels(struct gaudio *card) +{ + return card->playback.channels; +} + +int u_audio_get_playback_rate(struct gaudio *card) +{ + return card->playback.rate; +} + +/** + * Open ALSA PCM and control device files + * Initial the PCM or control device + */ +static int gaudio_open_snd_dev(struct gaudio *card) +{ + struct snd_pcm_file *pcm_file; + struct gaudio_snd_dev *snd; + struct f_uac1_legacy_opts *opts; + char *fn_play, *fn_cap, *fn_cntl; + + opts = container_of(card->func.fi, struct f_uac1_legacy_opts, + func_inst); + fn_play = opts->fn_play; + fn_cap = opts->fn_cap; + fn_cntl = opts->fn_cntl; + + /* Open control device */ + snd = &card->control; + snd->filp = filp_open(fn_cntl, O_RDWR, 0); + if (IS_ERR(snd->filp)) { + int ret = PTR_ERR(snd->filp); + ERROR(card, "unable to open sound control device file: %s\n", + fn_cntl); + snd->filp = NULL; + return ret; + } + snd->card = card; + + /* Open PCM playback device and setup substream */ + snd = &card->playback; + snd->filp = filp_open(fn_play, O_WRONLY, 0); + if (IS_ERR(snd->filp)) { + int ret = PTR_ERR(snd->filp); + + ERROR(card, "No such PCM playback device: %s\n", fn_play); + snd->filp = NULL; + return ret; + } + pcm_file = snd->filp->private_data; + snd->substream = pcm_file->substream; + snd->card = card; + playback_default_hw_params(snd); + + /* Open PCM capture device and setup substream */ + snd = &card->capture; + snd->filp = filp_open(fn_cap, O_RDONLY, 0); + if (IS_ERR(snd->filp)) { + ERROR(card, "No such PCM capture device: %s\n", fn_cap); + snd->substream = NULL; + snd->card = NULL; + snd->filp = NULL; + } else { + pcm_file = snd->filp->private_data; + snd->substream = pcm_file->substream; + snd->card = card; + } + + return 0; +} + +/** + * Close ALSA PCM and control device files + */ +static int gaudio_close_snd_dev(struct gaudio *gau) +{ + struct gaudio_snd_dev *snd; + + /* Close control device */ + snd = &gau->control; + if (snd->filp) + filp_close(snd->filp, NULL); + + /* Close PCM playback device and setup substream */ + snd = &gau->playback; + if (snd->filp) + filp_close(snd->filp, NULL); + + /* Close PCM capture device and setup substream */ + snd = &gau->capture; + if (snd->filp) + filp_close(snd->filp, NULL); + + return 0; +} + +/** + * gaudio_setup - setup ALSA interface and preparing for USB transfer + * + * This sets up PCM, mixer or MIDI ALSA devices fore USB gadget using. + * + * Returns negative errno, or zero on success + */ +int gaudio_setup(struct gaudio *card) +{ + int ret; + + ret = gaudio_open_snd_dev(card); + if (ret) + ERROR(card, "we need at least one control device\n"); + + return ret; + +} + +/** + * gaudio_cleanup - remove ALSA device interface + * + * This is called to free all resources allocated by @gaudio_setup(). + */ +void gaudio_cleanup(struct gaudio *the_card) +{ + if (the_card) + gaudio_close_snd_dev(the_card); +} + diff --git a/drivers/usb/gadget/function/u_uac1_legacy.h b/drivers/usb/gadget/function/u_uac1_legacy.h new file mode 100644 index 000000000000..d715b1af56a4 --- /dev/null +++ b/drivers/usb/gadget/function/u_uac1_legacy.h @@ -0,0 +1,82 @@ +/* + * u_uac1.h -- interface to USB gadget "ALSA AUDIO" utilities + * + * Copyright (C) 2008 Bryan Wu + * Copyright (C) 2008 Analog Devices, Inc + * + * Enter bugs at http://blackfin.uclinux.org/ + * + * Licensed under the GPL-2 or later. + */ + +#ifndef __U_UAC1_LEGACY_H +#define __U_UAC1_LEGACY_H + +#include +#include +#include +#include + +#include +#include +#include + +#define FILE_PCM_PLAYBACK "/dev/snd/pcmC0D0p" +#define FILE_PCM_CAPTURE "/dev/snd/pcmC0D0c" +#define FILE_CONTROL "/dev/snd/controlC0" + +#define UAC1_OUT_EP_MAX_PACKET_SIZE 200 +#define UAC1_REQ_COUNT 256 +#define UAC1_AUDIO_BUF_SIZE 48000 + +/* + * This represents the USB side of an audio card device, managed by a USB + * function which provides control and stream interfaces. + */ + +struct gaudio_snd_dev { + struct gaudio *card; + struct file *filp; + struct snd_pcm_substream *substream; + int access; + int format; + int channels; + int rate; +}; + +struct gaudio { + struct usb_function func; + struct usb_gadget *gadget; + + /* ALSA sound device interfaces */ + struct gaudio_snd_dev control; + struct gaudio_snd_dev playback; + struct gaudio_snd_dev capture; + + /* TODO */ +}; + +struct f_uac1_legacy_opts { + struct usb_function_instance func_inst; + int req_buf_size; + int req_count; + int audio_buf_size; + char *fn_play; + char *fn_cap; + char *fn_cntl; + unsigned bound:1; + unsigned fn_play_alloc:1; + unsigned fn_cap_alloc:1; + unsigned fn_cntl_alloc:1; + struct mutex lock; + int refcnt; +}; + +int gaudio_setup(struct gaudio *card); +void gaudio_cleanup(struct gaudio *the_card); + +size_t u_audio_playback(struct gaudio *card, void *buf, size_t count); +int u_audio_get_playback_channels(struct gaudio *card); +int u_audio_get_playback_rate(struct gaudio *card); + +#endif /* __U_UAC1_LEGACY_H */ diff --git a/drivers/usb/gadget/legacy/Kconfig b/drivers/usb/gadget/legacy/Kconfig index 5344064f5721..87bacb638a9c 100644 --- a/drivers/usb/gadget/legacy/Kconfig +++ b/drivers/usb/gadget/legacy/Kconfig @@ -54,8 +54,8 @@ config USB_AUDIO depends on SND select USB_LIBCOMPOSITE select SND_PCM - select USB_F_UAC1 if GADGET_UAC1 - select USB_F_UAC2 if !GADGET_UAC1 + select USB_F_UAC1_LEGACY if GADGET_UAC1_LEGACY + select USB_F_UAC2 if !GADGET_UAC1_LEGACY select USB_U_AUDIO if USB_F_UAC2 help This Gadget Audio driver is compatible with USB Audio Class @@ -73,7 +73,7 @@ config USB_AUDIO Say "y" to link the driver statically, or "m" to build a dynamically linked module called "g_audio". -config GADGET_UAC1 +config GADGET_UAC1_LEGACY bool "UAC 1.0 (Legacy)" depends on USB_AUDIO help diff --git a/drivers/usb/gadget/legacy/audio.c b/drivers/usb/gadget/legacy/audio.c index 8a39f42a4d56..bf5592a5b9e9 100644 --- a/drivers/usb/gadget/legacy/audio.c +++ b/drivers/usb/gadget/legacy/audio.c @@ -20,7 +20,7 @@ USB_GADGET_COMPOSITE_OPTIONS(); -#ifndef CONFIG_GADGET_UAC1 +#ifndef CONFIG_GADGET_UAC1_LEGACY #include "u_uac2.h" /* Playback(USB-IN) Default Stereo - Fl/Fr */ @@ -53,7 +53,7 @@ static int c_ssize = UAC2_DEF_CSSIZE; module_param(c_ssize, uint, S_IRUGO); MODULE_PARM_DESC(c_ssize, "Capture Sample Size(bytes)"); #else -#include "u_uac1.h" +#include "u_uac1_legacy.h" static char *fn_play = FILE_PCM_PLAYBACK; module_param(fn_play, charp, S_IRUGO); @@ -99,7 +99,7 @@ static struct usb_gadget_strings *audio_strings[] = { NULL, }; -#ifndef CONFIG_GADGET_UAC1 +#ifndef CONFIG_GADGET_UAC1_LEGACY static struct usb_function_instance *fi_uac2; static struct usb_function *f_uac2; #else @@ -125,7 +125,7 @@ static struct usb_device_descriptor device_desc = { /* .bcdUSB = DYNAMIC */ -#ifdef CONFIG_GADGET_UAC1 +#ifdef CONFIG_GADGET_UAC1_LEGACY .bDeviceClass = USB_CLASS_PER_INTERFACE, .bDeviceSubClass = 0, .bDeviceProtocol = 0, @@ -164,7 +164,7 @@ static int audio_do_config(struct usb_configuration *c) c->bmAttributes |= USB_CONFIG_ATT_WAKEUP; } -#ifdef CONFIG_GADGET_UAC1 +#ifdef CONFIG_GADGET_UAC1_LEGACY f_uac1 = usb_get_function(fi_uac1); if (IS_ERR(f_uac1)) { status = PTR_ERR(f_uac1); @@ -204,24 +204,24 @@ static struct usb_configuration audio_config_driver = { static int audio_bind(struct usb_composite_dev *cdev) { -#ifndef CONFIG_GADGET_UAC1 +#ifndef CONFIG_GADGET_UAC1_LEGACY struct f_uac2_opts *uac2_opts; #else - struct f_uac1_opts *uac1_opts; + struct f_uac1_legacy_opts *uac1_opts; #endif int status; -#ifndef CONFIG_GADGET_UAC1 +#ifndef CONFIG_GADGET_UAC1_LEGACY fi_uac2 = usb_get_function_instance("uac2"); if (IS_ERR(fi_uac2)) return PTR_ERR(fi_uac2); #else - fi_uac1 = usb_get_function_instance("uac1"); + fi_uac1 = usb_get_function_instance("uac1_legacy"); if (IS_ERR(fi_uac1)) return PTR_ERR(fi_uac1); #endif -#ifndef CONFIG_GADGET_UAC1 +#ifndef CONFIG_GADGET_UAC1_LEGACY uac2_opts = container_of(fi_uac2, struct f_uac2_opts, func_inst); uac2_opts->p_chmask = p_chmask; uac2_opts->p_srate = p_srate; @@ -231,7 +231,7 @@ static int audio_bind(struct usb_composite_dev *cdev) uac2_opts->c_ssize = c_ssize; uac2_opts->req_number = UAC2_DEF_REQ_NUM; #else - uac1_opts = container_of(fi_uac1, struct f_uac1_opts, func_inst); + uac1_opts = container_of(fi_uac1, struct f_uac1_legacy_opts, func_inst); uac1_opts->fn_play = fn_play; uac1_opts->fn_cap = fn_cap; uac1_opts->fn_cntl = fn_cntl; @@ -269,7 +269,7 @@ fail_otg_desc: kfree(otg_desc[0]); otg_desc[0] = NULL; fail: -#ifndef CONFIG_GADGET_UAC1 +#ifndef CONFIG_GADGET_UAC1_LEGACY usb_put_function_instance(fi_uac2); #else usb_put_function_instance(fi_uac1); @@ -279,7 +279,7 @@ fail: static int audio_unbind(struct usb_composite_dev *cdev) { -#ifdef CONFIG_GADGET_UAC1 +#ifdef CONFIG_GADGET_UAC1_LEGACY if (!IS_ERR_OR_NULL(f_uac1)) usb_put_function(f_uac1); if (!IS_ERR_OR_NULL(fi_uac1)) -- cgit v1.2.3 From 0591bc2360152f851e29246884805bb77a2c3b9d Mon Sep 17 00:00:00 2001 From: Ruslan Bilovol Date: Sun, 18 Jun 2017 16:23:54 +0300 Subject: usb: gadget: add f_uac1 variant based on a new u_audio api This patch adds a new function 'f_uac1' (f_uac1 with virtual "ALSA card") that uses recently created u_audio API. Comparing to legacy f_uac1 function implementation it doesn't require any real Audio codec to be present on the device. In f_uac1 audio streams are simply sinked to and sourced from a virtual ALSA sound card created using u_audio API. Legacy f_uac1 approach is to write audio samples directly to existing ALSA sound card f_uac1 approach is more generic/flexible one - create an ALSA sound card that represents USB Audio function and allows to be used by userspace application that may choose to do whatever it wants with the data received from the USB Host and choose to provide whatever it wants as audio data to the USB Host. f_uac1 also has capture support (gadget->host) thanks to easy implementation via u_audio. By default, capture interface has 48000kHz/2ch configuration, same as playback channel has. f_uac1 descriptors naming convention uses f_uac2 driver naming convention that makes it more common and meaningful. Comparing to f_uac1_legacy, the f_uac1 doesn't have volume/mute functionality. This is because the f_uac1 volume/mute feature unit was dummy implementation since that driver creation (2009) and never had any real volume control or mute functionality, so there is no any difference here. Since f_uac1 functionality, exposed interface to userspace (virtual ALSA card), input parameters are so different comparing to f_uac1_legacy, that there is no any reason to keep them in the same file/module, and separate function was created. g_audio can be built using one of existing UAC functions (f_uac1, f_uac1_legacy or f_uac2) Signed-off-by: Ruslan Bilovol Signed-off-by: Felipe Balbi --- Documentation/ABI/testing/configfs-usb-gadget-uac1 | 14 + Documentation/usb/gadget-testing.txt | 44 ++ drivers/usb/gadget/Kconfig | 25 +- drivers/usb/gadget/function/Makefile | 2 + drivers/usb/gadget/function/f_uac1.c | 802 +++++++++++++++++++++ drivers/usb/gadget/function/u_uac1.h | 41 ++ drivers/usb/gadget/legacy/Kconfig | 18 +- drivers/usb/gadget/legacy/audio.c | 69 +- 8 files changed, 1000 insertions(+), 15 deletions(-) create mode 100644 Documentation/ABI/testing/configfs-usb-gadget-uac1 create mode 100644 drivers/usb/gadget/function/f_uac1.c create mode 100644 drivers/usb/gadget/function/u_uac1.h diff --git a/Documentation/ABI/testing/configfs-usb-gadget-uac1 b/Documentation/ABI/testing/configfs-usb-gadget-uac1 new file mode 100644 index 000000000000..abfe447c848f --- /dev/null +++ b/Documentation/ABI/testing/configfs-usb-gadget-uac1 @@ -0,0 +1,14 @@ +What: /config/usb-gadget/gadget/functions/uac1.name +Date: June 2017 +KernelVersion: 4.14 +Description: + The attributes: + + c_chmask - capture channel mask + c_srate - capture sampling rate + c_ssize - capture sample size (bytes) + p_chmask - playback channel mask + p_srate - playback sampling rate + p_ssize - playback sample size (bytes) + req_number - the number of pre-allocated request + for both capture and playback diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt index ce51d6e4e7d0..fbc397d17e98 100644 --- a/Documentation/usb/gadget-testing.txt +++ b/Documentation/usb/gadget-testing.txt @@ -20,6 +20,7 @@ provided by gadgets. 17. UAC2 function 18. UVC function 19. PRINTER function +20. UAC1 function (new API) 1. ACM function @@ -773,3 +774,46 @@ host: More advanced testing can be done with the prn_example described in Documentation/usb/gadget-printer.txt. + + +20. UAC1 function (virtual ALSA card, using u_audio API) +================= + +The function is provided by usb_f_uac1.ko module. +It will create a virtual ALSA card and the audio streams are simply +sinked to and sourced from it. + +Function-specific configfs interface +------------------------------------ + +The function name to use when creating the function directory is "uac1". +The uac1 function provides these attributes in its function directory: + + c_chmask - capture channel mask + c_srate - capture sampling rate + c_ssize - capture sample size (bytes) + p_chmask - playback channel mask + p_srate - playback sampling rate + p_ssize - playback sample size (bytes) + req_number - the number of pre-allocated request for both capture + and playback + +The attributes have sane default values. + +Testing the UAC1 function +------------------------- + +device: run the gadget +host: aplay -l # should list our USB Audio Gadget + +This function does not require real hardware support, it just +sends a stream of audio data to/from the host. In order to +actually hear something at the device side, a command similar +to this must be used at the device side: + +$ arecord -f dat -t wav -D hw:2,0 | aplay -D hw:0,0 & + +e.g.: + +$ arecord -f dat -t wav -D hw:CARD=UAC1Gadget,DEV=0 | \ +aplay -D default:CARD=OdroidU3 diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 01c2e2b9ab1d..35cc641d9f31 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -191,6 +191,9 @@ config USB_F_MASS_STORAGE config USB_F_FS tristate +config USB_F_UAC1 + tristate + config USB_F_UAC1_LEGACY tristate @@ -365,6 +368,24 @@ config USB_CONFIGFS_F_FS implemented in kernel space (for instance Ethernet, serial or mass storage) and other are implemented in user space. +config USB_CONFIGFS_F_UAC1 + bool "Audio Class 1.0" + depends on USB_CONFIGFS + depends on SND + select USB_LIBCOMPOSITE + select SND_PCM + select USB_U_AUDIO + select USB_F_UAC1 + help + This Audio function implements 1 AudioControl interface, + 1 AudioStreaming Interface each for USB-OUT and USB-IN. + This driver doesn't expect any real Audio codec to be present + on the device - the audio streams are simply sinked to and + sourced from a virtual ALSA sound card created. The user-space + application may choose to do whatever it wants with the data + received from the USB Host and choose to provide whatever it + wants as audio data to the USB Host. + config USB_CONFIGFS_F_UAC1_LEGACY bool "Audio Class 1.0 (legacy implementation)" depends on USB_CONFIGFS @@ -375,8 +396,8 @@ config USB_CONFIGFS_F_UAC1_LEGACY help This Audio function implements 1 AudioControl interface, 1 AudioStreaming Interface each for USB-OUT and USB-IN. - This driver requires a real Audio codec to be present - on the device. + This is a legacy driver and requires a real Audio codec + to be present on the device. config USB_CONFIGFS_F_UAC2 bool "Audio Class 2.0" diff --git a/drivers/usb/gadget/function/Makefile b/drivers/usb/gadget/function/Makefile index 50ee517faf74..86e825269947 100644 --- a/drivers/usb/gadget/function/Makefile +++ b/drivers/usb/gadget/function/Makefile @@ -33,6 +33,8 @@ obj-$(CONFIG_USB_F_MASS_STORAGE)+= usb_f_mass_storage.o usb_f_fs-y := f_fs.o obj-$(CONFIG_USB_F_FS) += usb_f_fs.o obj-$(CONFIG_USB_U_AUDIO) += u_audio.o +usb_f_uac1-y := f_uac1.o +obj-$(CONFIG_USB_F_UAC1) += usb_f_uac1.o usb_f_uac1_legacy-y := f_uac1_legacy.o u_uac1_legacy.o obj-$(CONFIG_USB_F_UAC1_LEGACY) += usb_f_uac1_legacy.o usb_f_uac2-y := f_uac2.o diff --git a/drivers/usb/gadget/function/f_uac1.c b/drivers/usb/gadget/function/f_uac1.c new file mode 100644 index 000000000000..8656f84e17d9 --- /dev/null +++ b/drivers/usb/gadget/function/f_uac1.c @@ -0,0 +1,802 @@ +/* + * f_uac1.c -- USB Audio Class 1.0 Function (using u_audio API) + * + * Copyright (C) 2016 Ruslan Bilovol + * + * This driver doesn't expect any real Audio codec to be present + * on the device - the audio streams are simply sinked to and + * sourced from a virtual ALSA sound card created. + * + * This file is based on f_uac1.c which is + * Copyright (C) 2008 Bryan Wu + * Copyright (C) 2008 Analog Devices, Inc + * + * 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; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include + +#include "u_audio.h" +#include "u_uac1.h" + +struct f_uac1 { + struct g_audio g_audio; + u8 ac_intf, as_in_intf, as_out_intf; + u8 ac_alt, as_in_alt, as_out_alt; /* needed for get_alt() */ +}; + +static inline struct f_uac1 *func_to_uac1(struct usb_function *f) +{ + return container_of(f, struct f_uac1, g_audio.func); +} + +/* + * DESCRIPTORS ... most are static, but strings and full + * configuration descriptors are built on demand. + */ + +/* + * We have three interfaces - one AudioControl and two AudioStreaming + * + * The driver implements a simple UAC_1 topology. + * USB-OUT -> IT_1 -> OT_2 -> ALSA_Capture + * ALSA_Playback -> IT_3 -> OT_4 -> USB-IN + */ +#define F_AUDIO_AC_INTERFACE 0 +#define F_AUDIO_AS_OUT_INTERFACE 1 +#define F_AUDIO_AS_IN_INTERFACE 2 +/* Number of streaming interfaces */ +#define F_AUDIO_NUM_INTERFACES 2 + +/* B.3.1 Standard AC Interface Descriptor */ +static struct usb_interface_descriptor ac_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, +}; + +/* + * The number of AudioStreaming and MIDIStreaming interfaces + * in the Audio Interface Collection + */ +DECLARE_UAC_AC_HEADER_DESCRIPTOR(2); + +#define UAC_DT_AC_HEADER_LENGTH UAC_DT_AC_HEADER_SIZE(F_AUDIO_NUM_INTERFACES) +/* 2 input terminals and 2 output terminals */ +#define UAC_DT_TOTAL_LENGTH (UAC_DT_AC_HEADER_LENGTH \ + + 2*UAC_DT_INPUT_TERMINAL_SIZE + 2*UAC_DT_OUTPUT_TERMINAL_SIZE) +/* B.3.2 Class-Specific AC Interface Descriptor */ +static struct uac1_ac_header_descriptor_2 ac_header_desc = { + .bLength = UAC_DT_AC_HEADER_LENGTH, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_HEADER, + .bcdADC = cpu_to_le16(0x0100), + .wTotalLength = cpu_to_le16(UAC_DT_TOTAL_LENGTH), + .bInCollection = F_AUDIO_NUM_INTERFACES, + .baInterfaceNr = { + /* Interface number of the AudioStream interfaces */ + [0] = 1, + [1] = 2, + } +}; + +#define USB_OUT_IT_ID 1 +static struct uac_input_terminal_descriptor usb_out_it_desc = { + .bLength = UAC_DT_INPUT_TERMINAL_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_INPUT_TERMINAL, + .bTerminalID = USB_OUT_IT_ID, + .wTerminalType = UAC_TERMINAL_STREAMING, + .bAssocTerminal = 0, + .wChannelConfig = 0x3, +}; + +#define IO_OUT_OT_ID 2 +static struct uac1_output_terminal_descriptor io_out_ot_desc = { + .bLength = UAC_DT_OUTPUT_TERMINAL_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_OUTPUT_TERMINAL, + .bTerminalID = IO_OUT_OT_ID, + .wTerminalType = UAC_OUTPUT_TERMINAL_SPEAKER, + .bAssocTerminal = 0, + .bSourceID = USB_OUT_IT_ID, +}; + +#define IO_IN_IT_ID 3 +static struct uac_input_terminal_descriptor io_in_it_desc = { + .bLength = UAC_DT_INPUT_TERMINAL_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_INPUT_TERMINAL, + .bTerminalID = IO_IN_IT_ID, + .wTerminalType = UAC_INPUT_TERMINAL_MICROPHONE, + .bAssocTerminal = 0, + .wChannelConfig = 0x3, +}; + +#define USB_IN_OT_ID 4 +static struct uac1_output_terminal_descriptor usb_in_ot_desc = { + .bLength = UAC_DT_OUTPUT_TERMINAL_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_OUTPUT_TERMINAL, + .bTerminalID = USB_IN_OT_ID, + .wTerminalType = UAC_TERMINAL_STREAMING, + .bAssocTerminal = 0, + .bSourceID = IO_IN_IT_ID, +}; + +/* B.4.1 Standard AS Interface Descriptor */ +static struct usb_interface_descriptor as_out_interface_alt_0_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING, +}; + +static struct usb_interface_descriptor as_out_interface_alt_1_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bAlternateSetting = 1, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING, +}; + +static struct usb_interface_descriptor as_in_interface_alt_0_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING, +}; + +static struct usb_interface_descriptor as_in_interface_alt_1_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bAlternateSetting = 1, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING, +}; + +/* B.4.2 Class-Specific AS Interface Descriptor */ +static struct uac1_as_header_descriptor as_out_header_desc = { + .bLength = UAC_DT_AS_HEADER_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_AS_GENERAL, + .bTerminalLink = USB_OUT_IT_ID, + .bDelay = 1, + .wFormatTag = UAC_FORMAT_TYPE_I_PCM, +}; + +static struct uac1_as_header_descriptor as_in_header_desc = { + .bLength = UAC_DT_AS_HEADER_SIZE, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_AS_GENERAL, + .bTerminalLink = USB_IN_OT_ID, + .bDelay = 1, + .wFormatTag = UAC_FORMAT_TYPE_I_PCM, +}; + +DECLARE_UAC_FORMAT_TYPE_I_DISCRETE_DESC(1); + +static struct uac_format_type_i_discrete_descriptor_1 as_out_type_i_desc = { + .bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_FORMAT_TYPE, + .bFormatType = UAC_FORMAT_TYPE_I, + .bSubframeSize = 2, + .bBitResolution = 16, + .bSamFreqType = 1, +}; + +/* Standard ISO OUT Endpoint Descriptor */ +static struct usb_endpoint_descriptor as_out_ep_desc = { + .bLength = USB_DT_ENDPOINT_AUDIO_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_SYNC_ADAPTIVE + | USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = cpu_to_le16(UAC1_OUT_EP_MAX_PACKET_SIZE), + .bInterval = 4, +}; + +/* Class-specific AS ISO OUT Endpoint Descriptor */ +static struct uac_iso_endpoint_descriptor as_iso_out_desc = { + .bLength = UAC_ISO_ENDPOINT_DESC_SIZE, + .bDescriptorType = USB_DT_CS_ENDPOINT, + .bDescriptorSubtype = UAC_EP_GENERAL, + .bmAttributes = 1, + .bLockDelayUnits = 1, + .wLockDelay = cpu_to_le16(1), +}; + +static struct uac_format_type_i_discrete_descriptor_1 as_in_type_i_desc = { + .bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = UAC_FORMAT_TYPE, + .bFormatType = UAC_FORMAT_TYPE_I, + .bSubframeSize = 2, + .bBitResolution = 16, + .bSamFreqType = 1, +}; + +/* Standard ISO OUT Endpoint Descriptor */ +static struct usb_endpoint_descriptor as_in_ep_desc = { + .bLength = USB_DT_ENDPOINT_AUDIO_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_SYNC_ASYNC + | USB_ENDPOINT_XFER_ISOC, + .wMaxPacketSize = cpu_to_le16(UAC1_OUT_EP_MAX_PACKET_SIZE), + .bInterval = 4, +}; + +/* Class-specific AS ISO OUT Endpoint Descriptor */ +static struct uac_iso_endpoint_descriptor as_iso_in_desc = { + .bLength = UAC_ISO_ENDPOINT_DESC_SIZE, + .bDescriptorType = USB_DT_CS_ENDPOINT, + .bDescriptorSubtype = UAC_EP_GENERAL, + .bmAttributes = 1, + .bLockDelayUnits = 0, + .wLockDelay = 0, +}; + +static struct usb_descriptor_header *f_audio_desc[] = { + (struct usb_descriptor_header *)&ac_interface_desc, + (struct usb_descriptor_header *)&ac_header_desc, + + (struct usb_descriptor_header *)&usb_out_it_desc, + (struct usb_descriptor_header *)&io_out_ot_desc, + (struct usb_descriptor_header *)&io_in_it_desc, + (struct usb_descriptor_header *)&usb_in_ot_desc, + + (struct usb_descriptor_header *)&as_out_interface_alt_0_desc, + (struct usb_descriptor_header *)&as_out_interface_alt_1_desc, + (struct usb_descriptor_header *)&as_out_header_desc, + + (struct usb_descriptor_header *)&as_out_type_i_desc, + + (struct usb_descriptor_header *)&as_out_ep_desc, + (struct usb_descriptor_header *)&as_iso_out_desc, + + (struct usb_descriptor_header *)&as_in_interface_alt_0_desc, + (struct usb_descriptor_header *)&as_in_interface_alt_1_desc, + (struct usb_descriptor_header *)&as_in_header_desc, + + (struct usb_descriptor_header *)&as_in_type_i_desc, + + (struct usb_descriptor_header *)&as_in_ep_desc, + (struct usb_descriptor_header *)&as_iso_in_desc, + NULL, +}; + +enum { + STR_AC_IF, + STR_USB_OUT_IT, + STR_USB_OUT_IT_CH_NAMES, + STR_IO_OUT_OT, + STR_IO_IN_IT, + STR_IO_IN_IT_CH_NAMES, + STR_USB_IN_OT, + STR_AS_OUT_IF_ALT0, + STR_AS_OUT_IF_ALT1, + STR_AS_IN_IF_ALT0, + STR_AS_IN_IF_ALT1, +}; + +static struct usb_string strings_uac1[] = { + [STR_AC_IF].s = "AC Interface", + [STR_USB_OUT_IT].s = "Playback Input terminal", + [STR_USB_OUT_IT_CH_NAMES].s = "Playback Channels", + [STR_IO_OUT_OT].s = "Playback Output terminal", + [STR_IO_IN_IT].s = "Capture Input terminal", + [STR_IO_IN_IT_CH_NAMES].s = "Capture Channels", + [STR_USB_IN_OT].s = "Capture Output terminal", + [STR_AS_OUT_IF_ALT0].s = "Playback Inactive", + [STR_AS_OUT_IF_ALT1].s = "Playback Active", + [STR_AS_IN_IF_ALT0].s = "Capture Inactive", + [STR_AS_IN_IF_ALT1].s = "Capture Active", + { }, +}; + +static struct usb_gadget_strings str_uac1 = { + .language = 0x0409, /* en-us */ + .strings = strings_uac1, +}; + +static struct usb_gadget_strings *uac1_strings[] = { + &str_uac1, + NULL, +}; + +/* + * This function is an ALSA sound card following USB Audio Class Spec 1.0. + */ + +static int audio_set_endpoint_req(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct usb_composite_dev *cdev = f->config->cdev; + int value = -EOPNOTSUPP; + u16 ep = le16_to_cpu(ctrl->wIndex); + u16 len = le16_to_cpu(ctrl->wLength); + u16 w_value = le16_to_cpu(ctrl->wValue); + + DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n", + ctrl->bRequest, w_value, len, ep); + + switch (ctrl->bRequest) { + case UAC_SET_CUR: + value = len; + break; + + case UAC_SET_MIN: + break; + + case UAC_SET_MAX: + break; + + case UAC_SET_RES: + break; + + case UAC_SET_MEM: + break; + + default: + break; + } + + return value; +} + +static int audio_get_endpoint_req(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct usb_composite_dev *cdev = f->config->cdev; + int value = -EOPNOTSUPP; + u8 ep = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF); + u16 len = le16_to_cpu(ctrl->wLength); + u16 w_value = le16_to_cpu(ctrl->wValue); + + DBG(cdev, "bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n", + ctrl->bRequest, w_value, len, ep); + + switch (ctrl->bRequest) { + case UAC_GET_CUR: + case UAC_GET_MIN: + case UAC_GET_MAX: + case UAC_GET_RES: + value = len; + break; + case UAC_GET_MEM: + break; + default: + break; + } + + return value; +} + +static int +f_audio_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + int value = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + + /* composite driver infrastructure handles everything; interface + * activation uses set_alt(). + */ + switch (ctrl->bRequestType) { + case USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT: + value = audio_set_endpoint_req(f, ctrl); + break; + + case USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT: + value = audio_get_endpoint_req(f, ctrl); + break; + + default: + ERROR(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + } + + /* respond with data transfer or status phase? */ + if (value >= 0) { + DBG(cdev, "audio req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + req->zero = 0; + req->length = value; + value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (value < 0) + ERROR(cdev, "audio response on err %d\n", value); + } + + /* device either stalls (value < 0) or reports success */ + return value; +} + +static int f_audio_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_gadget *gadget = cdev->gadget; + struct device *dev = &gadget->dev; + struct f_uac1 *uac1 = func_to_uac1(f); + int ret = 0; + + /* No i/f has more than 2 alt settings */ + if (alt > 1) { + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + return -EINVAL; + } + + if (intf == uac1->ac_intf) { + /* Control I/f has only 1 AltSetting - 0 */ + if (alt) { + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + return -EINVAL; + } + return 0; + } + + if (intf == uac1->as_out_intf) { + uac1->as_out_alt = alt; + + if (alt) + ret = u_audio_start_capture(&uac1->g_audio); + else + u_audio_stop_capture(&uac1->g_audio); + } else if (intf == uac1->as_in_intf) { + uac1->as_in_alt = alt; + + if (alt) + ret = u_audio_start_playback(&uac1->g_audio); + else + u_audio_stop_playback(&uac1->g_audio); + } else { + dev_err(dev, "%s:%d Error!\n", __func__, __LINE__); + return -EINVAL; + } + + return ret; +} + +static int f_audio_get_alt(struct usb_function *f, unsigned intf) +{ + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_gadget *gadget = cdev->gadget; + struct device *dev = &gadget->dev; + struct f_uac1 *uac1 = func_to_uac1(f); + + if (intf == uac1->ac_intf) + return uac1->ac_alt; + else if (intf == uac1->as_out_intf) + return uac1->as_out_alt; + else if (intf == uac1->as_in_intf) + return uac1->as_in_alt; + else + dev_err(dev, "%s:%d Invalid Interface %d!\n", + __func__, __LINE__, intf); + + return -EINVAL; +} + + +static void f_audio_disable(struct usb_function *f) +{ + struct f_uac1 *uac1 = func_to_uac1(f); + + uac1->as_out_alt = 0; + uac1->as_in_alt = 0; + + u_audio_stop_capture(&uac1->g_audio); +} + +/*-------------------------------------------------------------------------*/ + +/* audio function driver setup/binding */ +static int f_audio_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct usb_gadget *gadget = cdev->gadget; + struct f_uac1 *uac1 = func_to_uac1(f); + struct g_audio *audio = func_to_g_audio(f); + struct f_uac1_opts *audio_opts; + struct usb_ep *ep = NULL; + struct usb_string *us; + u8 *sam_freq; + int rate; + int status; + + audio_opts = container_of(f->fi, struct f_uac1_opts, func_inst); + + us = usb_gstrings_attach(cdev, uac1_strings, ARRAY_SIZE(strings_uac1)); + if (IS_ERR(us)) + return PTR_ERR(us); + ac_interface_desc.iInterface = us[STR_AC_IF].id; + usb_out_it_desc.iTerminal = us[STR_USB_OUT_IT].id; + usb_out_it_desc.iChannelNames = us[STR_USB_OUT_IT_CH_NAMES].id; + io_out_ot_desc.iTerminal = us[STR_IO_OUT_OT].id; + as_out_interface_alt_0_desc.iInterface = us[STR_AS_OUT_IF_ALT0].id; + as_out_interface_alt_1_desc.iInterface = us[STR_AS_OUT_IF_ALT1].id; + io_in_it_desc.iTerminal = us[STR_IO_IN_IT].id; + io_in_it_desc.iChannelNames = us[STR_IO_IN_IT_CH_NAMES].id; + usb_in_ot_desc.iTerminal = us[STR_USB_IN_OT].id; + as_in_interface_alt_0_desc.iInterface = us[STR_AS_IN_IF_ALT0].id; + as_in_interface_alt_1_desc.iInterface = us[STR_AS_IN_IF_ALT1].id; + + /* Set channel numbers */ + usb_out_it_desc.bNrChannels = num_channels(audio_opts->c_chmask); + usb_out_it_desc.wChannelConfig = cpu_to_le16(audio_opts->c_chmask); + as_out_type_i_desc.bNrChannels = num_channels(audio_opts->c_chmask); + as_out_type_i_desc.bSubframeSize = audio_opts->c_ssize; + as_out_type_i_desc.bBitResolution = audio_opts->c_ssize * 8; + io_in_it_desc.bNrChannels = num_channels(audio_opts->p_chmask); + io_in_it_desc.wChannelConfig = cpu_to_le16(audio_opts->p_chmask); + as_in_type_i_desc.bNrChannels = num_channels(audio_opts->p_chmask); + as_in_type_i_desc.bSubframeSize = audio_opts->p_ssize; + as_in_type_i_desc.bBitResolution = audio_opts->p_ssize * 8; + + /* Set sample rates */ + rate = audio_opts->c_srate; + sam_freq = as_out_type_i_desc.tSamFreq[0]; + memcpy(sam_freq, &rate, 3); + rate = audio_opts->p_srate; + sam_freq = as_in_type_i_desc.tSamFreq[0]; + memcpy(sam_freq, &rate, 3); + + /* allocate instance-specific interface IDs, and patch descriptors */ + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + ac_interface_desc.bInterfaceNumber = status; + uac1->ac_intf = status; + uac1->ac_alt = 0; + + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + as_out_interface_alt_0_desc.bInterfaceNumber = status; + as_out_interface_alt_1_desc.bInterfaceNumber = status; + uac1->as_out_intf = status; + uac1->as_out_alt = 0; + + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + as_in_interface_alt_0_desc.bInterfaceNumber = status; + as_in_interface_alt_1_desc.bInterfaceNumber = status; + uac1->as_in_intf = status; + uac1->as_in_alt = 0; + + audio->gadget = gadget; + + status = -ENODEV; + + /* allocate instance-specific endpoints */ + ep = usb_ep_autoconfig(cdev->gadget, &as_out_ep_desc); + if (!ep) + goto fail; + audio->out_ep = ep; + audio->out_ep->desc = &as_out_ep_desc; + + ep = usb_ep_autoconfig(cdev->gadget, &as_in_ep_desc); + if (!ep) + goto fail; + audio->in_ep = ep; + audio->in_ep->desc = &as_in_ep_desc; + + /* copy descriptors, and track endpoint copies */ + status = usb_assign_descriptors(f, f_audio_desc, f_audio_desc, NULL, + NULL); + if (status) + goto fail; + + audio->out_ep_maxpsize = as_out_ep_desc.wMaxPacketSize; + audio->in_ep_maxpsize = as_in_ep_desc.wMaxPacketSize; + audio->params.c_chmask = audio_opts->c_chmask; + audio->params.c_srate = audio_opts->c_srate; + audio->params.c_ssize = audio_opts->c_ssize; + audio->params.p_chmask = audio_opts->p_chmask; + audio->params.p_srate = audio_opts->p_srate; + audio->params.p_ssize = audio_opts->p_ssize; + audio->params.req_number = audio_opts->req_number; + + status = g_audio_setup(audio, "UAC1_PCM", "UAC1_Gadget"); + if (status) + goto err_card_register; + + return 0; + +err_card_register: + usb_free_all_descriptors(f); +fail: + return status; +} + +/*-------------------------------------------------------------------------*/ + +static inline struct f_uac1_opts *to_f_uac1_opts(struct config_item *item) +{ + return container_of(to_config_group(item), struct f_uac1_opts, + func_inst.group); +} + +static void f_uac1_attr_release(struct config_item *item) +{ + struct f_uac1_opts *opts = to_f_uac1_opts(item); + + usb_put_function_instance(&opts->func_inst); +} + +static struct configfs_item_operations f_uac1_item_ops = { + .release = f_uac1_attr_release, +}; + +#define UAC1_ATTRIBUTE(name) \ +static ssize_t f_uac1_opts_##name##_show( \ + struct config_item *item, \ + char *page) \ +{ \ + struct f_uac1_opts *opts = to_f_uac1_opts(item); \ + int result; \ + \ + mutex_lock(&opts->lock); \ + result = sprintf(page, "%u\n", opts->name); \ + mutex_unlock(&opts->lock); \ + \ + return result; \ +} \ + \ +static ssize_t f_uac1_opts_##name##_store( \ + struct config_item *item, \ + const char *page, size_t len) \ +{ \ + struct f_uac1_opts *opts = to_f_uac1_opts(item); \ + int ret; \ + u32 num; \ + \ + mutex_lock(&opts->lock); \ + if (opts->refcnt) { \ + ret = -EBUSY; \ + goto end; \ + } \ + \ + ret = kstrtou32(page, 0, &num); \ + if (ret) \ + goto end; \ + \ + opts->name = num; \ + ret = len; \ + \ +end: \ + mutex_unlock(&opts->lock); \ + return ret; \ +} \ + \ +CONFIGFS_ATTR(f_uac1_opts_, name) + +UAC1_ATTRIBUTE(c_chmask); +UAC1_ATTRIBUTE(c_srate); +UAC1_ATTRIBUTE(c_ssize); +UAC1_ATTRIBUTE(p_chmask); +UAC1_ATTRIBUTE(p_srate); +UAC1_ATTRIBUTE(p_ssize); +UAC1_ATTRIBUTE(req_number); + +static struct configfs_attribute *f_uac1_attrs[] = { + &f_uac1_opts_attr_c_chmask, + &f_uac1_opts_attr_c_srate, + &f_uac1_opts_attr_c_ssize, + &f_uac1_opts_attr_p_chmask, + &f_uac1_opts_attr_p_srate, + &f_uac1_opts_attr_p_ssize, + &f_uac1_opts_attr_req_number, + NULL, +}; + +static struct config_item_type f_uac1_func_type = { + .ct_item_ops = &f_uac1_item_ops, + .ct_attrs = f_uac1_attrs, + .ct_owner = THIS_MODULE, +}; + +static void f_audio_free_inst(struct usb_function_instance *f) +{ + struct f_uac1_opts *opts; + + opts = container_of(f, struct f_uac1_opts, func_inst); + kfree(opts); +} + +static struct usb_function_instance *f_audio_alloc_inst(void) +{ + struct f_uac1_opts *opts; + + opts = kzalloc(sizeof(*opts), GFP_KERNEL); + if (!opts) + return ERR_PTR(-ENOMEM); + + mutex_init(&opts->lock); + opts->func_inst.free_func_inst = f_audio_free_inst; + + config_group_init_type_name(&opts->func_inst.group, "", + &f_uac1_func_type); + + opts->c_chmask = UAC1_DEF_CCHMASK; + opts->c_srate = UAC1_DEF_CSRATE; + opts->c_ssize = UAC1_DEF_CSSIZE; + opts->p_chmask = UAC1_DEF_PCHMASK; + opts->p_srate = UAC1_DEF_PSRATE; + opts->p_ssize = UAC1_DEF_PSSIZE; + opts->req_number = UAC1_DEF_REQ_NUM; + return &opts->func_inst; +} + +static void f_audio_free(struct usb_function *f) +{ + struct g_audio *audio; + struct f_uac1_opts *opts; + + audio = func_to_g_audio(f); + opts = container_of(f->fi, struct f_uac1_opts, func_inst); + kfree(audio); + mutex_lock(&opts->lock); + --opts->refcnt; + mutex_unlock(&opts->lock); +} + +static void f_audio_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct g_audio *audio = func_to_g_audio(f); + + g_audio_cleanup(audio); + usb_free_all_descriptors(f); + + audio->gadget = NULL; +} + +static struct usb_function *f_audio_alloc(struct usb_function_instance *fi) +{ + struct f_uac1 *uac1; + struct f_uac1_opts *opts; + + /* allocate and initialize one new instance */ + uac1 = kzalloc(sizeof(*uac1), GFP_KERNEL); + if (!uac1) + return ERR_PTR(-ENOMEM); + + opts = container_of(fi, struct f_uac1_opts, func_inst); + mutex_lock(&opts->lock); + ++opts->refcnt; + mutex_unlock(&opts->lock); + + uac1->g_audio.func.name = "uac1_func"; + uac1->g_audio.func.bind = f_audio_bind; + uac1->g_audio.func.unbind = f_audio_unbind; + uac1->g_audio.func.set_alt = f_audio_set_alt; + uac1->g_audio.func.get_alt = f_audio_get_alt; + uac1->g_audio.func.setup = f_audio_setup; + uac1->g_audio.func.disable = f_audio_disable; + uac1->g_audio.func.free_func = f_audio_free; + + return &uac1->g_audio.func; +} + +DECLARE_USB_FUNCTION_INIT(uac1, f_audio_alloc_inst, f_audio_alloc); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ruslan Bilovol"); diff --git a/drivers/usb/gadget/function/u_uac1.h b/drivers/usb/gadget/function/u_uac1.h new file mode 100644 index 000000000000..6f188fd8633f --- /dev/null +++ b/drivers/usb/gadget/function/u_uac1.h @@ -0,0 +1,41 @@ +/* + * u_uac1.h - Utility definitions for UAC1 function + * + * Copyright (C) 2016 Ruslan Bilovol + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __U_UAC1_H +#define __U_UAC1_H + +#include + +#define UAC1_OUT_EP_MAX_PACKET_SIZE 200 +#define UAC1_DEF_CCHMASK 0x3 +#define UAC1_DEF_CSRATE 48000 +#define UAC1_DEF_CSSIZE 2 +#define UAC1_DEF_PCHMASK 0x3 +#define UAC1_DEF_PSRATE 48000 +#define UAC1_DEF_PSSIZE 2 +#define UAC1_DEF_REQ_NUM 2 + + +struct f_uac1_opts { + struct usb_function_instance func_inst; + int c_chmask; + int c_srate; + int c_ssize; + int p_chmask; + int p_srate; + int p_ssize; + int req_number; + unsigned bound:1; + + struct mutex lock; + int refcnt; +}; + +#endif /* __U_UAC1_H */ diff --git a/drivers/usb/gadget/legacy/Kconfig b/drivers/usb/gadget/legacy/Kconfig index 87bacb638a9c..a12fb459dbd9 100644 --- a/drivers/usb/gadget/legacy/Kconfig +++ b/drivers/usb/gadget/legacy/Kconfig @@ -54,9 +54,10 @@ config USB_AUDIO depends on SND select USB_LIBCOMPOSITE select SND_PCM - select USB_F_UAC1_LEGACY if GADGET_UAC1_LEGACY - select USB_F_UAC2 if !GADGET_UAC1_LEGACY - select USB_U_AUDIO if USB_F_UAC2 + select USB_F_UAC1 if (GADGET_UAC1 && !GADGET_UAC1_LEGACY) + select USB_F_UAC1_LEGACY if (GADGET_UAC1 && GADGET_UAC1_LEGACY) + select USB_F_UAC2 if !GADGET_UAC1 + select USB_U_AUDIO if (USB_F_UAC2 || USB_F_UAC1) help This Gadget Audio driver is compatible with USB Audio Class specification 2.0. It implements 1 AudioControl interface, @@ -73,11 +74,18 @@ config USB_AUDIO Say "y" to link the driver statically, or "m" to build a dynamically linked module called "g_audio". +config GADGET_UAC1 + bool "UAC 1.0" + depends on USB_AUDIO + help + If you instead want older USB Audio Class specification 1.0 support + with similar driver capabilities. + config GADGET_UAC1_LEGACY bool "UAC 1.0 (Legacy)" - depends on USB_AUDIO + depends on GADGET_UAC1 help - If you instead want older UAC Spec-1.0 driver that also has audio + If you instead want legacy UAC Spec-1.0 driver that also has audio paths hardwired to the Audio codec chip on-board and doesn't work without one. diff --git a/drivers/usb/gadget/legacy/audio.c b/drivers/usb/gadget/legacy/audio.c index bf5592a5b9e9..1f5cdbe162df 100644 --- a/drivers/usb/gadget/legacy/audio.c +++ b/drivers/usb/gadget/legacy/audio.c @@ -20,7 +20,7 @@ USB_GADGET_COMPOSITE_OPTIONS(); -#ifndef CONFIG_GADGET_UAC1_LEGACY +#ifndef CONFIG_GADGET_UAC1 #include "u_uac2.h" /* Playback(USB-IN) Default Stereo - Fl/Fr */ @@ -53,6 +53,39 @@ static int c_ssize = UAC2_DEF_CSSIZE; module_param(c_ssize, uint, S_IRUGO); MODULE_PARM_DESC(c_ssize, "Capture Sample Size(bytes)"); #else +#ifndef CONFIG_GADGET_UAC1_LEGACY +#include "u_uac1.h" + +/* Playback(USB-IN) Default Stereo - Fl/Fr */ +static int p_chmask = UAC1_DEF_PCHMASK; +module_param(p_chmask, uint, S_IRUGO); +MODULE_PARM_DESC(p_chmask, "Playback Channel Mask"); + +/* Playback Default 48 KHz */ +static int p_srate = UAC1_DEF_PSRATE; +module_param(p_srate, uint, S_IRUGO); +MODULE_PARM_DESC(p_srate, "Playback Sampling Rate"); + +/* Playback Default 16bits/sample */ +static int p_ssize = UAC1_DEF_PSSIZE; +module_param(p_ssize, uint, S_IRUGO); +MODULE_PARM_DESC(p_ssize, "Playback Sample Size(bytes)"); + +/* Capture(USB-OUT) Default Stereo - Fl/Fr */ +static int c_chmask = UAC1_DEF_CCHMASK; +module_param(c_chmask, uint, S_IRUGO); +MODULE_PARM_DESC(c_chmask, "Capture Channel Mask"); + +/* Capture Default 48 KHz */ +static int c_srate = UAC1_DEF_CSRATE; +module_param(c_srate, uint, S_IRUGO); +MODULE_PARM_DESC(c_srate, "Capture Sampling Rate"); + +/* Capture Default 16bits/sample */ +static int c_ssize = UAC1_DEF_CSSIZE; +module_param(c_ssize, uint, S_IRUGO); +MODULE_PARM_DESC(c_ssize, "Capture Sample Size(bytes)"); +#else /* CONFIG_GADGET_UAC1_LEGACY */ #include "u_uac1_legacy.h" static char *fn_play = FILE_PCM_PLAYBACK; @@ -78,6 +111,7 @@ MODULE_PARM_DESC(req_count, "ISO OUT endpoint request count"); static int audio_buf_size = UAC1_AUDIO_BUF_SIZE; module_param(audio_buf_size, int, S_IRUGO); MODULE_PARM_DESC(audio_buf_size, "Audio buffer size"); +#endif /* CONFIG_GADGET_UAC1_LEGACY */ #endif /* string IDs are assigned dynamically */ @@ -99,7 +133,7 @@ static struct usb_gadget_strings *audio_strings[] = { NULL, }; -#ifndef CONFIG_GADGET_UAC1_LEGACY +#ifndef CONFIG_GADGET_UAC1 static struct usb_function_instance *fi_uac2; static struct usb_function *f_uac2; #else @@ -164,7 +198,7 @@ static int audio_do_config(struct usb_configuration *c) c->bmAttributes |= USB_CONFIG_ATT_WAKEUP; } -#ifdef CONFIG_GADGET_UAC1_LEGACY +#ifdef CONFIG_GADGET_UAC1 f_uac1 = usb_get_function(fi_uac1); if (IS_ERR(f_uac1)) { status = PTR_ERR(f_uac1); @@ -204,24 +238,32 @@ static struct usb_configuration audio_config_driver = { static int audio_bind(struct usb_composite_dev *cdev) { -#ifndef CONFIG_GADGET_UAC1_LEGACY +#ifndef CONFIG_GADGET_UAC1 struct f_uac2_opts *uac2_opts; +#else +#ifndef CONFIG_GADGET_UAC1_LEGACY + struct f_uac1_opts *uac1_opts; #else struct f_uac1_legacy_opts *uac1_opts; +#endif #endif int status; -#ifndef CONFIG_GADGET_UAC1_LEGACY +#ifndef CONFIG_GADGET_UAC1 fi_uac2 = usb_get_function_instance("uac2"); if (IS_ERR(fi_uac2)) return PTR_ERR(fi_uac2); +#else +#ifndef CONFIG_GADGET_UAC1_LEGACY + fi_uac1 = usb_get_function_instance("uac1"); #else fi_uac1 = usb_get_function_instance("uac1_legacy"); +#endif if (IS_ERR(fi_uac1)) return PTR_ERR(fi_uac1); #endif -#ifndef CONFIG_GADGET_UAC1_LEGACY +#ifndef CONFIG_GADGET_UAC1 uac2_opts = container_of(fi_uac2, struct f_uac2_opts, func_inst); uac2_opts->p_chmask = p_chmask; uac2_opts->p_srate = p_srate; @@ -231,6 +273,16 @@ static int audio_bind(struct usb_composite_dev *cdev) uac2_opts->c_ssize = c_ssize; uac2_opts->req_number = UAC2_DEF_REQ_NUM; #else +#ifndef CONFIG_GADGET_UAC1_LEGACY + uac1_opts = container_of(fi_uac1, struct f_uac1_opts, func_inst); + uac1_opts->p_chmask = p_chmask; + uac1_opts->p_srate = p_srate; + uac1_opts->p_ssize = p_ssize; + uac1_opts->c_chmask = c_chmask; + uac1_opts->c_srate = c_srate; + uac1_opts->c_ssize = c_ssize; + uac1_opts->req_number = UAC1_DEF_REQ_NUM; +#else /* CONFIG_GADGET_UAC1_LEGACY */ uac1_opts = container_of(fi_uac1, struct f_uac1_legacy_opts, func_inst); uac1_opts->fn_play = fn_play; uac1_opts->fn_cap = fn_cap; @@ -238,6 +290,7 @@ static int audio_bind(struct usb_composite_dev *cdev) uac1_opts->req_buf_size = req_buf_size; uac1_opts->req_count = req_count; uac1_opts->audio_buf_size = audio_buf_size; +#endif /* CONFIG_GADGET_UAC1_LEGACY */ #endif status = usb_string_ids_tab(cdev, strings_dev); @@ -269,7 +322,7 @@ fail_otg_desc: kfree(otg_desc[0]); otg_desc[0] = NULL; fail: -#ifndef CONFIG_GADGET_UAC1_LEGACY +#ifndef CONFIG_GADGET_UAC1 usb_put_function_instance(fi_uac2); #else usb_put_function_instance(fi_uac1); @@ -279,7 +332,7 @@ fail: static int audio_unbind(struct usb_composite_dev *cdev) { -#ifdef CONFIG_GADGET_UAC1_LEGACY +#ifdef CONFIG_GADGET_UAC1 if (!IS_ERR_OR_NULL(f_uac1)) usb_put_function(f_uac1); if (!IS_ERR_OR_NULL(fi_uac1)) -- cgit v1.2.3 From a70df14602b33f645d1eb69aad536ec29457a006 Mon Sep 17 00:00:00 2001 From: Alexandre Bailon Date: Fri, 16 Jun 2017 10:40:54 -0500 Subject: usb: musb: musb_cppi41: Defer probe only if DMA is not ready If dma_request_slave_channel() failed to return a channel, then the driver will print an error and request to defer probe, regardless of the cause of the failure. Defer if the DMA is not ready yet otherwise print an error. Signed-off-by: Alexandre Bailon Reviewed-by: Johan Hovold Signed-off-by: Bin Liu Signed-off-by: Greg Kroah-Hartman --- drivers/usb/musb/musb_cppi41.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/usb/musb/musb_cppi41.c b/drivers/usb/musb/musb_cppi41.c index e7c8b1b8bf22..ba255280a624 100644 --- a/drivers/usb/musb/musb_cppi41.c +++ b/drivers/usb/musb/musb_cppi41.c @@ -673,12 +673,15 @@ static int cppi41_dma_controller_start(struct cppi41_dma_controller *controller) musb_dma->status = MUSB_DMA_STATUS_FREE; musb_dma->max_len = SZ_4M; - dc = dma_request_slave_channel(dev->parent, str); - if (!dc) { - dev_err(dev, "Failed to request %s.\n", str); - ret = -EPROBE_DEFER; + dc = dma_request_chan(dev->parent, str); + if (IS_ERR(dc)) { + ret = PTR_ERR(dc); + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to request %s: %d.\n", + str, ret); goto err; } + cppi41_channel->dc = dc; } return 0; -- cgit v1.2.3 From 9816c09e2c889dfc962f142805eb03fba32debb3 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 16 Jun 2017 10:40:55 -0500 Subject: dmaengine: omap-dma: port_window support correction for both direction When the port_window support was verified it was done on setup where only the MEM_TO_DEV direction was enabled. This got un-noticed and thus only this direction worked. Now that I have managed to get a setup to verify both direction it turned out that the setup was incorrect: omap_desc members are settings for the slave port while the omap_sg members apply to the memory side of the sDMA setup. Fixes: 527a27591312 ("dmaengine: omap-dma: Fix the port_window support") Signed-off-by: Peter Ujfalusi Cc: Russell King Cc: dmaengine@vger.kernel.org Cc: dan.j.williams@intel.com Cc: vinod.koul@intel.com Tested-by: Tony Lindgren Acked-by: Vinod Koul Signed-off-by: Bin Liu Signed-off-by: Greg Kroah-Hartman --- drivers/dma/omap-dma.c | 39 +++++++++++++++------------------------ 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/drivers/dma/omap-dma.c b/drivers/dma/omap-dma.c index daf479cce691..8c1665c8fe33 100644 --- a/drivers/dma/omap-dma.c +++ b/drivers/dma/omap-dma.c @@ -916,12 +916,6 @@ static struct dma_async_tx_descriptor *omap_dma_prep_slave_sg( return NULL; } - /* When the port_window is used, one frame must cover the window */ - if (port_window) { - burst = port_window; - port_window_bytes = port_window * es_bytes[es]; - } - /* Now allocate and setup the descriptor. */ d = kzalloc(sizeof(*d) + sglen * sizeof(d->sg[0]), GFP_ATOMIC); if (!d) @@ -931,6 +925,21 @@ static struct dma_async_tx_descriptor *omap_dma_prep_slave_sg( d->dev_addr = dev_addr; d->es = es; + /* When the port_window is used, one frame must cover the window */ + if (port_window) { + burst = port_window; + port_window_bytes = port_window * es_bytes[es]; + + d->ei = 1; + /* + * One frame covers the port_window and by configure + * the source frame index to be -1 * (port_window - 1) + * we instruct the sDMA that after a frame is processed + * it should move back to the start of the window. + */ + d->fi = -(port_window_bytes - 1); + } + d->ccr = c->ccr | CCR_SYNC_FRAME; if (dir == DMA_DEV_TO_MEM) { d->csdp = CSDP_DST_BURST_64 | CSDP_DST_PACKED; @@ -955,14 +964,6 @@ static struct dma_async_tx_descriptor *omap_dma_prep_slave_sg( d->ccr |= CCR_SRC_AMODE_POSTINC; if (port_window) { d->ccr |= CCR_DST_AMODE_DBLIDX; - d->ei = 1; - /* - * One frame covers the port_window and by configure - * the source frame index to be -1 * (port_window - 1) - * we instruct the sDMA that after a frame is processed - * it should move back to the start of the window. - */ - d->fi = -(port_window_bytes - 1); if (port_window_bytes >= 64) d->csdp |= CSDP_DST_BURST_64; @@ -1018,16 +1019,6 @@ static struct dma_async_tx_descriptor *omap_dma_prep_slave_sg( osg->addr = sg_dma_address(sgent); osg->en = en; osg->fn = sg_dma_len(sgent) / frame_bytes; - if (port_window && dir == DMA_DEV_TO_MEM) { - osg->ei = 1; - /* - * One frame covers the port_window and by configure - * the source frame index to be -1 * (port_window - 1) - * we instruct the sDMA that after a frame is processed - * it should move back to the start of the window. - */ - osg->fi = -(port_window_bytes - 1); - } if (d->using_ll) { osg->t2_desc = dma_pool_alloc(od->desc_pool, GFP_ATOMIC, -- cgit v1.2.3 From 1fa07c370bba23aa596ca143b90b64cd35918ec2 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 16 Jun 2017 10:40:56 -0500 Subject: usb: musb: Add quirk to avoid skb reserve in gadget mode For tusb6010 the DMA functionality only possible if the buffer is 32bit aligned (SYNC access to FIFO) since with ASYNC access the TX/RX offset registers will corrupt eventually. The MUSB_G_NO_SKB_RESERVE will set the quirk_avoids_skb_reserve flag in usb_gadget struct to provide correctly aligned buffer. Signed-off-by: Peter Ujfalusi Tested-by: Tony Lindgren Signed-off-by: Bin Liu Signed-off-by: Greg Kroah-Hartman --- drivers/usb/musb/musb_core.c | 3 +++ drivers/usb/musb/musb_core.h | 1 + 2 files changed, 4 insertions(+) diff --git a/drivers/usb/musb/musb_core.c b/drivers/usb/musb/musb_core.c index 870da18f5077..87cbd56cc761 100644 --- a/drivers/usb/musb/musb_core.c +++ b/drivers/usb/musb/musb_core.c @@ -2224,6 +2224,9 @@ musb_init_controller(struct device *dev, int nIrq, void __iomem *ctrl) musb->io.ep_select = musb_flat_ep_select; } + if (musb->io.quirks & MUSB_G_NO_SKB_RESERVE) + musb->g.quirk_avoids_skb_reserve = 1; + /* At least tusb6010 has its own offsets */ if (musb->ops->ep_offset) musb->io.ep_offset = musb->ops->ep_offset; diff --git a/drivers/usb/musb/musb_core.h b/drivers/usb/musb/musb_core.h index 3e98d4268a64..9f22c5b8ce37 100644 --- a/drivers/usb/musb/musb_core.h +++ b/drivers/usb/musb/musb_core.h @@ -172,6 +172,7 @@ struct musb_io; */ struct musb_platform_ops { +#define MUSB_G_NO_SKB_RESERVE BIT(9) #define MUSB_DA8XX BIT(8) #define MUSB_PRESERVE_SESSION BIT(7) #define MUSB_DMA_UX500 BIT(6) -- cgit v1.2.3 From 0efc1356393a5149287c51addc4242ce51aa867c Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 16 Jun 2017 10:40:57 -0500 Subject: usb: musb: tusb6010: Add MUSB_G_NO_SKB_RESERVE to quirks When using the g_ncm for networking this flag will make sure that the buffer is aligned to 32bit so the DMA can be used to offload the data movement. Signed-off-by: Peter Ujfalusi Tested-by: Tony Lindgren Signed-off-by: Bin Liu Signed-off-by: Greg Kroah-Hartman --- drivers/usb/musb/tusb6010.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/usb/musb/tusb6010.c b/drivers/usb/musb/tusb6010.c index e85cc8e4e7a9..4253bfb22043 100644 --- a/drivers/usb/musb/tusb6010.c +++ b/drivers/usb/musb/tusb6010.c @@ -1181,7 +1181,8 @@ static int tusb_musb_exit(struct musb *musb) } static const struct musb_platform_ops tusb_ops = { - .quirks = MUSB_DMA_TUSB_OMAP | MUSB_IN_TUSB, + .quirks = MUSB_DMA_TUSB_OMAP | MUSB_IN_TUSB | + MUSB_G_NO_SKB_RESERVE, .init = tusb_musb_init, .exit = tusb_musb_exit, -- cgit v1.2.3 From 3565b787fdf0ed52c94072b8f363a3b15b52d646 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 16 Jun 2017 10:40:58 -0500 Subject: usb: musb: tusb6010_omap: Use one musb_ep_select call in tusb_omap_dma_program Having one musb_ep_select() instead the two calls in if/else is the same thing, but makes the code a bit simpler to follow. Signed-off-by: Peter Ujfalusi Tested-by: Tony Lindgren Signed-off-by: Bin Liu Signed-off-by: Greg Kroah-Hartman --- drivers/usb/musb/tusb6010_omap.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/usb/musb/tusb6010_omap.c b/drivers/usb/musb/tusb6010_omap.c index 7870b37e0ea5..025b52e0b34d 100644 --- a/drivers/usb/musb/tusb6010_omap.c +++ b/drivers/usb/musb/tusb6010_omap.c @@ -368,15 +368,14 @@ static int tusb_omap_dma_program(struct dma_channel *channel, u16 packet_sz, /* * Prepare MUSB for DMA transfer */ + musb_ep_select(mbase, chdat->epnum); if (chdat->tx) { - musb_ep_select(mbase, chdat->epnum); csr = musb_readw(hw_ep->regs, MUSB_TXCSR); csr |= (MUSB_TXCSR_AUTOSET | MUSB_TXCSR_DMAENAB | MUSB_TXCSR_DMAMODE | MUSB_TXCSR_MODE); csr &= ~MUSB_TXCSR_P_UNDERRUN; musb_writew(hw_ep->regs, MUSB_TXCSR, csr); } else { - musb_ep_select(mbase, chdat->epnum); csr = musb_readw(hw_ep->regs, MUSB_RXCSR); csr |= MUSB_RXCSR_DMAENAB; csr &= ~(MUSB_RXCSR_AUTOCLEAR | MUSB_RXCSR_DMAMODE); -- cgit v1.2.3 From 1df9d9ec34e809ec227c2f13ec9d8cc479f22f5e Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 16 Jun 2017 10:40:59 -0500 Subject: usb: musb: tusb6010_omap: Create new struct for DMA data/parameters For the DMA we have ch (channel), dmareq and sync_dev parameters both within the tusb_omap_dma_ch and tusb_omap_dma struct. By creating a common struct the code can be simplified when selecting between the shared or multichannel DMA parameters. Signed-off-by: Peter Ujfalusi Tested-by: Tony Lindgren Signed-off-by: Bin Liu Signed-off-by: Greg Kroah-Hartman --- drivers/usb/musb/tusb6010_omap.c | 164 ++++++++++++++++++++------------------- 1 file changed, 85 insertions(+), 79 deletions(-) diff --git a/drivers/usb/musb/tusb6010_omap.c b/drivers/usb/musb/tusb6010_omap.c index 025b52e0b34d..c2ef8e3eb91e 100644 --- a/drivers/usb/musb/tusb6010_omap.c +++ b/drivers/usb/musb/tusb6010_omap.c @@ -31,6 +31,12 @@ #define OMAP242X_DMA_EXT_DMAREQ4 16 #define OMAP242X_DMA_EXT_DMAREQ5 64 +struct tusb_dma_data { + int ch; + s8 dmareq; + s8 sync_dev; +}; + struct tusb_omap_dma_ch { struct musb *musb; void __iomem *tbase; @@ -39,9 +45,7 @@ struct tusb_omap_dma_ch { u8 tx; struct musb_hw_ep *hw_ep; - int ch; - s8 dmareq; - s8 sync_dev; + struct tusb_dma_data dma_data; struct tusb_omap_dma *tusb_dma; @@ -58,9 +62,7 @@ struct tusb_omap_dma { struct dma_controller controller; void __iomem *tbase; - int ch; - s8 dmareq; - s8 sync_dev; + struct tusb_dma_data dma_data; unsigned multichannel:1; }; @@ -119,9 +121,9 @@ static void tusb_omap_dma_cb(int lch, u16 ch_status, void *data) spin_lock_irqsave(&musb->lock, flags); if (tusb_dma->multichannel) - ch = chdat->ch; + ch = chdat->dma_data.ch; else - ch = tusb_dma->ch; + ch = tusb_dma->dma_data.ch; if (ch_status != OMAP_DMA_BLOCK_IRQ) printk(KERN_ERR "TUSB DMA error status: %i\n", ch_status); @@ -140,8 +142,7 @@ static void tusb_omap_dma_cb(int lch, u16 ch_status, void *data) /* HW issue #10: XFR_SIZE may get corrupt on DMA (both async & sync) */ if (unlikely(remaining > chdat->transfer_len)) { dev_dbg(musb->controller, "Corrupt %s dma ch%i XFR_SIZE: 0x%08lx\n", - chdat->tx ? "tx" : "rx", chdat->ch, - remaining); + chdat->tx ? "tx" : "rx", ch, remaining); remaining = 0; } @@ -220,9 +221,7 @@ static int tusb_omap_dma_program(struct dma_channel *channel, u16 packet_sz, int src_burst, dst_burst; u16 csr; u32 psize; - int ch; - s8 dmareq; - s8 sync_dev; + struct tusb_dma_data *dma_data; if (unlikely(dma_addr & 0x1) || (len < 32) || (len > packet_sz)) return false; @@ -249,7 +248,7 @@ static int tusb_omap_dma_program(struct dma_channel *channel, u16 packet_sz, dma_remaining = TUSB_EP_CONFIG_XFR_SIZE(dma_remaining); if (dma_remaining) { dev_dbg(musb->controller, "Busy %s dma ch%i, not using: %08x\n", - chdat->tx ? "tx" : "rx", chdat->ch, + chdat->tx ? "tx" : "rx", chdat->dma_data.ch, dma_remaining); return false; } @@ -262,15 +261,15 @@ static int tusb_omap_dma_program(struct dma_channel *channel, u16 packet_sz, chdat->transfer_packet_sz = packet_sz; if (tusb_dma->multichannel) { - ch = chdat->ch; - dmareq = chdat->dmareq; - sync_dev = chdat->sync_dev; + dma_data = &chdat->dma_data; } else { + dma_data = &tusb_dma->dma_data; + if (tusb_omap_use_shared_dmareq(chdat) != 0) { dev_dbg(musb->controller, "could not get dma for ep%i\n", chdat->epnum); return false; } - if (tusb_dma->ch < 0) { + if (dma_data->ch < 0) { /* REVISIT: This should get blocked earlier, happens * with MSC ErrorRecoveryTest */ @@ -278,10 +277,7 @@ static int tusb_omap_dma_program(struct dma_channel *channel, u16 packet_sz, return false; } - ch = tusb_dma->ch; - dmareq = tusb_dma->dmareq; - sync_dev = tusb_dma->sync_dev; - omap_set_dma_callback(ch, tusb_omap_dma_cb, channel); + omap_set_dma_callback(dma_data->ch, tusb_omap_dma_cb, channel); } chdat->packet_sz = packet_sz; @@ -312,7 +308,7 @@ static int tusb_omap_dma_program(struct dma_channel *channel, u16 packet_sz, dev_dbg(musb->controller, "ep%i %s dma ch%i dma: %pad len: %u(%u) packet_sz: %i(%i)\n", chdat->epnum, chdat->tx ? "tx" : "rx", - ch, &dma_addr, chdat->transfer_len, len, + dma_data->ch, &dma_addr, chdat->transfer_len, len, chdat->transfer_packet_sz, packet_sz); /* @@ -329,7 +325,7 @@ static int tusb_omap_dma_program(struct dma_channel *channel, u16 packet_sz, dma_params.dst_ei = 1; dma_params.dst_fi = -31; /* Loop 32 byte window */ - dma_params.trigger = sync_dev; + dma_params.trigger = dma_data->sync_dev; dma_params.sync_mode = OMAP_DMA_SYNC_FRAME; dma_params.src_or_dst_synch = 0; /* Dest sync */ @@ -346,7 +342,7 @@ static int tusb_omap_dma_program(struct dma_channel *channel, u16 packet_sz, dma_params.dst_ei = 0; dma_params.dst_fi = 0; - dma_params.trigger = sync_dev; + dma_params.trigger = dma_data->sync_dev; dma_params.sync_mode = OMAP_DMA_SYNC_FRAME; dma_params.src_or_dst_synch = 1; /* Source sync */ @@ -360,10 +356,10 @@ static int tusb_omap_dma_program(struct dma_channel *channel, u16 packet_sz, ((dma_addr & 0x3) == 0) ? "sync" : "async", dma_params.src_start, dma_params.dst_start); - omap_set_dma_params(ch, &dma_params); - omap_set_dma_src_burst_mode(ch, src_burst); - omap_set_dma_dest_burst_mode(ch, dst_burst); - omap_set_dma_write_mode(ch, OMAP_DMA_WRITE_LAST_NON_POSTED); + omap_set_dma_params(dma_data->ch, &dma_params); + omap_set_dma_src_burst_mode(dma_data->ch, src_burst); + omap_set_dma_dest_burst_mode(dma_data->ch, dst_burst); + omap_set_dma_write_mode(dma_data->ch, OMAP_DMA_WRITE_LAST_NON_POSTED); /* * Prepare MUSB for DMA transfer @@ -386,7 +382,7 @@ static int tusb_omap_dma_program(struct dma_channel *channel, u16 packet_sz, /* * Start DMA transfer */ - omap_start_dma(ch); + omap_start_dma(dma_data->ch); if (chdat->tx) { /* Send transfer_packet_sz packets at a time */ @@ -415,16 +411,17 @@ static int tusb_omap_dma_abort(struct dma_channel *channel) { struct tusb_omap_dma_ch *chdat = to_chdat(channel); struct tusb_omap_dma *tusb_dma = chdat->tusb_dma; + struct tusb_dma_data *dma_data = &tusb_dma->dma_data; if (!tusb_dma->multichannel) { - if (tusb_dma->ch >= 0) { - omap_stop_dma(tusb_dma->ch); - omap_free_dma(tusb_dma->ch); - tusb_dma->ch = -1; + if (dma_data->ch >= 0) { + omap_stop_dma(dma_data->ch); + omap_free_dma(dma_data->ch); + dma_data->ch = -1; } - tusb_dma->dmareq = -1; - tusb_dma->sync_dev = -1; + dma_data->dmareq = -1; + dma_data->sync_dev = -1; } channel->status = MUSB_DMA_STATUS_FREE; @@ -462,8 +459,8 @@ static inline int tusb_omap_dma_allocate_dmareq(struct tusb_omap_dma_ch *chdat) reg |= ((1 << 4) << (dmareq_nr * 5)); musb_writel(chdat->tbase, TUSB_DMA_EP_MAP, reg); - chdat->dmareq = dmareq_nr; - chdat->sync_dev = sync_dev[chdat->dmareq]; + chdat->dma_data.dmareq = dmareq_nr; + chdat->dma_data.sync_dev = sync_dev[chdat->dma_data.dmareq]; return 0; } @@ -472,15 +469,15 @@ static inline void tusb_omap_dma_free_dmareq(struct tusb_omap_dma_ch *chdat) { u32 reg; - if (!chdat || chdat->dmareq < 0) + if (!chdat || chdat->dma_data.dmareq < 0) return; reg = musb_readl(chdat->tbase, TUSB_DMA_EP_MAP); - reg &= ~(0x1f << (chdat->dmareq * 5)); + reg &= ~(0x1f << (chdat->dma_data.dmareq * 5)); musb_writel(chdat->tbase, TUSB_DMA_EP_MAP, reg); - chdat->dmareq = -1; - chdat->sync_dev = -1; + chdat->dma_data.dmareq = -1; + chdat->dma_data.sync_dev = -1; } static struct dma_channel *dma_channel_pool[MAX_DMAREQ]; @@ -492,11 +489,13 @@ tusb_omap_dma_allocate(struct dma_controller *c, { int ret, i; const char *dev_name; + void *cb_data; struct tusb_omap_dma *tusb_dma; struct musb *musb; void __iomem *tbase; struct dma_channel *channel = NULL; struct tusb_omap_dma_ch *chdat = NULL; + struct tusb_dma_data *dma_data = NULL; u32 reg; tusb_dma = container_of(c, struct tusb_omap_dma, controller); @@ -529,56 +528,62 @@ tusb_omap_dma_allocate(struct dma_controller *c, if (!channel) return NULL; - if (tx) { - chdat->tx = 1; - dev_name = "TUSB transmit"; - } else { - chdat->tx = 0; - dev_name = "TUSB receive"; - } - chdat->musb = tusb_dma->controller.musb; chdat->tbase = tusb_dma->tbase; chdat->hw_ep = hw_ep; chdat->epnum = hw_ep->epnum; - chdat->dmareq = -1; chdat->completed_len = 0; chdat->tusb_dma = tusb_dma; + if (tx) + chdat->tx = 1; + else + chdat->tx = 0; channel->max_len = 0x7fffffff; channel->desired_mode = 0; channel->actual_len = 0; if (tusb_dma->multichannel) { + dma_data = &chdat->dma_data; ret = tusb_omap_dma_allocate_dmareq(chdat); if (ret != 0) goto free_dmareq; - ret = omap_request_dma(chdat->sync_dev, dev_name, - tusb_omap_dma_cb, channel, &chdat->ch); - if (ret != 0) - goto free_dmareq; - } else if (tusb_dma->ch == -1) { - tusb_dma->dmareq = 0; - tusb_dma->sync_dev = OMAP24XX_DMA_EXT_DMAREQ0; - + if (chdat->tx) + dev_name = "TUSB transmit"; + else + dev_name = "TUSB receive"; + cb_data = channel; + } else if (tusb_dma->dma_data.ch == -1) { + dma_data = &tusb_dma->dma_data; + dma_data->dmareq = 0; + dma_data->sync_dev = OMAP24XX_DMA_EXT_DMAREQ0; + + dev_name = "TUSB shared"; /* Callback data gets set later in the shared dmareq case */ - ret = omap_request_dma(tusb_dma->sync_dev, "TUSB shared", - tusb_omap_dma_cb, NULL, &tusb_dma->ch); + cb_data = NULL; + + chdat->dma_data.dmareq = -1; + chdat->dma_data.ch = -1; + chdat->dma_data.sync_dev = -1; + } + + if (dma_data) { + ret = omap_request_dma(dma_data->sync_dev, dev_name, + tusb_omap_dma_cb, cb_data, + &dma_data->ch); if (ret != 0) goto free_dmareq; - - chdat->dmareq = -1; - chdat->ch = -1; + } else { + /* Already allocated shared, single DMA channel. */ + dma_data = &tusb_dma->dma_data; } dev_dbg(musb->controller, "ep%i %s dma: %s dma%i dmareq%i sync%i\n", chdat->epnum, chdat->tx ? "tx" : "rx", - chdat->ch >= 0 ? "dedicated" : "shared", - chdat->ch >= 0 ? chdat->ch : tusb_dma->ch, - chdat->dmareq >= 0 ? chdat->dmareq : tusb_dma->dmareq, - chdat->sync_dev >= 0 ? chdat->sync_dev : tusb_dma->sync_dev); + chdat->dma_data.ch >= 0 ? "dedicated" : "shared", + dma_data->ch, dma_data->dmareq, dma_data->sync_dev); return channel; @@ -598,7 +603,8 @@ static void tusb_omap_dma_release(struct dma_channel *channel) void __iomem *tbase = musb->ctrl_base; u32 reg; - dev_dbg(musb->controller, "ep%i ch%i\n", chdat->epnum, chdat->ch); + dev_dbg(musb->controller, "ep%i ch%i\n", chdat->epnum, + chdat->dma_data.ch); reg = musb_readl(tbase, TUSB_DMA_INT_MASK); if (chdat->tx) @@ -616,13 +622,13 @@ static void tusb_omap_dma_release(struct dma_channel *channel) channel->status = MUSB_DMA_STATUS_UNKNOWN; - if (chdat->ch >= 0) { - omap_stop_dma(chdat->ch); - omap_free_dma(chdat->ch); - chdat->ch = -1; + if (chdat->dma_data.ch >= 0) { + omap_stop_dma(chdat->dma_data.ch); + omap_free_dma(chdat->dma_data.ch); + chdat->dma_data.ch = -1; } - if (chdat->dmareq >= 0) + if (chdat->dma_data.dmareq >= 0) tusb_omap_dma_free_dmareq(chdat); channel = NULL; @@ -642,8 +648,8 @@ void tusb_dma_controller_destroy(struct dma_controller *c) } } - if (tusb_dma && !tusb_dma->multichannel && tusb_dma->ch >= 0) - omap_free_dma(tusb_dma->ch); + if (tusb_dma && !tusb_dma->multichannel && tusb_dma->dma_data.ch >= 0) + omap_free_dma(tusb_dma->dma_data.ch); kfree(tusb_dma); } @@ -673,9 +679,9 @@ tusb_dma_controller_create(struct musb *musb, void __iomem *base) tusb_dma->controller.musb = musb; tusb_dma->tbase = musb->ctrl_base; - tusb_dma->ch = -1; - tusb_dma->dmareq = -1; - tusb_dma->sync_dev = -1; + tusb_dma->dma_data.ch = -1; + tusb_dma->dma_data.dmareq = -1; + tusb_dma->dma_data.sync_dev = -1; tusb_dma->controller.channel_alloc = tusb_omap_dma_allocate; tusb_dma->controller.channel_release = tusb_omap_dma_release; -- cgit v1.2.3 From 4cadc711cdc7a845cc5263c594ac3882027beadf Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 16 Jun 2017 10:41:00 -0500 Subject: usb: musb: tusb6010_omap: Allocate DMA channels upfront Instead of requesting the DMA channel in tusb_omap_dma_allocate() do it when the controller is created and in runtime work from the DMA channel pool. This change is needed for the DMAengine conversion of the driver since the tusb_omap_dma_allocate() is called in interrupt context which might lead to lock within the DMAengine API when requesting channel. Signed-off-by: Peter Ujfalusi Tested-by: Tony Lindgren Signed-off-by: Bin Liu Signed-off-by: Greg Kroah-Hartman --- drivers/usb/musb/tusb6010_omap.c | 185 +++++++++++++++++++-------------------- 1 file changed, 92 insertions(+), 93 deletions(-) diff --git a/drivers/usb/musb/tusb6010_omap.c b/drivers/usb/musb/tusb6010_omap.c index c2ef8e3eb91e..f8671110168b 100644 --- a/drivers/usb/musb/tusb6010_omap.c +++ b/drivers/usb/musb/tusb6010_omap.c @@ -45,7 +45,7 @@ struct tusb_omap_dma_ch { u8 tx; struct musb_hw_ep *hw_ep; - struct tusb_dma_data dma_data; + struct tusb_dma_data *dma_data; struct tusb_omap_dma *tusb_dma; @@ -62,7 +62,7 @@ struct tusb_omap_dma { struct dma_controller controller; void __iomem *tbase; - struct tusb_dma_data dma_data; + struct tusb_dma_data dma_pool[MAX_DMAREQ]; unsigned multichannel:1; }; @@ -120,10 +120,7 @@ static void tusb_omap_dma_cb(int lch, u16 ch_status, void *data) spin_lock_irqsave(&musb->lock, flags); - if (tusb_dma->multichannel) - ch = chdat->dma_data.ch; - else - ch = tusb_dma->dma_data.ch; + ch = chdat->dma_data->ch; if (ch_status != OMAP_DMA_BLOCK_IRQ) printk(KERN_ERR "TUSB DMA error status: %i\n", ch_status); @@ -248,7 +245,8 @@ static int tusb_omap_dma_program(struct dma_channel *channel, u16 packet_sz, dma_remaining = TUSB_EP_CONFIG_XFR_SIZE(dma_remaining); if (dma_remaining) { dev_dbg(musb->controller, "Busy %s dma ch%i, not using: %08x\n", - chdat->tx ? "tx" : "rx", chdat->dma_data.ch, + chdat->tx ? "tx" : "rx", + chdat->dma_data ? chdat->dma_data->ch : -1, dma_remaining); return false; } @@ -260,11 +258,8 @@ static int tusb_omap_dma_program(struct dma_channel *channel, u16 packet_sz, else chdat->transfer_packet_sz = packet_sz; - if (tusb_dma->multichannel) { - dma_data = &chdat->dma_data; - } else { - dma_data = &tusb_dma->dma_data; - + dma_data = chdat->dma_data; + if (!tusb_dma->multichannel) { if (tusb_omap_use_shared_dmareq(chdat) != 0) { dev_dbg(musb->controller, "could not get dma for ep%i\n", chdat->epnum); return false; @@ -276,10 +271,10 @@ static int tusb_omap_dma_program(struct dma_channel *channel, u16 packet_sz, WARN_ON(1); return false; } - - omap_set_dma_callback(dma_data->ch, tusb_omap_dma_cb, channel); } + omap_set_dma_callback(dma_data->ch, tusb_omap_dma_cb, channel); + chdat->packet_sz = packet_sz; chdat->len = len; channel->actual_len = 0; @@ -410,19 +405,9 @@ static int tusb_omap_dma_program(struct dma_channel *channel, u16 packet_sz, static int tusb_omap_dma_abort(struct dma_channel *channel) { struct tusb_omap_dma_ch *chdat = to_chdat(channel); - struct tusb_omap_dma *tusb_dma = chdat->tusb_dma; - struct tusb_dma_data *dma_data = &tusb_dma->dma_data; - if (!tusb_dma->multichannel) { - if (dma_data->ch >= 0) { - omap_stop_dma(dma_data->ch); - omap_free_dma(dma_data->ch); - dma_data->ch = -1; - } - - dma_data->dmareq = -1; - dma_data->sync_dev = -1; - } + if (chdat->dma_data) + omap_stop_dma(chdat->dma_data->ch); channel->status = MUSB_DMA_STATUS_FREE; @@ -434,15 +419,6 @@ static inline int tusb_omap_dma_allocate_dmareq(struct tusb_omap_dma_ch *chdat) u32 reg = musb_readl(chdat->tbase, TUSB_DMA_EP_MAP); int i, dmareq_nr = -1; - const int sync_dev[6] = { - OMAP24XX_DMA_EXT_DMAREQ0, - OMAP24XX_DMA_EXT_DMAREQ1, - OMAP242X_DMA_EXT_DMAREQ2, - OMAP242X_DMA_EXT_DMAREQ3, - OMAP242X_DMA_EXT_DMAREQ4, - OMAP242X_DMA_EXT_DMAREQ5, - }; - for (i = 0; i < MAX_DMAREQ; i++) { int cur = (reg & (0xf << (i * 5))) >> (i * 5); if (cur == 0) { @@ -459,8 +435,7 @@ static inline int tusb_omap_dma_allocate_dmareq(struct tusb_omap_dma_ch *chdat) reg |= ((1 << 4) << (dmareq_nr * 5)); musb_writel(chdat->tbase, TUSB_DMA_EP_MAP, reg); - chdat->dma_data.dmareq = dmareq_nr; - chdat->dma_data.sync_dev = sync_dev[chdat->dma_data.dmareq]; + chdat->dma_data = &chdat->tusb_dma->dma_pool[dmareq_nr]; return 0; } @@ -469,15 +444,14 @@ static inline void tusb_omap_dma_free_dmareq(struct tusb_omap_dma_ch *chdat) { u32 reg; - if (!chdat || chdat->dma_data.dmareq < 0) + if (!chdat || !chdat->dma_data || chdat->dma_data->dmareq < 0) return; reg = musb_readl(chdat->tbase, TUSB_DMA_EP_MAP); - reg &= ~(0x1f << (chdat->dma_data.dmareq * 5)); + reg &= ~(0x1f << (chdat->dma_data->dmareq * 5)); musb_writel(chdat->tbase, TUSB_DMA_EP_MAP, reg); - chdat->dma_data.dmareq = -1; - chdat->dma_data.sync_dev = -1; + chdat->dma_data = NULL; } static struct dma_channel *dma_channel_pool[MAX_DMAREQ]; @@ -488,8 +462,6 @@ tusb_omap_dma_allocate(struct dma_controller *c, u8 tx) { int ret, i; - const char *dev_name; - void *cb_data; struct tusb_omap_dma *tusb_dma; struct musb *musb; void __iomem *tbase; @@ -543,46 +515,22 @@ tusb_omap_dma_allocate(struct dma_controller *c, channel->desired_mode = 0; channel->actual_len = 0; - if (tusb_dma->multichannel) { - dma_data = &chdat->dma_data; - ret = tusb_omap_dma_allocate_dmareq(chdat); - if (ret != 0) - goto free_dmareq; - - if (chdat->tx) - dev_name = "TUSB transmit"; - else - dev_name = "TUSB receive"; - cb_data = channel; - } else if (tusb_dma->dma_data.ch == -1) { - dma_data = &tusb_dma->dma_data; - dma_data->dmareq = 0; - dma_data->sync_dev = OMAP24XX_DMA_EXT_DMAREQ0; - - dev_name = "TUSB shared"; - /* Callback data gets set later in the shared dmareq case */ - cb_data = NULL; - - chdat->dma_data.dmareq = -1; - chdat->dma_data.ch = -1; - chdat->dma_data.sync_dev = -1; + if (!chdat->dma_data) { + if (tusb_dma->multichannel) { + ret = tusb_omap_dma_allocate_dmareq(chdat); + if (ret != 0) + goto free_dmareq; + } else { + chdat->dma_data = &tusb_dma->dma_pool[0]; + } } - if (dma_data) { - ret = omap_request_dma(dma_data->sync_dev, dev_name, - tusb_omap_dma_cb, cb_data, - &dma_data->ch); - if (ret != 0) - goto free_dmareq; - } else { - /* Already allocated shared, single DMA channel. */ - dma_data = &tusb_dma->dma_data; - } + dma_data = chdat->dma_data; dev_dbg(musb->controller, "ep%i %s dma: %s dma%i dmareq%i sync%i\n", chdat->epnum, chdat->tx ? "tx" : "rx", - chdat->dma_data.ch >= 0 ? "dedicated" : "shared", + tusb_dma->multichannel ? "shared" : "dedicated", dma_data->ch, dma_data->dmareq, dma_data->sync_dev); return channel; @@ -604,7 +552,7 @@ static void tusb_omap_dma_release(struct dma_channel *channel) u32 reg; dev_dbg(musb->controller, "ep%i ch%i\n", chdat->epnum, - chdat->dma_data.ch); + chdat->dma_data->ch); reg = musb_readl(tbase, TUSB_DMA_INT_MASK); if (chdat->tx) @@ -622,14 +570,8 @@ static void tusb_omap_dma_release(struct dma_channel *channel) channel->status = MUSB_DMA_STATUS_UNKNOWN; - if (chdat->dma_data.ch >= 0) { - omap_stop_dma(chdat->dma_data.ch); - omap_free_dma(chdat->dma_data.ch); - chdat->dma_data.ch = -1; - } - - if (chdat->dma_data.dmareq >= 0) - tusb_omap_dma_free_dmareq(chdat); + omap_stop_dma(chdat->dma_data->ch); + tusb_omap_dma_free_dmareq(chdat); channel = NULL; } @@ -646,15 +588,73 @@ void tusb_dma_controller_destroy(struct dma_controller *c) kfree(ch->private_data); kfree(ch); } - } - if (tusb_dma && !tusb_dma->multichannel && tusb_dma->dma_data.ch >= 0) - omap_free_dma(tusb_dma->dma_data.ch); + /* Free up the DMA channels */ + if (tusb_dma && tusb_dma->dma_pool[i].ch >= 0) + omap_free_dma(tusb_dma->dma_pool[i].ch); + } kfree(tusb_dma); } EXPORT_SYMBOL_GPL(tusb_dma_controller_destroy); +static int tusb_omap_allocate_dma_pool(struct tusb_omap_dma *tusb_dma) +{ + int i; + int ret = 0; + const int sync_dev[6] = { + OMAP24XX_DMA_EXT_DMAREQ0, + OMAP24XX_DMA_EXT_DMAREQ1, + OMAP242X_DMA_EXT_DMAREQ2, + OMAP242X_DMA_EXT_DMAREQ3, + OMAP242X_DMA_EXT_DMAREQ4, + OMAP242X_DMA_EXT_DMAREQ5, + }; + + for (i = 0; i < MAX_DMAREQ; i++) { + struct tusb_dma_data *dma_data = &tusb_dma->dma_pool[i]; + + /* + * Request DMA channels: + * - one channel in case of non multichannel mode + * - MAX_DMAREQ number of channels in multichannel mode + */ + if (i == 0 || tusb_dma->multichannel) { + char ch_name[8]; + + sprintf(ch_name, "dmareq%d", i); + dma_data->sync_dev = sync_dev[i]; + dma_data->ch = -1; + /* callback data is ngoing to be set later */ + ret = omap_request_dma(dma_data->sync_dev, ch_name, + tusb_omap_dma_cb, NULL, &dma_data->ch); + if (ret != 0) { + dev_err(tusb_dma->controller.musb->controller, + "Failed to request %s\n", ch_name); + goto dma_error; + } + + dma_data->dmareq = i; + } else { + dma_data->dmareq = -1; + dma_data->sync_dev = -1; + dma_data->ch = -1; + } + } + + return 0; + +dma_error: + for (; i >= 0; i--) { + struct tusb_dma_data *dma_data = &tusb_dma->dma_pool[i]; + + if (dma_data->ch >= 0) + omap_free_dma(dma_data->ch); + } + + return ret; +} + struct dma_controller * tusb_dma_controller_create(struct musb *musb, void __iomem *base) { @@ -679,10 +679,6 @@ tusb_dma_controller_create(struct musb *musb, void __iomem *base) tusb_dma->controller.musb = musb; tusb_dma->tbase = musb->ctrl_base; - tusb_dma->dma_data.ch = -1; - tusb_dma->dma_data.dmareq = -1; - tusb_dma->dma_data.sync_dev = -1; - tusb_dma->controller.channel_alloc = tusb_omap_dma_allocate; tusb_dma->controller.channel_release = tusb_omap_dma_release; tusb_dma->controller.channel_program = tusb_omap_dma_program; @@ -709,6 +705,9 @@ tusb_dma_controller_create(struct musb *musb, void __iomem *base) ch->private_data = chdat; } + if (tusb_omap_allocate_dma_pool(tusb_dma)) + goto cleanup; + return &tusb_dma->controller; cleanup: -- cgit v1.2.3 From 47699b0aaa585d769b1d8c1ca6b579d3676779db Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 16 Jun 2017 10:41:01 -0500 Subject: usb: musb: tusb6010: Handle DMA TX completion in DMA callback as well Handle the DMA TX in a similar way as we do for the RX: in the DMA completion callback. Since we are no longer using DMA completion interrupt for the TX we can as wall keep these interrupts disabled, but keep the handler for debug purposes. Signed-off-by: Peter Ujfalusi Tested-by: Tony Lindgren Signed-off-by: Bin Liu Signed-off-by: Greg Kroah-Hartman --- drivers/usb/musb/tusb6010.c | 18 +++--------------- drivers/usb/musb/tusb6010_omap.c | 34 +--------------------------------- 2 files changed, 4 insertions(+), 48 deletions(-) diff --git a/drivers/usb/musb/tusb6010.c b/drivers/usb/musb/tusb6010.c index 4253bfb22043..4eb640c54f2c 100644 --- a/drivers/usb/musb/tusb6010.c +++ b/drivers/usb/musb/tusb6010.c @@ -881,26 +881,14 @@ static irqreturn_t tusb_musb_interrupt(int irq, void *__hci) | TUSB_INT_SRC_ID_STATUS_CHNG)) idle_timeout = tusb_otg_ints(musb, int_src, tbase); - /* TX dma callback must be handled here, RX dma callback is - * handled in tusb_omap_dma_cb. + /* + * Just clear the DMA interrupt if it comes as the completion for both + * TX and RX is handled by the DMA callback in tusb6010_omap */ if ((int_src & TUSB_INT_SRC_TXRX_DMA_DONE)) { u32 dma_src = musb_readl(tbase, TUSB_DMA_INT_SRC); - u32 real_dma_src = musb_readl(tbase, TUSB_DMA_INT_MASK); dev_dbg(musb->controller, "DMA IRQ %08x\n", dma_src); - real_dma_src = ~real_dma_src & dma_src; - if (tusb_dma_omap(musb) && real_dma_src) { - int tx_source = (real_dma_src & 0xffff); - int i; - - for (i = 1; i <= 15; i++) { - if (tx_source & (1 << i)) { - dev_dbg(musb->controller, "completing ep%i %s\n", i, "tx"); - musb_dma_completion(musb, i, 1); - } - } - } musb_writel(tbase, TUSB_DMA_INT_CLEAR, dma_src); } diff --git a/drivers/usb/musb/tusb6010_omap.c b/drivers/usb/musb/tusb6010_omap.c index f8671110168b..1c4592d753bf 100644 --- a/drivers/usb/musb/tusb6010_omap.c +++ b/drivers/usb/musb/tusb6010_omap.c @@ -173,13 +173,7 @@ static void tusb_omap_dma_cb(int lch, u16 ch_status, void *data) channel->status = MUSB_DMA_STATUS_FREE; - /* Handle only RX callbacks here. TX callbacks must be handled based - * on the TUSB DMA status interrupt. - * REVISIT: Use both TUSB DMA status interrupt and OMAP DMA callback - * interrupt for RX and TX. - */ - if (!chdat->tx) - musb_dma_completion(musb, chdat->epnum, chdat->tx); + musb_dma_completion(musb, chdat->epnum, chdat->tx); /* We must terminate short tx transfers manually by setting TXPKTRDY. * REVISIT: This same problem may occur with other MUSB dma as well. @@ -464,22 +458,12 @@ tusb_omap_dma_allocate(struct dma_controller *c, int ret, i; struct tusb_omap_dma *tusb_dma; struct musb *musb; - void __iomem *tbase; struct dma_channel *channel = NULL; struct tusb_omap_dma_ch *chdat = NULL; struct tusb_dma_data *dma_data = NULL; - u32 reg; tusb_dma = container_of(c, struct tusb_omap_dma, controller); musb = tusb_dma->controller.musb; - tbase = musb->ctrl_base; - - reg = musb_readl(tbase, TUSB_DMA_INT_MASK); - if (tx) - reg &= ~(1 << hw_ep->epnum); - else - reg &= ~(1 << (hw_ep->epnum + 15)); - musb_writel(tbase, TUSB_DMA_INT_MASK, reg); /* REVISIT: Why does dmareq5 not work? */ if (hw_ep->epnum == 0) { @@ -548,26 +532,10 @@ static void tusb_omap_dma_release(struct dma_channel *channel) { struct tusb_omap_dma_ch *chdat = to_chdat(channel); struct musb *musb = chdat->musb; - void __iomem *tbase = musb->ctrl_base; - u32 reg; dev_dbg(musb->controller, "ep%i ch%i\n", chdat->epnum, chdat->dma_data->ch); - reg = musb_readl(tbase, TUSB_DMA_INT_MASK); - if (chdat->tx) - reg |= (1 << chdat->epnum); - else - reg |= (1 << (chdat->epnum + 15)); - musb_writel(tbase, TUSB_DMA_INT_MASK, reg); - - reg = musb_readl(tbase, TUSB_DMA_INT_CLEAR); - if (chdat->tx) - reg |= (1 << chdat->epnum); - else - reg |= (1 << (chdat->epnum + 15)); - musb_writel(tbase, TUSB_DMA_INT_CLEAR, reg); - channel->status = MUSB_DMA_STATUS_UNKNOWN; omap_stop_dma(chdat->dma_data->ch); -- cgit v1.2.3 From 868772d89d08a7743d24babbde33abea61022590 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 16 Jun 2017 10:41:02 -0500 Subject: ARM: OMAP2+: DMA: Add slave map entries for 24xx external request lines The external request lines are used by tusb6010 on OMAP24xx platforms. Update the map so the driver can use dmaengine API to request the DMA channel. At the same time add temporary map containing only the external DMA request numbers for DT booted case on omap24xx since the tusb6010 stack is not yet supports DT boot. Signed-off-by: Peter Ujfalusi Acked-by: Tony Lindgren Signed-off-by: Bin Liu Signed-off-by: Greg Kroah-Hartman --- arch/arm/mach-omap2/dma.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/arch/arm/mach-omap2/dma.c b/arch/arm/mach-omap2/dma.c index e58c13a9bea5..0b77a0176018 100644 --- a/arch/arm/mach-omap2/dma.c +++ b/arch/arm/mach-omap2/dma.c @@ -249,6 +249,24 @@ static const struct dma_slave_map omap24xx_sdma_map[] = { { "omap_uart.2", "rx", SDMA_FILTER_PARAM(54) }, { "omap_hsmmc.0", "tx", SDMA_FILTER_PARAM(61) }, { "omap_hsmmc.0", "rx", SDMA_FILTER_PARAM(62) }, + + /* external DMA requests when tusb6010 is used */ + { "musb-tusb", "dmareq0", SDMA_FILTER_PARAM(2) }, + { "musb-tusb", "dmareq1", SDMA_FILTER_PARAM(3) }, + { "musb-tusb", "dmareq2", SDMA_FILTER_PARAM(14) }, /* OMAP2420 only */ + { "musb-tusb", "dmareq3", SDMA_FILTER_PARAM(15) }, /* OMAP2420 only */ + { "musb-tusb", "dmareq4", SDMA_FILTER_PARAM(16) }, /* OMAP2420 only */ + { "musb-tusb", "dmareq5", SDMA_FILTER_PARAM(64) }, /* OMAP2420 only */ +}; + +static const struct dma_slave_map omap24xx_sdma_dt_map[] = { + /* external DMA requests when tusb6010 is used */ + { "musb-hdrc.1.auto", "dmareq0", SDMA_FILTER_PARAM(2) }, + { "musb-hdrc.1.auto", "dmareq1", SDMA_FILTER_PARAM(3) }, + { "musb-hdrc.1.auto", "dmareq2", SDMA_FILTER_PARAM(14) }, /* OMAP2420 only */ + { "musb-hdrc.1.auto", "dmareq3", SDMA_FILTER_PARAM(15) }, /* OMAP2420 only */ + { "musb-hdrc.1.auto", "dmareq4", SDMA_FILTER_PARAM(16) }, /* OMAP2420 only */ + { "musb-hdrc.1.auto", "dmareq5", SDMA_FILTER_PARAM(64) }, /* OMAP2420 only */ }; static const struct dma_slave_map omap3xxx_sdma_map[] = { @@ -346,6 +364,12 @@ static int __init omap2_system_dma_init_dev(struct omap_hwmod *oh, void *unused) __func__); return -ENODEV; } + } else { + if (soc_is_omap24xx()) { + /* DMA slave map for drivers not yet converted to DT */ + p.slave_map = omap24xx_sdma_dt_map; + p.slavecnt = ARRAY_SIZE(omap24xx_sdma_dt_map); + } } pdev = omap_device_build(name, 0, oh, &p, sizeof(p)); -- cgit v1.2.3 From 9c691cc9f8b5f2f866f1235800f03a1d7a06c1af Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 16 Jun 2017 10:41:03 -0500 Subject: usb: musb: tusb6010_omap: Convert to DMAengine API With the port_window support in DMAengine and the sDMA driver we can convert the driver to DMAengine. Signed-off-by: Peter Ujfalusi Tested-by: Tony Lindgren Signed-off-by: Bin Liu Signed-off-by: Greg Kroah-Hartman --- drivers/usb/musb/tusb6010_omap.c | 201 ++++++++++++++++----------------------- 1 file changed, 80 insertions(+), 121 deletions(-) diff --git a/drivers/usb/musb/tusb6010_omap.c b/drivers/usb/musb/tusb6010_omap.c index 1c4592d753bf..e8060e49b0f4 100644 --- a/drivers/usb/musb/tusb6010_omap.c +++ b/drivers/usb/musb/tusb6010_omap.c @@ -15,7 +15,7 @@ #include #include #include -#include +#include #include "musb_core.h" #include "tusb6010.h" @@ -24,17 +24,9 @@ #define MAX_DMAREQ 5 /* REVISIT: Really 6, but req5 not OK */ -#define OMAP24XX_DMA_EXT_DMAREQ0 2 -#define OMAP24XX_DMA_EXT_DMAREQ1 3 -#define OMAP242X_DMA_EXT_DMAREQ2 14 -#define OMAP242X_DMA_EXT_DMAREQ3 15 -#define OMAP242X_DMA_EXT_DMAREQ4 16 -#define OMAP242X_DMA_EXT_DMAREQ5 64 - struct tusb_dma_data { - int ch; s8 dmareq; - s8 sync_dev; + struct dma_chan *chan; }; struct tusb_omap_dma_ch { @@ -105,7 +97,7 @@ static inline void tusb_omap_free_shared_dmareq(struct tusb_omap_dma_ch *chdat) * See also musb_dma_completion in plat_uds.c and musb_g_[tx|rx]() in * musb_gadget.c. */ -static void tusb_omap_dma_cb(int lch, u16 ch_status, void *data) +static void tusb_omap_dma_cb(void *data) { struct dma_channel *channel = (struct dma_channel *)data; struct tusb_omap_dma_ch *chdat = to_chdat(channel); @@ -116,18 +108,11 @@ static void tusb_omap_dma_cb(int lch, u16 ch_status, void *data) void __iomem *ep_conf = hw_ep->conf; void __iomem *mbase = musb->mregs; unsigned long remaining, flags, pio; - int ch; spin_lock_irqsave(&musb->lock, flags); - ch = chdat->dma_data->ch; - - if (ch_status != OMAP_DMA_BLOCK_IRQ) - printk(KERN_ERR "TUSB DMA error status: %i\n", ch_status); - - dev_dbg(musb->controller, "ep%i %s dma callback ch: %i status: %x\n", - chdat->epnum, chdat->tx ? "tx" : "rx", - ch, ch_status); + dev_dbg(musb->controller, "ep%i %s dma callback\n", + chdat->epnum, chdat->tx ? "tx" : "rx"); if (chdat->tx) remaining = musb_readl(ep_conf, TUSB_EP_TX_OFFSET); @@ -138,8 +123,8 @@ static void tusb_omap_dma_cb(int lch, u16 ch_status, void *data) /* HW issue #10: XFR_SIZE may get corrupt on DMA (both async & sync) */ if (unlikely(remaining > chdat->transfer_len)) { - dev_dbg(musb->controller, "Corrupt %s dma ch%i XFR_SIZE: 0x%08lx\n", - chdat->tx ? "tx" : "rx", ch, remaining); + dev_dbg(musb->controller, "Corrupt %s XFR_SIZE: 0x%08lx\n", + chdat->tx ? "tx" : "rx", remaining); remaining = 0; } @@ -206,13 +191,16 @@ static int tusb_omap_dma_program(struct dma_channel *channel, u16 packet_sz, struct musb_hw_ep *hw_ep = chdat->hw_ep; void __iomem *mbase = musb->mregs; void __iomem *ep_conf = hw_ep->conf; - dma_addr_t fifo = hw_ep->fifo_sync; - struct omap_dma_channel_params dma_params; + dma_addr_t fifo_addr = hw_ep->fifo_sync; u32 dma_remaining; - int src_burst, dst_burst; u16 csr; u32 psize; struct tusb_dma_data *dma_data; + struct dma_async_tx_descriptor *dma_desc; + struct dma_slave_config dma_cfg; + enum dma_transfer_direction dma_dir; + u32 port_window; + int ret; if (unlikely(dma_addr & 0x1) || (len < 32) || (len > packet_sz)) return false; @@ -238,10 +226,8 @@ static int tusb_omap_dma_program(struct dma_channel *channel, u16 packet_sz, dma_remaining = TUSB_EP_CONFIG_XFR_SIZE(dma_remaining); if (dma_remaining) { - dev_dbg(musb->controller, "Busy %s dma ch%i, not using: %08x\n", - chdat->tx ? "tx" : "rx", - chdat->dma_data ? chdat->dma_data->ch : -1, - dma_remaining); + dev_dbg(musb->controller, "Busy %s dma, not using: %08x\n", + chdat->tx ? "tx" : "rx", dma_remaining); return false; } @@ -258,7 +244,7 @@ static int tusb_omap_dma_program(struct dma_channel *channel, u16 packet_sz, dev_dbg(musb->controller, "could not get dma for ep%i\n", chdat->epnum); return false; } - if (dma_data->ch < 0) { + if (dma_data->dmareq < 0) { /* REVISIT: This should get blocked earlier, happens * with MSC ErrorRecoveryTest */ @@ -267,8 +253,6 @@ static int tusb_omap_dma_program(struct dma_channel *channel, u16 packet_sz, } } - omap_set_dma_callback(dma_data->ch, tusb_omap_dma_cb, channel); - chdat->packet_sz = packet_sz; chdat->len = len; channel->actual_len = 0; @@ -276,79 +260,68 @@ static int tusb_omap_dma_program(struct dma_channel *channel, u16 packet_sz, channel->status = MUSB_DMA_STATUS_BUSY; /* Since we're recycling dma areas, we need to clean or invalidate */ - if (chdat->tx) + if (chdat->tx) { + dma_dir = DMA_MEM_TO_DEV; dma_map_single(dev, phys_to_virt(dma_addr), len, DMA_TO_DEVICE); - else + } else { + dma_dir = DMA_DEV_TO_MEM; dma_map_single(dev, phys_to_virt(dma_addr), len, DMA_FROM_DEVICE); + } + + memset(&dma_cfg, 0, sizeof(dma_cfg)); /* Use 16-bit transfer if dma_addr is not 32-bit aligned */ if ((dma_addr & 0x3) == 0) { - dma_params.data_type = OMAP_DMA_DATA_TYPE_S32; - dma_params.elem_count = 8; /* Elements in frame */ + dma_cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + dma_cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + port_window = 8; } else { - dma_params.data_type = OMAP_DMA_DATA_TYPE_S16; - dma_params.elem_count = 16; /* Elements in frame */ - fifo = hw_ep->fifo_async; - } + dma_cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + dma_cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + port_window = 16; - dma_params.frame_count = chdat->transfer_len / 32; /* Burst sz frame */ + fifo_addr = hw_ep->fifo_async; + } - dev_dbg(musb->controller, "ep%i %s dma ch%i dma: %pad len: %u(%u) packet_sz: %i(%i)\n", - chdat->epnum, chdat->tx ? "tx" : "rx", - dma_data->ch, &dma_addr, chdat->transfer_len, len, - chdat->transfer_packet_sz, packet_sz); + dev_dbg(musb->controller, + "ep%i %s dma: %pad len: %u(%u) packet_sz: %i(%i)\n", + chdat->epnum, chdat->tx ? "tx" : "rx", &dma_addr, + chdat->transfer_len, len, chdat->transfer_packet_sz, packet_sz); + + dma_cfg.src_addr = fifo_addr; + dma_cfg.dst_addr = fifo_addr; + dma_cfg.src_port_window_size = port_window; + dma_cfg.src_maxburst = port_window; + dma_cfg.dst_port_window_size = port_window; + dma_cfg.dst_maxburst = port_window; + + ret = dmaengine_slave_config(dma_data->chan, &dma_cfg); + if (ret) { + dev_err(musb->controller, "DMA slave config failed: %d\n", ret); + return false; + } - /* - * Prepare omap DMA for transfer - */ - if (chdat->tx) { - dma_params.src_amode = OMAP_DMA_AMODE_POST_INC; - dma_params.src_start = (unsigned long)dma_addr; - dma_params.src_ei = 0; - dma_params.src_fi = 0; - - dma_params.dst_amode = OMAP_DMA_AMODE_DOUBLE_IDX; - dma_params.dst_start = (unsigned long)fifo; - dma_params.dst_ei = 1; - dma_params.dst_fi = -31; /* Loop 32 byte window */ - - dma_params.trigger = dma_data->sync_dev; - dma_params.sync_mode = OMAP_DMA_SYNC_FRAME; - dma_params.src_or_dst_synch = 0; /* Dest sync */ - - src_burst = OMAP_DMA_DATA_BURST_16; /* 16x32 read */ - dst_burst = OMAP_DMA_DATA_BURST_8; /* 8x32 write */ - } else { - dma_params.src_amode = OMAP_DMA_AMODE_DOUBLE_IDX; - dma_params.src_start = (unsigned long)fifo; - dma_params.src_ei = 1; - dma_params.src_fi = -31; /* Loop 32 byte window */ - - dma_params.dst_amode = OMAP_DMA_AMODE_POST_INC; - dma_params.dst_start = (unsigned long)dma_addr; - dma_params.dst_ei = 0; - dma_params.dst_fi = 0; - - dma_params.trigger = dma_data->sync_dev; - dma_params.sync_mode = OMAP_DMA_SYNC_FRAME; - dma_params.src_or_dst_synch = 1; /* Source sync */ - - src_burst = OMAP_DMA_DATA_BURST_8; /* 8x32 read */ - dst_burst = OMAP_DMA_DATA_BURST_16; /* 16x32 write */ + dma_desc = dmaengine_prep_slave_single(dma_data->chan, dma_addr, + chdat->transfer_len, dma_dir, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!dma_desc) { + dev_err(musb->controller, "DMA prep_slave_single failed\n"); + return false; } - dev_dbg(musb->controller, "ep%i %s using %i-bit %s dma from 0x%08lx to 0x%08lx\n", + dma_desc->callback = tusb_omap_dma_cb; + dma_desc->callback_param = channel; + dmaengine_submit(dma_desc); + + dev_dbg(musb->controller, + "ep%i %s using %i-bit %s dma from %pad to %pad\n", chdat->epnum, chdat->tx ? "tx" : "rx", - (dma_params.data_type == OMAP_DMA_DATA_TYPE_S32) ? 32 : 16, + dma_cfg.src_addr_width * 8, ((dma_addr & 0x3) == 0) ? "sync" : "async", - dma_params.src_start, dma_params.dst_start); - - omap_set_dma_params(dma_data->ch, &dma_params); - omap_set_dma_src_burst_mode(dma_data->ch, src_burst); - omap_set_dma_dest_burst_mode(dma_data->ch, dst_burst); - omap_set_dma_write_mode(dma_data->ch, OMAP_DMA_WRITE_LAST_NON_POSTED); + (dma_dir == DMA_MEM_TO_DEV) ? &dma_addr : &fifo_addr, + (dma_dir == DMA_MEM_TO_DEV) ? &fifo_addr : &dma_addr); /* * Prepare MUSB for DMA transfer @@ -368,10 +341,8 @@ static int tusb_omap_dma_program(struct dma_channel *channel, u16 packet_sz, csr | MUSB_RXCSR_P_WZC_BITS); } - /* - * Start DMA transfer - */ - omap_start_dma(dma_data->ch); + /* Start DMA transfer */ + dma_async_issue_pending(dma_data->chan); if (chdat->tx) { /* Send transfer_packet_sz packets at a time */ @@ -401,7 +372,7 @@ static int tusb_omap_dma_abort(struct dma_channel *channel) struct tusb_omap_dma_ch *chdat = to_chdat(channel); if (chdat->dma_data) - omap_stop_dma(chdat->dma_data->ch); + dmaengine_terminate_all(chdat->dma_data->chan); channel->status = MUSB_DMA_STATUS_FREE; @@ -511,11 +482,11 @@ tusb_omap_dma_allocate(struct dma_controller *c, dma_data = chdat->dma_data; - dev_dbg(musb->controller, "ep%i %s dma: %s dma%i dmareq%i sync%i\n", + dev_dbg(musb->controller, "ep%i %s dma: %s dmareq%i\n", chdat->epnum, chdat->tx ? "tx" : "rx", tusb_dma->multichannel ? "shared" : "dedicated", - dma_data->ch, dma_data->dmareq, dma_data->sync_dev); + dma_data->dmareq); return channel; @@ -533,12 +504,11 @@ static void tusb_omap_dma_release(struct dma_channel *channel) struct tusb_omap_dma_ch *chdat = to_chdat(channel); struct musb *musb = chdat->musb; - dev_dbg(musb->controller, "ep%i ch%i\n", chdat->epnum, - chdat->dma_data->ch); + dev_dbg(musb->controller, "Release for ep%i\n", chdat->epnum); channel->status = MUSB_DMA_STATUS_UNKNOWN; - omap_stop_dma(chdat->dma_data->ch); + dmaengine_terminate_sync(chdat->dma_data->chan); tusb_omap_dma_free_dmareq(chdat); channel = NULL; @@ -558,8 +528,8 @@ void tusb_dma_controller_destroy(struct dma_controller *c) } /* Free up the DMA channels */ - if (tusb_dma && tusb_dma->dma_pool[i].ch >= 0) - omap_free_dma(tusb_dma->dma_pool[i].ch); + if (tusb_dma && tusb_dma->dma_pool[i].chan) + dma_release_channel(tusb_dma->dma_pool[i].chan); } kfree(tusb_dma); @@ -568,16 +538,9 @@ EXPORT_SYMBOL_GPL(tusb_dma_controller_destroy); static int tusb_omap_allocate_dma_pool(struct tusb_omap_dma *tusb_dma) { + struct musb *musb = tusb_dma->controller.musb; int i; int ret = 0; - const int sync_dev[6] = { - OMAP24XX_DMA_EXT_DMAREQ0, - OMAP24XX_DMA_EXT_DMAREQ1, - OMAP242X_DMA_EXT_DMAREQ2, - OMAP242X_DMA_EXT_DMAREQ3, - OMAP242X_DMA_EXT_DMAREQ4, - OMAP242X_DMA_EXT_DMAREQ5, - }; for (i = 0; i < MAX_DMAREQ; i++) { struct tusb_dma_data *dma_data = &tusb_dma->dma_pool[i]; @@ -591,22 +554,18 @@ static int tusb_omap_allocate_dma_pool(struct tusb_omap_dma *tusb_dma) char ch_name[8]; sprintf(ch_name, "dmareq%d", i); - dma_data->sync_dev = sync_dev[i]; - dma_data->ch = -1; - /* callback data is ngoing to be set later */ - ret = omap_request_dma(dma_data->sync_dev, ch_name, - tusb_omap_dma_cb, NULL, &dma_data->ch); - if (ret != 0) { - dev_err(tusb_dma->controller.musb->controller, + dma_data->chan = dma_request_chan(musb->controller, + ch_name); + if (IS_ERR(dma_data->chan)) { + dev_err(musb->controller, "Failed to request %s\n", ch_name); + ret = PTR_ERR(dma_data->chan); goto dma_error; } dma_data->dmareq = i; } else { dma_data->dmareq = -1; - dma_data->sync_dev = -1; - dma_data->ch = -1; } } @@ -616,8 +575,8 @@ dma_error: for (; i >= 0; i--) { struct tusb_dma_data *dma_data = &tusb_dma->dma_pool[i]; - if (dma_data->ch >= 0) - omap_free_dma(dma_data->ch); + if (dma_data->dmareq >= 0) + dma_release_channel(dma_data->chan); } return ret; -- cgit v1.2.3 From 45e5d4d418f37cb9b2746c1fc63ab89b7b521d78 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 20 Jun 2017 12:52:02 +0200 Subject: USB: serial: refactor port endpoint setup Make the probe callback more readable by refactoring the port endpoint-resource setup by adding four helper functions. Reviewed-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold --- drivers/usb/serial/usb-serial.c | 222 +++++++++++++++++++++++----------------- 1 file changed, 130 insertions(+), 92 deletions(-) diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c index c7ca95f64edc..f68275dd1e9f 100644 --- a/drivers/usb/serial/usb-serial.c +++ b/drivers/usb/serial/usb-serial.c @@ -742,6 +742,124 @@ static void find_endpoints(struct usb_serial *serial, } } +static int setup_port_bulk_in(struct usb_serial_port *port, + struct usb_endpoint_descriptor *epd) +{ + struct usb_serial_driver *type = port->serial->type; + struct usb_device *udev = port->serial->dev; + int buffer_size; + int i; + + buffer_size = max_t(int, type->bulk_in_size, usb_endpoint_maxp(epd)); + port->bulk_in_size = buffer_size; + port->bulk_in_endpointAddress = epd->bEndpointAddress; + + for (i = 0; i < ARRAY_SIZE(port->read_urbs); ++i) { + set_bit(i, &port->read_urbs_free); + port->read_urbs[i] = usb_alloc_urb(0, GFP_KERNEL); + if (!port->read_urbs[i]) + return -ENOMEM; + port->bulk_in_buffers[i] = kmalloc(buffer_size, GFP_KERNEL); + if (!port->bulk_in_buffers[i]) + return -ENOMEM; + usb_fill_bulk_urb(port->read_urbs[i], udev, + usb_rcvbulkpipe(udev, epd->bEndpointAddress), + port->bulk_in_buffers[i], buffer_size, + type->read_bulk_callback, port); + } + + port->read_urb = port->read_urbs[0]; + port->bulk_in_buffer = port->bulk_in_buffers[0]; + + return 0; +} + +static int setup_port_bulk_out(struct usb_serial_port *port, + struct usb_endpoint_descriptor *epd) +{ + struct usb_serial_driver *type = port->serial->type; + struct usb_device *udev = port->serial->dev; + int buffer_size; + int i; + + if (kfifo_alloc(&port->write_fifo, PAGE_SIZE, GFP_KERNEL)) + return -ENOMEM; + if (type->bulk_out_size) + buffer_size = type->bulk_out_size; + else + buffer_size = usb_endpoint_maxp(epd); + port->bulk_out_size = buffer_size; + port->bulk_out_endpointAddress = epd->bEndpointAddress; + + for (i = 0; i < ARRAY_SIZE(port->write_urbs); ++i) { + set_bit(i, &port->write_urbs_free); + port->write_urbs[i] = usb_alloc_urb(0, GFP_KERNEL); + if (!port->write_urbs[i]) + return -ENOMEM; + port->bulk_out_buffers[i] = kmalloc(buffer_size, GFP_KERNEL); + if (!port->bulk_out_buffers[i]) + return -ENOMEM; + usb_fill_bulk_urb(port->write_urbs[i], udev, + usb_sndbulkpipe(udev, epd->bEndpointAddress), + port->bulk_out_buffers[i], buffer_size, + type->write_bulk_callback, port); + } + + port->write_urb = port->write_urbs[0]; + port->bulk_out_buffer = port->bulk_out_buffers[0]; + + return 0; +} + +static int setup_port_interrupt_in(struct usb_serial_port *port, + struct usb_endpoint_descriptor *epd) +{ + struct usb_serial_driver *type = port->serial->type; + struct usb_device *udev = port->serial->dev; + int buffer_size; + + port->interrupt_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!port->interrupt_in_urb) + return -ENOMEM; + buffer_size = usb_endpoint_maxp(epd); + port->interrupt_in_endpointAddress = epd->bEndpointAddress; + port->interrupt_in_buffer = kmalloc(buffer_size, GFP_KERNEL); + if (!port->interrupt_in_buffer) + return -ENOMEM; + usb_fill_int_urb(port->interrupt_in_urb, udev, + usb_rcvintpipe(udev, epd->bEndpointAddress), + port->interrupt_in_buffer, buffer_size, + type->read_int_callback, port, + epd->bInterval); + + return 0; +} + +static int setup_port_interrupt_out(struct usb_serial_port *port, + struct usb_endpoint_descriptor *epd) +{ + struct usb_serial_driver *type = port->serial->type; + struct usb_device *udev = port->serial->dev; + int buffer_size; + + port->interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!port->interrupt_out_urb) + return -ENOMEM; + buffer_size = usb_endpoint_maxp(epd); + port->interrupt_out_size = buffer_size; + port->interrupt_out_endpointAddress = epd->bEndpointAddress; + port->interrupt_out_buffer = kmalloc(buffer_size, GFP_KERNEL); + if (!port->interrupt_out_buffer) + return -ENOMEM; + usb_fill_int_urb(port->interrupt_out_urb, udev, + usb_sndintpipe(udev, epd->bEndpointAddress), + port->interrupt_out_buffer, buffer_size, + type->write_int_callback, port, + epd->bInterval); + + return 0; +} + static int usb_serial_probe(struct usb_interface *interface, const struct usb_device_id *id) { @@ -749,13 +867,10 @@ static int usb_serial_probe(struct usb_interface *interface, struct usb_device *dev = interface_to_usbdev(interface); struct usb_serial *serial = NULL; struct usb_serial_port *port; - struct usb_endpoint_descriptor *endpoint; struct usb_serial_endpoints *epds; struct usb_serial_driver *type = NULL; int retval; - int buffer_size; int i; - int j; int num_ports = 0; unsigned char max_endpoints; @@ -867,86 +982,24 @@ static int usb_serial_probe(struct usb_interface *interface, /* set up the endpoint information */ for (i = 0; i < epds->num_bulk_in; ++i) { - endpoint = epds->bulk_in[i]; - port = serial->port[i]; - buffer_size = max_t(int, serial->type->bulk_in_size, - usb_endpoint_maxp(endpoint)); - port->bulk_in_size = buffer_size; - port->bulk_in_endpointAddress = endpoint->bEndpointAddress; - - for (j = 0; j < ARRAY_SIZE(port->read_urbs); ++j) { - set_bit(j, &port->read_urbs_free); - port->read_urbs[j] = usb_alloc_urb(0, GFP_KERNEL); - if (!port->read_urbs[j]) - goto probe_error; - port->bulk_in_buffers[j] = kmalloc(buffer_size, - GFP_KERNEL); - if (!port->bulk_in_buffers[j]) - goto probe_error; - usb_fill_bulk_urb(port->read_urbs[j], dev, - usb_rcvbulkpipe(dev, - endpoint->bEndpointAddress), - port->bulk_in_buffers[j], buffer_size, - serial->type->read_bulk_callback, - port); - } - - port->read_urb = port->read_urbs[0]; - port->bulk_in_buffer = port->bulk_in_buffers[0]; + retval = setup_port_bulk_in(serial->port[i], epds->bulk_in[i]); + if (retval) + goto probe_error; } for (i = 0; i < epds->num_bulk_out; ++i) { - endpoint = epds->bulk_out[i]; - port = serial->port[i]; - if (kfifo_alloc(&port->write_fifo, PAGE_SIZE, GFP_KERNEL)) + retval = setup_port_bulk_out(serial->port[i], + epds->bulk_out[i]); + if (retval) goto probe_error; - buffer_size = serial->type->bulk_out_size; - if (!buffer_size) - buffer_size = usb_endpoint_maxp(endpoint); - port->bulk_out_size = buffer_size; - port->bulk_out_endpointAddress = endpoint->bEndpointAddress; - - for (j = 0; j < ARRAY_SIZE(port->write_urbs); ++j) { - set_bit(j, &port->write_urbs_free); - port->write_urbs[j] = usb_alloc_urb(0, GFP_KERNEL); - if (!port->write_urbs[j]) - goto probe_error; - port->bulk_out_buffers[j] = kmalloc(buffer_size, - GFP_KERNEL); - if (!port->bulk_out_buffers[j]) - goto probe_error; - usb_fill_bulk_urb(port->write_urbs[j], dev, - usb_sndbulkpipe(dev, - endpoint->bEndpointAddress), - port->bulk_out_buffers[j], buffer_size, - serial->type->write_bulk_callback, - port); - } - - port->write_urb = port->write_urbs[0]; - port->bulk_out_buffer = port->bulk_out_buffers[0]; } if (serial->type->read_int_callback) { for (i = 0; i < epds->num_interrupt_in; ++i) { - endpoint = epds->interrupt_in[i]; - port = serial->port[i]; - port->interrupt_in_urb = usb_alloc_urb(0, GFP_KERNEL); - if (!port->interrupt_in_urb) - goto probe_error; - buffer_size = usb_endpoint_maxp(endpoint); - port->interrupt_in_endpointAddress = - endpoint->bEndpointAddress; - port->interrupt_in_buffer = kmalloc(buffer_size, - GFP_KERNEL); - if (!port->interrupt_in_buffer) + retval = setup_port_interrupt_in(serial->port[i], + epds->interrupt_in[i]); + if (retval) goto probe_error; - usb_fill_int_urb(port->interrupt_in_urb, dev, - usb_rcvintpipe(dev, - endpoint->bEndpointAddress), - port->interrupt_in_buffer, buffer_size, - serial->type->read_int_callback, port, - endpoint->bInterval); } } else if (epds->num_interrupt_in) { dev_dbg(ddev, "The device claims to support interrupt in transfers, but read_int_callback is not defined\n"); @@ -954,25 +1007,10 @@ static int usb_serial_probe(struct usb_interface *interface, if (serial->type->write_int_callback) { for (i = 0; i < epds->num_interrupt_out; ++i) { - endpoint = epds->interrupt_out[i]; - port = serial->port[i]; - port->interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL); - if (!port->interrupt_out_urb) - goto probe_error; - buffer_size = usb_endpoint_maxp(endpoint); - port->interrupt_out_size = buffer_size; - port->interrupt_out_endpointAddress = - endpoint->bEndpointAddress; - port->interrupt_out_buffer = kmalloc(buffer_size, - GFP_KERNEL); - if (!port->interrupt_out_buffer) + retval = setup_port_interrupt_out(serial->port[i], + epds->interrupt_out[i]); + if (retval) goto probe_error; - usb_fill_int_urb(port->interrupt_out_urb, dev, - usb_sndintpipe(dev, - endpoint->bEndpointAddress), - port->interrupt_out_buffer, buffer_size, - serial->type->write_int_callback, port, - endpoint->bInterval); } } else if (epds->num_interrupt_out) { dev_dbg(ddev, "The device claims to support interrupt out transfers, but write_int_callback is not defined\n"); -- cgit v1.2.3 From c22ac6d29f18d3210c545f77e4f95b856ab5f983 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 20 Jun 2017 12:52:03 +0200 Subject: USB: serial: propagate late probe errors Propagate errnos for late probe errors (e.g. -ENOMEM on allocation failures) instead of always returning -EIO. Note that some drivers are currently returning -ENODEV from their attach callbacks when a device is not supported, but this has also been mapped to -EIO. Reviewed-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold --- drivers/usb/serial/usb-serial.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c index f68275dd1e9f..bb34f9f7eaf4 100644 --- a/drivers/usb/serial/usb-serial.c +++ b/drivers/usb/serial/usb-serial.c @@ -962,8 +962,10 @@ static int usb_serial_probe(struct usb_interface *interface, dev_dbg(ddev, "setting up %d port structure(s)\n", max_endpoints); for (i = 0; i < max_endpoints; ++i) { port = kzalloc(sizeof(struct usb_serial_port), GFP_KERNEL); - if (!port) - goto probe_error; + if (!port) { + retval = -ENOMEM; + goto err_free_epds; + } tty_port_init(&port->port); port->port.ops = &serial_port_ops; port->serial = serial; @@ -984,14 +986,14 @@ static int usb_serial_probe(struct usb_interface *interface, for (i = 0; i < epds->num_bulk_in; ++i) { retval = setup_port_bulk_in(serial->port[i], epds->bulk_in[i]); if (retval) - goto probe_error; + goto err_free_epds; } for (i = 0; i < epds->num_bulk_out; ++i) { retval = setup_port_bulk_out(serial->port[i], epds->bulk_out[i]); if (retval) - goto probe_error; + goto err_free_epds; } if (serial->type->read_int_callback) { @@ -999,7 +1001,7 @@ static int usb_serial_probe(struct usb_interface *interface, retval = setup_port_interrupt_in(serial->port[i], epds->interrupt_in[i]); if (retval) - goto probe_error; + goto err_free_epds; } } else if (epds->num_interrupt_in) { dev_dbg(ddev, "The device claims to support interrupt in transfers, but read_int_callback is not defined\n"); @@ -1010,7 +1012,7 @@ static int usb_serial_probe(struct usb_interface *interface, retval = setup_port_interrupt_out(serial->port[i], epds->interrupt_out[i]); if (retval) - goto probe_error; + goto err_free_epds; } } else if (epds->num_interrupt_out) { dev_dbg(ddev, "The device claims to support interrupt out transfers, but write_int_callback is not defined\n"); @@ -1022,7 +1024,7 @@ static int usb_serial_probe(struct usb_interface *interface, if (type->attach) { retval = type->attach(serial); if (retval < 0) - goto probe_error; + goto err_free_epds; serial->attached = 1; if (retval > 0) { /* quietly accept this device, but don't bind to a @@ -1034,9 +1036,10 @@ static int usb_serial_probe(struct usb_interface *interface, serial->attached = 1; } - if (allocate_minors(serial, num_ports)) { + retval = allocate_minors(serial, num_ports); + if (retval) { dev_err(ddev, "No more free serial minor numbers\n"); - goto probe_error; + goto err_free_epds; } /* register all of the individual ports with the driver core */ @@ -1058,8 +1061,6 @@ exit: module_put(type->driver.owner); return 0; -probe_error: - retval = -EIO; err_free_epds: kfree(epds); err_put_serial: -- cgit v1.2.3 From c68bb0ef177bbcaba505ed492101095c613dafa6 Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Wed, 21 Jun 2017 09:22:15 -0500 Subject: usb: musb: compress return logic into one line Simplify return logic to avoid unnecessary variable assignment. Signed-off-by: Gustavo A. R. Silva Acked-by: Bin Liu Signed-off-by: Greg Kroah-Hartman --- drivers/usb/musb/musb_host.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/usb/musb/musb_host.c b/drivers/usb/musb/musb_host.c index dbe617a735d8..76decb8011eb 100644 --- a/drivers/usb/musb/musb_host.c +++ b/drivers/usb/musb/musb_host.c @@ -1540,7 +1540,7 @@ static int musb_rx_dma_iso_cppi41(struct dma_controller *dma, struct dma_channel *channel = hw_ep->rx_channel; void __iomem *epio = hw_ep->regs; dma_addr_t *buf; - u32 length, res; + u32 length; u16 val; buf = (void *)urb->iso_frame_desc[qh->iso_idx].offset + @@ -1552,10 +1552,8 @@ static int musb_rx_dma_iso_cppi41(struct dma_controller *dma, val |= MUSB_RXCSR_DMAENAB; musb_writew(hw_ep->regs, MUSB_RXCSR, val); - res = dma->channel_program(channel, qh->maxpacket, 0, + return dma->channel_program(channel, qh->maxpacket, 0, (u32)buf, length); - - return res; } #else static inline int musb_rx_dma_iso_cppi41(struct dma_controller *dma, -- cgit v1.2.3 From c1b0bc2dabfa884dea49c02adaf3cd6b52b33d2f Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Fri, 16 Jun 2017 11:21:24 +0300 Subject: usb: typec: Add support for UCSI interface UCSI - USB Type-C Connector System Software Interface - is a specification that defines set of registers and data structures for controlling the USB Type-C ports. It's designed for systems where an embedded controller (EC) is in charge of the USB Type-C PHY or USB Power Delivery controller. It is designed for systems with EC, but it is not limited to them, and for example some USB Power Delivery controllers will use it as their direct control interface. With UCSI the EC (or USB PD controller) acts as the port manager, implementing all USB Type-C and Power Delivery state machines. The OS can use the interfaces for reading the status of the ports and controlling basic operations like role swapping. The UCSI specification highlights the fact that it does not define the interface method (PCI/I2C/ACPI/etc.). Therefore the driver is implemented as library and every supported interface method needs its own driver. Driver for ACPI is provided in separate patch following this one. The initial driver includes support for all required features from UCSI specification version 1.0 (getting connector capabilities and status, and support for power and data role swapping), but none of the optional UCSI features (alternate modes, power source capabilities, and cable capabilities). Signed-off-by: Heikki Krogerus Reviewed-by: Guenter Roeck Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/Kconfig | 2 + drivers/usb/typec/Makefile | 1 + drivers/usb/typec/ucsi/Kconfig | 23 ++ drivers/usb/typec/ucsi/Makefile | 7 + drivers/usb/typec/ucsi/debug.h | 64 ++++ drivers/usb/typec/ucsi/trace.c | 2 + drivers/usb/typec/ucsi/trace.h | 143 ++++++++ drivers/usb/typec/ucsi/ucsi.c | 790 ++++++++++++++++++++++++++++++++++++++++ drivers/usb/typec/ucsi/ucsi.h | 335 +++++++++++++++++ 9 files changed, 1367 insertions(+) create mode 100644 drivers/usb/typec/ucsi/Kconfig create mode 100644 drivers/usb/typec/ucsi/Makefile create mode 100644 drivers/usb/typec/ucsi/debug.h create mode 100644 drivers/usb/typec/ucsi/trace.c create mode 100644 drivers/usb/typec/ucsi/trace.h create mode 100644 drivers/usb/typec/ucsi/ucsi.c create mode 100644 drivers/usb/typec/ucsi/ucsi.h diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig index dfcfe459b7cf..bc1b7745f1d4 100644 --- a/drivers/usb/typec/Kconfig +++ b/drivers/usb/typec/Kconfig @@ -19,4 +19,6 @@ config TYPEC_WCOVE To compile this driver as module, choose M here: the module will be called typec_wcove +source "drivers/usb/typec/ucsi/Kconfig" + endmenu diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile index b9cb862221af..bc214f15f1b5 100644 --- a/drivers/usb/typec/Makefile +++ b/drivers/usb/typec/Makefile @@ -1,2 +1,3 @@ obj-$(CONFIG_TYPEC) += typec.o obj-$(CONFIG_TYPEC_WCOVE) += typec_wcove.o +obj-$(CONFIG_TYPEC_UCSI) += ucsi/ diff --git a/drivers/usb/typec/ucsi/Kconfig b/drivers/usb/typec/ucsi/Kconfig new file mode 100644 index 000000000000..da4c5c3d8870 --- /dev/null +++ b/drivers/usb/typec/ucsi/Kconfig @@ -0,0 +1,23 @@ +config TYPEC_UCSI + tristate "USB Type-C Connector System Software Interface driver" + depends on !CPU_BIG_ENDIAN + select TYPEC + help + USB Type-C Connector System Software Interface (UCSI) is a + specification for an interface that allows the operating system to + control the USB Type-C ports. On UCSI system the USB Type-C ports + function autonomously by default, but in order to get the status of + the ports and support basic operations like role swapping, the driver + is required. UCSI is available on most of the new Intel based systems + that are equipped with Embedded Controller and USB Type-C ports. + + UCSI specification does not define the interface method, so depending + on the platform, ACPI, PCI, I2C, etc. may be used. Therefore this + driver only provides the core part, and separate drivers are needed + for every supported interface method. + + The UCSI specification can be downloaded from: + http://www.intel.com/content/www/us/en/io/universal-serial-bus/usb-type-c-ucsi-spec.html + + To compile the driver as a module, choose M here: the module will be + called typec_ucsi. diff --git a/drivers/usb/typec/ucsi/Makefile b/drivers/usb/typec/ucsi/Makefile new file mode 100644 index 000000000000..87dd6ee6c9f3 --- /dev/null +++ b/drivers/usb/typec/ucsi/Makefile @@ -0,0 +1,7 @@ +CFLAGS_trace.o := -I$(src) + +obj-$(CONFIG_TYPEC_UCSI) += typec_ucsi.o + +typec_ucsi-y := ucsi.o + +typec_ucsi-$(CONFIG_FTRACE) += trace.o diff --git a/drivers/usb/typec/ucsi/debug.h b/drivers/usb/typec/ucsi/debug.h new file mode 100644 index 000000000000..e4d8fc763e6c --- /dev/null +++ b/drivers/usb/typec/ucsi/debug.h @@ -0,0 +1,64 @@ +#ifndef __UCSI_DEBUG_H +#define __UCSI_DEBUG_H + +#include "ucsi.h" + +static const char * const ucsi_cmd_strs[] = { + [0] = "Unknown command", + [UCSI_PPM_RESET] = "PPM_RESET", + [UCSI_CANCEL] = "CANCEL", + [UCSI_CONNECTOR_RESET] = "CONNECTOR_RESET", + [UCSI_ACK_CC_CI] = "ACK_CC_CI", + [UCSI_SET_NOTIFICATION_ENABLE] = "SET_NOTIFICATION_ENABLE", + [UCSI_GET_CAPABILITY] = "GET_CAPABILITY", + [UCSI_GET_CONNECTOR_CAPABILITY] = "GET_CONNECTOR_CAPABILITY", + [UCSI_SET_UOM] = "SET_UOM", + [UCSI_SET_UOR] = "SET_UOR", + [UCSI_SET_PDM] = "SET_PDM", + [UCSI_SET_PDR] = "SET_PDR", + [UCSI_GET_ALTERNATE_MODES] = "GET_ALTERNATE_MODES", + [UCSI_GET_CAM_SUPPORTED] = "GET_CAM_SUPPORTED", + [UCSI_GET_CURRENT_CAM] = "GET_CURRENT_CAM", + [UCSI_SET_NEW_CAM] = "SET_NEW_CAM", + [UCSI_GET_PDOS] = "GET_PDOS", + [UCSI_GET_CABLE_PROPERTY] = "GET_CABLE_PROPERTY", + [UCSI_GET_CONNECTOR_STATUS] = "GET_CONNECTOR_STATUS", + [UCSI_GET_ERROR_STATUS] = "GET_ERROR_STATUS", +}; + +static inline const char *ucsi_cmd_str(u64 raw_cmd) +{ + u8 cmd = raw_cmd & GENMASK(7, 0); + + return ucsi_cmd_strs[(cmd >= ARRAY_SIZE(ucsi_cmd_strs)) ? 0 : cmd]; +} + +static const char * const ucsi_ack_strs[] = { + [0] = "", + [UCSI_ACK_EVENT] = "event", + [UCSI_ACK_CMD] = "command", +}; + +static inline const char *ucsi_ack_str(u8 ack) +{ + return ucsi_ack_strs[(ack >= ARRAY_SIZE(ucsi_ack_strs)) ? 0 : ack]; +} + +static inline const char *ucsi_cci_str(u32 cci) +{ + if (cci & GENMASK(7, 0)) { + if (cci & BIT(29)) + return "Event pending (ACK completed)"; + if (cci & BIT(31)) + return "Event pending (command completed)"; + return "Connector Change"; + } + if (cci & BIT(29)) + return "ACK completed"; + if (cci & BIT(31)) + return "Command completed"; + + return ""; +} + +#endif /* __UCSI_DEBUG_H */ diff --git a/drivers/usb/typec/ucsi/trace.c b/drivers/usb/typec/ucsi/trace.c new file mode 100644 index 000000000000..006f65c72a34 --- /dev/null +++ b/drivers/usb/typec/ucsi/trace.c @@ -0,0 +1,2 @@ +#define CREATE_TRACE_POINTS +#include "trace.h" diff --git a/drivers/usb/typec/ucsi/trace.h b/drivers/usb/typec/ucsi/trace.h new file mode 100644 index 000000000000..98b404404834 --- /dev/null +++ b/drivers/usb/typec/ucsi/trace.h @@ -0,0 +1,143 @@ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM ucsi + +#if !defined(__UCSI_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define __UCSI_TRACE_H + +#include +#include "ucsi.h" +#include "debug.h" + +DECLARE_EVENT_CLASS(ucsi_log_ack, + TP_PROTO(u8 ack), + TP_ARGS(ack), + TP_STRUCT__entry( + __field(u8, ack) + ), + TP_fast_assign( + __entry->ack = ack; + ), + TP_printk("ACK %s", ucsi_ack_str(__entry->ack)) +); + +DEFINE_EVENT(ucsi_log_ack, ucsi_ack, + TP_PROTO(u8 ack), + TP_ARGS(ack) +); + +DECLARE_EVENT_CLASS(ucsi_log_control, + TP_PROTO(struct ucsi_control *ctrl), + TP_ARGS(ctrl), + TP_STRUCT__entry( + __field(u64, ctrl) + ), + TP_fast_assign( + __entry->ctrl = ctrl->raw_cmd; + ), + TP_printk("control=%08llx (%s)", __entry->ctrl, + ucsi_cmd_str(__entry->ctrl)) +); + +DEFINE_EVENT(ucsi_log_control, ucsi_command, + TP_PROTO(struct ucsi_control *ctrl), + TP_ARGS(ctrl) +); + +DECLARE_EVENT_CLASS(ucsi_log_command, + TP_PROTO(struct ucsi_control *ctrl, int ret), + TP_ARGS(ctrl, ret), + TP_STRUCT__entry( + __field(u64, ctrl) + __field(int, ret) + ), + TP_fast_assign( + __entry->ctrl = ctrl->raw_cmd; + __entry->ret = ret; + ), + TP_printk("%s -> %s (err=%d)", ucsi_cmd_str(__entry->ctrl), + __entry->ret < 0 ? "FAIL" : "OK", + __entry->ret < 0 ? __entry->ret : 0) +); + +DEFINE_EVENT(ucsi_log_command, ucsi_run_command, + TP_PROTO(struct ucsi_control *ctrl, int ret), + TP_ARGS(ctrl, ret) +); + +DEFINE_EVENT(ucsi_log_command, ucsi_reset_ppm, + TP_PROTO(struct ucsi_control *ctrl, int ret), + TP_ARGS(ctrl, ret) +); + +DECLARE_EVENT_CLASS(ucsi_log_cci, + TP_PROTO(u32 cci), + TP_ARGS(cci), + TP_STRUCT__entry( + __field(u32, cci) + ), + TP_fast_assign( + __entry->cci = cci; + ), + TP_printk("CCI=%08x %s", __entry->cci, ucsi_cci_str(__entry->cci)) +); + +DEFINE_EVENT(ucsi_log_cci, ucsi_notify, + TP_PROTO(u32 cci), + TP_ARGS(cci) +); + +DECLARE_EVENT_CLASS(ucsi_log_connector_status, + TP_PROTO(int port, struct ucsi_connector_status *status), + TP_ARGS(port, status), + TP_STRUCT__entry( + __field(int, port) + __field(u16, change) + __field(u8, opmode) + __field(u8, connected) + __field(u8, pwr_dir) + __field(u8, partner_flags) + __field(u8, partner_type) + __field(u32, request_data_obj) + __field(u8, bc_status) + ), + TP_fast_assign( + __entry->port = port - 1; + __entry->change = status->change; + __entry->opmode = status->pwr_op_mode; + __entry->connected = status->connected; + __entry->pwr_dir = status->pwr_dir; + __entry->partner_flags = status->partner_flags; + __entry->partner_type = status->partner_type; + __entry->request_data_obj = status->request_data_obj; + __entry->bc_status = status->bc_status; + ), + TP_printk("port%d status: change=%04x, opmode=%x, connected=%d, " + "sourcing=%d, partner_flags=%x, partner_type=%x, " + "request_data_obj=%08x, BC status=%x", __entry->port, + __entry->change, __entry->opmode, __entry->connected, + __entry->pwr_dir, __entry->partner_flags, __entry->partner_type, + __entry->request_data_obj, __entry->bc_status) +); + +DEFINE_EVENT(ucsi_log_connector_status, ucsi_connector_change, + TP_PROTO(int port, struct ucsi_connector_status *status), + TP_ARGS(port, status) +); + +DEFINE_EVENT(ucsi_log_connector_status, ucsi_register_port, + TP_PROTO(int port, struct ucsi_connector_status *status), + TP_ARGS(port, status) +); + +#endif /* __UCSI_TRACE_H */ + +/* This part must be outside protection */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . + +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE trace + +#include diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c new file mode 100644 index 000000000000..714c5bcedf2b --- /dev/null +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -0,0 +1,790 @@ +/* + * USB Type-C Connector System Software Interface driver + * + * Copyright (C) 2017, Intel Corporation + * Author: Heikki Krogerus + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "ucsi.h" +#include "trace.h" + +#define to_ucsi_connector(_cap_) container_of(_cap_, struct ucsi_connector, \ + typec_cap) + +/* + * UCSI_TIMEOUT_MS - PPM communication timeout + * + * Ideally we could use MIN_TIME_TO_RESPOND_WITH_BUSY (which is defined in UCSI + * specification) here as reference, but unfortunately we can't. It is very + * difficult to estimate the time it takes for the system to process the command + * before it is actually passed to the PPM. + */ +#define UCSI_TIMEOUT_MS 1000 + +/* + * UCSI_SWAP_TIMEOUT_MS - Timeout for role swap requests + * + * 5 seconds is close to the time it takes for CapsCounter to reach 0, so even + * if the PPM does not generate Connector Change events before that with + * partners that do not support USB Power Delivery, this should still work. + */ +#define UCSI_SWAP_TIMEOUT_MS 5000 + +enum ucsi_status { + UCSI_IDLE = 0, + UCSI_BUSY, + UCSI_ERROR, +}; + +struct ucsi_connector { + int num; + + struct ucsi *ucsi; + struct work_struct work; + struct completion complete; + + struct typec_port *port; + struct typec_partner *partner; + + struct typec_capability typec_cap; + + struct ucsi_connector_status status; + struct ucsi_connector_capability cap; +}; + +struct ucsi { + struct device *dev; + struct ucsi_ppm *ppm; + + enum ucsi_status status; + struct completion complete; + struct ucsi_capability cap; + struct ucsi_connector *connector; + + struct work_struct work; + + /* PPM Communication lock */ + struct mutex ppm_lock; + + /* PPM communication flags */ + unsigned long flags; +#define EVENT_PENDING 0 +#define COMMAND_PENDING 1 +#define ACK_PENDING 2 +}; + +static inline int ucsi_sync(struct ucsi *ucsi) +{ + if (ucsi->ppm && ucsi->ppm->sync) + return ucsi->ppm->sync(ucsi->ppm); + return 0; +} + +static int ucsi_command(struct ucsi *ucsi, struct ucsi_control *ctrl) +{ + int ret; + + trace_ucsi_command(ctrl); + + set_bit(COMMAND_PENDING, &ucsi->flags); + + ret = ucsi->ppm->cmd(ucsi->ppm, ctrl); + if (ret) + goto err_clear_flag; + + if (!wait_for_completion_timeout(&ucsi->complete, + msecs_to_jiffies(UCSI_TIMEOUT_MS))) { + dev_warn(ucsi->dev, "PPM NOT RESPONDING\n"); + ret = -ETIMEDOUT; + } + +err_clear_flag: + clear_bit(COMMAND_PENDING, &ucsi->flags); + + return ret; +} + +static int ucsi_ack(struct ucsi *ucsi, u8 ack) +{ + struct ucsi_control ctrl; + int ret; + + trace_ucsi_ack(ack); + + set_bit(ACK_PENDING, &ucsi->flags); + + UCSI_CMD_ACK(ctrl, ack); + ret = ucsi->ppm->cmd(ucsi->ppm, &ctrl); + if (ret) + goto out_clear_bit; + + /* Waiting for ACK with ACK CMD, but not with EVENT for now */ + if (ack == UCSI_ACK_EVENT) + goto out_clear_bit; + + if (!wait_for_completion_timeout(&ucsi->complete, + msecs_to_jiffies(UCSI_TIMEOUT_MS))) + ret = -ETIMEDOUT; + +out_clear_bit: + clear_bit(ACK_PENDING, &ucsi->flags); + + if (ret) + dev_err(ucsi->dev, "%s: failed\n", __func__); + + return ret; +} + +static int ucsi_run_command(struct ucsi *ucsi, struct ucsi_control *ctrl, + void *data, size_t size) +{ + struct ucsi_control _ctrl; + u8 data_length; + u16 error; + int ret; + + ret = ucsi_command(ucsi, ctrl); + if (ret) + goto err; + + switch (ucsi->status) { + case UCSI_IDLE: + ret = ucsi_sync(ucsi); + if (ret) + dev_warn(ucsi->dev, "%s: sync failed\n", __func__); + + if (data) + memcpy(data, ucsi->ppm->data->message_in, size); + + data_length = ucsi->ppm->data->cci.data_length; + + ret = ucsi_ack(ucsi, UCSI_ACK_CMD); + if (!ret) + ret = data_length; + break; + case UCSI_BUSY: + /* The caller decides whether to cancel or not */ + ret = -EBUSY; + break; + case UCSI_ERROR: + ret = ucsi_ack(ucsi, UCSI_ACK_CMD); + if (ret) + break; + + _ctrl.raw_cmd = 0; + _ctrl.cmd.cmd = UCSI_GET_ERROR_STATUS; + ret = ucsi_command(ucsi, &_ctrl); + if (ret) { + dev_err(ucsi->dev, "reading error failed!\n"); + break; + } + + memcpy(&error, ucsi->ppm->data->message_in, sizeof(error)); + + /* Something has really gone wrong */ + if (WARN_ON(ucsi->status == UCSI_ERROR)) { + ret = -ENODEV; + break; + } + + ret = ucsi_ack(ucsi, UCSI_ACK_CMD); + if (ret) + break; + + switch (error) { + case UCSI_ERROR_INCOMPATIBLE_PARTNER: + ret = -EOPNOTSUPP; + break; + case UCSI_ERROR_CC_COMMUNICATION_ERR: + ret = -ECOMM; + break; + case UCSI_ERROR_CONTRACT_NEGOTIATION_FAIL: + ret = -EPROTO; + break; + case UCSI_ERROR_DEAD_BATTERY: + dev_warn(ucsi->dev, "Dead battery condition!\n"); + ret = -EPERM; + break; + /* The following mean a bug in this driver */ + case UCSI_ERROR_INVALID_CON_NUM: + case UCSI_ERROR_UNREGONIZED_CMD: + case UCSI_ERROR_INVALID_CMD_ARGUMENT: + dev_warn(ucsi->dev, + "%s: possible UCSI driver bug - error 0x%x\n", + __func__, error); + ret = -EINVAL; + break; + default: + dev_warn(ucsi->dev, + "%s: error without status\n", __func__); + ret = -EIO; + break; + } + break; + } + +err: + trace_ucsi_run_command(ctrl, ret); + + return ret; +} + +/* -------------------------------------------------------------------------- */ + +static void ucsi_pwr_opmode_change(struct ucsi_connector *con) +{ + switch (con->status.pwr_op_mode) { + case UCSI_CONSTAT_PWR_OPMODE_PD: + typec_set_pwr_opmode(con->port, TYPEC_PWR_MODE_PD); + break; + case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5: + typec_set_pwr_opmode(con->port, TYPEC_PWR_MODE_1_5A); + break; + case UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0: + typec_set_pwr_opmode(con->port, TYPEC_PWR_MODE_3_0A); + break; + default: + typec_set_pwr_opmode(con->port, TYPEC_PWR_MODE_USB); + break; + } +} + +static int ucsi_register_partner(struct ucsi_connector *con) +{ + struct typec_partner_desc partner; + + if (con->partner) + return 0; + + memset(&partner, 0, sizeof(partner)); + + switch (con->status.partner_type) { + case UCSI_CONSTAT_PARTNER_TYPE_DEBUG: + partner.accessory = TYPEC_ACCESSORY_DEBUG; + break; + case UCSI_CONSTAT_PARTNER_TYPE_AUDIO: + partner.accessory = TYPEC_ACCESSORY_AUDIO; + break; + default: + break; + } + + partner.usb_pd = con->status.pwr_op_mode == UCSI_CONSTAT_PWR_OPMODE_PD; + + con->partner = typec_register_partner(con->port, &partner); + if (!con->partner) { + dev_err(con->ucsi->dev, "con%d: failed to register partner\n", + con->num); + return -ENODEV; + } + + return 0; +} + +static void ucsi_unregister_partner(struct ucsi_connector *con) +{ + typec_unregister_partner(con->partner); + con->partner = NULL; +} + +static void ucsi_connector_change(struct work_struct *work) +{ + struct ucsi_connector *con = container_of(work, struct ucsi_connector, + work); + struct ucsi *ucsi = con->ucsi; + struct ucsi_control ctrl; + int ret; + + mutex_lock(&ucsi->ppm_lock); + + UCSI_CMD_GET_CONNECTOR_STATUS(ctrl, con->num); + ret = ucsi_run_command(ucsi, &ctrl, &con->status, sizeof(con->status)); + if (ret < 0) { + dev_err(ucsi->dev, "%s: GET_CONNECTOR_STATUS failed (%d)\n", + __func__, ret); + goto out_unlock; + } + + if (con->status.change & UCSI_CONSTAT_POWER_OPMODE_CHANGE) + ucsi_pwr_opmode_change(con); + + if (con->status.change & UCSI_CONSTAT_POWER_DIR_CHANGE) { + typec_set_pwr_role(con->port, con->status.pwr_dir); + + /* Complete pending power role swap */ + if (!completion_done(&con->complete)) + complete(&con->complete); + } + + if (con->status.change & UCSI_CONSTAT_PARTNER_CHANGE) { + switch (con->status.partner_type) { + case UCSI_CONSTAT_PARTNER_TYPE_UFP: + typec_set_data_role(con->port, TYPEC_HOST); + break; + case UCSI_CONSTAT_PARTNER_TYPE_DFP: + typec_set_data_role(con->port, TYPEC_DEVICE); + break; + default: + break; + } + + /* Complete pending data role swap */ + if (!completion_done(&con->complete)) + complete(&con->complete); + } + + if (con->status.change & UCSI_CONSTAT_CONNECT_CHANGE) { + if (con->status.connected) + ucsi_register_partner(con); + else + ucsi_unregister_partner(con); + } + + ret = ucsi_ack(ucsi, UCSI_ACK_EVENT); + if (ret) + dev_err(ucsi->dev, "%s: ACK failed (%d)", __func__, ret); + + trace_ucsi_connector_change(con->num, &con->status); + +out_unlock: + clear_bit(EVENT_PENDING, &ucsi->flags); + mutex_unlock(&ucsi->ppm_lock); +} + +/** + * ucsi_notify - PPM notification handler + * @ucsi: Source UCSI Interface for the notifications + * + * Handle notifications from PPM of @ucsi. + */ +void ucsi_notify(struct ucsi *ucsi) +{ + struct ucsi_cci *cci; + + /* There is no requirement to sync here, but no harm either. */ + ucsi_sync(ucsi); + + cci = &ucsi->ppm->data->cci; + + if (cci->error) + ucsi->status = UCSI_ERROR; + else if (cci->busy) + ucsi->status = UCSI_BUSY; + else + ucsi->status = UCSI_IDLE; + + if (cci->cmd_complete && test_bit(COMMAND_PENDING, &ucsi->flags)) { + complete(&ucsi->complete); + } else if (cci->ack_complete && test_bit(ACK_PENDING, &ucsi->flags)) { + complete(&ucsi->complete); + } else if (cci->connector_change) { + struct ucsi_connector *con; + + con = &ucsi->connector[cci->connector_change - 1]; + + if (!test_and_set_bit(EVENT_PENDING, &ucsi->flags)) + schedule_work(&con->work); + } + + trace_ucsi_notify(ucsi->ppm->data->raw_cci); +} +EXPORT_SYMBOL_GPL(ucsi_notify); + +/* -------------------------------------------------------------------------- */ + +static int ucsi_reset_connector(struct ucsi_connector *con, bool hard) +{ + struct ucsi_control ctrl; + + UCSI_CMD_CONNECTOR_RESET(ctrl, con, hard); + + return ucsi_run_command(con->ucsi, &ctrl, NULL, 0); +} + +static int ucsi_reset_ppm(struct ucsi *ucsi) +{ + struct ucsi_control ctrl; + unsigned long tmo; + int ret; + + ctrl.raw_cmd = 0; + ctrl.cmd.cmd = UCSI_PPM_RESET; + trace_ucsi_command(&ctrl); + ret = ucsi->ppm->cmd(ucsi->ppm, &ctrl); + if (ret) + goto err; + + tmo = jiffies + msecs_to_jiffies(UCSI_TIMEOUT_MS); + + do { + /* Here sync is critical. */ + ret = ucsi_sync(ucsi); + if (ret) + goto err; + + if (ucsi->ppm->data->cci.reset_complete) + break; + + /* If the PPM is still doing something else, reset it again. */ + if (ucsi->ppm->data->raw_cci) { + dev_warn_ratelimited(ucsi->dev, + "Failed to reset PPM! Trying again..\n"); + + trace_ucsi_command(&ctrl); + ret = ucsi->ppm->cmd(ucsi->ppm, &ctrl); + if (ret) + goto err; + } + + /* Letting the PPM settle down. */ + msleep(20); + + ret = -ETIMEDOUT; + } while (time_is_after_jiffies(tmo)); + +err: + trace_ucsi_reset_ppm(&ctrl, ret); + + return ret; +} + +static int ucsi_role_cmd(struct ucsi_connector *con, struct ucsi_control *ctrl) +{ + int ret; + + ret = ucsi_run_command(con->ucsi, ctrl, NULL, 0); + if (ret == -ETIMEDOUT) { + struct ucsi_control c; + + /* PPM most likely stopped responding. Resetting everything. */ + ucsi_reset_ppm(con->ucsi); + + UCSI_CMD_SET_NTFY_ENABLE(c, UCSI_ENABLE_NTFY_ALL); + ucsi_run_command(con->ucsi, &c, NULL, 0); + + ucsi_reset_connector(con, true); + } + + return ret; +} + +static int +ucsi_dr_swap(const struct typec_capability *cap, enum typec_data_role role) +{ + struct ucsi_connector *con = to_ucsi_connector(cap); + struct ucsi_control ctrl; + int ret = 0; + + if (!con->partner) + return -ENOTCONN; + + mutex_lock(&con->ucsi->ppm_lock); + + if ((con->status.partner_type == UCSI_CONSTAT_PARTNER_TYPE_DFP && + role == TYPEC_DEVICE) || + (con->status.partner_type == UCSI_CONSTAT_PARTNER_TYPE_UFP && + role == TYPEC_HOST)) + goto out_unlock; + + UCSI_CMD_SET_UOR(ctrl, con, role); + ret = ucsi_role_cmd(con, &ctrl); + if (ret < 0) + goto out_unlock; + + mutex_unlock(&con->ucsi->ppm_lock); + + if (!wait_for_completion_timeout(&con->complete, + msecs_to_jiffies(UCSI_SWAP_TIMEOUT_MS))) + return -ETIMEDOUT; + + return 0; + +out_unlock: + mutex_unlock(&con->ucsi->ppm_lock); + + return ret; +} + +static int +ucsi_pr_swap(const struct typec_capability *cap, enum typec_role role) +{ + struct ucsi_connector *con = to_ucsi_connector(cap); + struct ucsi_control ctrl; + int ret = 0; + + if (!con->partner) + return -ENOTCONN; + + mutex_lock(&con->ucsi->ppm_lock); + + if (con->status.pwr_dir == role) + goto out_unlock; + + UCSI_CMD_SET_PDR(ctrl, con, role); + ret = ucsi_role_cmd(con, &ctrl); + if (ret < 0) + goto out_unlock; + + mutex_unlock(&con->ucsi->ppm_lock); + + if (!wait_for_completion_timeout(&con->complete, + msecs_to_jiffies(UCSI_SWAP_TIMEOUT_MS))) + return -ETIMEDOUT; + + mutex_lock(&con->ucsi->ppm_lock); + + /* Something has gone wrong while swapping the role */ + if (con->status.pwr_op_mode != UCSI_CONSTAT_PWR_OPMODE_PD) { + ucsi_reset_connector(con, true); + ret = -EPROTO; + } + +out_unlock: + mutex_unlock(&con->ucsi->ppm_lock); + + return ret; +} + +static struct fwnode_handle *ucsi_find_fwnode(struct ucsi_connector *con) +{ + struct fwnode_handle *fwnode; + int i = 1; + + device_for_each_child_node(con->ucsi->dev, fwnode) + if (i++ == con->num) + return fwnode; + return NULL; +} + +static int ucsi_register_port(struct ucsi *ucsi, int index) +{ + struct ucsi_connector *con = &ucsi->connector[index]; + struct typec_capability *cap = &con->typec_cap; + enum typec_accessory *accessory = cap->accessory; + struct ucsi_control ctrl; + int ret; + + INIT_WORK(&con->work, ucsi_connector_change); + init_completion(&con->complete); + con->num = index + 1; + con->ucsi = ucsi; + + /* Get connector capability */ + UCSI_CMD_GET_CONNECTOR_CAPABILITY(ctrl, con->num); + ret = ucsi_run_command(ucsi, &ctrl, &con->cap, sizeof(con->cap)); + if (ret < 0) + return ret; + + if (con->cap.op_mode & UCSI_CONCAP_OPMODE_DRP) + cap->type = TYPEC_PORT_DRP; + else if (con->cap.op_mode & UCSI_CONCAP_OPMODE_DFP) + cap->type = TYPEC_PORT_DFP; + else if (con->cap.op_mode & UCSI_CONCAP_OPMODE_UFP) + cap->type = TYPEC_PORT_UFP; + + cap->revision = ucsi->cap.typec_version; + cap->pd_revision = ucsi->cap.pd_version; + cap->prefer_role = TYPEC_NO_PREFERRED_ROLE; + + if (con->cap.op_mode & UCSI_CONCAP_OPMODE_AUDIO_ACCESSORY) + *accessory++ = TYPEC_ACCESSORY_AUDIO; + if (con->cap.op_mode & UCSI_CONCAP_OPMODE_DEBUG_ACCESSORY) + *accessory = TYPEC_ACCESSORY_DEBUG; + + cap->fwnode = ucsi_find_fwnode(con); + cap->dr_set = ucsi_dr_swap; + cap->pr_set = ucsi_pr_swap; + + /* Register the connector */ + con->port = typec_register_port(ucsi->dev, cap); + if (!con->port) + return -ENODEV; + + /* Get the status */ + UCSI_CMD_GET_CONNECTOR_STATUS(ctrl, con->num); + ret = ucsi_run_command(ucsi, &ctrl, &con->status, sizeof(con->status)); + if (ret < 0) { + dev_err(ucsi->dev, "con%d: failed to get status\n", con->num); + return 0; + } + + ucsi_pwr_opmode_change(con); + typec_set_pwr_role(con->port, con->status.pwr_dir); + + switch (con->status.partner_type) { + case UCSI_CONSTAT_PARTNER_TYPE_UFP: + typec_set_data_role(con->port, TYPEC_HOST); + break; + case UCSI_CONSTAT_PARTNER_TYPE_DFP: + typec_set_data_role(con->port, TYPEC_DEVICE); + break; + default: + break; + } + + /* Check if there is already something connected */ + if (con->status.connected) + ucsi_register_partner(con); + + trace_ucsi_register_port(con->num, &con->status); + + return 0; +} + +static void ucsi_init(struct work_struct *work) +{ + struct ucsi *ucsi = container_of(work, struct ucsi, work); + struct ucsi_connector *con; + struct ucsi_control ctrl; + int ret; + int i; + + mutex_lock(&ucsi->ppm_lock); + + /* Reset the PPM */ + ret = ucsi_reset_ppm(ucsi); + if (ret) { + dev_err(ucsi->dev, "failed to reset PPM!\n"); + goto err; + } + + /* Enable basic notifications */ + UCSI_CMD_SET_NTFY_ENABLE(ctrl, UCSI_ENABLE_NTFY_CMD_COMPLETE | + UCSI_ENABLE_NTFY_ERROR); + ret = ucsi_run_command(ucsi, &ctrl, NULL, 0); + if (ret < 0) + goto err_reset; + + /* Get PPM capabilities */ + UCSI_CMD_GET_CAPABILITY(ctrl); + ret = ucsi_run_command(ucsi, &ctrl, &ucsi->cap, sizeof(ucsi->cap)); + if (ret < 0) + goto err_reset; + + if (!ucsi->cap.num_connectors) { + ret = -ENODEV; + goto err_reset; + } + + /* Allocate the connectors. Released in ucsi_unregister_ppm() */ + ucsi->connector = kcalloc(ucsi->cap.num_connectors + 1, + sizeof(*ucsi->connector), GFP_KERNEL); + if (!ucsi->connector) { + ret = -ENOMEM; + goto err_reset; + } + + /* Register all connectors */ + for (i = 0; i < ucsi->cap.num_connectors; i++) { + ret = ucsi_register_port(ucsi, i); + if (ret) + goto err_unregister; + } + + /* Enable all notifications */ + UCSI_CMD_SET_NTFY_ENABLE(ctrl, UCSI_ENABLE_NTFY_ALL); + ret = ucsi_run_command(ucsi, &ctrl, NULL, 0); + if (ret < 0) + goto err_unregister; + + mutex_unlock(&ucsi->ppm_lock); + + return; + +err_unregister: + for (con = ucsi->connector; con->port; con++) { + ucsi_unregister_partner(con); + typec_unregister_port(con->port); + con->port = NULL; + } + +err_reset: + ucsi_reset_ppm(ucsi); +err: + mutex_unlock(&ucsi->ppm_lock); + dev_err(ucsi->dev, "PPM init failed (%d)\n", ret); +} + +/** + * ucsi_register_ppm - Register UCSI PPM Interface + * @dev: Device interface to the PPM + * @ppm: The PPM interface + * + * Allocates UCSI instance, associates it with @ppm and returns it to the + * caller, and schedules initialization of the interface. + */ +struct ucsi *ucsi_register_ppm(struct device *dev, struct ucsi_ppm *ppm) +{ + struct ucsi *ucsi; + + ucsi = kzalloc(sizeof(*ucsi), GFP_KERNEL); + if (!ucsi) + return ERR_PTR(-ENOMEM); + + INIT_WORK(&ucsi->work, ucsi_init); + init_completion(&ucsi->complete); + mutex_init(&ucsi->ppm_lock); + + ucsi->dev = dev; + ucsi->ppm = ppm; + + /* + * Communication with the PPM takes a lot of time. It is not reasonable + * to initialize the driver here. Using a work for now. + */ + queue_work(system_long_wq, &ucsi->work); + + return ucsi; +} +EXPORT_SYMBOL_GPL(ucsi_register_ppm); + +/** + * ucsi_unregister_ppm - Unregister UCSI PPM Interface + * @ucsi: struct ucsi associated with the PPM + * + * Unregister UCSI PPM that was created with ucsi_register(). + */ +void ucsi_unregister_ppm(struct ucsi *ucsi) +{ + struct ucsi_control ctrl; + int i; + + /* Make sure that we are not in the middle of driver initialization */ + cancel_work_sync(&ucsi->work); + + mutex_lock(&ucsi->ppm_lock); + + /* Disable everything except command complete notification */ + UCSI_CMD_SET_NTFY_ENABLE(ctrl, UCSI_ENABLE_NTFY_CMD_COMPLETE) + ucsi_run_command(ucsi, &ctrl, NULL, 0); + + mutex_unlock(&ucsi->ppm_lock); + + for (i = 0; i < ucsi->cap.num_connectors; i++) { + cancel_work_sync(&ucsi->connector[i].work); + ucsi_unregister_partner(&ucsi->connector[i]); + typec_unregister_port(ucsi->connector[i].port); + } + + ucsi_reset_ppm(ucsi); + + kfree(ucsi->connector); + kfree(ucsi); +} +EXPORT_SYMBOL_GPL(ucsi_unregister_ppm); + +MODULE_AUTHOR("Heikki Krogerus "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("USB Type-C Connector System Software Interface driver"); diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h new file mode 100644 index 000000000000..6b0d2f0918c6 --- /dev/null +++ b/drivers/usb/typec/ucsi/ucsi.h @@ -0,0 +1,335 @@ + +#ifndef __DRIVER_USB_TYPEC_UCSI_H +#define __DRIVER_USB_TYPEC_UCSI_H + +#include +#include + +/* -------------------------------------------------------------------------- */ + +/* Command Status and Connector Change Indication (CCI) data structure */ +struct ucsi_cci { + u8:1; /* reserved */ + u8 connector_change:7; + u8 data_length; + u16:9; /* reserved */ + u16 not_supported:1; + u16 cancel_complete:1; + u16 reset_complete:1; + u16 busy:1; + u16 ack_complete:1; + u16 error:1; + u16 cmd_complete:1; +} __packed; + +/* Default fields in CONTROL data structure */ +struct ucsi_command { + u8 cmd; + u8 length; + u64 data:48; +} __packed; + +/* ACK Command structure */ +struct ucsi_ack_cmd { + u8 cmd; + u8 length; + u8 cci_ack:1; + u8 cmd_ack:1; + u8:6; /* reserved */ +} __packed; + +/* Connector Reset Command structure */ +struct ucsi_con_rst { + u8 cmd; + u8 length; + u8 con_num:7; + u8 hard_reset:1; +} __packed; + +/* Set USB Operation Mode Command structure */ +struct ucsi_uor_cmd { + u8 cmd; + u8 length; + u16 con_num:7; + u16 role:3; +#define UCSI_UOR_ROLE_DFP BIT(0) +#define UCSI_UOR_ROLE_UFP BIT(1) +#define UCSI_UOR_ROLE_DRP BIT(2) + u16:6; /* reserved */ +} __packed; + +struct ucsi_control { + union { + u64 raw_cmd; + struct ucsi_command cmd; + struct ucsi_uor_cmd uor; + struct ucsi_ack_cmd ack; + struct ucsi_con_rst con_rst; + }; +}; + +#define __UCSI_CMD(_ctrl_, _cmd_) \ +{ \ + (_ctrl_).raw_cmd = 0; \ + (_ctrl_).cmd.cmd = _cmd_; \ +} + +/* Helper for preparing ucsi_control for CONNECTOR_RESET command. */ +#define UCSI_CMD_CONNECTOR_RESET(_ctrl_, _con_, _hard_) \ +{ \ + __UCSI_CMD(_ctrl_, UCSI_CONNECTOR_RESET) \ + (_ctrl_).con_rst.con_num = (_con_)->num; \ + (_ctrl_).con_rst.hard_reset = _hard_; \ +} + +/* Helper for preparing ucsi_control for ACK_CC_CI command. */ +#define UCSI_CMD_ACK(_ctrl_, _ack_) \ +{ \ + __UCSI_CMD(_ctrl_, UCSI_ACK_CC_CI) \ + (_ctrl_).ack.cci_ack = ((_ack_) == UCSI_ACK_EVENT); \ + (_ctrl_).ack.cmd_ack = ((_ack_) == UCSI_ACK_CMD); \ +} + +/* Helper for preparing ucsi_control for SET_NOTIFY_ENABLE command. */ +#define UCSI_CMD_SET_NTFY_ENABLE(_ctrl_, _ntfys_) \ +{ \ + __UCSI_CMD(_ctrl_, UCSI_SET_NOTIFICATION_ENABLE) \ + (_ctrl_).cmd.data = _ntfys_; \ +} + +/* Helper for preparing ucsi_control for GET_CAPABILITY command. */ +#define UCSI_CMD_GET_CAPABILITY(_ctrl_) \ +{ \ + __UCSI_CMD(_ctrl_, UCSI_GET_CAPABILITY) \ +} + +/* Helper for preparing ucsi_control for GET_CONNECTOR_CAPABILITY command. */ +#define UCSI_CMD_GET_CONNECTOR_CAPABILITY(_ctrl_, _con_) \ +{ \ + __UCSI_CMD(_ctrl_, UCSI_GET_CONNECTOR_CAPABILITY) \ + (_ctrl_).cmd.data = _con_; \ +} + +/* Helper for preparing ucsi_control for GET_CONNECTOR_STATUS command. */ +#define UCSI_CMD_GET_CONNECTOR_STATUS(_ctrl_, _con_) \ +{ \ + __UCSI_CMD(_ctrl_, UCSI_GET_CONNECTOR_STATUS) \ + (_ctrl_).cmd.data = _con_; \ +} + +#define __UCSI_ROLE(_ctrl_, _cmd_, _con_num_) \ +{ \ + __UCSI_CMD(_ctrl_, _cmd_) \ + (_ctrl_).uor.con_num = _con_num_; \ + (_ctrl_).uor.role = UCSI_UOR_ROLE_DRP; \ +} + +/* Helper for preparing ucsi_control for SET_UOR command. */ +#define UCSI_CMD_SET_UOR(_ctrl_, _con_, _role_) \ +{ \ + __UCSI_ROLE(_ctrl_, UCSI_SET_UOR, (_con_)->num) \ + (_ctrl_).uor.role |= (_role_) == TYPEC_HOST ? UCSI_UOR_ROLE_DFP : \ + UCSI_UOR_ROLE_UFP; \ +} + +/* Helper for preparing ucsi_control for SET_PDR command. */ +#define UCSI_CMD_SET_PDR(_ctrl_, _con_, _role_) \ +{ \ + __UCSI_ROLE(_ctrl_, UCSI_SET_PDR, (_con_)->num) \ + (_ctrl_).uor.role |= (_role_) == TYPEC_SOURCE ? UCSI_UOR_ROLE_DFP : \ + UCSI_UOR_ROLE_UFP; \ +} + +/* Commands */ +#define UCSI_PPM_RESET 0x01 +#define UCSI_CANCEL 0x02 +#define UCSI_CONNECTOR_RESET 0x03 +#define UCSI_ACK_CC_CI 0x04 +#define UCSI_SET_NOTIFICATION_ENABLE 0x05 +#define UCSI_GET_CAPABILITY 0x06 +#define UCSI_GET_CONNECTOR_CAPABILITY 0x07 +#define UCSI_SET_UOM 0x08 +#define UCSI_SET_UOR 0x09 +#define UCSI_SET_PDM 0x0a +#define UCSI_SET_PDR 0x0b +#define UCSI_GET_ALTERNATE_MODES 0x0c +#define UCSI_GET_CAM_SUPPORTED 0x0d +#define UCSI_GET_CURRENT_CAM 0x0e +#define UCSI_SET_NEW_CAM 0x0f +#define UCSI_GET_PDOS 0x10 +#define UCSI_GET_CABLE_PROPERTY 0x11 +#define UCSI_GET_CONNECTOR_STATUS 0x12 +#define UCSI_GET_ERROR_STATUS 0x13 + +/* ACK_CC_CI commands */ +#define UCSI_ACK_EVENT 1 +#define UCSI_ACK_CMD 2 + +/* Bits for SET_NOTIFICATION_ENABLE command */ +#define UCSI_ENABLE_NTFY_CMD_COMPLETE BIT(0) +#define UCSI_ENABLE_NTFY_EXT_PWR_SRC_CHANGE BIT(1) +#define UCSI_ENABLE_NTFY_PWR_OPMODE_CHANGE BIT(2) +#define UCSI_ENABLE_NTFY_CAP_CHANGE BIT(5) +#define UCSI_ENABLE_NTFY_PWR_LEVEL_CHANGE BIT(6) +#define UCSI_ENABLE_NTFY_PD_RESET_COMPLETE BIT(7) +#define UCSI_ENABLE_NTFY_CAM_CHANGE BIT(8) +#define UCSI_ENABLE_NTFY_BAT_STATUS_CHANGE BIT(9) +#define UCSI_ENABLE_NTFY_PARTNER_CHANGE BIT(11) +#define UCSI_ENABLE_NTFY_PWR_DIR_CHANGE BIT(12) +#define UCSI_ENABLE_NTFY_CONNECTOR_CHANGE BIT(14) +#define UCSI_ENABLE_NTFY_ERROR BIT(15) +#define UCSI_ENABLE_NTFY_ALL 0xdbe7 + +/* Error information returned by PPM in response to GET_ERROR_STATUS command. */ +#define UCSI_ERROR_UNREGONIZED_CMD BIT(0) +#define UCSI_ERROR_INVALID_CON_NUM BIT(1) +#define UCSI_ERROR_INVALID_CMD_ARGUMENT BIT(2) +#define UCSI_ERROR_INCOMPATIBLE_PARTNER BIT(3) +#define UCSI_ERROR_CC_COMMUNICATION_ERR BIT(4) +#define UCSI_ERROR_DEAD_BATTERY BIT(5) +#define UCSI_ERROR_CONTRACT_NEGOTIATION_FAIL BIT(6) + +/* Data structure filled by PPM in response to GET_CAPABILITY command. */ +struct ucsi_capability { + u32 attributes; +#define UCSI_CAP_ATTR_DISABLE_STATE BIT(0) +#define UCSI_CAP_ATTR_BATTERY_CHARGING BIT(1) +#define UCSI_CAP_ATTR_USB_PD BIT(2) +#define UCSI_CAP_ATTR_TYPEC_CURRENT BIT(6) +#define UCSI_CAP_ATTR_POWER_AC_SUPPLY BIT(8) +#define UCSI_CAP_ATTR_POWER_OTHER BIT(10) +#define UCSI_CAP_ATTR_POWER_VBUS BIT(14) + u32 num_connectors:8; + u32 features:24; +#define UCSI_CAP_SET_UOM BIT(0) +#define UCSI_CAP_SET_PDM BIT(1) +#define UCSI_CAP_ALT_MODE_DETAILS BIT(2) +#define UCSI_CAP_ALT_MODE_OVERRIDE BIT(3) +#define UCSI_CAP_PDO_DETAILS BIT(4) +#define UCSI_CAP_CABLE_DETAILS BIT(5) +#define UCSI_CAP_EXT_SUPPLY_NOTIFICATIONS BIT(6) +#define UCSI_CAP_PD_RESET BIT(7) + u8 num_alt_modes; + u8 reserved; + u16 bc_version; + u16 pd_version; + u16 typec_version; +} __packed; + +/* Data structure filled by PPM in response to GET_CONNECTOR_CAPABILITY cmd. */ +struct ucsi_connector_capability { + u8 op_mode; +#define UCSI_CONCAP_OPMODE_DFP BIT(0) +#define UCSI_CONCAP_OPMODE_UFP BIT(1) +#define UCSI_CONCAP_OPMODE_DRP BIT(2) +#define UCSI_CONCAP_OPMODE_AUDIO_ACCESSORY BIT(3) +#define UCSI_CONCAP_OPMODE_DEBUG_ACCESSORY BIT(4) +#define UCSI_CONCAP_OPMODE_USB2 BIT(5) +#define UCSI_CONCAP_OPMODE_USB3 BIT(6) +#define UCSI_CONCAP_OPMODE_ALT_MODE BIT(7) + u8 provider:1; + u8 consumer:1; + u8:6; /* reserved */ +} __packed; + +struct ucsi_altmode { + u16 svid; + u32 mid; +} __packed; + +/* Data structure filled by PPM in response to GET_CABLE_PROPERTY command. */ +struct ucsi_cable_property { + u16 speed_supported; + u8 current_capability; + u8 vbus_in_cable:1; + u8 active_cable:1; + u8 directionality:1; + u8 plug_type:2; +#define UCSI_CABLE_PROPERTY_PLUG_TYPE_A 0 +#define UCSI_CABLE_PROPERTY_PLUG_TYPE_B 1 +#define UCSI_CABLE_PROPERTY_PLUG_TYPE_C 2 +#define UCSI_CABLE_PROPERTY_PLUG_OTHER 3 + u8 mode_support:1; + u8:2; /* reserved */ + u8 latency:4; + u8:4; /* reserved */ +} __packed; + +/* Data structure filled by PPM in response to GET_CONNECTOR_STATUS command. */ +struct ucsi_connector_status { + u16 change; +#define UCSI_CONSTAT_EXT_SUPPLY_CHANGE BIT(1) +#define UCSI_CONSTAT_POWER_OPMODE_CHANGE BIT(2) +#define UCSI_CONSTAT_PDOS_CHANGE BIT(5) +#define UCSI_CONSTAT_POWER_LEVEL_CHANGE BIT(6) +#define UCSI_CONSTAT_PD_RESET_COMPLETE BIT(7) +#define UCSI_CONSTAT_CAM_CHANGE BIT(8) +#define UCSI_CONSTAT_BC_CHANGE BIT(9) +#define UCSI_CONSTAT_PARTNER_CHANGE BIT(11) +#define UCSI_CONSTAT_POWER_DIR_CHANGE BIT(12) +#define UCSI_CONSTAT_CONNECT_CHANGE BIT(14) +#define UCSI_CONSTAT_ERROR BIT(15) + u16 pwr_op_mode:3; +#define UCSI_CONSTAT_PWR_OPMODE_NONE 0 +#define UCSI_CONSTAT_PWR_OPMODE_DEFAULT 1 +#define UCSI_CONSTAT_PWR_OPMODE_BC 2 +#define UCSI_CONSTAT_PWR_OPMODE_PD 3 +#define UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5 4 +#define UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0 5 + u16 connected:1; + u16 pwr_dir:1; + u16 partner_flags:8; +#define UCSI_CONSTAT_PARTNER_FLAG_USB BIT(0) +#define UCSI_CONSTAT_PARTNER_FLAG_ALT_MODE BIT(1) + u16 partner_type:3; +#define UCSI_CONSTAT_PARTNER_TYPE_DFP 1 +#define UCSI_CONSTAT_PARTNER_TYPE_UFP 2 +#define UCSI_CONSTAT_PARTNER_TYPE_CABLE 3 /* Powered Cable */ +#define UCSI_CONSTAT_PARTNER_TYPE_CABLE_AND_UFP 4 /* Powered Cable */ +#define UCSI_CONSTAT_PARTNER_TYPE_DEBUG 5 +#define UCSI_CONSTAT_PARTNER_TYPE_AUDIO 6 + u32 request_data_obj; + u8 bc_status:2; +#define UCSI_CONSTAT_BC_NOT_CHARGING 0 +#define UCSI_CONSTAT_BC_NOMINAL_CHARGING 1 +#define UCSI_CONSTAT_BC_SLOW_CHARGING 2 +#define UCSI_CONSTAT_BC_TRICKLE_CHARGING 3 + u8 provider_cap_limit_reason:4; +#define UCSI_CONSTAT_CAP_PWR_LOWERED 0 +#define UCSI_CONSTAT_CAP_PWR_BUDGET_LIMIT 1 + u8:2; /* reserved */ +} __packed; + +/* -------------------------------------------------------------------------- */ + +struct ucsi; + +struct ucsi_data { + u16 version; + u16 reserved; + union { + u32 raw_cci; + struct ucsi_cci cci; + }; + struct ucsi_control ctrl; + u32 message_in[4]; + u32 message_out[4]; +} __packed; + +/* + * struct ucsi_ppm - Interface to UCSI Platform Policy Manager + * @data: memory location to the UCSI data structures + * @cmd: UCSI command execution routine + * @sync: Refresh UCSI mailbox (the data structures) + */ +struct ucsi_ppm { + struct ucsi_data *data; + int (*cmd)(struct ucsi_ppm *, struct ucsi_control *); + int (*sync)(struct ucsi_ppm *); +}; + +struct ucsi *ucsi_register_ppm(struct device *dev, struct ucsi_ppm *ppm); +void ucsi_unregister_ppm(struct ucsi *ucsi); +void ucsi_notify(struct ucsi *ucsi); + +#endif /* __DRIVER_USB_TYPEC_UCSI_H */ -- cgit v1.2.3 From 8243edf44152c08c3efa1d551fc48605d674ad18 Mon Sep 17 00:00:00 2001 From: Heikki Krogerus Date: Fri, 16 Jun 2017 11:21:25 +0300 Subject: usb: typec: ucsi: Add ACPI driver Driver for ACPI UCSI interface method. This driver replaces the previous UCSI driver drivers/usb/misc/ucsi.c. Signed-off-by: Heikki Krogerus Reviewed-by: Guenter Roeck Signed-off-by: Greg Kroah-Hartman --- drivers/usb/misc/Kconfig | 26 -- drivers/usb/misc/Makefile | 1 - drivers/usb/misc/ucsi.c | 478 ------------------------------------- drivers/usb/misc/ucsi.h | 215 ----------------- drivers/usb/typec/ucsi/Kconfig | 16 ++ drivers/usb/typec/ucsi/Makefile | 2 + drivers/usb/typec/ucsi/ucsi_acpi.c | 158 ++++++++++++ 7 files changed, 176 insertions(+), 720 deletions(-) delete mode 100644 drivers/usb/misc/ucsi.c delete mode 100644 drivers/usb/misc/ucsi.h create mode 100644 drivers/usb/typec/ucsi/ucsi_acpi.c diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig index 1d1d70d62a19..0f9f25db9163 100644 --- a/drivers/usb/misc/Kconfig +++ b/drivers/usb/misc/Kconfig @@ -275,29 +275,3 @@ config USB_CHAOSKEY To compile this driver as a module, choose M here: the module will be called chaoskey. - -config UCSI - tristate "USB Type-C Connector System Software Interface driver" - depends on ACPI - help - UCSI driver is meant to be used as a convenience tool for desktop and - server systems that are not equipped to handle USB in device mode. It - will always select USB host role for the USB Type-C ports on systems - that provide UCSI interface. - - USB Type-C Connector System Software Interface (UCSI) is a - specification for an interface that allows the Operating System to - control the USB Type-C ports on a system. Things the need controlling - include the USB Data Role (host or device), and when USB Power - Delivery is supported, the Power Role (source or sink). With USB - Type-C connectors, when two dual role capable devices are attached - together, the data role is selected randomly. Therefore it is - important to give the OS a way to select the role. Otherwise the user - would have to unplug and replug in order in order to attempt to swap - the data and power roles. - - The UCSI specification can be downloaded from: - http://www.intel.com/content/www/us/en/io/universal-serial-bus/usb-type-c-ucsi-spec.html - - To compile the driver as a module, choose M here: the module will be - called ucsi. diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile index f6ac6c99a6e6..7fdb45fc976f 100644 --- a/drivers/usb/misc/Makefile +++ b/drivers/usb/misc/Makefile @@ -27,7 +27,6 @@ obj-$(CONFIG_USB_HUB_USB251XB) += usb251xb.o obj-$(CONFIG_USB_HSIC_USB3503) += usb3503.o obj-$(CONFIG_USB_HSIC_USB4604) += usb4604.o obj-$(CONFIG_USB_CHAOSKEY) += chaoskey.o -obj-$(CONFIG_UCSI) += ucsi.o obj-$(CONFIG_USB_SISUSBVGA) += sisusbvga/ obj-$(CONFIG_USB_LINK_LAYER_TEST) += lvstest.o diff --git a/drivers/usb/misc/ucsi.c b/drivers/usb/misc/ucsi.c deleted file mode 100644 index 07397bddefa3..000000000000 --- a/drivers/usb/misc/ucsi.c +++ /dev/null @@ -1,478 +0,0 @@ -/* - * USB Type-C Connector System Software Interface driver - * - * Copyright (C) 2016, Intel Corporation - * Author: Heikki Krogerus - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include -#include -#include -#include - -#include "ucsi.h" - -/* Double the time defined by MIN_TIME_TO_RESPOND_WITH_BUSY */ -#define UCSI_TIMEOUT_MS 20 - -enum ucsi_status { - UCSI_IDLE = 0, - UCSI_BUSY, - UCSI_ERROR, -}; - -struct ucsi_connector { - int num; - struct ucsi *ucsi; - struct work_struct work; - struct ucsi_connector_capability cap; -}; - -struct ucsi { - struct device *dev; - struct ucsi_data __iomem *data; - - enum ucsi_status status; - struct completion complete; - struct ucsi_capability cap; - struct ucsi_connector *connector; - - /* device lock */ - spinlock_t dev_lock; - - /* PPM Communication lock */ - struct mutex ppm_lock; - - /* PPM communication flags */ - unsigned long flags; -#define EVENT_PENDING 0 -#define COMMAND_PENDING 1 -}; - -static int ucsi_acpi_cmd(struct ucsi *ucsi, struct ucsi_control *ctrl) -{ - uuid_le uuid = UUID_LE(0x6f8398c2, 0x7ca4, 0x11e4, - 0xad, 0x36, 0x63, 0x10, 0x42, 0xb5, 0x00, 0x8f); - union acpi_object *obj; - - ucsi->data->ctrl.raw_cmd = ctrl->raw_cmd; - - obj = acpi_evaluate_dsm(ACPI_HANDLE(ucsi->dev), uuid.b, 1, 1, NULL); - if (!obj) { - dev_err(ucsi->dev, "%s: failed to evaluate _DSM\n", __func__); - return -EIO; - } - - ACPI_FREE(obj); - return 0; -} - -static void ucsi_acpi_notify(acpi_handle handle, u32 event, void *data) -{ - struct ucsi *ucsi = data; - struct ucsi_cci *cci; - - spin_lock(&ucsi->dev_lock); - - ucsi->status = UCSI_IDLE; - cci = &ucsi->data->cci; - - /* - * REVISIT: This is not documented behavior, but all known PPMs ACK - * asynchronous events by sending notification with cleared CCI. - */ - if (!ucsi->data->raw_cci) { - if (test_bit(EVENT_PENDING, &ucsi->flags)) - complete(&ucsi->complete); - else - dev_WARN(ucsi->dev, "spurious notification\n"); - goto out_unlock; - } - - if (test_bit(COMMAND_PENDING, &ucsi->flags)) { - if (cci->busy) { - ucsi->status = UCSI_BUSY; - complete(&ucsi->complete); - - goto out_unlock; - } else if (cci->ack_complete || cci->cmd_complete) { - /* Error Indication is only valid with commands */ - if (cci->error && cci->cmd_complete) - ucsi->status = UCSI_ERROR; - - ucsi->data->ctrl.raw_cmd = 0; - complete(&ucsi->complete); - } - } - - if (cci->connector_change) { - struct ucsi_connector *con; - - /* - * This is workaround for buggy PPMs that create asynchronous - * event notifications before OPM has enabled them. - */ - if (!ucsi->connector) - goto out_unlock; - - con = ucsi->connector + (cci->connector_change - 1); - - /* - * PPM will not clear the connector specific bit in Connector - * Change Indication field of CCI until the driver has ACK it, - * and the driver can not ACK it before it has been processed. - * The PPM will not generate new events before the first has - * been acknowledged, even if they are for an other connector. - * So only one event at a time. - */ - if (!test_and_set_bit(EVENT_PENDING, &ucsi->flags)) - schedule_work(&con->work); - } -out_unlock: - spin_unlock(&ucsi->dev_lock); -} - -static int ucsi_ack(struct ucsi *ucsi, u8 cmd) -{ - struct ucsi_control ctrl; - int ret; - - ctrl.cmd.cmd = UCSI_ACK_CC_CI; - ctrl.cmd.length = 0; - ctrl.cmd.data = cmd; - ret = ucsi_acpi_cmd(ucsi, &ctrl); - if (ret) - return ret; - - /* Waiting for ACK also with ACK CMD for now */ - ret = wait_for_completion_timeout(&ucsi->complete, - msecs_to_jiffies(UCSI_TIMEOUT_MS)); - if (!ret) - return -ETIMEDOUT; - return 0; -} - -static int ucsi_run_cmd(struct ucsi *ucsi, struct ucsi_control *ctrl, - void *data, size_t size) -{ - u16 err_value = 0; - int ret; - - set_bit(COMMAND_PENDING, &ucsi->flags); - - ret = ucsi_acpi_cmd(ucsi, ctrl); - if (ret) - goto err_clear_flag; - - ret = wait_for_completion_timeout(&ucsi->complete, - msecs_to_jiffies(UCSI_TIMEOUT_MS)); - if (!ret) { - ret = -ETIMEDOUT; - goto err_clear_flag; - } - - switch (ucsi->status) { - case UCSI_IDLE: - if (data) - memcpy(data, ucsi->data->message_in, size); - - ret = ucsi_ack(ucsi, UCSI_ACK_CMD); - break; - case UCSI_BUSY: - /* The caller decides whether to cancel or not */ - ret = -EBUSY; - goto err_clear_flag; - case UCSI_ERROR: - ret = ucsi_ack(ucsi, UCSI_ACK_CMD); - if (ret) - goto err_clear_flag; - - ctrl->cmd.cmd = UCSI_GET_ERROR_STATUS; - ctrl->cmd.length = 0; - ctrl->cmd.data = 0; - ret = ucsi_acpi_cmd(ucsi, ctrl); - if (ret) - goto err_clear_flag; - - ret = wait_for_completion_timeout(&ucsi->complete, - msecs_to_jiffies(UCSI_TIMEOUT_MS)); - if (!ret) { - ret = -ETIMEDOUT; - goto err_clear_flag; - } - - memcpy(&err_value, ucsi->data->message_in, sizeof(err_value)); - - /* Something has really gone wrong */ - if (WARN_ON(ucsi->status == UCSI_ERROR)) { - ret = -ENODEV; - goto err_clear_flag; - } - - ret = ucsi_ack(ucsi, UCSI_ACK_CMD); - if (ret) - goto err_clear_flag; - - switch (err_value) { - case UCSI_ERROR_INCOMPATIBLE_PARTNER: - ret = -EOPNOTSUPP; - break; - case UCSI_ERROR_CC_COMMUNICATION_ERR: - ret = -ECOMM; - break; - case UCSI_ERROR_CONTRACT_NEGOTIATION_FAIL: - ret = -EIO; - break; - case UCSI_ERROR_DEAD_BATTERY: - dev_warn(ucsi->dev, "Dead battery condition!\n"); - ret = -EPERM; - break; - /* The following mean a bug in this driver */ - case UCSI_ERROR_INVALID_CON_NUM: - case UCSI_ERROR_UNREGONIZED_CMD: - case UCSI_ERROR_INVALID_CMD_ARGUMENT: - default: - dev_warn(ucsi->dev, - "%s: possible UCSI driver bug - error %hu\n", - __func__, err_value); - ret = -EINVAL; - break; - } - break; - } - ctrl->raw_cmd = 0; -err_clear_flag: - clear_bit(COMMAND_PENDING, &ucsi->flags); - return ret; -} - -static void ucsi_connector_change(struct work_struct *work) -{ - struct ucsi_connector *con = container_of(work, struct ucsi_connector, - work); - struct ucsi_connector_status constat; - struct ucsi *ucsi = con->ucsi; - struct ucsi_control ctrl; - int ret; - - mutex_lock(&ucsi->ppm_lock); - - ctrl.cmd.cmd = UCSI_GET_CONNECTOR_STATUS; - ctrl.cmd.length = 0; - ctrl.cmd.data = con->num; - ret = ucsi_run_cmd(con->ucsi, &ctrl, &constat, sizeof(constat)); - if (ret) { - dev_err(ucsi->dev, "%s: failed to read connector status (%d)\n", - __func__, ret); - goto out_ack_event; - } - - /* Ignoring disconnections and Alternate Modes */ - if (!constat.connected || !(constat.change & - (UCSI_CONSTAT_PARTNER_CHANGE | UCSI_CONSTAT_CONNECT_CHANGE)) || - constat.partner_flags & UCSI_CONSTAT_PARTNER_FLAG_ALT_MODE) - goto out_ack_event; - - /* If the partner got USB Host role, attempting swap */ - if (constat.partner_type & UCSI_CONSTAT_PARTNER_TYPE_DFP) { - ctrl.uor.cmd = UCSI_SET_UOR; - ctrl.uor.con_num = con->num; - ctrl.uor.role = UCSI_UOR_ROLE_DFP; - - ret = ucsi_run_cmd(con->ucsi, &ctrl, NULL, 0); - if (ret) - dev_err(ucsi->dev, "%s: failed to swap role (%d)\n", - __func__, ret); - } -out_ack_event: - ucsi_ack(ucsi, UCSI_ACK_EVENT); - clear_bit(EVENT_PENDING, &ucsi->flags); - mutex_unlock(&ucsi->ppm_lock); -} - -static int ucsi_reset_ppm(struct ucsi *ucsi) -{ - int timeout = UCSI_TIMEOUT_MS; - struct ucsi_control ctrl; - int ret; - - memset(&ctrl, 0, sizeof(ctrl)); - ctrl.cmd.cmd = UCSI_PPM_RESET; - ret = ucsi_acpi_cmd(ucsi, &ctrl); - if (ret) - return ret; - - /* There is no quarantee the PPM will ever set the RESET_COMPLETE bit */ - while (!ucsi->data->cci.reset_complete && timeout--) - usleep_range(1000, 2000); - return 0; -} - -static int ucsi_init(struct ucsi *ucsi) -{ - struct ucsi_connector *con; - struct ucsi_control ctrl; - int ret; - int i; - - init_completion(&ucsi->complete); - spin_lock_init(&ucsi->dev_lock); - mutex_init(&ucsi->ppm_lock); - - /* Reset the PPM */ - ret = ucsi_reset_ppm(ucsi); - if (ret) - return ret; - - /* - * REVISIT: Executing second reset to WA an issue seen on some of the - * Broxton based platforms, where the first reset puts the PPM into a - * state where it's unable to recognise some of the commands. - */ - ret = ucsi_reset_ppm(ucsi); - if (ret) - return ret; - - mutex_lock(&ucsi->ppm_lock); - - /* Enable basic notifications */ - ctrl.cmd.cmd = UCSI_SET_NOTIFICATION_ENABLE; - ctrl.cmd.length = 0; - ctrl.cmd.data = UCSI_ENABLE_NTFY_CMD_COMPLETE | UCSI_ENABLE_NTFY_ERROR; - ret = ucsi_run_cmd(ucsi, &ctrl, NULL, 0); - if (ret) - goto err_reset; - - /* Get PPM capabilities */ - ctrl.cmd.cmd = UCSI_GET_CAPABILITY; - ret = ucsi_run_cmd(ucsi, &ctrl, &ucsi->cap, sizeof(ucsi->cap)); - if (ret) - goto err_reset; - - if (!ucsi->cap.num_connectors) { - ret = -ENODEV; - goto err_reset; - } - - ucsi->connector = devm_kcalloc(ucsi->dev, ucsi->cap.num_connectors, - sizeof(*ucsi->connector), GFP_KERNEL); - if (!ucsi->connector) { - ret = -ENOMEM; - goto err_reset; - } - - for (i = 1, con = ucsi->connector; i < ucsi->cap.num_connectors + 1; - i++, con++) { - /* Get connector capability */ - ctrl.cmd.cmd = UCSI_GET_CONNECTOR_CAPABILITY; - ctrl.cmd.data = i; - ret = ucsi_run_cmd(ucsi, &ctrl, &con->cap, sizeof(con->cap)); - if (ret) - goto err_reset; - - con->num = i; - con->ucsi = ucsi; - INIT_WORK(&con->work, ucsi_connector_change); - } - - /* Enable all notifications */ - ctrl.cmd.cmd = UCSI_SET_NOTIFICATION_ENABLE; - ctrl.cmd.data = UCSI_ENABLE_NTFY_ALL; - ret = ucsi_run_cmd(ucsi, &ctrl, NULL, 0); - if (ret < 0) - goto err_reset; - - mutex_unlock(&ucsi->ppm_lock); - return 0; -err_reset: - ucsi_reset_ppm(ucsi); - mutex_unlock(&ucsi->ppm_lock); - return ret; -} - -static int ucsi_acpi_probe(struct platform_device *pdev) -{ - struct resource *res; - acpi_status status; - struct ucsi *ucsi; - int ret; - - ucsi = devm_kzalloc(&pdev->dev, sizeof(*ucsi), GFP_KERNEL); - if (!ucsi) - return -ENOMEM; - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - dev_err(&pdev->dev, "missing memory resource\n"); - return -ENODEV; - } - - /* - * NOTE: ACPI has claimed the memory region as it's also an Operation - * Region. It's not possible to request it in the driver. - */ - ucsi->data = devm_ioremap(&pdev->dev, res->start, resource_size(res)); - if (!ucsi->data) - return -ENOMEM; - - ucsi->dev = &pdev->dev; - - status = acpi_install_notify_handler(ACPI_HANDLE(&pdev->dev), - ACPI_ALL_NOTIFY, - ucsi_acpi_notify, ucsi); - if (ACPI_FAILURE(status)) - return -ENODEV; - - ret = ucsi_init(ucsi); - if (ret) { - acpi_remove_notify_handler(ACPI_HANDLE(&pdev->dev), - ACPI_ALL_NOTIFY, - ucsi_acpi_notify); - return ret; - } - - platform_set_drvdata(pdev, ucsi); - return 0; -} - -static int ucsi_acpi_remove(struct platform_device *pdev) -{ - struct ucsi *ucsi = platform_get_drvdata(pdev); - - acpi_remove_notify_handler(ACPI_HANDLE(&pdev->dev), - ACPI_ALL_NOTIFY, ucsi_acpi_notify); - - /* Make sure there are no events in the middle of being processed */ - if (wait_on_bit_timeout(&ucsi->flags, EVENT_PENDING, - TASK_UNINTERRUPTIBLE, - msecs_to_jiffies(UCSI_TIMEOUT_MS))) - dev_WARN(ucsi->dev, "%s: Events still pending\n", __func__); - - ucsi_reset_ppm(ucsi); - return 0; -} - -static const struct acpi_device_id ucsi_acpi_match[] = { - { "PNP0CA0", 0 }, - { }, -}; -MODULE_DEVICE_TABLE(acpi, ucsi_acpi_match); - -static struct platform_driver ucsi_acpi_platform_driver = { - .driver = { - .name = "ucsi_acpi", - .acpi_match_table = ACPI_PTR(ucsi_acpi_match), - }, - .probe = ucsi_acpi_probe, - .remove = ucsi_acpi_remove, -}; - -module_platform_driver(ucsi_acpi_platform_driver); - -MODULE_AUTHOR("Heikki Krogerus "); -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("USB Type-C System Software Interface (UCSI) driver"); diff --git a/drivers/usb/misc/ucsi.h b/drivers/usb/misc/ucsi.h deleted file mode 100644 index 6dd11d1fe225..000000000000 --- a/drivers/usb/misc/ucsi.h +++ /dev/null @@ -1,215 +0,0 @@ - -#include - -/* -------------------------------------------------------------------------- */ - -/* Command Status and Connector Change Indication (CCI) data structure */ -struct ucsi_cci { - unsigned int RESERVED1:1; - unsigned int connector_change:7; - u8 data_length; - unsigned int RESERVED9:9; - unsigned int not_supported:1; - unsigned int cancel_complete:1; - unsigned int reset_complete:1; - unsigned int busy:1; - unsigned int ack_complete:1; - unsigned int error:1; - unsigned int cmd_complete:1; -} __packed; - -/* Default fields in CONTROL data structure */ -struct ucsi_command { - u8 cmd; - u8 length; - u64 data:48; -} __packed; - -/* Set USB Operation Mode Command structure */ -struct ucsi_uor_cmd { - u8 cmd; - u8 length; - u64 con_num:7; - u64 role:3; -#define UCSI_UOR_ROLE_DFP BIT(0) -#define UCSI_UOR_ROLE_UFP BIT(1) -#define UCSI_UOR_ROLE_DRP BIT(2) - u64 data:38; -} __packed; - -struct ucsi_control { - union { - u64 raw_cmd; - struct ucsi_command cmd; - struct ucsi_uor_cmd uor; - }; -}; - -struct ucsi_data { - u16 version; - u16 RESERVED; - union { - u32 raw_cci; - struct ucsi_cci cci; - }; - struct ucsi_control ctrl; - u32 message_in[4]; - u32 message_out[4]; -} __packed; - -/* Commands */ -#define UCSI_PPM_RESET 0x01 -#define UCSI_CANCEL 0x02 -#define UCSI_CONNECTOR_RESET 0x03 -#define UCSI_ACK_CC_CI 0x04 -#define UCSI_SET_NOTIFICATION_ENABLE 0x05 -#define UCSI_GET_CAPABILITY 0x06 -#define UCSI_GET_CONNECTOR_CAPABILITY 0x07 -#define UCSI_SET_UOM 0x08 -#define UCSI_SET_UOR 0x09 -#define UCSI_SET_PDM 0x0A -#define UCSI_SET_PDR 0x0B -#define UCSI_GET_ALTERNATE_MODES 0x0C -#define UCSI_GET_CAM_SUPPORTED 0x0D -#define UCSI_GET_CURRENT_CAM 0x0E -#define UCSI_SET_NEW_CAM 0x0F -#define UCSI_GET_PDOS 0x10 -#define UCSI_GET_CABLE_PROPERTY 0x11 -#define UCSI_GET_CONNECTOR_STATUS 0x12 -#define UCSI_GET_ERROR_STATUS 0x13 - -/* ACK_CC_CI commands */ -#define UCSI_ACK_EVENT 1 -#define UCSI_ACK_CMD 2 - -/* Bits for SET_NOTIFICATION_ENABLE command */ -#define UCSI_ENABLE_NTFY_CMD_COMPLETE BIT(0) -#define UCSI_ENABLE_NTFY_EXT_PWR_SRC_CHANGE BIT(1) -#define UCSI_ENABLE_NTFY_PWR_OPMODE_CHANGE BIT(2) -#define UCSI_ENABLE_NTFY_CAP_CHANGE BIT(5) -#define UCSI_ENABLE_NTFY_PWR_LEVEL_CHANGE BIT(6) -#define UCSI_ENABLE_NTFY_PD_RESET_COMPLETE BIT(7) -#define UCSI_ENABLE_NTFY_CAM_CHANGE BIT(8) -#define UCSI_ENABLE_NTFY_BAT_STATUS_CHANGE BIT(9) -#define UCSI_ENABLE_NTFY_PARTNER_CHANGE BIT(11) -#define UCSI_ENABLE_NTFY_PWR_DIR_CHANGE BIT(12) -#define UCSI_ENABLE_NTFY_CONNECTOR_CHANGE BIT(14) -#define UCSI_ENABLE_NTFY_ERROR BIT(15) -#define UCSI_ENABLE_NTFY_ALL 0xdbe7 - -/* Error information returned by PPM in response to GET_ERROR_STATUS command. */ -#define UCSI_ERROR_UNREGONIZED_CMD BIT(0) -#define UCSI_ERROR_INVALID_CON_NUM BIT(1) -#define UCSI_ERROR_INVALID_CMD_ARGUMENT BIT(2) -#define UCSI_ERROR_INCOMPATIBLE_PARTNER BIT(3) -#define UCSI_ERROR_CC_COMMUNICATION_ERR BIT(4) -#define UCSI_ERROR_DEAD_BATTERY BIT(5) -#define UCSI_ERROR_CONTRACT_NEGOTIATION_FAIL BIT(6) - -/* Data structure filled by PPM in response to GET_CAPABILITY command. */ -struct ucsi_capability { - u32 attributes; -#define UCSI_CAP_ATTR_DISABLE_STATE BIT(0) -#define UCSI_CAP_ATTR_BATTERY_CHARGING BIT(1) -#define UCSI_CAP_ATTR_USB_PD BIT(2) -#define UCSI_CAP_ATTR_TYPEC_CURRENT BIT(6) -#define UCSI_CAP_ATTR_POWER_AC_SUPPLY BIT(8) -#define UCSI_CAP_ATTR_POWER_OTHER BIT(10) -#define UCSI_CAP_ATTR_POWER_VBUS BIT(14) - u8 num_connectors; - u32 features:24; -#define UCSI_CAP_SET_UOM BIT(0) -#define UCSI_CAP_SET_PDM BIT(1) -#define UCSI_CAP_ALT_MODE_DETAILS BIT(2) -#define UCSI_CAP_ALT_MODE_OVERRIDE BIT(3) -#define UCSI_CAP_PDO_DETAILS BIT(4) -#define UCSI_CAP_CABLE_DETAILS BIT(5) -#define UCSI_CAP_EXT_SUPPLY_NOTIFICATIONS BIT(6) -#define UCSI_CAP_PD_RESET BIT(7) - u8 num_alt_modes; - u8 RESERVED; - u16 bc_version; - u16 pd_version; - u16 typec_version; -} __packed; - -/* Data structure filled by PPM in response to GET_CONNECTOR_CAPABILITY cmd. */ -struct ucsi_connector_capability { - u8 op_mode; -#define UCSI_CONCAP_OPMODE_DFP BIT(0) -#define UCSI_CONCAP_OPMODE_UFP BIT(1) -#define UCSI_CONCAP_OPMODE_DRP BIT(2) -#define UCSI_CONCAP_OPMODE_AUDIO_ACCESSORY BIT(3) -#define UCSI_CONCAP_OPMODE_DEBUG_ACCESSORY BIT(4) -#define UCSI_CONCAP_OPMODE_USB2 BIT(5) -#define UCSI_CONCAP_OPMODE_USB3 BIT(6) -#define UCSI_CONCAP_OPMODE_ALT_MODE BIT(7) - u8 provider:1; - u8 consumer:1; -} __packed; - -/* Data structure filled by PPM in response to GET_CABLE_PROPERTY command. */ -struct ucsi_cable_property { - u16 speed_supported; - u8 current_capability; - u8 vbus_in_cable:1; - u8 active_cable:1; - u8 directionality:1; - u8 plug_type:2; -#define UCSI_CABLE_PROPERTY_PLUG_TYPE_A 0 -#define UCSI_CABLE_PROPERTY_PLUG_TYPE_B 1 -#define UCSI_CABLE_PROPERTY_PLUG_TYPE_C 2 -#define UCSI_CABLE_PROPERTY_PLUG_OTHER 3 - u8 mode_support:1; - u8 RESERVED_2:2; - u8 latency:4; - u8 RESERVED_4:4; -} __packed; - -/* Data structure filled by PPM in response to GET_CONNECTOR_STATUS command. */ -struct ucsi_connector_status { - u16 change; -#define UCSI_CONSTAT_EXT_SUPPLY_CHANGE BIT(1) -#define UCSI_CONSTAT_POWER_OPMODE_CHANGE BIT(2) -#define UCSI_CONSTAT_PDOS_CHANGE BIT(5) -#define UCSI_CONSTAT_POWER_LEVEL_CHANGE BIT(6) -#define UCSI_CONSTAT_PD_RESET_COMPLETE BIT(7) -#define UCSI_CONSTAT_CAM_CHANGE BIT(8) -#define UCSI_CONSTAT_BC_CHANGE BIT(9) -#define UCSI_CONSTAT_PARTNER_CHANGE BIT(11) -#define UCSI_CONSTAT_POWER_DIR_CHANGE BIT(12) -#define UCSI_CONSTAT_CONNECT_CHANGE BIT(14) -#define UCSI_CONSTAT_ERROR BIT(15) - u16 pwr_op_mode:3; -#define UCSI_CONSTAT_PWR_OPMODE_NONE 0 -#define UCSI_CONSTAT_PWR_OPMODE_DEFAULT 1 -#define UCSI_CONSTAT_PWR_OPMODE_BC 2 -#define UCSI_CONSTAT_PWR_OPMODE_PD 3 -#define UCSI_CONSTAT_PWR_OPMODE_TYPEC1_3 4 -#define UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0 5 - u16 connected:1; - u16 pwr_dir:1; - u16 partner_flags:8; -#define UCSI_CONSTAT_PARTNER_FLAG_USB BIT(0) -#define UCSI_CONSTAT_PARTNER_FLAG_ALT_MODE BIT(1) - u16 partner_type:3; -#define UCSI_CONSTAT_PARTNER_TYPE_DFP 1 -#define UCSI_CONSTAT_PARTNER_TYPE_UFP 2 -#define UCSI_CONSTAT_PARTNER_TYPE_CABLE_NO_UFP 3 /* Powered Cable */ -#define UCSI_CONSTAT_PARTNER_TYPE_CABLE_AND_UFP 4 /* Powered Cable */ -#define UCSI_CONSTAT_PARTNER_TYPE_DEBUG 5 -#define UCSI_CONSTAT_PARTNER_TYPE_AUDIO 6 - u32 request_data_obj; - u8 bc_status:2; -#define UCSI_CONSTAT_BC_NOT_CHARGING 0 -#define UCSI_CONSTAT_BC_NOMINAL_CHARGING 1 -#define UCSI_CONSTAT_BC_SLOW_CHARGING 2 -#define UCSI_CONSTAT_BC_TRICKLE_CHARGING 3 - u8 provider_cap_limit_reason:4; -#define UCSI_CONSTAT_CAP_PWR_LOWERED 0 -#define UCSI_CONSTAT_CAP_PWR_BUDGET_LIMIT 1 - u8 RESERVED:2; -} __packed; - -/* -------------------------------------------------------------------------- */ - diff --git a/drivers/usb/typec/ucsi/Kconfig b/drivers/usb/typec/ucsi/Kconfig index da4c5c3d8870..d0c31cee4720 100644 --- a/drivers/usb/typec/ucsi/Kconfig +++ b/drivers/usb/typec/ucsi/Kconfig @@ -21,3 +21,19 @@ config TYPEC_UCSI To compile the driver as a module, choose M here: the module will be called typec_ucsi. + +if TYPEC_UCSI + +config UCSI_ACPI + tristate "UCSI ACPI Interface Driver" + depends on ACPI + help + This driver enables UCSI support on platforms that expose UCSI + interface as ACPI device. On new Intel Atom based platforms starting + from Broxton SoCs and Core platforms stating from Skylake, UCSI is an + ACPI enumerated device. + + To compile the driver as a module, choose M here: the module will be + called ucsi_acpi + +endif diff --git a/drivers/usb/typec/ucsi/Makefile b/drivers/usb/typec/ucsi/Makefile index 87dd6ee6c9f3..8372fc22f9b3 100644 --- a/drivers/usb/typec/ucsi/Makefile +++ b/drivers/usb/typec/ucsi/Makefile @@ -5,3 +5,5 @@ obj-$(CONFIG_TYPEC_UCSI) += typec_ucsi.o typec_ucsi-y := ucsi.o typec_ucsi-$(CONFIG_FTRACE) += trace.o + +obj-$(CONFIG_UCSI_ACPI) += ucsi_acpi.o diff --git a/drivers/usb/typec/ucsi/ucsi_acpi.c b/drivers/usb/typec/ucsi/ucsi_acpi.c new file mode 100644 index 000000000000..3fb2e48e1c91 --- /dev/null +++ b/drivers/usb/typec/ucsi/ucsi_acpi.c @@ -0,0 +1,158 @@ +/* + * UCSI ACPI driver + * + * Copyright (C) 2017, Intel Corporation + * Author: Heikki Krogerus + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include + +#include "ucsi.h" + +#define UCSI_DSM_UUID "6f8398c2-7ca4-11e4-ad36-631042b5008f" +#define UCSI_DSM_FUNC_WRITE 1 +#define UCSI_DSM_FUNC_READ 2 + +struct ucsi_acpi { + struct device *dev; + struct ucsi *ucsi; + struct ucsi_ppm ppm; + uuid_le uuid; +}; + +static int ucsi_acpi_dsm(struct ucsi_acpi *ua, int func) +{ + union acpi_object *obj; + + obj = acpi_evaluate_dsm(ACPI_HANDLE(ua->dev), ua->uuid.b, 1, func, + NULL); + if (!obj) { + dev_err(ua->dev, "%s: failed to evaluate _DSM %d\n", + __func__, func); + return -EIO; + } + + ACPI_FREE(obj); + return 0; +} + +static int ucsi_acpi_cmd(struct ucsi_ppm *ppm, struct ucsi_control *ctrl) +{ + struct ucsi_acpi *ua = container_of(ppm, struct ucsi_acpi, ppm); + + ppm->data->ctrl.raw_cmd = ctrl->raw_cmd; + + return ucsi_acpi_dsm(ua, UCSI_DSM_FUNC_WRITE); +} + +static int ucsi_acpi_sync(struct ucsi_ppm *ppm) +{ + struct ucsi_acpi *ua = container_of(ppm, struct ucsi_acpi, ppm); + + return ucsi_acpi_dsm(ua, UCSI_DSM_FUNC_READ); +} + +static void ucsi_acpi_notify(acpi_handle handle, u32 event, void *data) +{ + struct ucsi_acpi *ua = data; + + ucsi_notify(ua->ucsi); +} + +static int ucsi_acpi_probe(struct platform_device *pdev) +{ + struct ucsi_acpi *ua; + struct resource *res; + acpi_status status; + int ret; + + ua = devm_kzalloc(&pdev->dev, sizeof(*ua), GFP_KERNEL); + if (!ua) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "missing memory resource\n"); + return -ENODEV; + } + + /* + * NOTE: The memory region for the data structures is used also in an + * operation region, which means ACPI has already reserved it. Therefore + * it can not be requested here, and we can not use + * devm_ioremap_resource(). + */ + ua->ppm.data = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!ua->ppm.data) + return -ENOMEM; + + if (!ua->ppm.data->version) + return -ENODEV; + + ret = uuid_le_to_bin(UCSI_DSM_UUID, &ua->uuid); + if (ret) + return ret; + + ua->ppm.cmd = ucsi_acpi_cmd; + ua->ppm.sync = ucsi_acpi_sync; + ua->dev = &pdev->dev; + + status = acpi_install_notify_handler(ACPI_HANDLE(&pdev->dev), + ACPI_DEVICE_NOTIFY, + ucsi_acpi_notify, ua); + if (ACPI_FAILURE(status)) { + dev_err(&pdev->dev, "failed to install notify handler\n"); + return -ENODEV; + } + + ua->ucsi = ucsi_register_ppm(&pdev->dev, &ua->ppm); + if (IS_ERR(ua->ucsi)) { + acpi_remove_notify_handler(ACPI_HANDLE(&pdev->dev), + ACPI_DEVICE_NOTIFY, + ucsi_acpi_notify); + return PTR_ERR(ua->ucsi); + } + + platform_set_drvdata(pdev, ua); + + return 0; +} + +static int ucsi_acpi_remove(struct platform_device *pdev) +{ + struct ucsi_acpi *ua = platform_get_drvdata(pdev); + + ucsi_unregister_ppm(ua->ucsi); + + acpi_remove_notify_handler(ACPI_HANDLE(&pdev->dev), ACPI_DEVICE_NOTIFY, + ucsi_acpi_notify); + + return 0; +} + +static const struct acpi_device_id ucsi_acpi_match[] = { + { "PNP0CA0", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, ucsi_acpi_match); + +static struct platform_driver ucsi_acpi_platform_driver = { + .driver = { + .name = "ucsi_acpi", + .acpi_match_table = ACPI_PTR(ucsi_acpi_match), + }, + .probe = ucsi_acpi_probe, + .remove = ucsi_acpi_remove, +}; + +module_platform_driver(ucsi_acpi_platform_driver); + +MODULE_AUTHOR("Heikki Krogerus "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("UCSI ACPI driver"); -- cgit v1.2.3 From 80dc6e1cd85fc09dde37b262f1beb9f953e215e2 Mon Sep 17 00:00:00 2001 From: Rafał Miłecki Date: Thu, 8 Jun 2017 18:08:31 +0200 Subject: dt-bindings: leds: document new trigger-sources property MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some LEDs can be related to a specific device(s) described in the DT. This property allows specifying such relations. E.g. USB LED should usually be used to indicate some USB port(s) state. Please note this binding is designed to be generic and not influenced by any operating system design. Linux developers may find "trigger" part a bit confusing since in Linux triggers are separated drivers. It shouldn't define the binding though (we shouldn't add an extra level of indirection). Signed-off-by: Rafał Miłecki Reviewed-by: Rob Herring Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/leds/common.txt | 35 +++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/leds/common.txt b/Documentation/devicetree/bindings/leds/common.txt index 24b656014089..1d4afe9644b6 100644 --- a/Documentation/devicetree/bindings/leds/common.txt +++ b/Documentation/devicetree/bindings/leds/common.txt @@ -1,4 +1,4 @@ -Common leds properties. +* Common leds properties. LED and flash LED devices provide the same basic functionality as current regulators, but extended with LED and flash LED specific features like @@ -49,6 +49,22 @@ Optional properties for child nodes: - panic-indicator : This property specifies that the LED should be used, if at all possible, as a panic indicator. +- trigger-sources : List of devices which should be used as a source triggering + this LED activity. Some LEDs can be related to a specific + device and should somehow indicate its state. E.g. USB 2.0 + LED may react to device(s) in a USB 2.0 port(s). + Another common example is switch or router with multiple + Ethernet ports each of them having its own LED assigned + (assuming they are not hardwired). In such cases this + property should contain phandle(s) of related source + device(s). + In many cases LED can be related to more than one device + (e.g. one USB LED vs. multiple USB ports). Each source + should be represented by a node in the device tree and be + referenced by a phandle and a set of phandle arguments. A + length of arguments should be specified by the + #trigger-source-cells property in the source node. + Required properties for flash LED child nodes: - flash-max-microamp : Maximum flash LED supply current in microamperes. - flash-max-timeout-us : Maximum timeout in microseconds after which the flash @@ -59,7 +75,17 @@ property can be omitted. For controllers that have no configurable timeout the flash-max-timeout-us property can be omitted. -Examples: +* Trigger source providers + +Each trigger source should be represented by a device tree node. It may be e.g. +a USB port or an Ethernet device. + +Required properties for trigger source: +- #trigger-source-cells : Number of cells in a source trigger. Typically 0 for + nodes of simple trigger sources (e.g. a specific USB + port). + +* Examples gpio-leds { compatible = "gpio-leds"; @@ -69,6 +95,11 @@ gpio-leds { linux,default-trigger = "heartbeat"; gpios = <&gpio0 0 GPIO_ACTIVE_HIGH>; }; + + usb { + gpios = <&gpio0 1 GPIO_ACTIVE_HIGH>; + trigger-sources = <&ohci_port1>, <&ehci_port1>; + }; }; max77693-led { -- cgit v1.2.3 From 4f04c210d031667e503d6538a72345a36f3b5d71 Mon Sep 17 00:00:00 2001 From: Rafał Miłecki Date: Thu, 8 Jun 2017 18:08:32 +0200 Subject: usb: core: read USB ports from DT in the usbport LED trigger driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This uses DT info to read relation description of LEDs and USB ports. If DT has properly described LEDs, trigger will know when to turn them on. Signed-off-by: Rafał Miłecki Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/ledtrig-usbport.c | 56 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/drivers/usb/core/ledtrig-usbport.c b/drivers/usb/core/ledtrig-usbport.c index 1713248ab15a..16c19a31dad1 100644 --- a/drivers/usb/core/ledtrig-usbport.c +++ b/drivers/usb/core/ledtrig-usbport.c @@ -11,8 +11,10 @@ #include #include #include +#include #include #include +#include struct usbport_trig_data { struct led_classdev *led_cdev; @@ -123,6 +125,57 @@ static const struct attribute_group ports_group = { * Adding & removing ports ***************************************/ +/** + * usbport_trig_port_observed - Check if port should be observed + */ +static bool usbport_trig_port_observed(struct usbport_trig_data *usbport_data, + struct usb_device *usb_dev, int port1) +{ + struct device *dev = usbport_data->led_cdev->dev; + struct device_node *led_np = dev->of_node; + struct of_phandle_args args; + struct device_node *port_np; + int count, i; + + if (!led_np) + return false; + + /* Get node of port being added */ + port_np = usb_of_get_child_node(usb_dev->dev.of_node, port1); + if (!port_np) + return false; + + /* Amount of trigger sources for this LED */ + count = of_count_phandle_with_args(led_np, "trigger-sources", + "#trigger-source-cells"); + if (count < 0) { + dev_warn(dev, "Failed to get trigger sources for %s\n", + led_np->full_name); + return false; + } + + /* Check list of sources for this specific port */ + for (i = 0; i < count; i++) { + int err; + + err = of_parse_phandle_with_args(led_np, "trigger-sources", + "#trigger-source-cells", i, + &args); + if (err) { + dev_err(dev, "Failed to get trigger source phandle at index %d: %d\n", + i, err); + continue; + } + + of_node_put(args.np); + + if (args.np == port_np) + return true; + } + + return false; +} + static int usbport_trig_add_port(struct usbport_trig_data *usbport_data, struct usb_device *usb_dev, const char *hub_name, int portnum) @@ -141,6 +194,8 @@ static int usbport_trig_add_port(struct usbport_trig_data *usbport_data, port->data = usbport_data; port->hub = usb_dev; port->portnum = portnum; + port->observed = usbport_trig_port_observed(usbport_data, usb_dev, + portnum); len = strlen(hub_name) + 8; port->port_name = kzalloc(len, GFP_KERNEL); @@ -255,6 +310,7 @@ static void usbport_trig_activate(struct led_classdev *led_cdev) if (err) goto err_free; usb_for_each_dev(usbport_data, usbport_trig_add_usb_dev_ports); + usbport_trig_update_count(usbport_data); /* Notifications */ usbport_data->nb.notifier_call = usbport_trig_notify, -- cgit v1.2.3 From f0d657e80d788c43ff4a5dbeb47d2ad60059f1c7 Mon Sep 17 00:00:00 2001 From: Yuyang Du Date: Tue, 27 Jun 2017 09:44:26 -0600 Subject: usbip: Fix uninitialized variable bug in vhci The patch 03cd00d538a6: "usbip: vhci-hcd: Set the vhci structure up to work" introduced a bug which uses a vairable without initialization in error handling code. Fix it. Reported-by: Dan Carpenter Signed-off-by: Yuyang Du Acked-by: Shuah Khan Signed-off-by: Greg Kroah-Hartman --- drivers/usb/usbip/vhci_hcd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c index 64c38603df7b..2c4b2fd40406 100644 --- a/drivers/usb/usbip/vhci_hcd.c +++ b/drivers/usb/usbip/vhci_hcd.c @@ -1301,7 +1301,7 @@ static struct hc_driver vhci_hc_driver = { static int vhci_hcd_probe(struct platform_device *pdev) { - struct vhci *vhci; + struct vhci *vhci = *((void **)dev_get_platdata(&pdev->dev)); struct usb_hcd *hcd_hs; struct usb_hcd *hcd_ss; int ret; -- cgit v1.2.3 From fd90f73a9925f248d696bde1cfc836d9fda5570d Mon Sep 17 00:00:00 2001 From: Jeremie Rapin Date: Wed, 28 Jun 2017 18:23:25 +0200 Subject: USB: serial: cp210x: add ID for CEL EM3588 USB ZigBee stick Added the USB serial device ID for the CEL ZigBee EM3588 radio stick. Signed-off-by: Jeremie Rapin Cc: stable Acked-by: Johan Hovold Signed-off-by: Greg Kroah-Hartman --- drivers/usb/serial/cp210x.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index 0c55e7f64269..f64e914a8985 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -141,6 +141,7 @@ static const struct usb_device_id id_table[] = { { USB_DEVICE(0x10C4, 0x8977) }, /* CEL MeshWorks DevKit Device */ { USB_DEVICE(0x10C4, 0x8998) }, /* KCF Technologies PRN */ { USB_DEVICE(0x10C4, 0x8A2A) }, /* HubZ dual ZigBee and Z-Wave dongle */ + { USB_DEVICE(0x10C4, 0x8A5E) }, /* CEL EM3588 ZigBee USB Stick Long Range */ { USB_DEVICE(0x10C4, 0xEA60) }, /* Silicon Labs factory default */ { USB_DEVICE(0x10C4, 0xEA61) }, /* Silicon Labs factory default */ { USB_DEVICE(0x10C4, 0xEA70) }, /* Silicon Labs factory default */ -- cgit v1.2.3 From af06d0094fa7fc2613294d409128671856c5471d Mon Sep 17 00:00:00 2001 From: Arvind Yadav Date: Wed, 28 Jun 2017 10:21:28 +0530 Subject: usb: host: ohci-pxa27x: Handle return value of clk_prepare_enable clk_prepare_enable() can fail here and we must check its return value. Signed-off-by: Arvind Yadav Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ohci-pxa27x.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/usb/host/ohci-pxa27x.c b/drivers/usb/host/ohci-pxa27x.c index 79efde8f21e0..21c010ffb03c 100644 --- a/drivers/usb/host/ohci-pxa27x.c +++ b/drivers/usb/host/ohci-pxa27x.c @@ -274,14 +274,16 @@ extern void pxa27x_clear_otgph(void); static int pxa27x_start_hc(struct pxa27x_ohci *pxa_ohci, struct device *dev) { - int retval = 0; + int retval; struct pxaohci_platform_data *inf; uint32_t uhchr; struct usb_hcd *hcd = dev_get_drvdata(dev); inf = dev_get_platdata(dev); - clk_prepare_enable(pxa_ohci->clk); + retval = clk_prepare_enable(pxa_ohci->clk); + if (retval) + return retval; pxa27x_reset_hc(pxa_ohci); @@ -296,8 +298,10 @@ static int pxa27x_start_hc(struct pxa27x_ohci *pxa_ohci, struct device *dev) if (inf->init) retval = inf->init(dev); - if (retval < 0) + if (retval < 0) { + clk_disable_unprepare(pxa_ohci->clk); return retval; + } if (cpu_is_pxa3xx()) pxa3xx_u2d_start_hc(&hcd->self); -- cgit v1.2.3 From c94dc34f771a25b8c3e0955147fdc4f5e3d79908 Mon Sep 17 00:00:00 2001 From: Eugene Korenevsky Date: Sun, 25 Jun 2017 11:08:14 +0300 Subject: USB hub_probe: rework ugly goto-into-compound-statement Rework smelling code (goto inside compound statement). Perhaps this is legacy. Anyway such code is not appropriate for Linux kernel. Signed-off-by: Eugene Korenevsky Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 59e0418a5ed5..6e6797d145dd 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -1661,10 +1661,28 @@ static void hub_disconnect(struct usb_interface *intf) kref_put(&hub->kref, hub_release); } +static bool hub_descriptor_is_sane(struct usb_host_interface *desc) +{ + /* Some hubs have a subclass of 1, which AFAICT according to the */ + /* specs is not defined, but it works */ + if (desc->desc.bInterfaceSubClass != 0 && + desc->desc.bInterfaceSubClass != 1) + return false; + + /* Multiple endpoints? What kind of mutant ninja-hub is this? */ + if (desc->desc.bNumEndpoints != 1) + return false; + + /* If the first endpoint is not interrupt IN, we'd better punt! */ + if (!usb_endpoint_is_int_in(&desc->endpoint[0].desc)) + return false; + + return true; +} + static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id) { struct usb_host_interface *desc; - struct usb_endpoint_descriptor *endpoint; struct usb_device *hdev; struct usb_hub *hub; @@ -1739,25 +1757,11 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id) } #endif - /* Some hubs have a subclass of 1, which AFAICT according to the */ - /* specs is not defined, but it works */ - if ((desc->desc.bInterfaceSubClass != 0) && - (desc->desc.bInterfaceSubClass != 1)) { -descriptor_error: + if (!hub_descriptor_is_sane(desc)) { dev_err(&intf->dev, "bad descriptor, ignoring hub\n"); return -EIO; } - /* Multiple endpoints? What kind of mutant ninja-hub is this? */ - if (desc->desc.bNumEndpoints != 1) - goto descriptor_error; - - endpoint = &desc->endpoint[0].desc; - - /* If it's not an interrupt in endpoint, we'd better punt! */ - if (!usb_endpoint_is_int_in(endpoint)) - goto descriptor_error; - /* We found a hub */ dev_info(&intf->dev, "USB hub found\n"); @@ -1784,7 +1788,7 @@ descriptor_error: if (id->driver_info & HUB_QUIRK_CHECK_PORT_AUTOSUSPEND) hub->quirk_check_port_auto_suspend = 1; - if (hub_configure(hub, endpoint) >= 0) + if (hub_configure(hub, &desc->endpoint[0].desc) >= 0) return 0; hub_disconnect(intf); -- cgit v1.2.3 From 6836796de4019944f4ba4c99a360e8250fd2e735 Mon Sep 17 00:00:00 2001 From: Devin Heitmueller Date: Tue, 27 Jun 2017 13:08:51 -0400 Subject: Add USB quirk for HVR-950q to avoid intermittent device resets The USB core and sysfs will attempt to enumerate certain parameters which are unsupported by the au0828 - causing inconsistent behavior and sometimes causing the chip to reset. Avoid making these calls. This problem manifested as intermittent cases where the au8522 would be reset on analog video startup, in particular when starting up ALSA audio streaming in parallel - the sysfs entries created by snd-usb-audio on streaming startup would result in unsupported control messages being sent during tuning which would put the chip into an unknown state. Signed-off-by: Devin Heitmueller Cc: stable Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/quirks.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c index 96b21b0dac1e..3116edfcdc18 100644 --- a/drivers/usb/core/quirks.c +++ b/drivers/usb/core/quirks.c @@ -223,6 +223,10 @@ static const struct usb_device_id usb_quirk_list[] = { /* Blackmagic Design UltraStudio SDI */ { USB_DEVICE(0x1edb, 0xbd4f), .driver_info = USB_QUIRK_NO_LPM }, + /* Hauppauge HVR-950q */ + { USB_DEVICE(0x2040, 0x7200), .driver_info = + USB_QUIRK_CONFIG_INTF_STRINGS }, + /* INTEL VALUE SSD */ { USB_DEVICE(0x8086, 0xf1a5), .driver_info = USB_QUIRK_RESET_RESUME }, -- cgit v1.2.3