Appears to be an issue with the duration required to finish the animation, the same issue is present with smoothScrollBy(int distance, int duration), at cursory glance, smoothScrollToPosition() is a friendly wrapper around smoothScrollBy() that does a lot of the legwork. smoothScrollBy() in turn is faking a "fling gesture", as if a user had made the motion.
smoothScrollBy really just posts the fling runnable that continues to repost itself till the duration runs out. Meaning that it simply computes the scroll offset required based on the offset it previously decided to move to, hence if duration runs out before it gets to the target offset, it stops at the last offset calculated. (Rather than all of sudden jumping to the target offset, which is perhaps more jarring as it would not be animated).
The difficulty for the Android guys is determining how much to move by each run() call to reach the required offset because ListView cells (children) are entirely dynamic in height, so they can't just do a simple distance calculation as the non-visible children's height are unknown to them. It is the same reason the Android scrollbar can fluctuate in size as you scroll, it has to take a best guess at how big it should be based on what it is currently seeing.
Anyway that doesn't help you solve it but some one might find it interesting :)
If you know you have static cell heights however, you can write your own method to calculate the distance and duration to pass to smoothScrollBy() yourself and have a static amount of time to move X distance. If you don't, it will have to suffice to use the solution bigstones posted, which really is working because of the high SCROLL_DURATION of 1000ms. You can take the ICS version and change this attribute as well, rather than using the 2.2 version, which is not the root cause.
You can also adapt those runnables with your own custom algorithm, it shouldn't be too difficult to tweak things.